engine_observables_api/interaction.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Focus, selection, input affordances, activation targets. The
6//! bridge between observable geometry and user input.
7//!
8//! Cf. Hekate doc §"Interaction Plane". The host stores a per-tile
9//! lane handle and queries this trait on input — Hekate is *not* on
10//! the per-event hot path.
11
12use malloc_size_of_derive::MallocSizeOf;
13use serde::{Deserialize, Serialize};
14
15use crate::types::{Point, SourceNodeId, SourceRange};
16
17/// Common-minimum interaction queries every lane publishes.
18pub trait InteractionQuery {
19 /// Current focused node, if any.
20 fn focus_target(&self) -> Option<SourceNodeId>;
21
22 /// Current selection (the active range across the lane's source).
23 fn selection(&self) -> Option<Selection>;
24
25 /// Affordances at a point — what *kinds* of interaction the user
26 /// can perform here (link, button, scrollable, editable, etc.).
27 /// Returns multiple entries for stacked affordances (e.g., a
28 /// link inside a scrollable region — both are reachable).
29 fn affordances_at(&self, point: Point) -> Vec<Affordance>;
30
31 /// What gets activated when the user clicks at `point`.
32 /// Distinct from `affordances_at` — affordances is "what's
33 /// possible", activation_target is "what would happen on
34 /// default-click."
35 fn activation_target(&self, point: Point) -> Option<SourceNodeId>;
36}
37
38/// The host's current interaction snapshot, fed *into* the lane to drive
39/// dynamic pseudo-classes and answered back by [`InteractionQuery`].
40///
41/// This is the input twin of the read-only [`InteractionQuery`] trait: the
42/// host owns input state (which node the pointer is over, which is pressed,
43/// which is focused, the active selection) and hands a snapshot to the lane,
44/// which (a) populates each affected element's cascade state so `:hover` /
45/// `:active` / `:focus` / `:focus-within` rules match and restyle, and (b)
46/// answers `focus_target()` / `selection()` from the same snapshot — one
47/// source for both the cascade and the queries.
48///
49/// CSS scoping is applied when the snapshot is resolved to element state:
50/// `:hover` / `:active` match the target *and its ancestors* (you hover a
51/// button by hovering its label), `:focus` matches only the focused element,
52/// and `:focus-within` matches the focused element and its ancestors.
53#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
54pub struct InteractionState {
55 /// The node the pointer is currently over (the deepest one), or `None`.
56 /// Drives `:hover` on it and every ancestor.
57 pub hovered: Option<SourceNodeId>,
58 /// The node currently being pressed (the deepest one), or `None`.
59 /// Drives `:active` on it and every ancestor.
60 pub active: Option<SourceNodeId>,
61 /// The focused node, or `None`. Drives `:focus` on it and `:focus-within`
62 /// on it and every ancestor.
63 pub focused: Option<SourceNodeId>,
64 /// The active selection, if any. Answered back by [`InteractionQuery::selection`].
65 pub selection: Option<Selection>,
66}
67
68/// One active selection range.
69#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
70pub struct Selection {
71 /// Anchor of the selection (where the user first pressed).
72 pub anchor: SourceNodeId,
73 /// Focus end (where the cursor currently is). Equal to `anchor`
74 /// for a caret/collapsed selection.
75 pub focus: SourceNodeId,
76 /// Source-range form of the selection — what `text_range_for_*`
77 /// equivalents produce, normalized so `start <= end`.
78 pub range: SourceRange,
79}
80
81/// One affordance the user can act on at a given point.
82#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
83pub struct Affordance {
84 pub kind: AffordanceKind,
85 /// Source node that publishes this affordance. The host uses
86 /// this for hover tooltips, cursor changes, focus-ring rendering.
87 pub source_node: SourceNodeId,
88 /// Optional descriptive label (link href, button label, etc.).
89 /// `None` if the affordance has no canonical short label.
90 pub label: Option<String>,
91}
92
93#[derive(
94 Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize,
95)]
96pub enum AffordanceKind {
97 /// Hyperlink — left-click navigates, hover shows URL.
98 Link,
99 /// Button — left-click activates.
100 Button,
101 /// Editable region (text field, contenteditable).
102 Editable,
103 /// Scrollable container — wheel/drag scrolls the container.
104 Scrollable,
105 /// Hover target with no default activation (e.g., abbr title).
106 #[default]
107 Hoverable,
108 /// Form input with non-text affordance (checkbox, radio,
109 /// select, etc.).
110 FormControl,
111}