[ Concepts ]
IDs and the Spatial Hit Buffer
The idea in plain words
Section titled “The idea in plain words”Terminal emulators report mouse clicks as (row, col) coordinates, not widget references. Instead of maintaining a separate scene graph to intersect mouse coordinates against widget rectangles, koto::Frame stores a parallel hit buffer (std::vector<int> m_hit_buffer) of the exact same dimensions as the cell grid and records each widget’s id as it draws.
How koto implements it
Section titled “How koto implements it”- Universal
Idtokens:koto::Id(include/koto/id.hpp:52) hashes strings (Id("save")), indexed strings (Id("item", i)), 2D coordinates (Id("cell", r, c)), or object pointers (Id(&state)) into a 32-bit unsigned integer usingfnv1a_32. If FNV-1a produces0xFFFFFFFFu,fnv1a_32remaps it to0xFFFFFFFEuso no valid hash collides withTAG_NONE(-1). - Stamping the
hit bufferandm_tag_bounds:- At the start of each frame,
Frame::clear_back_buffer()fillsm_hit_bufferwithTAG_NONE(-1) and clearsm_tag_bounds(src/frame.cpp:183-184). - Whenever a widget calls
frame.set_cell(r, c, cell, tag_id),frame.fill(area, cell, tag_id),frame.tag_cell(r, c, tag_id), orframe.tag_area(area, tag_id)with!tag_id.is_none(),Framewritesstatic_cast<int>(tag_id.value())intom_hit_buffer[r * m_cols + c]and unions the cell orareaintom_tag_bounds[tag_id.value()](src/frame.cpp:127-140, 464-507). - Later draws overwrite earlier draws in
m_hit_buffer(painter’s algorithm), so a modal dialog drawn last automatically claims the hit buffer cells underneath it.
- At the start of each frame,
O(1)lookup and geometry harvesting:Frame::hit_at(row, col)indexesm_hit_buffer[row * m_cols + col]inO(1)time (src/frame.cpp:509-513).- After
on_renderfinishes,App::draw_frame()callsFocusManager::sync_geometry(m_frame), which queriesframe.bounds_of(target.id)(O(1)hash map lookup inm_tag_bounds) to populate eachFocusTarget::bounds(src/focus.cpp:382-395).
Box() .title("Sidebar") .id(Id("sidebar")) .draw(frame, sidebar_area);
Id hit = frame.hit_at(sidebar_area.row, sidebar_area.col);static_cast<void>(hit);Consequences for your code
Section titled “Consequences for your code”- Stateful widgets tag themselves automatically:
List,Tabs,TextInput, andTextAreaautomatically tag their drawnareawithId(&m_state)(unless overridden via.id(Id(...))). You do not need to call.id(...)on them to use mouse clicking or 2D spatial navigation withFocusManager. - Stateless
Box,Button,Fill, andClearrequire.id(Id(...)): To hit-test or focus aBoxorButton, pass.id(Id("my_id"))when drawing it. - When using
FocusManager::scope, tag withfm.id(...): Inside a normal or modalscope,FocusManagerprefixes target IDs with the active scope hash. Pass.id(fm.id("btn"))when drawing a statelessBoxso thehit buffertag matches the scopedFocusTarget::id.