Events, input and dispatch

ManyUI._KEY_NAMESConstant

Every accepted key NAME, already lowercased. Keys not listed here are single characters, which are taken literally (case included) – only names fold case.

source
ManyUI.DispatchType
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::Event

  • target::Widget

  • current::Widget: The widget currently being visited.

  • phase::ManyUI.Phase.T: Capture, at-target or bubble.

  • consumed::Bool: Set by consume!; stops propagation.

source
ManyUI.DispatchMethod

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!.

source
ManyUI.FocusEventType
struct FocusEvent <: Event

The terminal or browser tab gained or lost focus.

Fields

  • gained::Bool: True on focus gained, false on focus lost.
source
ManyUI.KeyEventType
struct KeyEvent <: Event

A keystroke. char is meaningful only when code === Key.CHAR; otherwise it is '\0'.

Fields

  • code::ManyUI.Key.T: Logical key.

  • char::Char: Character produced, when code === Key.CHAR.

  • mods::Modifiers: Active modifiers.

source
ManyUI.ModifiersType
struct Modifiers

A bitset of Modifier.T values. isbits.

Fields

  • bits::UInt8: Bitwise OR of the active Modifier.T values.
source
ManyUI.ModifiersMethod
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.

source
ManyUI.MouseEventType
struct MouseEvent <: Event

A 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.

source
ManyUI.PasteEventType
struct PasteEvent <: Event

A completed bracketed paste.

Fields

  • text::String: The pasted text, verbatim.
source
ManyUI.QuitEventType
struct QuitEvent <: Event

Requests loop shutdown. handle! sets app.running = false.

Fields

source
ManyUI.RefreshEventType
struct RefreshEvent <: Event

Forces 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

source
ManyUI.ResizeEventType
struct ResizeEvent <: Event

The renderable area changed. Never parsed from bytes: injected by a driver through notify_resize!.

Fields

  • size::Size: The new renderable area.
source
ManyUI.TickEventType
struct TickEvent <: Event

A timer tick.

Fields

  • time::Float64: Wall clock stamp of the tick.
source
Base.:|Method
|(a::Modifiers, b::ManyUI.Modifier.T) -> Modifiers

Add a modifier to the set. Pure.

source
Base.:|Method
|(a::Modifiers, b::Modifiers) -> Modifiers

Union of two modifier sets. Pure.

source
Base.inMethod
in(m::ManyUI.Modifier.T, ms::Modifiers) -> Bool

True when m is in the set. Pure.

source
Base.isemptyMethod
isempty(ms::Modifiers) -> Bool

True when no modifier is active. Pure.

source
Base.parseMethod
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.

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

Parse a key description, returning nothing instead of throwing. Pure.

source
ManyUI._modsMethod
_mods(ctrl::Bool, alt::Bool, shift::Bool) -> Modifiers

The modifier bitset for the three keyword flags key and the key parser share. Pure.

source
ManyUI._parse_key_nameMethod
_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.

source
ManyUI._parse_modifierMethod
_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.

source
ManyUI._split_key_specMethod
_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.

source
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.

source
ManyUI.eventMethod
event(d::Dispatch{E}) -> Any

The wrapped event. Type-stable accessor. Pure.

source
ManyUI.is_scrollMethod
is_scroll(e::MouseEvent) -> Bool

True for the four wheel pseudo-buttons. Pure.

source
ManyUI.keyMethod
key(c::Char; ctrl, alt, shift) -> KeyEvent

A character keystroke. Pure.

source
ManyUI.keyMethod
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.

source
ManyUI.local_offsetMethod
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.

source
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.

source
ManyUI._path_withinMethod
_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.

source
ManyUI._routeMethod
_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.

source
ManyUI._routeMethod
_route(
    root::Widget,
    _::KeyEvent,
    focus::Union{Nothing, Widget}
) -> Widget

Keystrokes go to the focused widget, and to root when nothing has focus. Internal.

source
ManyUI._routeMethod
_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.

source
ManyUI._routeMethod
_route(
    root::Widget,
    _::PasteEvent,
    focus::Union{Nothing, Widget}
) -> Widget

A paste goes wherever a keystroke would. Internal.

source
ManyUI._routeMethod
_route(
    _::Widget,
    _::Union{QuitEvent, RefreshEvent},
    _::Union{Nothing, Widget}
)

RefreshEvent and QuitEvent are App-level only: the tree never sees them. Internal.

source
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.

source
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)
source
ManyUI.focusable_widgetsMethod
focusable_widgets(root::Widget) -> Vector{Widget}

The tab order: every visible, focusable widget in pre-order. Pure.

source
ManyUI.hit_testMethod
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.

source
ManyUI.hit_testMethod
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.

source
ManyUI.propagate!Method
propagate!(d::Dispatch) -> Bool

E3. Propagate an already-built envelope. Returns true iff consumed.

source
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.

  1. path = propagation_path(target)
  2. CAPTURE: walk the path root to target, EXCLUDING target, with d.phase = Phase.CAPTURE
  3. ATTARGET: `d.phase = Phase.ATTARGET`, call on target
  4. 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).

source
ManyUI.propagation_pathMethod
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.

source