Connect IQ SDK

User Interface

Making a user interface toolkit that can adapt to multiple products is hard, but it is even harder in the Garmin ecosystem. The mechanical designs of these products may be very different depending on the use case the product is designed for. As a result, the user interfaces of Garmin devices also vary by product: some have physical buttons, some have coordinate touch screens, and some provide up, down, and action buttons.

Rather than design a user interface toolkit that adapts to what device the UI is running on, the Connect IQ SDK makes it easier to port the user interface to different products. At the resource compiler level, we include a way to define a page in XML as well as specify page definitions per product. These tools make it easy to support multiple devices with a single app.

Drawables, Views, And Layouts

Watch faces and apps have a page stack. A View is an object that represents a page. Views can be pushed onto and popped off of the page stack, or a view can replace another view on the page stack with a transition. Below is a closer look at the general View structure:

// View is the base class for a drawable page. It handles the lifecycle of each
// View in an app. For widgets and watch-apps the lifecyle is onStart(),
// onLayout(), onShow(), onUpdate() and onHide(). For watchfaces and datafields
// the lifecycle is onLayout(), onShow() and onUpdate().
class View
{
    // The entry point for the View is onLayout(). This is called before the
    // View is shown to load resources and set up the layout of the View.
    // @param [Graphics.Dc] dc The drawing context
    // @return [Boolean] true if handled, false otherwise
    function onLayout( dc );

    // When the View is brought into the foreground, onShow() is called.
    // @return [Boolean] true if handled, false otherwise
    function onShow();

    // When a View is active, onUpdate() is used to update dynamic content.
    // This function is called when the View is brought to the foreground.
    // For widgets and watch-apps it is also called when WatchUi.requestUpdate()
    // is called. For watchfaces it is called once a minute and for datafields
    // it is called once a second. If a class that extends View does not
    // implement this function then any Drawables contained in the View will
    // automatically be drawn.
    // @param [Graphics.Dc] dc The drawing context
    // @return [Boolean] true if handled, false otherwise
    function onUpdate( dc );

    // Before the View is removed from the foreground, onHide() is called.
    function onHide();

    // Use setLayout() to set the layout for the View. If the extending class
    // does not override onUpdate(), then all Drawables contained in layout
    // will automatically be drawn by the View.
    // @param [Array] layout An array of Drawables
    function setLayout(layout);
}

Each View contains a layout. A layout is an array of drawable objects, called Drawables, where each Drawable is positioned in the View. A Drawable can draw itself to a device context through the draw( dc ) method.

An illustration of Layouts, Views, and Drawables
An illustration of Layouts, Views, and Drawables

Any object that extends Drawable is a drawable object, expose their properties publicly. (This will become useful when we discuss the animation system.) Monkey C provides the basic drawables Text and Bitmap, allowing text and bitmap resources to be included in layouts.

Defining Layouts And Drawables In Resources

The resource compiler allows page layouts to be customized to a particular device without changing any Monkey C code. In addition, DrawableList objects can also be defined, which are Drawable objects that can draw a number of graphics primitives.

Using a Layout

When defining a layout in XML, simply list the drawable objects to include in your layout inside a set of layout tags. Each drawable listed will be turned into Monkey C Drawable objects and drawn one after the other. Because of this, if two drawables in your layout overlap each other the drawable that is defined second will draw on top of the drawable that is defined first. Here is an example of a basic layout:

<resources>
    <layout id="MainLayout">
        <drawable id="MainBackground" />
        <label text="Page Heading" x="10" y="5" font="Gfx.FONT_LARGE" color="Gfx.COLOR_BLACK" />
        <label text="Your information goes here." x="10" y="25" font="Gfx.FONT_MEDIUM" color="Gfx.COLOR_DK_GRAY" />
    </layout>
</resources>

To use this layout in your code simply call setLayout() inside the onLayout(dc) function. Call the View’s onUpdate(dc) if you plan on using the onUpdate(dc) function to update dynamic values on the screen. For example:

class MainView {
    function onLayout( dc ) {
        setLayout( Rez.Layouts.MainLayout( dc ) );
    }

    function onUpdate( dc ) {
        // Call parent’s onUpdate(dc) to redraw the layout
        View.onUpdate( dc );

        // Include anything that needs to be updated here
    }
}

The following attributes are supported by the layout tag:

Attribute Definition Valid Values Default Value Notes
id The handle for the layout. This is used to reference the layout in the Rez module Any value that starts with a letter NA Required

Drawables (both bitmaps and drawable XML resources) can be included in a Layout using the drawable tag. The following attributes are supported by the drawable tag:

Attribute Definition Valid Values Default Value Notes
id The handle for the drawable. The ID provided here references the drawable defined in the resource XML file Any value that starts with a letter NA Required; the drawable must also be defined in a resource XML file

Text can also be included in layouts. To inlude text use the label tag, which supports the following attributes:

Attribute Definition Valid Values Default Value Notes
text The text to be displayed NA An empty string
font The font to be used when drawing the text Graphics font constant or the ID of a user-defined font Graphics.FONT_MEDIUM
x The X coordinate of the point that the text will be justified against pixel value or center 0
y The Y coordinate of the point that the text will be justified against pixel value or center 0
justification How the text should be justified in relation to the X & Y location Graphics text justify constant Graphics.TEXT_JUSTIFY_LEFT
color The color of the text Graphics color constant or a 24-bit integer of the form 0xRRGGBB The current draw context’s foreground color

Drawables

Drawable XML resources consist of a list of basic drawables: bitmaps and shapes. To create an XML drawable, define a <drawable-list> in an XML resource file. Both <bitmap> and <shape> tags should be be placed as child nodes inside the <drawable-list>. An example drawable-list:

<resources>
    <drawable-list id="Smiley" background="Gfx.COLOR_YELLOW">
        <shape type="circle" x="10" y="10" radius="5" color="Gfx.COLOR_BLACK" />
        <shape type="circle" x="30" y="10" radius="5" color="Gfx.COLOR_BLACK" />
        <bitmap id="mouth" x="15" y="25" filename="../bitmaps/mouth.png" />
    </drawable-list>
</resources>

To use this drawable in code do the following:

function onUpdate( dc ) {
    var mySmiley = new Rez.Drawables.Smiley();
    mySmiley.draw( dc );
}

The <drawable-list> tag supports the following attributes:

Attribute Definition Valid Values Default Value Notes
id The ID of the drawable Any string that starts with a character NA Required
x The X coordinate of the top left corner in relation to the parent element pixel value or center 0
y The Y coordinate of the top left corner in relation to the parent element pixel value or center 0
width The width of the drawable list. pixel value or fill fill
height The height of the drawable list. pixel value of fill fill
foreground The color of elements (shapes and text) drawn in this layout Graphics color constant or a 24-bit integer of the form 0xRRGGBB The current draw context’s foreground color
background The background color of this layout Graphics color constant or a 24-bit integer of the form 0xRRGGBB Gfx.COLOR_TRANSPARENT

The <shape> tag supports the following attributes:

Attribute Definition Valid Values Default Value Notes
type The type of the shape to be drawn rectangle, ellipse, circle, or polygon NA Required
x For circles and ellipses: X coordinate of the center of the shape in relation to the parent; for everything else: the X coordinate of the top left corner in relation to the parent element. pixel value or center 0
y For circles and ellipses: Y coordinate of the center of the shape in relation to the parent; for everything else: the Y coordinate of the top left corner in relation to the parent element pixel value or center 0
points A list of points which defines the polygon [[x1, y1], [x2, y2], ... , [xN, yN]] NA Required for polygon; must have at least 3 points
width The width of the shape to be drawn pixel value or fill fill Only valid for rectangle
height The height of the shape to be drawn pixel value or fill fill Only valid for rectangle
a The a value of the ellipse to be drawn pixel value or fill fill Only valid for ellipse
b The b value of the ellipse to be drawn pixel value or fill fill Only valid for ellipse
color The color of the shape to be drawn Graphics color constant or a 24-bit integer of the form 0xRRGGBB The current draw context’s foreground color
corner_radius The radius of the rounded corners of the rectangle pixel value 0 Only valid for rectangle
radius The radius of the circle pixel value 0 Required for circle
border_width The width of the border around the shape pixel value 0 Only valid for rectangle, ellipse, and circle
border_color The color of the border around the shape Graphics color constant or a 24-bit integer of the form 0xRRGGBB The current draw context’s foreground color Only valid for rectangle, ellipse, and circle

The <bitmap> tag supports the following attributes:

Attribute Definition Valid Values Default Value Notes
id The ID of the drawable Any string that starts with a character NA Required
x The X coordinate of the top left corner in relation to the parent element pixel value or center 0
y The Y coordinate of the top left corner in relation to the parent element pixel value or center 0
filename The relative path to the image that should be shown A valid, relative path NA Required

Custom Drawables

In some cases it is useful to have a drawable entry within a layout point to a custom defined class which extends Toybox.WatchUi.Drawable. This is possible by using the class attribute of the <drawable> tag.

<layout>
    <drawable id="MoveBar" class="CustomMoveBar" />
</layout>

The drawable entry above will add a new instance of the CustomMoveBar class to the layout. You can then define how the CustomMoveBar is drawn by overriding the draw(dc) function.

using Toybox.WatchUi as Ui;

class CustomMoveBar extends Ui.Drawable {
    function draw(dc) {
        // Draw the move bar here
    }
}

Passing Parameters to Custom Drawables

If may be useful to pass values into a custom Drawable. To do this you define <param> children within the <drawable> tag. The <param> tag should define the name of the parameter using the name attribute and the value as it’s content. The content value is passed as is to the custom Drawable. This means if you want to pass a string you must wrap the content in quotes.

<layout>
    <drawable id="MoveBar" class="CustomMoveBar">
        <param name="color">Gfx.COLOR_RED</param>
        <param name="string">"Hello Custom Drawable!"</param>
    </drawable>
</layout>

The parameters defined in XML are passed to the custom Drawable’s initialize function as a dictionary. The names are passed as symbols and the values are passed as they appear in the XML.

using Toybox.WatchUi as Ui;

class CustomMoveBar extends Ui.Drawable {

    hidden var mColor, mString;

    function initialize(params) {
        // You should always call the parent's initializer and
        // in this case you should pass the params along as size
        // and location values may be defined.
        Drawable.initialize(params);

        // Get any extra values you wish to use out of the params Dictionary
        mColor = params.get(:color);
        mString = params.get(:string);
    }
}

Goal Views (CIQ 1.3)

The screen displayed when activity tracking goals are reached can be overridden by the active watchface. This is done by implementing the getGoalView() function in the application’s AppBase class.

class AppBase {
    function getGoalView(goalType);
}

The ‘getGoalView()’ function is passed one of the supported goal view types: ‘GOAL_TYPE_STEPS’, ‘GOAL_TYPE_FLOORS_CLIMBED’, or ‘GOAL_TYPE_ACTIVE_MINUTES’. Not all types are supported on every product. The application can return a view to be displayed from this function, or null to allow the system to display the default goal display. Goal Views can start animations, similar to the main WatchFace view when onExitSleep() is triggered. Goal Views are displayed for approximately 10 seconds. When they expire, the system will call ‘getInitialView()’ to switch back to the main WatchFace view.

Graphics

The graphics module handles drawing bitmaps, fonts, and shapes to the device screen. In ConnectIQ version 2.3 a new type of drawing surface, called a BufferedBitmap was added. These surfaces are not visible, but the content can be copied to the device display using the drawBitmap() method.

Drawing Context

The Dc object is used to draw to a graphics surface. The primary device surface is provided to the View object methods onLayout(), onUpdate(), and onPartialUpdate(). The size of the surface can be queried with the getWidth() and getHeight() methods.

There are several methods in the Dc object that are used to draw to the surface, including methods to draw primative shapes, drawBitmap() to render bitmaps, and drawText() to draw strings using a specified font. The drawing pen width can be set with the setPenWidth() method. The clear() method will set all active (unclipped) pixels on the surface to the background color.

The setColor() method allows you to set the foreground and background drawing colors. Colors are passed to setColor as 24-bit colors of the form 0xRRGGBB. When setting a color, the device will select the closest available color on the system. To see what colors are available on devices, see the User Experience Guide, which is also available in the ConnectIQ SDK.

module Graphics
{
    class Dc
    {
        //! Erase the screen using the background color by calling clear().
        //! @since 1.0.0
        function clear();

        //! Use drawCircle() to draw a circle around a point.
        //! @param [Number] x X location of circle center
        //! @param [Number] y Y location of circle center
        //! @param [Number] radius Radius of circle
        //! @since 1.0.0
        function drawCircle(x, y, radius);

        //! Use drawEllispe() to draw an ellipse around a point.
        //! @param [Number] x X location of ellipse center
        //! @param [Number] y Y location of ellipse center
        //! @param [Number] a The radius of the ellipse along the x axis
        //! @param [Number] b The radius of the ellipse along the y axis
        //! @since 1.0.0
        function drawEllipse(x, y, a, b);

        //! Draw a point on the screen with drawPoint().
        //! @param [Number] x X location of point
        //! @param [Number] y Y location of point
        //! @since 1.0.0
        function drawPoint(x, y);

        //! Draw a rectangle with drawRectangle().
        //! @param [Number] x X location of upper corner
        //! @param [Number] y Y location of upper corner
        //! @param [Number] width Width value of rectangle
        //! @param [Number] height Height value of rectangle
        //! @since 1.0.0
        function drawRectangle(x, y, width, height);

        //! Draw a rounded rectangle with drawRoundedRectangle().
        //! @param [Number] x X location of upper corner
        //! @param [Number] y Y location of upper corner
        //! @param [Number] width Width value of rectangle
        //! @param [Number] height Height value of rectangle
        //! @param [Number] radius Radius of rounding.
        //! @since 1.0.0
        function drawRoundedRectangle(x, y, width, height, radius);

        //! Draw a bitmap to the screen with drawBitmap().
        //! @param [Number] x Top left X coordinate to begin the draw
        //! @param [Number] y Top left Y coordinate to begin the draw
        //! @param [Object] bitmap The WatchUi.BitmapResource or Graphics.BufferedBitmap to render.
        //!                 The source color palette must be a subset of the destination color palette.
        //! @raise InvalidPaletteException Thrown if the source color palette is not a subset of the destination palette
        //! @since 1.0.0
        function drawBitmap(x, y, rez);

        //! Draw a line between two points using drawLine().
        //! @param [Number] x1 First x coord
        //! @param [Number] y1 First y coord
        //! @param [Number] x2 Second x coord
        //! @param [Number] y2 Second y coord
        //! @since 1.0.0
        function drawLine(x1, y1, x2, y2);

        //! Fill a circle with the foreground color using fillCircle().
        //! @param [Number] x X location of circle center
        //! @param [Number] y Y location of circle center
        //! @param [Number] radius Radius of circle
        //! @since 1.0.0
        function fillCircle(x, y, radius);

        //! Fill an ellipse with the foreground color using fillEllipse().
        //! @param [Number] x X location of ellipse center
        //! @param [Number] y Y location of ellipse center
        //! @param [Number] a The radius of the ellipse along the x axis
        //! @param [Number] b The radius of the ellipse along the y axis
        //! @since 1.0.0
        function fillEllipse(x, y, a, b);

        //! To fill a polygon, use fillPolygon().
        //! @param [Array] pts Array of coordinates with a 64 point limit
        //! @since 1.0.0
        function fillPolygon(pts);

        //! Fill a rectangle with the foreground color using fillRectangle().
        //! @param [Number] x X location of upper corner
        //! @param [Number] y Y location of upper corner
        //! @param [Number] width Width value of rectangle
        //! @param [Number] height Height value of rectangle
        //! @since 1.0.0
        function fillRectangle(x, y, width, height);

        //! Fill a rounded rectangle with the foreground color using fillRoundedRectangle().
        //! @param [Number] x X location of upper corner
        //! @param [Number] y Y location of upper corner
        //! @param [Number] width Width value of rectangle
        //! @param [Number] height Height value of rectangle
        //! @param [Number] radius Radius of rounding
        //! @since 1.0.0
        function fillRoundedRectangle(x, y, width, height, radius);

        //! Use setColor() to set the current foreground and background colors.
        //! @param [Number] foreground Graphics.COLOR_* constant or 24-bit integer of the form 0xRRGGBB
        //! @param [Number] background Graphics.COLOR_* constant or 24-bit integer of the form 0xRRGGBB
        //! @since 1.0.0
        function setColor(foreground, background);

        //! Use setPenWidth() to set the width of a line
        //! @param [Number] width in pixels
        //! @since 1.0.0
        function setPenWidth(width);

        //! Use drawArc() to draw an arc.
        //! 0 degree: 3 o'clock position.
        //! 90 degrees: 12 o'clock position.
        //! 180 degrees: 9 o'clock position.
        //! 270 degrees: 6 o'clock position.
        //! @param [Number] x X location of arc center
        //! @param [Number] y Y location of arc center
        //! @param [Number] r radius of arc.
        //! @param [Number] attr Arc drawing attributes. (ARC_COUNTER_CLOCKWISE or ARC_CLOCKWISE)
        //! @param [Number] degreeStart The start angle of the arc by degrees.
        //! @param [Number] degreeEnd The end angle of the arc by degrees.
        //! @since 1.2.0
        function drawArc(x, y, r, attr, degreeStart, degreeEnd);
    }
}

Clipping (CIQ 2.3)

A clipping region can be set for a Dc object using the setClip() method. The top left corner coordinates, width, and height are specified to set this region. All pixels outside of this region will be unaffected by any drawing operations. The pixels within the region will be updated normally. The clearClip() method will remove the clipping region.

module Graphics
{
    class Dc
    {
        //! setClip applies a clipping region to the dc.
        //! Pixels outside of the region will not be affected by any operations
        //! @param [Number] x the x-coordinate of the top left corner of the clipping region
        //! @param [Number] y the y-coordinate of the top left corner of the clipping region
        //! @param [Number] width the width of the clipping region in pixels
        //! @param [Number] height the height of the clipping region in pixels
        //! @since 2.3.0
        function setClip( x, y, width, height );

        //! clearClip resets the drawable area to the full area of the dc
        //! @since 2.3.0
        function clearClip();
    }
}

Strings and Fonts

Text can be drawn to the Dc object using the drawText() method. The Dc object also has methods available to get the text width and height of a string with a specified font. Note that text size methods are also available in the Graphics module outside the Dc object.

module Graphics
{
    class Dc
    {
        //! Draw text at the given location using drawText().
        //! This method is not supported for anti-aliased fonts (includes most built in
        //! fonts) for BufferedBitmaps that have a palette.
        //! @param [Number] x X location of text
        //! @param [Number] y Y location of text
        //! @param [FontResource] font Font to use. This can be a custom font loaded from resources or a Graphics.FONT_* value.
        //! @param [String] text String to render. This can be a string loaded from resources or a hard coded string.
        //! @param justification One of the Graphics.TEXT_JUSTIFY_XXX constants
        //! @raise InvalidPaletteException Thrown if an anti-aliased font is used on a paletted bitmap.
        //! @since 1.0.0
        function drawText(x, y, font, text, justification);

        //! Get the width and height of a string. This takes new lines into account when
        //! determining the height. The width is the maximum width for a given line of the string.
        //! If a string has two \n in it, the height would be for three lines and the width would be the
        //! width of the longest string.
        //! @param [String] text Text to get width for
        //! @param [FontResource] font Graphics.FONT_* constant or font resource of the text to be measured
        //! @return [Array] [width, height] of string in pixels
        //! @since 1.0.0
        function getTextDimensions(text, font);

        //! Get the width of a string with getTextWidthInPixels().
        //! @param [String] text Text to get width for
        //! @param [FontResource] font Graphics.FONT_* constant or font resource of the text to be measured
        //! @return [Number] Width of string in pixels
        //! @since 1.0.0
        function getTextWidthInPixels(text, font);

        //! Get The Font Height by using getFontHeight().
        //! @param [FontResource] font The font to measure
        //! @return [Number] The height of the font
        //! @since 1.0.0
        function getFontHeight(font);
    }

    //! Gets the recommended distance above the baseline for single
    //! spaced text. The base line is the line on which the text sits.
    //! @param [Number] font Font to use
    //! @return [Number] The ascent of the font
    //! @since 1.2.0
    function getFontAscent(font);

    //! Gets the recommended distance below the baseline for single
    //! spaced text. The base line is the line on which the text sits.
    //! @param [Number] font Font to use
    //! @return [Number] The descent of the font
    //! @since 1.2.0
    function getFontDescent(font);

    //! Gets the height (ascent plus descent) of the given font.
    //! @param [Number] font Font to use
    //! @return [Number] The height of the font
    //! @since 1.2.0
    function getFontHeight(font);
}

Buffered Bitmaps (CIQ 2.3)

The BufferedBitmap class can be used to draw to surface other than the primary display surface. There are two options for creating a BufferedBitmap object. The first is to generate one from a loaded bitmap resource. In this case, the provided bitmap is used as the drawing surface that is manipulated. The second option is to specify the width, and height of the surface, and optionally a color palette. If no color palette is specified, the BufferedBitmap will use the system colors, and will not have a palette. If a bitmap resource is provided to the initializer, the width, height, and palette parameters are ignored.

module Graphics
{
    //! This class represents a offscreen bitmap. It provides methods to
    //! modify the bitmap palette, and get a drawable context.
    //! @since 2.3.0
    class BufferedBitmap {
        //! @param [Dictionary] options Dictionary of options. Must contain width and height, with optional palette,
        //!                     or a Bitmap Resource. This resorurce is not allowed to have an alpha channel.
        //! @option options [Number] :width The width of the surface in pixels
        //! @option options [Number] :height The height of the surface in pixels
        //! @option options [Array] :palette The colors used in this surface. Using less will reduce the bitmap size.
        //!                                  The bitmap will use the system default if not provided. The maximum palette
        //!                                  size allowed is 256 colors. If a palette is provided, the number of colors
        //!                                  must also be <= to the number of system colors.
        //! @option options [BitmapResource] :bitmapResource A bitmap resource to initialize
        //! @raise InvalidPaletteException Thrown if the palette size exceeds the number of system colors.
        //! @raise InvalidPaletteException Thrown if the palette size exceeds 256 colors.
        //! @raise InvalidBitmapResourceException Thrown if the bitmap resource provided has an alpha channel.
        //! @since 2.3.0
        function initialize(options);
    }
}

If a BufferedBitmap does have a palette, it can be read using the getPalette() method. The palette can also be modified using the setPalette() method. The palette provided must have the same number of colors as the existing palette for that bitmap. All pixels in the image will change color to the new color assigned at each color index. Note that Bitmaps with a palette that are generated by the resource compiler will have an additional transparent index at the end of the specified palette unless the disableTransparency flag has been specified.

module Graphics
{
    //! This class represents a offscreen bitmap. It provides methods to
    //! modify the bitmap palette, and get a drawable context.
    //! @since 2.3.0
    class BufferedBitmap {
        //! @return [Array] The current palette for this bitmap. null if this surface uses the system palette
        //! @since 2.3.0
        function getPalette();

        //! @param [Array] A new palette for this bitmap. The number of colors must match the current palette.
        //!                Each color in the image will be replaced with the colors specified in the new palette.
        //! @raise InvalidPaletteException Thrown if the palette size does not match the current palette.
        //! @since 2.3.0
        function setPalette(palette);
    }
}

A Drawing Context can be obtained from the BufferedBitmap using the getDc() method. This returns a Dc class that has the same capabilities as the primary device Dc that is provided to the methods onLayout(), onUpdate() and onPartialUpdate(). This object can be used to modify the contents of the BufferedBitmap by drawing shapes, text, and bitmaps to it.

module Graphics
{
    //! This class represents a offscreen bitmap. It provides methods to
    //! modify the bitmap palette, and get a drawable context.
    //! @since 2.3.0
    class BufferedBitmap {
        //! Get the DC to draw on the buffered bitmap
        //! @return [Graphics.Dc] Draw context
        //! @since 2.3.0
        function getDc();
    }
}

Input Handling

As if input handling wasn’t already one of the most important and complicated pieces of a UI toolkit, Garmin devices take the complication up a level. Unlike those touchable glowing rectangles that are modern smart phones, Garmin devices come in lots of shapes and sizes. Touch screens are not always ideal for all watch products, so there is a mix of input styles and screen technologies. It’s the job of the UI toolkit to make this coherent to the developer.

Input and App Types

Not all app types have access to input. Watch faces and data fields cannot handle input, though widgets can receive input (may be limited on some devices) and watch-apps will have the most input capability.

Input Delegates

The Delegate object is an object that implements a certain interface specific to input handling. Monkey C provides a low level InputDelegate that allows handling of events at a basic level. This is good when the app needs to handle button presses or screen presses in a particular way.

module WatchUi
{
    // This class implements basic input handling. A developer needs to override
    // the events they want to handle.
    class InputDelegate
    {
        // Key event
        // @param evt KEY_XXX enum value
        // @return true if handled, false otherwise
        function onKey( evt );

        // Click event
        // @param evt Event object with click information
        // @return true if handled, false otherwise
        function onTap( evt );

        // Screen hold event. Sent if user is touching
        // the screen
        // @param evt Event object with hold information
        // @return true if handled, false otherwise
        function onHold( evt );

        // Screen release. Sent after an onHold
        // @param evt Event object with hold information
        // @return true if handled, false otherwise
        function onRelease( evt );

        // Screen swipe event
        // @param evt Event
        function onSwipe( evt );
    }
}

Behaviors

Garmin makes products with a purpose, and that purpose can alter the design of one product line over another. Deciding whether a product has a touch screen or has buttons can depend on the environment in which the user will take. For instance, if the product is intended to be used in water (swimming, canoeing, on a boat), it may not have a touch screen. These decisions make for superior products, but also add to developer frustration due to device fragmentation.

Most products will support common behaviors (next page, back a page), but how they are executed by the user may differ based on the available input types. To help with this dilemma, Monkey C exposes events at a behavior level. Behaviors separate high-level intentions from the actual input type—next page versus screen press. The BehaviorDelegate is a super class of InputDelegate and maps its low level inputs to common operations across multiple products. Using the BehaviorDelegate can lead to much more portable code.

// BehaviorDelegate handles behavior inputs. A BehaviorDelegate differs from
// an InputDelegate in that it acts upon device independent behaviours such as
// next page and previous page. On touch screen devices these behaviors might be
// mapped to the basic swipe left and right inputs, while on non-touch screen
// devices these behaviors might be mapped to actual keys.
// BehaviorDelegate extends InputDelegate so it can also act on basic inputs as
// well. If a BehaviorDelegate does return true for a function, indicating that
// the input was used, then the InputDelegate function that corresponds to the
// behavior will be called.
// @see InputDelegate
class BehaviorDelegate extends InputDelegate
{
    // When a next page behavior occurs, onNextPage() is called.
    // @return [Boolean] true if handled, false otherwise
    function onNextPage();

    // When a previous page behavior occurs, onPreviousPage() is called.
    // @return [Boolean] true if handled, false otherwise
    function onPreviousPage();

    // When a menu behavior occurs, onMenu() is called.
    // @return [Boolean] true if handled, false otherwise
    function onMenu();

    // When a back behavior occurs, onBack() is called.
    // @return [Boolean] true if handled, false otherwise
    function onBack();

    // When a next mode behavior occurs, onNextMode() is called.
    // @return [Boolean] true if handled, false otherwise
    function onNextMode();

    // When a previous mode behavior occurs, onPreviousMode() is called.
    // @return [Boolean] true if handled, false otherwise
    function onPreviousMode();
}

Built In Handlers

In general, complicated input from users and feedback to users should not be done on a wearable device, but should instead be done via a phone app or web page. However, sometimes the device needs to get some form of user input or provide confirmation to the user.

WatchUi provides three widgets to handle input: menus, the generic picker, and the number picker (the number picker has been deprecated in favor of the generic picker).

Two additional handlers provided by WatchUi can be used to give feedback to the user: the confirmation dialog and progress dialog.

A menu is a list of options for the user. The options are displayed in a list that matches the device the app is running on. A menu can be defined in the resource XML file using the following format:

<menu id="MainMenu">
    <menu-item id="item_1" label="@Strings.menu_item_1_label" />
    <menu-item id="item_1" label="@Strings.menu_item_2_label" />
</menu>

The resource compiler will then take this XML and generate a Menu object in the Rez module. In order to use this menu, a developer simply needs to push the menu and a delegate for the menu using Ui.pushView():

class MyClass extends Ui.View {
    function openTheMenu() {
        Ui.pushView( new Rez.Menus.MainMenu(), new MyMenuDelegate(), Ui.SLIDE_UP );
    }
}

class MyMenuDelegate extends Ui.MenuInputDelegate {
    function onMenuItem(item) {
        if ( item == :item_1 ) {
            // Do something here
        } else if ( item == :item_2 ) {
            // Do something else here
        }
    }
}

Generic Picker

The Picker class, along with the PickerDelegate and PickerFactory classes, provides applications with the ability to create onscreen lists of user-selectable objects. A picker consists of one or more choosable objects, a title, a next and previous arrow, and a confirm button. The next and previous arrows and the confirm button are device specific but can be overwritten if desired. Pickers are pushed using WatchUi.pushView(), providing a PickerDelegate for the input delegate. A PickerFactory is required to indicate what should be displayed for each pickable value.

Sample picker implementations can be found in the Picker sample project, inside the samples/Picker directory.

User Interface

The above image is a representation of the general structure of what a picker should look like on a square screen. Other screen formats should have the same layout with some size differences to account for the screen and button layout.

Development Interface

// A Picker uses a PickerFactory to decide what to display to the user. It is
// capable of displaying any number of entries for the user to choose from.
// For instance, if the PickerFactory was [new NumberFactory(), new Ui.Text({:text=>"-"}), new NumberFactory()]
// then the Picker will have three entries: a choosable number, a non-choosable "-", and another choosable number.
class Picker extends Toybox.WatchUi.View
{
    // Constructor
    // @param [Dictionary] options @see Picker#setOptions
    function initialize( options );

    // Set the options for the Picker.
    // @param [Dictionary] options the options for the Picker
    // @option options [Drawable] :title the title for the Picker. Required.
    // @option options [Array] :pattern an Array of [Object] for the Picker to display. If the Array entry is a PickerFactory then it is presented to the user to make a choice. If it is a Drawable then it is display only. Required.
    // @option options [Array] :defaults an Array of [Number] indicating the starting index for each entry in :pattern. Optional.
    // @option options [Drawable] :nextArrow a custom next icon for the Picker. Optional.
    // @option options [Drawable] :previousArrow a custom previous icon for the Picker. Optional.
    // @option options [Drawable] :confirm a custom confirm icon for the Picker. Optional.
    function setOptions( options );
}

// PickerDelegate responds to a confirm or cancel. The
// Picker does not automatically call popView.
// event from a [Picker].
class PickerDelegate
{
    // Handle a confirm event from a [Picker]
    // @param [Array] values The chosen values from the Picker. For [Drawable] entries, null will be returned for that value.
    function onAccept( values );

    // Handle a cancel event from a [Picker]
    function onCancel();
}

// A PickerFactory is used by a Picker to display Drawables to
// be chosen.
class PickerFactory
{
    // Generate a Drawable instance for a given item
    // @param [Number] item Item index
    // @param [Boolean] isSelected true if the current item is the selected item, false otherwise
    // @return [Drawable] object to be rendered
    function getDrawable( item, isSelected );

    // Return a representative value for this item
    // @param [Number] item Item index
    // @return [Object] Representative item for the given index
    function getValue( item );

    // Get the number of items in the factory
    // @return [Number] number of items in the factory
    function getSize();
}

Number Picker - DEPRECATED

To allow the user to pick or adjust numerical data, the number picker widget is your best bet. The number picker widget allows for editing or adjusting of common numerical types. Here’s an example of number picker use:

enum
{
    NUMBER_PICKER_DISTANCE,     // A Float in meters
    NUMBER_PICKER_TIME,         // A Duration
    NUMBER_PICKER_TIME_MIN_SEC, // A Duration
    NUMBER_PICKER_TIME_OF_DAY,  // A Duration representing the number of seconds since midnight
    NUMBER_PICKER_WEIGHT,       // A Float in kilograms
    NUMBER_PICKER_HEIGHT,       // A Float in meters
    NUMBER_PICKER_CALORIES,     // A Number
    NUMBER_PICKER_BIRTH_YEAR    // A Number
}

// This is the on-screen representation of number picker. The look-and-feel
// will be device specific. NumberPickers are pushed using WatchUi.pushView(),
// providing a NumberPickerDelegate for the input delegate. There are minimum
// and maximum values enforced by the product for each mode. The initial value
// will be modified to be within these bounds.
class NumberPicker
{
    // Constructor
    // @param mode An enum value of type NUMBER_PICKER_*
    // @param initialValue The initial value for the Number Picker, type
    //       depends on mode
    function initialize( mode, initialValue );
}

// NumberPickerDelegate responds to a number being chosen. This class should be
// extended to get the chosen number.
class NumberPickerDelegate
{
    // When a number is chosen, onNumberPicked() is called, passing the
    // chosen value.
    // @param value The chosen number, type depends on the NumberPicker mode
    function onNumberPicked( value );
}

Confirmation Dialog

The confirmation dialog provides a simple yes/no dialog. This is useful when presenting a simple selection.

enum
{
    CONFIRM_NO,
    CONFIRM_YES
}

// This is the on-screen representation of the confirmation dialog.
// The look-and-feel will be device specific. Confirmations
// are pushed using WatchUi.pushView(), providing a ConfirmationDelegate
// for the input delegate.
class Confirmation
{
    // Constructor
    // @param [String] message A textual confirmation message
    function initialize( message );
}

// ConfirmationDelegate handles a response being chosen in the Confirmation.
class ConfirmationDelegate
{
    // When a response is chosen, onResponse() is called, passing
    // the response of CONFIRM_NO or CONFIRM_YES
    // @param response The response of the confirmation of CONFIRM_NO or CONFIRM_YES
    function onResponse( response );
}

Progress Bar

The progress dialog gives a standard wait dialog. It has two modes—one that shows the completion of some process, and a second that acts a wait timer displaying an indeterminate amount of progress. The look and feel of the progress bar will be device-specific.

// This is the on-screen representation of a progress bar.
// The look-and-feel will be device specific. ProgressBars are pushed
// using WatchUi.pushView(), providing either null or an input delegate.
// This can display a string and either a progress bar from 0-100%
// or a busy bar.
class ProgressBar
{
    // Constructor
    // @param [String] displayString The string to display on the progress bar view
    // @param [Float] startValue The initial value for the progress bar (0-100), null for "busy"
    function initialize( displayString, startValue );

    // Use setProgress() to set the value of the progress bar.
    // @param [Float] progressValue The value for the progress bar (0-100), null for "busy"
    function setProgress( progressValue );

    // Use setDisplayString() to set the string displayed on the progress bar view.
    // @param [String] displayString The string to display on the progress bar view
    function setDisplayString( displayString );
}

Data Field

On Devices with touch screen support, an input delegate can be used to accept input. Only the onTap() behavior is supported and will be triggered when the user touches a point inside the data field when it is active on the screen. The behavior delegate should be the second element of the array that is returned from getInitialView as with other app types.

// This data field accepts touch input
class DataFieldApp extends App.AppBase {
    // Data field view with associated behavior delegate
    function getInitialView() {
        return [ new DataFieldView(), new DataFieldDelegate() ];
    }
}

class DataFieldDelegate extends Ui.InputDelegate {
    // Handle touch events
    function onTap(evt) {
        // Process the touch event
    }

Selectables and Buttons (CIQ 2.1)

Selectables

Monkey C provides an interface for products with large touch screens (and few buttons) to easily define touchable objects on-screen known as a WatchUi.Selectable:

//! Selectable is the class representation of an on-screen
//! selectable object with defined states depending on selection mode
//! @see Drawable
//! @since 2.1.0
class Selectable extends Toybox.WatchUi.Drawable
{
    //! Default state Drawable, Graphics.COLOR_* constant, or 24-bit integer of the form 0xRRGGBB
    //! @return [Object]
    //! @since 2.1.0
    (:property) var stateDefault;

    //! Highlighted state Drawable, Graphics.COLOR_* constant, or 24-bit integer of the form 0xRRGGBB
    //! @return [Object]
    //! @since 2.1.0
    (:property) var stateHighlighted;

    //! Selected state Drawable, Graphics.COLOR_* constant, or 24-bit integer of the form 0xRRGGBB
    //! @return [Object]
    //! @since 2.1.0
    (:property) var stateSelected;

    //! Disabled state Drawable, Graphics.COLOR_* constant, or 24-bit integer of the form 0xRRGGBB
    //! @return [Object]
    //! @since 2.1.0
    (:property) var stateDisabled;

    //! Constructor
    //! @param [Dictionary] options Parameters to specify in Selectable creation
    //! @option options [Number] :locX @see Drawable#initialize. Required.
    //! @option options [Number] :locY @see Drawable#initialize. Required.
    //! @option options [Number] :width @see Drawable#initialize. Required.
    //! @option options [Number] :height @see Drawable#initialize. Required.
    //! @option options [Object] :stateDefault Drawable to display in default state. Optional.
    //! @option options [Object] :stateHighlighted Drawable to display in disable state. Optional.
    //! @option options [Object] :stateSelected Drawable to display in highlight state. Optional.
    //! @option options [Object] :stateDisabled Drawable to display in selected state. Optional.
    //! @since 2.1.0
    function initialize(options);

    //! Get the current state.
    //! @return [Symbol] The current state (:stateDefault, :stateHighlighted, etc).
    //! @since 2.1.0
    function getState();

    //! Set the current state.
    //! @param [Symbol] state The desired state (:stateDefault, :stateHighlighted, etc) to make current.
    //! @since 2.1.0
    function setState(state);

    //! Draw the object to the display context with draw(). Assume that the
    //! device context has already been configured to the proper settings.
    //! @param [Graphics.Dc] dc The device context
    //! @since 2.1.0
    function draw(dc);
}

//! SelectableEvent is an object sent to InputDelegate when the user
//! manipulates a Selectable using hard-keys or the touchscreen.
//! @since 2.1.0
class SelectableEvent
{
    //! Constructor
    //! @param [Selectable] instance The Selectable that generated the event
    //! @param [Symbol] previousState The previous Selectable state (stateDefault, stateHighlighted, etc)
    //! @since 2.1.0
    function initialize(instance, previousState);

    //! Get the previous state of the Selectable that generated the event.
    //! @return [Symbol] The previous Selectable state (stateDefault, stateHighlighted, etc)
    //! @since 2.1.0
    function getPreviousState();

    //! Get the instance of the manipulated Selectable.
    //! @return The Reference to the Selectable manipulated
    //! @since 2.1.0
    function getInstance();
}

//! InputDelegate handles basic input events. There are three types of basic
//! inputs: key, touch, and swipe.
//! @since 1.0.0
class InputDelegate
{
    //! When the state of a Selectable changes, onSelectable() is called. The instance of the
    //! associated Selectable is passed so that its current state may be determined.
    //! @param [Selectable] instance The Selectable that generated the event
    //! @param [Symbol] previousState The previous Selectable state (stateDefault, stateHighlighted, etc)
    //! @return [Boolean] true if handled, false otherwise
    //! @since 2.1.0
    function onSelectable(instance, previousState);
}

A Selectable is a state driven object that supports four optional built-in states which are set based on touch interaction:

Once a state change occurs, a SelectableEvent is sent which results in a call to WatchUi.InputDelegate.onSelectable(), which passes both the instance of the current Selectable and the symbol of the previous state for comparison, and allows for custom actions as a result of the state change. Selectables must be registered as part of the View’s layout via the WatchUi.View.setLayout() call in order to for the View to know about them and direct touch events to the object. Stacked instances of Selectables will have input directed to them depending on their order as passed to setLayout() (last in, first drawn / selected).

A new method for the View class is defined to enable compatiblity mode with button-only products:

//! Set the state of the keys to Selectable interaction mode. In this mode the
//! keys are used to cycle through the on screen Selectable objects. The first
//! registered Selectable in the current layout will be highlighted.
//! @param [Boolean] enabled If the mode should be enabled
//! @since 2.1.0
function setKeyToSelectableInteraction(enabled);

The above routine may be called to enable the up/down keys to cycle through the list of Selectables registered via the View’s setLayout() call, which sets the highlighted state on a Selectable until it is put in the Selected state via the enter key.

Each of the four states must be mapped to a Drawable, Graphics.COLOR_* constant, or 24-bit integer of the form 0xRRGGBB. States are optionally defined and be specified in the Selectable constructor, or manually modified as members of the instance. Each Selectable will draw the current state using the defined locX and locY coordinates as an offset if it has been defined. Extending Selectable with additional states is encouraged and the getState() and setState() routines may used to alter the default state machine (see Selectable sample application for a demonstration of check boxes).

Buttons

A WatchUi.Button is derived from Selectable and adds the ability to define a background and map interaction to an existing or new custom method (i.e. onMenu()) within WatchUi.BehaviorDelegate:

//! Button is the class representation of a Selectable that is mappable
//! is to a BehaviorDelegate method on selection.
//! @see Selectable
//! @since 2.1.0
class Button extends Toybox.WatchUi.Selectable {

    //! Background Drawable, Graphics.COLOR_* constant, or 24-bit integer of the form 0xRRGGBB to be
    //! drawn before the current Selectable state is drawn.
    //! @return [Object]
    //! @since 2.1.0
    (:property) var background;

    //! Behavior method executed when button is selected. This method must be a member of the active
    //! View's registered BehaviorDelegate, such as :onBack, but may also be a method of an extended
    //! class. If the method returns false, or is not specified (null), then a SelectableEvent will be
    //! issued.
    //! @return [Method]
    //! @since 2.1.0
    (:property) var behavior;

    //! Constructor
    //! Initialize the Button foreground, background, and behavior. The Button must be registered via a
    //! setLayout() call in order to be usable. Accepts the standard Selectable symbols and
    //! also includes its requirements.
    //! @param options [Dictionary] Parameters to specify in Button creation
    //! @option options [Method] :behavior Method object to call when selected, or null to use a SelectableEvent. Optional.
    //! @option options [Object] :background Drawable, Graphics.COLOR_* constant, or 24-bit integer of the form 0xRRGGBB. Optional.
    function initialize(options);

    //! Draw the object to the display context with draw(). Assume that the
    //! device context has already been configured to the proper settings.
    //! @param [Graphics.Dc] dc The device context
    //! @since 2.1.0
    function draw(dc);
}

Defining Selectables And Buttons In Layouts

Much like their parent Drawable, Selectables and Buttons both support the layout system. Selectable and Button XML resources consist of a list of state ID’s and optional parameters. To create an XML Selectable, define a <selectable> in an XML resource file:

Attribute Definition Valid Values Default Value Notes
x The X coordinate of the top left corner of the Selectable region Pixel value, center, left, or right N/A Required
y The Y coordinate of the top left corner of the Selectable region Pixel value, center, top, or bottom N/A Required
width The width of the Selectable region pixel value N/A Required
height The height of the Selectable region pixel value N/A Required

The <button> resource expands the definition of <selectable> and adds the following:

Attribute Definition Valid Values Default Value Notes
behavior A method existing within a View’s registered BehaviorDelegate Method symbol null Optional / Param
background The background color of the Button Graphics color constant or a 24-bit integer of the form 0xRRGGBB Gfx.COLOR_TRANSPARENT Optional / Param

Both <selectable> and <button> tags use <state> as child nodes to define their states at construction. Each state is defined optionally but is defined as the following:

Attribute Definition Valid Values Default Value Notes
id The state ID for the Button / Selectable stateDefault, stateHighlighted, stateSelected, or stateDisabled N/A Required
bitmap A bitmap resource in the layout drawn at the Selectable / Button origin WatchUi.Bitmap objects N/A Required or color must be specified
color A color fill applied to the Selectable / Button region Graphics color constant or a 24-bit integer of the form 0xRRGGBB Gfx.COLOR_TRANSPARENT Required or bitmap must be specified

An example layout containing two a menu and back button:

<layout id="ButtonLayout">
    <button x="40" y="center" width="50" height="50" background="Gfx.COLOR_BLACK" behavior="onBack">
        <state id="stateDefault" bitmap="@Drawables.DefaultBackButton" />
        <state id="stateHighlighted" bitmap="@Drawables.PressedBackButton" />
        <state id="stateSelected" bitmap="@Drawables.PressedBackButton" />
        <state id="stateDisabled" color="Gfx.COLOR_BLACK" />
    </button>
    <button x="115" y="center" width="50" height="50">
        <state id="stateDefault" bitmap="@Drawables.DefaultMenuButton" />
        <state id="stateHighlighted" bitmap="@Drawables.PressedMenuButton" />
        <state id="stateSelected" bitmap="@Drawables.PressedMenuButton" />
        <state id="stateDisabled" color="Gfx.COLOR_BLACK" />
        <param name="background">Gfx.COLOR_BLACK</param>
        <param name="behavior">onMenu</param>
    </button>
</layout>