1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! [`UiState`] — the renderer's interaction-state side store.
//!
//! Holds pointer position, hovered/pressed/focused targets, per-node
//! scroll offsets, the app-supplied hotkey registry, and the per-(node,
//! prop) animation map. The host doesn't touch this directly; backend
//! runners such as `aetna_wgpu::Runner` own one and route input events
//! through it.
//!
//! Visual delta application: if `pressed` is set, that node renders with
//! `state = Press`. Otherwise, if `hovered` is set, that node renders
//! with `state = Hover`. Press takes precedence so clicking a button
//! that's also hovered shows the press visual, not the hover visual.
//! Focus is independent of both — the focus ring is its own envelope.
mod animation;
mod click;
mod cursor;
mod focus;
mod interaction;
mod keyboard;
pub(crate) mod query;
mod scroll;
mod selection;
mod toast;
mod types;
mod widget_state;
use std::fmt::Debug;
// `web_time::Instant` is API-identical to `std::time::Instant` on
// native and uses `performance.now()` on wasm32 — std's `Instant::now()`
// panics in the browser because there is no monotonic clock there.
use crate::event::{KeyModifiers, PointerButton, UiTarget};
pub use types::{AnimationMode, EnvelopeKind, ScrollMetrics, ThumbDrag, WidgetState};
pub(crate) use types::{
SelectionDrag, SelectionDragGranularity, VirtualAnchor, caret_blink_alpha_for,
};
use types::{
AnimationState, CaretState, ClickState, FocusState, HotkeyState, LayoutState,
NodeInteractionState, PopoverFocusState, ScrollState, SelectionState, ToastState, TooltipState,
WidgetStateStore,
};
/// Internal UI state — interaction trackers + the side maps the library
/// writes during layout / state-apply / animation-tick passes. Owned by
/// the renderer; the host doesn't interact with this directly.
///
/// The side maps replace the per-node bookkeeping fields that used to
/// live on `El` (computed rect, interaction state, envelope amounts).
/// Keying is by `El::computed_id`, the path-shaped string assigned by
/// the layout pass.
#[derive(Default)]
pub struct UiState {
/// Last known pointer position in **logical** pixels. `None` until
/// the pointer enters the window.
pub pointer_pos: Option<(f32, f32)>,
pub hovered: Option<UiTarget>,
pub pressed: Option<UiTarget>,
/// Secondary / middle button down-target, kept on a separate
/// channel so it doesn't fight the primary `pressed` envelope or
/// move focus. Carries the button kind so `pointer_up` knows which
/// click variant to emit. Cleared by `pointer_up` matching the
/// same button.
pub(crate) pressed_secondary: Option<(UiTarget, PointerButton)>,
/// URL of the text-link run under a primary press, when present.
/// Set by `pointer_down` from `hit_test::link_at`; consumed by
/// `pointer_up`, which emits `UiEventKind::LinkActivated` only
/// when the up position lands on the same link URL — same
/// press-then-confirm contract as a normal `Click`.
pub(crate) pressed_link: Option<String>,
/// URL of the text-link run currently under the pointer (no
/// button press required). Tracked by `pointer_moved` so the
/// cursor resolver can return [`crate::cursor::Cursor::Pointer`]
/// over links without the text leaves having to be keyed
/// hover-test targets. Cleared on `pointer_left`.
pub(crate) hovered_link: Option<String>,
pub focused: Option<UiTarget>,
/// Whether the focused element should display its focus ring.
/// Tracks the web platform's `:focus-visible` heuristic: keyboard
/// focus (Tab, arrow-nav) raises the flag; pointer-down clears it.
/// Widgets where the ring belongs even on click — text inputs and
/// text areas, where the ring communicates "this surface is now
/// active" beyond the caret alone — opt back in via
/// [`crate::tree::El::always_show_focus_ring`].
pub focus_visible: bool,
pub(crate) focus: FocusState,
/// Mirror of the application's current
/// [`crate::selection::Selection`]. Set by the host runner once
/// per frame from [`crate::event::App::selection`]; read by the
/// painter to draw highlight bands and by the selection manager
/// to know what's currently active when extending a drag.
pub current_selection: crate::selection::Selection,
/// Internal selection traversal and drag state.
pub(crate) selection: SelectionState,
pub(crate) click: ClickState,
pub(crate) caret: CaretState,
pub(crate) popover_focus: PopoverFocusState,
pub(crate) tooltip: TooltipState,
pub(crate) scroll: ScrollState,
/// Runtime-managed toast notification queue and id allocator.
pub(crate) toast: ToastState,
/// App-declared keyboard shortcuts and their action names.
pub(crate) hotkeys: HotkeyState,
/// Visual prop animations, state envelopes, and animation pacing.
pub(crate) animation: AnimationState,
// ---- side maps (formerly El bookkeeping) ----
/// Layout-owned rect and key-index side maps.
pub(crate) layout: LayoutState,
/// Per-node interaction states derived from focused/pressed/hovered
/// trackers by [`Self::apply_to_state`].
pub(crate) node_states: NodeInteractionState,
/// Per-(node, type) widget state buckets. The library owns the
/// storage but never reads the values — they're for widget authors
/// to stash text-input carets, dropdown open flags, etc. Entries
/// are GC'd alongside envelopes/animations when a node leaves the
/// tree (see [`Self::tick_visual_animations`]).
widget_states: WidgetStateStore,
/// Last known keyboard modifier mask. Updated by the host runner
/// from winit's `ModifiersChanged`; pointer events stamp this
/// value into their `UiEvent.modifiers` so widgets that need to
/// detect Shift+click / Ctrl+drag can read it without separate
/// plumbing.
pub modifiers: KeyModifiers,
}
impl UiState {
pub fn new() -> Self {
Self::default()
}
}
impl Debug for UiState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiState")
.field("pointer_pos", &self.pointer_pos)
.field("hovered", &self.hovered)
.field("pressed", &self.pressed)
.field("focused", &self.focused)
.field("focus_visible", &self.focus_visible)
.field("focus", &self.focus)
.field("popover_focus", &self.popover_focus)
.field("click", &self.click)
.field("caret", &self.caret)
.field("scroll", &self.scroll)
.field("toast", &self.toast)
.field("tooltip", &self.tooltip)
.field("hotkeys", &self.hotkeys)
.field("animation", &self.animation)
.field("layout", &self.layout)
.field("node_states", &self.node_states)
.field("modifiers", &self.modifiers)
.field("widget_states", &self.widget_states)
.finish()
}
}
#[cfg(test)]
mod tests;