• ----- ui_ ------------------------- 20190919 - intro

    *** 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

    The ui base module 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")     // 314 / 282 -  361 -  302 20       (-Base)
        .add(require("uiExt")) // 237 / 214 -  598 -  515
        .add(require("uiRad")) // 190 / 168 -  788 -  683
    var ui = require("ui")     // 314 / 282 -  361 -  302 20
        .add(require("uiExt")) // 237 / 214 -  598 -  515
        .add(require("uiRad")) // 190 / 168 -  788 -  683

    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)
        .add(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

    //    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                        fv 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, "Y-w-B"]);
    // 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 and touch event runtime
    // data; colors: 3-bit depth coded: 0b### ###=rgb (0=black, 7=white, 4=red,...)

    --- Define application stuff

    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

    --- Do devices stuff

    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
          .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

    --- Functions / methods of ui base module

    The ui base module 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); ).

    • .evt(x,y) - main entry point for touch and touch emulating functions (tap
      function in uiExt module). A tap starts with invocation of .evt(x,y)
      with x and y values; subsequent invocations with x and y values
      indicate dragging, and invocation without x and y indicate an un-touch,
      which completes a tap or touch. ui base module keeps track of the
      changes and makes them available to the application thru callbacks
      with the ui singleton - variable ui / _ - and touch event
      object t with detailed flags t.f. Any other 'entries' can
      be used but keeping the states consistent become the burden of the
      application and is not recommended.
    • .iib(x,y,e) - returns true when x and y (of touch) lay in bounding box of
      ui element e
    • .foc(e) - set focus to element e (and takes it away from element that had
      it so far)
    • .blr(e) - blur/unfocus element e (if .blr(), element currently in focus is
    • .w(x,y,w,h,c) - wipe rectangular area at x/y and w(idth)/h(eight) of
      display w/ optional color c; default color is background color (.bc).
    • .c(arguments) generic, single point entry for creating ui elements. Uses
      first arguments[1] as class or type name and concatenates it with "C"
      for specific create entry. To create a custom ui element and
      integrate it (with .add(require("customUiElementModule")) is
      discussed in a separate publication.
    • .d(e) - (re) draw element e, and when just .d(), redraw the whole ui (all ui
      elements). In general, a ui is built on upload without displaying it
      to save variables and displayed on demand or in onInit() to
      re-display on every re-power up or reset. Note ui elements with
      custom (label) renderers / formatters that use ui () or touch
      event states / flags (
      .te.f, t.f), these states / flags in their
      logic, such as the optional value renderer / formatter of the slider,
      this logic needs adjustment, or, the state(s) / flag(s) need to be
      set accordingly, latter though without interfering with their overall\
      life cycle or their dependents (ui components' logic).
    • .clr(c,i) - set color according this.clrs{0](c,i) (customizable) function.
      Default is set for bit coded value for 3-bit (rgb) color depth and
      provides 8 colors: b&000=0=black, b&111=7=white, b&100=4=red, etc.
      this.clrs[0]() can be customized to support any color depth - coded,
      table looked up, or literal. For details see Colors and color
      section below.
    • .fnt(fv) - set fontVector (size) ...currently w/ no frills, as opposed to
      .clr(). More font options will be implemented at a later time.
    • .ld(x,y,l(abelInfoArray)) - label draw function; label info array:
      • l[0]: fontVector (size)
      • l[1]: color (see Colors section
      • l[2]: xOffset to x - left of bounding box of label (.drawString(...))
      • l[3]: yOffset to y - top of bounding box of label (.drawString(...))
      • l[4]: label text (used in .drawString(...))
        Note: the reason for x and xOffset - and y and yOffset - is because
        .ld() is used in conjunction with a ui element that has a bounding
        box that provides x and y - usually left-top corner and xOffset
        and yOffset are used to place the label relative to the ui
        element's corner coordinates. For plain, direct use with absolute
        coordinates, either xOffset and yOffset have to be set to 0. For
        reuse of label at same position, it is better to set both x and
        y to 0.
    • .add(module,keep) - extend ui module with ui element and other extensions
      to support modularity. After module code is merged into ui base code,
      module is removed from cache to save variables. For that purpose, the
      module has to know its name in module name property mn. For example,
      for a module onOff.js for switching back light on/off on pin
      B2, exports = { mn:"onOff" , on: function(on) { digitalWrite(B2, on || on===undefined); }
      defines the module code, ui.add(require("onOff")) adds it to the
      ui.js base code in order to be used as ui.on();. Without building
      a file, modules can also be put into cache directly on upload in level
      0 (and served with require() with module name provided as variable to
      escape regular module detection and pre-upload from modules
      sources): 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:

    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:

    • bit-coded as single positive value
    • (negative) index into palette of [r,g,b] triplets
    • literal [r,g,b] triplet

    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 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.

    --- 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.

    --- More...

    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).

    --- ui - base code into which elements and custom code are mixed in:

    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
    // ....
    // ...  public methods - documented above
    // ... private methods
    // ...

    Attached shot: Button b1 and then button b2 'tapped'.

    1 Attachment

    • uiBtnExample.png

Avatar for allObjects @allObjects started