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 newGroup <> invalid
    if currentGroup <> invalid
      'Search through group and store off last focused item
      if currentGroup.focusedChild <> invalid
        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 currentGroup <> invalid
      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 group <> invalid
    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 group <> invalid
    registerOverhangData(group)

    group.visible = true

    m.content.replaceChild(group, 0)

    if group.isSubType("JRScreen")
      group.callFunc("OnScreenShown")
    else
      ' Restore focus
      if group.lastFocus <> invalid
        group.lastFocus.setFocus(true)
      else
        if group.focusedChild <> invalid
          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 m.content <> invalid 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 group.overhangTitle <> invalid 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 m.overhang <> invalid 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 m.scene.dialog <> invalid
end function