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? Prism 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 does cross 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, every widget reinvents click-vs-drag with its own pixel fudge factors, and they disagree. With it, the rule is uniform and correct everywhere: 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 is 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 are what make the toolkit feel like a real desktop app rather than a canvas with click handlers.