Built-in widgets

ManyUI.LabelType
mutable struct Label <: Widget

Wrapping text. measure wraps to avail.width via wrap_width.

Fields

  • node::WidgetNode: Per-widget state.

  • text::Reactive{RichText}: The text; writing it marks the label dirty.

source
ManyUI.LabelMethod
Label(text::TextLike; id, classes) -> Label

A label showing text, given either as a plain string or as a RichText whose style varies along the line.

text is Dirty.LAYOUT-reactive: new text wraps differently, so it can change the label's extent and therefore its siblings' positions.

The cell holds a RichText in BOTH cases – a string converts on the way in, and on the way back out too, so label.text[] = "hi" keeps working. Styling is therefore never a different widget or a different code path, only a different value, and a Label that is coloured mid-line still wraps exactly where the same text wraps unstyled.

source
ManyUI.StaticType
mutable struct Static <: Widget

Non-wrapping, single-line text; no wrap cost in measure.

Fields

  • node::WidgetNode: Per-widget state.

  • text::Reactive{RichText}: The text; writing it marks the widget dirty.

source
ManyUI.StaticMethod
Static(text::TextLike; id, classes) -> Static

A single-line label showing text, given either as a plain string or as a RichText whose style varies along the line.

This is the widget most rich text lands in: a status readout, a table cell, a tab caption – one line, no wrapping, styled in places.

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

The wrapped extent of the text at avail.width: the widest wrapped line by the number of lines. Pure with respect to the tree.

Size(0, 0) when there is no text or no width – a label that cannot be laid out is empty, not an error.

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

Size(text_width(text), 1), independent of avail. Pure with respect to the tree.

A Static never wraps, so it never pays the allocation wrap_width costs Label on every measure – which is the whole reason the two types are separate.

source
ManyUI.ContainerType
mutable struct Container <: Widget

A bare grouping widget: no content of its own, all behaviour from its BoxStyle and its children.

Fields

  • node::WidgetNode: Per-widget state.

  • title::Reactive{RichText}: The caption painted on the top border. Dirty.PAINT-reactive, and the licence is the one TabStrip.selected states: measure must be independent of the state, and it is – Container defines no measure at all, so a new caption cannot move this widget or its siblings. The border row it lands on exists whether or not there is a caption on it.

  • title_align::ManyUI.Align.T: Where the caption sits along the top edge.
source
ManyUI.ContainerMethod
Container(children::Widget...; kwargs...) -> Container

A container with children already mounted, in order.

source
ManyUI.ContainerMethod
Container(; id, classes, title, title_align) -> Container

An empty container, optionally captioned.

It defines neither measure nor render!, and that is the point: the defaults – the union of the children's outer measures, and a no-op paint – are already exactly right for a widget whose whole job is to hold other widgets. Direction, gap, padding, border and background all come from its BoxStyle, which is the cascade's business.

title is painted BY THE PAINT PASS on the top border, and only when there is a border to put it on. It is not content and takes no content row.

source
ManyUI.ButtonType
mutable struct Button{F} <: Widget

A pressable button. Parametric on the handler, so on_click is a CONCRETE field and never a boxed closure.

Fields

  • node::WidgetNode: Per-widget state.

  • label::Reactive{String}: The caption; writing it marks the button dirty.

  • pressed::Reactive{Bool}: True while the button is held.

  • on_click::Any: Called as on_click(button) when activated.

  • disabled::Reactive{Bool}: True if the button is disabled and cannot be pressed.

source
ManyUI.ButtonMethod

A button captioned label that calls on_click(button) when activated.

Focusable by construction, so it appears in focusable_widgets and is reachable by TAB with no further wiring.

label is Dirty.LAYOUT-reactive (a new caption is a new extent); pressed is Dirty.PAINT-reactive (it cannot move a single cell).

source
ManyUI._bt_activatesMethod
_bt_activates(d::Dispatch) -> Bool

True while d is live and at or past its target – everywhere except the capture phase.

A button activates on the way UP: capture belongs to ancestors that want to intercept, and a button is never an interceptor. AT_TARGET counts because a childless button IS the target, and the bubble phase excludes the target, so bubble alone would never visit it.

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

The caption's extent, Size(text_width(label), 1). Pure with respect to the tree.

This is a CONTENT extent, like every other measure. The box overhead – margin, border, padding – is added by the layout engine through outer_size; adding it here as well would count it twice.

source
ManyUI.on_event!Method
on_event!(w::Button, d::Dispatch{KeyEvent})

Activate on ENTER or SPACE: calls w.on_click(w) and consume!(d).

Unmodified keys only. ctrl+enter and friends belong to an application-level binding, and consuming them here would silently shadow it.

source
ManyUI.on_event!Method
on_event!(w::Button, d::Dispatch{MouseEvent})

Activate on a LEFT press: calls w.on_click(w) and consume!(d).

The matching LEFT release clears pressed – that is what makes "True while the button is held" true – but activates nothing and consumes nothing: the press already did the work.

source
ManyUI.OVERLAY_MESSAGEConstant

The default headline. Shared by the widget and by the tree-free fallback, so the two X2 paths cannot drift apart.

source
ManyUI.OVERLAY_MIN_SIZEConstant

X2. The smallest area in which MinSizeOverlay can lay out. Below this, render_min_size_overlay! is used instead.

source
ManyUI.MinSizeOverlayType
mutable struct MinSizeOverlay <: Widget

X2. The "Increase Terminal Size" fallback, as an ordinary widget.

Fields

  • node::WidgetNode: Per-widget state.

  • required::Reactive{Size}: The minimum the app needs.

  • actual::Reactive{Size}: What the target currently offers.

  • message::Reactive{String}: The headline message.

source
ManyUI.MinSizeOverlayMethod
MinSizeOverlay(; message, id) -> MinSizeOverlay

A fresh overlay. Its id defaults to :_min_size_overlay so a stylesheet can target it.

source
ManyUI._ov_dimsMethod
_ov_dims(required::Size, actual::Size) -> String

The dimensions line: what the app needs against what it has. Pure.

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

The extent of the message plus the dimensions line: the wider of the two, by two rows. Pure with respect to the tree.

source
ManyUI.should_suspendMethod
should_suspend(actual::Size, min::Size) -> Bool

X2. PURE predicate, so the threshold is testable with no App:

should_suspend(Size(12, 3), Size(20, 5)) === true
should_suspend(Size(80, 24), Size(20, 5)) === false
source

Toggles

ManyUI.CB_WIDTHConstant

Cells every glyph in this file occupies.

ALL FIVE GLYPHS ARE THE SAME WIDTH AND THAT IS LOAD-BEARING, not tidiness: it is what makes measure independent of state, which is what LICENSES state's Dirty.PAINT reactivity – the exact criterion textinput.jl:158 states and list.jl:43 cites. A width-1 check against a width-3 [ ] would make a click on a checkbox a LAYOUT pass, and the suite asserts the widths so nobody discovers that by measuring frames. ASCII, not [check]/(dot): a checkbox that renders as a tofu box is worse than one that renders as an x.

source
ManyUI.CheckboxType
mutable struct Checkbox{F} <: Widget

A toggleable box with a caption. Parametric on the handler, so on_change is a CONCRETE field and never a boxed closure.

Fields

  • node::WidgetNode: Per-widget state.

  • label::Reactive{String}: The caption. LAYOUT-reactive: a new caption is a new extent.

  • state::Reactive{ManyUI.CheckState.T}: UNCHECKED, CHECKED or MIXED. PAINT-reactive; see CB_WIDTH.

  • disabled::Reactive{Bool}: True if the box is disabled.

  • focused::Reactive{Bool}: True while focused. PAINT-reactive.

  • on_change::Any: Called as on_change(checkbox) after a REAL change.

source
ManyUI.CheckboxMethod

A checkbox captioned label that calls on_change(checkbox) after a real state change.

Focusable by construction, so it appears in focusable_widgets and is reachable by TAB with no further wiring. The label/state split is Button's exactly (button.jl:30-31): label is Dirty.LAYOUT-reactive (a new caption is a new extent), state is Dirty.PAINT-reactive (every glyph is CB_WIDTH wide, so it cannot move a cell).

source
ManyUI.RadioGroupType
mutable struct RadioGroup{F} <: Widget

A one-of-N chooser. ONE widget, N rows, ZERO child widgets. Parametric on the handler, so on_change is a CONCRETE field and never boxed.

(o) Small
( ) Medium
( ) Large

WHERE THE MUTUAL EXCLUSION LIVES: in selected, which is ONE Int. There is no state in which two options are on, therefore there is no code that prevents it, therefore there is no bug in which that code fails. The alternative – a Radio widget per option with a checked::Bool and a back-pointer – makes "exactly one" an INVARIANT MAINTAINED BY A LOOP, and radio.state[] = CHECKED from application code leaves it broken PERMANENTLY. An Int needs no maintenance, so there is no maintenance to get wrong. This is Selection.cursor's argument (tablecore.jl:236) and List's "a row is not a widget" (list.jl:14), one type down: N options are N Strings and ZERO WidgetNodes – and, easy to miss, zero hit-test nodes, so hit_test stays O(depth) on every POINTER MOVE.

THERE IS NO Radio WIDGET IN THIS FILE and there is not going to be.

NOT SCROLLABLE, NOT WINDOWED: render! is O(options), and an option IS a row, so O(options) IS O(viewport) here. Ten options is the design centre; two hundred options are a List. SIZES TO CONTENT and is NOT greedy: Size(CB_WIDTH + CB_GAP + widest, n). options is ALIASED, never copied.

Fields

  • node::WidgetNode: Per-widget state.

  • options::Vector{String}

  • selected::Reactive{Int64}: The CHOSEN option, 1-based; 0 when nothing is chosen – and NOTHING is chosen initially, exactly as Selection chooses nothing (tablecore.jl:258). PAINT-reactive: every glyph is CB_WIDTH wide and the box is as wide as the widest caption whatever is picked, so this provably cannot move a cell.

  • cursor::Reactive{Int64}: The cursor, 1-based; 0 when there are no options. The option SPACE would choose. ARROWS MOVE THIS WITHOUT CHOOSING, and that is why it is a SECOND field: selection-follows-arrow would make SPACE a NO-OP on the row it just moved to and fire on_change on EVERY KEYSTROKE – the trap List documents (list.jl:96). PAINT-reactive.
  • focused::Reactive{Bool}: True while focused. PAINT-reactive.

  • on_change::Any: Called as on_change(group) after a REAL choice.

  • disabled::Reactive{Set{Int64}}: Disabled option indices (1-based). PAINT-reactive.

source
ManyUI.RadioGroupMethod

A radio group over options, calling on_change(group) after a real choice.

Focusable by construction. selected seeds to 0 (nothing chosen); cursor seeds to row 1, or 0 when there are no options. options is ALIASED, not copied.

source
ManyUI._rg_cursor!Method
_rg_cursor!(w::RadioGroup, i::Int64) -> Bool

Move the cursor to i, clamped to 1:n. True iff it moved. Does NOT choose and does NOT fire on_change. Internal.

source
ManyUI._rg_row_atMethod
_rg_row_at(w::RadioGroup, e::MouseEvent) -> Int64

The 1-based option row under mouse event e; 0 for a miss.

_tc_local, NEVER local_offset (events.jl:450): a RadioGroup inside a Scrollpane scrolled off the top would choose the wrong row with the unshifted border box. Bounds-checked against layout_of(w).content and the option count. Internal.

source
ManyUI.choose!Method
choose!(w::RadioGroup, i::Int64) -> Bool

Choose option i, clamped to 1:n, and move the cursor there – a choice is where you are. True iff the SELECTION changed; false when n == 0 or the option was already chosen. Fires on_change only on a real change.

THE single exit for a choice.

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

The CONTENT extent: Size(CB_WIDTH, 1) bare, or CB_WIDTH + CB_GAP + text_width(label) wide with a caption.

A content extent, like every other measure: the box overhead is outer_size's job and adding it here would count it twice (button.jl:52). NOT avail – a greedy checkbox would shrink a one-row Label beside it to ZERO rows. Pure w.r.t. the tree.

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

The CONTENT extent: Size(0, 0) when empty, else `Size(CBWIDTH + CBGAP

  • widest, n). NOTavail`. Pure w.r.t. the tree.
source
ManyUI.on_event!Method
on_event!(w::Checkbox, d::Dispatch{KeyEvent})

SPACE toggles and consumes. ENTER does NEITHER.

BOTH space forms are checked – Key.SPACE (what the parser emits) and Key.CHAR(' ') – exactly as Button does (button.jl:105). ENTER is left unconsumed on purpose: ENTER in a form is SUBMIT, and a checkbox that eats it makes ENTER the one key an app cannot bind. Unmodified keys only; TAB, ESCAPE and modified keys pass through untouched so the tab order stays alive.

source
ManyUI.on_event!Method
on_event!(w::Checkbox, d::Dispatch{MouseEvent})

A LEFT press toggles and consumes, anywhere in the box or caption.

Button's rule (button.jl:119): the press does the work; the matching release activates nothing and consumes nothing. The box is one widget, so a click on the caption toggles it just like a click on the glyph.

source
ManyUI.on_event!Method
on_event!(w::RadioGroup, d::Dispatch{KeyEvent})

Arrows move the cursor; SPACE or ENTER choose it.

UP/LEFT and DOWN/RIGHT move the cursor without choosing; HOME/END jump to the ends; SPACE (BOTH forms – Key.SPACE and Key.CHAR(' ')) or ENTER commit the choice. Consumes ONLY on a real move or choice, so UP on option 1 bubbles. Unmodified keys only; TAB, ESCAPE and modified keys pass through untouched.

source
ManyUI.on_event!Method
on_event!(w::RadioGroup, d::Dispatch{MouseEvent})

A LEFT press on an option row chooses it, cursor and all, and consumes.

A press outside the rows chooses nothing and does not consume. Button's rule (button.jl:119): the press does the work; the release does nothing.

source
ManyUI.on_focus!Method
on_focus!(w::Checkbox)

Show the focus underline. reveal! is called EXPLICITLY because overriding on_focus! REPLACES the default that would have called it (widget.jl:666).

source
ManyUI.on_focus!Method
on_focus!(w::RadioGroup)

Show the focus underline. reveal! is called EXPLICITLY, as for Checkbox.

source
ManyUI.selectedMethod
selected(w::RadioGroup) -> Int64

The chosen option index; 0 iff nothing is chosen. Pure.

source
ManyUI.set_state!Method
set_state!(w::Checkbox, s::ManyUI.CheckState.T) -> Bool

Set the state, firing on_change only when it actually moves. True iff it changed.

THE single exit. The Reactive == guard already makes a redundant write free (reactive.jl:63); the explicit compare here is what stops on_change firing on a write that changes nothing.

source
ManyUI.toggle!Method
toggle!(w::Checkbox) -> ManyUI.CheckState.T

Toggle the box and return the NEW state.

CHECKED becomes UNCHECKED; anything else – INCLUDING MIXED – becomes CHECKED. See CheckState for why a program-set MIXED must toggle out to CHECKED.

source

Tabs

ManyUI.TabStripType
mutable struct TabStrip <: Widget

The clickable row of captions. INTERNAL machinery of Tabs, exactly as Scrollpane's row/canvas are (scroll.jl:216), but a WIDGET rather than a render! branch of Tabs for one hard reason: _paint_node! hands render! the CONTENT box (paint.jl:134), so a Tabs that drew its own strip would have to reserve row 1 with padding.top = 1 – which puts the strip OUTSIDE the very buffer it would draw into.

Fields

  • node::WidgetNode: Per-widget state.

  • titles::Vector{RichText}

  • selected::Reactive{Int64}: The chosen tab, 1-based; 0 IFF isempty(titles). THE single source of truth – Tabs has NO selected field and reads this. One cell, one meaning, no mirror to keep in step.

    Dirty.PAINT-reactive, and the licence is the one TextInput states verbatim (textinput.jl:158) and List cites (list.jl:43): measure must be independent of the state. It is – measure below is a function of titles alone. THE PANELS' Dirty.LAYOUT COMES FROM set_visible!, NOT FROM THIS CELL, which is why PAINT here is provable and not optimistic.

  • focused::Reactive{Bool}: True while focused. PAINT-reactive.

  • disabled::Reactive{Bool}: True if the tabs are disabled.

source
ManyUI.TabsType
mutable struct Tabs <: Widget

A tab strip plus panels.

Tabs SIZES TO CONTENT and is NOT greedy. It defines no measure and no render! – the Container defaults are already exactly right (container.jl:27), and the inactive panels are absent from the children's measure union because they are invisible. Give it grow: 1 or a height for a full-height tab view. A measure returning avail here would make a one-row Label beside a Tabs into a ZERO-row label.

Fields

  • node::WidgetNode: Per-widget state.

  • strip::TabStrip

  • disabled::Reactive{Bool}: True if the tabs are disabled.

source
ManyUI.TabsMethod
Tabs(
    pairs::Pair{<:TextLike, <:Widget}...;
    id,
    classes,
    disabled
) -> Tabs

A Tabs whose captions and panels are given as title => panel pairs, in order. The first tab is selected; the rest of the panels start hidden.

The strip is mounted as child 1 through invoke (the Scrollpane idiom, scroll.jl:37), because mount!(::Tabs, ::Widget) throws to force add_tab!.

source
ManyUI._tb_ownerMethod
_tb_owner(w::TabStrip) -> Union{Nothing, Tabs}

parent(w) as a Tabs, or nothing.

THE ONE isa IN THIS FILE, and its scope is why it is affordable: both types are in THIS file, at THIS layer, and the check runs on a KEYSTROKE or a CLICK, NEVER on the frame path. The owner is parent(w) BY CONSTRUCTION: Tabs mounts its strip as child 1 and nothing else ever mounts a TabStrip.

A strip with no Tabs parent is INERT rather than an error: a bare TabStrip is a legal, if useless, widget, and a test that builds one must not throw. Internal.

source
ManyUI._tb_sync!Method
_tb_sync!(w::Tabs)

Set exactly the selected panel visible and every other hidden.

set_visible! returns early on the value it already has (widget.jl:513), so this is O(tabs) compares and at most TWO Dirty.LAYOUT marks. Internal.

source
ManyUI._tb_title_xMethod
_tb_title_x(
    titles::AbstractVector{<:TextLike},
    i::Int64
) -> Int64

The strip-local column at which caption i begins. Captions abut with no separator, so this is one running sum – the SAME one render! and tab_at walk. Internal.

source
ManyUI.add_tab!Method
add_tab!(w::Tabs, title::TextLike, panel::Widget) -> Int64

Append a tab captioned title showing panel, and return its index.

The panel is mounted through invoke (the strip's own idiom), given grow = 1f0, and hidden unless it is the FIRST – the first tab becomes the selection. mark!(w.strip, Dirty.LAYOUT) because a new caption is a new strip extent.

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

Size(sum(text_width(t) + 2 * TABS_PAD), 1).

NOT avail, and this is browser-lesson 2 written into a signature: a measure that returns avail demands the whole viewport and flex then shrinks its neighbours – a one-row Label beside it becomes a ZERO-row label and VANISHES. A strip is exactly as wide as its captions and exactly one row tall. IT IS NOT GREEDY. Give the Tabs grow: 1, not the strip. Pure w.r.t. the tree.

source
ManyUI.mount!Method
mount!(_::Tabs, _::Widget)

Panels are added with add_tab!, never mount!: mounting one directly would leave the strip's titles and the children out of step. Throws.

source
ManyUI.on_event!Method
on_event!(w::TabStrip, d::Dispatch{KeyEvent})

Keyboard: LEFT/RIGHT step the selection (clamped, no wrap), HOME/END go to the ends, and '1'-'9' select by ordinal. Consumes ONLY when the selection actually moved, so RIGHT on the last tab bubbles.

TAB, ESCAPE, modified keys and everything else are left untouched: a strip that ate TAB would trap focus forever.

source
ManyUI.on_event!Method
on_event!(w::TabStrip, d::Dispatch{MouseEvent})

Mouse: a LEFT press selects the caption under the pointer, located through _tc_local (NOT local_offset, which ignores scrolled ancestors). Consumes only on a real change.

source
ManyUI.on_focus!Method
on_focus!(w::TabStrip)

The strip gained focus: light the focus underline and reveal it.

reveal! is called EXPLICITLY because overriding on_focus! REPLACES the default that would have called it (widget.jl:666).

source
ManyUI.select_tab!Method
select_tab!(w::Tabs, i::Int64) -> Bool

Select tab i, CLAMPED to 1:n_tabs. True iff the selection moved; false when it was unmoved or there are no tabs.

THE single exit: it writes w.strip.selected[] and then _tb_sync!. Clamped rather than thrown – set_cursor! clamps for the same reason (tablecore.jl:353).

source
ManyUI.selectedMethod
selected(w::Tabs) -> Int64

The chosen tab, 1-based; 0 iff there are no tabs. Pure.

source
ManyUI.tab_atMethod
tab_at(
    titles::AbstractVector{<:TextLike},
    x::Int64
) -> Int64

The 1-based caption covering strip-local column x; 0 for none.

PURE – a vector of captions and an Int, no widget, no layout, no buffer. tab_at and render! walk the SAME running sum in the SAME direction, which is what makes "click the caption you see" true by construction rather than by two arithmetics agreeing by luck.

source
ManyUI.tab_panelMethod
tab_panel(w::Tabs, i::Int64) -> Widget

The panel of tab i. The strip is child 1, so panel i is child i + 1. Pure.

source
ManyUI.tab_titleMethod
tab_title(w::Tabs, i::Int64) -> RichText

The caption of tab i. Pure. Throws on a bad index – a caller naming a tab that does not exist has a bug.

source

Tree

ManyUI.TV_LEAFConstant

A leaf's twisty: a space. Width 1, so the TEXT COLUMN IS INVARIANT between leaves and branches – a tree whose labels jog left by one on a leaf is a tree nobody can read.

source
ManyUI.TreeNodeType
mutable struct TreeNode{T}

One node of a TreeView's data model. NOT A WIDGET.

List's argument (list.jl:14) one type up, and it is worth more here: a 10 000-node tree is 10 000 of THESE and ZERO WidgetNodes – no layout pass per expand, no 10 000 render! dispatches per frame, and – the part that is easy to miss – no 10 000 hit-test nodes, so hit_test stays O(depth) ON EVERY POINTER MOVE, not merely every frame.

NO PARENT POINTER, DELIBERATELY. A parent field is a second source of truth that push!(n.kids, x) silently fails to maintain, and building a tree stops being a literal. TreeView recovers the parent FROM THE FLATTENED ROWS – the nearest earlier row with a smaller depth – in O(depth-from-here) on a KEYSTROKE, never on a frame. That is the right price for a field that cannot go stale because it does not exist.

Fields

  • value::Any: The payload.

  • kids::Array{TreeNode{T}, 1} where T

  • expanded::Bool: True when this node's children are part of the visible flattening. A LEAF is always collapsed and toggle_node! will not move it – isempty(kids) IS the leaf test, so there is no separate flag to fall out of step with the vector it describes.

source
ManyUI.TreeNodeMethod

A tree node carrying value, with kids in display order.

expanded defaults to false: a tree opens showing its roots and nothing beneath them, which is what "collapsed" means and what every file browser does.

source
ManyUI.TreeRowType
struct TreeRow{T}

One DISPLAYED row: a node and its indentation depth. isbits-adjacent (a reference and an Int), so Vector{TreeRow{T}} is dense. Internal to the widget's cache; never a user type.

Fields

  • node::TreeNode: The node this row shows.

  • depth::Int64: Indent level. A root is 0.

source
ManyUI.TreeViewType
mutable struct TreeView{T, F, A, C} <: RowsWidget

A scrollable, focusable hierarchical view over TreeNode DATA.

render! touches rows[scroll.y+1 : scroll.y+height] and NOTHING ELSE, so paint is O(WINDOW), never O(nodes) – List's contract exactly, and a 10 000-node tree costs the same frame as a 10-node one.

THE FLATTEN IS O(VISIBLE), NOT O(WINDOW). A fully-expanded 10 000-node tree holds a 10 000-entry Vector{TreeRow}. It is 10 000 TreeRows and ZERO WidgetNodes, which is the claim that matters. A COLLAPSED SUBTREE CONTRIBUTES EXACTLY ONE ROW HOWEVER DEEP IT IS – that is not an optimisation, IT IS WHAT EXPAND/COLLAPSE MEANS.

flattened is the memo bit and it is KEYED ON THE SHAPE, NOT ON version – deliberately: version bumps on every ARROW KEY (_tc_touch!), so keying on it would re-flatten 10 000 nodes PER KEYSTROKE. A cursor move is not a shape change.

GREEDY BY DESIGN: measure(w, avail) = avail. List's argument (list.jl:239) – an auto-height tree would be as tall as its data and would NEVER SCROLL AT ALL. Give it height: 10 or a grow: 1 parent; do not put it beside a one-row Label you want to survive. This is also what licenses version's PAINT reactivity: a data change provably cannot move this box, so toggle_node! on a 10 000-node tree costs ZERO layout.

SINGLE SELECTION ONLY; mode IS NOT A KWARG. Re-pinning a BitSet of N rows across a re-flatten has no answer for a selected row whose parent just collapsed. Under SINGLE the selection IS the cursor and one rule covers both.

roots is ALIASED, never copied.

Fields

  • node::WidgetNode: Per-widget state.

  • roots::Array{TreeNode{T}, 1} where T

  • format::Any

  • rows::Array{TreeRow{T}, 1} where T

  • flattened::Bool: False when rows is stale. The memo bit of _tv_flat!.

  • version::Reactive{Int64}: Bumped by every data, expansion OR cursor change. THE cell. PAINT.

  • sel::Selection

  • widest::Int64: Widest visible row in cells, INCLUDING indent and twisty. EXACT.

  • focused::Reactive{Bool}: True while focused. PAINT-reactive.

  • disabled::Reactive{Bool}: True if the tree is disabled.

  • on_submit::Any: Called as on_submit(tree) on ENTER. NAMED on_submit BY FORCE: _tc_key! reads w.on_submit (tablecore.jl:1080).

  • on_change::Any: Called as on_change(tree) when the cursor moves.

source
ManyUI.TreeViewMethod

A tree over roots, calling on_submit(tree) on ENTER.

Focusable by construction, so it appears in focusable_widgets and is reachable by TAB with no further wiring. roots is ALIASED, not copied.

source
ManyUI._tc_extent_widthMethod
_tc_extent_width(w::TreeView) -> Int64

The widest visible row, in cells. OVERRIDES the RowsWidget default, which calls grid_of(w) – a tree has no columns, so that default is a MethodError. _tv_flat! computes the exact mark in the walk it was doing anyway; there is no scanned bit and no fixpoint to escape (list.jl:154). Internal.

source
ManyUI._tv_cursor_nodeMethod
_tv_cursor_node(w::TreeView{T}) -> Any

The node under the cursor, or nothing. Internal, pure w.r.t. tree.

source
ManyUI._tv_flat!Method
_tv_flat!(w::TreeView{T}) -> Array{TreeRow{T}, 1} where T

w.rows, REBUILT IFF flattened is false. One Bool test and ZERO allocation on the hit; O(VISIBLE) on the miss. _lst_scan! (list.jl:200) is the precedent and the memo bit means the same thing.

empty! + push! into the SAME Vector, never a fresh one: the buffer's capacity survives, so re-expanding the same subtree allocates nothing after the first time.

widest IS EXACT AND THERE IS NO scanned BIT: a tree MUST walk to know what is visible at all, so the width falls out of a walk that was happening anyway. One pass, exact answer, no memo to invalidate. Internal.

source
ManyUI._tv_key_left!Method
_tv_key_left!(w::TreeView, d::Dispatch{KeyEvent})

LEFT: collapse, or if already shut / a leaf move to the parent.

source
ManyUI._tv_key_right!Method
_tv_key_right!(w::TreeView, d::Dispatch{KeyEvent})

RIGHT: expand, or if already open move to the first child. Internal.

source
ManyUI._tv_key_toggle!Method
_tv_key_toggle!(w::TreeView, d::Dispatch{KeyEvent})

SPACE: toggle the cursor node; consume iff it moved. Internal.

source
ManyUI._tv_parent_rowMethod
_tv_parent_row(w::TreeView, i::Int64) -> Int64

The parent of visible row i: the NEAREST EARLIER row with a SMALLER depth; 0 when i is top-level or out of range. Pure w.r.t. tree.

O(depth-from-here) worst case – it walks UP the flattened vector and stops at the first shallower row – on a KEYSTROKE, never on a frame. This is what TreeNode having no parent pointer costs, and it CANNOT BE STALE, which a field would be the first time someone push!ed a child. Internal.

source
ManyUI._tv_rebuild!Method
_tv_rebuild!(w::TreeView{T})
_tv_rebuild!(
    w::TreeView{T},
    fallback::Union{Nothing, TreeNode{T}}
)

Re-flatten and RE-PIN the cursor by NODE IDENTITY: the row the user was on keeps the node they were on, whatever its flat index becomes.

RE-PINNING IS THE WHOLE FUNCTION AND IT IS WHY MULTI IS NOT OFFERED. The cursor is a ROW INDEX and the rows renumber, so a rebuild that did not re-pin would leave the cursor on whatever slid into its slot.

fallback is where the cursor goes when its node is NO LONGER VISIBLE – which is exactly collapse!'s case. _tv_set_expanded! passes THE COLLAPSED NODE, so collapsing an ancestor puts the cursor on the ancestor – one rule, testable, and unrepresentable as a bug.

Re-clamps the scroll afterwards: this is the only call that can SHRINK the extent and strand an offset past the end (refresh_extent!(::List), list.jl:299). Internal.

source
ManyUI._tv_row_ofMethod
_tv_row_of(w::TreeView{T}, n::TreeNode{T}) -> Int64

The flat row showing n by IDENTITY; 0 when n is not visible. O(visible), on a keystroke, never a frame. Internal.

source
ManyUI._tv_set_all!Method
_tv_set_all!(n::TreeNode, v::Bool)

Recursively set expanded on n and every non-leaf below it. Internal.

source
ManyUI._tv_set_expanded!Method
_tv_set_expanded!(
    w::TreeView{T},
    n::TreeNode{T},
    v::Bool
) -> Bool

Set n.expanded = v and re-flatten, re-pinning the cursor. False for a leaf or an unchanged flag. THE single exit of every expand/collapse, so none of the four steps can be forgotten. Internal.

source
ManyUI._tv_walk!Method
_tv_walk!(w::TreeView{T}, n::TreeNode{T}, depth::Int64)

Push n and, if it is expanded, its subtree, into w.rows; raise widest to fit each label. Internal.

source
ManyUI.collapse_all!Method
collapse_all!(w::TreeView)

Collapse EVERY node. O(nodes) – on a user action, never a frame.

source
ManyUI.collapse_node!Method
collapse_node!(w::TreeView{T}, n::TreeNode{T}) -> Bool

Collapse n. False for an already-shut node. Passes n as the rebuild's fallback, so a cursor inside the closing subtree lands on n.

source
ManyUI.content_extentMethod
content_extent(w::TreeView) -> Size

Size(widest, n_visible). OVERRIDES the container default: a tree's content is DATA, not children. THIS OVERRIDE IS THE WHOLE INTEGRATION WITH ScrollbarScrollbar{TreeView{T,F,A}} works with ZERO new code in scroll.jl (list.jl:212). O(1) on the memo hit; ON THE FRAME PATH several times per frame.

source
ManyUI.expand_all!Method
expand_all!(w::TreeView)

Expand EVERY node. O(nodes) – on a user action, never a frame.

source
ManyUI.expand_node!Method
expand_node!(w::TreeView{T}, n::TreeNode{T}) -> Bool

Expand n. False for a leaf or an already-open node.

source
ManyUI.is_expandedMethod
is_expanded(n::TreeNode) -> Bool

True when n is a non-leaf whose children are visible. Pure.

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

avail. List's argument verbatim (list.jl:239): a tree takes the space it is OFFERED and scrolls its content, because an auto-HEIGHT tree would be as tall as its data and would never scroll at all. Give it height: 10 or a grow: 1 parent. This is also what licenses version's PAINT reactivity. Pure w.r.t. the tree.

source
ManyUI.node_atMethod
node_at(w::TreeView{T}, k::Int64) -> Any

The TreeNode at flat row k, or nothing out of range. O(1). Pure.

source
ManyUI.on_event!Method
on_event!(w::TreeView, d::Dispatch{KeyEvent})

Three interceptions, then _tc_key!. SPACE toggles the cursor node, RIGHT expands / walks to the first child, LEFT collapses / walks to the parent – a tree's arrows have meant expand/collapse since 1990 and no user will accept the horizontal-scroll binding _tc_key! gives them. UP/DOWN/PAGE/HOME/END/ENTER and shift-extend are DELEGATED unchanged. Consumes ONLY when something moved, so LEFT on a root leaf bubbles. TAB and ESCAPE fall through, which is what keeps the tab order alive.

source
ManyUI.on_event!Method
on_event!(w::TreeView, d::Dispatch{MouseEvent})

A LEFT PRESS on the TWISTY COLUMN toggles the row; anywhere else DELEGATES to _tc_mouse! (cursor, drag, wheel). The twisty test uses the SAME arithmetic render! paints it at, so "click what you see" is true by construction. _tc_local, NEVER local_offset.

source
ManyUI.on_focus!Method
on_focus!(w::TreeView)

Show the cursor, and scroll every ancestor pane until this tree is visible. reveal! is called EXPLICITLY because overriding on_focus! REPLACES the default that would have called it (widget.jl:666).

source
ManyUI.refresh_tree!Method
refresh_tree!(w::TreeView)

Re-flatten from the current roots/kids. THE public escape hatch for "I mutated roots/kids myself" – refresh_rows!(::List)'s contract (list.jl:462), same meaning.

source
ManyUI.set_roots!Method
set_roots!(w::TreeView{T}, rs)

Replace the roots. CLEARS the selection and rewinds the scroll: every flat index the selection held names a row that may no longer exist. set_items!(::List)'s argument (list.jl:394).

source
ManyUI.toggle_node!Method
toggle_node!(w::TreeView, k::Int64) -> Bool

Flip the expansion of flat row k. False for a leaf or a bad row.

source
ManyUI.tree_cursorMethod
tree_cursor(w::TreeView{T}) -> Any

The node under the cursor, or nothing. Pure w.r.t. tree.

source
ManyUI.tree_rowsMethod
tree_rows(w::TreeView) -> Array{TreeRow{T}, 1} where T

The flattened VISIBLE rows. ALIASED; do not mutate. Pure w.r.t. tree.

source

Splitter

ManyUI.SplitHandleType
mutable struct SplitHandle <: Widget

The draggable divider between two panes. INTERNAL machinery of Splitter, mounted by it and never by a caller.

One cell thick on the main axis and stretched on the cross axis, with grow = 0 and shrink = 0 so the flex pass cannot eat it: a divider that can be squeezed to nothing is a divider that cannot be grabbed.

Fields

  • node::WidgetNode: Per-widget state.

  • active::Reactive{Bool}: True while this handle is the one being dragged. PAINT-reactive.

source
ManyUI.SplitterType
mutable struct Splitter <: Widget

A row or column of panes separated by draggable handles.

The panes are children, and so are the handles, interleaved: pane, handle, pane, handle, pane. A pane's share of the main axis is its grow, so the layout engine already knows how to distribute it and this widget only ever rewrites two numbers.

Fields

  • node::WidgetNode: Per-widget state.

  • direction::ManyUI.Direction.T: Main axis: Direction.ROW for side-by-side, COLUMN for stacked.

  • dragging::Int64: Index of the handle being dragged, 0 when idle. NOT reactive: it changes nothing on screen by itself – the handle's own active cell does that.

  • drag_from::Int64: Pointer coordinate on the main axis when the drag began.

  • drag_sizes::Tuple{Int64, Int64}: Main-axis sizes of the two panes either side, when it began.

  • drag_grows::Tuple{Float32, Float32}: Their grow values when it began.

  • on_resize::Any: Called as on_resize(splitter) after a drag changes the weights.

source
ManyUI.SplitterMethod
Splitter(
    panes::Widget...;
    direction,
    weights,
    on_resize,
    id,
    classes
) -> Splitter

A Splitter over panes, separated by handles.

weights is one positive number per pane, defaulting to equal shares; they are ratios, so [1, 1] and [3, 3] describe the same split. A handle is mounted between each neighbouring pair, which is why the child list is 2n - 1 long and why panes and handles exist rather than callers indexing children and counting in twos.

source
ManyUI._split_begin!Method
_split_begin!(sp::Splitter, i::Int64, at::Int64)

Begin a drag of handle i, anchored at pointer coordinate at.

The anchor is the pointer AND the two panes' sizes as laid out right now, not their weights: the weights are ratios over the whole splitter, whereas a drag is a number of cells moved. Recording cells makes the arithmetic below exact and independent of what the other panes are doing. Internal.

source
ManyUI._split_extentMethod
_split_extent(sp::Splitter, w::Widget) -> Int64

Main-axis extent of w's laid-out border box. Internal.

source
ManyUI._split_indexMethod
_split_index(sp::Splitter, h::SplitHandle) -> Int64

The index of handle h within its splitter, or 0. Internal.

source
ManyUI._split_move!Method
_split_move!(sp::Splitter, at::Int64) -> Bool

Move the live drag so the pointer sits at at, and return true when the weights actually changed.

Only the two panes either side move, and their grow total is preserved, so a drag NEVER disturbs a pane it is not between. Both are clamped to SPLIT_MIN_PANE, which is what stops a drag past the end from inverting them. Internal.

source
ManyUI._split_ownerMethod
_split_owner(w::SplitHandle) -> Union{Nothing, Splitter}

parent(w) as a Splitter, or nothing.

The owner is parent(w) BY CONSTRUCTION – Splitter mounts its handles and nothing else ever mounts a SplitHandle. A handle with no Splitter parent is INERT rather than an error, exactly as a bare TabStrip is (tabs.jl): a test that builds one must not throw. Internal.

source
ManyUI.handlesMethod
handles(sp::Splitter) -> Vector{Widget}

The handles, in order. Children 2, 4, 6, ...; handle i sits between pane i and pane i + 1.

source
ManyUI.is_horizontalMethod
is_horizontal(sp::Splitter) -> Bool

True when sp lays its panes out along the horizontal axis.

source
ManyUI.mount!Method
mount!(_::Splitter, _::Widget)

Refuse a stray child.

The interleaving – pane, handle, pane – is what panes, handles and every index in this file rely on. A child mounted from outside would shift the parity and silently turn one pane into a handle, so the constructor is the only way in, exactly as add_tab! is for Tabs.

source
ManyUI.on_event!Method
on_event!(w::SplitHandle, d::Dispatch{MouseEvent})

Press on a handle: arm the drag on the owning splitter.

Only the press is handled here. Everything after it belongs to the splitter, because a pointer that outruns the redraw is no longer over this one-cell widget.

source
ManyUI.on_event!Method
on_event!(sp::Splitter, d::Dispatch{MouseEvent})

Follow a live drag, in the CAPTURE phase.

CAPTURE and not BUBBLE: capture runs root-first, so the splitter sees the event BEFORE the pane the pointer has strayed onto and consumes it there. That is pointer capture, obtained from the propagation order that already exists rather than from a new mechanism in the app.

A no-op when no handle is down, which is every mouse event in a splitter that is not being resized.

source
ManyUI.panesMethod
panes(sp::Splitter) -> Vector{Widget}

The panes, in order. Children 1, 3, 5, ....

source
ManyUI.set_weights!Method
set_weights!(sp::Splitter, ws::AbstractVector{<:Real})

Give the panes new weights and relayout. Every weight must be positive and there must be one per pane.

THE single writer: a drag ends here too, so there is one place where a split changes and one place to look when it changes wrongly.

source

Readouts

ManyUI.SPARK_FLATConstant

Drawn for a sample when the series is flat – every value equal, so there is no range to scale against and every level would be a lie except the one that says "no change".

source
ManyUI.SPARK_GLYPHSConstant

The eight levels a sparkline draws with, lowest first. One cell each, so a sparkline of n samples is exactly n cells wide.

source
ManyUI.SparklineType
Sparkline(; ...) -> Sparkline
Sparkline(
    values::AbstractVector{<:Real};
    lo,
    hi,
    cap,
    id,
    classes
) -> Sparkline

A sparkline over values.

lo/hi fix the scale; left at nothing they are taken from the data. cap bounds how many samples are kept, so a live series cannot grow without limit – 0 keeps every one.

source
ManyUI.SparklineType
mutable struct Sparkline <: Widget

A one-row plot of a numeric series, one cell per sample.

The series is ALIASED and mutated in place: push_value! costs a push! and one Dirty.PAINT mark, never a rebuild. version is the reactive cell, exactly as List uses one, because the data is a plain Vector with nothing to make reactive.

Fields

  • node::WidgetNode: Per-widget state.

  • values::Vector{Float64}

  • version::Reactive{Int64}: Bumped by every data change. THE reactive cell. Dirty.PAINT.

  • lo::Union{Nothing, Float64}: Lower bound of the scale, or nothing to take it from the data.

    A FIXED bound is what makes two sparklines comparable: auto-scaling redraws the same series differently the moment one outlier arrives, which is exactly when a reader most needs the picture to hold still.

  • hi::Union{Nothing, Float64}: Upper bound of the scale, or nothing to take it from the data.

  • cap::Int64: Samples kept; the oldest are dropped past it. 0 keeps every one.

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

Size(n_values, 1): one cell per sample, one row.

Independent of avail, so a sparkline beside a Label does not eat the row. Give it grow or a width to stretch it; render! then shows the LAST samples that fit, which is what a live series wants – new data arrives on the right and the oldest scrolls off the left.

source
ManyUI.push_value!Method
push_value!(w::Sparkline, v::Real)

Append v, dropping the oldest sample if that would exceed cap.

Dirty.PAINT and not Dirty.LAYOUT even though the series got longer: measure is a function of the WIDGET, not of the series – see below.

source
ManyUI.spark_boundsMethod
spark_bounds(w::Sparkline) -> Tuple{Float64, Float64}

The scale in force: (lo, hi), from the fixed bounds where they are given and from the data otherwise. (0, 1) for an empty series.

hi <= lo is reported as-is rather than repaired – render! reads it as "flat" and draws the lowest level, which is the truthful picture of a series with no range.

source
ManyUI.spark_levelMethod
spark_level(v::Real, lo::Real, hi::Real) -> Int64

The glyph index 1:8 for v under the scale (lo, hi), clamped.

Pure and total: a value outside a FIXED scale is pinned to the end it overshot rather than dropped, because a sparkline that silently omits its outliers is worse than one that flattens them.

source
ManyUI.StatusBarType
mutable struct StatusBar <: Widget

A one-row bar with a left, a centre and a right segment.

Each is a RichText, so the parts that carry meaning can be coloured without becoming three more nodes – which is what a status line is made of: a state word, a count, a hint, each in a different colour, all on one row.

WHEN IT DOES NOT FIT, IT DROPS IN PRIORITY ORDER: the centre goes first, then the right, and the left is truncated only when it is alone and still too wide. A bar that shrank all three would show three fragments and say nothing; the left segment is the one an application puts its identity in, so it is the one that survives.

Fields

  • node::WidgetNode: Per-widget state.

  • left::Reactive{RichText}: Flush to the left edge. The last to be dropped.

  • center::Reactive{RichText}: Centred in the full width, when there is room for it.

  • right::Reactive{RichText}: Flush to the right edge.

source
ManyUI.StatusBarMethod
StatusBar(; left, center, right, id, classes) -> StatusBar

A status bar. Each segment takes a plain string or a RichText.

All three are Dirty.PAINT-reactive: the bar is one row whatever they say, so new text can never move it or its siblings.

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

Size(sum of the segments plus their gaps, 1).

One row, always. The width is what the bar WANTS; give it grow or a width and status_layout decides what survives the width it gets.

source
ManyUI.segmentsMethod
segments(
    w::StatusBar
) -> Tuple{RichText, RichText, RichText}

The three segments, left to right.

source
ManyUI.status_layoutMethod
status_layout(
    w::StatusBar,
    width::Int64
) -> Vector{Tuple{Int64, RichText}}

Where each segment goes in a bar width cells wide, and what is left of it: a tuple of (x, RichText) for the segments that survive, in paint order.

PURE, so the priority rule is testable with no buffer and no App – which matters, because the rule IS the widget.

left + right fit      -> both, flush to their edges
centre also fits      -> centred in the FULL width, not in the gap,
                         unless that would overlap a neighbour, in
                         which case it is centred in the gap
right does not fit    -> dropped; left keeps the row
left alone too wide   -> truncated
source
ManyUI.PROGRESS_FILLConstant

What a LABELLED bar folds over its filled span.

REVERSE rather than a colour: the caption is written across the whole bar, so the boundary has to stay legible THROUGH the text, and reversing does that whatever the theme has made the two colours.

source
ManyUI.ProgressBarType
ProgressBar(; ...) -> ProgressBar
ProgressBar(
    progress::Float64;
    label,
    id,
    classes
) -> ProgressBar

A progress bar showing progress (0.0 to 1.0), optionally captioned by label drawn across it.

progress is Dirty.PAINT-reactive, meaning it updates visually without forcing a layout recalculation.

source
ManyUI.ProgressBarType
mutable struct ProgressBar <: Widget

A progress bar indicating a percentage of completion.

Fields

  • node::WidgetNode: Per-widget state.

  • progress::Reactive{Float64}: The progress value, clamped between 0.0 and 1.0; writing it marks the bar dirty.

  • label::Reactive{RichText}: Text drawn ACROSS the bar, centred, or empty for none. A labelled bar is what other toolkits call a gauge; it is a field here rather than a second widget, because a Gauge would differ from this by exactly one of them.

    Dirty.PAINT: measure does not read it, so a new label cannot move the bar.

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

The default extent of a progress bar, Size(10, 1).

Progress bars usually expand using Flex layout.

source
ManyUI.progress_cellsMethod
progress_cells(w::ProgressBar, width::Int64) -> Int64

How many of width cells are filled at the bar's current progress.

Pure, so the split is testable without a buffer, and shared by both render paths so a labelled and an unlabelled bar can never disagree about where the boundary is.

source
ManyUI.ProgressItemType
struct ProgressItem

One row of a ProgressList: a caption and a ratio.

progress is clamped to 0:1 on construction, so a row cannot carry an out-of-range value into render! where there is nothing sensible to do about it.

Fields

  • label::RichText: Row caption, drawn in the label column.

  • progress::Float64: Completion, 0.0 to 1.0.

source
ManyUI.ProgressListType
mutable struct ProgressList <: Widget

A column of captioned bars, one row per item.

The label column is label_width cells wide, or as wide as the widest caption when that is AUTO – measured over ALL items, because a label column that changed width as the list scrolled would make every bar jump sideways.

Fields

  • node::WidgetNode: Per-widget state.

  • items::Vector{ProgressItem}

  • version::Reactive{Int64}: Bumped by every data change. THE reactive cell. Dirty.PAINT.

  • label_width::Length: Width of the label column, or AUTO to fit the widest caption.

source
ManyUI.ProgressListType
ProgressList(; ...) -> ProgressList
ProgressList(
    items::AbstractVector{ProgressItem};
    label_width,
    id,
    classes
) -> ProgressList

A progress list over items.

source
ManyUI.content_extentMethod
content_extent(w::ProgressList) -> Size

One row per item, so a Scrollbar reports on it with no new code – the same seam TextArea and the row widgets use.

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

avail. List's argument verbatim: a progress list takes the space it is OFFERED and scrolls, because an auto-height one would be as tall as its data and would never scroll at all. This is also what licenses version's PAINT reactivity.

source
ManyUI.pl_label_widthMethod
pl_label_width(w::ProgressList) -> Int64

Cells the label column takes: label_width when definite, otherwise the widest caption.

AUTO measures EVERY item and not a sample, unlike a table's AUTO column. The two differ because the cost differs: a caption is short and a progress list is a handful of rows, where a table's AUTO column guards against a hundred thousand. Measuring a sample here would let the column change width as the list scrolled, and every bar would jump sideways.

source
ManyUI.set_items!Method
set_items!(
    w::ProgressList,
    items::AbstractVector{ProgressItem}
)

Replace every row.

source
ManyUI.set_progress!Method
set_progress!(w::ProgressList, i::Int64, v::Real) -> Bool

Set row i's progress, clamped. Returns true iff it moved.

The reason a row is a struct and not a ProgressBar: updating one is this, not a widget lookup and a reactive write on a node.

source
ManyUI.DialogMethod
Dialog(
    message::TextLike;
    title,
    buttons,
    id,
    classes
) -> Container

A dialog: a captioned frame around message and a row of buttons.

buttons is a vector of caption => callback pairs, laid out left to right. The callback is the Button's own on_click, so a dialog that must close itself calls close_popup! from inside it – this function does not know about an App and cannot do it for you.

Returns an ordinary Container, so it composes, restyles and is queried like anything else. Pair it with dialog_size and open it with Popup(...; modal = true, placement = PopupPlacement.CENTER).

source
ManyUI.dialog_sizeMethod
dialog_size(message::TextLike; title, buttons, max) -> Size

The size a Dialog wants for message, title and buttons, bounded by max.

A dialog is opened on the POPUP layer, and the layer takes the owner's declared size rather than measuring the content – so something has to compute it, and guessing wrong shows as a clipped question. Wraps the message to the width it settles on, so the height is the height the message will actually occupy rather than one line per sentence.

source
ManyUI.MarkdownPaneType
MarkdownPane(; ...) -> MarkdownPane
MarkdownPane(
    source::AbstractString;
    id,
    classes
) -> MarkdownPane

A pane over the Markdown in source.

source
ManyUI.MarkdownPaneType
mutable struct MarkdownPane <: Widget

A scrollable rendered Markdown document.

Holds the SOURCE, the parsed AST and the lines it was last rendered to. The lines are RichText, so a heading, a bold run and a code span cost runs rather than nodes: a 500-line document is one widget.

Fields

  • node::WidgetNode: Per-widget state.

  • source::String: The Markdown source.

  • ast::Any: The parsed document. Reparsed only when source is replaced.

  • lines::Vector{RichText}: Rendered lines, valid for wrapped_at.

  • wrapped_at::Int64: The width lines was built at; -1 when there are none.

  • version::Reactive{Int64}: Bumped when the source changes. THE reactive cell. Dirty.PAINT.

source
ManyUI._md_block!Function
_md_block!(
    out::Vector{RichText},
    b,
    width::Int64,
    head::RichText
)
_md_block!(
    out::Vector{RichText},
    b,
    width::Int64,
    head::RichText,
    rest::RichText
)

Render the block b at width, appending to out. Internal.

source
ManyUI._md_inline!Method
_md_inline!(runs::Vector{TextRun}, x, base::Style)

Append the inline node x to runs, under base. Internal.

Unknown nodes fall through to their string form rather than being dropped: an unrendered footnote is a visible oddity, a missing one is a silent hole in the text.

source
ManyUI._md_inlines!Method
_md_inlines!(runs::Vector{TextRun}, xs, base::Style)

Append every inline node of xs. xs may be one node. Internal.

source
ManyUI._md_wrap!Function
_md_wrap!(out::Vector{RichText}, rt::RichText, width::Int64)
_md_wrap!(
    out::Vector{RichText},
    rt::RichText,
    width::Int64,
    head::RichText
)
_md_wrap!(
    out::Vector{RichText},
    rt::RichText,
    width::Int64,
    head::RichText,
    rest::RichText
)

Wrap rt to width and push the lines onto out, each prefixed by prefix. Internal.

TWO prefixes, and that is the point. A block quote wants the same marker on every line; a list item wants its bullet ONCE and blank indent under it, or a wrapped item reads as two items. One prefix cannot be both, which is exactly the bug a single one produces.

source
ManyUI.content_extentMethod
content_extent(w::MarkdownPane) -> Size

The size of the rendered document at the width it was last rendered to, so a Scrollbar reports on it with no new code.

Size(0, 0) before the first paint: nothing has decided a width yet, and guessing one here would produce an extent that the first paint then contradicts.

source
ManyUI.md_inlineFunction
md_inline(xs) -> RichText
md_inline(xs, base::Style) -> RichText

The inline content of xs as one RichText under base.

source
ManyUI.md_linesMethod
md_lines(w::MarkdownPane, width::Int64) -> Vector{RichText}

The document rendered to lines at width, cached.

Rebuilt only when width differs from the width the cache was built at. Everything about a rendered document depends on the wrap width, so a cache that ignored it would show the previous box's breaks in the new one, and rebuilding unconditionally would reflow the whole document once a frame.

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

avail. A pane takes the space it is OFFERED and scrolls, because an auto-height document would be as tall as itself and would never scroll. This is also what licenses version's PAINT reactivity.

source
ManyUI.set_source!Method
set_source!(w::MarkdownPane, source::AbstractString)

Replace the source and reparse. Drops the line cache.

source