Roku SDK Documentation : Events and Observers Markup

Table of Contents


As described in SceneGraph XML Reference, there are standard event loops and functions included in the SceneGraph API for triggering actions when certain events occur. The following are a few examples that illustrate how to perform actions in your application in response to certain events.

Observed Fields Markup

Example Application: EventObserverExample.zip
Node Class Reference: Timer

All SceneGraph objects include an observeField() function that can call an event handler function in response to a change in a field in the object (see ifSGNodeField). (As an alternate, the interface element also can also be used for the same purpose, as described in <interface>.) EventObserverExample.zip uses a Timer node to trigger a change in the text in a Label node we defined previously in Renderable Node Markup (to see a tutorial example of the Timer node, you can skip ahead a little to Timer Markup).

But we're going to make this more complicated by combining an animation node that also operates on the Label node with the Timer node. We're going to try to synchronize the Timer node with an animation node that operates on the translation field of the Label node, to change the text as well as move it around the display screen.

Event Observer Example
<component name = "EventObserverExample" extends = "Scene" >

  <script type = "text/brightscript" >

    <![CDATA[

    sub init()
      m.top.backgroundURI = "pkg:/images/rsgde_bg_hd.jpg"

      example = m.top.findNode("textRectangle")

      examplerect = example.boundingRect()
      centerx = (1280 - examplerect.width) / 2
      centery = (720 - examplerect.height) / 2
      example.translation = [ centerx, centery ]

      m.movinglabel = m.top.findNode("movingLabel")
      scrollback = m.top.findNode("scrollbackAnimation")
      texttimer = m.top.findNode("textTimer")
      m.defaulttext = "All The Best Videos!"
      m.alternatetext = "All The Time!!!"
      m.textchange = false
      texttimer.observeField("fire", "changetext")
      scrollback.control = "start"
      texttimer.control = "start"

      m.top.setFocus(true)
    end sub

    sub changetext()
      if (m.textchange = false) then 
        m.movinglabel.text = m.alternatetext
        m.textchange = true
      else
        m.movinglabel.text = m.defaulttext
        m.textchange = false
      end if
    end sub

    ]]>

  </script>

  <children>

    <Rectangle 
      id = "textRectangle"
      width = "640" 
      height = "60" 
      color = "0x10101000" >

      <Label 
        id = "movingLabel" 
        width = "280" 
        height = "60" 
        text = "All The Best Videos!" 
        horizAlign = "center" 
        vertAlign = "center" />

    </Rectangle>

    <Animation 
      id = "scrollbackAnimation" 
      duration = "10" 
      repeat = "true" 
      easeFunction = "linear" >

      <Vector2DFieldInterpolator 
        key = "[ 0.0, 0.5, 1.0 ]" 
        keyValue = "[ [0.0, 0.0], [360.0, 0.0], [0.0, 0.0] ]" 
        fieldToInterp = "movingLabel.translation" />

    </Animation>

    <Timer 
      id = "textTimer" 
      repeat = "true" 
      duration = "5" />

  </children>

</component>

Note that we are setting up an animation that is very similar to the one in Vector2DFieldInterpolator Markup. But instead of moving a rectangle around the display screen, we are moving a Label node. We make the animated Label node a child node of an invisible parent Rectangle node that sets the boundary of the furthermost limits of the animation:

<Rectangle 
  id = "textRectangle"
  width = "640" 
  height = "60" 
  color = "0x10101000" >

  <Label 
    id = "movingLabel" 
    width = "280" 
    height = "60" 
    text = "All The Best Videos!" 
    horizAlign = "center" 
    vertAlign = "center" />

</Rectangle>

Then we define the Animation node to move the Label node around, which uses the same Vector2DFieldInterpolator node we used before to change the translation field values of a Rectangle node:

<Animation 
  id = "scrollbackAnimation" 
  duration = "10" 
  repeat = "true" 
  easeFunction = "linear" >

  <Vector2DFieldInterpolator 
    key = "[ 0.0, 0.5, 1.0 ]" 
    keyValue = "[ [0.0, 0.0], [360.0, 0.0], [0.0, 0.0] ]" 
    fieldToInterp = "movingLabel.translation" />

</Animation>

But the important difference in this example is that we add a Timer node, which we intend to change the text of the Label node half-way through the animation. We give it an ID, which for this particular use, is required, because we must be able to identify the node in BrightScript code in the <script> element:

id = "textTimer"

We also define the repeat and duration fields in the same way as an Animation node, but note that we set the duration field value to 5, half the value of the same field in the Animation node:

repeat = "true"
duration = "5"

This is how we can change  the text of the Label node half-way through the animation. All we need is to add the following BrightScript code to the init() function:

m.movinglabel = m.top.findNode("movingLabel")
scrollback = m.top.findNode("scrollbackAnimation")
texttimer = m.top.findNode("textTimer")
m.defaulttext = "All The Best Videos!"
m.alternatetext = "All The Time!!!"
m.textchange = false
texttimer.observeField("fire", "changetext")
scrollback.control = "start"
texttimer.control = "start"

Most of the code is setting up variables and objects we will need to actually change the text itself. The most important line of code for this example is the use of the observeField() method to set an observer on the fire field of the Timer node:

texttimer.observeField("fire", "changetext")

As described in Timer Markup, the Timer node fire field will be set after the number of seconds specified by the duration field value. By setting the observer on this field, we can trigger an event handling callback function changetext() when the field changes:

sub changetext()
  if (m.textchange = false) then 
    m.movinglabel.text = m.alternatetext
    m.textchange = true
  else
    m.movinglabel.text = m.defaulttext
    m.textchange = false
  end if
end sub

This is the function that actually changes the Label node text every five seconds, as the repeating animation reaches its furthermost limits. The text alternates between All The Best Videos! and All The Time!!! every five seconds:

Again, as in Vector2DFieldInterpolator Markup, you can watch the animation scroll back and forth, and the text change, over and over and over again...

Key Events Markup

Example Application: KeyEventsExample.zip
SceneGraph References: <script>, onKeyEvent()

Pressing the keys on the Roku remote control generate specific events for each key, as well as a general event when any key is pressed. This gives you full control to initiate actions in your SceneGraph application in response to remote control key presses, many times in the <script> element of a SceneGraph XML component file (see <script>). The SceneGraph API includes a special function, onKeyEvent(), that can be used in the <script> element to control the component defined in the XML file (see onKeyEvent()).

In this example, we use the OK key on the remote control to show and hide a Label node.

Key Events Example
<component name = "KeyEventsExample" extends = "Scene" >

  <script type = "text/brightscript" >

    <![CDATA[

    sub init()
      m.top.backgroundURI = "pkg:/images/rsgde_bg_hd.jpg"

      m.label = m.top.findNode("exampleLabel")

      examplerect = m.label.boundingRect()
      centerx = (1280 - examplerect.width) / 2
      centery = (720 - examplerect.height) / 2
      m.label.translation = [ centerx, centery ]

      m.top.setFocus(true)
    end sub

    function onKeyEvent(key as String, press as Boolean) as Boolean
      if press then
        if (key = "OK") then 
          if (m.label.visible = true)
            m.label.visible = false
          else
            m.label.visible = true
          endif

          return true
        end if
      end if

      return false
    end function

    ]]>

  </script>

  <children>

    <Label 
      id = "exampleLabel" 
      width = "600" 
      height = "60" 
      text = "Press OK to Hide/Show" 
      horizAlign = "center" 
      vertAlign = "center" 
      visible = "true" />

  </children>

</component>

The Label node is defined like the example in Renderable Node Markup. But we've defined the visible field that is normally set to true by default:

visible = "true"

We're just doing this to absolutely ensure the starting condition of the Label node, which is visible, because we'll be setting this field alternating between true and false depending on the user pressing the OK remote control key. This is all done in the onKeyEvent() function, after declaring the m.label object in the init() function using the findNode() method:

function onKeyEvent(key as String, press as Boolean) as Boolean
  if press then
    if (key = "OK") then 
      if (m.label.visible = true)
        m.label.visible = false
      else
        m.label.visible = true
      endif

      return true
    end if
  end if

  return false
end function

The logic is simple. If the OK key is pressed, and if the m.label object is visible based on the setting of the visible field, the label is made invisible by setting the field to false. If the m.label object visible field value is false when the OK key is pressed, the field value is set to true, making the label visible.

There's a few important things to note in this event handler function definition. First, the function returns either true or false to the firmware, and by default we want to make sure the return value is false (the remote key press event has not been handled), to avoid any possibility that the firmware will prevent further action of the application, or the user to control the application. So we explicitly set the return value to be false at the bottom of the function, after the key press event has not handled by any of the conditions in the function:

return false

We only want to return true when we are sure we have handled the specific key press event correctly. This all typically takes place in an if press then conditional statement block as above, which handles the general key press event. We want to add a conditional block to detect and handle the specific key event, handle the event, then return true in that block to prevent any possible further processing of that key event. (Remember from the SceneGraph XML Guide that any remote control key events that are not handled by any node in the SceneGraph are automatically are handled by default at the top of the SceneGraph, or by the firmware, by default.)

The main event handling is done in the conditional code block that detects the OK key press. On each OK key press, the code checks the visibility state of the Label node, and toggles visibility according to the current state:

if (key = "OK") then 
  if (m.label.visible = true)
    m.label.visible = false
  else
    m.label.visible = true
  endif

  return true
end if

If you haven't done this already, download and side-load KeyEventsExample.zip, and press the OK key repeatedly.

 

Attachments:

labelchange.jpg (application/octet-stream)
EventObserver.zip (application/octet-stream)
keyevents.jpg (application/octet-stream)
KeyEvents.zip (application/octet-stream)
EventObserver.zip (application/zip)
KeyEvents.zip (application/zip)
eventobserver.jpg (image/jpeg)
eventobserver.jpg (image/jpeg)
keyevents.jpg (image/jpeg)
EventObserverExample.zip (application/zip)
eotextchangedoc.jpg (image/jpeg)
EventObserverExample.zip (application/zip)
EventObserverExample.zip (application/zip)
KeyEventsExample.zip (application/zip)
keyeventsdoc.jpg (image/jpeg)
KeyEventsExample.zip (application/zip)
EventObserverExample.zip (application/zip)
EventObserverExample.zip (application/zip)
KeyEventsExample.zip (application/zip)
eventobserverdoc.jpg (image/jpeg)
eventobserverdoc14pt.jpg (image/jpeg)
eventobserverdoc14pt.svg (image/svg+xml)
eventobserverdoc14pt.jpg (image/jpeg)
keyeventsdoc.jpg (image/jpeg)