Drop-Down Menus in 100% Pure Markup

Drop-Down Menus in 100% Pure Markup

  • Comments 4

The samples lately may have seemed a bit "script heavy" for a system that is supposed to be markup based, so today we have a drop-down menu system driven entirely from markup, savvy?

Of course the menu doesn't do anything without script (it is just a visual thing; any actual behaviour like changing chapters or switching audio tracks is done with a couple of lines of code) and in order to avoid a download I'm just going to paste the markup that uses background colours (rather than pretty images) but you will get the idea.

The Markup

Here it is in one big chunk:

<?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 id="btn" style:position="absolute" style:x="25px" style:y="25px" style:width="150px" style:height="100px" 
       
style:backgroundColor="yellow" style:opacity="0.4"/>

      <style id="div" style:position="absolute" style:x="0px" style:y="150px" style:width="200px" style:height="0px" 
       
style:backgroundColor="white" />
    </
styling>
    <
timing clock="page">
      <
par>

        <par begin="id('settings')[state:actioned()]" 
         
end="(class('SettingsButton')[state:actioned()] or class('NotSettings')[state:focused()])">

          <cue begin="0s" dur="0.3s" select="id('settingsMenu')" fill="hold">
            <
animate style:height="0px;400px"/>
          </
cue>

          <cue begin="0s" dur="1s" select="id('settings')" fill="hold">
           
<
set style:navDown="settings1" style:navUp="settings3"/>
          </
cue>
       
</
par>

        <par begin="id('chapters')[state:actioned()]" 
         
end="(class('ChaptersButton')[state:actioned()] or class('NotChapters')[state:focused()])">

          <cue begin="0s" dur="0.3s" select="id('chaptersMenu')" fill="hold">
           
<
animate style:height="0px;400px"/>
         
</
cue>

          <cue begin="0s" dur="1s" select="id('chapters')" fill="hold">
           
<
set style:navDown="chapters1" style:navUp="chapters3"/>
          
</
cue>
       
</
par>

        <par begin="id('audio')[state:actioned()]" 
         
end="(class('AudioButton')[state:actioned()] or class('NotAudio')[state:focused()])">

          <cue begin="0s" dur="0.3s" select="id('audioMenu')" fill="hold">
           
<
animate style:height="0px;400px"/>
         
</
cue>

          <cue begin="0s" dur="1s" select="id('audio')" fill="hold">
           
<
set style:navDown="audio1" style:navUp="audio3"/>
         
</
cue>
       
</
par>

        <cue begin="//button[state:focused() and count(//button[style:opacity()='1'])=0]" 
         
end="defaultNode()[state:focused() != 'true']">

          <set style:opacity="1"/>
       
</
cue>
     
</
par>
   
</
timing>
 
</
head>

  <body>

    <div style:position="absolute" style:x="0px" style:y="0px" style:width="1920px" style:height="1080px"
      style:backgroundColor
="black"
>

      <div id="mainMenu" style="div" style:y="0px" style:height="150px" style:width="600px">
       
<
button id="settings" style="btn" class="NotChapters NotAudio" 
         
style:navDown="chapters" style:navUp="audio" style:navLeft="audio" style:navRight="chapters"/>

        <button id="chapters" style="btn" class="NotSettings NotAudio" style:x="225px" style:backgroundColor="red" 
         
style:navDown="audio" style:navUp="settings" style:navLeft="settings" style:navRight="audio"/>

        <button id="audio" style="btn" class="NotChapters NotSettings" style:x="425px" style:backgroundColor="blue" 
         
style:navDown="settings" style:navUp="chapters" style:navLeft="chapters" style:navRight="settings"/>

      </div>

      <div id="settingsMenu" style="div">
       
<
button id="settings1" style="btn" class="SettingsButton" style:navUp="settings"/>
       
<
button id="settings2" style="btn" class="SettingsButton" style:y="150px" />
       
<
button id="settings3" style="btn" class="SettingsButton" style:y="275px" style:navDown="settings"/>
     
</
div>

      <div id="chaptersMenu" style="div" style:x="200px">
       
<
button id="chapters1" style="btn" class="ChaptersButton" style:backgroundColor="red" style:navUp="chapters"/>
       
<
button id="chapters2" style="btn" class="ChaptersButton" style:y="150px" style:backgroundColor="red" />
       
<
button id="chapters3" style="btn" class="ChaptersButton" style:y="275px" style:backgroundColor="red" style:navDown="chapters" />
     
</
div>

      <div id="audioMenu" style="div" style:x="400px">
       
<
button id="audio1" style="btn" class="AudioButton" style:backgroundColor="blue" style:navUp="audio"/>
       
<
button id="audio2" style="btn" class="AudioButton" style:y="150px" style:backgroundColor="blue" />
       
<
button id="audio3" style="btn" class="AudioButton" style:y="275px" style:backgroundColor="blue" style:navDown="audio" />
     
</
div>
   
</
div>
 
</
body>
</
root>

OK, so how's it work?

The Layout

The layout consists of a menu bar with three button elements, nominally "settings" (yellow), "chapters" (red), and "audio" (blue). These buttons all have an id and they also have a class stating which button they are not (so, for example, the "audio" button has a class saying it is "NotSettings" and "NotChapters").

Then there div elements (for each of the sub-menus), each of which contains three more button elements that have a class set to the kind of button that they are (so, for example, the audio sub-menu has buttons with the class set to "AudioButton").

The use of these class attributes makes the animation trivial. Also the navIndex attribute is used quite a bit to ensure that the menus work nicely with cursor navigation.

The Styling

Pretty basic, really, Set some default size and colour parameters.

The Timing

This is quite simple. There are three very similar blocks repeated for each of the three menus (they are copy-paste jobs with the id and class values renamed). We'll consider just the first one:

  <par begin="id('settings')[state:actioned()]" 
   
end="(class('SettingsButton')[state:actioned()] or class('NotSettings')[state:focused()])"
>

    <cue begin="0s" dur="0.3s" select="id('settingsMenu')" fill="hold">
      <
animate style:height="0px;400px"
/>
    </
cue
>

    <cue begin="0s" dur="1s" select="id('settings')" fill="hold">
      <
set style:navDown="settings1" style:navUp="settings3"
/>
    </
cue
>
  </
par>

The par will begin when the settings button is actioned (clicked), and end when either one of the sub-menu buttons is actioned (class is "SettingsButton") or when a different top-level button gets focused (class is "NotSettings"). Inside the par, we animate the settingsMenu over a dur of 0.3 seconds and then hold it at the open position. We also set the navUp and navDown attributes so that the up / down keys work naturally with the expanded menu.

There's also the case of highlighting the focused button:

  <cue begin="//button[state:focused() and count(//button[style:opacity()='1'])=0]" 
   
end="defaultNode()[state:focused() != 'true']">

    <set style:opacity="1"/>
  </
cue
>

In yet another way of solving the must-be-false-before-it-is-true-again problem, this time we add a count function into the mix to ensure that the begin becomes false as soon as the cue fires. It goes like this:

·No-one has the focus, and count(//button[style:opacity()='1'])=0 is true

·Button A gets the focus, and the begin is true

·Button A becomes 100% opaque, and count(//button[style:opacity()='1'])=0 becomes false, thus making the entire begin false and making it eligible to re-start as soon as it ends

·Button A loses the focus, so the end is true and the cue stops. Now count(//button[style:opacity()='1'])=0 is true again

·Button B gets the focus, and can immediately begin the cue because it has already done the false / true flip-flop

The Script

Surprise! There isn't any :-)

You can simply copy-and-paste the markup into an XMU file along with a bog-standard XPL and XMF (from any of the previous samples, for example) to watch it work.

  • That is pretty cool.  I'm going to play around with this some more.

    What I want to do is 'grow' a single pane of buttons from off screen.  So that during playback I can pull a pane of chapter selection buttons out from the side (or up from the bottom) to navigate elsewhere without stopping the current playback.
  • You mean like all the Universal and Warner titles? :-)

    Something should be coming down the pipe soon... stay tuned. Scrolling lists of things are not that hard, either.
  • Yes - exactly!

    What's cool about this example is that it's purely markup.  I didn't really consider a non-jscript route until seeing this example.
  • After looking at drop-down menus in 100% pure markup, we turn our attention to scrolling lists. These...
Page 1 of 1 (4 items)