App and event loop

ManyUI.TC_AUTO_SAMPLEConstant

Rows an AUTO column measures at a data change. 200: 200 formatter calls cost about what ONE frame of a 40-row table costs, so it is invisible; and a column's width is decided by its TYPICAL value, which 200 samples estimate as well as 100 000 do.

source
ManyUI.TC_CURSORConstant

The cursor row, drawn only while FOCUSED. Merged, so it composes with TC_SELECTED on a row that is both.

source
ManyUI.TC_MEASURE_CAPConstant

The measurement cap an AUTO column uses BEFORE the first layout, when there is no content box to cap against yet.

_tc_measure's cap is normally the content-box width, but an AUTO column is seeded at CONSTRUCTION, and at construction layout_of(w) is still empty – so the rule "cap at the viewport" has no viewport to name and a cap of 0 would measure every column at nothing. This is that fallback and nothing else: it keeps the seed O(cap) per cell rather than O(length), and refresh_extent!(w) re-measures against the real box for anyone who needs the exact answer. Internal.

source
ManyUI.ActionType
abstract type Action

An Action represents a discrete user intent or domain operation.

Fields

source
ManyUI.CLIType
struct CLI <: Projection

Command-Line Interface projection.

Fields

source
ManyUI.ColumnType
Column(; ...) -> Column
Column(
    header::AbstractString;
    width,
    align,
    min_width,
    max_width,
    sortable
) -> Column

A column captioned header.

source
ManyUI.ColumnType
struct Column

One column of a Table: a caption, a width policy, an alignment.

IMMUTABLE: a column is a SPEC. Changing one is grid_of(t).cols[j] = Column(...) followed by refresh_columns!(t).

width is a Lengthcells(12), pct(25), fr(1), AUTO – because those are already the framework's four answers to "how wide", already parsed by the CSS engine and already resolved by definite_size/_apportion. Inventing a fifth vocabulary for columns would be inventing flexbox next to flexbox.

align is Align.T, REUSED rather than reinvented: cross_align (layout.jl:163) does the placement, START/CENTER/END are left/centre/right, and Align.STRETCH degenerates to START FOR FREE because cross_align(n, STRETCH, w) returns (0, w) and a text painter reads only the offset. There is no CellAlign enum because there is nothing for one to say that Align does not.

min_width/max_width are Lengths, not Ints: a bound the box model spells as a Length must not be downgraded here. AUTO means unbounded – 0 low, typemax(Int) high.

sortable is ignored by Table, which never sorts. It is one Bool per COLUMN, not per row, and it buys DataTable two things: which headers reserve an indicator cell, and which headers a click sorts.

Fields

  • header::String: Header caption. ONE row; a newline is undefined.

  • width::Length: Width policy. See _tc_resolve!.

  • align::ManyUI.Align.T: How the cell text AND the header sit in the column.

  • min_width::Length: Lower bound. AUTO is unbounded.

  • max_width::Length: Upper bound. AUTO is unbounded. On an AUTO column this is ALSO the measurement cap – see _tc_measure.

  • sortable::Bool: DataTable only: may this column be sorted?
source
ManyUI.ProjectionType
abstract type Projection

A projection represents a specific target channel for the application UI.

Fields

source
ManyUI.RowsWidgetType
abstract type RowsWidget <: Widget

A widget whose content is a sequence of DATA ROWS rather than children.

THE SEAM, and it is functions, not fields – scroll.jl's own move ("the scrollable seam is three functions, not a type"), one layer up. Every subtype MUST define, each a one-liner:

selection_of(w)::Selection
row_count(w)::Int             # SOURCE rows. O(1). FRAME PATH.
view_count(w)::Int            # rows in view order. O(1).
view_source(w, k::Int)::Int   # view index -> source index
view_rank(w, s::Int)::Int     # source index -> view index
_tc_touch!(w)::Nothing        # bump the PAINT cell
_tc_header_rows(w)::Int       # pinned chrome rows at the top
_tc_extent_width(w)::Int      # the extent's WIDTH, read not computed

and MUST carry these DIRECT fields:

node::WidgetNode
version::Reactive{Int}        # Dirty.PAINT -- see below
focused::Reactive{Bool}

version/focused MUST be direct fields of the widget and MUST NOT be moved into a held struct: attach_reactives! walks fieldnames(typeof(w)) ONE level and binds only direct Reactive fields (reactive.jl:100). A Reactive one level down silently never gets an owner and never marks anything dirty. THAT FAILURE HAS NO ERROR MESSAGE.

This is NOT a scrollable supertype and must not become one. The scrollable seam stays the three functions scroll.jl:57-66 names – content_extent, layout_of(w).content, scroll_of(w) – which is why Scrollbar{List{T,F,A}} needs ZERO new code in scroll.jl. RowsWidget is a different axis: it is the seam for SELECTION and NAVIGATION, which are identical across the three rather than merely similar.

The seam methods take ::RowsWidget and have NO default, so a subtype that forgets one gets a MethodError – and a Button never answers them at all.

Fields

source
ManyUI.SelectionType
Selection() -> Selection
Selection(mode::ManyUI.SelectMode.T) -> Selection
Selection(mode::ManyUI.SelectMode.T, n::Int64) -> Selection

A selection of mode over n rows, cursor and anchor on row 1 (or 0 when n == 0). NOTHING is selected initially, in every mode – including SINGLE: a list that selects row 1 before the user has touched it has made a choice on their behalf.

source
ManyUI.SelectionType
mutable struct Selection

A row cursor and a set of selected rows, over n SOURCE rows.

NORMATIVE: every index here is a SOURCE index – an index into the user's rows/items – and NEVER a view index. That is what makes a sort harmless: sort_by! permutes the VIEW and this does not move. The alternative – view indices – silently reassigns the selection to DIFFERENT ROWS the moment the user clicks a header, which is data corruption with a pretty animation.

PURE: no widget, no layout, no buffer – four Ints and a BitSet, so the whole selection model is one table test. thumb_span (scroll.jl:572) set that bar and this meets it.

n IS A FIELD, and that is deliberate: a Selection that does not know how many rows there are makes every mutator take a caller-supplied n you can get wrong – set_cursor!(s, 5, 999) on a 3-row list parks the cursor out of range, silently. resize_selection! is the ONE way n changes.

cursor/anchor are 0 when there is no row, never nothing: 0 keeps the fields concrete Int and every operation total at n == 0.

rows is a BitSet and not a Set{Int} or a Vector{Bool}. MEASURED, not assumed: in allocates ZERO for BOTH BitSet and Set{Int}, so the usual allocation argument for BitSet is WRONG and is not made here. BitSet wins on the two axes that survive: an empty selection over 100 000 rows costs ~0 (a Vector{Bool}/BitVector costs 12.5 kB), and a contiguous anchor:target extend is a bitmap range rather than 100 000 hash inserts. All of Base; no dependency.

Fields

  • mode::ManyUI.SelectMode.T

  • n::Int64: Rows this selection is sized for. resize_selection! maintains it.

  • cursor::Int64: The cursor's SOURCE row, 1-based. 0 IFF n == 0.

  • anchor::Int64: The fixed end of a shift-extend, a SOURCE row. 0 IFF n == 0.

  • rows::BitSet

source
ManyUI.TUIType
struct TUI <: Projection

Terminal User Interface projection.

Fields

source
ManyUI.TableGridType
mutable struct TableGrid

Everything a Table and a DataTable need about COLUMNS, held BY COMPOSITION so the two widgets share one column model instead of declaring it twice and drifting.

NOT a Widget: no node, no dirty, no render!. It holds NO Reactive and MUST NOT ever hold one – attach_reactives! would not see it (reactive.jl:100).

It holds no rows and no selection: those stay on the widget, because Selection is shared with List (which has no columns) and rows is what DataTable sorts a permutation OVER.

Fields

  • cols::Vector{Column}

  • sep::String

  • sep_w::Int64

  • show_header::Bool

  • rule::Bool

  • rule_glyph::String

  • sample::Int64

  • autos::Vector{Int64}

  • widths::Vector{Int64}

  • xs::Vector{Int64}

  • cache_version::Int64: The version the memo was resolved at; -1 is never.

  • cache_width::Int64: The content-box width it was resolved for; -1 is never.

  • cache_total::Int64: sum(widths) + sep_w * (ncols - 1). What _tc_extent reads.

source
ManyUI.TableGridMethod
TableGrid(
    cols::Vector{Column};
    sep,
    show_header,
    rule,
    rule_glyph,
    sample
) -> TableGrid

A grid over cols. cols is ALIASED, never copied.

Throws ArgumentError when rule is true and rule_glyph is not width-1, and when sample < 0. Throwing beats a quiet nothing: mount!(::Scrollpane, ...) throws for the same class of reason.

source
ManyUI.WebNativeType
struct WebNative <: Projection

Native Web projection (e.g., HTML/DOM/CSS).

Fields

source
ManyUI.WebTerminalType
struct WebTerminal <: Projection

Web Terminal Emulation projection (e.g., using xterm.js over WebSockets).

Fields

source
ManyUI._tc_actsMethod
_tc_acts(d::Dispatch) -> Bool

True while d is live and at or past its target. CAPTURE belongs to ancestors that want to intercept, and a list is never an interceptor. _ta_acts/_sp_acts, third copy. Pure. Internal.

source
ManyUI._tc_auto!Method
_tc_auto!(w::RowsWidget, first::Int64, last::Int64) -> Bool

Measure every AUTO column of w from SOURCE rows first:last and RAISE its mark. True iff a mark rose. MONOTONE: nothing here lowers a mark.

Only AUTO columns pay: the loop continues on any column whose width is CELLS, PERCENT or FRACTION, so a table of fixed columns calls the cell function ZERO times outside the paint loop.

Every measurement is CAPPED via _tc_measure(s, cap) with cap = min(definite max_width, content-box width). O(cap) per cell, never O(length). Internal.

source
ManyUI._tc_auto_reset!Method
_tc_auto_reset!(w::RowsWidget)

Reset every AUTO mark to its SEED – the header text plus _tc_header_reserve – and invalidate the resolve memo. THE ONLY thing that can make a column NARROWER. Internal.

source
ManyUI._tc_bar_styleMethod
_tc_bar_style(s::Selection, src::Int64, foc::Bool) -> Style

The style to MERGE across a highlighted row: TC_SELECTED, TC_CURSOR, or both merged.

style_region! and NOT fill_region!: it MERGES onto the cells already there, KEEPING cur.content and cur.width (buffer.jl:476), so a highlight over a wide cluster restyles its HEAD and leaves its continuation a continuation. frame! blanks the back buffer every frame (app.jl:552), so a never-written padding cell is a blank and reversing a blank IS the bar – the full-width highlight costs one style_region! per highlighted VISIBLE row and no fill pass at all. Internal.

source
ManyUI._tc_body_heightMethod
_tc_body_height(w::RowsWidget) -> Int64

Rows of the SCROLLING body: the content box less the header. Internal.

source
ManyUI._tc_boundMethod
_tc_bound(col::Column, wj::Int64, avail::Int64) -> Int64

w clamped into column j's min_width:max_width, resolved against avail. An AUTO bound is unbounded: 0 low, typemax(Int) high. Pure. Internal.

source
ManyUI._tc_capMethod
_tc_cap(col::Column, avail::Int64) -> Int64

The measurement cap for column j in an avail-cell content box: min(definite max_width, content-box width).

TC_MEASURE_CAP stands in for the content box before the first layout, when avail is still 0 and there is no viewport for the rule to name. Pure. Internal.

source
ManyUI._tc_cell_textMethod
_tc_cell_text(
    w::RowsWidget,
    src::Int64,
    j::Int64
) -> TextLike

The cell text of SOURCE row src, column j.

SOURCE, not view: the painter resolves view_source ONCE per row (it needs it for the selection anyway), so this is IDENTICAL for Table and DataTable and neither defines it. THAT is why there is no per-widget cell_text and no second paint loop.

w.cell is a CONCRETE field of a parametric struct, so this is a STATIC dispatch. Internal.

source
ManyUI._tc_col_atMethod
_tc_col_at(w::RowsWidget, e::MouseEvent) -> Int64

The 1-based column index under mouse event e, from grid_of(w).xs and widths – LAST frame's, which is exactly the header the user SAW and clicked. 0 for none, and 0 before the first paint. Internal.

source
ManyUI._tc_extentMethod
_tc_extent(w::RowsWidget) -> Size

Size(_tc_extent_width(w), view_count(w) + _tc_header_rows(w)).

THE + header_rows IS LOAD-BEARING AND IS NOT A ROUNDING ERROR. It is FORCED, and here is the proof rather than the assertion: Scrollbar._sb_metrics (scroll.jl:624-634) reads content_extent(w.viewport) and layout_of(w.viewport).content DIRECTLY and NEVER calls max_scroll. There is therefore NO seam that can tell a bar the body is hh rows shorter than the box, and OVERRIDING max_scroll WOULD NOT WORK. Counting the header restores

max_scroll.y = extent.height - content.height = (n + hh) - H
last body row visible  <=>  scroll.y + (H - hh) == n
                       <=>  scroll.y == n + hh - H

– the SAME number. So max_scroll, clamp_scroll, scroll_to!, scroll_into_view and thumb_span are all already right, and Scrollbar{Table{R,F,A}} needs not one line in scroll.jl. Drop the + hh and THE LAST hh ROWS OF EVERY TABLE ARE UNREACHABLE.

THE PRICE, stated rather than discovered: content_extent(t).height is a white lie – row_count(t) is the honest accessor and it is public and exported – and the thumb over-reports the visible fraction by hh/(n + hh), ~5% on a 20-row table with a header, decaying to nothing as the data grows. That is the cost of not inventing a second scrollable seam for one row.

O(1). ON THE FRAME PATH, several times – see _tc_resolve!. Internal.

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

The WIDTH _tc_extent reports, READ and never computed.

For a grid it is grid_of(w).cache_total, resolved against the CURRENT content box – which is what makes max_scroll(t).x correct BEFORE the first paint. A List overrides with its widest high-water mark, because it has no columns to resolve.

NOT in the architect's seam list, and it is the one addition this file makes to it: _tc_extent is specified as "grid_of(w).cache_total for a grid and the row-extent mark for a List", which is a per-widget branch the contract names but never gives a name to. grid_of has no List method, so the branch cannot be written any other way without a type-unstable Union{Nothing,TableGrid} probe on the FRAME PATH. Internal.

source
ManyUI._tc_follow_cursor!Method
_tc_follow_cursor!(w::RowsWidget)

Scroll the MINIMUM needed to bring the cursor's VIEW row into the BODY window, via scroll_into_view. A no-op before the first layout and with no cursor.

set_scroll! and NOT scroll_to!, mirroring _ta_follow_caret! (textarea.jl:483): scroll_into_view(off, H - hh, r-1, r-1) already returns a value in 0 : n + hh - H, which IS max_scroll(w).y by the identity in _tc_extent, so the clamp is provably redundant and would cost a content_extent call on every keystroke.

VERTICAL ONLY. THE CURSOR IS A ROW, NOT A CELL: there is no column cursor, so there is nothing to follow horizontally. Horizontal scrolling is the wheel's and the bar's job.

The two halves of the header arithmetic MUST agree: _tc_extent says n + hh, this says the window is H - hh. The suite asserts the identity. Internal.

source
ManyUI._tc_header_reserveMethod
_tc_header_reserve(w::RowsWidget, j::Int64) -> Int64

Cells reserved at the RIGHT of column j's header when seeding its AUTO mark. 0 by default. DataTable returns 1 for a sortable column, so a column sized to its header alone still has a cell for the indicator. Internal.

source
ManyUI._tc_header_rowsMethod
_tc_header_rows(w::RowsWidget) -> Int64

Rows of the content box that are pinned CHROME rather than data. 0 by default – a List has no header. Internal.

source
ManyUI._tc_header_textMethod
_tc_header_text(w::RowsWidget, j::Int64) -> String

The header text of column j. grid_of(w).cols[j].header by default; DataTable overrides to add its indicator. Internal.

source
ManyUI._tc_key!Method
_tc_key!(w::RowsWidget, d::Dispatch{KeyEvent}) -> Bool

UP/DOWN by one; PAGEUP/PAGEDOWN by one body LESS ONE ROW of overlap; HOME/END to the ends; SPACE toggles (MULTI); ENTER calls on_submit(w); LEFT/RIGHT scroll HORIZONTALLY by one cell. SHIFT on any movement key EXTENDS from the anchor. Returns true iff it consumed.

Each of the three widgets is exactly:

on_event!(w::List, d::Dispatch{KeyEvent})::Nothing =
    (_tc_key!(w, d); nothing)

CONSUMES ONLY WHEN SOMETHING ACTUALLY MOVED. That is _sp_move!'s rule (scroll.jl:435) applied to a cursor, and it is the whole of chaining: a List on its LAST row lets DOWN bubble to an outer Scrollpane. ENTER is the one exception and it is not one: firing on_submit IS the something that happened.

MODIFIER POLICY, a DELIBERATE and BOUNDED departure from the "unmodified keys only" rule TextInput/TextArea/Scrollpane follow: bare keys act, and SHIFT ALONE additionally extends. The rule exists so ctrl+a reaches an application binding (textinput.jl:477); the guard here leaves ctrl+, alt+ and super+* ENTIRELY to the app, and shift+arrow has exactly one meaning in every list ever shipped and is not a plausible accelerator.

CTRL+A IS NOT BOUND. select_all!(w) is public; bind! it.

TAB and ESCAPE FALL THROUGH UNCONSUMED, which is what keeps the tab order alive.

THERE IS NO CELL CURSOR. LEFT/RIGHT scroll; they do not walk columns. Internal.

source
ManyUI._tc_localMethod
_tc_local(w::Widget, e::MouseEvent) -> Offset

The pointer's position in w's CONTENT-box frame, 1-based, honouring every scrolled ancestor. (1, 1) is the first cell render! can write.

local_offset(d) IS FORBIDDEN IN THIS TIER AND THIS IS WHY. It is measured from region(d.current), the UNSHIFTED border box (events.jl:450). Scrollbar may use it only because "a scrollbar is never inside a scrolled subtree" (scroll.jl:683) – and a List inside a Scrollpane IS inside one. Inside a pane scrolled to y = 3 every click would select the row three above the pointer. painted_region's own docstring settles it: "THIS, not region(w), is what a hit test must compare a pointer against" (widget.jl:262).

O(depth), on a mouse event, NEVER on a frame. Pure. Internal.

source
ManyUI._tc_measureMethod
_tc_measure(s::TextLike, cap::Int64) -> Int64

text_width(truncate_width(s, cap)) – the width of s, CAPPED at cap cells. O(cap), NEVER O(length(s)).

THE bound on every AUTO measurement, and it is not a micro-optimisation: truncate_width (unicode.jl:138) breaks out of its grapheme loop the moment the budget is exceeded, so this is thousands of times cheaper than text_width on a 100 000-character cell. A 10 KB description column would otherwise cost 10 KB of grapheme iteration per measured cell to discover a fact truncate_width already knows. Pure. Internal.

source
ManyUI._tc_mouse!Method
_tc_mouse!(w::RowsWidget, d::Dispatch{MouseEvent}) -> Bool

Wheel scrolls the body, SHIFT swaps the axis; a LEFT PRESS puts the cursor on the row under the pointer and selects it; SHIFT extends from the anchor; CTRL toggles (MULTI); LEFT DRAG extends, so a press-drag paints a range. Returns true iff it consumed. Consumes ONLY when something changed.

A press on the HEADER rows returns false WITHOUT touching the selection, so DataTable can act on it in its own on_event!. There is no on_header_click! seam: per-concrete-type on_event! methods over this shared internal have no shadowing problem to escape.

A plain press routes through set_cursor!(w, k) and NOT select_only!(w, k): the two are IDENTICAL under SINGLE and MULTI (both re-pin the cursor and the anchor and select k alone), and they differ only under NONE, where select_only! is a no-op and a click would then not even move the cursor. A browsable list whose rows cannot be clicked onto is not browsable.

_tc_local, NEVER local_offset. Internal.

source
ManyUI._tc_moved!Method
_tc_moved!(w::RowsWidget, changed::Bool) -> Bool

Pure op, then _tc_touch! and _tc_follow_cursor!. THE single exit of every cursor/selection change, so neither can be forgotten at a call site. _ta_moved! (textarea.jl:449) is the precedent, line for line. Returns changed. Internal.

source
ManyUI._tc_only!Method
_tc_only!(s::Selection, i::Int64) -> Bool

Select i and nothing else, without touching the cursor or the anchor. True iff the set changed. Internal.

source
ManyUI._tc_pageMethod
_tc_page(w::RowsWidget) -> Int64

Rows one PAGEUP/PAGEDOWN travels: one body LESS ONE ROW of overlap, so the reader keeps a landmark. At least one, so an unlaid-out widget still moves. _ta_page and _sp_key_delta both say this; this is the third and last copy. Internal.

source
ManyUI._tc_replace_range!Method
_tc_replace_range!(s::Selection, lo::Int64, hi::Int64)

Replace the selection with the contiguous SOURCE range lo:hi, clipped to 1:n. A bitmap range, not hi - lo + 1 inserts. Internal.

source
ManyUI._tc_resolve!Method
_tc_resolve!(w::RowsWidget, avail::Int64) -> Vector{Int64}

Resolve every column of w to a cell width and an x offset for an avail-cell content box, MEMOIZED on (version, avail). Writes grid.widths, grid.xs, grid.cache_total and returns grid.widths.

BOTH render! AND _tc_extent CALL THIS, and that is what makes max_scroll(t).x correct BEFORE the first paint. Reading a widths field that only render! writes would make max_scroll(t).x == 0 – "cannot scroll" when it can – until the first frame.

NOT Pure, and this says so rather than hiding it: content_extent is documented Pure in scroll.jl:83 and this override writes a memo. The memo is OBSERVATIONALLY pure – same inputs, same output – and its key is (version, content-box width), NEITHER of which is a function of the scroll offset. That is the same property content_extent itself relies on ("the extent is independent of the current scroll offset and a scroll can never feed back into the extent"), and it is why the cache is safe. A future reader who trusts the base docstring will be surprised once; that is the price of not lying about max_scroll.

paint.jl guarantees render! and _tc_extent agree on avail: a widget "gets a frame that is its FULL content box, so size(buf) is the widget's real content box and is INVARIANT UNDER SCROLL" (buffer.jl:232), so size(buf)[1] == layout_of(w).content.width always and the key never thrashes within a frame.

ON A HIT: O(1) to decide, ZERO allocation, and the scratch comes back. ON A MISS: _apportion allocates – once per edit or resize, never per frame. Internal.

source
ManyUI._tc_resolve_now!Method
_tc_resolve_now!(g::TableGrid, avail::Int64)

Resolve every column of g for an avail-cell content box, writing widths, xs and cache_total. PURE with respect to the tree: a TableGrid and an Int in, three scratch fields out, which is what makes the whole of SS4 a table test.

1. `cells(n)`  -> `definite_size`. Exact, O(1).
2. `pct(p)`    -> `definite_size` against the CONTENT BOX.
3. `AUTO`      -> the cached mark `g.autos[j]`.
4. `fr(n)`     -> a share of `max(0, inner - sum(the above))` via
                  `_apportion`, the SAME largest-remainder-first
                  kernel `_arrange!` step 3 uses (layout.jl:417), so
                  `sum == leftover` EXACTLY and a column never
                  drifts a cell.

inner = avail - sep_w * (ncols - 1): the separators are paid for before the columns bid, which is _arrange!'s inner_main = main_avail - bs.gap * (n - 1) (layout.jl:388) verbatim.

NORMATIVE: cache_total MAY EXCEED avail, and that excess IS the horizontal scroll range. flex_distribute is NOT called: with no shrink and no grow factor it is provably the identity (layout.jl:73), so calling it to claim reuse would be ceremony. Internal.

source
ManyUI._tc_row_atMethod
_tc_row_at(w::RowsWidget, e::MouseEvent) -> Int64

The 1-based VIEW row under mouse event e; 0 for the header, the chrome, the margins or past the last row.

Arithmetic, not a hit test: scroll.y + local.y - hh. There are no row widgets to hit-test, which is "a row is not a widget" seen from the mouse's side – hit_test walks nodes, so widget-per-row would be O(n) on every POINTER MOVE, not merely every frame. Internal.

source
ManyUI._tc_row_styleMethod
_tc_row_style(
    w::RowsWidget,
    st::Style,
    src::Int64,
    foc::Bool
) -> Style

The style row src is painted in: st, REVERSED when selected, and additionally UNDERLINE on the cursor row while focused.

Two independent bits, because they are two independent facts: in MULTI the cursor is frequently NOT on a selected row, and a user who cannot see which row ENTER will act on has no cursor at all. An unfocused list shows its selection and hides its cursor – exactly as TextInput hides its caret on blur (textarea.jl:246). Internal.

source
ManyUI._tc_scroll_x!Method
_tc_scroll_x!(w::RowsWidget, d::Int64) -> Bool

Scroll w horizontally by d cells. True iff the offset moved. Internal.

source
ManyUI._tc_showMethod
_tc_show(s::AbstractString) -> AbstractString

x as an AbstractString, without copying one that already is: string(s::String) === s, so a List{String} formats for ZERO allocation per row. Internal.

source
ManyUI._tc_sync!Method
_tc_sync!(w::RowsWidget)

resize_selection!(selection_of(w), row_count(w)). Called at the TOP of render!, _tc_key! and _tc_mouse!.

THE self-healing guard, and it is the cure for the one footgun this whole family shares: items/rows are ALIASED and mutated behind version's back will not repaint. This makes a cursor past the end STRUCTURALLY IMPOSSIBLE even then – it downgrades the footgun from CORRUPTION to STALENESS. O(1) – one Int compare – when the count is unchanged, which is what makes it affordable every frame.

Mutating state inside render! is licensed by Scrollpane.render! (scroll.jl:389-415), which does exactly this and defends it at length: nothing here is a Reactive, so this marks NOTHING and cannot loop, and it converges. Internal.

source
ManyUI._tc_truncatedMethod
_tc_truncated(t::RichText, s::RichText) -> Bool

True when truncating s to t dropped something. The RichText reading of the rule above: truncate_width yields a PREFIX, so comparing the total text is comparing the same thing the string method compares – the styling cannot differ without the text differing first.

source
ManyUI._tc_truncatedMethod
_tc_truncated(
    t::SubString{String},
    s::AbstractString
) -> Bool

True when truncate_width actually cut something.

ncodeunits, NOT text_width(s) > cw: the obvious spelling walks the WHOLE untruncated string. _uw_string_backed (unicode.jl:123) returns a String/SubString{String} input UNCOPIED, so t shares s's codeunits and the comparison is exact. Pure. Internal.

source
ManyUI._tc_visible_colsMethod
_tc_visible_cols(
    g::TableGrid,
    off_x::Int64,
    width::Int64
) -> Tuple{Int64, Int64}

lo:hi, the columns the window [off_x + 1, off_x + width] touches. ONE O(ncols) scan per frame, so the per-row loop runs only over visible columns. (1, 0) when none. Pure. Internal.

source
ManyUI._tc_wheel!Method
_tc_wheel!(
    w::RowsWidget,
    d::Dispatch{MouseEvent},
    e::MouseEvent
) -> Bool

Scroll the body on a wheel notch; SHIFT swaps the axis. Consumes only when the offset actually moved, so a list at its limit lets the notch bubble to the next pane out – scroll chaining, out of the phase rule alone. Internal.

source
ManyUI.backend_capabilitiesFunction

Capabilities advertised by a backend.

The tuple is intentionally semantic rather than library-specific. Backends may add fields in their own metadata, but these common fields are stable: mouse, keyboard, text_input, focus, resize, transparency, animations, native_window, gpu and multi_session.

source
ManyUI.execute!Function
execute!(app, action::Action)

Domain logic entry point. Applications should implement this method for each of their specific Action subtypes to mutate the application model.

source
ManyUI.grid_ofFunction

The TableGrid of w. Table/DataTable only; a List has no columns and answers with a MethodError. No default. Pure.

source
ManyUI.is_selectedMethod
is_selected(w::RowsWidget, i::Int64) -> Bool

True when SOURCE row i of w is selected. O(1), 0 bytes. Pure.

source
ManyUI.is_selectedMethod
is_selected(s::Selection, i::Int64) -> Bool

True when SOURCE row i is selected.

O(1), ZERO allocation (MEASURED). FRAME PATH: one call per VISIBLE row. Total for any i, including i <= 0 and i > n. Pure.

source
ManyUI.move_cursor!Method
move_cursor!(w::RowsWidget, d::Int64; extend) -> Bool

Move the cursor by d VIEW rows, clamped. move_cursor!(w, typemax(Int) ÷ 2) is End and cannot run off it – ÷ 2 so that rank + d cannot overflow, which is _sp_key_delta's trick.

source
ManyUI.n_rowsMethod
n_rows(s::Selection) -> Int64

The number of SOURCE rows s is sized for. Pure.

source
ManyUI.n_selectedMethod
n_selected(w::RowsWidget) -> Int64

How many rows of w are selected. NEVER the frame path. Pure.

source
ManyUI.n_selectedMethod
n_selected(s::Selection) -> Int64

How many rows are selected. O(1) in NONE/SINGLE; O(n/64) in MULTI. NEVER the frame path. Pure.

source
ManyUI.post!Function
post!(app, event)

Schedule an event to be processed by the application loop.

source
ManyUI.reindex_delete!Method
reindex_delete!(s::Selection, i::Int64) -> Bool

As reindex_insert!, for a row DELETED at SOURCE index i. i itself is dropped from the selection; everything above moves down one; n shrinks. The cursor, if it was ON i, stays at i and is re-clamped, so it lands on the row that took i's place – or on the new last row.

source
ManyUI.reindex_insert!Method
reindex_insert!(s::Selection, i::Int64) -> Bool

Shift the selection, cursor and anchor for a row INSERTED at SOURCE index i: everything at or above i moves up one, and n grows.

An index-keyed selection is only correct while the indices mean what they meant. Source indices are structurally safe against REORDERING; they are NOT safe against insertion, and this is the price, paid by the mutation rather than by the frame. O(n_selected).

source
ManyUI.renderFunction
render(app, projection::Projection)

Presentation entry point. Applications should implement this method to project their current model state onto the targeted channel.

source
ManyUI.resize_selection!Method
resize_selection!(s::Selection, n::Int64) -> Bool

Resize to n SOURCE rows: drop selected rows above n, clamp the cursor and anchor into 1:n (0 when n == 0). O(1) – ONE Int compare – when n is unchanged, which is what lets render! call it every frame as a self-healing guard. See _tc_sync!.

source
ManyUI.row_anchorMethod
row_anchor(w::RowsWidget) -> Int64

The anchor's SOURCE row of w; 0 when there is no row. Pure.

source
ManyUI.row_anchorMethod
row_anchor(s::Selection) -> Int64

The anchor's SOURCE row; 0 iff there is no row. Pure.

source
ManyUI.row_cursorMethod
row_cursor(w::RowsWidget) -> Int64

The cursor's SOURCE row of w; 0 when there is no row. Pure.

source
ManyUI.row_cursorMethod
row_cursor(s::Selection) -> Int64

The cursor's SOURCE row; 0 iff there is no row. Pure.

source
ManyUI.sel_extend_ids!Method
sel_extend_ids!(s::Selection, ids) -> Bool

Select exactly the SOURCE rows in ids, replacing the selection. MULTI only; SINGLE falls back to select_only!(s, last(ids)).

ids is ANY iterable of Int, and that is the whole reason tablecore never learns what a permutation is:

sel_extend_ids!(s, min(a, b):max(a, b))            # List/Table
sel_extend_ids!(s, (w.order[k] for k in lo:hi))    # DataTable

Both ARGUMENTS allocate ZERO and Selection is none the wiser. Ids outside 1:n are DROPPED, never stored.

NORMATIVE: an extend REPLACES, it does not union. Shift+click after a run of ctrl+clicks replaces the selection with the anchor range – what every file manager does. toggle_row! is the documented escape.

source
ManyUI.select_all!Method
select_all!(w::RowsWidget) -> Bool

Select every row. MULTI only. ctrl+a is NOT bound to this: bind! it.

source
ManyUI.select_all!Method
select_all!(s::Selection) -> Bool

Select 1:n. MULTI only. O(n/64). A user gesture, NEVER a frame.

source
ManyUI.select_only!Method
select_only!(w::RowsWidget, k::Int64) -> Bool

Select VIEW row k and nothing else. A no-op under NONE.

source
ManyUI.select_only!Method
select_only!(s::Selection, i::Int64) -> Bool

Select i and NOTHING else; re-pin the cursor and the anchor to it. A no-op under NONE.

source
ManyUI.selected_rowsMethod
selected_rows(w::RowsWidget) -> Vector{Int64}

The selected SOURCE rows of w, ASCENDING. ALLOCATES; this is what an application calls after ENTER, never what render! calls. Pure.

source
ManyUI.selected_rowsMethod
selected_rows(s::Selection) -> Vector{Int64}

The selected SOURCE rows, ASCENDING. ALLOCATES. NEVER the frame path; this is what an application calls after ENTER. Pure.

source
ManyUI.set_cursor!Method
set_cursor!(w::RowsWidget, k::Int64; extend) -> Bool

Move the cursor to VIEW row k, clamped. 0 is Home, typemax(Int) is End.

VIEW, not source: "go to the third row on screen" is what a cursor means, and the selection stores SOURCE. view_source is the whole translation. A no-op returning false when view_count(w) == 0.

extend = true REPLACES the selection with the VIEW range from the anchor to k, mapped back through view_source – and NOT with the SOURCE range set_cursor!(::Selection, i; extend = true) would use. On a List or a Table the two are identical, because view_source is the identity. On a DataTable they are NOT: the user shift-arrowing down the screen means the rows BETWEEN the two ON SCREEN, which is what the generator below says and what a source range would get wrong.

source
ManyUI.set_cursor!Method
set_cursor!(s::Selection, i::Int64; extend) -> Bool

Move the cursor to SOURCE row i, CLAMPED to 1:n; 0 when n == 0. 0 is Home and typemax(Int) is End – "go as far as you can" needs no knowledge of how far that is, which is _sp_key_delta's trick (scroll.jl:475) reused rather than reinvented.

extend = false re-pins the anchor to i and, under SINGLE/MULTI, selects i ALONE. extend = true leaves the anchor and REPLACES the selection with anchor:i (MULTI), or moves the cursor alone (SINGLE/NONE – there is no range to extend to).

NORMATIVE: anchor:i is a SOURCE range, which is the only thing a Selection can mean – it has no view. A widget whose view is a PERMUTATION must extend over VIEW rows instead, and set_cursor!(::RowsWidget, k; extend = true) is where that happens.

source
ManyUI.toggle_row!Method
toggle_row!(w::RowsWidget, k::Int64) -> Bool

Flip VIEW row k's membership. MULTI only. See toggle_row!.

source
ManyUI.toggle_row!Method
toggle_row!(s::Selection, i::Int64) -> Bool

Flip i's membership; re-pin the cursor and anchor to it. MULTI only: under SINGLE this is a no-op returning false, because toggling the one selected row off would leave a single-select list with nothing selected, which is a contradiction. A no-op under NONE.

i is NOT clamped: clamping a toggle would flip the WRONG row.

source
ManyUI._precompile!Method
_precompile!()

Force compilation of the hot render path at precompile time.

Called once at the bottom of the module. Every directive is best-effort: a precompile call that fails to resolve returns false and is ignored.

source

Preferences

ManyUITUI.is_persistable_idMethod

True when id is stable enough to key a preference on.

gensym ids are not: gensym(:splitter) is ##splitter#277 this run and something else next. A widget whose geometry should survive a restart must be given an explicit id.

source
ManyUITUI.restore_splits!Method

Restore remembered splitter positions into root. Returns how many splitters were moved.

A remembered entry whose pane COUNT no longer matches is skipped: the tree has been rebuilt with a different shape since, and applying the old weights would either throw or silently mean something else.

source
ManyUITUI.restore_theme!Method

Put the remembered theme in force, if there is one and it is still registered. Returns the theme applied, or nothing.

A theme that has since been un-registered is IGNORED rather than thrown: a preference file outlives the code that wrote it, and a stale name in it must not stop the application starting.

source
ManyUITUI.restore_ui_prefs!Method

Apply the remembered theme and splitter positions to app.

The one verb an application calls on the way in. A theme swap does not dirty the tree – nothing in it holds a resolved colour – so this asks for a full repaint itself rather than leaving the caller to remember.

source
ManyUITUI.save_splits!Method

Remember where every splitter in root is dragged to.

Splitters with a gensym id are SKIPPED and their number returned, so a caller can say so rather than wondering why nothing came back. See is_persistable_id.

source
ManyUITUI.save_theme_pref!Method

Remember name as the theme to use next time.

Writes to the ACTIVE project's LocalPreferences.toml, so a theme is remembered per project rather than per user – which is what an application wants when two of them disagree about what looks right.

source