components_data_SceneManager.bs

import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/misc.bs"

sub init()
  m.log = log.Logger("SceneManager")
  m.groups = []
  m.scene = m.top.getScene()
  m.content = m.scene.findNode("content")
  m.overhang = m.scene.findNode("overhang")
end sub

' Push a new group onto the stack, replacing the existing group on the screen
sub pushScene(newGroup)

  currentGroup = m.groups.peek()
  if isValid(newGroup)
    if isValid(currentGroup)
      'Search through group and store off last focused item
      if isValid(currentGroup.focusedChild)
        focused = currentGroup.focusedChild
        while focused.hasFocus() = false
          focused = focused.focusedChild
        end while

        currentGroup.lastFocus = focused
        currentGroup.setFocus(false)
      else
        currentGroup.setFocus(false)
      end if

      if currentGroup.isSubType("JRGroup")
        unregisterOverhangData(currentGroup)
      end if

      currentGroup.visible = false

      if currentGroup.isSubType("JRScreen")
        currentGroup.callFunc("OnScreenHidden")
      end if

    end if

    m.groups.push(newGroup)

    if isValid(currentGroup)
      m.content.replaceChild(newGroup, 0)
    else
      m.content.appendChild(newGroup)
    end if

    if newGroup.isSubType("JRScreen")
      newGroup.callFunc("OnScreenShown")
    end if

    'observe info about new group, set overhang title, etc.
    if newGroup.isSubType("JRGroup")
      registerOverhangData(newGroup)

      ' Some groups set focus to a specific component within init(), so we don't want to
      ' change if that is the case.
      if newGroup.isInFocusChain() = false
        newGroup.setFocus(true)
      end if
    else if newGroup.isSubType("JRVideo")
      newGroup.setFocus(true)
      newGroup.control = "play"
      m.overhang.visible = false
    end if
  else
    currentGroup.focusedChild.setFocus(true)
  end if

end sub

' Remove the current group and load the last group from the stack
sub popScene()
  group = m.groups.pop()
  if isValid(group)
    if group.isSubType("JRGroup")
      unregisterOverhangData(group)
    else if group.isSubType("JRVideo")
      ' Stop video to make sure app communicates stop playstate to server
      group.control = "stop"
    end if

    group.visible = false

    if group.isSubType("JRScreen")
      group.callFunc("OnScreenHidden")
    end if
  else
    ' Exit app if for some reason we don't have anything on the stack
    m.scene.exit = true
  end if

  group = m.groups.peek()
  if isValid(group)
    registerOverhangData(group)

    group.visible = true

    m.content.replaceChild(group, 0)

    if group.isSubType("JRScreen")
      group.callFunc("OnScreenShown")
    else
      ' Restore focus
      if isValid(group.lastFocus)
        group.lastFocus.setFocus(true)
      else
        if isValid(group.focusedChild)
          group.focusedChild.setFocus(true)
        else
          group.setFocus(true)
        end if
      end if
    end if
  else
    ' Exit app if the stack is empty after removing group
    m.scene.exit = true
  end if
  stopLoadingSpinner()
end sub

' Return group at top of stack without removing
function getActiveScene() as object
  return m.groups.peek()
end function

' Clear all content from group stack
sub clearScenes()
  if isValid(m.content) then m.content.removeChildrenIndex(m.content.getChildCount(), 0)
  for each group in m.groups
    if type(group) = "roSGNode" and group.isSubtype("JRScreen")
      group.callFunc("OnScreenHidden")
    end if
  end for
  m.groups = []
end sub

' Clear previous scene from group stack
sub clearPreviousScene()
  m.groups.pop()
end sub

' Delete scene from group stack at passed index
sub deleteSceneAtIndex(index = 1)
  m.groups.Delete(index)
end sub

' Display user/device settings screen
sub settings()
  settingsScreen = createObject("roSGNode", "Settings")
  pushScene(settingsScreen)
end sub

' Register observers for overhang data
sub registerOverhangData(group)
  if group.isSubType("JRGroup")
    if isValid(group.overhangTitle) then m.overhang.title = group.overhangTitle

    group.observeField("overhangTitle", "updateOverhangTitle")

    if group.overhangVisible
      m.overhang.visible = true
    else
      m.overhang.visible = false
    end if
    group.observeField("overhangVisible", "updateOverhangVisible")
  else if group.isSubType("JRVideo")
    m.overhang.visible = false
  else
    m.log.error("registerOverhangData(): Unexpected group type.", group, group.subtype())
  end if
end sub

' Remove observers for overhang data
sub unregisterOverhangData(group)
  group.unobserveField("overhangTitle")
end sub

' Update overhang title
sub updateOverhangTitle(msg)
  m.overhang.title = msg.getData()
end sub

' Update whether the overhang is visible or not
sub updateOverhangVisible(msg)
  m.overhang.visible = msg.getData()
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

' Display dialog to user with an OK button
sub userMessage(title as string, message as string)
  dialog = createObject("roSGNode", "StandardMessageDialog")
  dialog.title = title
  dialog.message = message
  dialog.buttons = [tr("OK")]
  dialog.observeField("buttonSelected", "dismissDialog")
  m.scene.dialog = dialog
end sub

' Display dialog to user with an OK button
sub standardDialog(title, message)
  colorConstants = m.global.constants.colors

  dialog = createObject("roSGNode", "StandardDialog")
  dlgPalette = createObject("roSGNode", "RSGPalette")
  dlgPalette.colors = {
    DialogBackgroundColor: colorConstants.background_primary,
    DialogFocusColor: colorConstants.primary,
    DialogFocusItemColor: colorConstants.text_primary,
    DialogSecondaryTextColor: colorConstants.text_secondary,
    DialogSecondaryItemColor: colorConstants.secondary,
    DialogTextColor: colorConstants.text_primary
  }
  dialog.palette = dlgPalette
  dialog.observeField("buttonSelected", "dismissDialog")
  dialog.title = title
  dialog.contentData = message
  dialog.buttons = [tr("OK")]

  m.scene.dialog = dialog
end sub

' Display dialog to user with an OK button
sub radioDialog(title, message)
  colorConstants = m.global.constants.colors

  dialog = createObject("roSGNode", "RadioDialog")
  dlgPalette = createObject("roSGNode", "RSGPalette")
  dlgPalette.colors = {
    DialogBackgroundColor: colorConstants.background_primary,
    DialogFocusColor: colorConstants.primary,
    DialogFocusItemColor: colorConstants.text_primary,
    DialogSecondaryTextColor: colorConstants.text_secondary,
    DialogSecondaryItemColor: colorConstants.secondary,
    DialogTextColor: colorConstants.text_primary
  }
  dialog.palette = dlgPalette
  dialog.observeField("buttonSelected", "dismissDialog")
  dialog.title = title
  dialog.contentData = message
  dialog.buttons = [tr("OK")]

  m.scene.dialog = dialog
end sub

' Display dialog to user with an OK button
sub optionDialog(title, message, buttons)
  colorConstants = m.global.constants.colors
  m.top.dataReturned = false
  m.top.returnData = invalid
  m.userselection = false

  dialog = createObject("roSGNode", "StandardMessageDialog")
  dlgPalette = createObject("roSGNode", "RSGPalette")
  dlgPalette.colors = {
    DialogBackgroundColor: colorConstants.background_primary,
    DialogFocusColor: colorConstants.primary,
    DialogFocusItemColor: colorConstants.text_primary,
    DialogSecondaryTextColor: colorConstants.text_secondary,
    DialogSecondaryItemColor: colorConstants.secondary,
    DialogTextColor: colorConstants.text_primary
  }
  dialog.palette = dlgPalette
  dialog.observeField("buttonSelected", "optionSelected")
  dialog.observeField("wasClosed", "optionClosed")
  dialog.title = title
  dialog.message = message
  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.dataReturned = 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.dataReturned = 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