1pub(crate) mod focus;
30mod lazy;
31mod widgets;
32
33pub use crate::boundary::LaunchBuilder;
34pub use crate::brush::{Brush, Stop, brush};
35pub use crate::clipboard::{Clipboard, use_clipboard};
36pub use crate::drawing::{DrawCommand, DrawList, DrawListBuilder};
37pub use crate::message::{Message, show_message};
38pub use crate::runtime::{application, launch};
39pub use crate::schema::SemanticsRole as Role;
40pub use crate::schema::{
41 Alignment, Arrangement, ButtonKind, ButtonVariant, Color, ColorRole, ColorScheme,
42 CompactNavigation, DesignSystem, HorizontalAlignment, IconRole, Key, MaterialRole,
43 MessageDuration, MotionRole, Paint, PopoverAlign, PopoverPlacement, SemanticsRole, ShapeRole,
44 SpaceRole, TextAlign, TextOverflow, Theme, TypeRole, VerticalAlignment, WindowHeightClass,
45 WindowSizeClass,
46};
47pub use crate::spans::TextSpans;
48pub use crate::theme::{ThemeHandle, current_theme};
49
50pub fn theme() -> ThemeHandle {
53 ThemeHandle::default()
54}
55pub use crate::window::{NodeSize, WindowSafeArea, WindowSize};
56pub use crate::{
57 Cursor, FileDrop, KeyEvent, PointerEvent, PointerKind, PointerKinds, RangeRequest, UserSelect,
58 asset, pointer_button,
59};
60pub use focus::{FocusHandle, use_focus_handle};
61pub use lazy::{LazyColumn, LazyGrid, LazyList, LazyListScope, LazyRow};
62pub use widgets::*;
63
64use crate::runtime::composer::{EventCallback, OwnedAttr};
65use crate::runtime::{group_guard, with_composer, with_composer_if_idle};
66use crate::schema::WidgetKind;
67
68pub const MAX_SHADOWS: usize = 4;
70
71#[derive(Clone, Copy, Debug, PartialEq)]
74pub struct Shadow {
75 pub x: f32,
76 pub y: f32,
77 pub blur: f32,
78 pub spread: f32,
79 pub paint: Paint,
80}
81
82impl Shadow {
83 pub const fn new(x: f32, y: f32, blur: f32, spread: f32, paint: Paint) -> Self {
84 Self {
85 x,
86 y,
87 blur,
88 spread,
89 paint,
90 }
91 }
92}
93
94#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
97pub struct SemanticsState {
98 pub checked: Option<bool>,
99 pub selected: Option<bool>,
100 pub expanded: Option<bool>,
101 pub disabled: Option<bool>,
102 pub focused: Option<bool>,
103 pub hidden: bool,
104 pub click_action: bool,
105 pub focus_action: bool,
106}
107
108impl SemanticsState {
109 const NONE: Self = Self {
110 checked: None,
111 selected: None,
112 expanded: None,
113 disabled: None,
114 focused: None,
115 hidden: false,
116 click_action: false,
117 focus_action: false,
118 };
119
120 pub const fn checked(checked: bool) -> Self {
121 Self {
122 checked: Some(checked),
123 ..Self::NONE
124 }
125 }
126
127 pub const fn selected(selected: bool) -> Self {
128 Self {
129 selected: Some(selected),
130 ..Self::NONE
131 }
132 }
133
134 pub const fn expanded(expanded: bool) -> Self {
135 Self {
136 expanded: Some(expanded),
137 ..Self::NONE
138 }
139 }
140
141 pub const fn disabled(disabled: bool) -> Self {
142 Self {
143 disabled: Some(disabled),
144 ..Self::NONE
145 }
146 }
147
148 pub const fn focused(focused: bool) -> Self {
149 Self {
150 focused: Some(focused),
151 ..Self::NONE
152 }
153 }
154
155 pub const fn hidden(hidden: bool) -> Self {
156 Self {
157 hidden,
158 ..Self::NONE
159 }
160 }
161
162 pub const fn click_action(exposed: bool) -> Self {
163 Self {
164 click_action: exposed,
165 ..Self::NONE
166 }
167 }
168
169 pub const fn focus_action(exposed: bool) -> Self {
170 Self {
171 focus_action: exposed,
172 ..Self::NONE
173 }
174 }
175
176 pub(crate) fn bits(self) -> u32 {
178 use crate::schema::*;
179 let tri = |value: Option<bool>| value.map_or(0_u32, |on| if on { 2 } else { 1 });
180 (tri(self.checked) << SEMANTICS_CHECKED_SHIFT)
181 | (tri(self.selected) << SEMANTICS_SELECTED_SHIFT)
182 | (tri(self.expanded) << SEMANTICS_EXPANDED_SHIFT)
183 | (tri(self.disabled) << SEMANTICS_DISABLED_SHIFT)
184 | (tri(self.focused) << SEMANTICS_FOCUSED_SHIFT)
185 | if self.hidden { SEMANTICS_HIDDEN_BIT } else { 0 }
186 | if self.click_action {
187 SEMANTICS_CLICK_ACTION_BIT
188 } else {
189 0
190 }
191 | if self.focus_action {
192 SEMANTICS_FOCUS_ACTION_BIT
193 } else {
194 0
195 }
196 }
197}
198
199pub(crate) const MAX_LABELS: usize = 4096;
203
204struct LabelTable {
206 kept: std::sync::RwLock<std::collections::HashSet<&'static str>>,
207 warned: std::sync::atomic::AtomicBool,
208}
209
210impl LabelTable {
211 fn new() -> Self {
212 Self {
213 kept: Default::default(),
214 warned: Default::default(),
215 }
216 }
217
218 fn intern(&self, label: &str, cap: usize) -> Option<&'static str> {
222 use std::sync::atomic::Ordering;
223
224 if label.is_empty() {
225 return None;
226 }
227 {
228 let kept = self.kept.read().unwrap_or_else(|p| p.into_inner());
229 if let Some(found) = kept.get(label) {
230 return Some(*found);
231 }
232 }
233 let mut kept = self.kept.write().unwrap_or_else(|p| p.into_inner());
234 if let Some(found) = kept.get(label) {
235 return Some(*found);
236 }
237 if kept.len() >= cap {
238 if cfg!(debug_assertions) && !self.warned.swap(true, Ordering::Relaxed) {
239 eprintln!(
240 "compose-rust: more than {cap} distinct accessible names; \
241 names past the limit are left off. Use a fixed set of names."
242 );
243 }
244 return None;
245 }
246 let leaked: &'static str = Box::leak(label.to_owned().into_boxed_str());
247 kept.insert(leaked);
248 Some(leaked)
249 }
250
251 #[cfg(test)]
252 fn len(&self) -> usize {
253 self.kept.read().unwrap_or_else(|p| p.into_inner()).len()
254 }
255}
256
257fn keep_label(label: &str) -> Option<&'static str> {
258 use std::cell::Cell;
259 use std::sync::OnceLock;
260
261 static LABELS: OnceLock<LabelTable> = OnceLock::new();
262 thread_local! {
263 static LAST: Cell<Option<&'static str>> = const { Cell::new(None) };
266 }
267 if let Some(last) = LAST.with(Cell::get) {
268 if last == label {
269 return Some(last);
270 }
271 }
272 let kept = LABELS
273 .get_or_init(LabelTable::new)
274 .intern(label, MAX_LABELS);
275 if kept.is_some() {
276 LAST.with(|last| last.set(kept));
277 }
278 kept
279}
280
281#[derive(Clone, Copy, Debug, Default, PartialEq)]
294pub struct Modifier {
295 observe_size: Option<i64>,
296 observe_size_every_dp: bool,
297 motion: Option<MotionRole>,
298 material: Option<MaterialRole>,
299 weight: Option<f32>,
300 width: Option<f32>,
301 height: Option<f32>,
302 padding: Option<f32>,
303 padding_role: Option<SpaceRole>,
304 background: Option<Paint>,
305 shape_role: Option<ShapeRole>,
306 corner_radius: Option<f32>,
307 corner_radii: Option<[f32; 4]>,
308 corner_curve: Option<f32>,
309 border_width: Option<f32>,
310 border_color: Option<Paint>,
311 elevation: Option<f32>,
312 alpha: Option<f32>,
313 press_feedback: Option<(f32, f32)>,
314 semantics_role: Option<SemanticsRole>,
315 semantics_label: Option<&'static str>,
316 semantics_state: SemanticsState,
317 transform: Option<[f32; 8]>,
318 animated_alpha: Option<crate::Animated<f32>>,
319 animated_background: Option<crate::Animated<Paint>>,
320 animated_offset: Option<crate::Animated<(f32, f32)>>,
321 cursor: Option<crate::Cursor>,
322 user_select: Option<crate::UserSelect>,
323 clip: bool,
324 window_drag: Option<bool>,
325 offset: Option<(f32, f32)>,
326 shadows: [Option<Shadow>; MAX_SHADOWS],
327 fill_max_width: bool,
328 match_parent_size: bool,
329 fill_max_height: bool,
330}
331
332#[allow(non_upper_case_globals)]
334pub const Modifier: Modifier = Modifier::new();
335
336impl Modifier {
337 pub const fn new() -> Self {
339 Self {
340 observe_size: None,
341 observe_size_every_dp: false,
342 motion: None,
343 material: None,
344 weight: None,
345 width: None,
346 height: None,
347 padding: None,
348 padding_role: None,
349 background: None,
350 shape_role: None,
351 corner_radius: None,
352 corner_radii: None,
353 corner_curve: None,
354 border_width: None,
355 border_color: None,
356 elevation: None,
357 alpha: None,
358 press_feedback: None,
359 semantics_role: None,
360 semantics_label: None,
361 semantics_state: SemanticsState::NONE,
362 transform: None,
363 animated_alpha: None,
364 animated_background: None,
365 animated_offset: None,
366 cursor: None,
367 user_select: None,
368 clip: false,
369 window_drag: None,
370 offset: None,
371 shadows: [None; MAX_SHADOWS],
372 fill_max_width: false,
373 match_parent_size: false,
374 fill_max_height: false,
375 }
376 }
377
378 pub const fn weight(mut self, weight: f32) -> Self {
380 self.weight = Some(weight);
381 self
382 }
383
384 pub const fn width(mut self, dp: f32) -> Self {
385 self.width = Some(dp);
386 self
387 }
388
389 pub const fn height(mut self, dp: f32) -> Self {
390 self.height = Some(dp);
391 self
392 }
393
394 pub const fn size(self, width: f32, height: f32) -> Self {
396 self.width(width).height(height)
397 }
398
399 pub const fn width_if(mut self, dp: Option<f32>) -> Self {
401 self.width = dp;
402 self
403 }
404
405 pub const fn match_parent_size(mut self) -> Self {
407 self.match_parent_size = true;
408 self
409 }
410
411 pub const fn fill_max_width(mut self) -> Self {
412 self.fill_max_width = true;
413 self
414 }
415
416 pub const fn fill_max_height(mut self) -> Self {
417 self.fill_max_height = true;
418 self
419 }
420
421 pub const fn fill_max_size(self) -> Self {
422 self.fill_max_width().fill_max_height()
423 }
424
425 pub const fn fill_max_width_if(mut self, fill: bool) -> Self {
427 self.fill_max_width = fill;
428 self
429 }
430
431 pub const fn padding(mut self, dp: f32) -> Self {
433 self.padding = Some(dp);
434 self
435 }
436
437 pub const fn padding_role(mut self, role: SpaceRole) -> Self {
439 self.padding_role = Some(role);
440 self
441 }
442
443 pub const fn padding_role_if(mut self, role: Option<SpaceRole>) -> Self {
445 self.padding_role = role;
446 self
447 }
448
449 pub const fn background(mut self, paint: Paint) -> Self {
450 self.background = Some(paint);
451 self
452 }
453
454 pub const fn background_if(mut self, paint: Option<Paint>) -> Self {
456 self.background = paint;
457 self
458 }
459
460 pub const fn shape_role(mut self, role: ShapeRole) -> Self {
462 self.shape_role = Some(role);
463 self
464 }
465
466 pub const fn corner_radius(mut self, dp: f32) -> Self {
468 self.corner_radius = Some(dp);
469 self.corner_radii = None;
470 self
471 }
472
473 pub const fn corner_radii(mut self, radii: [f32; 4]) -> Self {
476 self.corner_radii = Some(radii);
477 self.corner_radius = None;
478 self
479 }
480
481 pub const fn clip(mut self, clip: bool) -> Self {
483 self.clip = clip;
484 self
485 }
486
487 pub const fn window_drag(mut self, enabled: bool) -> Self {
489 self.window_drag = Some(enabled);
490 self
491 }
492
493 pub const fn border(mut self, width: f32, paint: Paint) -> Self {
494 self.border_width = Some(width);
495 self.border_color = Some(paint);
496 self
497 }
498
499 pub const fn elevation(mut self, dp: f32) -> Self {
501 self.elevation = Some(dp);
502 self
503 }
504
505 pub const fn observe_size(mut self, token: i64) -> Self {
512 self.observe_size = Some(token);
513 self.observe_size_every_dp = false;
514 self
515 }
516
517 pub const fn observe_size_every_dp(mut self, token: i64) -> Self {
521 self.observe_size = Some(token);
522 self.observe_size_every_dp = true;
523 self
524 }
525
526 pub const fn motion(mut self, role: MotionRole) -> Self {
528 self.motion = Some(role);
529 self
530 }
531
532 pub const fn material(mut self, role: MaterialRole) -> Self {
534 self.material = Some(role);
535 self
536 }
537
538 pub const fn offset(mut self, x: f32, y: f32) -> Self {
540 self.offset = Some((x, y));
541 self
542 }
543
544 pub const fn shadow(mut self, shadow: Shadow) -> Self {
547 let mut index = 0;
548 while index < MAX_SHADOWS {
549 if self.shadows[index].is_none() {
550 self.shadows[index] = Some(shadow);
551 break;
552 }
553 index += 1;
554 }
555 self
556 }
557
558 pub fn shadows(mut self, shadows: impl IntoIterator<Item = Shadow>) -> Self {
560 for shadow in shadows {
561 self = self.shadow(shadow);
562 }
563 self
564 }
565
566 pub const fn alpha(mut self, alpha: f32) -> Self {
568 self.alpha = Some(alpha);
569 self
570 }
571
572 pub fn corner_curve(mut self, smoothing: f32) -> Self {
579 self.corner_curve = Some(crate::schema::corner_smoothing(smoothing));
580 self
581 }
582
583 pub const fn press_feedback(mut self, scale: f32, tint_alpha: f32) -> Self {
585 self.press_feedback = Some((scale, tint_alpha));
586 self
587 }
588
589 pub fn label(mut self, label: &str) -> Self {
596 self.semantics_label = keep_label(label);
597 self
598 }
599
600 pub const fn role(mut self, role: Role) -> Self {
603 self.semantics_role = Some(role);
604 self
605 }
606
607 pub const fn checked(mut self, checked: bool) -> Self {
609 self.semantics_state.checked = Some(checked);
610 self
611 }
612
613 pub const fn selected(mut self, selected: bool) -> Self {
615 self.semantics_state.selected = Some(selected);
616 self
617 }
618
619 pub const fn expanded(mut self, expanded: bool) -> Self {
621 self.semantics_state.expanded = Some(expanded);
622 self
623 }
624
625 pub const fn disabled(mut self, disabled: bool) -> Self {
627 self.semantics_state.disabled = Some(disabled);
628 self
629 }
630
631 pub const fn focused(mut self, focused: bool) -> Self {
633 self.semantics_state.focused = Some(focused);
634 self
635 }
636
637 pub const fn hidden(mut self, hidden: bool) -> Self {
639 self.semantics_state.hidden = hidden;
640 self
641 }
642
643 pub const fn click_action(mut self, exposed: bool) -> Self {
645 self.semantics_state.click_action = exposed;
646 self
647 }
648
649 pub const fn focus_action(mut self, exposed: bool) -> Self {
651 self.semantics_state.focus_action = exposed;
652 self
653 }
654
655 pub fn semantics(self, role: Role, label: &str, state: SemanticsState) -> Self {
657 let mut named = self.role(role).label(label);
658 named.semantics_state = state;
659 named
660 }
661
662 pub fn graphics_layer(mut self, layer: crate::GraphicsLayer) -> Self {
666 if let crate::schema::Modifier::Transform {
667 a,
668 b,
669 c,
670 d,
671 e,
672 f,
673 origin_x,
674 origin_y,
675 } = layer.transform()
676 {
677 self.transform = Some([a, b, c, d, e, f, origin_x, origin_y]);
678 }
679 self.alpha = Some(layer.alpha);
680 self
681 }
682
683 pub const fn animated_alpha(mut self, value: crate::Animated<f32>) -> Self {
686 self.animated_alpha = Some(value);
687 self
688 }
689
690 pub const fn animated_background(mut self, value: crate::Animated<Paint>) -> Self {
693 self.animated_background = Some(value);
694 self
695 }
696
697 pub const fn animated_offset(mut self, value: crate::Animated<(f32, f32)>) -> Self {
700 self.animated_offset = Some(value);
701 self
702 }
703
704 pub const fn cursor(mut self, cursor: crate::Cursor) -> Self {
708 self.cursor = Some(cursor);
709 self
710 }
711
712 pub const fn user_select(mut self, user_select: crate::UserSelect) -> Self {
715 self.user_select = Some(user_select);
716 self
717 }
718}
719
720#[derive(Clone, Copy, PartialEq, Eq)]
723pub(crate) enum Mods {
724 Container,
726 Standard,
728 Chip,
730 Scaffold,
732 None,
734}
735
736pub(crate) type Attrs = Vec<(&'static str, OwnedAttr)>;
737
738fn float(value: Option<f32>) -> OwnedAttr {
739 value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
740}
741
742fn role(value: Option<impl Into<u16>>) -> OwnedAttr {
743 value.map_or(OwnedAttr::None, |value| {
744 OwnedAttr::Int(i64::from(value.into()))
745 })
746}
747
748fn paint(value: Option<Paint>) -> OwnedAttr {
749 value.map_or(OwnedAttr::None, |paint| {
750 OwnedAttr::Int(paint.to_bits() as i64)
751 })
752}
753
754pub(crate) fn role_or_zero(value: Option<impl Into<u16>>) -> OwnedAttr {
756 OwnedAttr::Int(value.map_or(0, |value| i64::from(value.into())))
757}
758
759pub(crate) fn dp_or_zero(value: Option<f32>) -> OwnedAttr {
761 OwnedAttr::Float(f64::from(value.unwrap_or(0.0)))
762}
763
764pub(crate) fn paint_or_zero(value: Option<Paint>) -> OwnedAttr {
766 OwnedAttr::Int(value.map_or(0, |paint| paint.to_bits() as i64))
767}
768
769pub(crate) fn optional_text(value: Option<String>) -> OwnedAttr {
770 value.map_or(OwnedAttr::None, OwnedAttr::Text)
771}
772
773fn semantics_bits(modifier: &Modifier) -> Option<i64> {
776 let state = modifier.semantics_state;
777 if modifier.semantics_role.is_none() && state == SemanticsState::NONE {
778 return None;
779 }
780 let role = i64::from(u16::from(
781 modifier.semantics_role.unwrap_or(SemanticsRole::None),
782 ));
783 Some(role | (i64::from(state.bits()) << 16))
784}
785
786pub(crate) fn push_modifiers(modifier: &Modifier, mods: Mods, out: &mut Attrs) {
788 let container = mods == Mods::Container;
789 let standard = container || mods == Mods::Standard;
790 let chip = standard || mods == Mods::Chip;
791 let scaffold = mods == Mods::Scaffold;
792 let mut push = |always: bool, name: &'static str, value: OwnedAttr| {
793 let set = !matches!(value, OwnedAttr::None | OwnedAttr::Bool(false));
794 if always || set {
795 out.push((name, value));
796 }
797 };
798 let (plain, every_dp) = match modifier.observe_size {
801 Some(token) if modifier.observe_size_every_dp => (OwnedAttr::None, OwnedAttr::Int(token)),
802 Some(token) => (OwnedAttr::Int(token), OwnedAttr::None),
803 None => (OwnedAttr::None, OwnedAttr::None),
804 };
805 push(container, "observe_size", plain);
806 push(container, "observe_size_every_dp", every_dp);
807 push(container, "motion", role(modifier.motion));
808 push(container || scaffold, "material", role(modifier.material));
809 push(chip, "weight", float(modifier.weight));
810 push(chip, "width", float(modifier.width));
811 push(chip, "height", float(modifier.height));
812 push(chip, "padding", float(modifier.padding));
813 push(chip, "padding_role", role(modifier.padding_role));
814 push(
816 standard || scaffold,
817 "background",
818 paint(if modifier.animated_background.is_some() {
819 None
820 } else {
821 modifier.background
822 }),
823 );
824 push(standard, "shape_role", role(modifier.shape_role));
825 push(standard, "corner_radius", float(modifier.corner_radius));
826 push(
827 false,
828 "corner_radii",
829 modifier.corner_radii.map_or(OwnedAttr::None, |values| {
830 OwnedAttr::Bytes(
831 values
832 .iter()
833 .flat_map(|value| value.to_le_bytes())
834 .collect(),
835 )
836 }),
837 );
838 push(false, "corner_curve", float(modifier.corner_curve));
839 push(false, "clip", OwnedAttr::Bool(modifier.clip));
840 push(
841 true,
842 "window_drag",
843 modifier
844 .window_drag
845 .map_or(OwnedAttr::None, OwnedAttr::Bool),
846 );
847 push(standard, "border_width", float(modifier.border_width));
848 push(standard, "border_color", paint(modifier.border_color));
849 push(standard, "elevation", float(modifier.elevation));
850 push(
851 false,
852 "match_parent_size",
853 OwnedAttr::Bool(modifier.match_parent_size),
854 );
855 push(
856 chip,
857 "fill_max_width",
858 OwnedAttr::Bool(modifier.fill_max_width),
859 );
860 push(
861 standard,
862 "fill_max_height",
863 OwnedAttr::Bool(modifier.fill_max_height),
864 );
865 push(
868 false,
869 "press_feedback",
870 modifier
871 .press_feedback
872 .map_or(OwnedAttr::None, |(scale, alpha)| {
873 OwnedAttr::Bytes([scale.to_le_bytes(), alpha.to_le_bytes()].concat())
874 }),
875 );
876 push(
879 false,
880 "semantics",
881 semantics_bits(modifier).map_or(OwnedAttr::None, OwnedAttr::Int),
882 );
883 push(
884 false,
885 "accessible_name",
886 modifier
887 .semantics_label
888 .map_or(OwnedAttr::None, OwnedAttr::Label),
889 );
890 push(
891 false,
892 "alpha",
893 float(if modifier.animated_alpha.is_some() {
894 None
895 } else {
896 modifier.alpha
897 }),
898 );
899 push(
900 false,
901 "transform",
902 match (modifier.animated_offset, modifier.transform) {
903 (None, Some(values)) => OwnedAttr::Bytes(
904 values
905 .iter()
906 .flat_map(|value| value.to_le_bytes())
907 .collect(),
908 ),
909 _ => OwnedAttr::None,
910 },
911 );
912 push(
914 false,
915 "cursor",
916 match modifier.cursor {
917 Some(crate::Cursor::Auto) | None => OwnedAttr::None,
918 Some(cursor) => OwnedAttr::Int(i64::from(u16::from(cursor))),
919 },
920 );
921 push(
922 false,
923 "user_select",
924 match modifier.user_select {
925 Some(crate::UserSelect::Auto) | None => OwnedAttr::None,
926 Some(select) => OwnedAttr::Int(i64::from(u16::from(select))),
927 },
928 );
929 push(
931 false,
932 "offset",
933 modifier.offset.map_or(OwnedAttr::None, |(x, y)| {
934 let mut bytes = Vec::with_capacity(8);
935 bytes.extend_from_slice(&x.to_le_bytes());
936 bytes.extend_from_slice(&y.to_le_bytes());
937 OwnedAttr::Bytes(bytes)
938 }),
939 );
940 push(
941 false,
942 "shadows",
943 if modifier.shadows[0].is_none() {
944 OwnedAttr::None
945 } else {
946 let mut bytes = Vec::new();
947 for shadow in modifier.shadows.iter().flatten() {
948 for value in [shadow.x, shadow.y, shadow.blur, shadow.spread] {
949 bytes.extend_from_slice(&value.to_le_bytes());
950 }
951 bytes.extend_from_slice(&shadow.paint.to_bits().to_le_bytes());
952 }
953 OwnedAttr::Bytes(bytes)
954 },
955 );
956 push(
957 false,
958 "animated_alpha",
959 modifier.animated_alpha.map_or(OwnedAttr::None, |value| {
960 OwnedAttr::Bytes(crate::animation::animated_bytes(
961 &[value.target.to_bits()],
962 &value.spec,
963 ))
964 }),
965 );
966 push(
967 false,
968 "animated_background",
969 modifier
970 .animated_background
971 .map_or(OwnedAttr::None, |value| {
972 let bits = value.target.to_bits();
973 OwnedAttr::Bytes(crate::animation::animated_bytes(
974 &[bits as u32, (bits >> 32) as u32],
975 &value.spec,
976 ))
977 }),
978 );
979 push(
980 false,
981 "animated_offset",
982 modifier.animated_offset.map_or(OwnedAttr::None, |value| {
983 OwnedAttr::Bytes(crate::animation::animated_bytes(
984 &[value.target.0.to_bits(), value.target.1.to_bits()],
985 &value.spec,
986 ))
987 }),
988 );
989}
990
991pub(crate) fn emit(
997 key: u64,
998 widget: WidgetKind,
999 fill: impl FnOnce(&mut Attrs),
1000 listeners: Vec<(&'static str, EventCallback)>,
1001 content: impl FnOnce(),
1002) {
1003 let _group = group_guard(key);
1004 emit_node(widget, fill, listeners, content);
1005}
1006
1007pub(crate) fn emit_node(
1010 widget: WidgetKind,
1011 fill: impl FnOnce(&mut Attrs),
1012 listeners: Vec<(&'static str, EventCallback)>,
1013 content: impl FnOnce(),
1014) {
1015 let node = with_composer_if_idle(|composer| {
1016 if !composer.composing() {
1017 return None;
1018 }
1019 let mut attrs = composer.take_attrs();
1020 fill(&mut attrs);
1021 Some(composer.update_node(widget, attrs, listeners))
1022 })
1023 .flatten();
1024 let Some(node) = node else {
1025 return;
1026 };
1027 with_composer(|composer| composer.push_node_context(node));
1028 let _context = NodeContextGuard;
1029 content();
1030}
1031
1032struct NodeContextGuard;
1034
1035impl Drop for NodeContextGuard {
1036 fn drop(&mut self) {
1037 with_composer_if_idle(|composer| composer.pop_node_context());
1038 }
1039}
1040
1041pub(crate) const fn widget_key(name: &str) -> u64 {
1043 crate::runtime::composer::call_site(name, 0)
1044}
1045
1046#[cfg(test)]
1047mod fr19_9_tests {
1048 use super::*;
1049
1050 #[test]
1051 fn fr19_9_window_drag_builder_preserves_false() {
1052 for enabled in [false, true] {
1053 let mut attrs = Vec::new();
1054 push_modifiers(
1055 &Modifier::new().window_drag(enabled),
1056 Mods::Standard,
1057 &mut attrs,
1058 );
1059 assert!(attrs.iter().any(|(name, value)| *name == "window_drag"
1060 && matches!(value, OwnedAttr::Bool(value) if *value == enabled)));
1061 }
1062 }
1063}
1064
1065#[cfg(test)]
1066mod fr68_label_tests {
1067 use super::*;
1068
1069 #[test]
1070 fn fr68_repeated_labels_do_not_grow_the_table() {
1071 let table = LabelTable::new();
1072 let first = table.intern("Refresh", MAX_LABELS).unwrap();
1073 for _ in 0..10_000 {
1074 let again = table.intern("Refresh", MAX_LABELS).unwrap();
1075 assert!(std::ptr::eq(first, again));
1076 }
1077 assert_eq!(table.len(), 1);
1078 }
1079
1080 #[test]
1081 fn fr68_labels_past_the_cap_are_dropped() {
1082 let table = LabelTable::new();
1083 for n in 0..MAX_LABELS {
1084 assert!(table.intern(&format!("label {n}"), MAX_LABELS).is_some());
1085 }
1086 assert_eq!(table.len(), MAX_LABELS);
1087 assert_eq!(table.intern("one too many", MAX_LABELS), None);
1088 assert_eq!(table.len(), MAX_LABELS);
1089 assert!(table.intern("label 0", MAX_LABELS).is_some());
1091 }
1092}