function clamp(x, min, max) if x < min then x = min end if x > max then x = max end return x end function smootherstep(edge0, edge1, x) -- Scale, and clamp x to 0..1 range x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0) -- Evaluate polynomial return x * x * x * (x * (x * 6 - 15) + 10) end function to_range(val, start, stop) return start + (stop - start) * val end Animation = { start = 0, stop = 0, progress = 0, duration = 1, smoothStart = false } function Animation:new(o) o = o or {} setmetatable(o, self) self.__index = self return o end function Animation:restart(start, stop, duration) self.progress = 0 self.start = start self.stop = stop self.duration = duration end function Animation:tick(deltaTime) self.progress = math.min(1, self.progress + deltaTime / self.duration) if self.progress == 1 then return self.stop end if self.smoothStart then return to_range(smootherstep(0, 1, self.progress), self.start, self.stop) else return to_range(smootherstep(-1, 1, self.progress) * 2 - 1, self.start, self.stop) end end local yScale = Animation:new() local diagWidth = 600 local diagHeight = 400 local tabStroke = {start=0, stop=1} local tabStrokeAnimation = {start=Animation:new(), stop=Animation:new()} local settingsStrokeAnimation = {x=Animation:new(), y=Animation:new()} local prevTab = -1 local prevSettingStroke = {x=0, y=0} local settingStroke = {x=0, y=0} local prevVis = false function processSkinSettings() for ti, tab in ipairs(SettingsDiag.tabs) do for si, setting in ipairs(tab.settings) do if (tab.name == 'Game') then if (setting.name == 'Gauge') then game.SetSkinSetting('_gaugeType', setting.value); end if (setting.name == 'Backup Gauge') then game.SetSkinSetting('_gaugeARS', setting.value and 1 or 0); end end end end end function render(deltaTime, visible) if visible and not prevVis then yScale:restart(0, 1, 0.25) elseif not visible and prevVis then yScale:restart(1, 0, 0.25) end processSkinSettings() if not visible and yScale:tick(0) < 0.05 then return end local posX = SettingsDiag.posX or 0.5 local posY = SettingsDiag.posY or 0.5 local message_1 = "Press both FXs to open/close. Use the Start button to press buttons." local message_2 = "Use FX keys to navigate tabs. Use arrow keys to navigate and modify settings." resX, resY = game.GetResolution() local scale = resY / 1080 gfx.ResetTransform() gfx.Translate(math.floor(diagWidth/2 + posX*(resX-diagWidth)), math.floor(diagHeight/2 + posY*(resY-diagHeight))) gfx.Scale(scale, scale) gfx.Scale(1.0, smootherstep(0, 1, yScale:tick(deltaTime))) gfx.BeginPath() gfx.Rect(-diagWidth/2, -diagHeight/2, diagWidth, diagHeight) gfx.FillColor(50,50,50) gfx.Fill() gfx.FillColor(255,255,255) gfx.FontSize(20) local m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_1) gfx.Text(message_1, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax - 20) m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_2) gfx.Text(message_2, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax) tabStroke.start = tabStrokeAnimation.start:tick(deltaTime) tabStroke.stop = tabStrokeAnimation.stop:tick(deltaTime) settingStroke.x = settingsStrokeAnimation.x:tick(deltaTime) settingStroke.y = settingsStrokeAnimation.y:tick(deltaTime) local tabBarHeight = 0 local nextTabX = 5 gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT) gfx.FontSize(35) gfx.Save() --draw tab bar gfx.Translate(-diagWidth / 2, -diagHeight / 2) for ti, tab in ipairs(SettingsDiag.tabs) do local xmin,ymin, xmax,ymax = gfx.TextBounds(nextTabX, 5, tab.name) if ti == SettingsDiag.currentTab and SettingsDiag.currentTab ~= prevTab then tabStrokeAnimation.start:restart(tabStroke.start, nextTabX, 0.1) tabStrokeAnimation.stop:restart(tabStroke.stop, xmax, 0.1) end tabBarHeight = math.max(tabBarHeight, ymax + 5) gfx.Text(tab.name, nextTabX, 5) nextTabX = xmax + 10 end gfx.BeginPath() gfx.MoveTo(0, tabBarHeight) gfx.LineTo(diagWidth, tabBarHeight) gfx.StrokeWidth(2) gfx.StrokeColor(0,127,255) gfx.Stroke() gfx.BeginPath() gfx.MoveTo(tabStroke.start, tabBarHeight) gfx.LineTo(tabStroke.stop, tabBarHeight) gfx.StrokeColor(255, 127, 0) gfx.Stroke() gfx.Restore() --draw tab bar end gfx.FontSize(30) gfx.Save() --draw current tab gfx.Translate(-diagWidth / 2, -diagHeight / 2) gfx.Translate(5, tabBarHeight + 5) gfx.BeginPath() gfx.MoveTo(0, settingStroke.y) gfx.LineTo(settingStroke.x, settingStroke.y) gfx.StrokeWidth(2) gfx.StrokeColor(255, 127, 0) gfx.Stroke() local settingHeight = 30 local tab = SettingsDiag.tabs[SettingsDiag.currentTab] for si, setting in ipairs(tab.settings) do processSkinSettings(setting.name, setting.value) local disp = "" if setting.type == "enum" then disp = string.format("%s: %s", setting.name, setting.options[setting.value]) elseif setting.type == "int" then disp = string.format("%s: %d", setting.name, setting.value) elseif setting.type == "float" then disp = string.format("%s: %.2f", setting.name, setting.value) if setting.max == 1 and setting.min == 0 then --draw slider disp = setting.name .. ": " local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp) local width = diagWidth - 20 - xmax gfx.BeginPath() gfx.MoveTo(xmax + 5, 20) gfx.LineTo(xmax + 5 + width, 20) gfx.StrokeColor(0,127,255) gfx.StrokeWidth(2) gfx.Stroke() gfx.BeginPath() gfx.MoveTo(xmax + 5, 20) gfx.LineTo(xmax + 5 + width * setting.value, 20) gfx.StrokeColor(255,127,0) gfx.StrokeWidth(2) gfx.Stroke() end elseif setting.type == "button" then disp = string.format("%s", setting.name) local xmin, ymin, xmax,ymax = gfx.TextBounds(0, 0, disp) gfx.BeginPath() gfx.Rect(-2, 3, 4+xmax-xmin, 28) gfx.FillColor(0, 64, 128) if si == SettingsDiag.currentSetting then gfx.StrokeColor(255, 127, 0) else gfx.StrokeColor(0,127,255) end gfx.StrokeWidth(2) gfx.Fill() gfx.Stroke() gfx.FillColor(255,255,255) else disp = string.format("%s:", setting.name) local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp) gfx.BeginPath() gfx.Rect(xmax + 5, 5, 20,20) gfx.FillColor(255, 127, 0, setting.value and 255 or 0) gfx.StrokeColor(0,127,255) gfx.StrokeWidth(2) gfx.Fill() gfx.Stroke() gfx.FillColor(255,255,255) end gfx.Text(disp, 0 ,0) if si == SettingsDiag.currentSetting then local setting_name = setting.name .. ":" if setting.type == "button" then setting_name = setting.name end local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, setting_name) ymax = ymax + settingHeight * (si - 1) if xmax ~= prevSettingStroke.x or ymax ~= prevSettingStroke.y then settingsStrokeAnimation.x:restart(settingStroke.x, xmax, 0.1) settingsStrokeAnimation.y:restart(settingStroke.y, ymax, 0.1) end prevSettingStroke.x = xmax prevSettingStroke.y = ymax end gfx.Translate(0, settingHeight) end gfx.Restore() --draw current tab end prevTab = SettingsDiag.currentTab prevVis = visible end