Standard library · Graphics and games

gui.cssbox

A CSS box layout engine in pure Salam: block flow with margin collapsing, flexbox, grid, tables, absolute positioning and incremental relayout.

import gui.cssbox

This package computes where every box of a user interface goes, using the rules of CSS, so that a page drawn natively has the same geometry as the same page in a browser. You build a Tree of boxes, give each one a Style (display, size, margins, padding, border, flex properties and so on), and call Compute with the size of the window. Layout then gives each box its position, size, content box and scrollable area. The package draws nothing and knows nothing about fonts: text and images are leaves whose size comes from a measure callback you supply.

Block flow follows CSS 2 with margin collapsing between siblings, between a parent and its first or last child, and through empty boxes. Flexbox follows the CSS flexbox specification: flex base sizes, the grow and shrink loop with freezing, wrapping, every justify and align value, auto margins, baselines, reverse directions, order and right to left rows. Absolute and fixed boxes are placed against their containing block, including their static position when insets are auto. Floats, inline text flow and writing modes other than horizontal are not done.

Grid follows CSS Grid Level 1: track lists with pixels, percentages, fr, auto, min-content, max-content, fit-content(), minmax() and repeat() including auto-fill and auto-fit, named lines, grid-template-areas, implicit tracks from grid-auto-rows and grid-auto-columns, sparse and dense auto placement in either direction, spans and negative lines, the full track sizing algorithm, every justify and align value for items and content, baselines, and absolutely positioned items placed in their grid area. Subgrid is not done. Grid properties are strings in CSS syntax, such as grid_template_columns = "repeat(3, 1fr)"; the tree keeps its own copy, and IsTrackList and the other checks tell whether a value is valid.

Tables follow the CSS 2.1 table model the way Chrome lays it out: rows, row groups, header and footer groups (moved to the top and bottom), columns and column groups, captions on either side, anonymous rows and cells around misplaced children, col_span and row_span, automatic column widths from each cell's minimum and maximum content width (with percentage and fixed cells), fixed table layout, border-spacing, collapsed borders, extra height given to rows, and vertical-align in cells.

The block, flex and grid algorithms are ports of Taffy, and the package is tested against 1,527 of Taffy's fixtures measured in Chrome (616 of them with grid), in both box sizing models and both directions. Tables were written from the specification and checked against 53 cases measured in headless Chrome.

Things to know

  • Lengths are already resolved: give pixels or percentages. Convert em, rem, vw and vh before building the style.
  • Defaults are the CSS initial values: display is block, position is static and box_sizing is content-box.
  • Ids are plain integers in the order of AddNode; the node you pass to Compute is the root.
  • Results are rounded to whole pixels the way Chrome rounds them. Turn this off with SetRounding(false) to read fractions.
  • Each node caches its last results. After a style change only the changed node and its ancestors are laid out again; call MarkDirty when the content behind a measure callback changes.

A toolbar with flexbox and text

A row with a growing title, a button and a badge moved first with order. The measure callback stands in for text: it answers how wide the text is with unlimited room, how narrow it can get, and how tall it is at a given width. Switching the direction to right to left mirrors the row.

gui-cssbox-guide-flex.salam
import gui.cssbox

func label(chars: int, mi: cssbox.MeasureInput): cssbox.MeasureOutput:
    full := (chars as f64) * 8.0
    if mi.available_width.kind == cssbox.SpaceKind.MinContent:
        ret cssbox.MeasuredWithBaseline(40.0, 32.0, 12.0)
    end
    if mi.available_width.kind == cssbox.SpaceKind.Definite and mi.available_width.value < full:
        ret cssbox.MeasuredWithBaseline(mi.available_width.value, 32.0, 12.0)
    end
    ret cssbox.MeasuredWithBaseline(full, 16.0, 12.0)
end

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    bar := t.AddNode(cssbox.Style { display = cssbox.Display.Flex, width = cssbox.Px(400.0), padding = cssbox.AllSides(cssbox.Px(8.0)), column_gap = cssbox.Px(8.0), align_items = cssbox.Align.Baseline, justify_content = cssbox.Align.SpaceBetween })
    title := t.AddNode(cssbox.Style { flex_grow = 1.0, flex_shrink = 1.0, flex_basis = cssbox.Auto() })
    t.SetMeasure(title, (mi: cssbox.MeasureInput): ret label(30, mi) end)
    button := t.AddNode(cssbox.Style { padding = cssbox.SidesOf(cssbox.Px(4.0), cssbox.Px(12.0), cssbox.Px(4.0), cssbox.Px(12.0)), border = cssbox.AllSides(cssbox.Px(1.0)) })
    t.SetMeasure(button, (mi: cssbox.MeasureInput): ret label(4, mi) end)
    badge := t.AddNode(cssbox.Style { width = cssbox.Px(24.0), height = cssbox.Px(24.0), align_self = cssbox.Align.Center, order = -1 })
    t.AddChild(bar, title)
    t.AddChild(bar, button)
    t.AddChild(bar, badge)
    t.Compute(bar, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    each id in [bar, badge, title, button]:
        l := t.Layout(id)
        println id, l.x, l.y, l.width, l.height
    end
    rtl := t.GetStyle(bar)
    mut s := rtl
    s.direction = cssbox.Direction.Rtl
    t.SetStyle(bar, s)
    t.Compute(bar, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "rtl badge x", t.Layout(badge).x, "title x", t.Layout(title).x
end
Output0 0 0 416 42 3 8 9 24 24 1 40 13 294 16 2 342 8 66 26 rtl badge x 384 title x 82

Block flow and absolute positioning

The header's bottom margin and the card's top margin collapse into one 30 pixel gap, the card is centred with auto margins, and the close button is placed in the corner of the page, which is its containing block because it is positioned. A box with display none takes no space.

gui-cssbox-guide-block.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    page := t.AddNode(cssbox.Style { width = cssbox.Px(320.0), position = cssbox.Position.Relative, border = cssbox.AllSides(cssbox.Px(2.0)) })
    head := t.AddNode(cssbox.Style { height = cssbox.Px(40.0), margin = cssbox.SidesOf(cssbox.Px(10.0), cssbox.Px(0.0), cssbox.Px(20.0), cssbox.Px(0.0)) })
    card := t.AddNode(cssbox.Style { width = cssbox.Percent(50.0), margin = cssbox.SidesOf(cssbox.Px(30.0), cssbox.Auto(), cssbox.Px(0.0), cssbox.Auto()), padding = cssbox.AllSides(cssbox.Px(10.0)), box_sizing = cssbox.BoxSizing.BorderBox, min_height = cssbox.Px(60.0) })
    close := t.AddNode(cssbox.Style { position = cssbox.Position.Absolute, inset = cssbox.SidesOf(cssbox.Px(4.0), cssbox.Px(4.0), cssbox.Auto(), cssbox.Auto()), width = cssbox.Px(16.0), height = cssbox.Px(16.0) })
    hidden := t.AddNode(cssbox.Style { display = cssbox.Display.None, height = cssbox.Px(500.0) })
    t.AddChild(page, head)
    t.AddChild(page, card)
    t.AddChild(page, close)
    t.AddChild(page, hidden)
    t.Compute(page, cssbox.Definite(800.0), cssbox.Definite(600.0))
    each id in [page, head, card, close, hidden]:
        l := t.Layout(id)
        println id, l.x, l.y, l.width, l.height, "margin", l.margin.top, l.margin.left
    end
end
Output0 0 0 324 144 margin 0 0 1 2 12 320 40 margin 10 0 2 82 82 160 60 margin 30 80 3 302 6 16 16 margin 0 0 4 0 0 0 0 margin 0 0

Changing one box

The counters show the cache at work: a second Compute with nothing changed computes nothing, and changing one leaf recomputes that leaf and its ancestors. Laying out a 5,000 box page (50 wrapping flex rows of 100 text leaves) takes about 15 ms the first time on one desktop core, and about 0.7 ms after one leaf changes; most of that is rounding the whole tree.

gui-cssbox-guide-incremental.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    list := t.AddNode(cssbox.Style { display = cssbox.Display.Flex, flex_direction = cssbox.FlexDirection.Column, row_gap = cssbox.Px(4.0) })
    mut items := Vector {} as Vector<int>
    defer items.free()
    repeat 3 in i:
        item := t.AddNode(cssbox.Style { height = cssbox.Px(20.0) })
        t.SetMeasure(item, (_mi: cssbox.MeasureInput): ret cssbox.Measured(100.0 + (i as f64) * 10.0, 20.0) end)
        items.push(item)
    end
    t.SetChildren(list, items)
    println "nodes", t.NodeCount(), "children", t.ChildCount(list), "second", t.Child(list, 1), "parent", t.Parent(items.get(0))
    t.Compute(list, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "size", t.Layout(list).width, t.Layout(list).height, "calls", t.LayoutCalls(), "measures", t.MeasureCalls()
    t.ResetCounters()
    t.Compute(list, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "again", t.LayoutCalls(), "dirty", t.IsDirty(list)
    t.SetMeasure(items.get(2), (_mi: cssbox.MeasureInput): ret cssbox.Measured(200.0, 20.0) end)
    println "dirty", t.IsDirty(list), t.IsDirty(items.get(0))
    t.Compute(list, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "width", t.Layout(list).width, "calls", t.LayoutCalls()
    t.ClearMeasure(items.get(2))
    t.MarkDirty(items.get(1))
    t.SetRounding(false)
    t.Compute(list, cssbox.MinContentSpace(), cssbox.MaxContentSpace())
    println "width", t.Layout(list).width
end
Outputnodes 4 children 3 second 2 parent 0 size 120 68 calls 10 measures 9 again 0 dirty false dirty true false width 200 calls 6 width 110

A page grid with named areas

A header, sidebar, main area and footer are placed by area name; the main box is centred in its cell with justify_self. The second grid fills a 250 pixel row with as many 70 pixel columns as fit, then stretches them, and the fourth tile wraps to the next row. A 20 by 50 grid of measured text cells lays out in about 1 to 3.5 ms the first time and about 0.8 to 3.3 ms after one cell changes, on one desktop core.

gui-cssbox-guide-grid.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    page := t.AddNode(cssbox.Style { display = cssbox.Display.Grid, width = cssbox.Px(400.0), grid_template_columns = "120px 1fr", grid_template_rows = "40px auto 30px", grid_template_areas = `"head head" "side main" "foot foot"`, column_gap = cssbox.Px(10.0), row_gap = cssbox.Px(6.0) })
    head := t.AddNode(cssbox.Style { grid_row_start = "head", grid_row_end = "head", grid_column_start = "head", grid_column_end = "head" })
    side := t.AddNode(cssbox.Style { grid_row_start = "side", grid_row_end = "side", grid_column_start = "side", grid_column_end = "side", height = cssbox.Px(80.0) })
    body := t.AddNode(cssbox.Style { grid_row_start = "main", grid_row_end = "main", grid_column_start = "main", grid_column_end = "main", justify_self = cssbox.Align.Center, width = cssbox.Px(100.0), height = cssbox.Px(50.0) })
    foot := t.AddNode(cssbox.Style { grid_row_start = "foot", grid_row_end = "foot", grid_column_start = "foot", grid_column_end = "foot" })
    tiles := t.AddNode(cssbox.Style { display = cssbox.Display.Grid, width = cssbox.Px(250.0), grid_template_columns = "repeat(auto-fill, minmax(70px, 1fr))", grid_auto_flow = cssbox.GridAutoFlow.Row, column_gap = cssbox.Px(4.0) })
    each c in [head, side, body, foot]:
        t.AddChild(page, c)
    end
    repeat 4:
        tile := t.AddNode(cssbox.Style { height = cssbox.Px(20.0) })
        t.AddChild(tiles, tile)
    end
    t.Compute(page, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    each id in [page, head, side, body, foot]:
        l := t.Layout(id)
        println id, l.x, l.y, l.width, l.height
    end
    t.Compute(tiles, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    repeat t.ChildCount(tiles) in i:
        l := t.Layout(t.Child(tiles, i))
        println "tile", i, l.x, l.y, l.width
    end
    println cssbox.IsTrackList("1fr [mid] minmax(40px, 2fr)"), cssbox.IsTrackSizes("50px auto"), cssbox.IsGridLine("span 2"), cssbox.IsGridAreas("'a b' 'a c'")
end
Output0 0 0 400 162 1 0 0 400 40 2 0 46 120 80 3 215 46 100 50 4 0 132 400 30 tile 0 0 0 81 tile 1 85 0 80 tile 2 169 0 81 tile 3 0 20 81 true true true true

A table with spans and a caption

Column widths come from the cells' content. The first cell spans two rows and is centred vertically, the last spans two columns, and the caption sits below. The table then switches to a fixed 300 pixel layout with collapsed borders: the four columns share the width equally and each cell keeps half of every shared border. A table of 200 rows and 5 measured columns takes about 0.7 to 1 ms the first time and about 0.4 to 0.5 ms after one cell changes.

gui-cssbox-guide-table.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    table := t.AddNode(cssbox.Style { display = cssbox.Display.Table, border_spacing_x = 4.0, border_spacing_y = 2.0, border = cssbox.AllSides(cssbox.Px(1.0)) })
    caption := t.AddNode(cssbox.Style { display = cssbox.Display.TableCaption, caption_side = cssbox.CaptionSide.Bottom, height = cssbox.Px(16.0) })
    body := t.AddNode(cssbox.Style { display = cssbox.Display.TableRowGroup })
    t.AddChild(table, caption)
    t.AddChild(table, body)
    widths := [30.0, 80.0, 50.0, 40.0, 120.0]
    mut cells := Vector {} as Vector<int>
    defer cells.free()
    repeat 2 in r:
        row := t.AddNode(cssbox.Style { display = cssbox.Display.TableRow })
        t.AddChild(body, row)
        repeat 3 - r in c:
            mut st := cssbox.Style { display = cssbox.Display.TableCell, border = cssbox.AllSides(cssbox.Px(2.0)), padding = cssbox.AllSides(cssbox.Px(2.0)), vertical_align = cssbox.VerticalAlign.Middle }
            if r == 1 and c == 1:
                st.col_span = 2
            end
            if r == 0 and c == 0:
                st.row_span = 2
            end
            cell := t.AddNode(st)
            inner := t.AddNode(cssbox.Style { width = cssbox.Px(widths[r * 3 + c]), height = cssbox.Px(12.0 + (r as f64) * 10.0) })
            t.AddChild(cell, inner)
            t.AddChild(row, cell)
            cells.push(cell)
        end
    end
    t.Compute(table, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    tl := t.Layout(table)
    println "table", tl.width, tl.height
    each c in cells:
        l := t.Layout(c)
        println "cell", c, l.x, l.y, l.width, l.height
    end
    cl := t.Layout(caption)
    println "caption", cl.y, cl.width
    mut fixed := t.GetStyle(table)
    fixed.table_layout = cssbox.TableLayout.Fixed
    fixed.width = cssbox.Px(300.0)
    fixed.border_collapse = cssbox.BorderCollapse.Collapse
    t.SetStyle(table, fixed)
    t.Compute(table, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    each c in cells:
        l := t.Layout(c)
        println "fixed", c, l.x, l.width, l.border.left
    end
    mut first := t.GetStyle(cells[0])
    first.border_kinds = cssbox.BorderKinds { top = cssbox.BorderKind.Hidden, right = cssbox.BorderKind.Double, bottom = cssbox.BorderKind.Solid, left = cssbox.BorderKind.Dashed }
    t.SetStyle(cells[0], first)
    t.Compute(table, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    mut segs := t.CollapsedBorders(table)
    defer segs.free()
    println "segments", segs.len()
    s0 := segs[0]
    println "segment", s0.x, s0.y, s0.width, s0.height, s0.node, s0.side, s0.vertical
end
Outputtable 272 74 cell 4 0 0 38 52 cell 6 42 0 88 20 cell 8 134 0 124 20 cell 11 42 0 88 30 cell 13 134 0 128 30 caption 58 272 fixed 4 0 75 1 fixed 6 75 75 1 fixed 8 150 75 1 fixed 11 75 75 1 fixed 13 150 150 1 segments 19 segment 300 18 2 30 13 1 true

Types

LengthKind

enum LengthKind
What a Length holds: auto, pixels, a percentage or a sizing keyword.
NameTypeDescription
AutoThe CSS auto value.
PxA length in logical pixels.
PercentA percentage of the containing block; value is the CSS number, so 50 means 50%.
MinContentThe min-content keyword: as narrow as the content allows.
MaxContentThe max-content keyword: as wide as the content wants without wrapping.
FitContentThe fit-content keyword: max-content, but never wider than the space available.
StretchThe stretch keyword: fill the containing block minus margins.
gui-cssbox-Style.salam
import gui.cssbox

func main:
    mut st := cssbox.Style {}
    println st.display == cssbox.Display.Block, st.position == cssbox.Position.Static, st.flex_shrink, st.width.kind == cssbox.LengthKind.Auto
    st.width = cssbox.Percent(50.0)
    st.max_width = cssbox.Px(300.0)
    st.min_height = cssbox.MinContent()
    st.height = cssbox.FitContent()
    st.margin = cssbox.AllSides(cssbox.Auto())
    st.padding = cssbox.SidesOf(cssbox.Px(1.0), cssbox.Px(2.0), cssbox.Px(3.0), cssbox.Px(4.0))
    println st.width.value, st.padding.left.value, st.margin.top.kind == cssbox.LengthKind.Auto
    m := cssbox.MaxContent()
    s := cssbox.Stretch()
    println m.kind == cssbox.LengthKind.MaxContent, s.kind == cssbox.LengthKind.Stretch
    sp := cssbox.Definite(120.0)
    println sp.kind == cssbox.SpaceKind.Definite, sp.value, cssbox.MinContentSpace().kind == cssbox.SpaceKind.MinContent
    mut t := cssbox.Tree {}
    defer t.Free()
    root := t.AddNode(cssbox.Style { width = cssbox.Px(200.0), overflow_y = cssbox.Overflow.Scroll, scrollbar_width = 12.0, height = cssbox.Px(50.0), border = cssbox.AllSides(cssbox.Px(3.0)), padding = cssbox.AllSides(cssbox.Px(5.0)) })
    child := t.AddNode(cssbox.Style { height = cssbox.Px(90.0) })
    t.SetMeasure(child, (mi: cssbox.MeasureInput): ret cssbox.MeasuredWithBaseline(mi.known_width, 90.0, 14.0) end)
    t.AddChild(root, child)
    t.Compute(root, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    l := t.Layout(root)
    println l.x, l.y, l.abs_x, l.abs_y, l.width, l.height
    println l.content_x, l.content_y, l.content_width, l.content_height
    println l.padding.top, l.padding.right, l.padding.bottom, l.padding.left
    println l.border.top, l.border.right, l.border.bottom, l.border.left, l.margin.top
    println l.scrollbar_width, l.scrollbar_height, l.scroll_width, l.scroll_height, l.has_baseline, l.baseline
end
Outputtrue true 1 true 50 4 true true true true 120 true 0 0 0 0 216 66 8 8 188 50 5 5 5 5 3 3 3 3 0 12 0 198 100 true 22

Display

enum Display
The CSS display property.
NameTypeDescription
BlockA block box laid out with block flow and margin collapsing.
InlineBlockTreated as a block that shrinks to fit its content and starts a new formatting context; inline flow is not done yet.
FlowRootA block that starts a new block formatting context, so its children's margins do not collapse through it.
FlexA flex container laid out with the CSS flexbox algorithm.
InlineFlexA flex container that shrinks to fit inside a block parent.
ContentsThe box itself disappears and its children take its place in the parent.
NoneThe box and its subtree take no space and get a zero layout.
GridA grid container laid out with the CSS grid algorithm.
InlineGridA grid container that shrinks to fit inside a block parent.
TableA table: its children are laid out with the CSS table model, and the box shrinks to fit its columns unless it has a width.
InlineTableA table that shrinks to fit inside a block parent.
TableRowGroupA group of rows, like tbody.
TableHeaderGroupA group of rows drawn first, like thead.
TableFooterGroupA group of rows drawn last, like tfoot.
TableRowA table row; its children are its cells.
TableCellA table cell, laid out inside like a block that starts a new formatting context.
TableColumnA column, like col; its width and span apply to the table's columns.
TableColumnGroupA group of columns, like colgroup.
TableCaptionA caption placed above or below the table, as wide as the table.
gui-cssbox-block.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    page := t.AddNode(cssbox.Style { width = cssbox.Px(320.0), position = cssbox.Position.Relative, border = cssbox.AllSides(cssbox.Px(2.0)) })
    head := t.AddNode(cssbox.Style { height = cssbox.Px(40.0), margin = cssbox.SidesOf(cssbox.Px(10.0), cssbox.Px(0.0), cssbox.Px(20.0), cssbox.Px(0.0)) })
    card := t.AddNode(cssbox.Style { width = cssbox.Percent(50.0), margin = cssbox.SidesOf(cssbox.Px(30.0), cssbox.Auto(), cssbox.Px(0.0), cssbox.Auto()), padding = cssbox.AllSides(cssbox.Px(10.0)), box_sizing = cssbox.BoxSizing.BorderBox, min_height = cssbox.Px(60.0) })
    close := t.AddNode(cssbox.Style { position = cssbox.Position.Absolute, inset = cssbox.SidesOf(cssbox.Px(4.0), cssbox.Px(4.0), cssbox.Auto(), cssbox.Auto()), width = cssbox.Px(16.0), height = cssbox.Px(16.0) })
    hidden := t.AddNode(cssbox.Style { display = cssbox.Display.None, height = cssbox.Px(500.0) })
    t.AddChild(page, head)
    t.AddChild(page, card)
    t.AddChild(page, close)
    t.AddChild(page, hidden)
    t.Compute(page, cssbox.Definite(800.0), cssbox.Definite(600.0))
    each id in [page, head, card, close, hidden]:
        l := t.Layout(id)
        println id, l.x, l.y, l.width, l.height, "margin", l.margin.top, l.margin.left
    end
end
Output0 0 0 324 144 margin 0 0 1 2 12 320 40 margin 10 0 2 82 82 160 60 margin 30 80 3 302 6 16 16 margin 0 0 4 0 0 0 0 margin 0 0

GridAutoFlow

enum GridAutoFlow
The CSS grid-auto-flow property: how items without a position are placed.
NameTypeDescription
RowFill each row, then start a new one.
ColumnFill each column, then start a new one.
RowDenseLike Row, but later items may fill earlier holes.
ColumnDenseLike Column, but later items may fill earlier holes.
gui-cssbox-grid.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    page := t.AddNode(cssbox.Style { display = cssbox.Display.Grid, width = cssbox.Px(400.0), grid_template_columns = "120px 1fr", grid_template_rows = "40px auto 30px", grid_template_areas = `"head head" "side main" "foot foot"`, column_gap = cssbox.Px(10.0), row_gap = cssbox.Px(6.0) })
    head := t.AddNode(cssbox.Style { grid_row_start = "head", grid_row_end = "head", grid_column_start = "head", grid_column_end = "head" })
    side := t.AddNode(cssbox.Style { grid_row_start = "side", grid_row_end = "side", grid_column_start = "side", grid_column_end = "side", height = cssbox.Px(80.0) })
    body := t.AddNode(cssbox.Style { grid_row_start = "main", grid_row_end = "main", grid_column_start = "main", grid_column_end = "main", justify_self = cssbox.Align.Center, width = cssbox.Px(100.0), height = cssbox.Px(50.0) })
    foot := t.AddNode(cssbox.Style { grid_row_start = "foot", grid_row_end = "foot", grid_column_start = "foot", grid_column_end = "foot" })
    tiles := t.AddNode(cssbox.Style { display = cssbox.Display.Grid, width = cssbox.Px(250.0), grid_template_columns = "repeat(auto-fill, minmax(70px, 1fr))", grid_auto_flow = cssbox.GridAutoFlow.Row, column_gap = cssbox.Px(4.0) })
    each c in [head, side, body, foot]:
        t.AddChild(page, c)
    end
    repeat 4:
        tile := t.AddNode(cssbox.Style { height = cssbox.Px(20.0) })
        t.AddChild(tiles, tile)
    end
    t.Compute(page, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    each id in [page, head, side, body, foot]:
        l := t.Layout(id)
        println id, l.x, l.y, l.width, l.height
    end
    t.Compute(tiles, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    repeat t.ChildCount(tiles) in i:
        l := t.Layout(t.Child(tiles, i))
        println "tile", i, l.x, l.y, l.width
    end
    println cssbox.IsTrackList("1fr [mid] minmax(40px, 2fr)"), cssbox.IsTrackSizes("50px auto"), cssbox.IsGridLine("span 2"), cssbox.IsGridAreas("'a b' 'a c'")
end
Output0 0 0 400 162 1 0 0 400 40 2 0 46 120 80 3 215 46 100 50 4 0 132 400 30 tile 0 0 0 81 tile 1 85 0 80 tile 2 169 0 81 tile 3 0 20 81 true true true true

TableLayout

enum TableLayout
The CSS table-layout property.
NameTypeDescription
AutoColumn widths come from the content of all cells.
FixedColumn widths come from the columns and the first row only; it applies when the table has a width.
gui-cssbox-table.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    table := t.AddNode(cssbox.Style { display = cssbox.Display.Table, border_spacing_x = 4.0, border_spacing_y = 2.0, border = cssbox.AllSides(cssbox.Px(1.0)) })
    caption := t.AddNode(cssbox.Style { display = cssbox.Display.TableCaption, caption_side = cssbox.CaptionSide.Bottom, height = cssbox.Px(16.0) })
    body := t.AddNode(cssbox.Style { display = cssbox.Display.TableRowGroup })
    t.AddChild(table, caption)
    t.AddChild(table, body)
    widths := [30.0, 80.0, 50.0, 40.0, 120.0]
    mut cells := Vector {} as Vector<int>
    defer cells.free()
    repeat 2 in r:
        row := t.AddNode(cssbox.Style { display = cssbox.Display.TableRow })
        t.AddChild(body, row)
        repeat 3 - r in c:
            mut st := cssbox.Style { display = cssbox.Display.TableCell, border = cssbox.AllSides(cssbox.Px(2.0)), padding = cssbox.AllSides(cssbox.Px(2.0)), vertical_align = cssbox.VerticalAlign.Middle }
            if r == 1 and c == 1:
                st.col_span = 2
            end
            if r == 0 and c == 0:
                st.row_span = 2
            end
            cell := t.AddNode(st)
            inner := t.AddNode(cssbox.Style { width = cssbox.Px(widths[r * 3 + c]), height = cssbox.Px(12.0 + (r as f64) * 10.0) })
            t.AddChild(cell, inner)
            t.AddChild(row, cell)
            cells.push(cell)
        end
    end
    t.Compute(table, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    tl := t.Layout(table)
    println "table", tl.width, tl.height
    each c in cells:
        l := t.Layout(c)
        println "cell", c, l.x, l.y, l.width, l.height
    end
    cl := t.Layout(caption)
    println "caption", cl.y, cl.width
    mut fixed := t.GetStyle(table)
    fixed.table_layout = cssbox.TableLayout.Fixed
    fixed.width = cssbox.Px(300.0)
    fixed.border_collapse = cssbox.BorderCollapse.Collapse
    t.SetStyle(table, fixed)
    t.Compute(table, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    each c in cells:
        l := t.Layout(c)
        println "fixed", c, l.x, l.width, l.border.left
    end
    mut first := t.GetStyle(cells[0])
    first.border_kinds = cssbox.BorderKinds { top = cssbox.BorderKind.Hidden, right = cssbox.BorderKind.Double, bottom = cssbox.BorderKind.Solid, left = cssbox.BorderKind.Dashed }
    t.SetStyle(cells[0], first)
    t.Compute(table, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    mut segs := t.CollapsedBorders(table)
    defer segs.free()
    println "segments", segs.len()
    s0 := segs[0]
    println "segment", s0.x, s0.y, s0.width, s0.height, s0.node, s0.side, s0.vertical
end
Outputtable 272 74 cell 4 0 0 38 52 cell 6 42 0 88 20 cell 8 134 0 124 20 cell 11 42 0 88 30 cell 13 134 0 128 30 caption 58 272 fixed 4 0 75 1 fixed 6 75 75 1 fixed 8 150 75 1 fixed 11 75 75 1 fixed 13 150 150 1 segments 19 segment 300 18 2 30 13 1 true

BorderCollapse

enum BorderCollapse
The CSS border-collapse property.
NameTypeDescription
SeparateEvery cell keeps its own border and border-spacing separates them.
CollapseNeighbouring cells, rows, row groups, columns, column groups and the table share one border per edge, chosen by the CSS conflict rules (see BorderKind); each cell's layout border is half of the winning width on each of its sides, and spacing is zero.

The example for this item is shown with the ones above: gui-cssbox-table.salam

CaptionSide

enum CaptionSide
The CSS caption-side property.
NameTypeDescription
TopThe caption is placed above the table.
BottomThe caption is placed below the table.

The example for this item is shown with the ones above: gui-cssbox-table.salam

VerticalAlign

enum VerticalAlign
The CSS vertical-align property as it applies to table cells.
NameTypeDescription
BaselineThe first line of the cell lines up with the other baseline cells of the row.
TopThe content sits at the top of the cell.
MiddleThe content is centred vertically in the cell.
BottomThe content sits at the bottom of the cell.

The example for this item is shown with the ones above: gui-cssbox-table.salam

Position

enum Position
The CSS position property.
NameTypeDescription
StaticNormal flow; insets are ignored.
RelativeNormal flow, then moved by its insets; it is a containing block for absolute descendants.
AbsoluteOut of flow, placed against the nearest positioned ancestor, or the viewport when there is none.
FixedOut of flow, placed against the viewport given to Compute.
StickyLaid out like Relative for now; scrolling offsets are not applied.

The example for this item is shown with the ones above: gui-cssbox-block.salam

BoxSizing

enum BoxSizing
Whether width and height include padding and border.
NameTypeDescription
ContentBoxSizes are for the content box (the CSS default).
BorderBoxSizes include padding and border.

The example for this item is shown with the ones above: gui-cssbox-block.salam

Overflow

enum Overflow
The CSS overflow value for one axis.
NameTypeDescription
VisibleContent may overflow and counts toward the parent's scrollable overflow.
HiddenA scroll container without a scrollbar; its automatic minimum size is zero.
ClipContent is clipped, but the box is not a scroll container.
ScrollA scroll container that always reserves scrollbar_width for its scrollbar.
AutoA scroll container; layout treats it like Hidden and reserves no gutter.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Direction

enum Direction
The CSS direction property.
NameTypeDescription
LtrLeft to right.
RtlRight to left: block children start on the right, flex rows run from the right, and start and end swap.
gui-cssbox-flex.salam
import gui.cssbox

func label(chars: int, mi: cssbox.MeasureInput): cssbox.MeasureOutput:
    full := (chars as f64) * 8.0
    if mi.available_width.kind == cssbox.SpaceKind.MinContent:
        ret cssbox.MeasuredWithBaseline(40.0, 32.0, 12.0)
    end
    if mi.available_width.kind == cssbox.SpaceKind.Definite and mi.available_width.value < full:
        ret cssbox.MeasuredWithBaseline(mi.available_width.value, 32.0, 12.0)
    end
    ret cssbox.MeasuredWithBaseline(full, 16.0, 12.0)
end

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    bar := t.AddNode(cssbox.Style { display = cssbox.Display.Flex, width = cssbox.Px(400.0), padding = cssbox.AllSides(cssbox.Px(8.0)), column_gap = cssbox.Px(8.0), align_items = cssbox.Align.Baseline, justify_content = cssbox.Align.SpaceBetween })
    title := t.AddNode(cssbox.Style { flex_grow = 1.0, flex_shrink = 1.0, flex_basis = cssbox.Auto() })
    t.SetMeasure(title, (mi: cssbox.MeasureInput): ret label(30, mi) end)
    button := t.AddNode(cssbox.Style { padding = cssbox.SidesOf(cssbox.Px(4.0), cssbox.Px(12.0), cssbox.Px(4.0), cssbox.Px(12.0)), border = cssbox.AllSides(cssbox.Px(1.0)) })
    t.SetMeasure(button, (mi: cssbox.MeasureInput): ret label(4, mi) end)
    badge := t.AddNode(cssbox.Style { width = cssbox.Px(24.0), height = cssbox.Px(24.0), align_self = cssbox.Align.Center, order = -1 })
    t.AddChild(bar, title)
    t.AddChild(bar, button)
    t.AddChild(bar, badge)
    t.Compute(bar, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    each id in [bar, badge, title, button]:
        l := t.Layout(id)
        println id, l.x, l.y, l.width, l.height
    end
    rtl := t.GetStyle(bar)
    mut s := rtl
    s.direction = cssbox.Direction.Rtl
    t.SetStyle(bar, s)
    t.Compute(bar, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "rtl badge x", t.Layout(badge).x, "title x", t.Layout(title).x
end
Output0 0 0 416 42 3 8 9 24 24 1 40 13 294 16 2 342 8 66 26 rtl badge x 384 title x 82

FlexDirection

enum FlexDirection
The CSS flex-direction property.
NameTypeDescription
RowItems run along the inline axis.
RowReverseA row in reverse order.
ColumnItems run top to bottom.
ColumnReverseA column in reverse order.

The example for this item is shown with the ones above: gui-cssbox-flex.salam

FlexWrap

enum FlexWrap
The CSS flex-wrap property.
NameTypeDescription
NoWrapAll items stay on one line.
WrapItems move to a new line when the line is full.
WrapReverseLike Wrap, with lines stacked in the reverse cross direction.

The example for this item is shown with the ones above: gui-cssbox-flex.salam

Align

enum Align
One type for justify-content, align-items, align-self and align-content.
NameTypeDescription
AutoFor align_self: use the container's align_items.
NormalThe CSS normal value: stretch for items, flex-start for content.
StartThe start edge of the axis in the writing direction.
EndThe end edge of the axis in the writing direction.
FlexStartThe start edge of the flex direction.
FlexEndThe end edge of the flex direction.
CenterCentred.
BaselineItems share the first baseline reported by their measure callbacks.
StretchFill the line or the container.
SpaceBetweenFree space goes between items; falls back to the start when there is none.
SpaceAroundFree space goes around each item; falls back to the start when negative.
SpaceEvenlyEqual gaps between and around items; falls back to the start when negative.

The example for this item is shown with the ones above: gui-cssbox-flex.salam

SpaceKind

enum SpaceKind
The kind of space a box is laid out in.
NameTypeDescription
DefiniteA known number of pixels.
MinContentLay out as narrow as possible, to find the min-content size.
MaxContentLay out with unlimited room, to find the max-content size.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

BorderKind

enum BorderKind
The line style of one border side, used only to resolve collapsed table borders (CSS 2.1 section 17.6.2.1): hidden wins, then the wider border, then the style in the order double, solid, dashed, dotted, ridge, outset, groove, inset, then the box (cell, row, row group, column, column group, table), then the start or top one.
NameTypeDescription
Solidsolid; the default, so a style that sets only widths behaves as before.
Nonenone: the side takes no part in collapsing.
Hiddenhidden: wins every conflict and removes the shared border.
Insetinset; ranked as ridge when collapsing, as CSS says.
Groovegroove.
Outsetoutset; ranked as groove when collapsing.
Ridgeridge.
Dotteddotted.
Dasheddashed.
Doubledouble: the highest ranked style.

The example for this item is shown with the ones above: gui-cssbox-table.salam

Length

struct Length
A CSS length that is already resolved to pixels or a percentage; em, rem, vw and vh are converted by the caller.
NameTypeDescription
kindLengthKindWhat the length is.
valuef64 = 0Pixels for Px, the CSS percentage for Percent, unused otherwise.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Sides

struct Sides
Four lengths for margin, padding, border or inset.
NameTypeDescription
topLengthThe top side.
rightLengthThe right side.
bottomLengthThe bottom side.
leftLengthThe left side.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Edges

struct Edges
Four resolved pixel values, used in a BoxLayout.
NameTypeDescription
topf64 = 0The top edge in pixels.
rightf64 = 0The right edge in pixels.
bottomf64 = 0The bottom edge in pixels.
leftf64 = 0The left edge in pixels.

The example for this item is shown with the ones above: gui-cssbox-block.salam

BorderKinds

struct BorderKinds
The line style of each side of a box.
NameTypeDescription
topBorderKindThe top side.
rightBorderKindThe right side.
bottomBorderKindThe bottom side.
leftBorderKindThe left side.

The example for this item is shown with the ones above: gui-cssbox-table.salam

CollapsedBorder

struct CollapsedBorder
One painted segment of a collapsed table border: a rectangle relative to the table's border box and the side of the box whose border won it.
NameTypeDescription
xf64 = 0Left edge of the segment.
yf64 = 0Top edge of the segment.
widthf64 = 0Width of the segment.
heightf64 = 0Height of the segment.
nodeint = -1The node (cell, row, row group, column, column group or table) whose border won.
sideint = 0Which side of that node won: 0 top, 1 right, 2 bottom, 3 left.
verticalbool = falseWhether the segment runs along a column line.

The example for this item is shown with the ones above: gui-cssbox-table.salam

Style

struct Style
The computed style of one box. Every field starts at the CSS initial value, so a literal only names what differs.
NameTypeDescription
displayDisplaydisplay, default block.
positionPositionposition, default static.
insetSidestop, right, bottom and left, default auto.
box_sizingBoxSizingbox-sizing, default content-box.
widthLengthwidth, default auto.
heightLengthheight, default auto.
min_widthLengthmin-width; auto means no minimum except the flex automatic minimum.
min_heightLengthmin-height.
max_widthLengthmax-width; auto means none.
max_heightLengthmax-height; auto means none.
aspect_ratiof64 = 0aspect-ratio as width divided by height; zero means none.
marginSidesmargin; auto margins centre blocks and absorb free space in flex.
paddingSidespadding.
borderSidesBorder widths; percentages resolve against the containing block width.
overflow_xOverflowoverflow-x.
overflow_yOverflowoverflow-y.
scrollbar_widthf64 = 0Width reserved for a scrollbar when overflow is Scroll, default zero.
directionDirectiondirection, default left to right.
flex_directionFlexDirectionflex-direction.
flex_wrapFlexWrapflex-wrap.
flex_growf64 = 0flex-grow, default 0.
flex_shrinkf64 = 1flex-shrink, default 1.
flex_basisLengthflex-basis, default auto.
justify_contentAlignjustify-content for flex containers.
align_itemsAlignalign-items.
align_selfAlignalign-self; also aligns an absolute box between two vertical insets.
align_contentAlignalign-content for wrapped flex lines and for block containers.
row_gapLengthrow-gap.
column_gapLengthcolumn-gap.
orderint = 0order of a flex item.
justify_itemsAlignjustify-items of a grid container: the default inline alignment of its items.
justify_selfAlignjustify-self of a grid item, or of an absolutely positioned box.
grid_template_columnsstr = ""grid-template-columns in CSS syntax, such as "100px [main] repeat(auto-fill, minmax(4em, 1fr))". Lengths must already be pixels or percentages. Empty means none.
grid_template_rowsstr = ""grid-template-rows in CSS syntax. Empty means none.
grid_template_areasstr = ""grid-template-areas as quoted rows, such as "'head head' 'side main'". Each area makes the lines name-start and name-end.
grid_auto_columnsstr = ""grid-auto-columns: the sizes of implicit columns, cycled. Empty means auto.
grid_auto_rowsstr = ""grid-auto-rows: the sizes of implicit rows, cycled. Empty means auto.
grid_auto_flowGridAutoFlowgrid-auto-flow of a grid container.
grid_row_startstr = ""grid-row-start of a grid item: a line number, a negative number from the end, span 2, a line or area name, or empty for auto.
grid_row_endstr = ""grid-row-end of a grid item.
grid_column_startstr = ""grid-column-start of a grid item.
grid_column_endstr = ""grid-column-end of a grid item.
table_layoutTableLayouttable-layout of a table.
border_collapseBorderCollapseborder-collapse of a table.
border_kindsBorderKindsThe line style of each border side, for resolving collapsed table borders.
border_spacing_xf64 = 0The horizontal border-spacing of a table, in pixels.
border_spacing_yf64 = 0The vertical border-spacing of a table, in pixels.
caption_sideCaptionSidecaption-side of a table caption.
vertical_alignVerticalAlignvertical-align of a table cell.
col_spanint = 1How many columns a cell spans (the HTML colspan), or how many columns a column element stands for (the HTML span).
row_spanint = 1How many rows a cell spans (the HTML rowspan); 0 means to the end of its row group.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Space

struct Space
The space a tree is laid out in, given to Compute and to measure callbacks.
NameTypeDescription
kindSpaceKindDefinite, min-content or max-content.
valuef64 = 0The pixels when the kind is definite.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

MeasureInput

struct MeasureInput
What a measure callback is asked: the node, any sizes already fixed, and the space available. Sizes are for the content box.
NameTypeDescription
nodeint = 0The node being measured.
known_widthf64 = 0The fixed width when has_known_width is true.
has_known_widthbool = falseTrue when the width is already decided and the callback only needs the height.
known_heightf64 = 0The fixed height when has_known_height is true.
has_known_heightbool = falseTrue when the height is already decided.
available_widthSpaceThe width available: definite, min-content or max-content.
available_heightSpaceThe height available.

The example for this item is shown with the ones above: gui-cssbox-flex.salam

MeasureOutput

struct MeasureOutput
What a measure callback returns: the content size and an optional first baseline.
NameTypeDescription
widthf64 = 0The content width.
heightf64 = 0The content height.
baselinef64 = 0The first baseline, measured from the top of the content box.
has_baselinebool = falseTrue when baseline is set.

The example for this item is shown with the ones above: gui-cssbox-flex.salam

BoxLayout

struct BoxLayout
The result for one box, rounded to whole pixels the way a browser snaps them.
NameTypeDescription
xf64 = 0Border box left edge relative to the parent's border box.
yf64 = 0Border box top edge relative to the parent's border box.
abs_xf64 = 0Left edge relative to the root.
abs_yf64 = 0Top edge relative to the root.
widthf64 = 0Border box width.
heightf64 = 0Border box height.
content_xf64 = 0Content box left edge inside the border box.
content_yf64 = 0Content box top edge inside the border box.
content_widthf64 = 0Content box width, after padding, border and scrollbar.
content_heightf64 = 0Content box height.
paddingEdgesResolved padding.
borderEdgesResolved border widths.
marginEdgesResolved margins, with auto margins filled in.
scrollbar_widthf64 = 0Width taken by a vertical scrollbar.
scrollbar_heightf64 = 0Height taken by a horizontal scrollbar.
scroll_widthf64 = 0Width of the scrollable overflow, at least the padding box width.
scroll_heightf64 = 0Height of the scrollable overflow.
baselinef64 = 0First baseline from the top of the border box.
has_baselinebool = falseTrue when the box has a baseline.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Tree

struct Tree
A tree of boxes addressed by integer ids, with a layout cache per node.
gui-cssbox-Tree.salam
import gui.cssbox

func main:
    mut t := cssbox.Tree {}
    defer t.Free()
    list := t.AddNode(cssbox.Style { display = cssbox.Display.Flex, flex_direction = cssbox.FlexDirection.Column, row_gap = cssbox.Px(4.0) })
    mut items := Vector {} as Vector<int>
    defer items.free()
    repeat 3 in i:
        item := t.AddNode(cssbox.Style { height = cssbox.Px(20.0) })
        t.SetMeasure(item, (_mi: cssbox.MeasureInput): ret cssbox.Measured(100.0 + (i as f64) * 10.0, 20.0) end)
        items.push(item)
    end
    t.SetChildren(list, items)
    println "nodes", t.NodeCount(), "children", t.ChildCount(list), "second", t.Child(list, 1), "parent", t.Parent(items.get(0))
    t.Compute(list, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "size", t.Layout(list).width, t.Layout(list).height, "calls", t.LayoutCalls(), "measures", t.MeasureCalls()
    t.ResetCounters()
    t.Compute(list, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "again", t.LayoutCalls(), "dirty", t.IsDirty(list)
    t.SetMeasure(items.get(2), (_mi: cssbox.MeasureInput): ret cssbox.Measured(200.0, 20.0) end)
    println "dirty", t.IsDirty(list), t.IsDirty(items.get(0))
    t.Compute(list, cssbox.MaxContentSpace(), cssbox.MaxContentSpace())
    println "width", t.Layout(list).width, "calls", t.LayoutCalls()
    t.ClearMeasure(items.get(2))
    t.MarkDirty(items.get(1))
    t.SetRounding(false)
    t.Compute(list, cssbox.MinContentSpace(), cssbox.MaxContentSpace())
    println "width", t.Layout(list).width
end
Outputnodes 4 children 3 second 2 parent 0 size 120 68 calls 10 measures 9 again 0 dirty false dirty true false width 200 calls 6 width 110

AddNode

func AddNode(style: Style): int
Adds a box with a style and returns its id. Ids start at zero.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

NodeCount

func NodeCount(): int
The number of nodes.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

SetChildren

func SetChildren(node: int, children: Vector<int>)
Replaces a node's children with the ids in the vector, in order.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

AddChild

func AddChild(node: int, child: int)
Appends one child.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

ChildCount

func ChildCount(node: int): int
The number of children of a node.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

Child

func Child(node: int, index: int): int
The id of a child by index, or -1.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

Parent

func Parent(node: int): int
The parent id, or -1 for a root.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

GetStyle

func GetStyle(node: int): Style
A copy of a node's style.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

SetStyle

func SetStyle(node: int, style: Style)
Replaces a node's style and marks it dirty. Setting a style equal to the current one changes nothing and keeps the cached layout.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

SetMeasure

func SetMeasure(node: int, measure: func(MeasureInput): MeasureOutput)
Sets the callback that measures a leaf, such as text or an image.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

ClearMeasure

func ClearMeasure(node: int)
Removes the measure callback; the leaf then has no content size.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

MarkDirty

func MarkDirty(node: int)
Drops the cached layout of a node and its ancestors; call it when the measured content changes.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

IsDirty

func IsDirty(node: int): bool
True when the node has no cached result.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

SetRounding

func SetRounding(enabled: bool)
Turns pixel snapping of the results on or off (on by default).

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

MeasureCalls

func MeasureCalls(): int
How many times measure callbacks ran since the last reset.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

LayoutCalls

func LayoutCalls(): int
How many node layouts were computed rather than taken from the cache.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

ResetCounters

func ResetCounters()
Sets both counters to zero.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

Compute

func Compute(root: int, width: Space, height: Space)
Lays out the tree under a root in the given viewport. Only dirty nodes and nodes whose available space changed are computed again.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

Layout

func Layout(node: int): BoxLayout
The result for one node after Compute.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

CollapsedBorders

func CollapsedBorders(table: int): Vector<CollapsedBorder>
The segments to paint for a table with collapsed borders after Compute, one per cell edge with a visible winner, with the joints given to the winning border the way Chromium paints them. The caller frees the vector; it is empty for a table with separate borders.

The example for this item is shown with the ones above: gui-cssbox-table.salam

Free

func Free()
Releases the tree.

The example for this item is shown with the ones above: gui-cssbox-Tree.salam

Functions

Px

func Px(v: f64): Length
A length in pixels.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Percent

func Percent(v: f64): Length
A percentage length; Percent(50.0) is 50%.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Auto

func Auto(): Length
The auto length.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

MinContent

func MinContent(): Length
The min-content keyword.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

MaxContent

func MaxContent(): Length
The max-content keyword.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

FitContent

func FitContent(): Length
The fit-content keyword.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Stretch

func Stretch(): Length
The stretch keyword.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

AllSides

func AllSides(v: Length): Sides
The same length on all four sides.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

SidesOf

func SidesOf(top: Length, right: Length, bottom: Length, left: Length): Sides
Four lengths in CSS order: top, right, bottom, left.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Definite

func Definite(v: f64): Space
A definite space in pixels.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

MinContentSpace

func MinContentSpace(): Space
The min-content space.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

MaxContentSpace

func MaxContentSpace(): Space
The max-content space.

The example for this item is shown with the ones above: gui-cssbox-Style.salam

Measured

func Measured(width: f64, height: f64): MeasureOutput
A measure result without a baseline.

The example for this item is shown with the ones above: gui-cssbox-flex.salam

MeasuredWithBaseline

func MeasuredWithBaseline(width: f64, height: f64, baseline: f64): MeasureOutput
A measure result with a first baseline.

The example for this item is shown with the ones above: gui-cssbox-flex.salam

IsTrackList

func IsTrackList(text: str): bool
True when the text is a valid grid-template-columns or grid-template-rows value.

The example for this item is shown with the ones above: gui-cssbox-grid.salam

IsTrackSizes

func IsTrackSizes(text: str): bool
True when the text is a valid grid-auto-columns or grid-auto-rows value.

The example for this item is shown with the ones above: gui-cssbox-grid.salam

IsGridLine

func IsGridLine(text: str): bool
True when the text is a valid grid-row-start style placement.

The example for this item is shown with the ones above: gui-cssbox-grid.salam

IsGridAreas

func IsGridAreas(text: str): bool
True when the text is a valid grid-template-areas value with rectangular areas.

The example for this item is shown with the ones above: gui-cssbox-grid.salam