When I was a kid programming was magical and RealBASIC made designing GUIs easy. Today we have so many options, all annoyingly tedious; what's a kid at heart to do?

The guys at subformapp.com are tackling the difficulty of GUI design with a new layout system (subform). HN discussion here. Actually trying the app involves a $25/mo subscription... nope. The engine itself is provided free for non-commercial use:

https://github.com/lynaghk/subform-layout

What's missing from the freely available engine is documentation so here's what I've got after playing with it:

Subform is concerned with boxes and where to place them. Boxes can either be:

Boxes have children. The ones that are parent-directed can be layed out either as a stack or a grid.

Boxes contain other boxes. The root of the tree of boxes is called the "artboard".

Subform uses a generalized dimension format everywhere:

Layout properties are split between the parent box and child boxes.

Absolutely positioned / self-directed items are relative to the parent box but do not follow their parent's layout mode. They have before/size/after dimensions.

Stack Layout

Stacked layouts have a direction (horizontal or vertical). This is the main direction. The opposite direction is the cross direction.

Parent specifies Child specifies
  • area before the first box in the main direction
  • area between the boxes in the main direction
  • area after the last box in the main direction
  • area before and after boxes in the crosswise direction
  • area before, size, and after the child box in the main and cross-wise direction

Example with all properties set to defaults. Properties are optional.

  {
      layout: {
          mode: "self-directed",
          horizontal: {before: "1s", size: "1s", after: "1s"},
          vertical: {before: "1s", size: "1s", after: "1s"},
      },
      childrenLayout: {
          mode: "stack-vertical",
          mainBeforeFirst: "1s",
          mainBetween: 0,
          mainAfterLast: "1s",
          crossBefore: "1s",
          crossAfter: "1s"
      }
      children: [
          {
              layout: {
                  mode: "parent-directed",
                  main: {before: null, size: "1s", after: null},
                  cross: {before: null, size: "1s", after: null}
              }
          }
      ]
  }

Grid Layout

Grid layouts have both a horizontal and vertical direction (rows and cols).

Parent specifies Child specifies
  • area before the first box
  • area between the boxes (can be overridden by children)
  • area after the last box
  • sizes of rows and columns using the general dimension system
  • their row and column (children placed on the same grid location overlap)
  • the rowspan and colspan (how many grid locations to occupy in each direction)

Example with all properties set to defaults. Properties are optional.

  {
      layout: {
          mode: "self-directed",
          horizontal: {before: "1s", size: "1s", after: "1s"},
          vertical: {before: "1s", size: "1s", after: "1s"},
      },
      childrenLayout: {
          mode: "grid",
          rows: {beforeFirst: 0, between: 0, afterLast: 0, sizes: ["1s", "1s"]},
          cols: {beforeFirst: 0, between: 0, afterLast: 0, sizes: ["1s", "1s"]}
      }
      children: [
          {
              layout: {
                  mode: "parent-directed",
                  rowIdx: 0,
                  rowSpan: 1,
                  colIdx: 0,
                  colSpan: 1
              }
          }
      ]
  }

Overall, it reminds me of Java's GroupLayout and the benefits of html table layouts.

Play with layout below: (ctrl-mousedown to change the bounds of the art area)


Specification for layout as far as I can tell:

<dimension> := <int> | <int>s | <int>% | empty
 - so dimensions can be Absolute, Stretch, Percentage, or Default

<node (can be empty)>
 layout:
  - mode: ("self-directed" | "parent-directed"(default))
  (self-directed)
  - horizontal:
   - before: <dimension> (default 1s)
   - size: <dimension> (default 1s)
   - after: <dimension> (default 1s)
  - vertical: (same as horizontal)
  (parent-directed, parent is stack-horizontal or stack-vertical)
  - main:
   - before: <dimension> (default Default)
   - size: <dimension> (default 1s)
   - after: <dimension> (default Default)
  - cross:
   - before: <dimension> (default Default)
   - size: <dimension> (default 1s)
   - after: <dimension> (default Default)
  (parent-directed, parent is grid)
  - rowSpan: <int> (default 1)
  - colSpan: <int> (default 1)
  - rowIdx: <int> (default 0)
  - colIdx: <int> (default 0)
 childrenLayout:
  - mode: ("grid" | "stack-horizontal" | "stack-vertical"(default))
  (stack-horizontal or stack-vertical)
  - mainBeforeFirst: <dimension> (default 1s)
  - mainBetween: <dimension> (default 0)
  - mainAfterLast: <dimension> (default 1s)
  - crossBefore: <dimension> (default 1s)
  - crossAfter: <dimension> (default 1s)
  (grid)
  - rows:
   - beforeFirst: <dimension> (default 0)
   - between: <dimension> (default 0)
   - afterLast: <dimension> (default 0)
   - sizes: [<dimension>, <dimension>, ...] (default [1s, 1s])
  - cols: (same as rows)
 children: [<node>, <node>, ...]