From 9137d048a7dbc224eb422b8b19b2fbbfa7569453 Mon Sep 17 00:00:00 2001 From: Alkorith <101947793+Alkorith@users.noreply.github.com> Date: Mon, 11 Sep 2023 19:08:46 -0500 Subject: [PATCH 1/4] chore: Remove trailing whitespace and use unix line endings --- .../playerInfoFrame/searchfilter.lua | 12 +- .../ScreenSelectMusic decorations/filters.lua | 1312 ++++++++--------- src/Etterna/Singletons/ThemeManager.cpp | 2 +- 3 files changed, 663 insertions(+), 663 deletions(-) diff --git a/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua b/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua index 7ec33f88fa..ab1bf13c84 100644 --- a/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua +++ b/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua @@ -189,13 +189,13 @@ local function upperSection() local foundtitle = "" local foundsubtitle = "" local foundgroup = "" - + if artistpos ~= nil or authorpos ~= nil or titlepos ~= nil or subtitlepos ~= nil or mapperpos ~= nil or charterpos ~= nil or stepperpos ~= nil or grouppos ~= nil or packpos ~= nil then - + if artistpos ~= nil then local strend = input:find("[;]", artistpos+1) if strend == nil then strend = #input else strend = strend-1 end @@ -251,7 +251,7 @@ local function upperSection() end -- you know what im just going to update all the other entry fields based on this one - + end, -- "Title Search" function(input) @@ -436,7 +436,7 @@ local function upperSection() end if searchentry.Group ~= "" then finalstr = finalstr .. "group="..searchentry.Group..";" - end + end end self:GetChild("RowFrame_1"):GetChild("RowInput"):settext(finalstr) end @@ -1066,7 +1066,7 @@ local function lowerSection() else return end - + minrate = clamp(clamp(minrate + increment, 0.7, FILTERMAN:GetMaxFilterRate()), 0.7, 3) FILTERMAN:SetMinFilterRate(minrate) self:playcommand("UpdateText") @@ -1218,4 +1218,4 @@ t[#t+1] = Def.Quad { t[#t+1] = upperSection() t[#t+1] = lowerSection() -return t \ No newline at end of file +return t diff --git a/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua b/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua index 372b8ce5a7..c8ad8f8e60 100644 --- a/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua +++ b/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua @@ -1,656 +1,656 @@ -local numbershers = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"} -local frameX = 10 -local frameY = 45 -local active = false -local whee -local spacingY = 20 -local textzoom = 0.35 -local ActiveSS = 0 -local SSQuery = {} -SSQuery[0] = {} -SSQuery[1] = {} -local frameWidth = capWideScale(360, 400) -local frameHeight = 350 -local offsetX = 10 -local offsetY = 20 -local activebound = 0 -for i = 1, #ms.SkillSets + 2 do - SSQuery[0][i] = "0" - SSQuery[1][i] = "0" -end -local numbersafterthedecimal = 0 - -local hoverAlpha = 0.6 -local instantSearch = themeConfig:get_data().global.InstantSearch - -local function FilterInput(event) - if event.type ~= "InputEventType_Release" and ActiveSS > 0 and active then - local shouldUpdate = false - if event.button == "Start" or event.button == "Back" then - ActiveSS = 0 - MESSAGEMAN:Broadcast("NumericInputEnded") - SCREENMAN:set_input_redirected(PLAYER_1, false) - return true - elseif event.DeviceInput.button == "DeviceButton_backspace" then - SSQuery[activebound][ActiveSS] = SSQuery[activebound][ActiveSS]:sub(1, -2) - shouldUpdate = true - elseif event.DeviceInput.button == "DeviceButton_delete" then - SSQuery[activebound][ActiveSS] = "" - shouldUpdate = true - else - for i = 1, #numbershers do - if event.DeviceInput.button == "DeviceButton_" .. numbershers[i] then - shouldUpdate = true - if SSQuery[activebound][ActiveSS] == "0" then - SSQuery[activebound][ActiveSS] = "" - end - SSQuery[activebound][ActiveSS] = SSQuery[activebound][ActiveSS] .. numbershers[i] - if (ActiveSS < #ms.SkillSets + 1 and #SSQuery[activebound][ActiveSS] > 2) or (ActiveSS < #ms.SkillSets + 2 and #SSQuery[activebound][ActiveSS] > 3) or #SSQuery[activebound][ActiveSS] > 5 then - SSQuery[activebound][ActiveSS] = numbershers[i] - end - end - end - end - if SSQuery[activebound][ActiveSS] == "" then - shouldUpdate = true - SSQuery[activebound][ActiveSS] = "0" - end - if shouldUpdate then - local num = 0 - if ActiveSS == #ms.SkillSets+2 then - local q = SSQuery[activebound][ActiveSS] - numbersafterthedecimal = 0 - if #q > 2 then - numbersafterthedecimal = #q-2 - local n = tonumber(q) / (10 ^ (#q-2)) - n = notShit.round(n, numbersafterthedecimal) - num = n - else - num = tonumber(q) - end - else - num = tonumber(SSQuery[activebound][ActiveSS]) - end - FILTERMAN:SetSSFilter(num, ActiveSS, activebound) - whee:SongSearch("") -- stupid workaround? - MESSAGEMAN:Broadcast("UpdateFilter") - end - end -end - -local translated_info = { - Mode = THEME:GetString("TabFilter", "Mode"), - HighestOnly = THEME:GetString("TabFilter", "HighestOnly"), - HighestDifficultyOnly = THEME:GetString("TabFilter", "HighestDifficultyOnly"), - On = THEME:GetString("OptionNames", "On"), - Off = THEME:GetString("OptionNames", "Off"), - Matches = THEME:GetString("TabFilter", "Matches"), - CommonPackFilter = THEME:GetString("TabFilter", "CommonPackFilter"), - Length = THEME:GetString("TabFilter", "Length"), - BestPercent = "Best %", - AND = THEME:GetString("TabFilter", "AND"), - OR = THEME:GetString("TabFilter", "OR"), - ExplainStartInput = THEME:GetString("TabFilter", "ExplainStartInput"), - ExplainCancelInput = THEME:GetString("TabFilter", "ExplainCancelInput"), - ExplainGrey = THEME:GetString("TabFilter", "ExplainGrey"), - ExplainBounds = THEME:GetString("TabFilter", "ExplainBounds"), - ExplainHighest = THEME:GetString("TabFilter", "ExplainHighest"), - ExplainHighestDifficulty = THEME:GetString("TabFilter", "ExplainHighestDifficulty"), - MaxRate = THEME:GetString("TabFilter", "MaxRate"), - Title = THEME:GetString("TabFilter", "Title"), - MinRate = THEME:GetString("TabFilter", "MinRate"), -} - -local f = Def.ActorFrame { - BeginCommand = function(self) - self:halign(0):visible(false) - whee = SCREENMAN:GetTopScreen():GetMusicWheel() - SCREENMAN:GetTopScreen():AddInputCallback(FilterInput) - self:queuecommand("Set") - end, - OffCommand = function(self) - self:bouncebegin(0.2):xy(-500, frameY):diffusealpha(0) - self:sleep(0.04):queuecommand("Invis") - end, - InvisCommand= function(self) - self:visible(false) - end, - OnCommand = function(self) - self:bouncebegin(0.2):xy(frameX, frameY):diffusealpha(1) - end, - SetCommand = function(self) - self:finishtweening() - if getTabIndex() == 5 then - self:visible(true) - self:queuecommand("On") - active = true - else - MESSAGEMAN:Broadcast("NumericInputEnded") - self:queuecommand("Off") - active = false - end - end, - TabChangedMessageCommand = function(self) - self:queuecommand("Set") - end, - MouseRightClickMessageCommand = function(self) - ActiveSS = 0 - MESSAGEMAN:Broadcast("NumericInputEnded") - MESSAGEMAN:Broadcast("UpdateFilter") - SCREENMAN:set_input_redirected(PLAYER_1, false) - end, - Def.Quad { - InitCommand = function(self) - self:zoomto(frameWidth, frameHeight):halign(0):valign(0):diffuse(getMainColor("tabs")) - end - }, - Def.Quad { - InitCommand = function(self) - self:zoomto(frameWidth, offsetY):halign(0):valign(0):diffuse(getMainColor("frames")):diffusealpha(0.5) - end - }, - LoadFont("Common Normal") .. { - InitCommand = function(self) - self:xy(5, offsetY - 9):zoom(0.6):halign(0):settext(translated_info["Title"]) - self:diffuse(Saturation(getMainColor("positive"), 0.1)) - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:xy(frameX, frameY -17):zoom(0.3):halign(0) - self:settext(translated_info["ExplainStartInput"]) - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:xy(frameX, frameY + 20 -17):zoom(0.3):halign(0) - self:settext(translated_info["ExplainCancelInput"]) - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:xy(frameX, frameY + 40 -17):zoom(0.3):halign(0) - self:settext(translated_info["ExplainGrey"]) - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:xy(frameX, frameY + 60 -17):zoom(0.3):halign(0) - self:settext(translated_info["ExplainBounds"]) - end - }, - --[[ -- hiding extra unnecessary information - LoadFont("Common Large") .. { - InitCommand = function(self) - self:xy(frameX, frameY + 80 -17):zoom(0.3):halign(0) - self:settext(translated_info["ExplainHighest"]) - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:xy(frameX, frameY + 100 -17):zoom(0.3):halign(0) - self:settext(translated_info["ExplainHighestDifficulty"]) - end - }, - ]] - UIElements.TextToolTip(1, 1, "Common Large") ..{ - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175):zoom(textzoom):halign(0) - self:diffuse(getMainColor("positive")) - end, - SetCommand = function(self) - self:settextf("%s:%5.1fx", translated_info["MaxRate"], FILTERMAN:GetMaxFilterRate()) - end, - MaxFilterRateChangedMessageCommand = function(self) - self:queuecommand("Set") - end, - ResetFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - MouseOverCommand = function(self) - self:diffusealpha(hoverAlpha) - end, - MouseOutCommand = function(self) - self:diffusealpha(1) - end, - }, - UIElements.QuadButton(1, 1) .. { - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175):zoomto(130, 18):halign(0):diffusealpha(0) - end, - MouseDownCommand = function(self, params) - if params.event == "DeviceButton_left mouse button" and active then - FILTERMAN:SetMaxFilterRate(FILTERMAN:GetMaxFilterRate() + 0.1) - MESSAGEMAN:Broadcast("MaxFilterRateChanged") - whee:SongSearch("") - elseif params.event == "DeviceButton_right mouse button" and active then - FILTERMAN:SetMaxFilterRate(FILTERMAN:GetMaxFilterRate() - 0.1) - MESSAGEMAN:Broadcast("MaxFilterRateChanged") - whee:SongSearch("") - end - end, - }, - UIElements.TextToolTip(1, 1, "Common Large") ..{ - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY):zoom(textzoom):halign(0) - self:diffuse(getMainColor("positive")) - end, - SetCommand = function(self) - self:settextf("%s:%5.1fx", translated_info["MinRate"], FILTERMAN:GetMinFilterRate()) - end, - MaxFilterRateChangedMessageCommand = function(self) - self:queuecommand("Set") - end, - ResetFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - MouseOverCommand = function(self) - self:diffusealpha(hoverAlpha) - end, - MouseOutCommand = function(self) - self:diffusealpha(1) - end, - }, - UIElements.QuadButton(1, 1) .. { - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY):zoomto(130, 18):halign(0):diffusealpha(0) - end, - MouseDownCommand = function(self, params) - if params.event == "DeviceButton_left mouse button" and active then - FILTERMAN:SetMinFilterRate(FILTERMAN:GetMinFilterRate() + 0.1) - MESSAGEMAN:Broadcast("MaxFilterRateChanged") - whee:SongSearch("") - elseif params.event == "DeviceButton_right mouse button" and active then - FILTERMAN:SetMinFilterRate(FILTERMAN:GetMinFilterRate() - 0.1) - MESSAGEMAN:Broadcast("MaxFilterRateChanged") - whee:SongSearch("") - end - end, - }, - UIElements.TextToolTip(1, 1, "Common Large") ..{ - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 2):zoom(textzoom):halign(0) - self:diffuse(getMainColor("positive")) - end, - SetCommand = function(self) - if FILTERMAN:GetFilterMode() then - self:settextf("%s: %s", translated_info["Mode"], translated_info["AND"]) - else - self:settextf("%s: %s", translated_info["Mode"], translated_info["OR"]) - end - end, - FilterModeChangedMessageCommand = function(self) - self:queuecommand("Set") - end, - ResetFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - MouseOverCommand = function(self) - self:diffusealpha(hoverAlpha) - end, - MouseOutCommand = function(self) - self:diffusealpha(1) - end, - }, - UIElements.QuadButton(1, 1) .. { - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 2):zoomto(120, 18):halign(0):diffusealpha(0) - end, - MouseDownCommand = function(self, params) - if params.event == "DeviceButton_left mouse button" and active then - FILTERMAN:ToggleFilterMode() - MESSAGEMAN:Broadcast("FilterModeChanged") - whee:SongSearch("") - end - end - }, - UIElements.TextToolTip(1, 1, "Common Large") ..{ - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 3):zoom(textzoom):halign(0) - end, - SetCommand = function(self) - local translated = translated_info["HighestOnly"] - if FILTERMAN:GetHighestSkillsetsOnly() then - self:settextf("%s: %s", translated, translated_info["On"]):maxwidth(frameWidth / 2 / textzoom - 50) - else - self:settextf("%s: %s", translated, translated_info["Off"]):maxwidth(frameWidth / 2 / textzoom - 50) - end - if FILTERMAN:GetFilterMode() then - self:diffuse(1,1,1,0.2) - else - self:diffuse(getMainColor("positive")) - end - end, - FilterModeChangedMessageCommand = function(self) - self:queuecommand("Set") - end, - ResetFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - MouseOverCommand = function(self) - if not FILTERMAN:GetFilterMode() then - self:diffusealpha(hoverAlpha) - end - end, - MouseOutCommand = function(self) - if FILTERMAN:GetFilterMode() then - self:diffusealpha(0.2) - else - self:diffusealpha(1) - end - end, - }, - UIElements.QuadButton(1, 1) .. { - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 3):zoomto(180, 18):halign(0):diffusealpha(0) - end, - MouseDownCommand = function(self, params) - if params.event == "DeviceButton_left mouse button" and active and not FILTERMAN:GetFilterMode() then - FILTERMAN:ToggleHighestSkillsetsOnly() - MESSAGEMAN:Broadcast("FilterModeChanged") - whee:SongSearch("") - end - end - }, - UIElements.TextToolTip(1, 1, "Common Large") ..{ - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 4):zoom(textzoom):halign(0) - end, - SetCommand = function(self) - local translated = translated_info["HighestDifficultyOnly"] - if FILTERMAN:GetHighestDifficultyOnly() then - self:settextf("%s: %s", translated, translated_info["On"]):maxwidth(frameWidth / 2 / textzoom - 50) - else - self:settextf("%s: %s", translated, translated_info["Off"]):maxwidth(frameWidth / 2 / textzoom - 50) - end - if FILTERMAN:GetFilterMode() then - self:diffuse(1,1,1,0.2) - else - self:diffuse(getMainColor("positive")) - end - end, - FilterModeChangedMessageCommand = function(self) - self:queuecommand("Set") - end, - ResetFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - MouseOverCommand = function(self) - if not FILTERMAN:GetFilterMode() then - self:diffusealpha(hoverAlpha) - end - end, - MouseOutCommand = function(self) - if FILTERMAN:GetFilterMode() then - self:diffusealpha(0.2) - else - self:diffusealpha(1) - end - end, - }, - UIElements.QuadButton(1, 1) .. { - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 4):zoomto(180, 18):halign(0):diffusealpha(0) - end, - MouseDownCommand = function(self, params) - if params.event == "DeviceButton_left mouse button" and active and not FILTERMAN:GetFilterMode() then - FILTERMAN:ToggleHighestDifficultyOnly() - MESSAGEMAN:Broadcast("FilterModeChanged") - whee:SongSearch("") - end - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 6):zoom(textzoom):halign(0):settext("") - end, - FilterResultsMessageCommand = function(self, msg) - self:settextf("%s: %i/%i", translated_info["Matches"], msg.Matches, msg.Total) - end - }, - UIElements.TextToolTip(1, 1, "Common Large") .. { - BeginCommand = function(self) - self:xy(frameX + frameWidth / 2, 175 + spacingY * 7):zoom(textzoom):halign(0):maxwidth(300) - self.packlistFiltering = FILTERMAN:GetFilteringCommonPacks() - self.enabled = SCREENMAN:GetTopScreen():GetName() == "ScreenNetSelectMusic" - if not self.enabled then - self:visible(false) - end - self:queuecommand("Set") - end, - MouseDownCommand = function(self, params) - if self.enabled and params.event == "DeviceButton_left mouse button" and active then - self.packlistFiltering = whee:SetPackListFiltering(not self.packlistFiltering) - self:queuecommand("Set") - end - end, - SetCommand = function(self) - self:settextf("%s: %s", translated_info["CommonPackFilter"], (self.packlistFiltering and translated_info["On"] or translated_info["Off"])) - end, - FilterModeChangedMessageCommand = function(self) - self:queuecommand("Set") - end, - ResetFilterMessageCommand = function(self) - self:queuecommand("Set") - end - } -} - -local function CreateFilterInputBox(i) - local t = Def.ActorFrame { - InitCommand = function(self) - self:y(-17) - end, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:addx(10):addy(175 + (i - 1) * spacingY):halign(0):zoom(textzoom) - end, - SetCommand = function(self) - self:settext(i == (#ms.SkillSets + 1) and translated_info["Length"] or (i == (#ms.SkillSets + 2) and translated_info["BestPercent"] or ms.SkillSetsTranslated[i])) - end - }, - UIElements.QuadButton(1, 1) .. { - InitCommand = function(self) - self:addx(i == (#ms.SkillSets + 1) and 159 or (i == (#ms.SkillSets + 2) and 159 or 150)):addy(175 + (i - 1) * spacingY):zoomto( - i == (#ms.SkillSets + 1) and 27 or (i == (#ms.SkillSets + 2) and 27 or 18), - 18 - ):halign(1) - end, - MouseDownCommand = function(self, params) - if params.event == "DeviceButton_left mouse button" and active then - ActiveSS = i - activebound = 0 - MESSAGEMAN:Broadcast("NumericInputActive") - self:diffusealpha(0.1) - SCREENMAN:set_input_redirected(PLAYER_1, true) - end - end, - SetCommand = function(self) - if ActiveSS == i and activebound == 0 then - self:diffuse(color("#666666")) - else - self:diffuse(color("#000000")) - end - end, - UpdateFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - NumericInputEndedMessageCommand = function(self) - self:queuecommand("Set") - end, - NumericInputActiveMessageCommand = function(self) - self:queuecommand("Set") - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:addx(i == (#ms.SkillSets + 1) and 159 or (i == (#ms.SkillSets + 2) and 159 or 150)):addy(175 + (i - 1) * spacingY):halign(1):maxwidth(60):zoom( - textzoom - ) - end, - SetCommand = function(self) - local fval = notShit.round(FILTERMAN:GetSSFilter(i, 0), numbersafterthedecimal) -- lower bounds - local fmtstr = "" - if i == #ms.SkillSets+2 then - if numbersafterthedecimal > 0 then - fmtstr = "%5."..numbersafterthedecimal.."f" - else - fmtstr = "%02d." - end - else - fmtstr = "%d" - end - self:settextf(fmtstr, fval) - if fval <= 0 and ActiveSS ~= i then - self:diffuse(color("#666666")) - elseif activebound == 0 then - self:diffuse(color("#FFFFFF")) - end - end, - UpdateFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - NumericInputActiveMessageCommand = function(self) - self:queuecommand("Set") - end - }, - UIElements.QuadButton(1, 1) .. { - InitCommand = function(self) - self:addx(i == (#ms.SkillSets + 1) and 193 or (i == (#ms.SkillSets + 2) and 193 or 175)):addy(175 + (i - 1) * spacingY):zoomto( - i == (#ms.SkillSets + 1) and 27 or (i == (#ms.SkillSets + 2) and 27 or 18), - 18 - ):halign(1) - end, - MouseDownCommand = function(self, params) - if params.event == "DeviceButton_left mouse button" and active then - ActiveSS = i - activebound = 1 - MESSAGEMAN:Broadcast("NumericInputActive") - self:diffusealpha(0.1) - SCREENMAN:set_input_redirected(PLAYER_1, true) - end - end, - SetCommand = function(self) - if ActiveSS == i and activebound == 1 then - self:diffuse(color("#666666")) - else - self:diffuse(color("#000000")) - end - end, - UpdateFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - NumericInputEndedMessageCommand = function(self) - self:queuecommand("Set") - end, - NumericInputActiveMessageCommand = function(self) - self:queuecommand("Set") - end - }, - LoadFont("Common Large") .. { - InitCommand = function(self) - self:addx(i == (#ms.SkillSets + 1) and 193 or (i == (#ms.SkillSets + 2) and 193 or 175)):addy(175 + (i - 1) * spacingY):halign(1):maxwidth(60):zoom( - textzoom - ) - end, - SetCommand = function(self) - local fval = notShit.round(FILTERMAN:GetSSFilter(i, 1), numbersafterthedecimal) -- upper bounds - local fmtstr = "%5." - if i == #ms.SkillSets+2 then - if numbersafterthedecimal > 0 then - fmtstr = "%5."..numbersafterthedecimal.."f" - else - fmtstr = "%02d." - end - else - fmtstr = "%d" - end - self:settextf(fmtstr, fval) - if fval <= 0 and ActiveSS ~= i then - self:diffuse(color("#666666")) - elseif activebound == 1 then - self:diffuse(color("#FFFFFF")) - end - end, - UpdateFilterMessageCommand = function(self) - self:queuecommand("Set") - end, - NumericInputActiveMessageCommand = function(self) - self:queuecommand("Set") - end - } - } - return t -end - ---reset button -f[#f + 1] = UIElements.TextButton(1, 1, "Common Large") .. { - InitCommand = function(self) - self:xy(frameX + frameWidth - 150, frameY + 250 + spacingY * 2) - local txt = self:GetChild("Text") - local bg = self:GetChild("BG") - txt:zoom(0.35) - txt:settext(THEME:GetString("TabFilter", "Reset")) - txt:diffuse(getMainColor("positive")) - bg:zoomto(60, 20) - end, - RolloverUpdateCommand = function(self, params) - if params.update == "in" then - self:diffusealpha(hoverAlpha) - else - self:diffusealpha(1) - end - end, - ClickCommand = function(self, params) - if params.update ~= "OnMouseDown" then return end - if params.event == "DeviceButton_left mouse button" and active then - FILTERMAN:ResetAllFilters() - for i = 1, #ms.SkillSets + 2 do - SSQuery[0][i] = "0" - SSQuery[1][i] = "0" - end - numbersafterthedecimal = 0 - activebound = 0 - ActiveSS = 0 - MESSAGEMAN:Broadcast("UpdateFilter") - MESSAGEMAN:Broadcast("ResetFilter") - MESSAGEMAN:Broadcast("NumericInputEnded") - SCREENMAN:set_input_redirected(PLAYER_1, false) - whee:SongSearch("") - end - end, -} ---[[ --- apply button -f[#f + 1] = UIElements.TextButton(1, 1, "Common Large") .. { - InitCommand = function(self) - self:xy(frameX + frameWidth - 150, frameY + 250 + spacingY * -1) - local txt = self:GetChild("Text") - local bg = self:GetChild("BG") - txt:zoom(0.35) - txt:settext(THEME:GetString("TabFilter", "Apply")) - txt:diffuse(getMainColor("positive")) - bg:zoomto(60, 20) - end, - RolloverUpdateCommand = function(self, params) - if params.update == "in" then - self:diffusealpha(hoverAlpha) - else - self:diffusealpha(1) - end - end, - ClickCommand = function(self, params) - if params.update ~= "OnMouseDown" then return end - if params.event == "DeviceButton_left mouse button" and active then - MESSAGEMAN:Broadcast("NumericInputEnded") - SCREENMAN:set_input_redirected(PLAYER_1, false) - whee:SongSearch("") - end - end, -} -]] - -for i = 1, (#ms.SkillSets + 2) do - f[#f + 1] = CreateFilterInputBox(i) -end -return f +local numbershers = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"} +local frameX = 10 +local frameY = 45 +local active = false +local whee +local spacingY = 20 +local textzoom = 0.35 +local ActiveSS = 0 +local SSQuery = {} +SSQuery[0] = {} +SSQuery[1] = {} +local frameWidth = capWideScale(360, 400) +local frameHeight = 350 +local offsetX = 10 +local offsetY = 20 +local activebound = 0 +for i = 1, #ms.SkillSets + 2 do + SSQuery[0][i] = "0" + SSQuery[1][i] = "0" +end +local numbersafterthedecimal = 0 + +local hoverAlpha = 0.6 +local instantSearch = themeConfig:get_data().global.InstantSearch + +local function FilterInput(event) + if event.type ~= "InputEventType_Release" and ActiveSS > 0 and active then + local shouldUpdate = false + if event.button == "Start" or event.button == "Back" then + ActiveSS = 0 + MESSAGEMAN:Broadcast("NumericInputEnded") + SCREENMAN:set_input_redirected(PLAYER_1, false) + return true + elseif event.DeviceInput.button == "DeviceButton_backspace" then + SSQuery[activebound][ActiveSS] = SSQuery[activebound][ActiveSS]:sub(1, -2) + shouldUpdate = true + elseif event.DeviceInput.button == "DeviceButton_delete" then + SSQuery[activebound][ActiveSS] = "" + shouldUpdate = true + else + for i = 1, #numbershers do + if event.DeviceInput.button == "DeviceButton_" .. numbershers[i] then + shouldUpdate = true + if SSQuery[activebound][ActiveSS] == "0" then + SSQuery[activebound][ActiveSS] = "" + end + SSQuery[activebound][ActiveSS] = SSQuery[activebound][ActiveSS] .. numbershers[i] + if (ActiveSS < #ms.SkillSets + 1 and #SSQuery[activebound][ActiveSS] > 2) or (ActiveSS < #ms.SkillSets + 2 and #SSQuery[activebound][ActiveSS] > 3) or #SSQuery[activebound][ActiveSS] > 5 then + SSQuery[activebound][ActiveSS] = numbershers[i] + end + end + end + end + if SSQuery[activebound][ActiveSS] == "" then + shouldUpdate = true + SSQuery[activebound][ActiveSS] = "0" + end + if shouldUpdate then + local num = 0 + if ActiveSS == #ms.SkillSets+2 then + local q = SSQuery[activebound][ActiveSS] + numbersafterthedecimal = 0 + if #q > 2 then + numbersafterthedecimal = #q-2 + local n = tonumber(q) / (10 ^ (#q-2)) + n = notShit.round(n, numbersafterthedecimal) + num = n + else + num = tonumber(q) + end + else + num = tonumber(SSQuery[activebound][ActiveSS]) + end + FILTERMAN:SetSSFilter(num, ActiveSS, activebound) + whee:SongSearch("") -- stupid workaround? + MESSAGEMAN:Broadcast("UpdateFilter") + end + end +end + +local translated_info = { + Mode = THEME:GetString("TabFilter", "Mode"), + HighestOnly = THEME:GetString("TabFilter", "HighestOnly"), + HighestDifficultyOnly = THEME:GetString("TabFilter", "HighestDifficultyOnly"), + On = THEME:GetString("OptionNames", "On"), + Off = THEME:GetString("OptionNames", "Off"), + Matches = THEME:GetString("TabFilter", "Matches"), + CommonPackFilter = THEME:GetString("TabFilter", "CommonPackFilter"), + Length = THEME:GetString("TabFilter", "Length"), + BestPercent = "Best %", + AND = THEME:GetString("TabFilter", "AND"), + OR = THEME:GetString("TabFilter", "OR"), + ExplainStartInput = THEME:GetString("TabFilter", "ExplainStartInput"), + ExplainCancelInput = THEME:GetString("TabFilter", "ExplainCancelInput"), + ExplainGrey = THEME:GetString("TabFilter", "ExplainGrey"), + ExplainBounds = THEME:GetString("TabFilter", "ExplainBounds"), + ExplainHighest = THEME:GetString("TabFilter", "ExplainHighest"), + ExplainHighestDifficulty = THEME:GetString("TabFilter", "ExplainHighestDifficulty"), + MaxRate = THEME:GetString("TabFilter", "MaxRate"), + Title = THEME:GetString("TabFilter", "Title"), + MinRate = THEME:GetString("TabFilter", "MinRate"), +} + +local f = Def.ActorFrame { + BeginCommand = function(self) + self:halign(0):visible(false) + whee = SCREENMAN:GetTopScreen():GetMusicWheel() + SCREENMAN:GetTopScreen():AddInputCallback(FilterInput) + self:queuecommand("Set") + end, + OffCommand = function(self) + self:bouncebegin(0.2):xy(-500, frameY):diffusealpha(0) + self:sleep(0.04):queuecommand("Invis") + end, + InvisCommand= function(self) + self:visible(false) + end, + OnCommand = function(self) + self:bouncebegin(0.2):xy(frameX, frameY):diffusealpha(1) + end, + SetCommand = function(self) + self:finishtweening() + if getTabIndex() == 5 then + self:visible(true) + self:queuecommand("On") + active = true + else + MESSAGEMAN:Broadcast("NumericInputEnded") + self:queuecommand("Off") + active = false + end + end, + TabChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + MouseRightClickMessageCommand = function(self) + ActiveSS = 0 + MESSAGEMAN:Broadcast("NumericInputEnded") + MESSAGEMAN:Broadcast("UpdateFilter") + SCREENMAN:set_input_redirected(PLAYER_1, false) + end, + Def.Quad { + InitCommand = function(self) + self:zoomto(frameWidth, frameHeight):halign(0):valign(0):diffuse(getMainColor("tabs")) + end + }, + Def.Quad { + InitCommand = function(self) + self:zoomto(frameWidth, offsetY):halign(0):valign(0):diffuse(getMainColor("frames")):diffusealpha(0.5) + end + }, + LoadFont("Common Normal") .. { + InitCommand = function(self) + self:xy(5, offsetY - 9):zoom(0.6):halign(0):settext(translated_info["Title"]) + self:diffuse(Saturation(getMainColor("positive"), 0.1)) + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:xy(frameX, frameY -17):zoom(0.3):halign(0) + self:settext(translated_info["ExplainStartInput"]) + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:xy(frameX, frameY + 20 -17):zoom(0.3):halign(0) + self:settext(translated_info["ExplainCancelInput"]) + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:xy(frameX, frameY + 40 -17):zoom(0.3):halign(0) + self:settext(translated_info["ExplainGrey"]) + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:xy(frameX, frameY + 60 -17):zoom(0.3):halign(0) + self:settext(translated_info["ExplainBounds"]) + end + }, + --[[ -- hiding extra unnecessary information + LoadFont("Common Large") .. { + InitCommand = function(self) + self:xy(frameX, frameY + 80 -17):zoom(0.3):halign(0) + self:settext(translated_info["ExplainHighest"]) + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:xy(frameX, frameY + 100 -17):zoom(0.3):halign(0) + self:settext(translated_info["ExplainHighestDifficulty"]) + end + }, + ]] + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175):zoom(textzoom):halign(0) + self:diffuse(getMainColor("positive")) + end, + SetCommand = function(self) + self:settextf("%s:%5.1fx", translated_info["MaxRate"], FILTERMAN:GetMaxFilterRate()) + end, + MaxFilterRateChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + ResetFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + }, + UIElements.QuadButton(1, 1) .. { + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175):zoomto(130, 18):halign(0):diffusealpha(0) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + FILTERMAN:SetMaxFilterRate(FILTERMAN:GetMaxFilterRate() + 0.1) + MESSAGEMAN:Broadcast("MaxFilterRateChanged") + whee:SongSearch("") + elseif params.event == "DeviceButton_right mouse button" and active then + FILTERMAN:SetMaxFilterRate(FILTERMAN:GetMaxFilterRate() - 0.1) + MESSAGEMAN:Broadcast("MaxFilterRateChanged") + whee:SongSearch("") + end + end, + }, + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY):zoom(textzoom):halign(0) + self:diffuse(getMainColor("positive")) + end, + SetCommand = function(self) + self:settextf("%s:%5.1fx", translated_info["MinRate"], FILTERMAN:GetMinFilterRate()) + end, + MaxFilterRateChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + ResetFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + }, + UIElements.QuadButton(1, 1) .. { + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY):zoomto(130, 18):halign(0):diffusealpha(0) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + FILTERMAN:SetMinFilterRate(FILTERMAN:GetMinFilterRate() + 0.1) + MESSAGEMAN:Broadcast("MaxFilterRateChanged") + whee:SongSearch("") + elseif params.event == "DeviceButton_right mouse button" and active then + FILTERMAN:SetMinFilterRate(FILTERMAN:GetMinFilterRate() - 0.1) + MESSAGEMAN:Broadcast("MaxFilterRateChanged") + whee:SongSearch("") + end + end, + }, + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 2):zoom(textzoom):halign(0) + self:diffuse(getMainColor("positive")) + end, + SetCommand = function(self) + if FILTERMAN:GetFilterMode() then + self:settextf("%s: %s", translated_info["Mode"], translated_info["AND"]) + else + self:settextf("%s: %s", translated_info["Mode"], translated_info["OR"]) + end + end, + FilterModeChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + ResetFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + }, + UIElements.QuadButton(1, 1) .. { + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 2):zoomto(120, 18):halign(0):diffusealpha(0) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + FILTERMAN:ToggleFilterMode() + MESSAGEMAN:Broadcast("FilterModeChanged") + whee:SongSearch("") + end + end + }, + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 3):zoom(textzoom):halign(0) + end, + SetCommand = function(self) + local translated = translated_info["HighestOnly"] + if FILTERMAN:GetHighestSkillsetsOnly() then + self:settextf("%s: %s", translated, translated_info["On"]):maxwidth(frameWidth / 2 / textzoom - 50) + else + self:settextf("%s: %s", translated, translated_info["Off"]):maxwidth(frameWidth / 2 / textzoom - 50) + end + if FILTERMAN:GetFilterMode() then + self:diffuse(1,1,1,0.2) + else + self:diffuse(getMainColor("positive")) + end + end, + FilterModeChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + ResetFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + MouseOverCommand = function(self) + if not FILTERMAN:GetFilterMode() then + self:diffusealpha(hoverAlpha) + end + end, + MouseOutCommand = function(self) + if FILTERMAN:GetFilterMode() then + self:diffusealpha(0.2) + else + self:diffusealpha(1) + end + end, + }, + UIElements.QuadButton(1, 1) .. { + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 3):zoomto(180, 18):halign(0):diffusealpha(0) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active and not FILTERMAN:GetFilterMode() then + FILTERMAN:ToggleHighestSkillsetsOnly() + MESSAGEMAN:Broadcast("FilterModeChanged") + whee:SongSearch("") + end + end + }, + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 4):zoom(textzoom):halign(0) + end, + SetCommand = function(self) + local translated = translated_info["HighestDifficultyOnly"] + if FILTERMAN:GetHighestDifficultyOnly() then + self:settextf("%s: %s", translated, translated_info["On"]):maxwidth(frameWidth / 2 / textzoom - 50) + else + self:settextf("%s: %s", translated, translated_info["Off"]):maxwidth(frameWidth / 2 / textzoom - 50) + end + if FILTERMAN:GetFilterMode() then + self:diffuse(1,1,1,0.2) + else + self:diffuse(getMainColor("positive")) + end + end, + FilterModeChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + ResetFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + MouseOverCommand = function(self) + if not FILTERMAN:GetFilterMode() then + self:diffusealpha(hoverAlpha) + end + end, + MouseOutCommand = function(self) + if FILTERMAN:GetFilterMode() then + self:diffusealpha(0.2) + else + self:diffusealpha(1) + end + end, + }, + UIElements.QuadButton(1, 1) .. { + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 4):zoomto(180, 18):halign(0):diffusealpha(0) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active and not FILTERMAN:GetFilterMode() then + FILTERMAN:ToggleHighestDifficultyOnly() + MESSAGEMAN:Broadcast("FilterModeChanged") + whee:SongSearch("") + end + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 6):zoom(textzoom):halign(0):settext("") + end, + FilterResultsMessageCommand = function(self, msg) + self:settextf("%s: %i/%i", translated_info["Matches"], msg.Matches, msg.Total) + end + }, + UIElements.TextToolTip(1, 1, "Common Large") .. { + BeginCommand = function(self) + self:xy(frameX + frameWidth / 2, 175 + spacingY * 7):zoom(textzoom):halign(0):maxwidth(300) + self.packlistFiltering = FILTERMAN:GetFilteringCommonPacks() + self.enabled = SCREENMAN:GetTopScreen():GetName() == "ScreenNetSelectMusic" + if not self.enabled then + self:visible(false) + end + self:queuecommand("Set") + end, + MouseDownCommand = function(self, params) + if self.enabled and params.event == "DeviceButton_left mouse button" and active then + self.packlistFiltering = whee:SetPackListFiltering(not self.packlistFiltering) + self:queuecommand("Set") + end + end, + SetCommand = function(self) + self:settextf("%s: %s", translated_info["CommonPackFilter"], (self.packlistFiltering and translated_info["On"] or translated_info["Off"])) + end, + FilterModeChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + ResetFilterMessageCommand = function(self) + self:queuecommand("Set") + end + } +} + +local function CreateFilterInputBox(i) + local t = Def.ActorFrame { + InitCommand = function(self) + self:y(-17) + end, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:addx(10):addy(175 + (i - 1) * spacingY):halign(0):zoom(textzoom) + end, + SetCommand = function(self) + self:settext(i == (#ms.SkillSets + 1) and translated_info["Length"] or (i == (#ms.SkillSets + 2) and translated_info["BestPercent"] or ms.SkillSetsTranslated[i])) + end + }, + UIElements.QuadButton(1, 1) .. { + InitCommand = function(self) + self:addx(i == (#ms.SkillSets + 1) and 159 or (i == (#ms.SkillSets + 2) and 159 or 150)):addy(175 + (i - 1) * spacingY):zoomto( + i == (#ms.SkillSets + 1) and 27 or (i == (#ms.SkillSets + 2) and 27 or 18), + 18 + ):halign(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + ActiveSS = i + activebound = 0 + MESSAGEMAN:Broadcast("NumericInputActive") + self:diffusealpha(0.1) + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end, + SetCommand = function(self) + if ActiveSS == i and activebound == 0 then + self:diffuse(color("#666666")) + else + self:diffuse(color("#000000")) + end + end, + UpdateFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + NumericInputEndedMessageCommand = function(self) + self:queuecommand("Set") + end, + NumericInputActiveMessageCommand = function(self) + self:queuecommand("Set") + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:addx(i == (#ms.SkillSets + 1) and 159 or (i == (#ms.SkillSets + 2) and 159 or 150)):addy(175 + (i - 1) * spacingY):halign(1):maxwidth(60):zoom( + textzoom + ) + end, + SetCommand = function(self) + local fval = notShit.round(FILTERMAN:GetSSFilter(i, 0), numbersafterthedecimal) -- lower bounds + local fmtstr = "" + if i == #ms.SkillSets+2 then + if numbersafterthedecimal > 0 then + fmtstr = "%5."..numbersafterthedecimal.."f" + else + fmtstr = "%02d." + end + else + fmtstr = "%d" + end + self:settextf(fmtstr, fval) + if fval <= 0 and ActiveSS ~= i then + self:diffuse(color("#666666")) + elseif activebound == 0 then + self:diffuse(color("#FFFFFF")) + end + end, + UpdateFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + NumericInputActiveMessageCommand = function(self) + self:queuecommand("Set") + end + }, + UIElements.QuadButton(1, 1) .. { + InitCommand = function(self) + self:addx(i == (#ms.SkillSets + 1) and 193 or (i == (#ms.SkillSets + 2) and 193 or 175)):addy(175 + (i - 1) * spacingY):zoomto( + i == (#ms.SkillSets + 1) and 27 or (i == (#ms.SkillSets + 2) and 27 or 18), + 18 + ):halign(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + ActiveSS = i + activebound = 1 + MESSAGEMAN:Broadcast("NumericInputActive") + self:diffusealpha(0.1) + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end, + SetCommand = function(self) + if ActiveSS == i and activebound == 1 then + self:diffuse(color("#666666")) + else + self:diffuse(color("#000000")) + end + end, + UpdateFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + NumericInputEndedMessageCommand = function(self) + self:queuecommand("Set") + end, + NumericInputActiveMessageCommand = function(self) + self:queuecommand("Set") + end + }, + LoadFont("Common Large") .. { + InitCommand = function(self) + self:addx(i == (#ms.SkillSets + 1) and 193 or (i == (#ms.SkillSets + 2) and 193 or 175)):addy(175 + (i - 1) * spacingY):halign(1):maxwidth(60):zoom( + textzoom + ) + end, + SetCommand = function(self) + local fval = notShit.round(FILTERMAN:GetSSFilter(i, 1), numbersafterthedecimal) -- upper bounds + local fmtstr = "%5." + if i == #ms.SkillSets+2 then + if numbersafterthedecimal > 0 then + fmtstr = "%5."..numbersafterthedecimal.."f" + else + fmtstr = "%02d." + end + else + fmtstr = "%d" + end + self:settextf(fmtstr, fval) + if fval <= 0 and ActiveSS ~= i then + self:diffuse(color("#666666")) + elseif activebound == 1 then + self:diffuse(color("#FFFFFF")) + end + end, + UpdateFilterMessageCommand = function(self) + self:queuecommand("Set") + end, + NumericInputActiveMessageCommand = function(self) + self:queuecommand("Set") + end + } + } + return t +end + +--reset button +f[#f + 1] = UIElements.TextButton(1, 1, "Common Large") .. { + InitCommand = function(self) + self:xy(frameX + frameWidth - 150, frameY + 250 + spacingY * 2) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:zoom(0.35) + txt:settext(THEME:GetString("TabFilter", "Reset")) + txt:diffuse(getMainColor("positive")) + bg:zoomto(60, 20) + end, + RolloverUpdateCommand = function(self, params) + if params.update == "in" then + self:diffusealpha(hoverAlpha) + else + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if params.update ~= "OnMouseDown" then return end + if params.event == "DeviceButton_left mouse button" and active then + FILTERMAN:ResetAllFilters() + for i = 1, #ms.SkillSets + 2 do + SSQuery[0][i] = "0" + SSQuery[1][i] = "0" + end + numbersafterthedecimal = 0 + activebound = 0 + ActiveSS = 0 + MESSAGEMAN:Broadcast("UpdateFilter") + MESSAGEMAN:Broadcast("ResetFilter") + MESSAGEMAN:Broadcast("NumericInputEnded") + SCREENMAN:set_input_redirected(PLAYER_1, false) + whee:SongSearch("") + end + end, +} +--[[ +-- apply button +f[#f + 1] = UIElements.TextButton(1, 1, "Common Large") .. { + InitCommand = function(self) + self:xy(frameX + frameWidth - 150, frameY + 250 + spacingY * -1) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:zoom(0.35) + txt:settext(THEME:GetString("TabFilter", "Apply")) + txt:diffuse(getMainColor("positive")) + bg:zoomto(60, 20) + end, + RolloverUpdateCommand = function(self, params) + if params.update == "in" then + self:diffusealpha(hoverAlpha) + else + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if params.update ~= "OnMouseDown" then return end + if params.event == "DeviceButton_left mouse button" and active then + MESSAGEMAN:Broadcast("NumericInputEnded") + SCREENMAN:set_input_redirected(PLAYER_1, false) + whee:SongSearch("") + end + end, +} +]] + +for i = 1, (#ms.SkillSets + 2) do + f[#f + 1] = CreateFilterInputBox(i) +end +return f diff --git a/src/Etterna/Singletons/ThemeManager.cpp b/src/Etterna/Singletons/ThemeManager.cpp index 19f50a7514..69fdb61610 100644 --- a/src/Etterna/Singletons/ThemeManager.cpp +++ b/src/Etterna/Singletons/ThemeManager.cpp @@ -1535,7 +1535,7 @@ class LunaThemeManager : public Luna for (auto& s : langs) { result.push_back(s); } - + LuaHelpers::CreateTableFromArray(result, L); return 1; } From 9da3d43b3307d6aab066dfe4f5a554116daf6886 Mon Sep 17 00:00:00 2001 From: Alkorith <101947793+Alkorith@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:19:46 -0500 Subject: [PATCH 2/4] chore(FilterManager): De-duplicate constants --- src/Etterna/Singletons/FilterManager.cpp | 18 +++++++----------- src/Etterna/Singletons/FilterManager.h | 20 +++++++++++++++----- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Etterna/Singletons/FilterManager.cpp b/src/Etterna/Singletons/FilterManager.cpp index 904b867004..5536801cbb 100644 --- a/src/Etterna/Singletons/FilterManager.cpp +++ b/src/Etterna/Singletons/FilterManager.cpp @@ -49,23 +49,19 @@ FilterManager::SetFilter(float v, Skillset ss, int bound) void FilterManager::ResetSSFilters() { - for (auto& val : FilterLowerBounds) { - val = 0; - } - for (auto& val : FilterUpperBounds) { - val = 0; - } + FilterLowerBounds.fill(0); + FilterUpperBounds.fill(0); } void FilterManager::ResetAllFilters() { ResetSSFilters(); - ExclusiveFilter = false; - HighestSkillsetsOnly = false; - HighestDifficultyOnly = false; - MinFilterRate = 1.F; - MaxFilterRate = 1.F; + ExclusiveFilter = FilterManagerDefault::ExclusiveFilter; + HighestSkillsetsOnly = FilterManagerDefault::HighestSkillsetsOnly; + HighestDifficultyOnly = FilterManagerDefault::HighestDifficultyOnly; + MinFilterRate = FilterManagerDefault::MinFilterRate; + MaxFilterRate = FilterManagerDefault::MaxFilterRate; } // tmp filter stuff - mina diff --git a/src/Etterna/Singletons/FilterManager.h b/src/Etterna/Singletons/FilterManager.h index c855c75557..39f9d17eaa 100644 --- a/src/Etterna/Singletons/FilterManager.h +++ b/src/Etterna/Singletons/FilterManager.h @@ -6,6 +6,16 @@ #include class PlayerState; + +namespace FilterManagerDefault +{ + inline constexpr float MinFilterRate = 1.F; + inline constexpr float MaxFilterRate = 1.F; + inline constexpr bool ExclusiveFilter = false; + inline constexpr bool HighestSkillsetsOnly = false; + inline constexpr bool HighestDifficultyOnly = false; +} + class FilterManager { public: @@ -28,18 +38,18 @@ class FilterManager * Length, //REQUIRED in non-exclusive filter if set * Clear % //REQUIRED in non-exclusive filter if set */ - float MaxFilterRate = 1.F; - float MinFilterRate = 1.F; - bool ExclusiveFilter = false; // if true the filter system will only match + float MaxFilterRate = FilterManagerDefault::MaxFilterRate; + float MinFilterRate = FilterManagerDefault::MinFilterRate; + bool ExclusiveFilter = FilterManagerDefault::ExclusiveFilter; // if true the filter system will only match // songs that meet all criteria rather than // all that meet any - mina auto GetFilter(Skillset ss, int bound) -> float; void SetFilter(float v, Skillset ss, int bound); void ResetSSFilters(); // reset button for filters void ResetAllFilters(); - bool HighestSkillsetsOnly = false; + bool HighestSkillsetsOnly = FilterManagerDefault::HighestSkillsetsOnly; // Skillset is highest of the chart's skillset - bool HighestDifficultyOnly = false; + bool HighestDifficultyOnly = FilterManagerDefault::HighestDifficultyOnly; // Chart's skillset's MSD is the highest of all the MSDS of that // skillset for all charts for that song. From 15eabb1287eab7c9d2fda658d8a18588904ddbf1 Mon Sep 17 00:00:00 2001 From: Alkorith <101947793+Alkorith@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:29:44 -0500 Subject: [PATCH 3/4] feat(ThemeManager): Allow adding paths to lua package.path --- src/Etterna/Singletons/ThemeManager.cpp | 55 +++++++++++++++++++++++++ src/Etterna/Singletons/ThemeManager.h | 3 ++ 2 files changed, 58 insertions(+) diff --git a/src/Etterna/Singletons/ThemeManager.cpp b/src/Etterna/Singletons/ThemeManager.cpp index 69fdb61610..ee1bed1caf 100644 --- a/src/Etterna/Singletons/ThemeManager.cpp +++ b/src/Etterna/Singletons/ThemeManager.cpp @@ -20,9 +20,11 @@ #include "Core/Services/Locator.hpp" #include "Core/Platform/Platform.hpp" +#include #include "PrefsManager.h" #include +#include #include #include #include @@ -504,6 +506,59 @@ ThemeManager::ClearSubscribers() } } +void +ThemeManager::AppendToLuaPackagePath(const std::string& path) +{ + + Lua* L = nullptr; + std::string packagePath; + // Get current package.path value + { + L = LUA->Get(); + LuaHelpers::RunExpression(L, "package.path"); + LuaHelpers::Pop(L, packagePath); + LUA->Release(L); + L = nullptr; + } + + // Verify whether the path already exists in lua package path. + // If it does, leave function call early. + { + const std::regex path_regex(R"([^;]+)"); + const auto end = std::sregex_iterator(); + for (auto it = std::sregex_iterator( + packagePath.begin(), packagePath.end(), path_regex); + it != end; + ++it) { + // Path already exists. No need to add. + if (it->str() == path) { + Locator::getLogger()->warn( + "Path \"{}\" already in lua package.path.", path.c_str()); + return; + } + } + } + + // Append new path to `package.path` + { + const std::string expression = + fmt::format("package.path = package.path .. ';{}'", path); + std::string error = + fmt::format("Lua runtime error parsing \"{}\"", expression); + + L = LUA->Get(); + + Locator::getLogger()->info("Appending \"{}\" to lua package.path", + path.c_str()); + LuaHelpers::RunScript( + L, expression, std::string("in"), error, 0, 1, true, false); + + LuaHelpers::Pop(L, packagePath); // no op to clean up stack + LUA->Release(L); + L = nullptr; + } +} + void ThemeManager::RunLuaScripts(const std::string& sMask, bool bUseThemeDir) { diff --git a/src/Etterna/Singletons/ThemeManager.h b/src/Etterna/Singletons/ThemeManager.h index 10e8263ac9..592d23a260 100644 --- a/src/Etterna/Singletons/ThemeManager.h +++ b/src/Etterna/Singletons/ThemeManager.h @@ -222,6 +222,9 @@ class ThemeManager std::string m_sRealCurThemeName = ""; std::string m_sCurLanguage; bool m_bPseudoLocalize; + + private: + void AppendToLuaPackagePath(const std::string& path); }; extern ThemeManager* From 1a5e97b8d1fc137e3774d25d7fa95ba83acf26e4 Mon Sep 17 00:00:00 2001 From: Alkorith <101947793+Alkorith@users.noreply.github.com> Date: Mon, 11 Sep 2023 19:17:59 -0500 Subject: [PATCH 4/4] feat(filterpreset): Implement loading/saving filter presets commit 3f6636a2fb6f60c18769148680e1e60f186663a3 Author: Alkorith <101947793+Alkorith@users.noreply.github.com> Date: Mon Sep 11 11:25:05 2023 -0500 feat(filterpreset): Implement loading/saving filter presets commit 98725542c8dbc223d963987e2c0668badb5fe5e9 Author: Alkorith <101947793+Alkorith@users.noreply.github.com> Date: Mon Sep 11 11:30:53 2023 -0500 chore(filterpresets): Add lunajson dependency --- .../playerInfoFrame/searchfilter.lua | 63 ++ Themes/Rebirth/Languages/en.ini | 7 +- .../ScreenSelectMusic decorations/filters.lua | 87 ++- Themes/Til Death/Languages/en.ini | 7 + Themes/_fallback/lib/lua/5.1/filterpreset.lua | 7 + .../lib/lua/5.1/filterpreset/load_preset.lua | 80 +++ .../lib/lua/5.1/filterpreset/save_preset.lua | 44 ++ lib/lua/5.1/lunajson.lua | 8 + lib/lua/5.1/lunajson/decoder.lua | 538 ++++++++++++++++++ lib/lua/5.1/lunajson/encoder.lua | 208 +++++++ src/Etterna/Singletons/FilterManager.cpp | 21 + src/Etterna/Singletons/ThemeManager.cpp | 7 + 12 files changed, 1075 insertions(+), 2 deletions(-) create mode 100644 Themes/_fallback/lib/lua/5.1/filterpreset.lua create mode 100644 Themes/_fallback/lib/lua/5.1/filterpreset/load_preset.lua create mode 100644 Themes/_fallback/lib/lua/5.1/filterpreset/save_preset.lua create mode 100644 lib/lua/5.1/lunajson.lua create mode 100644 lib/lua/5.1/lunajson/decoder.lua create mode 100644 lib/lua/5.1/lunajson/encoder.lua diff --git a/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua b/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua index ab1bf13c84..3429b11b3d 100644 --- a/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua +++ b/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua @@ -1,3 +1,5 @@ +local filterpreset = require('filterpreset') + local ratios = { Width = 782 / 1920, Height = 971 / 1080, @@ -55,6 +57,9 @@ local translations = { Results = THEME:GetString("SearchFilter", "Results"), Reset = THEME:GetString("SearchFilter", "Reset"), Apply = THEME:GetString("SearchFilter", "Apply"), + SaveToDefaultPreset = THEME:GetString("FilterPreset", "SaveToDefaultPreset"), + ExportPresetToFile = THEME:GetString("FilterPreset", "ExportPresetToFile"), + SaveFilterPresetPrompt = THEME:GetString("FilterPreset", "SaveFilterPresetPrompt"), } local visibleframeX = SCREEN_WIDTH - actuals.Width @@ -1170,6 +1175,60 @@ local function lowerSection() end } + t[#t+1] = filterMiscLine(9) .. { + Name = "SaveLine", + InitCommand = function(self) + self:settext(translations["SaveToDefaultPreset"]) + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self) + filterpreset.save_preset("default", PLAYER_1) + end + } + + + t[#t+1] = filterMiscLine(10) .. { + Name = "ExportLine", + InitCommand = function(self) + self:settext(translations["ExportPresetToFile"]) + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self) + local redir = SCREENMAN:get_input_redirected(PLAYER_1) + local function off() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + local function on() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end + off() + + askForInputStringWithFunction( + translations["SaveFilterPresetPrompt"], + 32, + false, + function(answer) + -- success if the answer isnt blank + if answer:gsub("^%s*(.-)%s*$", "%1") ~= "" then + filterpreset.save_preset(answer) + else + on() + end + end, + function() return true, "" end, + function() + on() + end + ) + end + } + return t end @@ -1218,4 +1277,8 @@ t[#t+1] = Def.Quad { t[#t+1] = upperSection() t[#t+1] = lowerSection() +-- Load default preset if it exists. We should only be setting the values once +-- at startup. Subsequent calls should not occur. +filterpreset.load_preset("default", false, PLAYER_1) + return t diff --git a/Themes/Rebirth/Languages/en.ini b/Themes/Rebirth/Languages/en.ini index 77ad14c004..3bf175bf23 100644 --- a/Themes/Rebirth/Languages/en.ini +++ b/Themes/Rebirth/Languages/en.ini @@ -627,6 +627,11 @@ Results=Matches Reset=Reset Apply=Apply +[FilterPreset] +ExportPresetToFile=Export +SaveFilterPresetPrompt=Enter filter preset file name: +SaveToDefaultPreset=Save + [Settings] NothingBound=none CurrentlyBinding=Currently Binding @@ -1021,4 +1026,4 @@ Bm_Double7=14K+2 Maniax_Single=4K Maniax_Double=8K Pnm_Five=5K -Pnm_Nine=9K \ No newline at end of file +Pnm_Nine=9K diff --git a/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua b/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua index c8ad8f8e60..9563da4f15 100644 --- a/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua +++ b/Themes/Til Death/BGAnimations/ScreenSelectMusic decorations/filters.lua @@ -1,3 +1,5 @@ +local filterpreset = require('filterpreset') + local numbershers = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"} local frameX = 10 local frameY = 45 @@ -99,6 +101,9 @@ local translated_info = { MaxRate = THEME:GetString("TabFilter", "MaxRate"), Title = THEME:GetString("TabFilter", "Title"), MinRate = THEME:GetString("TabFilter", "MinRate"), + ExportFilterToFile = THEME:GetString("FilterPreset", "ExportFilterToFile"), + SaveFilterPresetPrompt = THEME:GetString("FilterPreset", "SaveFilterPresetPrompt"), + SaveToDefaultFilterPreset = THEME:GetString("FilterPreset", "SaveToDefaultFilterPreset"), } local f = Def.ActorFrame { @@ -433,6 +438,81 @@ local f = Def.ActorFrame { ResetFilterMessageCommand = function(self) self:queuecommand("Set") end + }, + --[[ + -- FIXME: Hot Reloading does not work (yet). You would need to leave the + -- song select menu, then go back in for the filter to apply. + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(10, 175 + spacingY * -2) + self:zoom(textzoom) + self:halign(0) + self:diffuse(getMainColor("positive")) + self:settext(translated_info["LoadFilterPreset"]) + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + easyInputStringWithParams( + translated_info["LoadFilterPresetPrompt"], + 32, + false, + filterpreset.load_preset, + true + ) + end + end + }, + --]] + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(frameX + frameWidth / 2 + 100, 175 + spacingY * 7) + self:zoom(textzoom) + self:halign(0) + self:settext(translated_info["SaveToDefaultFilterPreset"]) + self:diffuse(getMainColor("positive")) + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + filterpreset.save_preset("default", PLAYER_1) + end + end + }, + UIElements.TextToolTip(1, 1, "Common Large") ..{ + InitCommand = function(self) + self:xy(frameX + frameWidth / 2 + 100, 175 + spacingY * 8) + self:zoom(textzoom) + self:halign(0) + self:diffuse(getMainColor("positive")) + self:settext("Export") + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" and active then + easyInputStringWithFunction( + THEME:GetString("FilterPreset", "SaveFilterPresetPrompt"), + 32, + false, + filterpreset.save_preset + ) + end + end } } @@ -648,9 +728,14 @@ f[#f + 1] = UIElements.TextButton(1, 1, "Common Large") .. { end end, } -]] +--]] for i = 1, (#ms.SkillSets + 2) do f[#f + 1] = CreateFilterInputBox(i) end + +-- Load default preset if it exists. We should only be setting the values once +-- at startup. Subsequent calls should not occur. +filterpreset.load_preset("default", false, PLAYER_1) + return f diff --git a/Themes/Til Death/Languages/en.ini b/Themes/Til Death/Languages/en.ini index 02b9136fe4..195446a516 100644 --- a/Themes/Til Death/Languages/en.ini +++ b/Themes/Til Death/Languages/en.ini @@ -572,6 +572,13 @@ Apply=Apply AND=ALL OR=ANY +[FilterPreset] +ExportFilterToFile=Export +LoadFilterPreset=Load +LoadFilterPresetPrompt=Enter filter preset file name: +SaveFilterPresetPrompt=Enter filter preset file name: +SaveToDefaultFilterPreset=Save + [TabGoals] PriorityLong=Priority PriorityShort=P diff --git a/Themes/_fallback/lib/lua/5.1/filterpreset.lua b/Themes/_fallback/lib/lua/5.1/filterpreset.lua new file mode 100644 index 0000000000..e27534aff9 --- /dev/null +++ b/Themes/_fallback/lib/lua/5.1/filterpreset.lua @@ -0,0 +1,7 @@ +local load_preset = require("filterpreset.load_preset") +local save_preset = require("filterpreset.save_preset") + +return { + load_preset = load_preset, + save_preset = save_preset +} diff --git a/Themes/_fallback/lib/lua/5.1/filterpreset/load_preset.lua b/Themes/_fallback/lib/lua/5.1/filterpreset/load_preset.lua new file mode 100644 index 0000000000..2c73c3b42f --- /dev/null +++ b/Themes/_fallback/lib/lua/5.1/filterpreset/load_preset.lua @@ -0,0 +1,80 @@ +local json = require('lunajson') + +local first_time_call_guard = true + +-- Use default skillset names +local json_filter_categories = DeepCopy(ms.SkillSets) +table.insert(json_filter_categories, "Length") +table.insert(json_filter_categories, "ClearPercent") + +local RateKey = "Rate" +local ExclusiveFilterKey = "ExclusiveFilter" +local HighestSkillsetOnlyKey = "HighestSkillsetOnly" +local HighestDifficultyOnlyKey = "HighestDifficultyOnly" + +local function load_preset(filename, explicit_call, pn) + explicit_call = explicit_call or false + + if not (explicit_call or first_time_call_guard) then return end + if first_time_call_guard then first_time_call_guard = false end + + pn = pn or PLAYER_1 + filename = filename or "default.json" + + local full_path = ( + PROFILEMAN:GetProfileDir(pn_to_profile_slot(pn)) + .. '/filter_presets/' + .. filename + .. '.json' + ) + + local file_output = File.Read(full_path) + if not file_output then return end + + + local data = json.decode(file_output) + + for i = 1, #json_filter_categories do + if data[json_filter_categories[i]] then + local setting = data[json_filter_categories[i]] + + if setting.min then + FILTERMAN:SetSSFilter(setting.min, i, 0) + end + + if setting.max then + FILTERMAN:SetSSFilter(setting.max, i, 1) + end + end + end + + if data[RateKey] then + local setting = data[RateKey] + + if setting.min then + FILTERMAN:SetMinFilterRate(setting.min) + end + + if setting.max then + FILTERMAN:SetMaxFilterRate(setting.max) + end + end + + if data[ExclusiveFilterKey] then + FILTERMAN:SetFilterMode(data[ExclusiveFilterKey]) + end + + if data[HighestSkillsetOnlyKey] then + FILTERMAN:SetHighestSkillsetsOnly(data[HighestSkillsetOnlyKey]) + end + + if data[HighestDifficultyOnlyKey] then + FILTERMAN:HighestDifficultyOnlyKey(data[HighestDifficultyOnlyKey]) + end + + if explicit_call then + ms.ok(string.format("Loaded filter preset: %s", full_path)) + end +end + +return load_preset diff --git a/Themes/_fallback/lib/lua/5.1/filterpreset/save_preset.lua b/Themes/_fallback/lib/lua/5.1/filterpreset/save_preset.lua new file mode 100644 index 0000000000..bce68a5d89 --- /dev/null +++ b/Themes/_fallback/lib/lua/5.1/filterpreset/save_preset.lua @@ -0,0 +1,44 @@ +local json = require('lunajson') + +-- Use default skillset names +local json_filter_categories = DeepCopy(ms.SkillSets) +table.insert(json_filter_categories, "Length") +table.insert(json_filter_categories, "ClearPercent") + +local RateKey = "Rate" +local ExclusiveFilterKey = "ExclusiveFilter" +local HighestSkillsetOnlyKey = "HighestSkillsetOnly" +local HighestDifficultyOnlyKey = "HighestDifficultyOnly" + +local function save_preset(filename, pn) + filename = filename or "default" + pn = pn or PLAYER_1 + + local filter_preset = { + Rate = { + min = math.round(FILTERMAN:GetMinFilterRate(), 1), + max = math.round(FILTERMAN:GetMaxFilterRate(), 1) + }, + ExclusiveFilter = FILTERMAN:GetFilterMode(), + HighestSkillsetOnly = FILTERMAN:GetHighestSkillsetsOnly(), + HighestDifficultyOnly = FILTERMAN:GetHighestDifficultyOnly() + } + + for i = 1, #json_filter_categories do + filter_preset[json_filter_categories[i]] = { + min = FILTERMAN:GetSSFilter(i, 0), + max = FILTERMAN:GetSSFilter(i, 1) + } + end + + local full_path = ( + PROFILEMAN:GetProfileDir(pn_to_profile_slot(pn)) + .. '/filter_presets/' + .. filename + .. '.json' + ) + File.Write(full_path, json.encode(filter_preset)) + ms.ok(string.format("Saved file to: %s", full_path)) +end + +return save_preset diff --git a/lib/lua/5.1/lunajson.lua b/lib/lua/5.1/lunajson.lua new file mode 100644 index 0000000000..2ed9700287 --- /dev/null +++ b/lib/lua/5.1/lunajson.lua @@ -0,0 +1,8 @@ +local newdecoder = require 'lunajson.decoder' +local newencoder = require 'lunajson.encoder' +-- If you need multiple contexts of decoder and/or encoder, +-- you can require lunajson.decoder and/or lunajson.encoder directly. +return { + decode = newdecoder(), + encode = newencoder(), +} diff --git a/lib/lua/5.1/lunajson/decoder.lua b/lib/lua/5.1/lunajson/decoder.lua new file mode 100644 index 0000000000..e61f2ce1be --- /dev/null +++ b/lib/lua/5.1/lunajson/decoder.lua @@ -0,0 +1,538 @@ +--[=====[ +The MIT License (MIT) + +Copyright (c) 2015-2017 Shunsuke Shimizu (grafi) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--]=====] +local setmetatable, tonumber, tostring = + setmetatable, tonumber, tostring +local floor, inf = + math.floor, math.huge +local mininteger, tointeger = + math.mininteger or nil, math.tointeger or nil +local byte, char, find, gsub, match, sub = + string.byte, string.char, string.find, string.gsub, string.match, string.sub + +local function _decode_error(pos, errmsg) + error("parse error at " .. pos .. ": " .. errmsg, 2) +end + +local f_str_ctrl_pat +if _VERSION == "Lua 5.1" then + -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly + f_str_ctrl_pat = '[^\32-\255]' +else + f_str_ctrl_pat = '[\0-\31]' +end + +local _ENV = nil + + +local function newdecoder() + local json, pos, nullv, arraylen, rec_depth + + -- `f` is the temporary for dispatcher[c] and + -- the dummy for the first return value of `find` + local dispatcher, f + + --[[ + Helper + --]] + local function decode_error(errmsg) + return _decode_error(pos, errmsg) + end + + --[[ + Invalid + --]] + local function f_err() + decode_error('invalid value') + end + + --[[ + Constants + --]] + -- null + local function f_nul() + if sub(json, pos, pos+2) == 'ull' then + pos = pos+3 + return nullv + end + decode_error('invalid value') + end + + -- false + local function f_fls() + if sub(json, pos, pos+3) == 'alse' then + pos = pos+4 + return false + end + decode_error('invalid value') + end + + -- true + local function f_tru() + if sub(json, pos, pos+2) == 'rue' then + pos = pos+3 + return true + end + decode_error('invalid value') + end + + --[[ + Numbers + Conceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp) + is captured as a number and its conformance to the JSON spec is checked. + --]] + -- deal with non-standard locales + local radixmark = match(tostring(0.5), '[^0-9]') + local fixedtonumber = tonumber + if radixmark ~= '.' then + if find(radixmark, '%W') then + radixmark = '%' .. radixmark + end + fixedtonumber = function(s) + return tonumber(gsub(s, '.', radixmark)) + end + end + + local function number_error() + return decode_error('invalid number') + end + + -- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?` + local function f_zro(mns) + local num, c = match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0 + + if num == '' then + if c == '' then + if mns then + return -0.0 + end + return 0 + end + + if c == 'e' or c == 'E' then + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + if c == '' then + pos = pos + #num + if mns then + return -0.0 + end + return 0.0 + end + end + number_error() + end + + if byte(num) ~= 0x2E or byte(num, -1) == 0x2E then + number_error() + end + + if c ~= '' then + if c == 'e' or c == 'E' then + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + end + if c ~= '' then + number_error() + end + end + + pos = pos + #num + c = fixedtonumber(num) + + if mns then + c = -c + end + return c + end + + -- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?` + local function f_num(mns) + pos = pos-1 + local num, c = match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos) + if byte(num, -1) == 0x2E then -- error if ended with period + number_error() + end + + if c ~= '' then + if c ~= 'e' and c ~= 'E' then + number_error() + end + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + if not num or c ~= '' then + number_error() + end + end + + pos = pos + #num + c = fixedtonumber(num) + + if mns then + c = -c + if c == mininteger and not find(num, '[^0-9]') then + c = mininteger + end + end + return c + end + + -- skip minus sign + local function f_mns() + local c = byte(json, pos) + if c then + pos = pos+1 + if c > 0x30 then + if c < 0x3A then + return f_num(true) + end + else + if c > 0x2F then + return f_zro(true) + end + end + end + decode_error('invalid number') + end + + --[[ + Strings + --]] + local f_str_hextbl = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, inf, inf, inf, inf, inf, inf, + inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, + __index = function() + return inf + end + } + setmetatable(f_str_hextbl, f_str_hextbl) + + local f_str_escapetbl = { + ['"'] = '"', + ['\\'] = '\\', + ['/'] = '/', + ['b'] = '\b', + ['f'] = '\f', + ['n'] = '\n', + ['r'] = '\r', + ['t'] = '\t', + __index = function() + decode_error("invalid escape sequence") + end + } + setmetatable(f_str_escapetbl, f_str_escapetbl) + + local function surrogate_first_error() + return decode_error("1st surrogate pair byte not continued by 2nd") + end + + local f_str_surrogate_prev = 0 + local function f_str_subst(ch, ucode) + if ch == 'u' then + local c1, c2, c3, c4, rest = byte(ucode, 1, 5) + ucode = f_str_hextbl[c1-47] * 0x1000 + + f_str_hextbl[c2-47] * 0x100 + + f_str_hextbl[c3-47] * 0x10 + + f_str_hextbl[c4-47] + if ucode ~= inf then + if ucode < 0x80 then -- 1byte + if rest then + return char(ucode, rest) + end + return char(ucode) + elseif ucode < 0x800 then -- 2bytes + c1 = floor(ucode / 0x40) + c2 = ucode - c1 * 0x40 + c1 = c1 + 0xC0 + c2 = c2 + 0x80 + if rest then + return char(c1, c2, rest) + end + return char(c1, c2) + elseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes + c1 = floor(ucode / 0x1000) + ucode = ucode - c1 * 0x1000 + c2 = floor(ucode / 0x40) + c3 = ucode - c2 * 0x40 + c1 = c1 + 0xE0 + c2 = c2 + 0x80 + c3 = c3 + 0x80 + if rest then + return char(c1, c2, c3, rest) + end + return char(c1, c2, c3) + elseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st + if f_str_surrogate_prev == 0 then + f_str_surrogate_prev = ucode + if not rest then + return '' + end + surrogate_first_error() + end + f_str_surrogate_prev = 0 + surrogate_first_error() + else -- surrogate pair 2nd + if f_str_surrogate_prev ~= 0 then + ucode = 0x10000 + + (f_str_surrogate_prev - 0xD800) * 0x400 + + (ucode - 0xDC00) + f_str_surrogate_prev = 0 + c1 = floor(ucode / 0x40000) + ucode = ucode - c1 * 0x40000 + c2 = floor(ucode / 0x1000) + ucode = ucode - c2 * 0x1000 + c3 = floor(ucode / 0x40) + c4 = ucode - c3 * 0x40 + c1 = c1 + 0xF0 + c2 = c2 + 0x80 + c3 = c3 + 0x80 + c4 = c4 + 0x80 + if rest then + return char(c1, c2, c3, c4, rest) + end + return char(c1, c2, c3, c4) + end + decode_error("2nd surrogate pair byte appeared without 1st") + end + end + decode_error("invalid unicode codepoint literal") + end + if f_str_surrogate_prev ~= 0 then + f_str_surrogate_prev = 0 + surrogate_first_error() + end + return f_str_escapetbl[ch] .. ucode + end + + -- caching interpreted keys for speed + local f_str_keycache = setmetatable({}, {__mode="v"}) + + local function f_str(iskey) + local newpos = pos + local tmppos, c1, c2 + repeat + newpos = find(json, '"', newpos, true) -- search '"' + if not newpos then + decode_error("unterminated string") + end + tmppos = newpos-1 + newpos = newpos+1 + c1, c2 = byte(json, tmppos-1, tmppos) + if c2 == 0x5C and c1 == 0x5C then -- skip preceding '\\'s + repeat + tmppos = tmppos-2 + c1, c2 = byte(json, tmppos-1, tmppos) + until c2 ~= 0x5C or c1 ~= 0x5C + tmppos = newpos-2 + end + until c2 ~= 0x5C -- leave if '"' is not preceded by '\' + + local str = sub(json, pos, tmppos) + pos = newpos + + if iskey then -- check key cache + tmppos = f_str_keycache[str] -- reuse tmppos for cache key/val + if tmppos then + return tmppos + end + tmppos = str + end + + if find(str, f_str_ctrl_pat) then + decode_error("unescaped control string") + end + if find(str, '\\', 1, true) then -- check whether a backslash exists + -- We need to grab 4 characters after the escape char, + -- for encoding unicode codepoint to UTF-8. + -- As we need to ensure that every first surrogate pair byte is + -- immediately followed by second one, we grab upto 5 characters and + -- check the last for this purpose. + str = gsub(str, '\\(.)([^\\]?[^\\]?[^\\]?[^\\]?[^\\]?)', f_str_subst) + if f_str_surrogate_prev ~= 0 then + f_str_surrogate_prev = 0 + decode_error("1st surrogate pair byte not continued by 2nd") + end + end + if iskey then -- commit key cache + f_str_keycache[tmppos] = str + end + return str + end + + --[[ + Arrays, Objects + --]] + -- array + local function f_ary() + rec_depth = rec_depth + 1 + if rec_depth > 1000 then + decode_error('too deeply nested json (> 1000)') + end + local ary = {} + + pos = match(json, '^[ \n\r\t]*()', pos) + + local i = 0 + if byte(json, pos) == 0x5D then -- check closing bracket ']' which means the array empty + pos = pos+1 + else + local newpos = pos + repeat + i = i+1 + f = dispatcher[byte(json,newpos)] -- parse value + pos = newpos+1 + ary[i] = f() + newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) -- check comma + until not newpos + + newpos = match(json, '^[ \n\r\t]*%]()', pos) -- check closing bracket + if not newpos then + decode_error("no closing bracket of an array") + end + pos = newpos + end + + if arraylen then -- commit the length of the array if `arraylen` is set + ary[0] = i + end + rec_depth = rec_depth - 1 + return ary + end + + -- objects + local function f_obj() + rec_depth = rec_depth + 1 + if rec_depth > 1000 then + decode_error('too deeply nested json (> 1000)') + end + local obj = {} + + pos = match(json, '^[ \n\r\t]*()', pos) + if byte(json, pos) == 0x7D then -- check closing bracket '}' which means the object empty + pos = pos+1 + else + local newpos = pos + + repeat + if byte(json, newpos) ~= 0x22 then -- check '"' + decode_error("not key") + end + pos = newpos+1 + local key = f_str(true) -- parse key + + -- optimized for compact json + -- c1, c2 == ':', or + -- c1, c2, c3 == ':', ' ', + f = f_err + local c1, c2, c3 = byte(json, pos, pos+3) + if c1 == 0x3A then + if c2 ~= 0x20 then + f = dispatcher[c2] + newpos = pos+2 + else + f = dispatcher[c3] + newpos = pos+3 + end + end + if f == f_err then -- read a colon and arbitrary number of spaces + newpos = match(json, '^[ \n\r\t]*:[ \n\r\t]*()', pos) + if not newpos then + decode_error("no colon after a key") + end + f = dispatcher[byte(json, newpos)] + newpos = newpos+1 + end + pos = newpos + obj[key] = f() -- parse value + newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) + until not newpos + + newpos = match(json, '^[ \n\r\t]*}()', pos) + if not newpos then + decode_error("no closing bracket of an object") + end + pos = newpos + end + + rec_depth = rec_depth - 1 + return obj + end + + --[[ + The jump table to dispatch a parser for a value, + indexed by the code of the value's first char. + Nil key means the end of json. + --]] + dispatcher = { [0] = + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err, + f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num, + f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err, + f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err, + f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err, + __index = function() + decode_error("unexpected termination") + end + } + setmetatable(dispatcher, dispatcher) + + --[[ + run decoder + --]] + local function decode(json_, pos_, nullv_, arraylen_) + json, pos, nullv, arraylen = json_, pos_, nullv_, arraylen_ + rec_depth = 0 + + pos = match(json, '^[ \n\r\t]*()', pos) + + f = dispatcher[byte(json, pos)] + pos = pos+1 + local v = f() + + if pos_ then + return v, pos + else + f, pos = find(json, '^[ \n\r\t]*', pos) + if pos ~= #json then + decode_error('json ended') + end + return v + end + end + + return decode +end + +return newdecoder diff --git a/lib/lua/5.1/lunajson/encoder.lua b/lib/lua/5.1/lunajson/encoder.lua new file mode 100644 index 0000000000..5dea5ad73e --- /dev/null +++ b/lib/lua/5.1/lunajson/encoder.lua @@ -0,0 +1,208 @@ +--[=====[ +The MIT License (MIT) + +Copyright (c) 2015-2017 Shunsuke Shimizu (grafi) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--]=====] +local error = error +local byte, find, format, gsub, match = string.byte, string.find, string.format, string.gsub, string.match +local concat = table.concat +local tostring = tostring +local pairs, type = pairs, type +local setmetatable = setmetatable +local huge, tiny = 1/0, -1/0 + +local f_string_esc_pat +if _VERSION == "Lua 5.1" then + -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly + f_string_esc_pat = '[^ -!#-[%]^-\255]' +else + f_string_esc_pat = '[\0-\31"\\]' +end + +local _ENV = nil + + +local function newencoder() + local v, nullv + local i, builder, visited + + local function f_tostring(v) + builder[i] = tostring(v) + i = i+1 + end + + local radixmark = match(tostring(0.5), '[^0-9]') + local delimmark = match(tostring(12345.12345), '[^0-9' .. radixmark .. ']') + if radixmark == '.' then + radixmark = nil + end + + local radixordelim + if radixmark or delimmark then + radixordelim = true + if radixmark and find(radixmark, '%W') then + radixmark = '%' .. radixmark + end + if delimmark and find(delimmark, '%W') then + delimmark = '%' .. delimmark + end + end + + local f_number = function(n) + if tiny < n and n < huge then + local s = format("%.17g", n) + if radixordelim then + if delimmark then + s = gsub(s, delimmark, '') + end + if radixmark then + s = gsub(s, radixmark, '.') + end + end + builder[i] = s + i = i+1 + return + end + error('invalid number') + end + + local doencode + + local f_string_subst = { + ['"'] = '\\"', + ['\\'] = '\\\\', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t', + __index = function(_, c) + return format('\\u00%02X', byte(c)) + end + } + setmetatable(f_string_subst, f_string_subst) + + local function f_string(s) + builder[i] = '"' + if find(s, f_string_esc_pat) then + s = gsub(s, f_string_esc_pat, f_string_subst) + end + builder[i+1] = s + builder[i+2] = '"' + i = i+3 + end + + local function f_table(o) + if visited[o] then + error("loop detected") + end + visited[o] = true + + local tmp = o[0] + if type(tmp) == 'number' then -- arraylen available + builder[i] = '[' + i = i+1 + for j = 1, tmp do + doencode(o[j]) + builder[i] = ',' + i = i+1 + end + if tmp > 0 then + i = i-1 + end + builder[i] = ']' + + else + tmp = o[1] + if tmp ~= nil then -- detected as array + builder[i] = '[' + i = i+1 + local j = 2 + repeat + doencode(tmp) + tmp = o[j] + if tmp == nil then + break + end + j = j+1 + builder[i] = ',' + i = i+1 + until false + builder[i] = ']' + + else -- detected as object + builder[i] = '{' + i = i+1 + local tmp = i + for k, v in pairs(o) do + if type(k) ~= 'string' then + error("non-string key") + end + f_string(k) + builder[i] = ':' + i = i+1 + doencode(v) + builder[i] = ',' + i = i+1 + end + if i > tmp then + i = i-1 + end + builder[i] = '}' + end + end + + i = i+1 + visited[o] = nil + end + + local dispatcher = { + boolean = f_tostring, + number = f_number, + string = f_string, + table = f_table, + __index = function() + error("invalid type value") + end + } + setmetatable(dispatcher, dispatcher) + + function doencode(v) + if v == nullv then + builder[i] = 'null' + i = i+1 + return + end + return dispatcher[type(v)](v) + end + + local function encode(v_, nullv_) + v, nullv = v_, nullv_ + i, builder, visited = 1, {}, {} + + doencode(v) + return concat(builder) + end + + return encode +end + +return newencoder diff --git a/src/Etterna/Singletons/FilterManager.cpp b/src/Etterna/Singletons/FilterManager.cpp index 5536801cbb..de90b7f4ed 100644 --- a/src/Etterna/Singletons/FilterManager.cpp +++ b/src/Etterna/Singletons/FilterManager.cpp @@ -167,6 +167,12 @@ class LunaFilterManager : public Luna p->ExclusiveFilter = !p->ExclusiveFilter; return 0; } + static int SetFilterMode(T* p, lua_State* L) + { + bool ExclusiveFilter = BArg(1); + p->ExclusiveFilter = ExclusiveFilter; + return 0; + } static int GetFilterMode(T* p, lua_State* L) { lua_pushboolean(L, p->ExclusiveFilter); @@ -177,6 +183,12 @@ class LunaFilterManager : public Luna p->HighestSkillsetsOnly = !p->HighestSkillsetsOnly; return 0; } + static int SetHighestSkillsetsOnly(T* p, lua_State* L) + { + bool HighestSkillsetsOnly = BArg(1); + p->HighestSkillsetsOnly = HighestSkillsetsOnly; + return 0; + } static int GetHighestSkillsetsOnly(T* p, lua_State* L) { lua_pushboolean(L, p->HighestSkillsetsOnly); @@ -187,6 +199,12 @@ class LunaFilterManager : public Luna p->HighestDifficultyOnly = !p->HighestDifficultyOnly; return 0; } + static int SetHighestDifficultyOnly(T* p, lua_State* L) + { + bool HighestDifficultyOnly = BArg(1); + p->HighestDifficultyOnly = HighestDifficultyOnly; + return 0; + } static int GetHighestDifficultyOnly(T* p, lua_State* L) { lua_pushboolean(L, p->HighestDifficultyOnly); @@ -242,10 +260,13 @@ class LunaFilterManager : public Luna ADD_METHOD(SetMinFilterRate); ADD_METHOD(GetMinFilterRate); ADD_METHOD(ToggleFilterMode); + ADD_METHOD(SetFilterMode); ADD_METHOD(GetFilterMode); ADD_METHOD(ToggleHighestSkillsetsOnly); + ADD_METHOD(SetHighestSkillsetsOnly); ADD_METHOD(GetHighestSkillsetsOnly); ADD_METHOD(ToggleHighestDifficultyOnly); + ADD_METHOD(SetHighestDifficultyOnly); ADD_METHOD(GetHighestDifficultyOnly); ADD_METHOD(HelpImTrappedInAChineseFortuneCodingFactory); ADD_METHOD(oopsimlazylol); diff --git a/src/Etterna/Singletons/ThemeManager.cpp b/src/Etterna/Singletons/ThemeManager.cpp index ee1bed1caf..0b46f38d24 100644 --- a/src/Etterna/Singletons/ThemeManager.cpp +++ b/src/Etterna/Singletons/ThemeManager.cpp @@ -627,8 +627,15 @@ ThemeManager::UpdateLuaGlobals() // explicitly refresh cached metrics that we use. ScreenDimensions::ReloadScreenDimensions(); + // Include global lua libraries + AppendToLuaPackagePath("./lib/lua/5.1/?.lua"); + // run global scripts RunLuaScripts("*.lua"); + + // Include fallback theme lua libraries + AppendToLuaPackagePath("./Themes/_fallback/lib/lua/5.1/?.lua"); + // run theme scripts RunLuaScripts("*.lua", true); #endif