The event-value model
Raw input becomes intent
Mouse hardware reports presses, releases and moves. What a widget actually wants to know is intent: was that a click, the start of a drag, or a double-click? Kinogaki UI answers that with an event-value model synthesized at the platform layer, so widgets handle intent instead of reconstructing it from raw down/up themselves.
Every Event carries a value (None, Press, Release, Click, DoubleClick, or Drag) alongside its raw type. The synthesizer derives it:
- Click: a press and release with no movement past a small drag threshold (≈6 px).
- Drag: a held move that crosses the threshold (every subsequent move while held is a
Drag). - DoubleClick: a second press near the first within a short time window.
bool Button::handle(const Event& e) {
if (e.type == EventType::ButtonDown && contains(e)) { capture(); return true; }
if (e.type == EventType::ButtonUp && active_) {
release();
if (e.value == EventValue::Click && onClick) onClick(); // a clean click fires; a drag cancels
return true;
}
return false;
}
Why it matters
Without synthesized intent, each widget reconstructs click-vs-drag with its own pixel fudge factor, and the factors drift apart. With it, the rule is uniform: a button fires on a clean click but cancels if you press it and drag away; a slider starts scrubbing only once you cross the threshold; a tree row selects on click but a drag begins a reorder. App.run feeds every raw event through the synthesizer before dispatch, so this stays backward-compatible: a widget that ignores value still sees ordinary presses and releases.
Normalized input
The platform layer does the rest of the smoothing before events even reach the synthesizer: modifier keys are unified across operating systems; macOS's Option-drag is mapped to a middle-button gesture for the whole press-drag-release; physical key positions are mapped so a keyboard shortcut binds the same chord regardless of layout. By the time an event reaches a widget, every platform quirk is gone and the only question left is intent.
Keyboard
A keymap dispatches keyboard shortcuts before region routing, so a bound chord (⌘Z, ⌘S) fires from anywhere, and because every binding carries a modifier, plain typing falls through to the focused widget untouched. Focus follows the last click (not the cursor), so typing into a field is robust even as the pointer moves away. The intent model and the keymap together give the toolkit the feel of a desktop app.