Chapter 31. Input Devices and Gestures

Table of Contents

1. Overview
2. The Mouse
2.1. Basic Mouse Events
2.2. Making Views Clickable
2.3. ClickRegion
2.4. Cursor Management
2.5. Custom Cursors for multiple runtimes
2.6. Custom buttons
2.7. Dragging Views
3. Tracking the Mouse
3.1. Tracking the mouse within a single view
3.2. Tracking the Mouse in overlapping views
4. "View Source" right click menu item
5. Keyboard Input
5.1. Focus and the lz.Focus service
5.2. Focus trap and the focus group
5.3. Reading Key Values

1. Overview

This chapter explains how to incorporate mouse and keyboard input into an OpenLaszlo application. It covers the basic concepts as well as more advanced topics such as tracking the mouse across multiple views while the mouse is down, and how to implement the "right click" context menu in OpenLaszlo applications.

2. The Mouse

Interaction with a view via the mouse is fundamental to all applications. These sections will explore the basic concepts as well some more advanced topics.

2.1. Basic Mouse Events

Interacting with the example below displays the five basic mouse events that can be recognized by a view: onmouseover, onmouseout, onmousedown, onmouseup, and onclick

Although onmouseup and onclick are similar, onmouseup is sent whenever the user lets up on the mouse, while onclick is only sent when the user lets up on the mouse while the cursor is still contained within the boundary of the view.

Example 31.1. The basic mouse events

<canvas height="40" width="100%">
  <view bgcolor="red" onmouseover="txt.setAttribute('text', 'onmouseover')" onmouseout="txt.setAttribute('text', 'onmouseout')" onmousedown="txt.setAttribute('text', 'onmousedown')" onmouseup="txt.setAttribute('text', 'onmouseup')" onclick="txt.setAttribute('text', 'onmouseup, onclick')" x="10" y="10" width="20" height="20"/>

  <text id="txt" x="40" y="11" width="150"/>
</canvas>

2.2. Making Views Clickable

A view will only respond to mouse events when its attribute clickable="true". Views, by default, are not clickable. If, however, any of the mouse events are included in a view's tag (like above) then clickable will be set to true automatically. In the example below, there are no mouse events declared within the tag itself, only in its methods. In this case it becomes necessary to explicitly define clickable="true" for the view.

Example 31.2. Making views clickable

<canvas height="40" width="100%">
  <view bgcolor="red" clickable="true" x="10" y="10" width="20" height="20">
    <handler name="onmousedown">
      this.setAttribute('width', 30);
      this.setAttribute('height', 30);
      this.setAttribute('x', 5);
      this.setAttribute('y', 5);
    </handler>
    <handler name="onmouseup">
      this.setAttribute('width', 20);
      this.setAttribute('height', 20);
      this.setAttribute('x', 10);
      this.setAttribute('y', 10);
    </handler>
  </view>
</canvas>

2.3. ClickRegion

Setting clickregion to a vector-based SWF turns the SWF shape into a clickable hotspot.

Example 31.3. Setting a clickregion

<canvas width="100%" height="300">
  <resource name="waitcursor" src="resources/lzwaitcursor_rsc.swf"/>
  <view height="150" width="100" bgcolor="red" clickable="true" resource="waitcursor" stretches="both" clickregion="waitcursor" onclick="message.addText('\nclick')"/>
  <text id="message" y="140" multiline="true"/>
</canvas>

2.4. Cursor Management

The cursor automatically changes to a hand when it is over a clickable view, but if a custom cursor is desired, then this can be accomplished by defining a custom resource for the cursor and using it with lz.Cursor service as shown below.

Example 31.4. Changing the cursor

<canvas height="30" width="100%">
  <resource name="waitcursor" src="resources/lzwaitcursor_rsc.swf"/>
  <button onclick="setCursor()" text="Click me to change the cursor for 1/2 second">
    <method name="setCursor" args="cursor=null">
      lz.Cursor.setCursorGlobal('waitcursor');
      // call lz.Cursor.restoreCursor() after 1/2 second
      var del= new LzDelegate(lz.Cursor, 'restoreCursor');
      lz.Timer.addTimer(del, 500);
    </method>
  </button>
</canvas>

2.4.1. Disabling the "hand" cursor

By setting lz.Cursor.showHandCursor() to true or false, you show or hide the hand cursor for all clickable views. This means that you can, for example, prevent clicks from traveling through windows, without the hand cursor appearing.

Here is an example showing the hand cursor turned on and off.

Example 31.5. Enabling and disabling the hand cursor

<canvas width="100%" height="110">
    <view width="100" height="100" bgcolor="green">
        <handler name="onclick">
            lz.Cursor.showHandCursor(true);
        </handler>
    </view>
    <view x="110" width="100" height="100" bgcolor="yellow">
        <handler name="onmouseover">
            lz.Cursor.showHandCursor(true);
        </handler>
        <handler name="onmouseout">
            lz.Cursor.showHandCursor(false);
        </handler>
    </view>
    <view x="220" width="100" height="100" bgcolor="red">
        <handler name="onclick">
            lz.Cursor.showHandCursor(false);
        </handler>
    </view>
</canvas>

2.5. Custom Cursors for multiple runtimes

With OpenLaszlo 4, you can set custom cursors in DHTML and SWF. If you plan to use custom cursors across DHTML and SWF, be sure you're using appropriate cursor IDs for DHTML (see http://www.quirksmode.org/css/cursor.html for more info), and make sure you've included resources named after those IDs for SWF.

[Note] Note

Cursor IDs with hypens work fine in DHTML but are not valid SWF names. For example, you should change the cursor ID col-resize, to colResize.

Note that global cursors currently only work for clickable items in DHTML.

Example 31.6. Using custom cursors

<canvas width="100%" height="260">
    <resource name="crosshair" src="resources/cursor_crosshair.png"/>
    <resource name="wait" src="resources/cursor_wait.png"/>
    <simplelayout spacing="10"/>
    <button onclick="lz.Cursor.showHandCursor(true)">Change default cursor for all buttons to hand
        (on by default)</button>
    <button onclick="lz.Cursor.showHandCursor(false)">Change default cursor for all buttons to
        default</button>
    <button onclick="lz.Cursor.setCursorGlobal('crosshair')">Turn on crosshairs</button>
    <button onclick="lz.Cursor.unlock()">Turn off crosshairs</button>

    <view bgcolor="red" showhandcursor="false" clickable="true">
        <text>showhandcursor is false</text>
    </view>
    <view bgcolor="green" showhandcursor="true" clickable="true">
        <text>showhandcursor is true</text>
    </view>
    <view bgcolor="yellow" cursor="wait" clickable="true">
        <text>cursor is 'wait'</text>
    </view>
</canvas>

2.6. Custom buttons

Creating a custom button that changes its images with onmouseup, onmouseover, and onmousedown is a common practice. Here is an example using the <basebutton> tag:

Example 31.7. Creating a custom button using basebutton

<canvas height="150" width="100%">
  <!-- first create the multi-frame resource and give it a name -->
  <resource name="mybutton_rsrc">
    <!-- first frame MUST be the mouseup state of the button -->     
    <frame src="resources/button-up.png"/>
    <!-- second frame MUST be the mouseover state of the button -->
    <frame src="resources/button-over.png"/>
    <!-- third frame MUST be the mousedown state of the button -->
    <frame src="resources/button-down.png"/>
  </resource>
  
  <!-- Second, assign the resource to a basebutton tag -->
  <basebutton resource="mybutton_rsrc"/>
</canvas>

2.7. Dragging Views

Dragging a view can be accomplished with the use of a dragstate. When a dragstate is applied, views can be dragged freely or constrained to a boundary as well as an axis. The example below shows the use of a dragstate with its max, min, and axis attributes.

Example 31.8. Dragging a view

<canvas height="120" width="100%">
  <simplelayout spacing="3"/>
  
  <text>dragging within a bounded area</text>
  <view bgcolor="yellow" height="40" width="160">
    <view bgcolor="red" width="20" height="20" onmousedown="this.dragger.apply()" onmouseup="this.dragger.remove()">
      <dragstate name="dragger" drag_min_x="0" drag_max_x="$once{parent.width - this.width}" drag_min_y="0" drag_max_y="$once{parent.height - this.height}" drag_axis="both"/>
    </view>
  </view>
  
  <text>dragging along the x-axis</text>
  <view bgcolor="yellow" height="40" width="160">
    <view bgcolor="red" width="20" height="20" onmousedown="this.dragger.apply()" onmouseup="this.dragger.remove()">
      <dragstate name="dragger" drag_min_x="0" drag_max_x="$once{parent.width - this.width}" drag_min_y="0" drag_max_y="$once{parent.height - this.height}" drag_axis="x"/>
    </view>
  </view>
 </canvas>

3. Tracking the Mouse

3.1. Tracking the mouse within a single view

This simple program shows how to do basic mouse tracking in a view:

Example 31.9. Simple mouse tracking

<canvas height="500" width="100%" debug="true">
    <debug y="300"/>
    <view width="300" height="300" bgcolor="red" clickable="true">
        <attribute name="moustracker_del" value="$once{ new LzDelegate( this, 'trackmouse' )}"/>
        <handler name="onmousedown">
            moustracker_del.register(lz.Idle,'onidle');
        </handler>
        
        <handler name="onmouseup">
            moustracker_del.unregisterAll();
        </handler>
        
        <method name="trackmouse" args="v">
            Debug.debug("%s %d %d", "mousex, mousey:", this.getMouse('x'),this.getMouse('y'));
        </method>
    </view>
</canvas>

This generates a lot of points, but you can decide how much data to filter or not in the trackmouse method.

3.2. Tracking the Mouse in overlapping views

Once the mouse is down in relation to a specific view, onmouseover and onmouseout events are only sent to that view. This makes it difficult to track the mouse over other views. This section explains how to accomplish this, first by using a base component called <basetrackgroup>, and then with the low level APIs that basetrackgroup is built upon.

In the example below, notice that the onmouseover and onmouseout events change the size of the view while the onmousetrackover and onmousetrackout events change the color of the view. As you drag the mouse, the first view will continue to change its size and color while the other views will only change their color. Again, this is because the basic onmouseover and onmouseout events are only being sent to the first view that was clicked. However, onmousetrackover and onmousetrackout are sent to all views while the mouse is down.

Example 31.10. Using basetrackgroup

<canvas height="160" width="100%">
  <class name="myTrackableView" bgcolor="red" onmouseover="setAttribute('width', 70)" onmouseout="setAttribute('width', 60)" onmouseup="setAttribute('bgcolor', 0xFF0000)" width="60" height="30">

    <!-- Use methods for mouse-tracking events -->
    <handler name="onmousetrackover">
      setAttribute('bgcolor', 0x0000FF); //blue
    </handler>

    <handler name="onmousetrackout">
      setAttribute('bgcolor', 0xFF0000); //red
    </handler>

    <handler name="onmousetrackup">
      setAttribute('bgcolor', 0xFF0000); //red
    </handler>
  </class>

  <text>Click on any red view and drag the mouse. </text>
  <basetrackgroup bgcolor="yellow" x="20" y="40">
    <myTrackableView/>
    <myTrackableView/>
    <myTrackableView/>
    <simplelayout axis="y" spacing="5"/>
  </basetrackgroup>
</canvas>

4. "View Source" right click menu item

The default canvas context (right click) menu has a "view source" item, available in Flash player Version 7 and later. By default this menu includes an "About OpenLaszlo" entry, which links to the OpenLaszlo website, and an option to view source.

For an application framitz.lzx that is deployed proxied (using the OpenLaszlo Server), clicking on View Source will fetch framitz.lzx?lzt=source in a new browser window. If you don't wish to make source visible, set allowRequestSOURCE=false in WEB-INF/lps/config/lps.properties.

Starting with Flash 8, you can control the content of the menu using the class lz.contextmenu, which you instantiate using new lz.contextmenu(). The method lz.contextmenu.makeMenuItem ()instantiates an lz.contextmenuitem.

To make a new menu, first you create the menu, then you create a menu item, and then you add the menu item to the menu. The data type of the menu item is lz.contextmenuitem; you supply the text that you want to appear in the menu (the word 'Delete' may not be used as context menu text) and a delegate that specifies how you want that menu item to be handled. lz.contextmenu.addItem() adds an item to that menu. So, to create a new menu you would do:

Example 31.11. Creating a context menu

<canvas height="200" width="100%">
  <view width="350" height="100" bgcolor="#cccccc">
    <text text="Right click in the gray area (but not on this text)"/> 
    <handler name="oninit">
      var cmenu = new lz.contextmenu(); // create the menu
      var item1 = cmenu.makeMenuItem('hello', new LzDelegate(this, "handlerightclick")); // create the menu item, and set up an LzDelegate as a callback
      cmenu.addItem(item1); //add the item to the menu
      this.setAttribute("contextmenu", cmenu);
    </handler>   
    <method name="handlerightclick" args="val">
      message.addText("\nhello world");
    </method>
  </view>
  <text id="message" y="100" multiline="true"/>
</canvas>

There is no method to clear the menu (Settings and About Flash Player are always there), but you can erase one with something like

var cmenu = new lz.contextmenu();
canvas.setDefaultContextMenu(cmenu)

That would give you an empty context menu.

[Warning]
[SOLO]

SOLO deployed applications, say, framitz.lzx.swf, will attempt to fetch "framitz.lzx.swf.zip".This assumes that a copy of the source will be placed there if you desire to make your source visible. If you don't wish to share code of your SOLO application, use the method above.

5. Keyboard Input

In OpenLaszlo applications, the keyboard input can be accepted by the view which has the keyboard "focus". The lz.Focus service allows you to set and control the focus. The opposite of focus is "blur", as discussed below.

5.1. Focus and the lz.Focus service

A focusable view is a view whose focusable attribute is true (the default value). When the user clicks on a focusable view, the focus is set to that view. If the view is different from the previously focused view, an onblur is sent to the previously focused view, and an onfocus is sent to the newly focused view.

To prevent a view from taking keyboard input, you can set its focusable attribute to "false".

5.2. Focus trap and the focus group

Focus trap: A view's focus trap is its most direct ancestor whose focustrap attribute is true, or the canvas. A view's focus group is the set of focusable views that are descendants of the focus trap, and are not descendants of any more immediate ancestor whose focustrap attribute is also true.

The effect of this definition is that tabbing within the children of a view that has a true focustrap, such as a window, will tab to another child of the same view, but won't tab to a view within another window.

The Contacts demo demonstrates the use of keyboard focus. Double-click on a name to open the details window. Click in a text, and then press tab and shift-tab to move to other text fields within the same focus group.

5.2.1. Using Tab key to move within a focus group

When the tab key is pressed, the focus moves to the next focusable view within the current focus group. If there is no next view, the first view within the current focus group is selected. Similarly, shift-tab selects the previous focusable view. If there is no previous view, the last view within the current focus group is selected.

A view can override the getNextSelection() and getPrevSelection() methods to change this default behavior. The tab and shift tab keys select the view returned by calling getNextSelection() and getPrevSelection(), respectively, on the view that currently has the keyboard focus.

5.2.2. Programmatic control of focus group

In addition to the tab key, focus within a focus group can also be set under programmatic control, by calling the lz.Focus.setFocus(), lz.Focus.next(), and lz.Focus.prev() methods.

5.3. Reading Key Values

One way of integrating keyboard input into your application is to have a focusable view capture pressed key codes. Only one view can be focusable at any one time. In this example, we're making that view focused oninit (although in a larger application, you could bring focus to that view using the tab key, or you could write an onclick method to tell it to focus).

The example below illustrates use of <lz.Focus> to make a view able to accept keyboard input.

[Note] Note
The actual character that corresponds to a particular key depends on the operating system and locale, so the character returned by String.fromCharCode() may be different on your system.

Example 31.12. Reading key values

<canvas width="100%" height="200" debug="true">
    <debug x="110" y="15"/>   
    <!-- NOTE: Even though the view below takes focus oninit,
        in some browsers you might still need to click on the
        application itself, to take the focus away from the 
        browser itself and to the Flash Player. -->
    <view width="100" height="100" bgcolor="0x333399" focusable="true" oninit="lz.Focus.setFocus(this)">    
        <handler name="oninit">
            this.keyCodes = new Array()
            this.keyCodes[65] = "A for Apple";
            this.keyCodes[76] = "L for Laszlo";
            this.keyCodes[79] = "O for Optometrist";
            this.keyCodes[83] = "S for Sammy";
            this.keyCodes[90] = "Z for Zebra";
        </handler>     
        <handler name="onkeydown" args="akeycode">
            // respond here    
            Debug.debug("Key pressed: %w", akeycode);
            if ( this.keyCodes[akeycode] != undefined ) {
            Debug.debug("%s", this.keyCodes[akeycode] );
            } else {
            Debug.debug("You pressed: %w", String.fromCharCode(akeycode));
            }
        </handler>
    </view>
</canvas>