UI Framework for microcontrollers

The framework consists of a bases module which provides the infrastructure for individual out of box and custom ui elements and utility extensions. Out of box ui elements include (for now):

Out of box extensions provide utility and convenience functions and is required for more complex ui elements - for now - for - out of box - radio button, input element and keyboard component. The extensions are also useful for the application to easy access and 'safely' handle ui elements.

Creating and integrating custom ui elements and extensions is easy based on the chosen architecture, available documentation and ready to run examples. Examples can be run on actual Espruino and display with and without touch screen. Most of the examples run also emulated in cross development environment in any browser as html / JavaScript - that's how and where the ui framework and ui elements are developed and maintained for most of their parts. The examples share / reuse the Espruino JavaScript project and module files from the Espruino IDE sandbox folder as they are: unchanged.

All ui elements handled by ui framework have roughly 12 (13) common properties in same and similar structure(s):

Only the 1st 9 of the 12 (13) properties are really mandatory. Same values in same structures have lead to an implementation of the ui elements as light weight, lean 'array objects' in order to save memory / variables.

Details about the ui element specific properties for construction and runtime are documented for each ui element separately.

The ui communicates input (and other) events through callbacks. The callback arguments include all pertinent information to allow a straight forward, from simple to sophisticated use in the application and keep the application code terse in order to save memory / variables:

ui framework - base module.

The ui base module takes care of most of the plumbing, logic and data flow between touch screen (or physical buttons) and display as input and output components and controling application.

Implementation approach

ui is currently implemented as singleton, literally constructed when loaded (required). At runtime it does not only hold on to its own code and data but also the mixed in code for the various ui element 'types' as needed (as loaded on demand by require() ) and the data of the created ui elements. The ui elements are implemented a light weight array objects: just data and - where needed - some format function(s).

Memory / Variable usage

Below some (ballpark) figures on various usage, categorized as U for unminified vs. M for minified and I for individual vs C for cumulated.

// vars (Idividual|Cumulated)  IU /  IM -   CU -   CM  B (Un|Minified)
var ui = require("ui")     // 392 / 314 -  412 -  334 20       (-Base)
    .adx(require("uiExt")) // 237 / 214 -  670 -  547
    .adx(require("uiRad")) // 190 / 168 -  860 -  725
    .adx(require("uiSli")) // 188 / 166 - 1048 -  891
var ui = require("ui")     // 392 / 314 -  412 -  334 20
    .adx(require("uiExt")) // 237 / 214 -  649 -  548
    .adx(require("uiRad")) // 190 / 168 -  839 -  813
var ui = require("ui")     // 392 / 314 -  412 -  334 20
    .adx(require("uiSli")) // 272 / 227 -  600 -  500

Minification setting in Espruino Web IDE: Esprima (offline) for both code in Editor window and from modules.

* Basic Application of ui base and - as example - use of uiBtn (Button) element

Get ui base and ui elements and build ui - like a DOM - by ui definitions in level 0. The build is executed on upload to Espruino. save() will save the built ui. The onInit() function will start the already built ui on power up, after save() or - while under development - manually by invocation in console or automatically in a setTimeout() (see below).

Require ui components

Get ui base and ui elements to the extent as needed into the system, such as uiBtn, uiSli(der), etc.

var ui = require("ui")     // getting ui base code (w/ 8 default colors)
    .adx(require("uiBtn")) // add / mixin btn ui elt support into ui

Define UI and add elements

Definition of UI(s) - creation of 'DOM'(s) - best happen (with Espruino) on code upload and is/are as such then saved on save(), but not displayed (rendered) yet. Creating it dynamically is possible as well. Example UI below defines ui with two (2) buttons -b1 and b2 - which on release (untouch, tap) will call the example callback cb. Button ui element definition includes just data and requires out of box no format functions. For more details see uiBtn module (unminified code) and related documentation.

//    0  1     2      3   4   5   6   7   8  9      10  11
// flas  clazz id     x   y   w   h  bc  fc  valObj cb,  l (label array obj)
//       btn               ->x2->y2                       fs tc    x  y  text
ui.c( 3,"btn","b1" ,  5, 40, 65, 35,  4,  4, "B_1", cb,  [15, 7,  13, 9,"RED"   ]);
ui.c( 3,"btn","b2" , 70, 40, 65, 35,  5,  6, {v:1}, cb,  [15, 0,   9, 9,"BonYbg"]);
// bc/fc/tc: border/fill/text colors; cb=callback(id, v, ui, e, t): id: button id,
// such as "b1"; v, ui, e, t provide btn value, ui, elt. touch event runtime data;
// label font(vector) s(ize)>0, 0 built-in bitmapfont, <0 loaded fonts, x,y offset
// colors: 3-bit depth coded: 0b### ###=rgb (0=black, 7=white, 4=red,...)

Application code

The application acts on ui events that are communicated thru callbacks. This sample callback cb - for simplicity reason - just logs the buttons' id and value (object) in the console.

function cb(id,v,_,e,t) { console.log(id+": "+v); } // sample callback


Device stuff happens on upload - once - and defines the display and the touch instance and module variables and sets latter accordingly.

The ILI9341 display controller used in the examples is a 240 x 320 pixel, max 16-bit color, TFT display controller. Other controller can be used as well with adjusted width and height values and color definitions.

If the touch screen has a controller, use its module - like XPT2046 for resistive touch screen (XPT2046 works also for the ADS7843 controller); otherwise use the module that can handle a resistive touch screen directly (TouchRD - from: Touchscreen, Resistive, where membrane edge traces are Directly connected to Espruino pins and controlled and sensed by Espruino).

// instance and module variables:
var dsp,  dspMod = require("ILI9341");   // display

var touch,touchMod = require("XPT2046"); // touch screen controller
  // or:
var touch,touchMod = require("TouchRD"); // touch screen w/o controller

Define the onInit() function

Function onInit() {...} gets everything initialized, connected and started. The code below shows touch screen controller module for touch screen connection (XPT2046 can also be used for equally specified ADS7843). Pins used are related to PICO.

function onInit() { // on power on/save() setting up all from scratch
  // setup and connect display, then ui and input (touch | phys btns)
  SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 1000000}); // display
  dsp = dspMod.connect(SPI2, B10, B1,  A4, function() { // using...
                  // ...spi,  dc, cs, rst, callback
    dsp.clear(); A1.set();  // display clear and turn back light on
    ui.connect(dsp)         // connect ui to dsp and display it
      .w(0,0,dspW-1,dspH-1) // wipe screen (rect) w/ default / bg color
      .d()                  // display all elements (w/ no extras)
      .di = true;           // set display changes to immediate
    SPI1.setup({sck:A5, miso:A6, mosi:A7, baud: 1000000}); // touch inp
    touch = touchMod.connect(SPI1, A3,  A2, function(x,y){ ui.evt(x,y);}
                        // ...spi, cs, irq, callback, /. calibrated...
        , function(yr, xr, d) { // ...function, maps touch to x/y of dsp
            return [ Math.round(xr / -121.44          + 259.70685111989)
                   , Math.round(yr /   88.90357142857 + -19.78130398103)
    } ).listen(); // for XPT2046 module (ADS7843: not needed/supported)
  } );
} // /onInit()

Start the code after upload

After uploading the code, enter onInit() in the Espruino IDE console to get the code started. For convenience you can add the following line as last line in the code, which gets the code automatically going after upload has successfully completed. Note though to remove or comment this line on the last upload before saving the code with save():

setTimeout(onInit,999); // for dev; remove before upload for save()

Description of (graphical) ui base module and touch controller interface

Properties - state and behavior / methods - of ui base module

ui provides also convenience functions/methods to be used in application to deal with ui elements as well as plain graphics. Due to ui's extensibility, you can add easily your own ui element or plain function extensions as desired (with .add(module);).

-- States of ui base module / ui singleton (also referred to as _ for this)

exports = // ui base / 'DOM'/ ui e(lement) data & code holder, singleton, for mixins)
{ dsp: null // display (Espruino Graphics object)
, mn: "ui"  // reserved, used temporary
, bc: 0     // dsp background color >=0: r*g*b* bit coded; <0: looked up [...,[R,G,B],...]
, tc: 7     // touch / focus color; >=0: r*g*b* bit coded; <0: looked up [...,[R,G,B],...]
, di: false // display instantly on create/change (not defer until after save())
, es: []    // ui elements
, ef: null  // (primary ui) element (in visual) focus (by touch down / hovering)
, lf: null  // (primary ui element) last (in visual) focus (by touch down / not hovering)
, af: null  // alternate (ui element in non-visual) focus (by drag over / hovering)
, it: false // (display) is touched
, td: false // is touch down (event)
, tt: 0     // touch down time
, lx: 0     // last x (touched)
, ly: 0     // last y (touched)
, dx: -1    // (last) x (touch) down
, dy: -1    // (last) y (touch) down
, te: {f:0} // (last) touch event
, clrs:     // default - bit coded color of 3-bit color-depth - 2^1^3(rgb) = 8 colors
    [function(c,i){ var v=(i)?c^7:c; return [v>>2&1,v>>1&1,v&1]; }.bind(exports)
, fnts: [0] // setFontMethodNames for font modules added w/ .adx(), accessed w/ fs<0)
// ....
// ... public methods - documented above
// ... private methods
// ...

Methods of ui base module / ui singleton (also referred to as _ for this)

Modules.addCached(mName,'{ mn:"onOff",on: function(on) {'
          + digitalWrite(B2, on || on===undefined); }');

and added with ui.add("onOff");.

UI (touch) event object (passed in callbacks)

The touch event object t and other objects, such as the ui / _ and the ui element e themselves, are passed around for easy access in the ui base and ui element implementation as well as in the application. The touch event object has this structure:

t = // (touch) event
   { x: x // current or last touched x coordinate
   , y: y // current or last touched y coordinate
   , t: t // touching: (truey), not touching (anymore): falsey
   , f: f // touch event flags (experimental)

Touch event flags t.f - experimental - are:

0000 0000 0000    0 unexpected (invalid?)
0000 0000 0001    1 untouch
0000 0000 0010    2 touching
0000 0000 0100    4 touch down
0000 0000 1000    8 'untouch' focused element while not in focus - _.lf
0000 0001 0000   16 untouched focused elt while in focus (typical untouch)
0000 0010 0000   32 moved out of focused element (touched down element)
0000 0100 0000   64 touching in focused elt (touched down elt) - _.ef / _.lf
0000 1000 0000  128 touching / focused 1st time or re-focused - _.ef / _.lf
0001 0000 0000  256 untouch on dragged over non-touched-down, alt elt - _.af
0010 0000 0000  512 moved out of non-touched-down, alternate focused elt
0100 0000 0000 1024 touching in non-touched-down, alternate focused element
1000 0000 0000 2048 touching 1st time in non-touched-down, alt. focused elt

Re-focusing happens when having touched down, dragged out of the element and now draging back 'into' the initially focused element (touched down element (_.ef / _.lf).

In preferred callback, a typical untouch - touch down on a ui element and untouch on same ui element - set these flags: conditions:

0000 0001 0001   7 = 16 untouched focused element while in focus (simple,
                    +  1 untouch event

Untouching after moving / dragging out of the bounding box of the touched down ui element sets these flags:

0000 0001 1001   25 = 16 untouched focused element while in focus (simple,
                         regular untouch event)
                    +  8 'untouch' focused element while not in focus
                         (_.lf) - last registered focused element, now not
                         in focus anymore (_.fe), but may need cleanup
                         or reset when element has been modified on other
                         events using alternative callback
                    +  1 untouch event

In the preferred callback fired on ontouch only - touch started with touch down and ended with untouch on same ui element - requires usually no or only rarely interpretation of touch event flags.

The alternative callback fires on every kind of touch event and demands significant interpretation of the flags from the application and ui status items - passed as well in the callback - in order to take appropriate action(s) in the application. The multitude of flags allows to determine - next to untouch - touch down, dragging within, leaving and re-entering bounding box of touched down ui element as well as same events of the alternate - not touched down - element (and can be used to implement drag and drop).

Colors and Color Definitions

ui base module includes a default color converter that supports 3-bit color depth - 1 bit each per red, green and blue base color - coded as single RGB integer value, yielding 8 colors: 2^1^3 = 8 colors (3 LSB - Least Significant Bits of an integer). The color converter is a function and is first element - index [0] - of an array which is set and stored as ui.clrs[] array property:

ui.clrs = [ function(c,i){ var v=(i)?c^7:c;
                           return [v>>2&1,v>>1&1,v&1]; }.bind(exports) ]

The default color converter accepts for c a value from 0..7 for the basic 8 colors and returns them as triplets of 0s and 1s (array of three (3) elements of values 0 and 1). It accepts an optional second parameter to invert the color when truey. The inverted, complementary color contrasts the non-inverted color and is defined by the color specified by the 1-complement of color c. For example, for a the 3-bit color depth defined color with value 4, [1r,0g,0b] or red, the inverted, contrasting complementary color value is 3, [0r,1g,1b] or aqua.

The color converter is is invoked by ui's color setting method ui.clr(c,i) with same arguments. Any color converter delivers - has to deliver - the normalized values 0..1 for each of the RGB colors as [r, g, b] triplet, because hat values triplet is parameter for setting the color by invoking the set-color method .setColor(r, g, b) of the Espruino Graphics object. The display module knows how to pass the color onto the display's controller to make the display show the proper color.

Using the enhanced color converter as below, colors can be specified in three ways:

The n custom colors of the custom color palette are stored in the same array as the color converter as 2nd to n-th + 1 element - index 1..n. The color converter has to be built to accept 'negative' color values -1 .. -n to pick a color from the custom palette: it takes the negative value as positive index into the ui.clrs[] array property tp pick a color from the custom color palette and return it as value-normalized [0..1r,0..1g,0..1b] triplet. The way the custom colors of the custom color palette are specified determines the implementation of the converter.

ui.clrs = // 'imply' color depth and populate table w/ converter and define
          // user colors / palette (colors accessed w/ negative index)
  [function(c,i) { // converts 'custom color info / spec' to [r,g,b]...
       var v, s=isNaN(c); // ...@ idx 0; internally called: _.clrs[0](...)
       if (s || c<0) { // c<0 (looked up [R,G,B]) or c=[R,G,B]
         return ( ((s) ? c : this.clrs[-c]).map( // convert 0..255->0.0..1.0
                               function(v){ return ((i)?v^255:v)/256; } ) );
       } else {        // c>=0 bit coded rgb color (0b001,010,011,100,...111
         v = (i) ? c^7 : c;  // (default 3-bit color depth w/ 2^1^3=8 colors)
         return [v>>2&1,v>>1&1,v&1]; }
     }.bind(ui)  // custom color palette (converter knows to convert spec)
  ,[216,216,216] // user color -1: light grey // keyboard special keys
  ,[192,192,192] // user color -2: medium light grey
  ,[128,128,128] // user color -3: medium gray
  ,[192,128,192] // user color -4: light purple
  ,[0  ,160, 64] // user color -5: kind of a 'darker' green
ui.bc = 0; // ui (screen) background color (override default w/ other value)
ui.tc = 7; // ui (screen) touch / focus color (override default w/ oth. val)

For [r,g,b] specification, Espruino Graphics modules accepts for each r,g,b base color the normalized value in the range of 0.0 .. 1.0. Under the hood and with help of the module for the display, this normalized color specification is transformed - most of the time - into a bit coded color again, but in a different, more elaborate / complicated encoding, especially when different number of bits are used for each of the base r, g and b colors, for example, (display specific) 5, 6, 5 bits for r, g, b. The module also handles the conversion the sequence of the r, g and b colors when needed, for example, [r,g,b] to [r,b,g].

Normalized color specification value is mapped to actual color based on color depth specified for or implied by the display (module) used in conjunction with the Espruino's Graphics object. The ui base module already includes a default color converter from bit coded rgb value to Espruino's normalized [r,g,b] values triplet. Passing the normalized values triplets makes bit coded colors in the ui independent from the display's version of bit coding.

The n custom colors of the custom color palette are stored in the same array as the color converter as 2nd to n-th + 1 element - index 1..n. The color converter has to be built to accept 'negative' color values in range of -1 .. -n to pick a color from the custom palette: it takes the negative value as positive index into the ui.clrs[] array to properly pick a color from the custom color palette and return it converted to value-normalized [0.0..1.0r, 0.0..1.0g, 0.0..1.0b] triplet. The way the custom colors of the custom color palette are specified determines the implementation of the converter.

Fonts, font definitions and font extensions

Espruino Graphics class has two built-in fonts: vector font and a 4x6 bit map font. Both are available to ui framework by default. Application can load any additional font module and access it indexed, similar to custom colors.

Loading custom font modules (module type: 2, -2 to delete by name (for more see ui.adx(mon,t,n) add any module w/ cache delete function):

ui.adx(require("Font6x8"     .-2,"Font6x8"     )) // ...and del from cache
  .adx(require("FontSinclair",-2,"FontSinclair")) // ...and del from cache

Using the fonts:

//     fs (also used in label spec array as 1at element = fs: font spec)
ui.fnt(15); // sets display's font to built-in vector font with size 15
ui.fnt( 0); // sets display's font to built-in 4x6 bitmap font
ui.fnt(-1); // sets display's font to custom loaded 6x8 bitmap font
ui.fnt(-2); // sets display's font to custom loaded Sinclair bitmap font

When loading fonts, they are added to the Espruino Graphics class (and removed when type is specified as -2 - font module - and name n), and the corresponding .dsp.setFontXyz() method name as string is added to the .fnts[] array (indices 1,2,3,... specified and accessed with negative index value for fonts spec (fs | s) in labels and ui.

Custom ui elements and extensions

In order to not collide with future development of the ui components out of the box and for easy distinction, choose for custom ui elements clazz names of 5 characters or more, use for mixed in properties - variables and methods - names with 6 or more characters. Also, add a custom parallel array object or real js object rather than adding elements to the existing array object.


The unminified sources and examples have in-line documentation - same as this - and in-line comments. Latter is useful when reading the code to grok 'what is going on'. Feedback - in general and in particular - and (improving) contributions are welcome - @allObjects.


This page is auto-generated from GitHub. If you see any mistakes or have suggestions, please let us know.