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 valueNone, Press, Release, Click, DoubleClick, or Drag — alongside its raw type. The synthesizer derives it:

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.