Skip to main content

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}