Declarative UI for the 8th

I wanted to be able to easily handle responsive layout in my home automation GUI. I ended up writing a simple JSON-based declative UI. Maybe creating a “domain-specific language” to describe these GUI structures would be easier to read? Maybe later…

When at root level “grid” key is null, the whole space is used for layout. Grids can be nested. Callback word gets a layout bounds rectangle and is called to do drawing or to add a widget into layout space.

Here is a simple sample:

{ 
  grid: null,
  layout: { rows: 2, cols: 1, rgap: 4, margin: 4 },
  tiles: [
           {
             grid: { row: 0, rows: 1, col: 0, cols: 1 }, 
             cb: ( 0 "red" nk:fill-rect )                 
           },
           {
             grid: { row: 1, rows: 1, col: 0, cols: 1 }, 
             layout: { rows: 1, cols: [80,0.45,-1], cgap: 4 },
             tiles: [
                      {
                        grid: { row: 0, rows: 1, col: 0, cols: 1 },
                        cb: ( 0 "blue" nk:fill-rect )
                      },
                      {
                        grid: { row: 0, rows: 1, col: 1, cols: 1 },
                        cb: ( 0 "green" nk:fill-rect )
                      },
                      {
                        grid: { row: 0, rows: 1, col: 2, cols: 1 },
                        cb: ( 0 "purple" nk:fill-rect )
                      }

                    ]
           }
         ]
}

dui

1 Like

I will admit, that 8th itself hold no interest for me what so ever, but I would be interested to see any expansion you have on this idea, since it can be adapted to other languages as well

1 Like

May I suggest perhaps following an existing pattern? For instance from what I’ve seen so far of Flutter is that it is largely interchangeable with SwiftUI, so adopting a similar pattern would make it more adoptable in the long run.

As 8th uses Nuklear based immediate gui, I don’t think existing declarative patterns are a good fit. I am currently writing a declarative node based grid/tile layout GUI builder. As my JSON-format is easy to traverse, an editable live preview is easy to implement.

I wrote the first version of my tileui library. I will use it to create my own node based GUI builder tool. It will work as a hybrid of declarative and programmatic paradigms. It will generate JSON for UI.

Below is a calculator sample program:


needs nk/gui
needs nk/tileui

32 constant FONTHEIGHT
FONTHEIGHT 1.75 n:* constant ROWHEIGHT

FONTHEIGHT font:system font:new "font1" font:atlas! drop

: +n \ nk n --
  >r 
  "calculator.display:num" tileui:data@ 10 n:*
  r> n:+ "calculator.display:num" swap tileui:data! ;

: mathop \ nk w -- nk
  "calculator:op" swap tileui:data!
  "calculator.display:num" tileui:data@  "calculator:a" swap tileui:data!
  "calculator.display:num" 0 tileui:data! ;

{
  name: "calculator",
  grid: null,
  layout: { rows: [ @ROWHEIGHT, -1], cols: 1, rgap: 4, cgap: 4, margin: 4 },
  data: { },
  cb: null , 
  tiles: [
           {
             name: "display",
             grid: { row: 0, rows: 1, col: 0, cols: 1 },
             data: { num: 0 }, 
             cb: ( nk:rect>local nk:grid-push     
                   "num" m:@ >s 32 nk:EDIT_SELECTABLE nk:EDIT_CLIPBOARD n:bor 
                   nk:PLUGIN_FILTER_FLOAT edit-string drop >n "num" m:_! drop )                
           },
           {
             name: "keypad",
             grid: { row: 1, rows: 1, col: 0, cols: 1 },
             layout: { rows: 4, cols: 4, rgap: 4, cgap: 4, margin: 4 },  
             data: { },
             cb: ( 2 2 "black" nk:stroke-rect drop ),
             tiles: [
                      {
                        name: "7",
                        grid: { row: 0, rows: 1, col: 0, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "7" ( 7 +n ) nk:button-label drop )
                      },
                      {
                        name: "8",
                        grid: { row: 0, rows: 1, col: 1, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "8" ( 8 +n ) nk:button-label drop )
                      },
                      {      
                        name: "9",
                        grid: { row: 0, rows: 1, col: 2, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "9" ( 9 +n ) nk:button-label drop )
                      },                                 
                      {
                        name: "+",
                        grid: { row: 0, rows: 1, col: 3, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "+"  ( ' n:+ mathop ) nk:button-label drop )
                      },
                      {
                        name: "4",
                        grid: { row: 1, rows: 1, col: 0, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "4" ( 4 +n ) nk:button-label drop )
                      },
                      {
                        name: "5",
                        grid: { row: 1, rows: 1, col: 1, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "5" ( 5 +n ) nk:button-label drop )
                      },
                      {
                        name: "6",
                        grid: { row: 1, rows: 1, col: 2, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "6" ( 6 +n ) nk:button-label drop )
                      },
                      {
                        name: "-",
                        grid: { row: 1, rows: 1, col: 3, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "-" ( ' n:- mathop ) nk:button-label drop )
                      },
                      {      
                        name: "1",
                        grid: { row: 2, rows: 1, col: 0, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "1" ( 1 +n ) nk:button-label drop )
                      },
                      {
                        name: "2",
                        grid: { row: 2, rows: 1, col: 1, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "2" ( 2 +n ) nk:button-label drop )
                      },
                      {
                        name: "3",
                        grid: { row: 2, rows: 1, col: 2, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "3" ( 3 +n ) nk:button-label drop )
                      },
                      {
                        name: "*",
                        grid: { row: 2, rows: 1, col: 3, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "*" ( ' n:* mathop ) nk:button-label drop )
                      },
                      {
                        name: "C",
                        grid: { row: 3, rows: 1, col: 0, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "C" 
                              ( "calculator:a" 0 tileui:data! "calculator.display:num" 0 tileui:data! )
                              nk:button-label drop 
                            )
                      },
                      {
                        name: "0",
                        grid: { row: 3, rows: 1, col: 1, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "0" ( 0 +n ) nk:button-label drop )
                      },
                      {
                        name: "=",
                        grid: { row: 3, rows: 1, col: 2, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "=" ( "calculator:op" tileui:data@ >r
                                                               "calculator.display:num" tileui:data@ >r
                                                               "calculator:a" tileui:data@ r> r> w:exec
                                                               "calculator.display:num" swap tileui:data!
                                                               "calculator:a" 0 tileui:data! ) nk:button-label drop )
                      },
                      {
                        name: "/",
                        grid: { row: 3, rows: 1, col: 3, cols: 1 },
                        data: { }, 
                        cb: ( nk:rect>local nk:grid-push "/"  ( ' n:/ mathop ) nk:button-label drop )
                      }
                    ]
           }
         ]
} constant UI


: init-window-size
  mobile? if
    hw:displaysize?
  else
    240 260
  then ;

init-window-size constant HEIGHT constant WIDTH

: new-win
  {
    name: "main",
    wide: @WIDTH,
    high: @HEIGHT,
    resizable: true,
    title: "Calculator"
  }
  nk:win ;

: main-render
  {
    name: "main",
    title: "main",
    bg: "white",
    padding: [0,0],
    flags: [ @nk:WINDOW_NO_SCROLLBAR ]
  }
 
  nk:begin
    UI tileui:render
  nk:end ;
           
: app:main
  UI tileui:init
  new-win ' main-render -1 nk:render-loop ;

calc

2 Likes