Events, input and dispatch
ManyUI._KEY_NAMES — Constant
Every accepted key NAME, already lowercased. Keys not listed here are single characters, which are taken literally (case included) – only names fold case.
ManyUI._WHEEL_BUTTONS — Constant
The four wheel pseudo-buttons. Scroll-ness is a property of the button, never of the action.
ManyUI.Dispatch — Type
mutable struct Dispatch{E<:Event}Mutable envelope around an IMMUTABLE event.
Consumption state lives here, so an event can be replayed, logged, or fed to a second tree without contamination. Parametric on E so handlers dispatch on the concrete event type with no boxing.
Fields
event::Eventtarget::Widgetcurrent::Widget: The widget currently being visited.phase::ManyUI.Phase.T: Capture, at-target or bubble.consumed::Bool: Set byconsume!; stops propagation.
ManyUI.Dispatch — Method
Envelope e for target, starting in Phase.CAPTURE, unconsumed.
current starts at target so an envelope that is never walked still reads consistently; a walk overwrites it before each on_event!.
ManyUI.FocusEvent — Type
struct FocusEvent <: EventThe terminal or browser tab gained or lost focus.
Fields
gained::Bool: True on focus gained, false on focus lost.
ManyUI.KeyEvent — Type
struct KeyEvent <: EventA keystroke. char is meaningful only when code === Key.CHAR; otherwise it is '\0'.
Fields
code::ManyUI.Key.T: Logical key.char::Char: Character produced, whencode === Key.CHAR.mods::Modifiers: Active modifiers.
ManyUI.Modifiers — Type
struct ModifiersA bitset of Modifier.T values. isbits.
Fields
bits::UInt8: Bitwise OR of the activeModifier.Tvalues.
ManyUI.Modifiers — Method
Modifiers(ms::ManyUI.Modifier.T...) -> Modifiers
The bitset of the given modifiers. Duplicates collapse and the argument order is irrelevant: this is a set union. Pure.
ManyUI.Modifiers — Method
Modifiers() -> Modifiers
No modifiers.
ManyUI.MouseEvent — Type
struct MouseEvent <: EventA mouse action. x/y are 1-based ABSOLUTE screen cells; widget-local coordinates come from local_offset(d), never hand-rolled at the call site.
Fields
action::ManyUI.MouseAction.T: Press, release, move or drag.button::ManyUI.MouseButton.T: Button involved, or a wheel direction.x::Int64: Absolute 1-based column.y::Int64: Absolute 1-based row.mods::Modifiers: Active modifiers.
ManyUI.PasteEvent — Type
struct PasteEvent <: EventA completed bracketed paste.
Fields
text::String: The pasted text, verbatim.
ManyUI.QuitEvent — Type
struct QuitEvent <: EventRequests loop shutdown. handle! sets app.running = false.
Fields
ManyUI.RefreshEvent — Type
struct RefreshEvent <: EventForces a full repaint. THE reconnect / wake seam: because it travels the same Channel{Event} as everything else, events(d) really is the only way into the App. handle!(app, ::RefreshEvent) calls invalidate! internally.
Fields
ManyUI.ResizeEvent — Type
struct ResizeEvent <: EventThe renderable area changed. Never parsed from bytes: injected by a driver through notify_resize!.
Fields
size::Size: The new renderable area.
ManyUI.TickEvent — Type
struct TickEvent <: EventA timer tick.
Fields
time::Float64: Wall clock stamp of the tick.
Base.isempty — Method
isempty(ms::Modifiers) -> Bool
True when no modifier is active. Pure.
Base.parse — Method
parse(_::Type{KeyEvent}, s::AbstractString) -> KeyEvent
Parse a key description.
Accepts "q", "ctrl+c", "shift+tab", "f1", "alt+enter", "ctrl+shift+left", "escape", "space". Case-insensitive on names. Throws ArgumentError on garbage. This is what makes every binding and every press! test a one-liner.
Base.tryparse — Method
tryparse(
_::Type{KeyEvent},
s::AbstractString
) -> Union{Nothing, KeyEvent}
Parse a key description, returning nothing instead of throwing. Pure.
ManyUI._mods — Method
_mods(ctrl::Bool, alt::Bool, shift::Bool) -> Modifiers
The modifier bitset for the three keyword flags key and the key parser share. Pure.
ManyUI._parse_key_name — Method
_parse_key_name(
name::AbstractString
) -> Union{Nothing, Tuple{ManyUI.Key.T, Any}}
The (code, char) a key name denotes, or nothing. A one-character name is a literal character; anything longer must be in _KEY_NAMES. Pure.
ManyUI._parse_modifier — Method
_parse_modifier(
name::AbstractString
) -> Union{Nothing, ManyUI.Modifier.T}
The Modifier.T a name denotes, or nothing. Case-insensitive. The four canonical names only – an unknown name is garbage, not a key. Pure.
ManyUI._split_key_spec — Method
_split_key_spec(
s::AbstractString
) -> Union{Nothing, Tuple{Union{BitVector, Vector}, String}}
Split a key description into its modifier names and its key name, or nothing when the shape is garbage.
+ is both the separator and a legitimate key, so a trailing + is the key itself: "+" is plus, "ctrl++" is ctrl plus. "ctrl+" is ambiguous and is therefore rejected rather than guessed at. Pure.
ManyUI.consume! — Method
consume!(d::Dispatch)
Stop propagation. One-way: an event cannot be un-consumed, which is why a walk need only ever test is_consumed, never a phase-specific flag.
ManyUI.event — Method
event(d::Dispatch{E}) -> Any
The wrapped event. Type-stable accessor. Pure.
ManyUI.is_consumed — Method
is_consumed(d::Dispatch) -> Bool
True once consume! has been called. Pure.
ManyUI.is_scroll — Method
is_scroll(e::MouseEvent) -> Bool
True for the four wheel pseudo-buttons. Pure.
ManyUI.key — Method
key(c::Char; ctrl, alt, shift) -> KeyEvent
A character keystroke. Pure.
ManyUI.key — Method
key(k::ManyUI.Key.T; ctrl, alt, shift) -> KeyEvent
A non-character keystroke. char is '\0', because it is meaningful only when code === Key.CHAR. Pure.
ManyUI.local_offset — Method
local_offset(d::Dispatch{MouseEvent}) -> Offset
The event position relative to region(d.current), as 1-based widget-local coordinates. Defined only for Dispatch{MouseEvent}.
Relative to current, NOT to target: a capture-phase handler on an ancestor gets the point in ITS own box. Out-of-box points are returned as-is, never clamped – the caller decides what a miss means. Pure.
ManyUI.on_event! — Method
on_event!(w::Widget, d::Dispatch)
E3. THE handler hook, with a default no-op fallback. Users add methods on (their widget type, their event type):
on_event!(b::MyButton, d::Dispatch{KeyEvent}) = ...Dispatch on both axes – no callback registry, no Dict{Symbol,Function}, no closures. Call consume!(d) to stop propagation.
ManyUI._path_within — Method
_path_within(root::Widget, target::Widget) -> Vector{Widget}
propagation_path(target) truncated to begin at root, so a subtree can be propagated through without the event reaching the widgets above it.
Falls back to the full path when root is not an ancestor of target. Pure. Internal.
ManyUI._route — Method
_route(
root::Widget,
_::Event,
_::Union{Nothing, Widget}
) -> Widget
Everything else – resize, focus, tick and any user-defined Event – is a whole-tree concern and goes to root, focus notwithstanding. Internal.
ManyUI._route — Method
_route(
root::Widget,
_::KeyEvent,
focus::Union{Nothing, Widget}
) -> Widget
Keystrokes go to the focused widget, and to root when nothing has focus. Internal.
ManyUI._route — Method
_route(
root::Widget,
e::MouseEvent,
_::Union{Nothing, Widget}
) -> Widget
A mouse event goes to whatever is under the pointer, and to root when the pointer is outside the tree entirely. Internal.
ManyUI._route — Method
_route(
root::Widget,
_::PasteEvent,
focus::Union{Nothing, Widget}
) -> Widget
A paste goes wherever a keystroke would. Internal.
ManyUI._route — Method
_route(
_::Widget,
_::Union{QuitEvent, RefreshEvent},
_::Union{Nothing, Widget}
)
RefreshEvent and QuitEvent are App-level only: the tree never sees them. Internal.
ManyUI._walk! — Method
_walk!(d::Dispatch, path::Vector{Widget}) -> Bool
The capture, at-target and bubble walk over path, which runs root first and ends at path[1]. Stops the instant the event is consumed. Returns true iff it was. Internal.
ManyUI.dispatch_event! — Function
dispatch_event!(root::Widget, e::Event) -> Bool
dispatch_event!(
root::Widget,
e::Event,
focus::Union{Nothing, Widget}
) -> Bool
E3. Route, then propagate. Returns true iff consumed.
NORMATIVE routing table:
MouseEvent -> `hit_test(root, e.x, e.y)`, falling back
to `root` when nothing is hit
KeyEvent, PasteEvent -> `focus`, falling back to `root` when
`focus === nothing`
ResizeEvent,
FocusEvent, TickEvent -> `root`
RefreshEvent,
QuitEvent -> NOT ROUTED (App-level only; returns false)ManyUI.focusable_widgets — Method
focusable_widgets(root::Widget) -> Vector{Widget}
The tab order: every visible, focusable widget in pre-order. Pure.
ManyUI.hit_test — Method
hit_test(
root::Widget,
x::Int64,
y::Int64
) -> Union{Nothing, Widget}
The deepest VISIBLE widget whose painted_region contains the point, or nothing when nothing is hit.
Pre-order descent; Display.NONE and invisible subtrees are skipped entirely. Pure.
ManyUI.hit_test — Method
hit_test(root::Widget, o::Offset) -> Union{Nothing, Widget}
The deepest VISIBLE widget whose painted_region contains o. Pure.
painted_region, NOT region: layout computes ABSOLUTE boxes and knows nothing about scrolling, so inside a scrolled subtree region(w) is where w WOULD be at scroll zero, while the pointer names a cell on the SCREEN. Comparing against the unshifted box targets whatever happens to occupy w's unscrolled slot – with a pane scrolled to y = 3, row 1 displays L4 but the click lands on L1.
The two agree exactly outside a scrolled subtree (paint_offset is then ORIGIN), so this is a no-op for every unscrolled tree. It costs O(depth) per node visited, on the MOUSE path only, never on the frame path – _paint_node! threads the same shift down for free and does not pay this.
Clipping needs no separate test: a node scrolled out of its pane is only reached by descending THROUGH the pane, whose own box is unshifted and already rejected the point.
ManyUI.propagate! — Method
propagate!(d::Dispatch) -> Bool
E3. Propagate an already-built envelope. Returns true iff consumed.
ManyUI.propagate! — Method
propagate!(root::Widget, target::Widget, e::Event) -> Bool
E3. A pure TREE WALK – it knows nothing about App or focus. Returns true iff the event was consumed.
path = propagation_path(target)- CAPTURE: walk the path root to target, EXCLUDING target, with
d.phase = Phase.CAPTURE - ATTARGET: `d.phase = Phase.ATTARGET`, call on target
- BUBBLE: walk the path target to root, EXCLUDING target, with
d.phase = Phase.BUBBLE
d.current is set to each visited widget before on_event!(w, d), and the walk STOPS the instant is_consumed(d).
ManyUI.propagation_path — Method
propagation_path(target::Widget) -> Vector{Widget}
Root to target, inclusive of both.
Separately named and PURE so the capture/bubble order assertion is a literal vector comparison against a stub tree.