diff --git a/.gitignore b/.gitignore index 8912bcc..4825237 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # IDE files .vscode +docs/diagrams/export # secret(?) assets _asset diff --git a/docs/diagrams/sequence/titlescreenScreenChangeEvent.puml b/docs/diagrams/sequence/titlescreenScreenChangeEvent.puml new file mode 100644 index 0000000..6ab5a01 --- /dev/null +++ b/docs/diagrams/sequence/titlescreenScreenChangeEvent.puml @@ -0,0 +1,35 @@ +@startuml titlescreen onScreenChange event +!theme materia-outline +skinparam DefaultFontName Courier +skinparam Shadowing false + +participant usc +participant "titlescreen.lua" as main +collections screens +collections pages + +activate main +activate screens +activate pages + +hnote across +Screen loaded and page displayed +endhnote + +pages -> screens : change screen event\n(eg. goes out of scope) +deactivate pages +screens --> main : onDeactivation(obj) +deactivate screens + +main -> main : handle replacing screen\nby inspecting `obj` + +main -> screens : call current screen's init() +activate screens + +screens -> usc : set current screen as last screen value + +screens -> pages : init() + +activate pages + +@enduml diff --git a/docs/diagrams/sequence/titlescreenStartup.puml b/docs/diagrams/sequence/titlescreenStartup.puml new file mode 100644 index 0000000..78a11e0 --- /dev/null +++ b/docs/diagrams/sequence/titlescreenStartup.puml @@ -0,0 +1,48 @@ +@startuml titlescreen startup +!theme materia-outline +skinparam DefaultFontName Courier +skinparam Shadowing false + +participant usc +participant "titlescreen.lua" as main +collections screens +collections pages + +usc -> main : load titlescreen.lua + +activate main + +group construct screens + main -> screens : create + screens -> pages : create + screens -> pages : set callbacks + main -> screens : set callbacks + main -> usc : get persistent states + main <-- usc + main -> screens : load persistent previous state values +end + +main -> usc : get last screen value +main <-- usc + +main -> main : set last screen value as current screen + +main -> screens : call current screen's init() + +activate screens + +screens -> usc : set current screen as last screen value + +screens -> pages : init() +activate pages + +loop main render loop + main -> screens : call current screen's render() + activate screens + screens -> pages : render() + activate pages + deactivate screens + deactivate pages +end + +@enduml diff --git a/scripts/api/page/page.lua b/scripts/api/page/page.lua index 8496f0f..b0c5348 100644 --- a/scripts/api/page/page.lua +++ b/scripts/api/page/page.lua @@ -9,19 +9,22 @@ local Page = { } ---Create a new Page instance ----@param o? table # initial parameters +---@param params? table # initial parameters ---@return Page -function Page.new(o) - o = o or {} +function Page.new(params) + params = params or {} - --set instance members + --set default parameters - o.content = o.content or {} - o.viewHandler = o.viewHandler or nil + params.content = params.content or {} + params.viewHandler = params.viewHandler or nil - return CreateInstance(Page, o) + return CreateInstance(Page, params) end +---Initialize Page +function Page:init() end + ---Add field to page ---@param field Field function Page:addField(field) @@ -49,6 +52,11 @@ end ---@param delta number # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW) function Page:handleKnobInput(knob, delta) end +---@param x number +---@param y number +---@param button integer +function Page:handleMouseInput(x, y, button) end + ---@param deltaTime number # frametime in seconds function Page:drawBackground(deltaTime) end diff --git a/scripts/api/page/pageview.lua b/scripts/api/page/pageview.lua index 4fa2725..d1c3023 100644 --- a/scripts/api/page/pageview.lua +++ b/scripts/api/page/pageview.lua @@ -15,21 +15,11 @@ local function popStack(t) end ---Create a new PageView instance ----@param rootPage Page ---@return PageView -function PageView.new(rootPage) - local o = {} - - --set viewHandler as this instance for rootPage - - rootPage.viewHandler = o - - --set instance members - - o.pageStack = {} - pushStack(o.pageStack, rootPage) - - return CreateInstance(PageView, o) +function PageView.new() + local self = CreateInstance(PageView, {}) + self.pageStack = {} + return self end ---Get page from pageStack diff --git a/scripts/common/globals.lua b/scripts/common/globals.lua index b3fbf1a..a2478e9 100644 --- a/scripts/common/globals.lua +++ b/scripts/common/globals.lua @@ -1,2 +1,10 @@ ---Drewol, what are you doing? Why is there no game.LOGGER_DEBUG? game.LOGGER_DEBUG = 0 + +-- add missing inputs +game.MOUSE_LEFT = 0 +game.MOUSE_RIGHT = 1 +game.MOUSE_MIDDLE = 2 + +game.KNOB_LEFT = 0 +game.KNOB_RIGHT = 1 diff --git a/scripts/titlescreen.lua b/scripts/titlescreen.lua index b50be37..e079921 100644 --- a/scripts/titlescreen.lua +++ b/scripts/titlescreen.lua @@ -1,33 +1,43 @@ require("common.globals") local Common = require("common.util") +local Dim = require("common.dimensions") +local Wallpaper = require("components.wallpaper") + +local PageView = require("api.page.pageview") + +local BootPage = require("titlescreen.pages.boot.bootpage") +local ModeSelectPage = require("titlescreen.pages.modeselect.modeselectpage") +local ServiceMenuPage = require("titlescreen.pages.service.mainmenupage") + +local BootScreen = require('titlescreen.boot') +local SplashScreen = require('titlescreen.splash') +local TitleScreen = require('titlescreen.title') +local ModeSelectScreen = require('titlescreen.modeselect') +local ServiceScreen = require('titlescreen.service') -local bootScreen = require('titlescreen.boot') -local splashScreen = require('titlescreen.splash') -local titleScreen = require('titlescreen.title') -local modeSelectScreen = require('titlescreen.modeselect') -local serviceScreen = require('titlescreen.service') local screens = { - boot = { - screen = bootScreen - }, - splash = { - screen = splashScreen - }, - title = { - screen = titleScreen - }, - mode_select = { - screen = modeSelectScreen - }, - service = { - screen = serviceScreen - } + [tostring(BootScreen)] = BootScreen.new(), + --splash = splashScreen, + --title = titleScreen, + --[tostring(ModeSelectPage)] = ModeSelectPage.new(), + --[tostring(ServiceMenuPage)] = ServiceMenuPage.new() } -local currentScreen = game.GetSkinSetting("animations_skipIntro") and screens.title or screens.boot -- show boot screen if skipIntro is not set +--local currentScreen = game.GetSkinSetting("animations_skipIntro") and screens.title or screens.boot -- show boot screen if skipIntro is not set +local currentScreen = screens[tostring(BootScreen)] + +---@param obj ScreenCallbackObject +local function onDeactivationCallback(obj) + currentScreen = screens[obj.hint] +end + +for _, value in pairs(screens) do + value.onDeactivation = onDeactivationCallback +end local function deltaKnob(delta) + -- what the hell does this do? if math.abs(delta) > 1.5 * math.pi then return delta + 2 * math.pi * Common.sign(delta) * -1 end @@ -35,44 +45,21 @@ local function deltaKnob(delta) end local lastKnobs = nil -local knobProgress = 0 +local knobThreshold = (2 * math.pi) / 360 local function handleKnobs() - if not currentScreen.screen.onKnobsChange then - return - end - if lastKnobs == nil then - lastKnobs = {game.GetKnob(0), game.GetKnob(1)} + lastKnobs = {game.GetKnob(game.KNOB_LEFT), game.GetKnob(game.KNOB_RIGHT)} else - local newKnobs = {game.GetKnob(0), game.GetKnob(1)} - - knobProgress = knobProgress - deltaKnob(lastKnobs[1] - newKnobs[1]) * 1.2 - knobProgress = knobProgress - deltaKnob(lastKnobs[2] - newKnobs[2]) * 1.2 - + local newKnobs = {game.GetKnob(game.KNOB_LEFT), game.GetKnob(game.KNOB_RIGHT)} + local knobProgress = {deltaKnob(lastKnobs[1] - newKnobs[1]), deltaKnob(lastKnobs[2] - newKnobs[2])} lastKnobs = newKnobs - if math.abs(knobProgress) > 1 then - if (knobProgress < 0) then - -- Negative - currentScreen.screen.onKnobsChange(-1) - else - -- Positive - currentScreen.screen.onKnobsChange(1) - end - knobProgress = knobProgress - Common.roundToZero(knobProgress) + if math.abs(knobProgress[1]) > knobThreshold then + currentScreen:handleKnobInput(game.KNOB_LEFT, knobProgress[1]) end - end -end -local function handleScreenResponse(res) - if res and res.eventType == 'switch' then - if not screens[res.toScreen] then - game.Log('Undefined screen ' .. res.toScreen, game.LOGGER_ERROR) - return - end - currentScreen = screens[res.toScreen] - if currentScreen.screen.reset then - currentScreen.screen.reset() + if math.abs(knobProgress[2]) > knobThreshold then + currentScreen:handleKnobInput(game.KNOB_RIGHT, knobProgress[2]) end end end @@ -80,18 +67,21 @@ end function render(deltaTime) handleKnobs() - handleScreenResponse(currentScreen.screen.render(deltaTime)) + Dim.updateResolution() + + Wallpaper.render() + + Dim.transformToScreenSpace() + + currentScreen:render(deltaTime) end function mouse_pressed(button) - if (currentScreen.screen.onMousePressed) then - currentScreen.screen.onMousePressed(button) - end - return 0 + local mouseX, mouseY = game.GetMousePos() + currentScreen:handleMouseInput(mouseX, mouseY, button) + return 0 --THIS '0' IS VERY IMPORTANT, IT WILL CRASH VERY HARD WITHOUT THIS end function button_pressed(button) - if (currentScreen.screen.onButtonPressed) then - currentScreen.screen.onButtonPressed(button) - end + currentScreen:handleButtonInput(button) end diff --git a/scripts/titlescreen/boot.lua b/scripts/titlescreen/boot.lua index d58684a..48bc694 100644 --- a/scripts/titlescreen/boot.lua +++ b/scripts/titlescreen/boot.lua @@ -1,28 +1,33 @@ -local Dim = require("common.dimensions") -local Wallpaper = require("components.wallpaper") -local BootPage = require("titlescreen.pages.boot.bootpage") -local PageView = require("api.page.pageview") +require "common.globals" +require "common.class" +local Screen = require "titlescreen.screen" +local BootPage = require "titlescreen.pages.boot.bootpage" +local PageView = require "api.page.pageview" +local SplashScreen = require "titlescreen.splash" -local bootpage = BootPage.new() -local pageview = PageView.new(bootpage) +---@class BootScreen : Screen +---@field bootpage BootPage +local BootScreen = { + __tostring = function() return "BootScreen" end +} -local function render(deltaTime) - Dim.updateResolution() +---Create a new BootScreen instance +---@param o? BootScreen +---@return BootScreen +function BootScreen.new(o) + o = o or {} - Wallpaper.render() + o.bootpage = o.bootpage or BootPage.new() - Dim.transformToScreenSpace() - - pageview:render(deltaTime) - - --pageview will be empty when you `back()` out of the root page - if not pageview:get() then - return {eventType = "switch", toScreen = "splash"} - end + return CreateInstance(BootScreen, o, Screen) end -local function onButtonPressed(button) - pageview:get():handleButtonInput(button) +function BootScreen:init() + self.pageview:replace(self.bootpage) end -return {render = render, onButtonPressed = onButtonPressed} \ No newline at end of file +function BootScreen:deactivate() + self.onDeactivation({reason = "deactivation", hint = tostring(SplashScreen)}) +end + +return BootScreen \ No newline at end of file diff --git a/scripts/titlescreen/modeselect.lua b/scripts/titlescreen/modeselect.lua index 2036c5c..98f1ff6 100644 --- a/scripts/titlescreen/modeselect.lua +++ b/scripts/titlescreen/modeselect.lua @@ -349,11 +349,17 @@ local function tickTransitions(deltaTime) end end +local PageView = require "api.page.pageview" +local ModeSelectPage = require "titlescreen.pages.modeselect.modeselectpage" +local PageViewInstance = PageView.new(ModeSelectPage.new()) + local function render(deltaTime) + --[[ if not playedBgm then game.PlaySample(resources.audiosamples.bgm, true) playedBgm = true end + ]] game.SetSkinSetting("_currentScreen", "title") @@ -365,7 +371,8 @@ local function render(deltaTime) tickTransitions(deltaTime) - draw_titlescreen(deltaTime) + --draw_titlescreen(deltaTime) + PageViewInstance:render(deltaTime) if (triggerServiceMenu) then triggerServiceMenu = false @@ -373,6 +380,10 @@ local function render(deltaTime) end end +local function reset() + PageViewInstance:get():init() +end + local function callButtonAction() if buttons[cursorIndex].action == nil then setButtonActions() end buttons[cursorIndex].action() @@ -459,6 +470,7 @@ end return { render = render, + reset = reset, onKnobsChange = onKnobsChange, onButtonPressed = onButtonPressed, onMousePressed = onMousePressed, diff --git a/scripts/titlescreen/pages/modeselect/modeselectpage.lua b/scripts/titlescreen/pages/modeselect/modeselectpage.lua index 26afb69..64a8dca 100644 --- a/scripts/titlescreen/pages/modeselect/modeselectpage.lua +++ b/scripts/titlescreen/pages/modeselect/modeselectpage.lua @@ -5,6 +5,7 @@ local AudioSample = require "api.audiosample" local Animation = require "api.animation" local Page = require "api.page.page" +local Background = require "components.background" local Footer = require "components.footer" local Header = require "components.headers.modeSelectHeader" @@ -13,6 +14,7 @@ local crew = game.GetSkinSetting("single_idol") ---@class ModeSelectPage: Page ---@field _idolAnimationState AnimationState local ModeSelectPage = { + __tostring = function () return "ModeSelectPage" end, images = { selectorBgImage = gfx.CreateSkinImage("titlescreen/selector_bg.png", 0), selectorArrowsImage = gfx.CreateSkinImage("titlescreen/selector_arrows.png", 0), @@ -40,15 +42,18 @@ local ModeSelectPage = { } function ModeSelectPage.new(o) - local this = CreateInstance(ModeSelectPage, o, Page) + local self = CreateInstance(ModeSelectPage, o, Page) - this._idolAnimationState = this.anims.idolAnimation:start() - this.audiosamples.bgm:play() + return self +end - return this +function ModeSelectPage:init() + self._idolAnimationState = self.anims.idolAnimation:start() + self.audiosamples.bgm:play() end function ModeSelectPage:drawBackground(deltaTime) + Background.draw(deltaTime) self._idolAnimationState:render(deltaTime) end diff --git a/scripts/titlescreen/screen.lua b/scripts/titlescreen/screen.lua new file mode 100644 index 0000000..a8a17ec --- /dev/null +++ b/scripts/titlescreen/screen.lua @@ -0,0 +1,73 @@ +require "common.globals" +require "common.class" + +local PageView = require "api.page.pageview" + +---@class Screen +---@field pageview PageView +local Screen = { + __tostring = function() return "Screen" end +} + +---Create a new Screen instance +---@param o? Screen +---@return Screen +function Screen.new(o) + local self = CreateInstance(Screen, o) + self.pageview = PageView.new() + return self +end + +---Initialize screen, override to push new page into pageview +function Screen:init() + +end + +---@param button integer # options are under the `game` table prefixed with `BUTTON` +function Screen:handleButtonInput(button) + if self.pageview:get() then + self.pageview:get():handleButtonInput(button) + end +end + +---@param knob integer # `0` = Left, `1` = Right +---@param delta number # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW) +function Screen:handleKnobInput(knob, delta) + if self.pageview:get() then + self.pageview:get():handleKnobInput(knob, delta) + end +end + +---@param x number +---@param y number +---@param button integer # `0` = Left, `1` = Right, `2` = Middle +function Screen:handleMouseInput(x, y, button) + if self.pageview:get() then + self.pageview:get():handleMouseInput(x, y, button) + end +end + +---@class ScreenCallbackObject +---@field reason string # short string representation what the cb object is about +---@field hint any # hint object to help continue code flow in parent + +---Event callback when screen gets deactivated (eg.: pageview is empty) +---@param obj ScreenCallbackObject +function Screen.onDeactivation(obj) + +end + +function Screen:deactivate() + self.onDeactivation({reason = "deactivation"}) +end + +---@param deltaTime number # frametime in seconds +function Screen:render(deltaTime) + if not self.pageview:get() then + self:deactivate() + end + + self.pageview:render(deltaTime) +end + +return Screen