[ Architecture ]
Focus System Architecture
Overview
Section titled “Overview”koto::FocusManager (include/koto/focus.hpp, src/focus.cpp) decouples input routing and focus state from widget layout. Widgets stamp Id tokens into Frame::m_hit_buffer and Frame::m_tag_bounds during on_render, and FocusManager::sync_geometry(const Frame&) harvests those rectangles after each frame draw to drive 1D ring traversal, 2D spatial navigation, and mouse hit-testing.
Mermaid diagram
Section titled “Mermaid diagram”flowchart TD
DrawFrame["App::draw_frame"] --> OnRender["m_render_handler(m_frame)"]
OnRender --> TagArea["Frame::tag_area / Frame::set_cell"]
TagArea --> HitBuf["Frame::m_hit_buffer & Frame::m_tag_bounds"]
DrawFrame --> SyncGeom["FocusManager::sync_geometry(m_frame)"]
HitBuf --> BoundsOf["Frame::bounds_of(target.id)"]
BoundsOf --> SyncGeom
Dispatch["App::dispatch_event"] --> HandleKey["FocusManager::handle_key(const KeyEvent&)"]
HandleKey --> Interceptor["m_key_interceptor"]
Interceptor -->|unconsumed| TabRing["Key::Tab -> next() / Key::BackTab -> prev()"]
TabRing -->|unconsumed| AltSpatial["FocusNavPolicy::AltArrows -> move(Direction)"]
AltSpatial -->|unconsumed| TargetKey["m_targets[m_active_index].handle_key(key)"]
TargetKey -->|unconsumed| Spatial["FocusNavPolicy::SpatialArrows -> move(Direction)"]
Dispatch --> HandleMouse["FocusManager::handle_mouse(const MouseEvent&, const Frame&)"]
HandleMouse --> HitAt["Frame::hit_at(mouse.row, mouse.col)"]
HitAt --> FocusIdx["FocusManager::focus(i) & m_targets[i].handle_mouse(mouse)"]
Walkthrough
Section titled “Walkthrough”- Target registration and in-place update (
src/focus.cpp:50-128):FocusManager::add(T& state, Id custom_id)constructs aFocusTargetwith.id = scoped_id(has_custom ? custom_id : Id(&state))and.modal_scope = active_modal_scope().register_target(FocusTarget)scansm_targetsfor an existing entry with the sameid. If found, it updatesbounds,modal_scope, and callbacks in place and callsset_focus(i == m_active_index)to keep.focusedsynchronized.- If the target is new and
m_targetswas empty, it callstarget.set_focus(true)and setsm_active_index = 0. If amodal scopeis currently active and the currently focused target is outside that modal,register_targetimmediately callsfocus(new_idx).
- Post-render geometry harvesting (
src/focus.cpp:382-406):- After
m_render_handler(m_frame)completes inApp::draw_frame(),FocusManager::sync_geometry(m_frame)updatestarget.bounds = frame.bounds_of(target.id).value_or(Area{0, 0, 0, 0})for every target. - If
has_geometry()istrueand the currently active target is no longer focusable (!m_targets[m_active_index].is_focusable(true), e.g. it was not rendered on this frame or was disabled),sync_geometryautomatically callsnext()to move focus to a visible target.
- After
- Key routing pipeline (
src/focus.cpp:537-589):- Step 1: Invokes
m_key_interceptor(*this, key)if set (even ifm_targetsis empty). - Step 2: Matches
Key::BackTab(prev()) andKey::Tab(next()). - Step 3: If
m_policy == FocusNavPolicy::AltArrows && key.alt, matchesUp/Down/Left/Right(orAltUp..AltRight) and callsmove(Direction). - Step 4: Invokes
m_targets[m_active_index].handle_key(key). - Step 5: If
m_policy == FocusNavPolicy::SpatialArrowsand step 4 returnedfalse, matchesKey::Up/Down/Left/Rightand callsmove(Direction).
- Step 1: Invokes
- 2D spatial scoring (
src/focus.cpp:408-498):FocusManager::move(Direction dir)computes the center of the active target (cur_mid_r,cur_mid_c) and filters candidates with validboundsand matchingmodal_scope == active_modal_scope().- A candidate is eligible for
Direction::Rightifcand_mid_c > cur_mid_c(withprimary_dist = std::max(0, cand.bounds.col - current.bounds.right())andsecondary_dist = std::abs(cand_mid_r - cur_mid_r)), and analogously forLeft,Down, andUp. - The candidate with the minimum
score = primary_dist + 2 * secondary_distis focused viafocus(best_index).
Invariants
Section titled “Invariants”- Modal isolation: When
active_modal_scope()is notId::none(),next(),prev(),focus(size_t), andmove(Direction)reject any target whosemodal_scope != active_modal_scope()(src/focus.cpp:272, 300, 323, 436).handle_mouse()returnstrue(absorbing the event without forwarding it) whenever a mouse press lands outside the modal or on a target withmodal_scope != current_modal(src/focus.cpp:642-660, 694-697). - Modal target pruning on
pop_scope(): When aScopeMode::Modalentry is popped fromm_scope_stack,pop_scope()runsstd::erase_if(m_targets, [popped_id](const FocusTarget& t) { return t.modal_scope == popped_id; })and restores focus to theIdpopped fromm_saved_focus_stack(src/focus.cpp:754-776). - Pointer-hash collision guard: When two targets have
has_custom_id == false(both derived viaId(&state)) and share the same 32-bitId,register_targetassertsm_targets[i].state_ptr == target.state_ptrso distinct state objects with a hash collision are caught immediately (src/focus.cpp:63-68).