remove Screen class

switch to a event based page api design
This commit is contained in:
Hersi 2022-06-22 02:32:27 +02:00
parent 9b170a519f
commit 6fbc39e028
6 changed files with 181 additions and 275 deletions

View File

@ -5,39 +5,67 @@ local Wallpaper = require("components.wallpaper")
local PageView = require("api.page.pageview")
local BootPage = require("titlescreen.pages.boot.bootpage")
local BootPage = require("titlescreen.boot")
local SplashPage = require('titlescreen.splash')
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')
game.Log("HELLO FROM TITLESCREEN", game.LOGGER_DEBUG)
local screens = {
[tostring(BootScreen)] = BootScreen.new(),
--splash = splashScreen,
[BootPage.__name] = BootPage.new(),
[SplashPage.__name] = SplashPage.new(),
--title = titleScreen,
--[tostring(ModeSelectPage)] = ModeSelectPage.new(),
--[tostring(ServiceMenuPage)] = ServiceMenuPage.new()
}
local pageView = PageView.new()
pageView.onNavigated = function(self, back)
game.Log(tostring(self) .. " navigated " .. (back and "back " or "") .. "to: " .. tostring(pageView:get()),
game.LOGGER_INFO
)
end
pageView.onEmptied = function(self)
game.Log(tostring(self) .. " empty!", game.LOGGER_WARNING)
end
local function switchScreens(newScreen)
pageView:replace(newScreen)
pageView:get():init()
end
screens[BootPage.__name].onInvalidation = function(self)
switchScreens(screens[SplashPage.__name])
end
screens[SplashPage.__name].onInvalidation = function(self)
--TODO: TitlePage
--switchScreens(screens[TitlePage.__name])
end
--[[ TODO: remaining Pages
screens[TitlePage.__name].onInvalidation = function(self)
switchScreens(screens[ModeSelectPage.__name])
end
screens[ServiceMenuPage.__name].onInvalidation = function(self)
switchScreens(screens[SplashPage.__name])
end
]]
--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
switchScreens(screens[BootPage.__name])
local function deltaKnob(delta)
-- what the hell does this do?
-- can someone tell me what the hell does this do? - Hersi
if math.abs(delta) > 1.5 * math.pi then
return delta + 2 * math.pi * Common.sign(delta) * -1
end
@ -55,11 +83,15 @@ local function handleKnobs()
lastKnobs = newKnobs
if math.abs(knobProgress[1]) > knobThreshold then
currentScreen:handleKnobInput(game.KNOB_LEFT, knobProgress[1])
if pageView:get() then
pageView:get():handleKnobInput(game.KNOB_LEFT, knobProgress[1])
end
end
if math.abs(knobProgress[2]) > knobThreshold then
currentScreen:handleKnobInput(game.KNOB_RIGHT, knobProgress[2])
if pageView:get() then
pageView:get():handleKnobInput(game.KNOB_RIGHT, knobProgress[2])
end
end
end
end
@ -73,15 +105,19 @@ function render(deltaTime)
Dim.transformToScreenSpace()
currentScreen:render(deltaTime)
pageView:render(deltaTime)
end
function mouse_pressed(button)
local mouseX, mouseY = game.GetMousePos()
currentScreen:handleMouseInput(mouseX, mouseY, button)
if pageView:get() then
pageView:get():handleMouseInput(mouseX, mouseY, button)
end
return 0 --THIS '0' IS VERY IMPORTANT, IT WILL CRASH VERY HARD WITHOUT THIS
end
function button_pressed(button)
currentScreen:handleButtonInput(button)
if pageView:get() then
pageView:get():handleButtonInput(button)
end
end

View File

@ -1,34 +1,111 @@
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"
require("common.globals")
require("common.class")
require("common.filereader")
local Dim = require("common.dimensions")
local Version = require("common.version")
local Page = require("api.page.page")
local CheckUpdatePage = require("titlescreen.pages.boot.checkupdatepage")
local ServiceField = require("titlescreen.components.servicefield")
local ListField = require("titlescreen.components.listfield")
local SelfTestField = require("titlescreen.components.selftestfield")
---@class BootScreen : Screen
---@field bootpage BootPage
local BootScreen = {
__name = "BootScreen"
---@class BootPage: Page
local BootPage = {
__name = "BootPage",
}
---Create a new BootScreen instance
---@param params? BootScreen
---@return BootScreen
function BootScreen.new(params)
---Create a new BootPage instance
---@param params? BootPage # initial parameters
---@return BootPage
function BootPage.new(params)
params = params or {}
params.bootpage = params.bootpage or BootPage.new()
local self = CreateInstance(BootPage, params, Page)
return CreateInstance(BootScreen, params, Screen)
self._networkResult = {}
self:addField(ServiceField.new{posX = 32, posY = 32, label = Version.getLongVersion(), value = ""})
self:addField(ServiceField.new{posX = 64, posY = 64, label = "UNNAMED SDVX CLONE STARTUP...", value = ""})
local valueOffX = 220
self._mainIoTestField = SelfTestField.new{label = "MAIN I/O", VALUE_OFFSETX = valueOffX}
self._mainIoTestField.checkTask = function(obj)
return SelfTestStatus.OK
end
self._mainIoTestField.onStatusChange = function(_, status)
if status == SelfTestStatus.OK then
self._skinConfigTestField:activate()
end
end
function BootScreen:init()
Screen.init(self)
self.pageview:replace(self.bootpage)
self._skinConfigTestField = SelfTestField.new{label = "SKIN CONFIG", VALUE_OFFSETX = valueOffX}
self._skinConfigTestField.checkTask = function(obj)
local crewpath = "skins/" .. game.GetSkin() .. "/textures/crew/anim/" .. game.GetSkinSetting("single_idol")
if not IsDir(crewpath) then
return SelfTestStatus.ERROR
end
return SelfTestStatus.OK
end
self._skinConfigTestField.onStatusChange = function(_, status)
if status == SelfTestStatus.OK then
self._networkTestField:activate()
end
end
function BootScreen:deactivate()
self.onDeactivation({reason = "deactivation", hint = SplashScreen.__name})
self._networkTestField = SelfTestField.new{label = "NETWORK", VALUE_OFFSETX = valueOffX}
-- set up async network check
self._networkTestField.checkTask = function(obj)
local status = SelfTestStatus.INPROGRESS
if not IRData.Active then
return SelfTestStatus.PASS
end
return BootScreen
while status == SelfTestStatus.INPROGRESS do
if self._networkResult.statusCode == IRData.States.Success then
status = SelfTestStatus.OK
elseif self._networkResult.statusCode then
status = SelfTestStatus.ERROR -- there's a response, but it's not success
end
coroutine.yield(status)
end
return status
end
self._networkTestField.onStatusChange = function(_, status)
if status == SelfTestStatus.INPROGRESS then
IR.Heartbeat(function(res) self._networkResult = res end) -- IR doesn't like being called in a coroutine
elseif status == SelfTestStatus.PASS or status == SelfTestStatus.OK then
if self.viewHandler then
self.viewHandler:navigate(CheckUpdatePage.new())
end
end
end
self._mainIoTestField:init()
self._skinConfigTestField:init()
self._networkTestField:init()
local list = ListField.new{posX = 64, posY = 96}
list:addField(self._mainIoTestField)
list:addField(self._skinConfigTestField)
list:addField(self._networkTestField)
self:addField(list)
return self
end
function BootPage:init()
self._mainIoTestField:activate()
end
---@param deltaTime number # frametime in seconds
function BootPage:drawBackground(deltaTime)
gfx.BeginPath()
gfx.FillColor(0, 0, 0)
gfx.Rect(0, 0, Dim.design.width, Dim.design.height)
gfx.Fill()
end
return BootPage

View File

@ -2,8 +2,8 @@ require("common.class")
local Util = require("common.util")
local ServiceField = require("titlescreen.components.servicefield")
---@class SelfTestStatusEnum
SelfTestStatusEnum = {
---@class SelfTestStatus
SelfTestStatus = {
IDLE = 1,
INPROGRESS = 2,
OK = 3,
@ -17,11 +17,9 @@ local function statusToString(status)
end
---@class SelfTestField: ServiceField
---@field checkTask nil|fun(obj: any): SelfTestStatusEnum # a function that will run asynchronously on activating the Field
---@field status SelfTestStatusEnum
---@field onStatusChange nil|fun(status) # a callback function on finishing the checkTask
---@field checkTask nil|fun(): SelfTestStatus # a function that will run asynchronously on activating the Field
---@field status SelfTestStatus
---@field _thread thread
---@field _timer number
local SelfTestField = {
__name = "SelfTestField",
COLOR_INPROGRESS = {255, 255, 255, 255},
@ -37,20 +35,23 @@ local SelfTestField = {
function SelfTestField.new(params)
params = params or {}
params.status = params.status or SelfTestStatusEnum.IDLE
assert((not params.onStatusChange) or (params.checkTask and params.onStatusChange),
"Failed to construct SelfTestField, checkTask is mandatory when onStatusChange is defined!\n" .. debug.traceback()
)
params.status = params.status or SelfTestStatus.IDLE
local self = CreateInstance(SelfTestField, params, ServiceField)
self._timer = 0
self._thread = nil
return self
end
function SelfTestField:init()
self._thread = coroutine.create(self.checkTask)
end
function SelfTestField.checkTask()
return SelfTestStatus.PASS
end
function SelfTestField:_closeThread()
if self._thread and coroutine.status(self._thread) ~= "dead" then
coroutine.close(self._thread)
@ -71,7 +72,7 @@ function SelfTestField:_resumeThread()
statusToString(status) .. ")",
game.LOGGER_DEBUG
)
self.onStatusChange(status)
self:onStatusChange(status)
end
end
end
@ -79,12 +80,8 @@ end
function SelfTestField:activate(obj)
self:_closeThread()
if self.checkTask then
self._thread = coroutine.create(self.checkTask)
self:_resumeThread()
end
end
function SelfTestField:deactivate(obj)
self:_closeThread()
@ -92,10 +89,11 @@ end
function SelfTestField:tick(deltaTime)
self:_resumeThread()
self._timer = self._timer + deltaTime
end
---@param status SelfTestStatus
function SelfTestField:onStatusChange(status) end
function SelfTestField:drawValue(deltaTime)
gfx.Translate(self.VALUE_OFFSETX, 0)
@ -104,22 +102,22 @@ function SelfTestField:drawValue(deltaTime)
gfx.Text(": ", 0, 0)
local color, text
if self.status == SelfTestStatusEnum.IDLE then
if self.status == SelfTestStatus.IDLE then
color = self.FONT_COLOR
text = ""
elseif self.status == SelfTestStatusEnum.INPROGRESS then
elseif self.status == SelfTestStatus.INPROGRESS then
local progress = math.ceil(Util.lerp(self._timer % self.INPROGRESS_FREQ,
0, 0, self.INPROGRESS_FREQ, 4
))
color = self.COLOR_INPROGRESS
text = string.rep(".", progress)
elseif self.status == SelfTestStatusEnum.OK then
elseif self.status == SelfTestStatus.OK then
color = self.COLOR_OK
text = "OK"
elseif self.status == SelfTestStatusEnum.PASS then
elseif self.status == SelfTestStatus.PASS then
color = self.COLOR_PASS
text = "PASS"
elseif self.status == SelfTestStatusEnum.ERROR then
elseif self.status == SelfTestStatus.ERROR then
color = self.COLOR_ERROR
text = "ERROR"
end

View File

@ -2,18 +2,10 @@ require("common.class")
local Dim = require("common.dimensions")
local Field = require("api.page.field")
---@class ServiceFieldState
ServiceFieldState = {
INACTIVE = 0,
FOCUSED = 1,
ACTIVE = 2
}
---@class ServiceField: Field
---@field label string
---@field value any
---@field footer string|string[]
---@field _state ServiceFieldState
---@field FONT_SIZE number
---@field FONT_FACE string
---@field FONT_COLOR integer[] # {r, g, b, a}
@ -49,8 +41,6 @@ function ServiceField.new(params)
local self = CreateInstance(ServiceField, params, Field)
self._state = ServiceFieldState.INACTIVE
if self.aabbH < h then
self.aabbH = h
end
@ -58,25 +48,10 @@ function ServiceField.new(params)
return self
end
---@param obj? any # message object for the field
function ServiceField:activate(obj)
self._state = ServiceFieldState.ACTIVE
end
---@param obj? any # message object for the field
function ServiceField:focus(obj)
self._state = ServiceFieldState.FOCUSED
end
---@param obj? any # message object for the field
function ServiceField:deactivate(obj)
self._state = ServiceFieldState.INACTIVE
end
---@param deltaTime number # frametime in seconds
function ServiceField:drawLabel(deltaTime)
local color
if self._state == ServiceFieldState.FOCUSED then
if self.focused then
color = self.FONT_FOCUSED_COLOR
else
color = self.FONT_COLOR

View File

@ -1,111 +0,0 @@
require("common.class")
require("common.filereader")
local Dim = require("common.dimensions")
local Version = require("common.version")
local Page = require("api.page.page")
local CheckUpdatePage = require("titlescreen.pages.boot.checkupdatepage")
local ServiceField = require("titlescreen.components.servicefield")
local ListField = require("titlescreen.components.listfield")
local SelfTestField = require("titlescreen.components.selftestfield")
---@class BootPage: Page
local BootPage = {
__name = "BootPage",
}
---Create a new BootPage instance
---@param params? BootPage # initial parameters
---@return BootPage
function BootPage.new(params)
params = params or {}
local self = CreateInstance(BootPage, params, Page)
self._networkResult = {}
self:addField(ServiceField.new{posX = 32, posY = 32, label = Version.getLongVersion(), value = ""})
self:addField(ServiceField.new{posX = 64, posY = 64, label = "UNNAMED SDVX CLONE STARTUP...", value = ""})
local valueOffX = 220
self._mainIoTestField = SelfTestField.new{label = "MAIN I/O", VALUE_OFFSETX = valueOffX}
self._mainIoTestField.checkTask = function(obj)
return SelfTestStatusEnum.OK
end
self._mainIoTestField.onStatusChange = function(status)
if status == SelfTestStatusEnum.OK then
self._skinConfigTestField:activate()
end
end
self._skinConfigTestField = SelfTestField.new{label = "SKIN CONFIG", VALUE_OFFSETX = valueOffX}
self._skinConfigTestField.checkTask = function(obj)
local crewpath = "skins/" .. game.GetSkin() .. "/textures/crew/anim/" .. game.GetSkinSetting("single_idol")
if not IsDir(crewpath) then
return SelfTestStatusEnum.ERROR
end
return SelfTestStatusEnum.OK
end
self._skinConfigTestField.onStatusChange = function(status)
if status == SelfTestStatusEnum.OK then
self._networkTestField:activate()
end
end
self._networkTestField = SelfTestField.new{label = "NETWORK", VALUE_OFFSETX = valueOffX}
-- set up async network check
self._networkTestField.checkTask = function(obj)
local status = SelfTestStatusEnum.INPROGRESS
if not IRData.Active then
return SelfTestStatusEnum.PASS
end
while status == SelfTestStatusEnum.INPROGRESS do
if self._networkResult.statusCode == IRData.States.Success then
status = SelfTestStatusEnum.OK
elseif self._networkResult.statusCode then
status = SelfTestStatusEnum.ERROR -- there's a response, but it's not success
end
coroutine.yield(status)
end
return status
end
self._networkTestField.onStatusChange = function(status)
if status == SelfTestStatusEnum.INPROGRESS then
IR.Heartbeat(function(res) self._networkResult = res end) -- IR doesn't like being called in a coroutine
elseif status == SelfTestStatusEnum.PASS or status == SelfTestStatusEnum.OK then
if self.viewHandler then
self.viewHandler:navigate(CheckUpdatePage.new())
end
end
end
local list = ListField.new{posX = 64, posY = 96}
list:addField(self._mainIoTestField)
list:addField(self._skinConfigTestField)
list:addField(self._networkTestField)
self:addField(list)
return self
end
---@param deltaTime number # frametime in seconds
function BootPage:drawBackground(deltaTime)
gfx.BeginPath()
gfx.FillColor(0, 0, 0)
gfx.Rect(0, 0, Dim.design.width, Dim.design.height)
gfx.Fill()
end
local first = true
function BootPage:render(deltaTime)
if first then
self._mainIoTestField:activate()
first = false
end
Page.render(self, deltaTime)
end
return BootPage

View File

@ -1,69 +0,0 @@
require "common.globals"
require "common.class"
local PageView = require "api.page.pageview"
---@class Screen
---@field pageview PageView
local Screen = {
__name = "Screen"
}
---Create a new Screen instance
---@param params? Screen
---@return Screen
function Screen.new(params)
local self = CreateInstance(Screen, params)
self.pageview = PageView.new()
return self
end
---Initialize screen
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)
self.pageview:render(deltaTime)
end
return Screen