Widget tree, box model and layout

ManyUI.WidgetNodeType
mutable struct WidgetNode

Per-widget state, held by COMPOSITION: every widget HAS a node. No abstract-field inheritance anywhere.

parent/children are Widget-typed by necessity – a heterogeneous tree is the point – and every hot path recovers inference through node(w), whose return type is concrete.

Fields

  • id::Symbol

  • classes::Set{Symbol}

  • type_name::Symbol

  • parent::Union{Nothing, Widget}: Parent widget, or nothing at the root.

  • children::Vector{Widget}

  • inline_style::Style: Author-set style; highest specificity.

  • inline_box::BoxPatch: Author-set box overrides; highest specificity.

  • computed_style::Style: Post-cascade style.

  • box::BoxStyle: Post-cascade layout inputs.

  • layout::LayoutBox: Post-layout boxes, in ABSOLUTE coordinates.

  • scroll::Offset: Cells this node's CHILDREN are shifted by at paint time. ORIGIN is unscrolled.

    Offset(3, 0) means "scrolled right by 3", so a child paints three cells to the LEFT. Shifts the CHILDREN and NEVER this node's own render frame – that is what keeps size(buf) inside render! invariant under scroll. A widget with no children that scrolls its own content reads this field itself.

    NEVER an input to compute_layout: the LayoutMap is invariant under scrolling, which is why a wheel tick is a repaint and not a relayout.

  • dirty::UInt8: What is stale about this node.

  • visible::Bool: False hides the node and its subtree.

  • focusable::Bool: True makes the node part of the tab order.

  • focused::Bool: True when THIS node holds focus. What :focus reads.

    Maintained by focus!, not by a widget's own focused cell: those are per-widget appearance, this is the one authoritative answer the cascade asks of every node.

  • focus_within::Bool: True when a DESCENDANT holds focus. What :focus-within reads.

    A stored flag and not a walk of the subtree. The cascade asks this of every node against every rule, so answering it by descending would make one focus change cost the square of the tree; focus! maintains it along the ONE chain from the focused node to the root.

  • app::Any: The owning App, once mounted.

  • on_focus::Union{Nothing, Function}: Optional callback when focus is gained.

  • on_blur::Union{Nothing, Function}: Optional callback when focus is lost.

source
ManyUI.WidgetNodeType
WidgetNode(
    id::Symbol,
    classes::Set{Symbol},
    type_name::Symbol,
    parent::Union{Nothing, Widget},
    children::Vector{Widget},
    inline_style::Style,
    inline_box::BoxPatch,
    computed_style::Style,
    box::BoxStyle,
    layout::LayoutBox,
    dirty::UInt8,
    visible::Bool,
    focusable::Bool,
    app
) -> WidgetNode
WidgetNode(
    id::Symbol,
    classes::Set{Symbol},
    type_name::Symbol,
    parent::Union{Nothing, Widget},
    children::Vector{Widget},
    inline_style::Style,
    inline_box::BoxPatch,
    computed_style::Style,
    box::BoxStyle,
    layout::LayoutBox,
    dirty::UInt8,
    visible::Bool,
    focusable::Bool,
    app,
    on_focus::Union{Nothing, Function}
) -> WidgetNode
WidgetNode(
    id::Symbol,
    classes::Set{Symbol},
    type_name::Symbol,
    parent::Union{Nothing, Widget},
    children::Vector{Widget},
    inline_style::Style,
    inline_box::BoxPatch,
    computed_style::Style,
    box::BoxStyle,
    layout::LayoutBox,
    dirty::UInt8,
    visible::Bool,
    focusable::Bool,
    app,
    on_focus::Union{Nothing, Function},
    on_blur::Union{Nothing, Function}
) -> WidgetNode

The pre-scroll POSITIONAL form: scroll defaults to ORIGIN.

Exists because adding a field silently invalidates the auto-generated positional constructor, and call sites in test_paint.jl and css_tests.jl depend on the 14-argument shape. Keep the argument order in lockstep with the struct.

source
ManyUI.WidgetNodeMethod
WidgetNode(
    id::Symbol,
    classes::Set{Symbol},
    type_name::Symbol,
    parent::Union{Nothing, Widget},
    children::Vector{Widget},
    inline_style::Style,
    inline_box::BoxPatch,
    computed_style::Style,
    box::BoxStyle,
    layout::LayoutBox,
    scroll::Offset,
    dirty::UInt8,
    visible::Bool,
    focusable::Bool,
    app
) -> WidgetNode

The positional form that names scroll explicitly, retained for compatibility after adding focus callbacks to WidgetNode.

source
ManyUI.WidgetNodeMethod
WidgetNode(
;
    id,
    classes,
    type_name,
    visible,
    focusable,
    on_focus,
    on_blur
) -> WidgetNode

A fresh node: no parent, no children, nothing cascaded, nothing laid out, everything dirty.

source
Base.parentMethod
parent(w::Widget) -> Union{Nothing, Widget}

Parent of w, or nothing at the root. Pure.

source
ManyUI._attach!Method
_attach!(p::Widget, c::Widget)

Link c (and its whole subtree) into p's tree: set the parent, bind the app, fire on_mount! in pre-order, then flag the new subtree for a cascade and a layout.

Internal: the three public entry points (mount!, mount! variadic and insert_child!) all funnel through here so the lifecycle is defined exactly once.

source
ManyUI._breadcrumb!Method
_breadcrumb!(w::Widget)

Drop the Dirty.SUBTREE breadcrumb on every ancestor of w, and on nothing else.

Internal. The breadcrumb is a routing hint, not dirt: is_dirty(a) stays false for an ancestor that only carries it, which is exactly what makes E1 ("only that specific widget and its affected descendants") literally true.

source
ManyUI._find_firstMethod
_find_first(
    w::Widget,
    cs::Vector{String}
) -> Union{Nothing, Widget}

Depth-first search for the first match, exiting as soon as it is found. Pure.

source
ManyUI._focus_callback!Method
_focus_callback!(w::Widget)

Lifecycle hook: w has just gained focus. Default: reveal!(w), so TAB-ing to a widget inside a scrolling viewport scrolls it into view with no wiring at the call site.

OVERRIDING THIS REPLACES IT: a widget type with its own on_focus! MUST call reveal!(w) itself.

source
ManyUI._main_axis_is_definiteMethod
_main_axis_is_definite(bs::BoxStyle) -> Bool

True when bs's main-axis size resolves without measuring its children.

CELLS and PERCENT are definite; AUTO sizes to content and FRACTION is a share of free space, so both genuinely depend on the subtree. Pure.

source
ManyUI._main_axis_lengthMethod
_main_axis_length(bs::BoxStyle) -> Length

The Length governing bs's own main-axis size: width for a ROW, height for a COLUMN. Pure.

source
ManyUI._match_anyMethod
_match_any(w::Widget, cs::Vector{String}) -> Bool

True when w matches any compound in the parsed selector list. Pure.

source
ManyUI._match_compoundMethod
_match_compound(w::Widget, sel::AbstractString) -> Bool

True when w satisfies every token of the validated compound selector sel. Pure.

source
ManyUI._parse_selectorMethod
_parse_selector(sel::AbstractString) -> Vector{String}

Split a comma-separated selector list into validated compound selectors. Throws ArgumentError on anything this layer cannot express. Pure.

source
ManyUI.add_class!Method
add_class!(w::Widget, c::Symbol) -> Widget

Add a class and mark Dirty.STYLE. Adding a class w already carries is a no-op: nothing cascades differently, so nothing is flagged. Returns w.

source
ManyUI.ancestorsMethod
ancestors(w::Widget) -> Vector{Widget}

w's ancestors, from its parent up to the root. Pure.

source
ManyUI.appMethod
app(w::Widget) -> Any

The App this widget is mounted on, or nothing. Walks the tree.

source
ManyUI.border_titleMethod
border_title(_::Widget) -> RichText

The caption drawn ON w's top border, or empty for none.

A SEAM, not a field, for the same reason the scrollable one is three functions: a title belongs to the handful of widgets that frame something, and a slot on every WidgetNode would charge the other thousands for it. Override it and any widget gains a caption.

It is a seam AT ALL because a widget cannot draw this itself. _paint_node! hands render! the CONTENT box, and the border is outside it, so a titled box drawing its own caption would have to reserve a content row – which puts the caption inside the frame rather than on it. The paint pass asks instead.

Empty by default, and the default returns a SHARED constant: this is asked of every node on every frame.

source
ManyUI.border_title_alignMethod
border_title_align(_::Widget) -> ManyUI.Align.T

Where border_title(w) sits along the top edge: Align.START, Align.CENTER or Align.END. Align.START by default.

source
ManyUI.boxMethod
box(w::Widget) -> BoxStyle

Post-cascade layout inputs of w. Pure.

source
ManyUI.childrenMethod
children(w::Widget) -> Tuple{Widget}

Children of w, in paint and tab order. Pure.

source
ManyUI.clamp_scrollMethod
clamp_scroll(
    pos::Int64,
    viewport::Int64,
    content::Int64
) -> Int64

pos clamped to the range a scroll position may legally take: 0 when the content fits, 0:(content - viewport) otherwise.

THE definition of "clamped to the content extent". Pure, total, and shared by every scrolling widget so they cannot disagree – with no widget-to-widget dependency. Pure.

source
ManyUI.descendantsMethod
descendants(w::Widget) -> Vector{Widget}

w's descendants in pre-order, excluding w. Pure.

source
ManyUI.dirty_rootMethod
dirty_root(root::Widget) -> Union{Nothing, Widget}

The HIGHEST node carrying real Dirty.LAYOUT – the root of the minimal relayout. nothing when the tree is layout-clean.

Found by descending SUBTREE breadcrumbs, so it is O(depth), not O(n). When two independent branches are layout-dirty the descent stops at their common ancestor – the shallowest node a correct relayout can start from – even though that node carries only the breadcrumb. Pure.

source
ManyUI.escalate_auto!Method
escalate_auto!(w::Widget)

Walk UP from w. An ancestor whose main-axis size is AUTO genuinely depends on its children, so its SUBTREE breadcrumb is PROMOTED to real LAYOUT.

Stops at the first ancestor with a definite (CELLS/PERCENT) size on the relevant axis – that ancestor keeps only SUBTREE, because its own box cannot move, and the walk goes no higher. This is what makes relayout! sublinear.

A promoted ancestor gets LAYOUT but NOT PAINT: whether its box actually changed is apply_layout!'s call to make, not ours.

source
ManyUI.has_dirtyMethod
has_dirty(m::UInt8, k::ManyUI.Dirty.T) -> Bool

True when k is set in m. Pure.

source
ManyUI.is_dirtyMethod
is_dirty(w::Widget, kind::ManyUI.Dirty.T) -> Bool

True when kind is set on w. Pure.

source
ManyUI.is_dirtyMethod
is_dirty(w::Widget) -> Bool

True when any of PAINT, LAYOUT or STYLE is set on w. Pure.

Dirty.SUBTREE alone is deliberately NOT dirt.

source
ManyUI.mark!Function
mark!(w::Widget)
mark!(w::Widget, kind::ManyUI.Dirty.T)

E1. The full propagation – THE function Reactive and the widget API call. Rules, normative:

PAINT  on w -> w ONLY. Never ancestors, never descendants.
LAYOUT on w -> LAYOUT|PAINT on w AND ALL DESCENDANTS (their
               absolute regions derive from w's, so they are
               genuinely stale -- this is "and its affected
               descendants" in the EARS text), then
               `escalate_auto!(w)`.
STYLE  on w -> STYLE on w and all descendants (inheritance plus
               descendant selectors).

In EVERY case each ANCESTOR receives Dirty.SUBTREE and NOTHING ELSE. Marking an ancestor LAYOUT would make E1 false as written.

SIBLINGS ARE NEVER MARKED.

source
ManyUI.mark_dirty!Function
mark_dirty!(w::Widget)
mark_dirty!(w::Widget, kind::ManyUI.Dirty.T)

E1. Set kind on w ONLY. Touches no ancestor and no descendant.

source
ManyUI.mount!Method
mount!(p::Widget, cs::Widget...) -> Scrollpane

Mount several children in order. Returns p.

source
ManyUI.mount!Method
mount!(p::Widget, c::Widget) -> Scrollpane

Append c to p, bind it to p's app, and call on_mount!. Returns p.

Throws ArgumentError when c already has a parent: a widget lives in exactly one tree, and silently re-parenting it would leave the old parent's children stale.

source
ManyUI.nodeMethod
node(w::Widget) -> WidgetNode

THE one required method per widget type. The default duck-types on the field name; override only for exotic widgets.

source
ManyUI.on_blur!Method
on_blur!(w::Widget)

Lifecycle hook: w has just lost focus. Default no-op.

source
ManyUI.on_mount!Method
on_mount!(w::Widget)

Lifecycle hook: w has just been added to a tree. Default no-op.

source
ManyUI.paint_offsetMethod
paint_offset(w::Widget) -> Offset

The accumulated shift w is PAINTED with: the sum of every STRICT ancestor's scroll, negated. ORIGIN outside any scrolled subtree.

A node's OWN scroll is deliberately not included – it shifts that node's children, not itself. O(depth), and never on the frame path: _paint_node! threads the same value down for free. Pure.

source
ManyUI.painted_regionMethod
painted_region(w::Widget) -> Region

Where w's border box is actually PAINTED: region(w) shifted by paint_offset(w). Identical to region(w) outside a scrolled subtree.

THIS, not region(w), is what a hit test must compare a pointer against: layout computes ABSOLUTE regions and knows nothing about scrolling, so region(w) is where w WOULD be if nothing had scrolled. Pure.

source
ManyUI.queryMethod
query(root::Widget, sel::AbstractString) -> Vector{Widget}

Every widget in root's tree matching the CSS selector sel, in pre-order. Pure.

Supports *, #id, .class, Type, compounds of those (Button.primary#ok) and comma-separated lists. Combinators live in css.jl; query is the dependency-free subset this layer can afford.

source
ManyUI.query_oneMethod
query_one(
    root::Widget,
    sel::AbstractString
) -> Union{Nothing, Widget}

The first widget matching sel, or nothing. Pure.

source
ManyUI.query_oneMethod
query_one(
    root::Widget,
    sel::AbstractString,
    _::Type{T<:Widget}
) -> Widget

The first widget matching sel, asserted to be a T. Throws when absent or of the wrong type. Pure.

source
ManyUI.remove_class!Method
remove_class!(w::Widget, c::Symbol) -> Widget

Remove a class and mark Dirty.STYLE. Removing an absent class is a no-op. Returns w.

source
ManyUI.replace_child!Method
replace_child!(
    p::Widget,
    old::Widget,
    new::Widget
) -> Widget

Swap old for new in place. Returns p.

source
ManyUI.reveal!Method
reveal!(w::Widget)

Ask every ancestor of w, NEAREST FIRST, to bring w into view.

Nearest-first is load-bearing: an inner pane must finish moving before an outer pane measures where w ended up. Never on the frame path – focus changes are events, not frames.

source
ManyUI.reveal_child!Method
reveal_child!(_::Widget, _::Widget)

Hook: bring d, a descendant of w, into w's visible area. Default no-op; a scrolling viewport overrides it.

The core therefore knows that a node MAY be able to reveal a descendant, and nothing whatever about how – there is no isa Scrollpane anywhere below layer 7.

source
ManyUI.root_ofMethod
root_of(w::Widget) -> Widget

The root of w's tree; w itself when it has no parent. Pure.

source
ManyUI.scroll_into_viewMethod
scroll_into_view(
    pos::Int64,
    viewport::Int64,
    lo::Int64,
    hi::Int64
) -> Int64

The scroll position NEAREST pos that puts the 0-based inclusive extent lo:hi inside a viewport-cell window starting at pos. Pure.

MINIMAL MOVEMENT: already-visible content does not move. When the extent is LARGER than the viewport its START wins – showing the top of an over-long item beats showing its bottom. Returns pos unchanged for a non-positive viewport (not laid out yet).

The asymmetry is deliberate and clamp cannot express it: clamp with an inverted lo/hi silently picks the wrong edge.

source
ManyUI.set_scroll!Method
set_scroll!(w::Widget, o::Offset) -> Bool

Set the shift applied to w's CHILDREN at paint time. Returns true iff it changed.

Clamped at ZERO per axis only. The UPPER bound needs the content extent, which this layer cannot see – scroll_to! in widgets/scroll.jl owns it. An over-scroll is a UX bug (blank cells), never corruption.

Marks Dirty.PAINT and NOTHING ELSE, and this is the whole "a wheel tick does not re-run layout" claim: mark! with PAINT sets PAINT on w alone and drops only SUBTREE breadcrumbs on ancestors; it calls escalate_auto! ONLY for Dirty.LAYOUT. dirty_root then finds no real LAYOUT and returns nothing, so relayout! returns on its first line – and compute_layout never reads this field anyway.

mark!(w, Dirty.PAINT) and NOT mark_subtree_dirty!: the normative rule above is "PAINT on w -> w ONLY", and frame! repaints the whole tree unconditionally in any case.

source
ManyUI.set_visible!Method
set_visible!(w::Widget, v::Bool)

Show or hide w and mark Dirty.LAYOUT. Setting the value w already has is a no-op.

source
ManyUI.toggle_class!Method
toggle_class!(w::Widget, c::Symbol) -> Bool

Toggle a class and mark Dirty.STYLE. Returns the new membership.

source
ManyUI.type_nameMethod
type_name(w::Widget) -> Symbol

Type name of w, as CSS type selectors see it. Pure.

source
ManyUI.unmount!Method
unmount!(c::Widget) -> Widget

Call on_unmount! and detach c from its parent. Returns c.

The detached subtree keeps its own structure and is left dirty-clean; the OLD parent is marked Dirty.LAYOUT, because its remaining children restack.

source
ManyUI.walkMethod
walk(f, w::Widget)

Call f on w and every descendant, pre-order.

source
ManyUI.ReactiveType
mutable struct Reactive{T}

A reactive cell.

Assigning a value that differs (!=) from the current one marks owner dirty with kind AND posts a RefreshEvent, so a write from a worker task actually repaints.

Concretely typed: a widget field count::Reactive{Int} stays type-stable. owner is a small union, NOT Any.

label.text[]          # read,  zero cost
label.text[] = "hi"   # write, marks dirty, schedules a frame

Choosing Dirty.PAINT for state that can change size is a correctness bug, so the default is the conservative Dirty.LAYOUT.

Fields

  • value::Any: The current value.

  • owner::Union{Nothing, Widget}: The widget marked dirty on a real change.

  • kind::ManyUI.Dirty.T

source
ManyUI.ReactiveMethod

An unbound cell holding v.

The parametric form pins T; the plain form infers it from v. Both default to the conservative Dirty.LAYOUT. Call bind_owner! (or attach_reactives!) to give the cell a widget to invalidate.

source
Base.getindexMethod
getindex(r::Reactive{T}) -> Any

Read the value. Type-stable and allocation-free. Pure.

source
Base.setindex!Method
setindex!(r::Reactive{T}, v) -> Any

Write the value, converting to T first.

source
Base.setindex!Method
setindex!(r::Reactive{T}, v) -> Any

E1. Write the value.

NO-OP – no dirty mark, no RefreshEvent – when v == r.value. The == guard means a redundant write costs one comparison and zero frames.

On a real change: r.value = v; mark!(r.owner, r.kind); and when app(r.owner) !== nothing, post!(app, RefreshEvent()).

source
ManyUI.attach_reactives!Method
attach_reactives!(w::Widget)

Wire every Reactive field of w to w. Call at the END of a widget constructor.

Uses fieldnames plus getfield, and skips non-Reactive fields.

source
ManyUI.BorderType
struct Border

A border: a line style plus the style its glyphs are drawn in. isbits.

Fields

  • kind::ManyUI.BorderKind.T: Line style.

  • style::Style: Style the glyphs are painted with.

source
ManyUI.BoxPatchType
struct BoxPatch

The optional-every-field twin of BoxStyle: nothing means "not specified".

Deliberately NOT isbits, and that is fine – it exists only at parse and cascade time, never in the render loop.

Fields

  • display::Union{Nothing, ManyUI.Display.T}: Overrides BoxStyle.display when set.

  • direction::Union{Nothing, ManyUI.Direction.T}: Overrides BoxStyle.direction when set.

  • justify::Union{Nothing, ManyUI.Justify.T}: Overrides BoxStyle.justify when set.

  • align::Union{Nothing, ManyUI.Align.T}: Overrides BoxStyle.align when set.

  • width::Union{Nothing, Length}: Overrides BoxStyle.width when set.

  • height::Union{Nothing, Length}: Overrides BoxStyle.height when set.

  • min_width::Union{Nothing, Length}: Overrides BoxStyle.min_width when set.

  • min_height::Union{Nothing, Length}: Overrides BoxStyle.min_height when set.

  • max_width::Union{Nothing, Length}: Overrides BoxStyle.max_width when set.

  • max_height::Union{Nothing, Length}: Overrides BoxStyle.max_height when set.

  • margin::Union{Nothing, Spacing}: Overrides BoxStyle.margin when set.

  • padding::Union{Nothing, Spacing}: Overrides BoxStyle.padding when set.

  • border::Union{Nothing, Border}: Overrides BoxStyle.border when set.

  • gap::Union{Nothing, Int64}: Overrides BoxStyle.gap when set.

  • overflow_x::Union{Nothing, ManyUI.Overflow.T}: Overrides BoxStyle.overflow_x when set.

  • overflow_y::Union{Nothing, ManyUI.Overflow.T}: Overrides BoxStyle.overflow_y when set.

  • grow::Union{Nothing, Float32}: Overrides BoxStyle.grow when set.

  • shrink::Union{Nothing, Float32}: Overrides BoxStyle.shrink when set.

source
ManyUI.BoxPatchMethod
BoxPatch(; kwargs...) -> BoxPatch

Keyword constructor; every field defaults to nothing.

An unknown keyword throws ArgumentError: a stylesheet property that silently patched nothing would be invisible at every layer above.

source
ManyUI.BoxStyleType
struct BoxStyle

The cascade's output and the layout engine's input. isbits, and it lives inline in WidgetNode – no pointer chasing during layout.

Fields

  • display::ManyUI.Display.T: Whether and how the box participates in layout.

  • direction::ManyUI.Direction.T: Main axis of the flex container.

  • justify::ManyUI.Justify.T: Main-axis distribution of the children.

  • align::ManyUI.Align.T: Cross-axis alignment of the children.

  • width::Length: Preferred width.

  • height::Length: Preferred height.

  • min_width::Length: Lower width bound.

  • min_height::Length: Lower height bound.

  • max_width::Length: Upper width bound.

  • max_height::Length: Upper height bound.

  • margin::Spacing: Space outside the border box.

  • padding::Spacing: Space between the border and the content.

  • border::Border: Border drawn on the perimeter of the border box.

  • gap::Int64: Cells inserted between adjacent children.

  • overflow_x::ManyUI.Overflow.T: Horizontal overflow policy.

  • overflow_y::ManyUI.Overflow.T: Vertical overflow policy.

  • grow::Float32: Flex grow factor.

  • shrink::Float32: Flex shrink factor.

source
ManyUI.BoxStyleMethod
BoxStyle(base::BoxStyle; kwargs...) -> BoxStyle

Functional update: a copy of base with the given fields replaced. Pure.

An unknown keyword throws ArgumentError rather than being ignored: a silently dropped BoxStyle(b; witdh = cells(3)) is a layout bug with no symptom at the call site.

source
ManyUI.BoxStyleMethod
BoxStyle(
;
    display,
    direction,
    justify,
    align,
    width,
    height,
    min_width,
    min_height,
    max_width,
    max_height,
    margin,
    padding,
    border,
    gap,
    overflow_x,
    overflow_y,
    grow,
    shrink
) -> BoxStyle

Keyword constructor with the documented defaults.

source
ManyUI.LayoutBoxType
struct LayoutBox

The four resolved boxes of one node, in ABSOLUTE 1-based screen coordinates.

INVARIANT: margin_box includes border_box includes padding_box includes content.

Fields

  • margin_box::Region: Outermost box; includes the margin.

  • border_box::Region: margin_box shrunk by the margin. The border is drawn ON its perimeter.

  • padding_box::Region: border_box shrunk by the border thickness.

  • content::Region: padding_box shrunk by the padding.

source
ManyUI.LengthType
struct Length

A resolvable length. isbits.

Fields

  • kind::ManyUI.Dimension.T: How to read value.

  • value::Float32: Cells, percent, or fr weight, per kind.

source
Base.isemptyMethod
isempty(p::BoxPatch) -> Bool

True when p sets no field at all. Pure.

BoxPatch is an immutable struct, so === is structural (field-wise egal, recursively) rather than pointer identity – comparing against the all-nothing value is exactly "sets no field", in one shot.

source
Base.mergeMethod
merge(a::BoxPatch, b::BoxPatch) -> BoxPatch

Merge two patches: b wins wherever it is set. Pure.

A monoid with identity BOX_PATCH_NONE, matching merge(::Style, ::Style). This is the box half of the cascade fold.

source
Base.parseMethod
parse(_::Type{Length}, s::AbstractString) -> Length

Parse a length, throwing ArgumentError on garbage. Pure.

source
Base.tryparseMethod
tryparse(
    _::Type{Length},
    s::AbstractString
) -> Union{Nothing, Length}

Parse a length, returning nothing instead of throwing.

Accepts "auto", "10", "10cells", "50%", "1fr", "3fr". Case-insensitive; surrounding whitespace is ignored. Pure.

source
ManyUI.applyMethod
apply(base::BoxStyle, p::BoxPatch) -> BoxStyle

Apply p onto base: p wins wherever it is set. Pure.

The identity is BOX_PATCH_NONE: apply(b, BOX_PATCH_NONE) === b.

source
ManyUI.border_glyphsMethod
border_glyphs(k::ManyUI.BorderKind.T) -> NTuple{8, Char}

The eight glyphs of a border kind: (tl, top, tr, right, br, bottom, bl, left) – clockwise from the top-left. Pure.

NONE and BLANK both yield spaces. They differ in thickness, not in their glyphs: NONE reserves no cells and is never drawn, whereas BLANK reserves its cells and paints them blank.

Every glyph is width-1 by construction (verified in test_paint.jl); a width-2 glyph here would desynchronise the grid on every border.

source
ManyUI.box_overheadMethod
box_overhead(bs::BoxStyle) -> Spacing

Total non-content spacing: margin + border + padding. Pure.

source
ManyUI.definite_sizeMethod
definite_size(d::Length, available::Int64) -> Int64

Resolve a definite length against available. CELLS and PERCENT only; PERCENT rounds via round(Int). Pure.

Throws ArgumentError for AUTO and FRACTION: neither can be resolved without measuring content or knowing the free space, so a silent 0 would be a layout bug that never announces itself.

source
ManyUI.frMethod
fr(n::Real) -> Length

A fraction of the remaining free space. Pure.

source
ManyUI.is_definiteMethod
is_definite(d::Length) -> Bool

True for CELLS and PERCENT – the kinds resolvable without measuring content. Pure.

source
ManyUI.layout_boxMethod
layout_box(bs::BoxStyle, margin_box::Region) -> LayoutBox

Derive all four boxes from an outer margin_box and a BoxStyle.

THE single definition of the box model: no other file may re-derive these. Pure.

source
ManyUI.outer_sizeMethod
outer_size(bs::BoxStyle, inner::Size) -> Size

The margin-box size needed to hold a content box of inner. Pure.

This is the sole content-box -> outer-box conversion in the package, and so it is what pins BoxStyle.width/height to the CONTENT box (the CSS box-sizing: content-box default).

source
ManyUI.pctMethod
pct(n::Real) -> Length

A percentage of the parent's content box. Pure.

source
ManyUI.thicknessMethod
thickness(b::Border) -> Spacing

Cells the border occupies per edge: NO_SPACING for NONE, one cell on every edge otherwise (BLANK included). Pure.

source
ManyUI._apportionMethod
_apportion(
    weights::AbstractVector{<:Real},
    total::Int64
) -> Vector{Int64}

Share total cells out over weights, largest-remainder-first with ties going to the LOWER index, so sum(result) == total exactly whenever sum(weights) > 0. Pure.

source
ManyUI._arrange!Method
_arrange!(
    lm::IdDict{Widget, LayoutBox},
    w::Widget,
    bs::BoxStyle,
    content::Region
)

Size and place every laid-out child of w inside content, then recurse. Pure with respect to the tree. Pure.

source
ManyUI._bound_outerMethod
_bound_outer(
    len::Length,
    over::Int64,
    ref::Int64,
    lower::Bool
) -> Int64

Resolve a min_*/max_* bound to an OUTER extent. An AUTO bound means "unbounded": 0 for a lower bound, typemax(Int) for an upper one. Pure.

source
ManyUI._content_extentMethod
_content_extent(
    w::Widget,
    len::Length,
    horiz::Bool,
    main::Bool,
    avail::Size
) -> Int64

Resolve one axis of w to a CONTENT extent against the parent content box avail. FRACTION contributes nothing here – it claims free space later. Pure.

source
ManyUI._cross_lengthMethod
_cross_length(bs::BoxStyle, horiz::Bool) -> Length

The Length sizing the cross axis of bs. Pure.

source
ManyUI._is_reverseMethod
_is_reverse(d::ManyUI.Direction.T) -> Bool

True when d runs the main axis backwards. Pure.

source
ManyUI._is_rowMethod
_is_row(d::ManyUI.Direction.T) -> Bool

True when the main axis of d is horizontal. Pure.

source
ManyUI._main_directionMethod
_main_direction(bs::BoxStyle) -> ManyUI.Direction.T

The direction a container actually arranges its children along: Display.BLOCK is COLUMN whatever direction says. Pure.

source
ManyUI._main_insetMethod
_main_inset(sp::Spacing, horiz::Bool) -> Int64

Main-axis component of a Spacing. Pure.

source
ManyUI._main_lengthMethod
_main_length(bs::BoxStyle, horiz::Bool) -> Length

The Length sizing the main axis of bs. Pure.

source
ManyUI._measure_outerMethod
_measure_outer(w::Widget, avail::Size) -> Size

The margin-box size w wants, given the parent content box avail. Used by the default measure. Pure.

source
ManyUI._place!Method
_place!(
    lm::IdDict{Widget, LayoutBox},
    w::Widget,
    margin_box::Region
)

Record the four boxes of w in lm, then arrange its children. Pure with respect to the tree: lm is the only thing written.

source
ManyUI._split_evenMethod
_split_even(total::Int64, k::Int64) -> Vector{Int64}

Split total cells into k as-equal-as-possible parts, the remainder going to the lowest indices. Pure.

source
ManyUI.apply_layout!Method
apply_layout!(lm::IdDict{Widget, LayoutBox})

The impure shell: write lm into each WidgetNode.layout, clear Dirty.LAYOUT and Dirty.SUBTREE on every written node, and set Dirty.PAINT on any node whose LayoutBox actually CHANGED.

Nodes absent from lm are left untouched, breadcrumbs included: an ancestor's SUBTREE may still be carrying a PAINT-dirty descendant.

source
ManyUI.compute_layoutMethod
compute_layout(
    root::Widget,
    viewport::Region
) -> IdDict{Widget, LayoutBox}

U2 + E4. PURE: tree plus viewport to boxes. Mutates NOTHING, and does not touch WidgetNode.layout. This is what makes the layout engine testable without an App, a Buffer, or a terminal.

viewport is root's MARGIN box, so root's own width/height are not consulted: the root fills what it is given. Every other node's margin box is derived from its parent's CONTENT box, and layout_box(bs, margin_box) derives all four regions – no file may re-derive them.

Two passes per container:

  1. MEASURE (bottom-up): resolve CELLS/PERCENT against the parent content box; AUTO calls measure(child, avail). Every Length sizes the CONTENT box, and box_overhead is added on top of it to reach the margin box that the flex kernel distributes over.
  2. DISTRIBUTE (top-down): FRACTION items claim free space pro-rata by fr weight AFTER 1 and BEFORE grow; flex_distribute applies grow/shrink to the residual, clamped by min_*/max_*; justify_offsets places along the main axis; cross_align places and stretches along the cross axis.

Display.BLOCK is FLEX with direction COLUMN, align STRETCH and the children's grow forced to zero – ONE code path, no special case. Display.NONE and invisible subtrees are ABSENT from the returned map and claim no main-axis space.

Overflow is NOT clipped here: the map reports true geometry under every Overflow policy, and paint.jl clips through a BufferView.

source
ManyUI.cross_alignMethod
cross_align(
    size::Int64,
    align::ManyUI.Align.T,
    available::Int64
) -> Tuple{Int64, Int64}

Cross-axis (offset, size) for one item. Align.STRETCH returns (0, available). An item larger than available is given offset zero under every mode rather than a negative offset. Pure.

source
ManyUI.flex_distributeMethod
flex_distribute(
    base::Vector{Int64},
    grow::Vector{Float32},
    shrink::Vector{Float32},
    available::Int64
) -> Vector{Int64}

THE heart of flex. Pure. Distributes available over the base sizes using the grow/shrink factors.

When sum(base) < available the slack is shared out by grow weight. When sum(base) > available the excess is taken back by CSS SCALED shrink weight (shrink[i] * base[i]); an item whose share exceeds its own size freezes at zero rather than going negative, and the residue is re-offered to the items still able to shrink. When no item can absorb the slack at all the base sizes are returned unchanged – an overflowing layout is clipped at paint time, never mangled here.

NORMATIVE: fractional leftovers resolve LARGEST-REMAINDER-FIRST, so sum(result) == available EXACTLY whenever any item can absorb the slack. Integer cells: no one-cell drift, ever. Ties in the remainder go to the LOWER index.

source
ManyUI.justify_offsetsMethod
justify_offsets(
    sizes::Vector{Int64},
    justify::ManyUI.Justify.T,
    gap::Int64,
    available::Int64
) -> Vector{Int64}

Main-axis start offsets, 0-based within the container's content box, for each item, given the item sizes, gap and total available. Pure.

The free space is available - sum(sizes) - gap * (n - 1), CLAMPED AT ZERO: when the items overflow, every mode degenerates to START rather than placing an item at a negative offset. Free space is shared out largest-remainder-first, so the trailing edge never drifts a cell.

source
ManyUI.layout!Method
layout!(root::Widget, viewport::Region)

U2 + E4. An unconditional FULL pass over the whole tree; assigns layout on every visible node.

This is what a ResizeEvent calls – "instantly recalculate the bounding boxes of the ENTIRE layout tree". Never consults dirty flags.

source
ManyUI.measureMethod
measure(w::Widget, avail::Size) -> Size

Intrinsic content size of w given the available space.

Override for leaf widgets – a Label returns its wrapped text extent via text_width. The default is the union of the children's OUTER (margin-box) measures laid out along display/direction, with gap between them; a widget with no laid-out child therefore measures Size(0, 0). Drives Dimension.AUTO.

avail is the CONTENT size of the parent, and it is what PERCENT child lengths resolve against.

Pure with respect to the tree: no dirty marks, no field writes.

source
ManyUI.relayout!Method
relayout!(root::Widget, viewport::Region)

E1 + U2. INCREMENTAL: recomputes from dirty_root(root) down ONLY.

  • dirty_root(root) === nothing – no-op, returns immediately.
  • dirty_root(root) === root – falls back to layout!.
  • otherwise – compute_layout on the dirty subtree, anchored at the subtree root's EXISTING margin_box, then apply_layout!.

Two functions, not one with a force::Bool keyword: E4 mandates a full recompute on resize, E1 mandates a minimal one on state change. Two requirements, two functions, two test suites.

source