• ...something like that: a RENDERER singleton for rendering on a display of a particular X and Y size panes - (full) pages and pop-ups w/ display nodes/items - in various styles:

    var panes =                      // declaration of your pages/popups w/ content 
    { start:                         // a page  (covers all of display)
      { id: "start", no: 0, title: "Start Page", s: 0 // s-tyle obj
      , nodes:                       // node displays label or property value
        [ { id: "s01", x:  0, y:  0, w: 30, h: 20,  
          , l: "ip:" , s:  0         // label "ip:" in default s-tyle
        , { id: "s02", x: 35, y:  0, w: 30, h: 20, 
          , p: "ip"  , s:  0         // property "ip" in model obj (see: MVC pattern)
        ]
      }
    , msg:                           // a pop-up pane (covers area of display)
      { id: "msg", no: 0, title: "Message", s: 0, x:  20, y:  20, w: 80, h: 28,
      , nodes:
        [ { id: "m01", x:  0, y:  0, w: 30, h: 20,  
          , l: "message:"            // label "message:"
        , { id: "m02", x: 35, y:  0, w: 30, h: 20, 
          , p: "msg"                 // property "msg" in model object
        ]
      }
    };
    var styles = // indexed 0,1,... define font, -size and colors
    [ { font: "...", size: 9, fg: [1,1,1], bg: [0,0,0] } // 0: default
    ];                           // f(ore)g(round) and b(ack)g(round) color 
      
    var RENDERER =               // Singleton (google it)
    { g: null, d: null           // graphics and display objects
    , X: 0, Y: 0                 // display size, set in init()           
    , ps: null                   // p(ane)s set in init()
    , ss: null                   // s(tyle)s set in inbit() 
    , cpid: null                 // current p(ane) id, for optimization
    , cs: -1                     // current style (index0, for optimization
    , doPane: function(pid) {    // render pane w/ p(ane)id
        if (pid != this.cpid) this._initP(pid); // initP(ane) if not current
        this.ps[this.cpid = pid].nodes.forEach(function(n){ // for each node in pane
          this._doN(n,0); });    // render node w/ node object w/o clear-first
        return this; }
    , doNode: function(nid) {    // render node w/ n(ode) id
        // code here... if node found on other than current pane, doPane(),
        //              else do node w/ node object w/ clear-first: this._doN(n,1);
        return this; }
    , _doN: function(n,c) {      // render node w/ n(node) object and c(lear-first, truthy)
        // code here... change style if other than current, remember new current style
        return this; }
    , _initP: function(pid) {    // init - clear w/ style bg and put title
        var p = this.ps[pid];
        if (p.p) { // page (no frame around)
          /* page init code here... if title present, set title bar and title */ }
        else { // pop-up (do frame around)
          /* popup init code here... if title present, set title bar and title */ }
        return this; }
    , _clear: function(x,y,x2,y2,s,b) { // s(tyle index), b(order, truthy) for pop-up
        // code here... consider b(border) and adjust x,y,x2,y2 accordingly
        // remember cs - c(urrent) s(tyle) - for optimiztion
        return this; }
    , getN(nid) { var n;         // get n(ode) by n(ode) id; returns node object n
        // code here...
        return n; }
    , setS(sx) { if (sx !== this.cs) { var s = this.ss[this.cs = sx]; // set stile
        // set style from s code here ... font / font-size / fore / background colors 
        } return this; }
    , init: function(g,d,X,Y,panes,styles) { var p, xo, yo; // p(ane) // init and optimize
        this.g=g, this.d=d, this.X=X; this.Y=Y, this.ps=panes; this.ss=styles;
        for (pid in panes) { p = panes[pid]; // for each pane w/ p(ane) id in panes
          if (p.p = (p["x"] === undefined)) { // page - set x,y,x2,y2 & offset for nodes
            p.x = xo = 0; p.y = 0; p.x2 = this.X-1; p.y2 = this.Y-1; }
          else { // pop-up - set x,y,x2,y2 & offset for nodes pop-up adjusted
            p.x2 = (xo = p.x)+p.w-1; p.y2 = (yo = p.y)+p.h-1; }
          p.nodes.forEach(function(n){                   // for each n(ode) in pane:
              n.p = p; n.x = n.x+xo; n.y = n.y+yo;       // - set pane & adjust x, y      
              n.x2 = n.x+ n.w-1; n.y2 = n.y+n.h-1; }); } // - extend for fill_Rect
        return this; }   
    }
    
    // other application code here...
    
    function onInit() {
      // other init code here...
      // get graphics object g and display object d setup
      RENDERER.init(g,d,128,64,panes,styles).d­oPane("start");
      // other init code here...
    }
    

    Still a good amount of coding work left, but that's how I would get started.

    The panes and styles definitions use real objects that use more variable space than other constructs, such as arrays... If you see fit, you can make them plain array objects and use the index as the variable name. I do that in my ui stuff. This then changes some of the condition coding, because 'existence' of a value has to be defined differently: length check of array, or if a place holder for the value has to be there to not mess up the indices, a application specific 'undefined' / 'absent' indicator value has to be defined, 0, -1, null, etc. and the test has to consider this 'absent' indicator definition. *Note: X and Y may be retrieved from g(raphics) or d(isplay) object and need then not to be passed... and same applies for either g(raphics) or d(isplay) object.

    If the display has no immediate / transparent update when writing to the Espruino Graphics object - in other words when a .flip() is required - the RENDERER needs to get a .flip() method.

About

Avatar for allObjects @allObjects started