Hello, World Part 3: Dealing with State

Hello, World Part 3: Dealing with State

  • Comments 3

In the last post, we looked at a very simple (and useless) animation. Today we will look at state and how that can be used to trigger slightly more useful animations. We'll also move from simple "Hello, World" text to have something a bit more practical on screen, like some buttons.

Hello.xmu

Here's a completely new file, since we've now graduated from white text on a blue background. I've included several new things here (not just state management), so we'll go through them one by one.

<?xml version="1.0"?>

<root xml:lang="en" xmlns="http://www.dvdforum.org/2005/ihd"

  xmlns:style="http://www.dvdforum.org/2005/ihd#style"

  xmlns:state="http://www.dvdforum.org/2005/ihd#state">

 

  <head>

    <styling>

      <style select="//button" style:backgroundColor="rgba(0,0,255,255)"

             style:position="absolute" style:width="200px" style:height="100px" />

    </styling>

 

    <timing clock="page">

      <par>

        <cue begin="//button[state:focused()=true() and style:backgroundColor()='rgba(0,0,255,255)']"

             end="//button[state:focused()=false() and style:backgroundColor()='rgba(255,0,0,255)']">

 

          <set style:backgroundColor="rgba(255,0,0,255)" />

 

        </cue>

      </par>

    </timing>

  </head>

 

  <body>

    <div style:position="absolute" style:x="0px" style:y="0px"

         style:width="1920px" style:height="1080px" style:backgroundColor="black">

 

      <button style:x="100px" style:y="100px" />

      <button style:x="400px" style:y="100px" />

      <button style:x="700px" style:y="100px" />

 

      <button style:x="100px" style:y="300px" />

      <button style:x="400px" style:y="300px" state:focused="true"/>

      <button style:x="700px" style:y="300px" />

 

      <button style:x="100px" style:y="500px" />

      <button style:x="400px" style:y="500px" />

      <button style:x="700px" style:y="500px" />

 

    </div>

  </body>

</root>

 

This will display a grid of 3 x 3 buttons (rectangles), each of which will be blue, except the centre one. It will look like this:

 

Then as you move between the buttons (either with the cursor keys or by clicking with the mouse) the button with the focus will remain red.

Looking at the Elements

Let's look at what's new; refer to the previous entry if you've forgotten what some of the tags do.

styling

The styling section now contains a single child element, style. Styling is used just like it is in HTML / CSS, but it uses an XML syntax rather than the CSS syntax.

style

Here we declare a style for all the buttons on the screen. This makes it much easier to manage the way the page looks without having to worry about messing around with the actual content elements (the buttons). In this example, we are going to make all the buttons the same size and colour. The select attribute takes an XPath query that says what element(s) to select; in this case, all the button elements. The remaining style attributes are self-explanatory; note that the rgba function is like the normal rgb (red, green, and blue) function for specifying colour values, but it also contains an alpha (transparency) value. 0 would be fully transparent, whilst 255 is fully opaque.

timing

Nothing new here; this section contains all the timing (animation) elements, using the page clock for any time offsets.

par

Nothing new here, and since there is only one child it's even less interesting.

cue

As before, this tag cues up (triggers) animations based on some conditions. Unlike the last time, though, we're using XPath queries to determine when to start and end the animation. The begin attribute tells iHD when to start this animation; in this case we want the animation to start when iHD detects a button that has the focus (state:focused()=true()) but it is still coloured in blue (style:backgroundColor()='rgba(0,0,255,255)'). Note that we don't need a select attribute in this case (we did last time) because the animation will automatically apply to the node(s) selected by the begin expression (in this case, the focused button). The end attribute (funnily enough) tells iHD when to stop the animation. In this case, we want it to stop the animation when it detects that a button has lost the focus (state:focused()=false()) but is still coloured red (style:backgroundColor()='rgba(255,0,0,255)'). I'll discuss why the expressions are written this way in a minute.

set

Nothing new here; we'll make the button selected by the cue red for as long as the cue is valid (ie, as long as it has the focus).

body

<yawn>

div

<yawn>

button

Here we define the nine buttons in the grid. Because the style information was set in the styling section, we only need to say what {x,y} co-ordinates the buttons start at. Bravo! We also set the button in the middle to have the input focus with the state:focused="true" attribute.

How the 'begin' and 'end' work

Someone used to writing HTML with simple onmouseover events (or the like) may be wondering why we can't write the cue thusly:

< cue begin = " //button[state:focused()=true()] " end = " //button[state:focused()=false() " >

In other words, "begin when a button gets the focus, and end when it loses the focus".

Unfortunately, it's not quite that simple...

In iHD, there are a handful of simple rules defining when a cue becomes valid:

·The parent par or seq must be valid

·The begin expression must be coercible to true (for example, selects a non-empty set of nodes)

·The end expression must be coercible to false (for example, selects an empty set of nodes)

There are also two rules to prevent "infinite" cues:

·Once a cue begins, it cannot begin again until ends (ie, the end expression becomes true)

·Once a cue ends, it cannot begin again until it becomes invalid (ie, the begin expression becomes false)

The first validity rule is a no-brainer in this case because the par is always valid.

The second rule appears to be a no-brainer, since clearly when one of the buttons gets the focus the expression "//button[state:focused()=true()]" will result in a non-empty nodeset. A bit more on this in a minute, though.

The third rule is interesting though. Given that we have nine buttons, but only one of them can ever have the focus, the expression "//button[state:focused()=false()]" will always return a nodeset of at least eight elements (ie, it selects all the buttons that don't have the focus!) A non-empty nodeset can be coerced to true, and that means the cue can never become valid! The "simple" solution to this -- make the end expression just be "(false())" -- will work for the first button, but will fail for the subsequent buttons due to the first "infinite" rule (the end must become true for one button so that the cue can start again for another button).

Quick note: It can be hard to get your head around the way these XPath queries work sometimes. "Clearly," you might say to yourself, "when I said I wanted the cue to end when a button was not focused, I was referring to the same darn button that you told me was focused at the beginning of the cue!" Indeed, that's often what you want, but iHD is very flexible and you could very well want your end condition to be based on some element(s) that aren't the one(s) you're currently animating. So iHD just shrugs and says "You asked me for some non-focused buttons, and I found them!" The good news is that there is a shortcut for automatically selecting the node-that's-currently-animating, but I'm not going to tell you what it is just yet. We need to have some fun first ;-)

OK. So what we need to do is think of an expression that will be false (empty) when a button gets the focus, but true (non-empty) when a button loses the focus. (Note per the above that I say "a button" and not "the button"). Here's where we get tricky introduce something of a "circular reference"; since we know that buttons start off blue and are set to red when they get the focus, we can use the background colour -- the very thing we are animating -- as the trigger!

Here's how it works:

·Initially, all the buttons are blue (per the style) and the centre button has the focus (per its state:focused attribute). We have eight non-focused blue buttons and one focused blue button

oiHD determines that there is a button with the focus, triggering the cue and making the centre (focused) button red. Now we have eight non-focused blue buttons and one red focused button

·The user changes focus (eg, hits "right" on the remote). Now we have seven blue non-focused buttons, one red non-focused button (in the centre), and one blue focused button (on the right)

Aha! Now we have a non-focused red button. That's something new and unique that we can use to end the animation and return the button to its non-focused (blue) state. That's how we end up with the end condition of "//button[state:focused()=false() and style:backgroundColor()='rgba(255,0,0,255)']". Note that you might think we could have chosen to end on a focused-blue-button instead (since that is also new and unique), but that would cause another problem...

Just as the end attribute had to be more complex in order to satisfy the first infinite rule, so the begin attribute needs to be more complex to satisfy the second infinite rule. Hopefully I don't need to go into so much detail this time -- basically when the focus moves from one button to the next, the expression "//button[state:focused()=true()]" remains true (remember: iHD can always find one button with the focus, it just might not be the same button :-) ) and so the cue won't re-start. To fix this, we require that the focused button also be blue. At the time a button gets the focus it will be blue, allowing the cue to start and thus turning it red. Now the begin attribute becomes false (the button isn't blue any more), making the cue eligible to re-start in the future.

Homework!

Now, about that last problem -- you know, the one we'd have if we used focused-blue-buttons to end the cue -- can anyone tell me why that wouldn't work?

Take a guess if you like, and remember that how you get to your answer is just as important as the answer itself (right or wrong). It's just like being in high school again :-)

I'll give the answer in a few days.

Page 1 of 1 (3 items)