Skip to main content

ravenrs_widgets/
context.rs

1use std::any::Any;
2use std::cell::RefCell;
3use std::collections::{HashMap, HashSet};
4use std::fmt;
5use std::rc::Rc;
6
7use ravenrs_animation::{Animatable, Animated, Transition};
8use ravenrs_core::{
9    AccessTree, Border, Brush, Color, CursorIcon, Key, Modifiers, NumericValue, Point, Rect, Role, Scene,
10    Shadow, Size, Toggled,
11};
12use ravenrs_layout::{LayoutError, LayoutId, LayoutStyle, LayoutTree};
13use ravenrs_reactive::State;
14use ravenrs_style::Theme;
15use ravenrs_text::{TextAttrs, TextLayout, TextSystem};
16
17use crate::View;
18use crate::motion::{Motion, MotionEntry, MotionKey};
19use crate::view::ViewSeq;
20
21/// Stable identity of a widget across rebuilds, derived from its position in the
22/// view tree (or from an explicit key, see [`BuildCx::keyed_child`]). Hover,
23/// press and focus are remembered per `WidgetId`.
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
26pub struct WidgetId(u64);
27
28#[cfg_attr(feature = "live", ravenrs_live::expose)]
29impl WidgetId {
30    pub(crate) const ROOT: Self = Self(0x5241_5645_4e52_5321);
31
32    fn child(self, key: u64) -> Self {
33        let mut h = self.0 ^ key.wrapping_mul(0x9e37_79b9_7f4a_7c15);
34        h = (h ^ (h >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
35        h = (h ^ (h >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
36        Self(h ^ (h >> 31))
37    }
38
39    /// The raw identity, for derived ids such as accessibility node ids.
40    pub(crate) fn raw(self) -> u64 {
41        self.0
42    }
43}
44
45/// A node of the element tree produced by one build. Only valid during the
46/// build that created it.
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
48#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
49pub struct NodeId(pub(crate) u32);
50
51/// A rounded rectangle painted behind an element's children.
52#[derive(Clone, Copy, Debug, PartialEq)]
53#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
54pub struct QuadPaint {
55    /// Fill color or gradient.
56    pub fill: Brush,
57    /// Corner radius in logical pixels.
58    pub corner_radius: f32,
59    /// Optional border stroke.
60    pub border: Option<Border>,
61    /// Optional drop shadow.
62    pub shadow: Option<Shadow>,
63}
64
65#[cfg_attr(feature = "live", ravenrs_live::expose)]
66impl QuadPaint {
67    /// A square-cornered quad with this fill, no border and no shadow.
68    pub fn new(fill: impl Into<Brush>) -> Self {
69        Self { fill: fill.into(), corner_radius: 0.0, border: None, shadow: None }
70    }
71}
72
73/// Custom drawing callback: receives the element's absolute frame in logical pixels.
74pub type DrawFn = Rc<dyn Fn(Rect, &mut Scene)>;
75
76/// Drawing that changes over time: receives the element's frame and the clock in
77/// seconds. It runs on paint-only frames, at most 60 times a second, for as long
78/// as the view is built. With reduced motion it runs once, at time zero.
79pub type LiveFn = Rc<dyn Fn(Rect, f64, &mut Scene)>;
80
81/// What [`Paint::Rich`] draws with.
82#[derive(Debug)]
83#[non_exhaustive]
84pub struct PaintCx<'a> {
85    /// The display list.
86    pub scene: &'a mut Scene,
87    /// Shapes text that depends on the frame, which a build does not know yet.
88    pub text: &'a mut TextSystem,
89    /// Physical pixels per logical pixel, to make pictures that stay sharp.
90    pub scale: f32,
91}
92
93/// Draws a node with [`PaintCx`], given its absolute frame.
94pub type RichFn = Rc<dyn Fn(Rect, &mut PaintCx<'_>)>;
95
96/// What an element draws before its children.
97#[derive(Clone, Default)]
98#[non_exhaustive]
99#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
100pub enum Paint {
101    /// Draws nothing.
102    #[default]
103    None,
104    /// A rounded rectangle.
105    Quad(QuadPaint),
106    /// Arbitrary drawing into the scene, given the element's absolute frame.
107    Custom(DrawFn),
108    /// Arbitrary drawing that keeps changing (spinners, pulses), without rebuilding.
109    Live(LiveFn),
110    /// Arbitrary drawing that also shapes text or needs the device scale.
111    Rich(RichFn),
112}
113
114impl fmt::Debug for Paint {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            Self::None => f.write_str("None"),
118            Self::Quad(q) => f.debug_tuple("Quad").field(q).finish(),
119            Self::Custom(_) => f.write_str("Custom"),
120            Self::Live(_) => f.write_str("Live"),
121            Self::Rich(_) => f.write_str("Rich"),
122        }
123    }
124}
125
126/// Called with the focused element's key presses; returns true when it handled the key.
127pub type KeyHandler = Rc<dyn Fn(&Key, Modifiers) -> bool>;
128
129/// Phase of a pointer drag that started on an element.
130#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131#[non_exhaustive]
132#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
133pub enum DragPhase {
134    /// The pointer went down and started moving.
135    Start,
136    /// The pointer moved while dragging.
137    Move,
138    /// The pointer was released.
139    End,
140}
141
142/// A pointer drag on an element, in absolute logical pixels, with the element's frame.
143#[derive(Clone, Copy, Debug, PartialEq)]
144#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
145pub struct Drag {
146    /// Which part of the drag this event is.
147    pub phase: DragPhase,
148    /// Pointer position in absolute logical pixels.
149    pub position: Point,
150    /// The dragged element's absolute frame.
151    pub frame: Rect,
152    /// The frame of the dragged element's parent (e.g. a splitter's container).
153    pub container: Rect,
154}
155
156#[cfg_attr(feature = "live", ravenrs_live::expose)]
157impl Drag {
158    /// Horizontal position inside the frame, from 0.0 (left) to 1.0 (right).
159    pub fn fraction_x(&self) -> f32 {
160        if self.frame.width() <= 0.0 {
161            return 0.0;
162        }
163        ((self.position.x - self.frame.x()) / self.frame.width()).clamp(0.0, 1.0)
164    }
165}
166
167/// Called for each phase of a pointer drag on an element.
168pub type DragHandler = Rc<dyn Fn(Drag)>;
169
170/// What just happened in the UI, for something watching it as a whole.
171#[derive(Clone, Copy, Debug)]
172#[non_exhaustive]
173pub enum UiActivity<'a> {
174    /// A frame finished painting, and this is it.
175    Painted(&'a Scene),
176    /// The pointer moved here.
177    PointerMoved(Point),
178    /// The primary button went down here.
179    Clicked(Point),
180    /// What is on screen, as asked for with [`BuildCx::ask_for_access_tree`].
181    Described(&'a AccessTree),
182}
183
184/// Receives what happens in the UI, with the clock; see [`BuildCx::observe_activity`].
185pub type ActivityObserver = Rc<dyn Fn(UiActivity<'_>, f64)>;
186
187/// Where an overlay is placed relative to its anchor.
188#[derive(Clone, Copy, Debug, PartialEq)]
189#[non_exhaustive]
190#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
191pub enum Placement {
192    /// Below the anchor, left edges aligned.
193    Below,
194    /// Above the anchor, left edges aligned.
195    Above,
196    /// To the right of the anchor, top edges aligned; to its left when only that fits.
197    Right,
198    /// Centered above the anchor (tooltips).
199    AboveCenter,
200    /// To the right of the anchor, centers aligned (tooltips beside a rail).
201    RightCenter,
202    /// To the left of the anchor, centers aligned (tooltips beside a rail).
203    LeftCenter,
204    /// To the right of the anchor, bottom edges aligned.
205    RightEnd,
206    /// To the left of the anchor, top edges aligned.
207    LeftStart,
208    /// To the left of the anchor, bottom edges aligned.
209    LeftEnd,
210    /// Covers the whole viewport (dialogs, sheets); the anchor is ignored.
211    Fill,
212    /// At a point in the viewport (context menus); the anchor is ignored.
213    At(Point),
214    /// The bottom-right corner of the viewport (toasts).
215    BottomRight,
216    /// The bottom-left corner of the viewport; the anchor is ignored.
217    BottomLeft,
218    /// The middle of the bottom edge of the viewport; the anchor is ignored.
219    BottomCenter,
220    /// The top-left corner of the viewport; the anchor is ignored.
221    TopLeft,
222    /// The middle of the top edge of the viewport; the anchor is ignored.
223    TopCenter,
224    /// The top-right corner of the viewport; the anchor is ignored.
225    TopRight,
226}
227
228/// A keyboard shortcut that works wherever focus is (unless the focused
229/// element handles the key first). `command` means Cmd on macOS, Ctrl elsewhere;
230/// both are accepted on every platform.
231#[derive(Clone)]
232pub(crate) struct Shortcut {
233    pub key: Key,
234    pub command: bool,
235    /// It also needs Shift; without this, Shift does not matter.
236    pub shift: bool,
237    pub handler: Rc<dyn Fn()>,
238}
239
240/// How a node appears to screen readers: its [`Role`], name and state.
241///
242/// Built-in components set this themselves. Custom components call
243/// [`BuildCx::semantics`] on the node they return:
244///
245/// ```
246/// use ravenrs_core::Role;
247/// use ravenrs_widgets::Semantics;
248///
249/// let s = Semantics::new(Role::Switch).name("Wi-Fi").toggled(true);
250/// assert_eq!(s.role(), Role::Switch);
251/// ```
252///
253/// Without semantics, a focusable node is announced as a button named by the
254/// text inside it, text is announced as a label, and other boxes are
255/// transparent: their children belong to the nearest described ancestor.
256#[derive(Clone, Debug, PartialEq)]
257#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
258pub struct Semantics {
259    pub(crate) role: Role,
260    pub(crate) name: Option<String>,
261    pub(crate) description: Option<String>,
262    pub(crate) value: Option<String>,
263    pub(crate) numeric: Option<NumericValue>,
264    pub(crate) toggled: Option<Toggled>,
265    pub(crate) selected: Option<bool>,
266    pub(crate) expanded: Option<bool>,
267    pub(crate) disabled: bool,
268    pub(crate) read_only: bool,
269    pub(crate) modal: bool,
270    pub(crate) level: Option<u32>,
271    pub(crate) hidden: bool,
272    pub(crate) active: bool,
273    /// The name is a guess (an icon's name) that a tooltip may replace.
274    pub(crate) fallback_name: bool,
275}
276
277#[cfg_attr(feature = "live", ravenrs_live::expose)]
278impl Semantics {
279    /// A node with this role, named by its content when the role allows it.
280    pub fn new(role: Role) -> Self {
281        Self {
282            role,
283            name: None,
284            description: None,
285            value: None,
286            numeric: None,
287            toggled: None,
288            selected: None,
289            expanded: None,
290            disabled: false,
291            read_only: false,
292            modal: false,
293            level: None,
294            hidden: false,
295            active: false,
296            fallback_name: false,
297        }
298    }
299
300    /// Hides the node and everything inside it from assistive technology
301    /// (decorations, duplicated content).
302    pub fn hidden() -> Self {
303        Self { hidden: true, ..Self::new(Role::Group) }
304    }
305
306    /// The role.
307    pub fn role(&self) -> Role {
308        self.role
309    }
310
311    /// Sets the accessible name, replacing the name from content.
312    pub fn name(mut self, name: impl Into<String>) -> Self {
313        self.name = Some(name.into());
314        self
315    }
316
317    /// Sets a description, read after the name.
318    pub fn description(mut self, description: impl Into<String>) -> Self {
319        self.description = Some(description.into());
320        self
321    }
322
323    /// Sets a text value (text fields, combo boxes).
324    pub fn value(mut self, value: impl Into<String>) -> Self {
325        self.value = Some(value.into());
326        self
327    }
328
329    /// Sets a numeric value and its range.
330    pub fn numeric(mut self, value: f64, min: f64, max: f64, step: Option<f64>) -> Self {
331        self.numeric = Some(NumericValue { value, min, max, step });
332        self
333    }
334
335    /// Sets the checked state.
336    pub fn toggled(mut self, toggled: impl Into<Toggled>) -> Self {
337        self.toggled = Some(toggled.into());
338        self
339    }
340
341    /// Sets the selected state.
342    pub fn selected(mut self, selected: bool) -> Self {
343        self.selected = Some(selected);
344        self
345    }
346
347    /// Sets the expanded state.
348    pub fn expanded(mut self, expanded: bool) -> Self {
349        self.expanded = Some(expanded);
350        self
351    }
352
353    /// Marks the control as not responding to input.
354    pub fn disabled(mut self, disabled: bool) -> Self {
355        self.disabled = disabled;
356        self
357    }
358
359    /// Marks the control's value as readable but not changeable.
360    pub fn read_only(mut self, read_only: bool) -> Self {
361        self.read_only = read_only;
362        self
363    }
364
365    /// Marks a dialog as modal.
366    pub fn modal(mut self) -> Self {
367        self.modal = true;
368        self
369    }
370
371    /// Marks the option highlighted in a list that a focused text field
372    /// drives (combobox pattern), so screen readers announce it.
373    pub fn active(mut self, active: bool) -> Self {
374        self.active = active;
375        self
376    }
377
378    /// Sets the heading level (1 to 6).
379    pub fn level(mut self, level: u32) -> Self {
380        self.level = Some(level.clamp(1, 6));
381        self
382    }
383}
384
385pub(crate) struct Overlay {
386    pub root: NodeId,
387    pub anchor: NodeId,
388    pub placement: Placement,
389    pub gap: f32,
390    pub on_dismiss: Option<Rc<dyn Fn()>>,
391    /// Stays below every other layer, whenever it was built.
392    pub floating: bool,
393}
394
395pub(crate) struct Element {
396    pub widget: WidgetId,
397    pub layout: LayoutId,
398    pub children: Vec<NodeId>,
399    pub paint: Paint,
400    /// Color of the text this element shows, if it is a text leaf.
401    pub text_color: Option<Color>,
402    pub on_click: Option<Rc<dyn Fn()>>,
403    /// Corner radius of the focus ring; `Some` makes the element focusable.
404    pub focus_radius: Option<f32>,
405    pub cursor: Option<CursorIcon>,
406    pub on_key: Option<KeyHandler>,
407    pub on_drag: Option<DragHandler>,
408    /// Children are clipped to the frame.
409    pub clip: bool,
410    /// Children scroll vertically inside the frame (implies `clip`).
411    pub scroll: bool,
412    /// Children also scroll horizontally.
413    pub scroll_x: bool,
414    /// How far past its edge the scrollbar goes.
415    pub scrollbar_outside: f32,
416    /// Scrolling rebuilds the UI (virtualized content that depends on the offset).
417    pub rebuild_on_scroll: bool,
418    /// Visual offset applied to the children's positions.
419    pub translate: Point,
420    pub on_double_click: Option<Rc<dyn Fn()>>,
421    pub on_pinch: Option<Rc<dyn Fn(f32)>>,
422    /// The wheel over it moves what this widget scrolls.
423    pub scrolls_with_widget: bool,
424    /// A wheel that only turns up and down moves this scroll container sideways.
425    pub wheel_sideways: bool,
426    pub on_context_menu: Option<Rc<dyn Fn(Point)>>,
427    pub on_file_drop: Option<Rc<dyn Fn(Vec<crate::PickedFile>)>>,
428    pub on_context_menu_in: Option<Rc<dyn Fn(Point, Rect)>>,
429    pub on_pointer_move: Option<Rc<dyn Fn(Point, Rect)>>,
430    /// How the element appears to assistive technology.
431    pub semantics: Option<Box<Semantics>>,
432    /// The string a text leaf shows.
433    pub text: Option<Rc<str>>,
434    /// Index of this element's entry among the build's motions.
435    pub motion: Option<u32>,
436    /// Report the size of the content (see [`BuildCx::measured_content`]).
437    pub measure: bool,
438    pub report_frame: bool,
439    /// Stays this far below the top of its scroll container while its parent passes.
440    pub pin: Option<f32>,
441    /// The track of a scroll scene: what [`crate::ScrollRange::Scene`] measures.
442    pub scroll_track: Option<crate::scroll_scene::ScrollTrack>,
443    /// Painted through this effect of the scroll position.
444    pub scroll_link: Option<crate::ScrollLink>,
445    /// A place links can scroll to (`#name`), and how far below the top it lands.
446    pub anchor: Option<(Rc<str>, f32)>,
447    /// Called once, the first time the element shows in its scroll container.
448    pub on_appear: Option<Rc<dyn Fn()>>,
449    /// Painted through this effect of the clock and the element's frame.
450    pub time_link: Option<crate::TimeLink>,
451    /// Told the scroll progress along this range before each paint.
452    pub progress_out: Option<(crate::ScrollRange, Rc<std::cell::Cell<f32>>)>,
453}
454
455#[cfg_attr(feature = "live", ravenrs_live::expose)]
456impl Element {
457    pub(crate) fn new(widget: WidgetId, layout: LayoutId) -> Self {
458        Self {
459            widget,
460            layout,
461            children: Vec::new(),
462            paint: Paint::None,
463            text_color: None,
464            on_click: None,
465            focus_radius: None,
466            cursor: None,
467            on_key: None,
468            on_drag: None,
469            clip: false,
470            scroll: false,
471            scrollbar_outside: 0.0,
472            scroll_x: false,
473            rebuild_on_scroll: false,
474            translate: Point::ZERO,
475            on_double_click: None,
476            on_pinch: None,
477            scrolls_with_widget: false,
478            wheel_sideways: false,
479            on_context_menu: None,
480            on_file_drop: None,
481            on_context_menu_in: None,
482            on_pointer_move: None,
483            semantics: None,
484            text: None,
485            motion: None,
486            measure: false,
487            report_frame: false,
488            pin: None,
489            scroll_track: None,
490            scroll_link: None,
491            anchor: None,
492            on_appear: None,
493            time_link: None,
494            progress_out: None,
495        }
496    }
497
498    pub fn is_interactive(&self) -> bool {
499        self.on_click.is_some()
500            || self.on_double_click.is_some()
501            || self.on_context_menu.is_some()
502            || self.on_context_menu_in.is_some()
503            || self.on_pointer_move.is_some()
504            || self.cursor.is_some()
505            || self.focus_radius.is_some()
506            || self.on_drag.is_some()
507    }
508}
509
510/// Layout context of measured leaves.
511pub(crate) struct TextLeaf {
512    pub layout: TextLayout,
513    /// One line ended with `…` when it does not fit; holds what to shape again then.
514    pub truncate: Option<(Rc<str>, TextAttrs)>,
515}
516
517/// Which widgets the pointer and keyboard are interacting with.
518#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
519pub(crate) struct Interaction {
520    pub hovered: Option<WidgetId>,
521    pub pressed: Option<WidgetId>,
522    pub focused: Option<WidgetId>,
523    /// Focus came from the keyboard, so the ring is shown.
524    pub focus_visible: bool,
525}
526
527struct Scope {
528    id: WidgetId,
529    next_child: u64,
530    next_state: u32,
531    next_motion: u64,
532}
533
534/// Scroll positions and visible sizes of scroll containers, by widget.
535#[derive(Debug, Default)]
536pub(crate) struct ScrollState {
537    pub offsets: HashMap<WidgetId, Point>,
538    /// Frame size of each scroll container in the last layout.
539    pub viewports: HashMap<WidgetId, Size>,
540    /// Content size of each measured element in the last layout.
541    pub contents: HashMap<WidgetId, Size>,
542}
543
544/// Widget-local state that survives rebuilds, keyed by widget identity and
545/// call order (see [`BuildCx::use_state`]). Slots of widgets that were not
546/// built in the latest build are dropped.
547#[derive(Default)]
548pub(crate) struct LocalStore {
549    slots: HashMap<(WidgetId, u32), Box<dyn Any>>,
550    used: HashSet<(WidgetId, u32)>,
551}
552
553#[cfg_attr(feature = "live", ravenrs_live::expose)]
554impl LocalStore {
555    pub fn begin_build(&mut self) {
556        self.used.clear();
557    }
558
559    pub fn end_build(&mut self) {
560        let used = &self.used;
561        self.slots.retain(|k, _| used.contains(k));
562    }
563}
564
565thread_local! {
566    // A build happens on every change and most never replace the words, while
567    // `Labels` holds hundreds of strings: built once, or each build pays for them.
568    static DEFAULT_LABELS: Rc<crate::Labels> = Rc::new(crate::Labels::default());
569}
570
571/// What a [`View`] uses to describe itself during a build: theme access,
572/// interaction state, child building, layout boxes, painting and behavior.
573pub struct BuildCx<'a> {
574    /// The views that asked about the pointer, so that it moving elsewhere rebuilds nothing.
575    pub(crate) hover_readers: Rc<RefCell<HashSet<WidgetId>>>,
576    pub(crate) view_profile: Option<Rc<RefCell<crate::ui::ViewProfile>>>,
577    pub(crate) theme: Theme,
578    pub(crate) theme_state: State<Theme>,
579    pub(crate) viewport: Size,
580    pub(crate) overlays: Vec<Overlay>,
581    floating_overlays: u64,
582    pub(crate) text: &'a mut TextSystem,
583    pub(crate) layout: &'a mut LayoutTree<TextLeaf>,
584    pub(crate) elements: &'a mut Vec<Element>,
585    pub(crate) interaction: Interaction,
586    pub(crate) hover_chain: &'a [WidgetId],
587    pub(crate) scroll: &'a ScrollState,
588    pub(crate) scroll_requests: Vec<(WidgetId, Point)>,
589    pub(crate) focus_request: Option<WidgetId>,
590    pub(crate) now: f64,
591    labels: Rc<crate::Labels>,
592    ui_waker: crate::UiWaker,
593    sidebar: Option<Rc<crate::nav::SidebarScope>>,
594    pub(crate) shortcuts: Vec<Shortcut>,
595    pub(crate) activity: Option<ActivityObserver>,
596    pub(crate) access_tree_asked: bool,
597    pub(crate) wake: Option<f64>,
598    pub(crate) modifiers: Rc<std::cell::Cell<Modifiers>>,
599    pub(crate) local: &'a mut LocalStore,
600    pub(crate) placeholder: LayoutId,
601    pub(crate) error: Option<LayoutError>,
602    pub(crate) clipboard: crate::Clipboard,
603    pub(crate) sound: crate::Sound,
604    pub(crate) links: crate::Links,
605    pub(crate) full_screen: crate::FullScreen,
606    pub(crate) file_picker: crate::FilePicker,
607    pub(crate) files_over: State<bool>,
608    pub(crate) motions: Vec<MotionEntry>,
609    pub(crate) motion_clock: f64,
610    pub(crate) motion_enabled: bool,
611    pub(crate) reduced_motion: bool,
612    /// A value from [`Self::animated`] is still moving.
613    pub(crate) value_moving: bool,
614    /// The text color of the view being built, for content that takes it from what it is in.
615    foreground: Option<Color>,
616    scopes: Vec<Scope>,
617}
618
619impl fmt::Debug for BuildCx<'_> {
620    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
621        f.debug_struct("BuildCx").field("widget", &self.widget_id()).finish_non_exhaustive()
622    }
623}
624
625#[cfg_attr(feature = "live", ravenrs_live::expose)]
626impl<'a> BuildCx<'a> {
627    #[allow(clippy::too_many_arguments)]
628    pub(crate) fn new(
629        theme_state: State<Theme>,
630        viewport: Size,
631        text: &'a mut TextSystem,
632        layout: &'a mut LayoutTree<TextLeaf>,
633        elements: &'a mut Vec<Element>,
634        interaction: Interaction,
635        hover_chain: &'a [WidgetId],
636        scroll: &'a ScrollState,
637        modifiers: Rc<std::cell::Cell<Modifiers>>,
638        local: &'a mut LocalStore,
639        placeholder: LayoutId,
640        clipboard: crate::Clipboard,
641        ui_waker: crate::UiWaker,
642    ) -> Self {
643        Self {
644            hover_readers: Rc::default(),
645            view_profile: None,
646            // Read through the state so a theme change rebuilds the UI.
647            theme: theme_state.get(),
648            theme_state,
649            viewport,
650            overlays: Vec::new(),
651            floating_overlays: 0,
652            text,
653            layout,
654            elements,
655            interaction,
656            hover_chain,
657            scroll,
658            scroll_requests: Vec::new(),
659            focus_request: None,
660            now: 0.0,
661            labels: DEFAULT_LABELS.with(Rc::clone),
662            ui_waker,
663            sidebar: None,
664            shortcuts: Vec::new(),
665            activity: None,
666            access_tree_asked: false,
667            wake: None,
668            modifiers,
669            local,
670            placeholder,
671            error: None,
672            clipboard,
673            sound: crate::Sound::default(),
674            links: crate::Links::default(),
675            full_screen: crate::FullScreen::default(),
676            file_picker: crate::FilePicker::default(),
677            files_over: State::new(false),
678            motions: Vec::new(),
679            motion_clock: 0.0,
680            motion_enabled: false,
681            reduced_motion: false,
682            value_moving: false,
683            foreground: None,
684            scopes: vec![Scope { id: WidgetId::ROOT, next_child: 0, next_state: 0, next_motion: 0 }],
685        }
686    }
687
688    /// The text color of the view being built, if a view around it set one with
689    /// [`Self::with_foreground`]. Colors do not cascade by themselves, so this is how
690    /// a [`Kbd`](crate::Kbd) inside a filled button reads as part of the button.
691    pub fn foreground(&self) -> Option<Color> {
692        self.foreground
693    }
694
695    /// Builds what `build` builds with `color` as its [`Self::foreground`]. Layers opened
696    /// inside it start without one, since they are not painted over this view.
697    pub fn with_foreground<R>(&mut self, color: Color, build: impl FnOnce(&mut Self) -> R) -> R {
698        let outer = self.foreground.replace(color);
699        let result = build(self);
700        self.foreground = outer;
701        result
702    }
703
704    /// The theme in effect for this build.
705    pub fn theme(&self) -> &Theme {
706        &self.theme
707    }
708
709    /// A handle that asks for a rebuild from another thread; see [`UiWaker`](crate::UiWaker).
710    pub fn ui_waker(&self) -> crate::UiWaker {
711        self.ui_waker.clone()
712    }
713
714    /// The words the components use here; see [`Localized`](crate::Localized).
715    pub fn labels(&self) -> Rc<crate::Labels> {
716        self.labels.clone()
717    }
718
719    pub(crate) fn replace_labels(&mut self, labels: Rc<crate::Labels>) -> Rc<crate::Labels> {
720        std::mem::replace(&mut self.labels, labels)
721    }
722
723    /// The sidebar this view is being built inside, if any.
724    pub(crate) fn sidebar(&self) -> Option<Rc<crate::nav::SidebarScope>> {
725        self.sidebar.clone()
726    }
727
728    pub(crate) fn replace_sidebar(
729        &mut self,
730        sidebar: Option<Rc<crate::nav::SidebarScope>>,
731    ) -> Option<Rc<crate::nav::SidebarScope>> {
732        std::mem::replace(&mut self.sidebar, sidebar)
733    }
734
735    /// The app-wide theme as state: set it to switch themes at runtime.
736    pub fn theme_state(&self) -> State<Theme> {
737        self.theme_state.clone()
738    }
739
740    /// Opens addresses: in the browser it navigates, on the desktop the system's
741    /// browser opens them. `#name` scrolls to the anchor of that name instead.
742    pub fn links(&self) -> crate::Links {
743        self.links.clone()
744    }
745
746    /// Puts the app in full screen and takes it out: the page's full screen in the
747    /// browser, a borderless full screen window on the desktop.
748    pub fn full_screen(&self) -> crate::FullScreen {
749        self.full_screen.clone()
750    }
751
752    /// Marks the node as the place `#name` links scroll to, landing `offset` pixels
753    /// below the top of its scroll container (the height of a bar pinned over it).
754    pub fn anchor(&mut self, node: NodeId, name: &str, offset: f32) {
755        if let Some(e) = self.element_mut(node) {
756            e.anchor = Some((Rc::from(name), offset));
757        }
758    }
759
760    /// Calls `handler` once, after the first frame in which the node shows inside its
761    /// scroll container: counters that start when they are seen, content loaded when
762    /// it is reached. The handler runs outside the build, so it may set state.
763    pub fn on_appear(&mut self, node: NodeId, handler: Rc<dyn Fn()>) {
764        if let Some(e) = self.element_mut(node) {
765            e.on_appear = Some(handler);
766        }
767    }
768
769    /// Paints the node and its subtree with the effect `link` gives for the clock and
770    /// the node's frame, on every frame while it shows: a strip that keeps moving, a
771    /// gentle float. It neither rebuilds nor lays out. With reduced motion the clock
772    /// stands at zero.
773    pub fn time_linked(&mut self, node: NodeId, link: crate::TimeLink) {
774        if let Some(e) = self.element_mut(node) {
775            e.time_link = Some(link);
776        }
777    }
778
779    /// Writes into `progress`, before each paint of the node, how far the scroll is along
780    /// `range` from 0 to 1, for drawing that follows the scroll itself, such as a
781    /// [`crate::QuadField`]. Effects that only move, fade or scale use
782    /// [`Self::scroll_linked`] instead.
783    pub fn scroll_progress(
784        &mut self,
785        node: NodeId,
786        range: crate::ScrollRange,
787        progress: Rc<std::cell::Cell<f32>>,
788    ) {
789        if let Some(e) = self.element_mut(node) {
790            e.progress_out = Some((range, progress));
791        }
792    }
793
794    /// The system clipboard, for copy and paste handlers.
795    pub fn clipboard(&self) -> crate::Clipboard {
796        self.clipboard.clone()
797    }
798
799    /// The system's sound and voice.
800    pub fn sound(&self) -> crate::Sound {
801        self.sound.clone()
802    }
803
804    /// The system's chooser of files.
805    pub fn file_picker(&self) -> crate::FilePicker {
806        self.file_picker.clone()
807    }
808
809    /// Whether files are being dragged over the window, for places that take them to show it.
810    pub fn files_over(&self) -> bool {
811        self.files_over.get()
812    }
813
814    /// Makes the node take files dropped on it.
815    pub fn on_file_drop(&mut self, node: NodeId, handler: Rc<dyn Fn(Vec<crate::PickedFile>)>) {
816        if let Some(e) = self.element_mut(node) {
817            e.on_file_drop = Some(handler);
818        }
819    }
820
821    /// The viewport size in logical pixels.
822    pub fn viewport(&self) -> Size {
823        self.viewport
824    }
825
826    /// The identity of the view being built.
827    pub fn widget_id(&self) -> WidgetId {
828        self.scopes.last().map_or(WidgetId::ROOT, |s| s.id)
829    }
830
831    /// The pointer is over this view's interactive element.
832    pub fn is_hovered(&self) -> bool {
833        self.hover_readers.borrow_mut().insert(self.widget_id());
834        self.interaction.hovered == Some(self.widget_id())
835    }
836
837    /// The pointer is over this view or over any interactive element inside it.
838    pub fn is_hover_within(&self) -> bool {
839        self.hover_readers.borrow_mut().insert(self.widget_id());
840        self.hover_chain.contains(&self.widget_id())
841    }
842
843    /// State owned by this view instance that survives rebuilds, like SwiftUI's
844    /// `@State`. Call it unconditionally and in the same order on every build;
845    /// `init` runs only the first time. The state is dropped when the view stops
846    /// being built.
847    pub fn use_state<T: 'static>(&mut self, init: impl FnOnce() -> T) -> State<T> {
848        let id = self.widget_id();
849        let slot = match self.scopes.last_mut() {
850            Some(scope) => {
851                scope.next_state += 1;
852                scope.next_state
853            }
854            None => 0,
855        };
856        let key = (id, slot);
857        self.local.used.insert(key);
858        if let Some(state) = self.local.slots.get(&key).and_then(|b| b.downcast_ref::<State<T>>()) {
859            return state.clone();
860        }
861        let state = State::new(init());
862        self.local.slots.insert(key, Box::new(state.clone()));
863        state
864    }
865
866    /// The primary button went down on this view and has not been released.
867    pub fn is_pressed(&self) -> bool {
868        self.interaction.pressed == Some(self.widget_id())
869    }
870
871    /// Keyboard focus is on this view.
872    pub fn is_focused(&self) -> bool {
873        self.interaction.focused == Some(self.widget_id())
874    }
875
876    /// Focus is on this view and came from the keyboard.
877    pub fn is_focus_visible(&self) -> bool {
878        self.is_focused() && self.interaction.focus_visible
879    }
880
881    /// Builds a child view; its identity comes from its position among this
882    /// view's children.
883    pub fn child(&mut self, view: &dyn View) -> NodeId {
884        let key = match self.scopes.last_mut() {
885            Some(scope) => {
886                scope.next_child += 1;
887                scope.next_child
888            }
889            None => 0,
890        };
891        self.build_scoped(key, view)
892    }
893
894    /// Builds a child with an explicit key, so it keeps its hover/focus state
895    /// when siblings are inserted or removed (use it for dynamic lists).
896    pub fn keyed_child(&mut self, key: u64, view: &dyn View) -> NodeId {
897        // Keep keyed and positional identities apart.
898        self.build_scoped(key ^ (1 << 63), view)
899    }
900
901    fn build_scoped(&mut self, key: u64, view: &dyn View) -> NodeId {
902        let id = self.widget_id().child(key);
903        self.scopes.push(Scope { id, next_child: 0, next_state: 0, next_motion: 0 });
904        let profile = self.view_profile.clone();
905        if let Some(profile) = &profile {
906            profile.borrow_mut().enter(view.type_name(), self.elements.len(), self.text.stats().shapes);
907        }
908        let node = view.build(self);
909        if let Some(profile) = &profile {
910            profile.borrow_mut().leave(self.elements.len(), self.text.stats().shapes);
911        }
912        self.scopes.pop();
913        node
914    }
915
916    /// Builds every view of a sequence as children.
917    pub fn children(&mut self, seq: &dyn ViewSeq) -> Vec<NodeId> {
918        let mut out = Vec::new();
919        seq.build_each(self, &mut out);
920        out
921    }
922
923    /// Creates a box that lays out `children` and paints `paint` behind them.
924    pub fn container(&mut self, style: LayoutStyle, children: &[NodeId], paint: Paint) -> NodeId {
925        let child_layouts: Vec<LayoutId> =
926            children.iter().filter_map(|c| self.elements.get(c.0 as usize).map(|e| e.layout)).collect();
927        let layout = match self.layout.node(style, &child_layouts) {
928            Ok(id) => id,
929            Err(e) => self.fail(e),
930        };
931        let mut element = Element::new(self.widget_id(), layout);
932        element.children = children.to_vec();
933        element.paint = paint;
934        self.push(element)
935    }
936
937    /// Creates a text leaf that wraps to the width its parent offers.
938    pub fn text(&mut self, text: &str, attrs: &TextAttrs, color: Color, style: LayoutStyle) -> NodeId {
939        let layout = self.text.layout(text, attrs);
940        let id = match self.layout.measured_leaf(style, TextLeaf { layout, truncate: None }) {
941            Ok(id) => id,
942            Err(e) => self.fail(e),
943        };
944        let mut element = Element::new(self.widget_id(), id);
945        element.text_color = Some(color);
946        element.text = Some(text.into());
947        self.push(element)
948    }
949
950    /// Like [`Self::text`], on one line that ends with `…` when it does not fit.
951    /// Screen readers still get the whole text.
952    pub fn text_truncated(
953        &mut self,
954        text: &str,
955        attrs: &TextAttrs,
956        color: Color,
957        style: LayoutStyle,
958    ) -> NodeId {
959        let node = self.text(text, attrs, color, style);
960        let layout = self.elements[node.0 as usize].layout;
961        if let Some(leaf) = self.layout.context_mut(layout) {
962            leaf.truncate = Some((text.into(), attrs.clone()));
963        }
964        node
965    }
966
967    /// Measures `text` without creating a node, e.g. to place a caret.
968    pub fn measure_text(&mut self, text: &str, attrs: &TextAttrs) -> Size {
969        self.text.layout(text, attrs).measure(None)
970    }
971
972    /// An empty box that takes no space.
973    pub fn empty(&mut self) -> NodeId {
974        // Out of the flow, so a `None` or a closed dialog takes no gap in its stack.
975        self.container(LayoutStyle { absolute: Some((0.0, 0.0)), ..LayoutStyle::leaf() }, &[], Paint::None)
976    }
977
978    /// Calls `handler` when the node is clicked, or activated with Enter/Space while focused.
979    pub fn on_click(&mut self, node: NodeId, handler: Rc<dyn Fn()>) {
980        if let Some(e) = self.element_mut(node) {
981            e.on_click = Some(handler);
982        }
983    }
984
985    /// Describes the node to screen readers; see [`Semantics`].
986    pub fn semantics(&mut self, node: NodeId, semantics: Semantics) {
987        if let Some(e) = self.element_mut(node) {
988            e.semantics = Some(Box::new(semantics));
989        }
990    }
991
992    /// Describes a node built by someone else (content passed in by the app)
993    /// unless it already describes itself.
994    pub(crate) fn semantics_if_none(&mut self, node: NodeId, semantics: Semantics) {
995        if let Some(e) = self.element_mut(node)
996            && e.semantics.is_none()
997            && e.text.is_none()
998        {
999            e.semantics = Some(Box::new(semantics));
1000        }
1001    }
1002
1003    /// Changes the semantics a child already set (marking a button selected, say).
1004    pub(crate) fn update_semantics(&mut self, node: NodeId, f: impl FnOnce(&mut Semantics)) {
1005        if let Some(s) = self.element_mut(node).and_then(|e| e.semantics.as_deref_mut()) {
1006            f(s);
1007        }
1008    }
1009
1010    /// The first described or focusable node at or under `node`, in tree
1011    /// order: the control a label or tooltip around it is about.
1012    fn control_in(&self, node: NodeId) -> Option<usize> {
1013        let mut stack = vec![node];
1014        while let Some(n) = stack.pop() {
1015            let e = self.elements.get(n.0 as usize)?;
1016            if e.semantics.as_deref().is_some_and(|s| !s.hidden) || e.focus_radius.is_some() {
1017                return Some(n.0 as usize);
1018            }
1019            stack.extend(e.children.iter().rev());
1020        }
1021        None
1022    }
1023
1024    /// Names the control at or under `node` with `name` unless it already has
1025    /// a name of its own, and sets `description` (form fields, tooltips).
1026    /// When it has a name, `name` becomes its description instead.
1027    pub(crate) fn label_control(&mut self, node: NodeId, name: &str, description: Option<&str>) {
1028        let Some(i) = self.control_in(node) else { return };
1029        let e = &mut self.elements[i];
1030        let s = e.semantics.get_or_insert_with(|| Box::new(Semantics::new(Role::Button)));
1031        if s.name.is_none() || s.fallback_name {
1032            s.name = Some(name.to_owned());
1033            s.fallback_name = false;
1034            if let Some(d) = description {
1035                s.description = Some(d.to_owned());
1036            }
1037        } else if s.name.as_deref() != Some(name) {
1038            s.description = Some(description.map_or_else(|| name.to_owned(), |d| format!("{name}. {d}")));
1039        }
1040    }
1041
1042    /// Gives the control at or under `node` this name and this description, replacing what it
1043    /// had: for a name that says more than the label on the screen.
1044    pub(crate) fn rename_control(&mut self, node: NodeId, name: Option<&str>, description: Option<&str>) {
1045        let Some(i) = self.control_in(node) else { return };
1046        let e = &mut self.elements[i];
1047        let s = e.semantics.get_or_insert_with(|| Box::new(Semantics::new(Role::Button)));
1048        if let Some(name) = name {
1049            s.name = Some(name.to_owned());
1050            s.fallback_name = false;
1051        }
1052        if let Some(description) = description {
1053            s.description = Some(description.to_owned());
1054        }
1055    }
1056
1057    /// Makes the node reachable with Tab; the focus ring uses `corner_radius`.
1058    pub fn focusable(&mut self, node: NodeId, corner_radius: f32) {
1059        if let Some(e) = self.element_mut(node) {
1060            e.focus_radius = Some(corner_radius);
1061        }
1062    }
1063
1064    /// Shows `icon` while the pointer is over the node, and makes it hoverable.
1065    pub fn cursor(&mut self, node: NodeId, icon: CursorIcon) {
1066        if let Some(e) = self.element_mut(node) {
1067            e.cursor = Some(icon);
1068        }
1069    }
1070
1071    /// Receives key presses while the node is focused (make it [`Self::focusable`]).
1072    /// Return true from the handler to stop the default handling (Tab, Enter...).
1073    pub fn on_key(&mut self, node: NodeId, handler: KeyHandler) {
1074        if let Some(e) = self.element_mut(node) {
1075            e.on_key = Some(handler);
1076        }
1077    }
1078
1079    /// Receives pointer drags that start on the node (sliders, splitters...).
1080    pub fn on_drag(&mut self, node: NodeId, handler: DragHandler) {
1081        if let Some(e) = self.element_mut(node) {
1082            e.on_drag = Some(handler);
1083        }
1084    }
1085
1086    /// Clips the node's children to its frame.
1087    pub fn clip(&mut self, node: NodeId) {
1088        if let Some(e) = self.element_mut(node) {
1089            e.clip = true;
1090        }
1091    }
1092
1093    /// This widget's scroll offset (for its scrollable element), as of this build.
1094    pub fn scroll_offset(&self) -> Point {
1095        self.scroll.offsets.get(&self.widget_id()).copied().unwrap_or(Point::ZERO)
1096    }
1097
1098    /// The visible size of this widget's scroll container in the last layout,
1099    /// or `None` before its first layout. Virtualized views use it with
1100    /// [`Self::scroll_offset`] to build only the visible rows.
1101    pub fn scroll_viewport(&self) -> Option<Size> {
1102        self.scroll.viewports.get(&self.widget_id()).copied()
1103    }
1104
1105    /// The size that the keyed child built with `key` ended up with in the last layout, if
1106    /// it asked with [`Self::report_frame`]: for a view that places its children by how much
1107    /// room each one took, before building them again.
1108    pub fn keyed_child_frame(&self, key: u64) -> Option<Size> {
1109        self.scroll.viewports.get(&self.widget_id().child(key ^ (1 << 63))).copied()
1110    }
1111
1112    /// Scrolls this widget's scroll container to `offset` after the build
1113    /// (clamped to its content), e.g. to keep a selected row visible.
1114    pub fn set_scroll_offset(&mut self, offset: Point) {
1115        let id = self.widget_id();
1116        self.scroll_requests.push((id, offset));
1117    }
1118
1119    /// Seconds since the app started, as of this build. Reading it does not
1120    /// schedule anything; use [`Self::wake_at`] to be rebuilt later.
1121    pub fn now(&self) -> f64 {
1122        self.now
1123    }
1124
1125    /// Watches the whole UI while this view is built: each painted frame, the
1126    /// pointer and clicks. For recording what happens, as a flight recorder does.
1127    /// It runs on every frame, so it has to be quick. The last call of a build wins.
1128    pub fn observe_activity(&mut self, observer: ActivityObserver) {
1129        self.activity = Some(observer);
1130    }
1131
1132    /// Asks that [`Self::observe_activity`] be told what is on screen once this frame
1133    /// is painted, as [`UiActivity::Described`]. Building that description costs more
1134    /// than a frame should, so it comes only when asked for.
1135    pub fn ask_for_access_tree(&mut self) {
1136        self.access_tree_asked = true;
1137    }
1138
1139    /// Asks for a rebuild at `time` (seconds, the same clock as [`Self::now`]):
1140    /// toasts that expire, carousels that advance, spinners that turn.
1141    pub fn wake_at(&mut self, time: f64) {
1142        self.wake = Some(self.wake.map_or(time, |w| w.min(time)));
1143    }
1144
1145    /// Declares how `node` animates; see [`Motion`]. Call it in the same order on
1146    /// every build, or give the motion a [`Motion::key`].
1147    pub fn motion(&mut self, node: NodeId, motion: Motion) {
1148        let id = self.widget_id();
1149        let key = match motion.shared {
1150            Some((scope, tag)) => MotionKey::Shared(scope, tag),
1151            None => match (motion.key, self.scopes.last_mut()) {
1152                (Some(k), _) => MotionKey::Node(id, k | (1 << 63)),
1153                (None, Some(scope)) => {
1154                    scope.next_motion += 1;
1155                    MotionKey::Node(id, scope.next_motion)
1156                }
1157                (None, None) => MotionKey::Node(id, 0),
1158            },
1159        };
1160        let index = self.motions.len() as u32;
1161        if let Some(e) = self.element_mut(node) {
1162            e.motion = Some(index);
1163            self.motions.push(MotionEntry { node, key, motion });
1164        }
1165    }
1166
1167    /// A value that eases toward `target`, for what [`Motion`] does not cover:
1168    /// numbers that feed layout or custom drawing. Call it in the same order on
1169    /// every build. It rebuilds the UI on each frame while it moves, so prefer
1170    /// [`Motion`] when you can.
1171    pub fn animated<T: Animatable + 'static>(&mut self, target: T, transition: Transition) -> T {
1172        let cell = self.use_state(|| Rc::new(RefCell::new(Animated::new(target))));
1173        let cell = cell.peek().clone();
1174        let mut value = cell.borrow_mut();
1175        let now = self.motion_clock;
1176        let transition =
1177            if !self.motion_enabled || self.reduced_motion { Transition::Instant } else { transition };
1178        value.set_target(target, transition, now);
1179        if value.step(now) {
1180            self.value_moving = true;
1181            self.wake_at(self.now);
1182        }
1183        value.value(now)
1184    }
1185
1186    /// Numbers that go from where they are to `target` along a tween, in full `f64`
1187    /// precision: a counter, or a chart's data changing. A new target while they move
1188    /// starts from where they are, with no jump. When the count of numbers changes, or
1189    /// under reduced motion, or for a spring, they take the target at once. Call it in
1190    /// the same order on every build, as with [`Self::animated`].
1191    pub fn animated_numbers(&mut self, target: &[f64], transition: Transition) -> Vec<f64> {
1192        let cell = self.use_state(|| Rc::new(RefCell::new(Numbers::at(target)))).peek().clone();
1193        let mut run = cell.borrow_mut();
1194        let now = self.motion_clock;
1195        let (duration, easing) = match transition {
1196            Transition::Tween { duration, easing } if self.motion_enabled && !self.reduced_motion => {
1197                (f64::from(duration), easing)
1198            }
1199            _ => (0.0, ravenrs_animation::Easing::Linear),
1200        };
1201        // By bits, so a gap (NaN) in the data counts as the same gap.
1202        if !run.to.iter().map(|v| v.to_bits()).eq(target.iter().map(|v| v.to_bits())) {
1203            let from =
1204                if run.to.len() == target.len() && duration > 0.0 { run.value(now) } else { target.to_vec() };
1205            *run = Numbers { from, to: target.to_vec(), start: now, duration, easing };
1206        }
1207        if now < run.start + run.duration {
1208            self.value_moving = true;
1209            self.wake_at(self.now);
1210        }
1211        run.value(now)
1212    }
1213
1214    /// How far a view has appeared, from 0 to 1, since it was first built: for
1215    /// content that draws itself in, such as a chart's data. A new `key` starts it
1216    /// over. Under reduced motion, or with motion off, it is 1 from the start. Call
1217    /// it in the same order on every build, as with [`Self::animated`].
1218    pub fn appear(&mut self, key: u64, transition: Transition) -> f32 {
1219        let cell = self.use_state(|| Rc::new(RefCell::new((key, Animated::new(0.0_f32))))).peek().clone();
1220        let mut cell = cell.borrow_mut();
1221        if cell.0 != key {
1222            *cell = (key, Animated::new(0.0));
1223        }
1224        let now = self.motion_clock;
1225        let transition =
1226            if !self.motion_enabled || self.reduced_motion { Transition::Instant } else { transition };
1227        let value = &mut cell.1;
1228        value.set_target(1.0, transition, now);
1229        if value.step(now) {
1230            self.value_moving = true;
1231            self.wake_at(self.now);
1232        }
1233        value.value(now)
1234    }
1235
1236    /// The user asked for less motion, in the system settings or in the theme.
1237    /// [`Motion`] already respects this; check it for motion you draw yourself.
1238    pub fn reduced_motion(&self) -> bool {
1239        self.reduced_motion
1240    }
1241
1242    /// Asks the runtime to report the size of the content of `node`, read back
1243    /// with [`Self::measured_content`] on the next build.
1244    pub fn measure_content(&mut self, node: NodeId) {
1245        if let Some(e) = self.element_mut(node) {
1246            e.measure = true;
1247        }
1248    }
1249
1250    /// Makes the size this node ended up with readable on the next build, with
1251    /// [`Self::scroll_viewport`], and builds again when it changes: for a view that
1252    /// arranges itself by the room it got.
1253    pub fn report_frame(&mut self, node: NodeId) {
1254        if let Some(e) = self.element_mut(node) {
1255            e.report_frame = true;
1256        }
1257    }
1258
1259    /// The size of the content of this view's measured node in the last layout.
1260    pub fn measured_content(&self) -> Option<Size> {
1261        self.scroll.contents.get(&self.widget_id()).copied()
1262    }
1263
1264    /// Registers an app-wide keyboard shortcut for as long as this view is built.
1265    /// With `command`, it needs Cmd (macOS) or Ctrl held, e.g. Cmd/Ctrl+K.
1266    pub fn shortcut(&mut self, key: Key, command: bool, handler: Rc<dyn Fn()>) {
1267        self.shortcuts.push(Shortcut { key, command, shift: false, handler });
1268    }
1269
1270    /// Makes the wheel over `node` move what this widget scrolls, for a part that
1271    /// follows the scroll without being inside it, such as frozen row names.
1272    pub fn scroll_with_widget(&mut self, node: NodeId) {
1273        if let Some(e) = self.element_mut(node) {
1274            e.scrolls_with_widget = true;
1275        }
1276    }
1277
1278    /// Like [`Self::shortcut`], for a key that also needs Shift. Register it before the
1279    /// same key without Shift, which would otherwise answer first.
1280    pub fn shortcut_with_shift(&mut self, key: Key, command: bool, handler: Rc<dyn Fn()>) {
1281        self.shortcuts.push(Shortcut { key, command, shift: true, handler });
1282    }
1283
1284    /// Calls `handler` with the pointer position on a secondary (right) click.
1285    pub fn on_context_menu(&mut self, node: NodeId, handler: Rc<dyn Fn(Point)>) {
1286        if let Some(e) = self.element_mut(node) {
1287            e.on_context_menu = Some(handler);
1288        }
1289    }
1290
1291    /// The same, also saying the element's absolute frame, for a menu that depends
1292    /// on where inside the element it was opened.
1293    pub fn on_context_menu_in(&mut self, node: NodeId, handler: Rc<dyn Fn(Point, Rect)>) {
1294        if let Some(e) = self.element_mut(node) {
1295            e.on_context_menu_in = Some(handler);
1296        }
1297    }
1298
1299    /// Calls `handler` with the pointer position and the element's absolute frame
1300    /// whenever the pointer moves over it, for something that depends on where inside
1301    /// it the pointer is. [`is_hover_within`](Self::is_hover_within) says when it left.
1302    pub fn on_pointer_move(&mut self, node: NodeId, handler: Rc<dyn Fn(Point, Rect)>) {
1303        if let Some(e) = self.element_mut(node) {
1304            e.on_pointer_move = Some(handler);
1305        }
1306    }
1307
1308    /// Moves keyboard focus to this widget's focusable element after the build,
1309    /// scrolling the containers around it until it shows.
1310    pub fn request_focus(&mut self) {
1311        self.focus_request = Some(self.widget_id());
1312    }
1313
1314    /// Moves keyboard focus to the widget that built `node` (a child's trigger
1315    /// button, say) after the build.
1316    pub(crate) fn request_focus_node(&mut self, node: NodeId) {
1317        if let Some(e) = self.elements.get(node.0 as usize) {
1318            self.focus_request = Some(e.widget);
1319        }
1320    }
1321
1322    /// Keyboard modifiers as of the latest input event, readable from click
1323    /// handlers (e.g. Shift-click to extend a selection).
1324    pub fn modifiers(&self) -> Rc<std::cell::Cell<Modifiers>> {
1325        self.modifiers.clone()
1326    }
1327
1328    /// A scroll container in both directions whose content is rebuilt on every
1329    /// scroll (see [`Self::scroll_viewport`]).
1330    pub fn scrollable_virtual(&mut self, node: NodeId, horizontal: bool) {
1331        if let Some(e) = self.element_mut(node) {
1332            e.scroll = true;
1333            e.scroll_x = horizontal;
1334            e.clip = true;
1335            e.rebuild_on_scroll = true;
1336        }
1337    }
1338
1339    /// Makes a wheel that only turns up and down move this scroll container sideways,
1340    /// for a strip with nothing to scroll in the other direction.
1341    pub fn wheel_scrolls_sideways(&mut self, node: NodeId) {
1342        if let Some(e) = self.element_mut(node) {
1343            e.wheel_sideways = true;
1344        }
1345    }
1346
1347    /// Lets a scroll container (see [`Self::scrollable`]) scroll sideways too.
1348    pub fn scroll_horizontally(&mut self, node: NodeId) {
1349        if let Some(e) = self.element_mut(node) {
1350            e.scroll_x = true;
1351        }
1352    }
1353
1354    /// Moves the node's children by `offset` when painting (e.g. a header that
1355    /// follows a body's horizontal scroll).
1356    pub fn translate(&mut self, node: NodeId, offset: Point) {
1357        if let Some(e) = self.element_mut(node) {
1358            e.translate = offset;
1359        }
1360    }
1361
1362    /// Keeps the node on screen while its parent scrolls past, `top` pixels below the
1363    /// top of the scroll container around it, and lets it leave with the bottom of the
1364    /// parent: CSS `position: sticky`. It is moved when frames are placed, so clicks,
1365    /// focus and assistive technology find it where it shows. See [`crate::Sticky`].
1366    pub fn pin(&mut self, node: NodeId, top: f32) {
1367        if let Some(e) = self.element_mut(node) {
1368            e.pin = Some(top);
1369        }
1370    }
1371
1372    /// Marks the node as the track of a scroll scene: content pinned `top` pixels
1373    /// below the top of the scroll container for `length` pixels of scrolling. Links
1374    /// inside it that measure [`crate::ScrollRange::Scene`] run from 0 to 1 over that
1375    /// length. See [`crate::ScrollScene`].
1376    pub fn scroll_track(&mut self, node: NodeId, top: f32, length: f32) {
1377        if let Some(e) = self.element_mut(node) {
1378            e.scroll_track = Some(crate::scroll_scene::ScrollTrack { top, length });
1379        }
1380    }
1381
1382    /// Paints the node and its subtree with the effect `link` gives for the current
1383    /// scroll position, worked out on every paint without rebuilding. Layout and hit
1384    /// testing are unaffected. See [`crate::ScrollLinked`].
1385    pub fn scroll_linked(&mut self, node: NodeId, link: crate::ScrollLink) {
1386        if let Some(e) = self.element_mut(node) {
1387            e.scroll_link = Some(link);
1388        }
1389    }
1390
1391    /// Calls `handler` on a zoom gesture over the node, with how much larger was
1392    /// asked for relative to now: 0.02 is 2% larger, and a negative value is smaller.
1393    pub fn on_pinch(&mut self, node: NodeId, handler: Rc<dyn Fn(f32)>) {
1394        if let Some(e) = self.element_mut(node) {
1395            e.on_pinch = Some(handler);
1396        }
1397    }
1398
1399    /// Calls `handler` on a double click on the node.
1400    pub fn on_double_click(&mut self, node: NodeId, handler: Rc<dyn Fn()>) {
1401        if let Some(e) = self.element_mut(node) {
1402            e.on_double_click = Some(handler);
1403        }
1404    }
1405
1406    /// Makes the node a vertical scroll container: its children scroll with the
1407    /// wheel or trackpad, are clipped, and a scrollbar shows while they move.
1408    /// Give the node `LayoutStyle::overflow_hidden` so it does not grow to fit.
1409    pub fn scrollable(&mut self, node: NodeId) {
1410        if let Some(e) = self.element_mut(node) {
1411            e.scroll = true;
1412            e.clip = true;
1413        }
1414    }
1415
1416    /// Puts this scroll container's scrollbar `by` past its right edge, in the room around it.
1417    pub fn scrollbar_outside(&mut self, node: NodeId, by: f32) {
1418        if let Some(e) = self.element_mut(node) {
1419            e.scrollbar_outside = by;
1420        }
1421    }
1422
1423    /// Builds `view` as a floating layer above the rest of the UI, placed relative
1424    /// to `anchor`. `on_dismiss` is called on a click outside the layer and its
1425    /// anchor, or on Escape.
1426    pub fn overlay(
1427        &mut self,
1428        anchor: NodeId,
1429        placement: Placement,
1430        view: &dyn View,
1431        on_dismiss: Option<Rc<dyn Fn()>>,
1432    ) {
1433        // The slot comes before the content: overlays are placed and painted in
1434        // order, so one opened from inside this one must come after it, or it
1435        // would be placed with no anchor yet and painted underneath.
1436        let key = 0x6f76_6572_6c61_7900 + self.overlays.len() as u64;
1437        self.keyed_overlay(key, anchor, placement, view, on_dismiss);
1438    }
1439
1440    /// Like [`Self::overlay`], for a layer that must stay the same one when other
1441    /// layers before it come and go. A plain overlay is told apart by its place among
1442    /// them, so a new one before it makes it start over, and animate in again.
1443    pub fn keyed_overlay(
1444        &mut self,
1445        key: u64,
1446        anchor: NodeId,
1447        placement: Placement,
1448        view: &dyn View,
1449        on_dismiss: Option<Rc<dyn Fn()>>,
1450    ) {
1451        let slot = self.overlays.len();
1452        let gap = self.theme.spacing.s2;
1453        self.overlays.push(Overlay { root: anchor, anchor, placement, gap, on_dismiss, floating: false });
1454        let outer = self.foreground.take();
1455        let root = self.keyed_child(key, view);
1456        self.foreground = outer;
1457        self.overlays[slot].root = root;
1458    }
1459
1460    /// Builds `view` as a layer that stays open beside the UI, like a chat in a
1461    /// corner: it is under every menu and dialog, wherever it is built, keeps its state
1462    /// when they open, and is not closed by a click outside it.
1463    pub fn floating_overlay(&mut self, anchor: NodeId, placement: Placement, view: &dyn View) {
1464        // Its key does not depend on the other layers, which come and go.
1465        let key = 0x666c_6f61_7400_0000 + self.floating_overlays;
1466        self.floating_overlays += 1;
1467        let slot = self.overlays.len();
1468        let gap = self.theme.spacing.s2;
1469        self.overlays.push(Overlay {
1470            root: anchor,
1471            anchor,
1472            placement,
1473            gap,
1474            on_dismiss: None,
1475            floating: true,
1476        });
1477        let outer = self.foreground.take();
1478        let root = self.keyed_child(key, view);
1479        self.foreground = outer;
1480        self.overlays[slot].root = root;
1481    }
1482
1483    fn element_mut(&mut self, node: NodeId) -> Option<&mut Element> {
1484        self.elements.get_mut(node.0 as usize)
1485    }
1486
1487    fn push(&mut self, element: Element) -> NodeId {
1488        self.elements.push(element);
1489        NodeId((self.elements.len() - 1) as u32)
1490    }
1491
1492    fn fail(&mut self, e: LayoutError) -> LayoutId {
1493        tracing::error!(error = %e, "layout node could not be created");
1494        if self.error.is_none() {
1495            self.error = Some(e);
1496        }
1497        self.placeholder
1498    }
1499}
1500
1501/// What [`BuildCx::animated_numbers`] keeps between builds.
1502struct Numbers {
1503    from: Vec<f64>,
1504    to: Vec<f64>,
1505    start: f64,
1506    duration: f64,
1507    easing: ravenrs_animation::Easing,
1508}
1509
1510#[cfg_attr(feature = "live", ravenrs_live::expose)]
1511impl Numbers {
1512    fn at(values: &[f64]) -> Self {
1513        let easing = ravenrs_animation::Easing::Linear;
1514        Self { from: values.to_vec(), to: values.to_vec(), start: 0.0, duration: 0.0, easing }
1515    }
1516
1517    /// Exactly the target once the time is up.
1518    fn value(&self, now: f64) -> Vec<f64> {
1519        let t = if self.duration > 0.0 { (now - self.start) / self.duration } else { 1.0 };
1520        if t >= 1.0 {
1521            return self.to.clone();
1522        }
1523        let eased = f64::from(self.easing.sample(t.max(0.0) as f32));
1524        self.from.iter().zip(&self.to).map(|(a, b)| a + (b - a) * eased).collect()
1525    }
1526}