Focus-able Graphical Button

Hi,

I am evaluating Fuse for developing TV interfaces on Android. The first thing I am trying to do is to generate a couple of menu ‘buttons’ on-screen, and to accept key input (up/down) to move focus between them.

The ‘buttons’ are fully graphical - with an image for focus and no-focus states. My first attempt was to draw two buttons to screen, and for each button to change image when given focus.

The buttons get rendered correctly, but clicking on a button does not display the focus image.

Any thoughts on how this may best be accompished? I’m happy enough to take control over managing the focus state, but wasn’t sure how to set an image based on some external state.

Any thoughts or directions welcomed.

<App Theme="Basic" Background="#eeeeeeff">
    <Panel>
        <FileImageSource ux:Key="btn_1" File="Assets/btn_1.png" />        
        <FileImageSource ux:Key="btn_1_f" File="Assets/btn_1_f.png" />  
        <FileImageSource ux:Key="btn_2" File="Assets/btn_2.png" />        
        <FileImageSource ux:Key="btn_2_f" File="Assets/btn_2_f.png" />  

        <JavaScript>
            var Observable = require("FuseJS/Observable");

            var menu = [
                { 
                    name : 'button_1',
                    background : 'btn_1',
                    background_focus : 'btn_1_f',
                    position : {
                        x:100,
                        y:100,
                        w:200,
                        h:50
                    },
                    next_up : null,
                    next_down : "button_2"                  
                },
                { 
                    name : 'button_2',
                    background : 'btn_2',
                    background_focus : 'btn_2_f',
                    position : {
                        x : 100,
                        y : 155,
                        w : 200,
                        h : 50
                    },
                    next_up : "button_1",
                    next_down : null
                },
            ];

            var currentFocus = Observable(menu[0].name); //Give focus to the first button
            module.exports = {
                currentFocus : currentFocus,
                menu : menu             
            }
        </JavaScript>

        <Each Items="{menu}">
            <Panel X="{position.x}" Y="{position.y}" Width="{position.w}" Height="{position.h}" Focus.IsFocusable="true" >
                <Image Source="{DataToResource background}" />
                <Tapped>
                  <GiveFocus/>
                </Tapped>                
                <WhileFocused>
                    <Image Source="{DataToResource background_focus}" />            
                </WhileFocused>
            </Panel>
        </Each>
    </Panel>
</App>

This solves what you are trying to do:

<App Theme="Basic" Background="#eeeeeeff">
  <Panel>
    <FileImageSource ux:Key="btn_1" File="Assets/btn_1.png" />    <FileImageSource ux:Key="btn_1_f" File="Assets/btn_1_f.png" />    <FileImageSource ux:Key="btn_2" File="Assets/btn_2.png" />    <FileImageSource ux:Key="btn_2_f" File="Assets/btn_2_f.png" />
    <JavaScript>
      var menu = [
        { 
          name : 'button_1',
          background : 'btn_1',
          background_focus : 'btn_1_f',
          position : {
            x:100,
            y:100,
            w:200,
            h:50
          },
          next_up : null,
          next_down : "button_2"        },
        { 
          name : 'button_2',
          background : 'btn_2',
          background_focus : 'btn_2_f',
          position : {
            x : 100,
            y : 155,
            w : 200,
            h : 50
          },
          next_up : "button_1",
          next_down : null
        }
      ];

      module.exports = {
        menu : menu
      }
    </JavaScript>
    <Each Items="{menu}">
      <Panel X="{position.x}" Y="{position.y}" Width="{position.w}" Height="{position.h}" Focus.IsFocusable="true" >
        <Image Source="{DataToResource background_focus}" ux:Name="Focus" Visibility="Hidden" />
        <Image Source="{DataToResource background}" Visibility="Visible" />
        <Tapped>
            <GiveFocus />
            <DebugAction Message="Tapped" />
        </Tapped>
        <WhileFocused>
          <Change Focus.Visibility="Visible" />        </WhileFocused>
      </Panel>
    </Each>
  </Panel>
</App>

But this solves the whole problem with up and down keys as well:

I don’t know if it is possible to set the focus from JavaScript or with Observables now, so this solution is a bit different.

<App Theme="Basic" Background="#eeeeeeff">
  <Panel>
    <FileImageSource ux:Key="btn_1" File="Assets/btn_1.png" />    <FileImageSource ux:Key="btn_1_f" File="Assets/btn_1_f.png" />    <FileImageSource ux:Key="btn_2" File="Assets/btn_2.png" />    <FileImageSource ux:Key="btn_2_f" File="Assets/btn_2_f.png" />
    <JavaScript>
      var Observable = require("FuseJS/Observable");

      var focus = 0;
      var menu = [
        { 
          name : 'button_1',
          background : 'btn_1',
          background_focus : 'btn_1_f',
          focus_vis: Observable('Hidden'),
          position : {
            x:100,
            y:100,
            w:200,
            h:50
          },
          next_up : null,
          next_down : "button_2"        },
        { 
          name : 'button_2',
          background : 'btn_2',
          background_focus : 'btn_2_f',
          focus_vis: Observable('Hidden'),
          position : {
            x : 100,
            y : 155,
            w : 200,
            h : 50
          },
          next_up : "button_1",
          next_down : null
        }
      ];

      setfocus(1);

      function setfocus(i) {
          menu[focus].focus_vis.value = 'Hidden';
          menu[i].focus_vis.value = 'Visible';
          focus = i;
      }

      function up () {
          setfocus(focus - 1);
      }
      function down () {
          setfocus(focus + 1);
      }

      module.exports = {
        menu : menu,
        up: up,
        down: down
      }
    </JavaScript>
    <KeyTrigger Key="Up"><Callback Handler="{up}" /></KeyTrigger>
    <KeyTrigger Key="Down"><Callback Handler="{down}" /></KeyTrigger>
    <Each Items="{menu}">
      <Panel X="{position.x}" Y="{position.y}" Width="{position.w}" Height="{position.h}" Focus.IsFocusable="true" >
        <Image Source="{DataToResource background_focus}" Visibility="{focus_vis}" />
        <Image Source="{DataToResource background}" Visibility="Visible" />
      </Panel>
    </Each>
  </Panel>
</App>

I had to write a new trigger to enable this:

KeyTrigger.uno

using Fuse.Triggers;
using Fuse;

public class KeyTrigger: Trigger
{
    Uno.Platform.Key Wanted;

    string _key;
    public string Key {
        get { return _key; }
        set { 
            _key = value; 
            if (_key == "Up") {
                Wanted = Uno.Platform.Key.Up;
            }
            else if (_key == "Down") {
                Wanted = Uno.Platform.Key.Down;            }
            else if (_key == "Enter") {
                Wanted = Uno.Platform.Key.Enter;            }
        }
    }
    protected override void OnRooted(Node n)
    {
        App.Current.Window.KeyPressed += Window_KeyPressed;
        base.OnRooted(n);
    }

    protected override void OnUnrooted(Node n)
    {
        App.Current.Window.KeyPressed -= Window_KeyPressed;
        base.OnUnrooted(n);
    }

    void Window_KeyPressed(object sender, Uno.Platform.KeyEventArgs args) {        if (args.Key == Wanted) {            Pulse();
        }
    }

}

Many thanks - that seems to do the trick! For reference, I’ve updated your solution to read the next focus elements in each direction from the menu item config object itself. Also, I’ve enabled focussing of an element by tapping gesture as well.

Obviously, handlers for left and right could be added to make this more universal.

<App Theme="Basic" Background="#eeeeeeff">
  <Panel>
  <FileImageSource ux:Key="btn_1" File="Assets/btn_1.png" />  
  <FileImageSource ux:Key="btn_1_f" File="Assets/btn_1_f.png" />  
  <FileImageSource ux:Key="btn_2" File="Assets/btn_2.png" />  
  <FileImageSource ux:Key="btn_2_f" File="Assets/btn_2_f.png" />  

  <JavaScript>
    var Observable = require("FuseJS/Observable");

    var focus_element;
    var menu = [
    { 
      id : 'button_1',
      background : 'btn_1',
      background_focus : 'btn_1_f',
      focus_vis: Observable('Hidden'),
      position : {
      x:100,
      y:100,
      w:200,
      h:50
      },
      next_up : null,
      next_down : "button_2"      
    },
    { 
      id : 'button_2',
      background : 'btn_2',
      background_focus : 'btn_2_f',
      focus_vis: Observable('Hidden'),
      position : {
      x : 100,
      y : 155,
      w : 200,
      h : 50
      },
      next_up : "button_1",
      next_down : null
    }
    ];

    //Give the first button focus
    setfocus(menu[0].id);

    function setfocus(element_id) {

        var target_element = findElement(element_id);

        if (!target_element){
            //Could not find element
            console.log("Error : Could not find element '" + element_id + "'");
        }
        else{
            //Turn off the previous button
            if (focus_element){
                focus_element.focus_vis.value = "Hidden";
            }

            //Highlight the new button
            focus_element = target_element;
            focus_element.focus_vis.value = "Visible";
        }
    }
    function findElement(element_id){
        var matched_elements = menu.filter( function(obj){
            return obj.id == element_id
        });
        if (!matched_elements || matched_elements.length == 0){

            return(null);
        }
        else
        {
            return(matched_elements[0]);
        }
    }

    function up (e) {
      if (focus_element.next_up){
        setfocus(focus_element.next_up);
      }      
    }

    function down () {
        if (focus_element.next_down){
            setfocus(focus_element.next_down);
        }   
    }
    function elementTapped(e){
        if (e.data.id){
            setfocus(e.data.id);    
        }
    }
    module.exports = {
    menu : menu,
    up: up,
    down: down,
    elementTapped : elementTapped
    }
  </JavaScript>
  <KeyTrigger Key="Up"><Callback Handler="{up}" /></KeyTrigger>
  <KeyTrigger Key="Down"><Callback Handler="{down}" /></KeyTrigger>
  <Each Items="{menu}">

    <Panel X="{position.x}" Y="{position.y}" Width="{position.w}" Height="{position.h}" Focus.IsFocusable="true" >
        <Tapped>
            <Callback Handler="{elementTapped}"/>
        </Tapped>
        <Image Source="{DataToResource background_focus}" Visibility="{focus_vis}" />
        <Image Source="{DataToResource background}" Visibility="Visible" />
    </Panel>
  </Each>
  </Panel>
</App>