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#[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 pub(crate) fn raw(self) -> u64 {
41 self.0
42 }
43}
44
45#[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#[derive(Clone, Copy, Debug, PartialEq)]
53#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
54pub struct QuadPaint {
55 pub fill: Brush,
57 pub corner_radius: f32,
59 pub border: Option<Border>,
61 pub shadow: Option<Shadow>,
63}
64
65#[cfg_attr(feature = "live", ravenrs_live::expose)]
66impl QuadPaint {
67 pub fn new(fill: impl Into<Brush>) -> Self {
69 Self { fill: fill.into(), corner_radius: 0.0, border: None, shadow: None }
70 }
71}
72
73pub type DrawFn = Rc<dyn Fn(Rect, &mut Scene)>;
75
76pub type LiveFn = Rc<dyn Fn(Rect, f64, &mut Scene)>;
80
81#[derive(Debug)]
83#[non_exhaustive]
84pub struct PaintCx<'a> {
85 pub scene: &'a mut Scene,
87 pub text: &'a mut TextSystem,
89 pub scale: f32,
91}
92
93pub type RichFn = Rc<dyn Fn(Rect, &mut PaintCx<'_>)>;
95
96#[derive(Clone, Default)]
98#[non_exhaustive]
99#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
100pub enum Paint {
101 #[default]
103 None,
104 Quad(QuadPaint),
106 Custom(DrawFn),
108 Live(LiveFn),
110 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
126pub type KeyHandler = Rc<dyn Fn(&Key, Modifiers) -> bool>;
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131#[non_exhaustive]
132#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
133pub enum DragPhase {
134 Start,
136 Move,
138 End,
140}
141
142#[derive(Clone, Copy, Debug, PartialEq)]
144#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
145pub struct Drag {
146 pub phase: DragPhase,
148 pub position: Point,
150 pub frame: Rect,
152 pub container: Rect,
154}
155
156#[cfg_attr(feature = "live", ravenrs_live::expose)]
157impl Drag {
158 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
167pub type DragHandler = Rc<dyn Fn(Drag)>;
169
170#[derive(Clone, Copy, Debug)]
172#[non_exhaustive]
173pub enum UiActivity<'a> {
174 Painted(&'a Scene),
176 PointerMoved(Point),
178 Clicked(Point),
180 Described(&'a AccessTree),
182}
183
184pub type ActivityObserver = Rc<dyn Fn(UiActivity<'_>, f64)>;
186
187#[derive(Clone, Copy, Debug, PartialEq)]
189#[non_exhaustive]
190#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
191pub enum Placement {
192 Below,
194 Above,
196 Right,
198 AboveCenter,
200 RightCenter,
202 LeftCenter,
204 RightEnd,
206 LeftStart,
208 LeftEnd,
210 Fill,
212 At(Point),
214 BottomRight,
216 BottomLeft,
218 BottomCenter,
220 TopLeft,
222 TopCenter,
224 TopRight,
226}
227
228#[derive(Clone)]
232pub(crate) struct Shortcut {
233 pub key: Key,
234 pub command: bool,
235 pub shift: bool,
237 pub handler: Rc<dyn Fn()>,
238}
239
240#[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 pub(crate) fallback_name: bool,
275}
276
277#[cfg_attr(feature = "live", ravenrs_live::expose)]
278impl Semantics {
279 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 pub fn hidden() -> Self {
303 Self { hidden: true, ..Self::new(Role::Group) }
304 }
305
306 pub fn role(&self) -> Role {
308 self.role
309 }
310
311 pub fn name(mut self, name: impl Into<String>) -> Self {
313 self.name = Some(name.into());
314 self
315 }
316
317 pub fn description(mut self, description: impl Into<String>) -> Self {
319 self.description = Some(description.into());
320 self
321 }
322
323 pub fn value(mut self, value: impl Into<String>) -> Self {
325 self.value = Some(value.into());
326 self
327 }
328
329 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 pub fn toggled(mut self, toggled: impl Into<Toggled>) -> Self {
337 self.toggled = Some(toggled.into());
338 self
339 }
340
341 pub fn selected(mut self, selected: bool) -> Self {
343 self.selected = Some(selected);
344 self
345 }
346
347 pub fn expanded(mut self, expanded: bool) -> Self {
349 self.expanded = Some(expanded);
350 self
351 }
352
353 pub fn disabled(mut self, disabled: bool) -> Self {
355 self.disabled = disabled;
356 self
357 }
358
359 pub fn read_only(mut self, read_only: bool) -> Self {
361 self.read_only = read_only;
362 self
363 }
364
365 pub fn modal(mut self) -> Self {
367 self.modal = true;
368 self
369 }
370
371 pub fn active(mut self, active: bool) -> Self {
374 self.active = active;
375 self
376 }
377
378 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 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 pub text_color: Option<Color>,
402 pub on_click: Option<Rc<dyn Fn()>>,
403 pub focus_radius: Option<f32>,
405 pub cursor: Option<CursorIcon>,
406 pub on_key: Option<KeyHandler>,
407 pub on_drag: Option<DragHandler>,
408 pub clip: bool,
410 pub scroll: bool,
412 pub scroll_x: bool,
414 pub scrollbar_outside: f32,
416 pub rebuild_on_scroll: bool,
418 pub translate: Point,
420 pub on_double_click: Option<Rc<dyn Fn()>>,
421 pub on_pinch: Option<Rc<dyn Fn(f32)>>,
422 pub scrolls_with_widget: bool,
424 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 pub semantics: Option<Box<Semantics>>,
432 pub text: Option<Rc<str>>,
434 pub motion: Option<u32>,
436 pub measure: bool,
438 pub report_frame: bool,
439 pub pin: Option<f32>,
441 pub scroll_track: Option<crate::scroll_scene::ScrollTrack>,
443 pub scroll_link: Option<crate::ScrollLink>,
445 pub anchor: Option<(Rc<str>, f32)>,
447 pub on_appear: Option<Rc<dyn Fn()>>,
449 pub time_link: Option<crate::TimeLink>,
451 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
510pub(crate) struct TextLeaf {
512 pub layout: TextLayout,
513 pub truncate: Option<(Rc<str>, TextAttrs)>,
515}
516
517#[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 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#[derive(Debug, Default)]
536pub(crate) struct ScrollState {
537 pub offsets: HashMap<WidgetId, Point>,
538 pub viewports: HashMap<WidgetId, Size>,
540 pub contents: HashMap<WidgetId, Size>,
542}
543
544#[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 static DEFAULT_LABELS: Rc<crate::Labels> = Rc::new(crate::Labels::default());
569}
570
571pub struct BuildCx<'a> {
574 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 pub(crate) value_moving: bool,
614 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 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 pub fn foreground(&self) -> Option<Color> {
692 self.foreground
693 }
694
695 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 pub fn theme(&self) -> &Theme {
706 &self.theme
707 }
708
709 pub fn ui_waker(&self) -> crate::UiWaker {
711 self.ui_waker.clone()
712 }
713
714 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 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 pub fn theme_state(&self) -> State<Theme> {
737 self.theme_state.clone()
738 }
739
740 pub fn links(&self) -> crate::Links {
743 self.links.clone()
744 }
745
746 pub fn full_screen(&self) -> crate::FullScreen {
749 self.full_screen.clone()
750 }
751
752 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 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 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 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 pub fn clipboard(&self) -> crate::Clipboard {
796 self.clipboard.clone()
797 }
798
799 pub fn sound(&self) -> crate::Sound {
801 self.sound.clone()
802 }
803
804 pub fn file_picker(&self) -> crate::FilePicker {
806 self.file_picker.clone()
807 }
808
809 pub fn files_over(&self) -> bool {
811 self.files_over.get()
812 }
813
814 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 pub fn viewport(&self) -> Size {
823 self.viewport
824 }
825
826 pub fn widget_id(&self) -> WidgetId {
828 self.scopes.last().map_or(WidgetId::ROOT, |s| s.id)
829 }
830
831 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 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 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 pub fn is_pressed(&self) -> bool {
868 self.interaction.pressed == Some(self.widget_id())
869 }
870
871 pub fn is_focused(&self) -> bool {
873 self.interaction.focused == Some(self.widget_id())
874 }
875
876 pub fn is_focus_visible(&self) -> bool {
878 self.is_focused() && self.interaction.focus_visible
879 }
880
881 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 pub fn keyed_child(&mut self, key: u64, view: &dyn View) -> NodeId {
897 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 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 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 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 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 pub fn measure_text(&mut self, text: &str, attrs: &TextAttrs) -> Size {
969 self.text.layout(text, attrs).measure(None)
970 }
971
972 pub fn empty(&mut self) -> NodeId {
974 self.container(LayoutStyle { absolute: Some((0.0, 0.0)), ..LayoutStyle::leaf() }, &[], Paint::None)
976 }
977
978 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 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 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 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 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 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 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 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 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 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 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 pub fn clip(&mut self, node: NodeId) {
1088 if let Some(e) = self.element_mut(node) {
1089 e.clip = true;
1090 }
1091 }
1092
1093 pub fn scroll_offset(&self) -> Point {
1095 self.scroll.offsets.get(&self.widget_id()).copied().unwrap_or(Point::ZERO)
1096 }
1097
1098 pub fn scroll_viewport(&self) -> Option<Size> {
1102 self.scroll.viewports.get(&self.widget_id()).copied()
1103 }
1104
1105 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 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 pub fn now(&self) -> f64 {
1122 self.now
1123 }
1124
1125 pub fn observe_activity(&mut self, observer: ActivityObserver) {
1129 self.activity = Some(observer);
1130 }
1131
1132 pub fn ask_for_access_tree(&mut self) {
1136 self.access_tree_asked = true;
1137 }
1138
1139 pub fn wake_at(&mut self, time: f64) {
1142 self.wake = Some(self.wake.map_or(time, |w| w.min(time)));
1143 }
1144
1145 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 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 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 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 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 pub fn reduced_motion(&self) -> bool {
1239 self.reduced_motion
1240 }
1241
1242 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 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 pub fn measured_content(&self) -> Option<Size> {
1261 self.scroll.contents.get(&self.widget_id()).copied()
1262 }
1263
1264 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 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 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 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 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 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 pub fn request_focus(&mut self) {
1311 self.focus_request = Some(self.widget_id());
1312 }
1313
1314 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 pub fn modifiers(&self) -> Rc<std::cell::Cell<Modifiers>> {
1325 self.modifiers.clone()
1326 }
1327
1328 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 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 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 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 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 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 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 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 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 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 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 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 let key = 0x6f76_6572_6c61_7900 + self.overlays.len() as u64;
1437 self.keyed_overlay(key, anchor, placement, view, on_dismiss);
1438 }
1439
1440 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 pub fn floating_overlay(&mut self, anchor: NodeId, placement: Placement, view: &dyn View) {
1464 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
1501struct 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 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}