Built-in widgets
ManyUI.Label — Type
mutable struct Label <: WidgetWrapping 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.
ManyUI.Label — Method
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.
ManyUI.Static — Type
mutable struct Static <: WidgetNon-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.
ManyUI.Static — Method
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.
ManyUI.measure — Method
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.
ManyUI.measure — Method
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.
ManyUI.Container — Type
mutable struct Container <: WidgetA 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 oneTabStrip.selectedstates:measuremust be independent of the state, and it is –Containerdefines nomeasureat 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.
ManyUI.Container — Method
Container(children::Widget...; kwargs...) -> Container
A container with children already mounted, in order.
ManyUI.Container — Method
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.
ManyUI.Button — Type
mutable struct Button{F} <: WidgetA 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 ason_click(button)when activated.disabled::Reactive{Bool}: True if the button is disabled and cannot be pressed.
ManyUI.Button — Method
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).
ManyUI._bt_activates — Method
_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.
ManyUI._bt_fire! — Method
_bt_fire!(w::Button, d::Dispatch)
Fire the handler and stop the event.
ManyUI.measure — Method
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.
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.
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.
ManyUI.OVERLAY_MESSAGE — Constant
The default headline. Shared by the widget and by the tree-free fallback, so the two X2 paths cannot drift apart.
ManyUI.OVERLAY_MIN_SIZE — Constant
X2. The smallest area in which MinSizeOverlay can lay out. Below this, render_min_size_overlay! is used instead.
ManyUI.OVERLAY_TINY_MESSAGE — Constant
The last thing worth saying when there is no room left to say the headline.
ManyUI.MinSizeOverlay — Type
mutable struct MinSizeOverlay <: WidgetX2. 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.
ManyUI.MinSizeOverlay — Method
MinSizeOverlay(; message, id) -> MinSizeOverlay
A fresh overlay. Its id defaults to :_min_size_overlay so a stylesheet can target it.
ManyUI._ov_dims — Method
_ov_dims(required::Size, actual::Size) -> String
The dimensions line: what the app needs against what it has. Pure.
ManyUI.measure — Method
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.
ManyUI.should_suspend — Method
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)) === falseToggles
ManyUI.CB_CHECKED — Constant
Checked glyph. Width CB_WIDTH BY CONSTRUCTION, asserted.
ManyUI.CB_FOCUS — Constant
The focused control: what SPACE will act on. TC_CURSOR's meaning.
ManyUI.CB_GAP — Constant
Cells between the glyph and the caption.
ManyUI.CB_MIXED — Constant
Mixed glyph. Width CB_WIDTH BY CONSTRUCTION, asserted.
ManyUI.CB_UNCHECKED — Constant
Unchecked glyph. Width CB_WIDTH BY CONSTRUCTION, asserted.
ManyUI.CB_WIDTH — Constant
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.
ManyUI.RB_OFF — Constant
Unselected radio. Width CB_WIDTH BY CONSTRUCTION, asserted.
ManyUI.RB_ON — Constant
Selected radio. Width CB_WIDTH BY CONSTRUCTION, asserted.
ManyUI.Checkbox — Type
mutable struct Checkbox{F} <: WidgetA 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; seeCB_WIDTH.disabled::Reactive{Bool}: True if the box is disabled.focused::Reactive{Bool}: True while focused. PAINT-reactive.on_change::Any: Called ason_change(checkbox)after a REAL change.
ManyUI.Checkbox — Method
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).
ManyUI.RadioGroup — Type
mutable struct RadioGroup{F} <: WidgetA 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
( ) LargeWHERE 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;0when nothing is chosen – and NOTHING is chosen initially, exactly asSelectionchooses nothing (tablecore.jl:258). PAINT-reactive: every glyph isCB_WIDTHwide 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;0when 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 fireon_changeon EVERY KEYSTROKE – the trapListdocuments (list.jl:96). PAINT-reactive.
focused::Reactive{Bool}: True while focused. PAINT-reactive.on_change::Any: Called ason_change(group)after a REAL choice.disabled::Reactive{Set{Int64}}: Disabled option indices (1-based). PAINT-reactive.
ManyUI.RadioGroup — Method
A radio group over any string vector, collected ONCE into a Vector{String}.
ManyUI.RadioGroup — Method
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.
ManyUI._cb_glyph — Method
_cb_glyph(s::ManyUI.CheckState.T) -> String
The glyph for s. Pure. Internal.
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.
ManyUI._rg_row_at — Method
_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.
ManyUI.check_state — Method
check_state(w::Checkbox) -> ManyUI.CheckState.T
The current state. Pure.
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.
ManyUI.is_checked — Method
is_checked(w::Checkbox) -> Bool
True iff the box is CHECKED. Pure.
ManyUI.measure — Method
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.
ManyUI.measure — Method
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.
ManyUI.on_blur! — Method
on_blur!(w::Checkbox)
Clear the focus underline.
ManyUI.on_blur! — Method
on_blur!(w::RadioGroup)
Clear the focus underline.
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.
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.
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.
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.
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).
ManyUI.on_focus! — Method
on_focus!(w::RadioGroup)
Show the focus underline. reveal! is called EXPLICITLY, as for Checkbox.
ManyUI.radio_cursor — Method
radio_cursor(w::RadioGroup) -> Int64
The cursor row; 0 iff there are no options. Pure.
ManyUI.selected — Method
selected(w::RadioGroup) -> Int64
The chosen option index; 0 iff nothing is chosen. Pure.
ManyUI.selected_option — Method
selected_option(w::RadioGroup) -> Union{Nothing, String}
The chosen caption, or nothing. Pure.
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.
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.
Tabs
ManyUI.TABS_ACTIVE — Constant
The active caption. TC_SELECTED's meaning, one file over.
ManyUI.TABS_FOCUS — Constant
The strip while focused: which caption the arrows will move from.
ManyUI.TABS_PAD — Constant
Cells of padding on each side of a caption. " One " is width 5.
ManyUI.TabStrip — Type
mutable struct TabStrip <: WidgetThe 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;0IFFisempty(titles). THE single source of truth –Tabshas NOselectedfield and reads this. One cell, one meaning, no mirror to keep in step.Dirty.PAINT-reactive, and the licence is the oneTextInputstates verbatim (textinput.jl:158) andListcites (list.jl:43):measuremust be independent of the state. It is –measurebelow is a function oftitlesalone. THE PANELS'Dirty.LAYOUTCOMES FROMset_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.
ManyUI.Tabs — Type
mutable struct Tabs <: WidgetA 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::TabStripdisabled::Reactive{Bool}: True if the tabs are disabled.
ManyUI.Tabs — Method
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!.
ManyUI._tb_owner — Method
_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.
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.
ManyUI._tb_title_x — Method
_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.
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.
ManyUI.measure — Method
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.
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.
ManyUI.n_tabs — Method
n_tabs(w::Tabs) -> Int64
The number of tabs. Pure.
ManyUI.on_blur! — Method
on_blur!(w::TabStrip)
The strip lost focus: drop the focus underline.
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.
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.
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).
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).
ManyUI.selected — Method
selected(w::Tabs) -> Int64
The chosen tab, 1-based; 0 iff there are no tabs. Pure.
ManyUI.tab_at — Method
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.
ManyUI.tab_panel — Method
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.
ManyUI.tab_title — Method
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.
Tree
ManyUI.TV_CLOSED — Constant
Collapsed twisty. Width 1 BY CONSTRUCTION, asserted.
ManyUI.TV_GAP — Constant
Cells between the twisty and the label.
ManyUI.TV_INDENT — Constant
Cells of indent per depth level.
ManyUI.TV_LEAF — Constant
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.
ManyUI.TV_OPEN — Constant
Expanded twisty. Width 1 BY CONSTRUCTION, asserted.
ManyUI.TreeNode — Type
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 Texpanded::Bool: True when this node's children are part of the visible flattening. A LEAF is always collapsed andtoggle_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.
ManyUI.TreeNode — Method
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.
ManyUI.TreeRow — Type
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.
ManyUI.TreeView — Type
mutable struct TreeView{T, F, A, C} <: RowsWidgetA 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 Tformat::Anyrows::Array{TreeRow{T}, 1} where Tflattened::Bool: False whenrowsis stale. The memo bit of_tv_flat!.version::Reactive{Int64}: Bumped by every data, expansion OR cursor change. THE cell. PAINT.sel::Selectionwidest::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 ason_submit(tree)on ENTER. NAMEDon_submitBY FORCE:_tc_key!readsw.on_submit(tablecore.jl:1080).on_change::Any: Called ason_change(tree)when the cursor moves.
ManyUI.TreeView — Method
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.
ManyUI._tc_extent_width — Method
_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.
ManyUI._tv_cursor_node — Method
_tv_cursor_node(w::TreeView{T}) -> Any
The node under the cursor, or nothing. Internal, pure w.r.t. tree.
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.
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.
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.
ManyUI._tv_key_toggle! — Method
_tv_key_toggle!(w::TreeView, d::Dispatch{KeyEvent})
SPACE: toggle the cursor node; consume iff it moved. Internal.
ManyUI._tv_parent_row — Method
_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.
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.
ManyUI._tv_row_of — Method
_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.
ManyUI._tv_set_all! — Method
_tv_set_all!(n::TreeNode, v::Bool)
Recursively set expanded on n and every non-leaf below it. Internal.
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.
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.
ManyUI.collapse_all! — Method
collapse_all!(w::TreeView)
Collapse EVERY node. O(nodes) – on a user action, never a frame.
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.
ManyUI.content_extent — Method
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 Scrollbar – Scrollbar{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.
ManyUI.expand_all! — Method
expand_all!(w::TreeView)
Expand EVERY node. O(nodes) – on a user action, never a frame.
ManyUI.expand_node! — Method
expand_node!(w::TreeView{T}, n::TreeNode{T}) -> Bool
Expand n. False for a leaf or an already-open node.
ManyUI.is_expanded — Method
is_expanded(n::TreeNode) -> Bool
True when n is a non-leaf whose children are visible. Pure.
ManyUI.is_leaf — Method
is_leaf(n::TreeNode) -> Bool
True when n has no children. Pure.
ManyUI.measure — Method
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.
ManyUI.node_at — Method
node_at(w::TreeView{T}, k::Int64) -> Any
The TreeNode at flat row k, or nothing out of range. O(1). Pure.
ManyUI.on_blur! — Method
on_blur!(w::TreeView)
Hide the cursor.
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.
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.
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).
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.
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).
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.
ManyUI.tree_cursor — Method
tree_cursor(w::TreeView{T}) -> Any
The node under the cursor, or nothing. Pure w.r.t. tree.
ManyUI.tree_rows — Method
tree_rows(w::TreeView) -> Array{TreeRow{T}, 1} where T
The flattened VISIBLE rows. ALIASED; do not mutate. Pure w.r.t. tree.
Splitter
ManyUI.SPLIT_ACTIVE — Constant
A handle being dragged, and the one under the pointer.
ManyUI.SPLIT_GLYPH_V — Constant
The glyph a handle paints along its length, per axis.
ManyUI.SPLIT_MIN_PANE — Constant
Smallest pane a drag may leave behind, in cells.
ManyUI.SplitHandle — Type
mutable struct SplitHandle <: WidgetThe 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.
ManyUI.Splitter — Type
mutable struct Splitter <: WidgetA 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.ROWfor side-by-side,COLUMNfor stacked.dragging::Int64: Index of the handle being dragged,0when idle. NOT reactive: it changes nothing on screen by itself – the handle's ownactivecell 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}: Theirgrowvalues when it began.on_resize::Any: Called ason_resize(splitter)after a drag changes the weights.
ManyUI.Splitter — Method
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.
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.
ManyUI._split_end! — Method
_split_end!(sp::Splitter)
End the live drag. Internal.
ManyUI._split_extent — Method
_split_extent(sp::Splitter, w::Widget) -> Int64
Main-axis extent of w's laid-out border box. Internal.
ManyUI._split_index — Method
_split_index(sp::Splitter, h::SplitHandle) -> Int64
The index of handle h within its splitter, or 0. Internal.
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.
ManyUI._split_owner — Method
_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.
ManyUI.handles — Method
handles(sp::Splitter) -> Vector{Widget}
The handles, in order. Children 2, 4, 6, ...; handle i sits between pane i and pane i + 1.
ManyUI.is_horizontal — Method
is_horizontal(sp::Splitter) -> Bool
True when sp lays its panes out along the horizontal axis.
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.
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.
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.
ManyUI.pane_count — Method
pane_count(sp::Splitter) -> Int64
How many panes sp has.
ManyUI.panes — Method
panes(sp::Splitter) -> Vector{Widget}
The panes, in order. Children 1, 3, 5, ....
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.
ManyUI.weights_of — Method
weights_of(sp::Splitter) -> Vector{Float32}
The current weights, one per pane.
Readouts
ManyUI.SPARK_FLAT — Constant
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".
ManyUI.SPARK_GLYPHS — Constant
The eight levels a sparkline draws with, lowest first. One cell each, so a sparkline of n samples is exactly n cells wide.
ManyUI.Sparkline — Type
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.
ManyUI.Sparkline — Type
mutable struct Sparkline <: WidgetA 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, ornothingto 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, ornothingto take it from the data.cap::Int64: Samples kept; the oldest are dropped past it.0keeps every one.
ManyUI._spark_trim! — Method
_spark_trim!(w::Sparkline)
Drop the oldest samples past cap. Internal.
ManyUI.measure — Method
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.
ManyUI.n_values — Method
n_values(w::Sparkline) -> Int64
How many samples the series holds.
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.
ManyUI.set_values! — Method
set_values!(w::Sparkline, vs::AbstractVector{<:Real})
Replace the whole series.
ManyUI.spark_bounds — Method
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.
ManyUI.spark_level — Method
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.
ManyUI.StatusBar — Type
mutable struct StatusBar <: WidgetA 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.
ManyUI.StatusBar — Method
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.
ManyUI.measure — Method
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.
ManyUI.segments — Method
segments(
w::StatusBar
) -> Tuple{RichText, RichText, RichText}
The three segments, left to right.
ManyUI.status_layout — Method
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 -> truncatedManyUI.PROGRESS_FILL — Constant
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.
ManyUI.ProgressBar — Type
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.
ManyUI.ProgressBar — Type
mutable struct ProgressBar <: WidgetA 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 aGaugewould differ from this by exactly one of them.Dirty.PAINT:measuredoes not read it, so a new label cannot move the bar.
ManyUI.measure — Method
measure(w::ProgressBar, avail::Size) -> Size
The default extent of a progress bar, Size(10, 1).
Progress bars usually expand using Flex layout.
ManyUI.progress_cells — Method
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.
ManyUI.PL_GAP — Constant
Cells between the label column and the bar.
ManyUI.PL_MIN_BAR — Constant
Narrowest bar worth drawing; below it the row is all label.
ManyUI.ProgressItem — Type
struct ProgressItemOne 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.0to1.0.
ManyUI.ProgressList — Type
mutable struct ProgressList <: WidgetA 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, orAUTOto fit the widest caption.
ManyUI.ProgressList — Type
ProgressList(; ...) -> ProgressList
ProgressList(
items::AbstractVector{ProgressItem};
label_width,
id,
classes
) -> ProgressList
A progress list over items.
ManyUI.content_extent — Method
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.
ManyUI.measure — Method
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.
ManyUI.n_items — Method
n_items(w::ProgressList) -> Int64
How many rows the list holds.
ManyUI.pl_label_width — Method
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.
ManyUI.push_item! — Method
push_item!(w::ProgressList, item::ProgressItem)
Append a row.
ManyUI.set_items! — Method
set_items!(
w::ProgressList,
items::AbstractVector{ProgressItem}
)
Replace every row.
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.
ManyUI.DIALOG_BUTTON_GAP — Constant
Cells between two buttons in the row.
ManyUI.DIALOG_MIN_WIDTH — Constant
Narrowest dialog worth opening.
ManyUI.DIALOG_PAD — Constant
Cells of padding inside a dialog's frame.
ManyUI.Dialog — Method
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).
ManyUI.dialog_size — Method
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.
ManyUI.MD_BULLET — Constant
Bullet for an unordered list item.
ManyUI.MD_CODE — Constant
Style of inline and block code.
ManyUI.MD_HEADING — Constant
Style of a heading. Themed, so a document tracks the palette.
ManyUI.MD_INDENT — Constant
Cells a nested list is indented by.
ManyUI.MD_LINK — Constant
Style of a link's text. The URL is not shown; see md_lines.
ManyUI.MD_MARKER — Constant
Style of a list bullet or number.
ManyUI.MD_QUOTE — Constant
Style of a block quote, marker included.
ManyUI.MD_QUOTE_MARK — Constant
Marker before a block-quote line.
ManyUI.MD_RULE — Constant
Drawn for a horizontal rule, repeated to the pane width.
ManyUI.MarkdownPane — Type
MarkdownPane(; ...) -> MarkdownPane
MarkdownPane(
source::AbstractString;
id,
classes
) -> MarkdownPane
A pane over the Markdown in source.
ManyUI.MarkdownPane — Type
mutable struct MarkdownPane <: WidgetA 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 whensourceis replaced.lines::Vector{RichText}: Rendered lines, valid forwrapped_at.wrapped_at::Int64: The widthlineswas built at;-1when there are none.version::Reactive{Int64}: Bumped when the source changes. THE reactive cell.Dirty.PAINT.
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.
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.
ManyUI._md_inlines! — Method
_md_inlines!(runs::Vector{TextRun}, xs, base::Style)
Append every inline node of xs. xs may be one node. Internal.
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.
ManyUI.content_extent — Method
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.
ManyUI.md_inline — Function
md_inline(xs) -> RichText
md_inline(xs, base::Style) -> RichText
The inline content of xs as one RichText under base.
ManyUI.md_lines — Method
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.
ManyUI.measure — Method
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.
ManyUI.set_source! — Method
set_source!(w::MarkdownPane, source::AbstractString)
Replace the source and reparse. Drops the line cache.