components_data_SceneManager.bs
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/translationKeys.bs"
import "pkg:/source/utils/misc.bs"
import "pkg:/source/utils/translate.bs"
sub init()
m.log = new log.Logger("SceneManager")
m.scene = m.top.getScene()
m.overhang = m.scene.findNode("overhang")
end sub
' SceneManager is purely a shared SERVICE node — dialogs, toast/spinner, backdrop, theme, and
' the overhang user/clock passthroughs below. The router host (JRScene) owns navigation and its
' own overhang controller; there is no scene stack (pushScene / popScene / getActiveScene etc.).
' Signal that home screen should be reloaded (used after theme color changes)
' Main.bs observes reloadHomeRequested field and handles the actual reload
sub reloadHome()
m.top.reloadHomeRequested = true
end sub
' Update username in overhang
sub updateUser()
' Passthrough to overhang
if isValid(m.overhang) then m.overhang.currentUser = m.top.currentUser
end sub
' Reset time
sub resetTime()
' Passthrough to overhang
m.overhang.callFunc("resetTime")
end sub
' Set the background image
' @param {string} uri - The image URI to display
' @param {boolean} isAnimated - Whether to animate the transition
' @param {boolean} forceBackdrop - Force show backdrop regardless of user setting (used for login splashscreen)
sub setBackgroundImage(uri as string, isAnimated = true as boolean, forceBackdrop = false as boolean)
m.log.info("SceneManager.setBackgroundImage called", uri, isAnimated, forceBackdrop)
' Passthrough to scene via callFunc
if isValid(m.scene)
m.scene.callFunc("setBackgroundImage", uri, isAnimated, forceBackdrop)
else
m.log.error("SceneManager.setBackgroundImage: m.scene is invalid!")
end if
end sub
' Display dialog to user with an OK button
sub userMessage(title as string, message as string)
dialog = createObject("roSGNode", "StandardMessageDialog")
dialog.title = title
' StandardMessageDialog.message is an array-of-strings field; assigning a
' bare string fails silently and renders an empty body.
dialog.message = [message]
dialog.buttons = [translate(translationKeys.ButtonOk)]
dialog.observeField("buttonSelected", "dismissDialog")
m.scene.dialog = dialog
end sub
' Display dialog to user with an OK button
sub standardDialog(title, message)
constants = m.global.constants
dialog = createObject("roSGNode", "StandardDialog")
dlgPalette = createObject("roSGNode", "RSGPalette")
dlgPalette.colors = {
DialogBackgroundColor: constants.colorBackgroundPrimary,
DialogFocusColor: constants.colorPrimary,
DialogFocusItemColor: constants.colorTextPrimary,
DialogSecondaryTextColor: constants.colorTextSecondary,
DialogSecondaryItemColor: constants.colorSecondary,
DialogTextColor: constants.colorTextPrimary
}
dialog.palette = dlgPalette
dialog.observeField("buttonSelected", "dismissDialog")
dialog.title = title
dialog.setField("contentData", message) ' setField: BSC cannot resolve custom fields on built-in dialog subtypes
dialog.setField("buttons", [translate(translationKeys.ButtonOk)])
m.scene.dialog = dialog
end sub
' Display dialog to user with an OK button
sub radioDialog(title, message)
constants = m.global.constants
dialog = createObject("roSGNode", "RadioDialog")
dlgPalette = createObject("roSGNode", "RSGPalette")
dlgPalette.colors = {
DialogBackgroundColor: constants.colorBackgroundPrimary,
DialogFocusColor: constants.colorPrimary,
DialogFocusItemColor: constants.colorTextPrimary,
DialogSecondaryTextColor: constants.colorTextSecondary,
DialogSecondaryItemColor: constants.colorSecondary,
DialogTextColor: constants.colorTextPrimary
}
dialog.palette = dlgPalette
dialog.observeField("buttonSelected", "dismissDialog")
dialog.title = title
dialog.setField("contentData", message) ' setField: BSC cannot resolve custom fields on built-in dialog subtypes
dialog.setField("buttons", [translate(translationKeys.ButtonOk)])
m.scene.dialog = dialog
end sub
' Display dialog to user with an OK button
sub showConfirmationDialog(title, message, buttons)
constants = m.global.constants
m.top.isDataReturned = false
m.top.returnData = invalid
m.userselection = false
dialog = createObject("roSGNode", "StandardMessageDialog")
dlgPalette = createObject("roSGNode", "RSGPalette")
dlgPalette.colors = {
DialogBackgroundColor: constants.colorBackgroundPrimary,
DialogFocusColor: constants.colorPrimary,
DialogFocusItemColor: constants.colorTextPrimary,
DialogSecondaryTextColor: constants.colorTextSecondary,
DialogSecondaryItemColor: constants.colorSecondary,
DialogTextColor: constants.colorTextPrimary
}
dialog.palette = dlgPalette
dialog.observeField("buttonSelected", "optionSelected")
dialog.observeField("wasClosed", "optionClosed")
dialog.title = title
if message = invalid
dialog.message = invalid
else if type(message) = "roArray"
dialog.message = message
else
dialog.message = [message]
end if
dialog.buttons = buttons
m.scene.dialog = dialog
end sub
' Return button the user selected
sub optionClosed()
if m.userselection then return
m.top.returnData = {
indexSelected: -1,
buttonSelected: ""
}
m.top.isDataReturned = true
end sub
' Return button the user selected
sub optionSelected()
m.userselection = true
m.top.returnData = {
indexSelected: m.scene.dialog.buttonSelected,
buttonSelected: m.scene.dialog.buttons[m.scene.dialog.buttonSelected]
}
m.top.isDataReturned = true
dismissDialog()
end sub
' Close currently displayed dialog
sub dismissDialog()
m.scene.dialog.close = true
end sub
' Returns bool indicating if dialog is currently displayed
function isDialogOpen() as boolean
return isValid(m.scene.dialog)
end function
' Refresh theme colors on JRScene and overhang.
' Called after theme color settings change (settings exit, login, logout).
' Uses refreshThemeOnTree() to walk the overhang's node tree and update all
' themed elements (labels, buttons, icons) in place — no component recreation
' or per-component refresh functions needed.
sub refreshThemeColors()
m.scene.backgroundColor = m.global.constants.colorBackgroundPrimary
if isValid(m.overhang)
refreshThemeOnTree(m.overhang)
end if
end sub