UI Events

Scope

The following input devices are supported by sky:

  • fingers on multitouch screens
  • mice, including mouse wheels
  • styluses on screens
  • other devices that emulate mice (track pads, track balls)
  • keyboards

The following input devices are not supported natively by sky, but can be used by connecting directly to the mojo application servicing the relevant device:

  • joysticks
  • track balls that move focus (or raw data from track balls)
  • raw data from track pads (e.g. multitouch gestures)
  • raw data from styluses that have their own absolute pads
  • raw data from mice (e.g. to handle mouse capture in 3D games)

The following interactions are intended to be easy to handle:

  • one finger starts panning, another finger is placed on the surface (and ignored), the first finger is lifted, and the second finger continues panning (without the scroll position jumping when the first finger is lifted)
  • right-clicking doesn't trigger buttons by default
  • fingers after the first within a surface don't trigger buttons by default
  • if there are two independent surfaces, they capture fingers unrelated to each other

Frameworks are responsible for converting pointer events described below into widget-specific events such as the following:

  • a click/tap/activation, as distinct from a short drag
  • a context menu request (e.g. right-click, long-press)
  • a drag (moving an item)
  • a pan (scroll)
  • a zoom/rotation (whether using two finger gestures, or one finger with the double-tap-and-hold gesture)
  • a double-tap autozoom

In particular, this means distinguishing whether a finger tap consists of a tap, a drag, or a long-press; it also means distinguishing whether a drag, once established as such, should be treated as a pan or a drag, and deciding whether a secondary touch should begin a zoom/rotation or not.

Pointer events

Each touch or pointer is tracked individually.

New touches and pointers can appear and disappear over time.

When a new one enters the system, a ‘pointer-added’ event is fired at the application's document.

When it is removed, a ‘pointer-removed’ event is fired at the application's document.

A pointer switches from “up” to “down” when it is a touch or stylus that is in contact with the display surface, or when it is a mouse that is being clicked, and from “down” back to “up” when this ends. (Note that clicking a button on a stylus doesn't change it from up to down. A stylus can have a button pressed while “up”.) In the case of a mouse with multiple buttons, the pointer switches back to “up” only when all the buttons have been released.

When one switches from “up” to “down”, the following algorithm is run:

  1. Hit test the position of the pointer, let ‘node’ be the result.
  2. Fire a pointer-down event at the layoutManager for ‘node’. Let ‘result’ be the returned value.
  3. If ‘result’ is undefined, then fire a pointer-down event at the Element for ‘node’. Let ‘result’ be the returned value.
  4. If ‘result’ is undefined or is not an EventTarget, let ‘result’ be the application document.
  5. Let ‘result’ capture this pointer.

A pointer that is “down” is captured -- all events for that pointer will be routed to the chosen target until the pointer goes up, regardless of whether it‘s in that target’s visible area.

When an object captures a pointer and it has no captured pointers so far, if either there are no buttons (touch, stylus) or only the primary button is active (mouse) and this is not an inverted stylus, then that pointer is marked as “primary” for that object. The pointer remains the primary pointer until the corresponding pointer-up event (even if the buttons change).

When one moves, if it is “up” then a ‘pointer-moved’ event is fired at the application's document, otherwise if it is “down” then the event is fired at the object or document that was selected for the ‘pointer-down’ event (the capturing object). If the return value of a ‘pointer-moved’ event is ‘cancel’ then cancel the pointer.

When a pointer‘s button state changes but this doesn’t impact whether it is “up” or “down”, e.g. when a mouse with a button down gets a second button down, or when a stylus' buttons change state, but the pointer doesn't simultaneously move, then a ‘pointer-moved’ event is fired anyway, with dx=dy=0.

When one switches from “down” to “up”, a ‘pointer-up’ event is fired at the object or document that was selected for the ‘pointer-down’ event (the capturing target). The buttons exposed on that event are those that were down immediately prior to the buttons being released.

At the time of a pointer-up event, if there is another pointer that is already down, captured by the same object, and is of the same kind, and that has either no buttons or only its primary button active, then that becomes the new primary pointer for that object before the pointer-up event is sent. Otherwise, the primary pointer stops being primary just after the pointer-up.

When a pointer is canceled, if it is “down”, pretend that the pointer moved to “up”, send ‘pointer-up’ as described below, and drop all events for this pointer until such time as it actually changes to be truly “up”.

Nothing special happens when a capturing target moves in the DOM.

The x and y position of an -up or -down event always match those of the previous -moved or -added event, so their dx and dy are always 0.

Positions are floating point numbers; they can have subpixel values.

These events all bubble and their data is an object with the following fields:

    pointer: an integer assigned to this touch or pointer when it
             enters the system, never reused, increasing
             monotonically every time a new value is assigned,
             starting from 1 (if the system gets a new tap every
             microsecond, this will cause a problem after 285
             years)

       kind: one of 'touch', 'mouse', 'stylus', 'inverted-stylus'

          x: x-position relative to the top-left corner of the
             surface of the node on which the event was fired

          y: y-position relative to the top-left corner of the
             surface of the node on which the event was fired

         dx: difference in x-position since last pointer-moved
             event

         dy: difference in y-position since last pointer-moved
             event

    buttons: a bitfield of the buttons pressed, from the following
             list:

               1: primary mouse button (not available on stylus)

               2: secondary mouse button, primary stylus button

               3: middle mouse button, secondary stylus button

               4: back button

               5: forward button

             additional buttons can be represented by numbers
             greater than six:

               n: (n-2)th mouse button, ignoring any buttons that
                  are explicitly back or forward buttons

                  (n-4)th stylus button, again ignoring any
                  explictly back or forward buttons

             note that stylus buttons can be pressed even when the
             pointer is not "down"

             e.g. if the left mouse button and the right mouse
             button are pressed at the same time, the value will
             be 3 (bits 1 and 2); if the right mouse button and
             the back button are pressed at the same time, the
             value will be 10 (bits 2 and 4)

       down: true if the pointer is down (in pointer-down event or
             subsequent pointer-move events); false otherwise (in
             pointer-added, pointer-up, and pointer-removed
             events, and in pointer-move events that aren't
             between pointer-down and pointer-up events)

    primary: true if this is a primary pointer/touch (see above)
             can only be set for pointer-moved and pointer-up

   obscured: true if the system was rendering another view on top
             of the sky application at the time of the event (this
             is intended to enable click-jacking protections)

When down is true:

   pressure: the pressure of the touch as a number ranging from
             0.0, indicating a touch with no discernible pressure,
             to 1.0, indicating a touch with "normal" pressure,
             and possibly beyond, indicating a stronger touch; for
             devices that do not detect pressure (e.g. mice),
             returns 1.0

pressure-min: the minimum value that pressure can return for this
              pointer

pressure-max: the maximum value that pressure can return for this
              pointer

When kind is ‘touch’, ‘stylus’, or ‘stylus-inverted’:

   distance: distance of detected object from surface (e.g.
             distance of stylus or finger from screen), if
             supported and down is not true, otherwise 0.0.

distance-min: the minimum value that distance can return for this
              pointer (always 0.0)

distance-max: the maximum value that distance can return for this
              pointer (0.0 if not supported)

When kind is ‘touch’, ‘stylus’, or ‘stylus-inverted’ and down is true:

radius-major: the radius of the contact ellipse along the major
              axis, in pixels

radius-minor: the radius of the contact ellipse along the major
              axis, in pixels

 radius-min: the minimum value that could be reported for
             radius-major or radius-minor for this pointer

 radius-max: the maximum value that could be reported for
             radius-major or radius-minor for this pointer

When kind is ‘touch’ and down is true:

orientation: the angle of the contact ellipse, in radians in the
             range

                -pi/2 < orientation <= pi/2

             ...giving the angle of the major axis of the ellipse
             with the y-axis (negative angles indicating an
             orientation along the top-left / bottom-right
             diagonal, positive angles indicating an orientation
             along the top-right / bottom-left diagonal, and zero
             indicating an orientation parallel with the y-axis)

When kind is ‘stylus’ or ‘stylus-inverted’:

orientation: the angle of the stylus, in radians in the range

                -pi < orientation <= pi

             ...giving the angle of the axis of the stylus
             projected onto the screen, relative to the positive
             y-axis of the screen (thus 0 indicates the stylus, if
             projected onto the screen, would go from the contact
             point vertically up in the positive y-axis direction,
             pi would indicate that the stylus would go down in
             the negative y-axis direction; pi/4 would indicate
             that the stylus goes up and to the right, -pi/2 would
             indicate that the stylus goes to the left, etc)

       tilt: the angle of the stylus, in radians in the range

                0 <= tilt <= pi/2

             ...giving the angle of the axis of the stylus,
             relative to the axis perpendicular to the screen
             (thus 0 indicates the stylus is orthogonal to the
             plane of the screen, while pi/2 indicates that the
             stylus is flat on the screen)

TODO(ianh): add an API that exposes the currently existing pointers, so that you can determine e.g. if you have a mouse.

TODO(ianh): determine what the update frequency of these events should be. One set of events per frame? Multiple updates per frame?

Wheel events

When a wheel input device is turned, a ‘wheel’ event that bubbles is fired at the application's document, with the following fields:

      wheel: an integer assigned to this wheel by the system. The
             same wheel on the same system must always be given
             the same ID. The primary wheel (e.g. the vertical
             wheel on a mouse) must be given ID 1.

      delta: an floating point number representing the fraction of
             the wheel that was turned, with positive numbers
             representing a downward movement on vertical wheels,
             rightward movement on horizontal wheels, and a
             clockwise movement on wheels with a user-facing side.

Additionally, if the wheel is associated with a pointer (e.g. a mouse wheel), the following fields must be present also:

    pointer: the integer assigned to the pointer in its
             'pointer-add' event (see above).

          x: x-position relative to the top-left corner of the
             display, in global layout coordinates

          y: x-position relative to the top-left corner of the
             display, in global layout coordinates

Note: The only wheels that are supported are mouse wheels and physical dials. Track balls are not reported as mouse wheels.

Text input events

TODO(ianh): keyboard events