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::font_features::FontFeature;
38pub use crate::message::{Message, show_message};
39pub use crate::runtime::{application, launch};
40pub use crate::schema::SemanticsRole as Role;
41pub use crate::schema::{
42 Alignment, Arrangement, BackgroundRepeat, ButtonKind, ButtonVariant, Color, ColorRole,
43 ColorScheme, CompactNavigation, ContentScale, DesignSystem, HorizontalAlignment, IconRole, Key,
44 MaterialRole, MessageDuration, MessageHorizontal, MessagePlacement, MessageVertical,
45 MotionRole, Paint, PopoverAlign, PopoverPlacement, SemanticsRole, ShapeRole, Snap, SpaceRole,
46 TextAlign, TextOverflow, Theme, TypeRole, VerticalAlignment, WindowHeightClass,
47 WindowSizeClass,
48};
49pub use crate::spans::TextSpans;
50pub use crate::theme::{ThemeHandle, current_theme};
51
52pub fn theme() -> ThemeHandle {
55 ThemeHandle::default()
56}
57pub use crate::window::{NodeSize, WindowSafeArea, WindowSize};
58pub use crate::{
59 Cursor, FileDrop, KeyEvent, PointerEvent, PointerKind, PointerKinds, RangeRequest, UserSelect,
60 asset, pointer_button,
61};
62pub use focus::{FocusHandle, use_focus_handle};
63pub use lazy::{LazyColumn, LazyGrid, LazyList, LazyListScope, LazyRow};
64pub use widgets::*;
65
66use crate::runtime::composer::{EventCallback, OwnedAttr};
67use crate::runtime::{group_guard, with_composer, with_composer_if_idle};
68use crate::schema::WidgetKind;
69
70pub const MAX_SHADOWS: usize = 4;
72
73#[derive(Clone, Copy, Debug, PartialEq)]
76pub struct Shadow {
77 pub x: f32,
78 pub y: f32,
79 pub blur: f32,
80 pub spread: f32,
81 pub paint: Paint,
82}
83
84impl Shadow {
85 pub const fn new(x: f32, y: f32, blur: f32, spread: f32, paint: Paint) -> Self {
86 Self {
87 x,
88 y,
89 blur,
90 spread,
91 paint,
92 }
93 }
94}
95
96pub const MAX_BACKGROUND_BRUSHES: usize = 4;
98
99#[derive(Clone, Copy, Debug, PartialEq)]
104pub struct BackgroundBrush {
105 pub x: f32,
106 pub y: f32,
107 pub width: f32,
108 pub height: f32,
109 pub paint: Paint,
110 pub repeat: BackgroundRepeat,
111 pub tile_width: f32,
113 pub tile_height: f32,
114}
115
116impl BackgroundBrush {
117 pub const fn new(x: f32, y: f32, width: f32, height: f32, paint: Paint) -> Self {
119 Self {
120 x,
121 y,
122 width,
123 height,
124 paint,
125 repeat: BackgroundRepeat::NoRepeat,
126 tile_width: width,
127 tile_height: height,
128 }
129 }
130
131 pub const fn repeat(mut self, repeat: BackgroundRepeat) -> Self {
134 self.repeat = repeat;
135 self
136 }
137
138 pub const fn step(mut self, width: f32, height: f32) -> Self {
141 self.tile_width = width;
142 self.tile_height = height;
143 self
144 }
145}
146
147#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
150pub struct SemanticsState {
151 pub checked: Option<bool>,
152 pub selected: Option<bool>,
153 pub expanded: Option<bool>,
154 pub disabled: Option<bool>,
155 pub focused: Option<bool>,
156 pub hidden: bool,
157 pub click_action: bool,
158 pub focus_action: bool,
159}
160
161impl SemanticsState {
162 const NONE: Self = Self {
163 checked: None,
164 selected: None,
165 expanded: None,
166 disabled: None,
167 focused: None,
168 hidden: false,
169 click_action: false,
170 focus_action: false,
171 };
172
173 pub const fn checked(checked: bool) -> Self {
174 Self {
175 checked: Some(checked),
176 ..Self::NONE
177 }
178 }
179
180 pub const fn selected(selected: bool) -> Self {
181 Self {
182 selected: Some(selected),
183 ..Self::NONE
184 }
185 }
186
187 pub const fn expanded(expanded: bool) -> Self {
188 Self {
189 expanded: Some(expanded),
190 ..Self::NONE
191 }
192 }
193
194 pub const fn disabled(disabled: bool) -> Self {
195 Self {
196 disabled: Some(disabled),
197 ..Self::NONE
198 }
199 }
200
201 pub const fn focused(focused: bool) -> Self {
202 Self {
203 focused: Some(focused),
204 ..Self::NONE
205 }
206 }
207
208 pub const fn hidden(hidden: bool) -> Self {
209 Self {
210 hidden,
211 ..Self::NONE
212 }
213 }
214
215 pub const fn click_action(exposed: bool) -> Self {
216 Self {
217 click_action: exposed,
218 ..Self::NONE
219 }
220 }
221
222 pub const fn focus_action(exposed: bool) -> Self {
223 Self {
224 focus_action: exposed,
225 ..Self::NONE
226 }
227 }
228
229 pub(crate) fn bits(self) -> u32 {
231 use crate::schema::*;
232 let tri = |value: Option<bool>| value.map_or(0_u32, |on| if on { 2 } else { 1 });
233 (tri(self.checked) << SEMANTICS_CHECKED_SHIFT)
234 | (tri(self.selected) << SEMANTICS_SELECTED_SHIFT)
235 | (tri(self.expanded) << SEMANTICS_EXPANDED_SHIFT)
236 | (tri(self.disabled) << SEMANTICS_DISABLED_SHIFT)
237 | (tri(self.focused) << SEMANTICS_FOCUSED_SHIFT)
238 | if self.hidden { SEMANTICS_HIDDEN_BIT } else { 0 }
239 | if self.click_action {
240 SEMANTICS_CLICK_ACTION_BIT
241 } else {
242 0
243 }
244 | if self.focus_action {
245 SEMANTICS_FOCUS_ACTION_BIT
246 } else {
247 0
248 }
249 }
250}
251
252pub(crate) const MAX_LABELS: usize = 4096;
256
257struct LabelTable {
259 kept: std::sync::RwLock<std::collections::HashSet<&'static str>>,
260 warned: std::sync::atomic::AtomicBool,
261}
262
263impl LabelTable {
264 fn new() -> Self {
265 Self {
266 kept: Default::default(),
267 warned: Default::default(),
268 }
269 }
270
271 fn intern(&self, label: &str, cap: usize) -> Option<&'static str> {
275 use std::sync::atomic::Ordering;
276
277 if label.is_empty() {
278 return None;
279 }
280 {
281 let kept = self.kept.read().unwrap_or_else(|p| p.into_inner());
282 if let Some(found) = kept.get(label) {
283 return Some(*found);
284 }
285 }
286 let mut kept = self.kept.write().unwrap_or_else(|p| p.into_inner());
287 if let Some(found) = kept.get(label) {
288 return Some(*found);
289 }
290 if kept.len() >= cap {
291 if cfg!(debug_assertions) && !self.warned.swap(true, Ordering::Relaxed) {
292 eprintln!(
293 "compose-rust: more than {cap} distinct accessible names; \
294 names past the limit are left off. Use a fixed set of names."
295 );
296 }
297 return None;
298 }
299 let leaked: &'static str = Box::leak(label.to_owned().into_boxed_str());
300 kept.insert(leaked);
301 Some(leaked)
302 }
303
304 #[cfg(test)]
305 fn len(&self) -> usize {
306 self.kept.read().unwrap_or_else(|p| p.into_inner()).len()
307 }
308}
309
310fn keep_label(label: &str) -> Option<&'static str> {
311 use std::cell::Cell;
312 use std::sync::OnceLock;
313
314 static LABELS: OnceLock<LabelTable> = OnceLock::new();
315 thread_local! {
316 static LAST: Cell<Option<&'static str>> = const { Cell::new(None) };
319 }
320 if let Some(last) = LAST.with(Cell::get) {
321 if last == label {
322 return Some(last);
323 }
324 }
325 let kept = LABELS
326 .get_or_init(LabelTable::new)
327 .intern(label, MAX_LABELS);
328 if kept.is_some() {
329 LAST.with(|last| last.set(kept));
330 }
331 kept
332}
333
334#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
336pub enum Side {
337 Top,
338 End,
340 Bottom,
341 Start,
343}
344
345#[derive(Clone, Copy, Debug, Default, PartialEq)]
358pub struct Modifier {
359 observe_size: Option<i64>,
360 observe_size_every_dp: bool,
361 motion: Option<MotionRole>,
362 material: Option<MaterialRole>,
363 weight: Option<f32>,
364 width: Option<f32>,
365 height: Option<f32>,
366 padding: Option<f32>,
367 padding_role: Option<SpaceRole>,
368 background: Option<Paint>,
369 shape_role: Option<ShapeRole>,
370 corner_radius: Option<f32>,
371 corner_radii: Option<[f32; 4]>,
372 corner_curve: Option<f32>,
373 border_width: Option<f32>,
374 border_color: Option<Paint>,
375 border_each: Option<[(f32, Paint); 4]>,
376 elevation: Option<f32>,
377 alpha: Option<f32>,
378 press_feedback: Option<(f32, f32)>,
379 semantics_role: Option<SemanticsRole>,
380 semantics_label: Option<&'static str>,
381 semantics_state: SemanticsState,
382 transform: Option<[f32; 8]>,
383 animated_alpha: Option<crate::Animated<f32>>,
384 animated_background: Option<crate::Animated<Paint>>,
385 animated_offset: Option<crate::Animated<(f32, f32)>>,
386 cursor: Option<crate::Cursor>,
387 user_select: Option<crate::UserSelect>,
388 clip: bool,
389 window_drag: Option<bool>,
390 offset: Option<(f32, f32)>,
391 shadows: [Option<Shadow>; MAX_SHADOWS],
392 background_brushes: [Option<BackgroundBrush>; MAX_BACKGROUND_BRUSHES],
393 fill_max_width: bool,
394 match_parent_size: bool,
395 fill_max_height: bool,
396}
397
398#[allow(non_upper_case_globals)]
400pub const Modifier: Modifier = Modifier::new();
401
402impl Modifier {
403 pub const fn new() -> Self {
405 Self {
406 observe_size: None,
407 observe_size_every_dp: false,
408 motion: None,
409 material: None,
410 weight: None,
411 width: None,
412 height: None,
413 padding: None,
414 padding_role: None,
415 background: None,
416 shape_role: None,
417 corner_radius: None,
418 corner_radii: None,
419 corner_curve: None,
420 border_width: None,
421 border_color: None,
422 border_each: None,
423 elevation: None,
424 alpha: None,
425 press_feedback: None,
426 semantics_role: None,
427 semantics_label: None,
428 semantics_state: SemanticsState::NONE,
429 transform: None,
430 animated_alpha: None,
431 animated_background: None,
432 animated_offset: None,
433 cursor: None,
434 user_select: None,
435 clip: false,
436 window_drag: None,
437 offset: None,
438 shadows: [None; MAX_SHADOWS],
439 background_brushes: [None; MAX_BACKGROUND_BRUSHES],
440 fill_max_width: false,
441 match_parent_size: false,
442 fill_max_height: false,
443 }
444 }
445
446 pub const fn weight(mut self, weight: f32) -> Self {
448 self.weight = Some(weight);
449 self
450 }
451
452 pub const fn width(mut self, dp: f32) -> Self {
453 self.width = Some(dp);
454 self
455 }
456
457 pub const fn height(mut self, dp: f32) -> Self {
458 self.height = Some(dp);
459 self
460 }
461
462 pub const fn size(self, width: f32, height: f32) -> Self {
464 self.width(width).height(height)
465 }
466
467 pub const fn width_if(mut self, dp: Option<f32>) -> Self {
469 self.width = dp;
470 self
471 }
472
473 pub const fn match_parent_size(mut self) -> Self {
475 self.match_parent_size = true;
476 self
477 }
478
479 pub const fn fill_max_width(mut self) -> Self {
480 self.fill_max_width = true;
481 self
482 }
483
484 pub const fn fill_max_height(mut self) -> Self {
485 self.fill_max_height = true;
486 self
487 }
488
489 pub const fn fill_max_size(self) -> Self {
490 self.fill_max_width().fill_max_height()
491 }
492
493 pub const fn fill_max_width_if(mut self, fill: bool) -> Self {
495 self.fill_max_width = fill;
496 self
497 }
498
499 pub const fn padding(mut self, dp: f32) -> Self {
501 self.padding = Some(dp);
502 self
503 }
504
505 pub const fn padding_role(mut self, role: SpaceRole) -> Self {
507 self.padding_role = Some(role);
508 self
509 }
510
511 pub const fn padding_role_if(mut self, role: Option<SpaceRole>) -> Self {
513 self.padding_role = role;
514 self
515 }
516
517 pub const fn background(mut self, paint: Paint) -> Self {
518 self.background = Some(paint);
519 self
520 }
521
522 pub const fn background_if(mut self, paint: Option<Paint>) -> Self {
524 self.background = paint;
525 self
526 }
527
528 pub const fn shape_role(mut self, role: ShapeRole) -> Self {
530 self.shape_role = Some(role);
531 self
532 }
533
534 pub const fn corner_radius(mut self, dp: f32) -> Self {
536 self.corner_radius = Some(dp);
537 self.corner_radii = None;
538 self
539 }
540
541 pub const fn corner_radii(mut self, radii: [f32; 4]) -> Self {
544 self.corner_radii = Some(radii);
545 self.corner_radius = None;
546 self
547 }
548
549 pub const fn clip(mut self, clip: bool) -> Self {
551 self.clip = clip;
552 self
553 }
554
555 pub const fn window_drag(mut self, enabled: bool) -> Self {
557 self.window_drag = Some(enabled);
558 self
559 }
560
561 pub const fn border(mut self, width: f32, paint: Paint) -> Self {
564 self.border_width = Some(width);
565 self.border_color = Some(paint);
566 self.border_each = None;
567 self
568 }
569
570 pub const fn border_each(
577 mut self,
578 top: (f32, Paint),
579 right: (f32, Paint),
580 bottom: (f32, Paint),
581 left: (f32, Paint),
582 ) -> Self {
583 self.border_each = Some([top, right, bottom, left]);
584 self.border_width = None;
585 self.border_color = None;
586 self
587 }
588
589 pub const fn border_side(mut self, side: Side, width: f32, paint: Paint) -> Self {
598 let mut sides = match self.border_each {
599 Some(sides) => sides,
600 None => [(0.0, paint); 4],
601 };
602 let index = match side {
603 Side::Top => 0,
604 Side::End => 1,
605 Side::Bottom => 2,
606 Side::Start => 3,
607 };
608 sides[index] = (width, paint);
609 self.border_each = Some(sides);
610 self.border_width = None;
611 self.border_color = None;
612 self
613 }
614
615 pub const fn elevation(mut self, dp: f32) -> Self {
617 self.elevation = Some(dp);
618 self
619 }
620
621 pub const fn observe_size(mut self, token: i64) -> Self {
628 self.observe_size = Some(token);
629 self.observe_size_every_dp = false;
630 self
631 }
632
633 pub const fn observe_size_every_dp(mut self, token: i64) -> Self {
637 self.observe_size = Some(token);
638 self.observe_size_every_dp = true;
639 self
640 }
641
642 pub const fn motion(mut self, role: MotionRole) -> Self {
644 self.motion = Some(role);
645 self
646 }
647
648 pub const fn material(mut self, role: MaterialRole) -> Self {
650 self.material = Some(role);
651 self
652 }
653
654 pub const fn offset(mut self, x: f32, y: f32) -> Self {
656 self.offset = Some((x, y));
657 self
658 }
659
660 pub const fn shadow(mut self, shadow: Shadow) -> Self {
663 let mut index = 0;
664 while index < MAX_SHADOWS {
665 if self.shadows[index].is_none() {
666 self.shadows[index] = Some(shadow);
667 break;
668 }
669 index += 1;
670 }
671 self
672 }
673
674 pub fn shadows(mut self, shadows: impl IntoIterator<Item = Shadow>) -> Self {
676 for shadow in shadows {
677 self = self.shadow(shadow);
678 }
679 self
680 }
681
682 pub const fn background_brush(mut self, layer: BackgroundBrush) -> Self {
686 let mut index = 0;
687 while index < MAX_BACKGROUND_BRUSHES {
688 if self.background_brushes[index].is_none() {
689 self.background_brushes[index] = Some(layer);
690 break;
691 }
692 index += 1;
693 }
694 self
695 }
696
697 pub fn background_brushes(mut self, layers: impl IntoIterator<Item = BackgroundBrush>) -> Self {
700 for layer in layers {
701 self = self.background_brush(layer);
702 }
703 self
704 }
705
706 pub const fn alpha(mut self, alpha: f32) -> Self {
708 self.alpha = Some(alpha);
709 self
710 }
711
712 pub fn corner_curve(mut self, smoothing: f32) -> Self {
719 self.corner_curve = Some(crate::schema::corner_smoothing(smoothing));
720 self
721 }
722
723 pub const fn press_feedback(mut self, scale: f32, tint_alpha: f32) -> Self {
725 self.press_feedback = Some((scale, tint_alpha));
726 self
727 }
728
729 pub fn label(mut self, label: &str) -> Self {
736 self.semantics_label = keep_label(label);
737 self
738 }
739
740 pub const fn role(mut self, role: Role) -> Self {
743 self.semantics_role = Some(role);
744 self
745 }
746
747 pub const fn checked(mut self, checked: bool) -> Self {
749 self.semantics_state.checked = Some(checked);
750 self
751 }
752
753 pub const fn selected(mut self, selected: bool) -> Self {
755 self.semantics_state.selected = Some(selected);
756 self
757 }
758
759 pub const fn expanded(mut self, expanded: bool) -> Self {
761 self.semantics_state.expanded = Some(expanded);
762 self
763 }
764
765 pub const fn disabled(mut self, disabled: bool) -> Self {
767 self.semantics_state.disabled = Some(disabled);
768 self
769 }
770
771 pub const fn focused(mut self, focused: bool) -> Self {
773 self.semantics_state.focused = Some(focused);
774 self
775 }
776
777 pub const fn hidden(mut self, hidden: bool) -> Self {
779 self.semantics_state.hidden = hidden;
780 self
781 }
782
783 pub const fn click_action(mut self, exposed: bool) -> Self {
785 self.semantics_state.click_action = exposed;
786 self
787 }
788
789 pub const fn focus_action(mut self, exposed: bool) -> Self {
791 self.semantics_state.focus_action = exposed;
792 self
793 }
794
795 pub fn semantics(self, role: Role, label: &str, state: SemanticsState) -> Self {
797 let mut named = self.role(role).label(label);
798 named.semantics_state = state;
799 named
800 }
801
802 pub fn graphics_layer(mut self, layer: crate::GraphicsLayer) -> Self {
806 if let crate::schema::Modifier::Transform {
807 a,
808 b,
809 c,
810 d,
811 e,
812 f,
813 origin_x,
814 origin_y,
815 } = layer.transform()
816 {
817 self.transform = Some([a, b, c, d, e, f, origin_x, origin_y]);
818 }
819 self.alpha = Some(layer.alpha);
820 self
821 }
822
823 pub const fn animated_alpha(mut self, value: crate::Animated<f32>) -> Self {
826 self.animated_alpha = Some(value);
827 self
828 }
829
830 pub const fn animated_background(mut self, value: crate::Animated<Paint>) -> Self {
833 self.animated_background = Some(value);
834 self
835 }
836
837 pub const fn animated_offset(mut self, value: crate::Animated<(f32, f32)>) -> Self {
840 self.animated_offset = Some(value);
841 self
842 }
843
844 pub const fn cursor(mut self, cursor: crate::Cursor) -> Self {
848 self.cursor = Some(cursor);
849 self
850 }
851
852 pub const fn user_select(mut self, user_select: crate::UserSelect) -> Self {
855 self.user_select = Some(user_select);
856 self
857 }
858}
859
860#[derive(Clone, Copy, PartialEq, Eq)]
863pub(crate) enum Mods {
864 Container,
866 Standard,
868 Chip,
870 Scaffold,
872 None,
874}
875
876pub(crate) type Attrs = Vec<(&'static str, OwnedAttr)>;
877
878fn float(value: Option<f32>) -> OwnedAttr {
879 value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
880}
881
882fn role(value: Option<impl Into<u16>>) -> OwnedAttr {
883 value.map_or(OwnedAttr::None, |value| {
884 OwnedAttr::Int(i64::from(value.into()))
885 })
886}
887
888fn paint(value: Option<Paint>) -> OwnedAttr {
889 value.map_or(OwnedAttr::None, |paint| {
890 OwnedAttr::Int(paint.to_bits() as i64)
891 })
892}
893
894pub(crate) fn role_or_zero(value: Option<impl Into<u16>>) -> OwnedAttr {
896 OwnedAttr::Int(value.map_or(0, |value| i64::from(value.into())))
897}
898
899pub(crate) fn dp_or_zero(value: Option<f32>) -> OwnedAttr {
901 OwnedAttr::Float(f64::from(value.unwrap_or(0.0)))
902}
903
904pub(crate) fn paint_or_zero(value: Option<Paint>) -> OwnedAttr {
906 OwnedAttr::Int(value.map_or(0, |paint| paint.to_bits() as i64))
907}
908
909pub(crate) fn optional_text(value: Option<String>) -> OwnedAttr {
910 value.map_or(OwnedAttr::None, OwnedAttr::Text)
911}
912
913fn semantics_bits(modifier: &Modifier) -> Option<i64> {
916 let state = modifier.semantics_state;
917 if modifier.semantics_role.is_none() && state == SemanticsState::NONE {
918 return None;
919 }
920 let role = i64::from(u16::from(
921 modifier.semantics_role.unwrap_or(SemanticsRole::None),
922 ));
923 Some(role | (i64::from(state.bits()) << 16))
924}
925
926pub(crate) fn push_modifiers(modifier: &Modifier, mods: Mods, out: &mut Attrs) {
928 let container = mods == Mods::Container;
929 let standard = container || mods == Mods::Standard;
930 let chip = standard || mods == Mods::Chip;
931 let scaffold = mods == Mods::Scaffold;
932 let mut push = |always: bool, name: &'static str, value: OwnedAttr| {
933 let set = !matches!(value, OwnedAttr::None | OwnedAttr::Bool(false));
934 if always || set {
935 out.push((name, value));
936 }
937 };
938 let (plain, every_dp) = match modifier.observe_size {
941 Some(token) if modifier.observe_size_every_dp => (OwnedAttr::None, OwnedAttr::Int(token)),
942 Some(token) => (OwnedAttr::Int(token), OwnedAttr::None),
943 None => (OwnedAttr::None, OwnedAttr::None),
944 };
945 push(container, "observe_size", plain);
946 push(container, "observe_size_every_dp", every_dp);
947 push(container, "motion", role(modifier.motion));
948 push(container || scaffold, "material", role(modifier.material));
949 push(chip, "weight", float(modifier.weight));
950 push(chip, "width", float(modifier.width));
951 push(chip, "height", float(modifier.height));
952 push(chip, "padding", float(modifier.padding));
953 push(chip, "padding_role", role(modifier.padding_role));
954 push(
956 standard || scaffold,
957 "background",
958 paint(if modifier.animated_background.is_some() {
959 None
960 } else {
961 modifier.background
962 }),
963 );
964 push(standard, "shape_role", role(modifier.shape_role));
965 push(standard, "corner_radius", float(modifier.corner_radius));
966 push(
967 false,
968 "corner_radii",
969 modifier.corner_radii.map_or(OwnedAttr::None, |values| {
970 OwnedAttr::Bytes(
971 values
972 .iter()
973 .flat_map(|value| value.to_le_bytes())
974 .collect(),
975 )
976 }),
977 );
978 push(false, "corner_curve", float(modifier.corner_curve));
979 push(false, "clip", OwnedAttr::Bool(modifier.clip));
980 push(
981 true,
982 "window_drag",
983 modifier
984 .window_drag
985 .map_or(OwnedAttr::None, OwnedAttr::Bool),
986 );
987 push(standard, "border_width", float(modifier.border_width));
988 push(standard, "border_color", paint(modifier.border_color));
989 push(
993 false,
994 "border_each",
995 modifier.border_each.map_or(OwnedAttr::None, |sides| {
996 let mut bytes = Vec::with_capacity(48);
997 for (width, _) in sides {
998 bytes.extend_from_slice(&width.to_le_bytes());
999 }
1000 for (_, paint) in sides {
1001 bytes.extend_from_slice(&paint.to_bits().to_le_bytes());
1002 }
1003 OwnedAttr::Bytes(bytes)
1004 }),
1005 );
1006 push(standard, "elevation", float(modifier.elevation));
1007 push(
1008 false,
1009 "match_parent_size",
1010 OwnedAttr::Bool(modifier.match_parent_size),
1011 );
1012 push(
1013 chip,
1014 "fill_max_width",
1015 OwnedAttr::Bool(modifier.fill_max_width),
1016 );
1017 push(
1018 standard,
1019 "fill_max_height",
1020 OwnedAttr::Bool(modifier.fill_max_height),
1021 );
1022 push(
1025 false,
1026 "press_feedback",
1027 modifier
1028 .press_feedback
1029 .map_or(OwnedAttr::None, |(scale, alpha)| {
1030 OwnedAttr::Bytes([scale.to_le_bytes(), alpha.to_le_bytes()].concat())
1031 }),
1032 );
1033 push(
1036 false,
1037 "semantics",
1038 semantics_bits(modifier).map_or(OwnedAttr::None, OwnedAttr::Int),
1039 );
1040 push(
1041 false,
1042 "accessible_name",
1043 modifier
1044 .semantics_label
1045 .map_or(OwnedAttr::None, OwnedAttr::Label),
1046 );
1047 push(
1048 false,
1049 "alpha",
1050 float(if modifier.animated_alpha.is_some() {
1051 None
1052 } else {
1053 modifier.alpha
1054 }),
1055 );
1056 push(
1057 false,
1058 "transform",
1059 match (modifier.animated_offset, modifier.transform) {
1060 (None, Some(values)) => OwnedAttr::Bytes(
1061 values
1062 .iter()
1063 .flat_map(|value| value.to_le_bytes())
1064 .collect(),
1065 ),
1066 _ => OwnedAttr::None,
1067 },
1068 );
1069 push(
1071 false,
1072 "cursor",
1073 match modifier.cursor {
1074 Some(crate::Cursor::Auto) | None => OwnedAttr::None,
1075 Some(cursor) => OwnedAttr::Int(i64::from(u16::from(cursor))),
1076 },
1077 );
1078 push(
1079 false,
1080 "user_select",
1081 match modifier.user_select {
1082 Some(crate::UserSelect::Auto) | None => OwnedAttr::None,
1083 Some(select) => OwnedAttr::Int(i64::from(u16::from(select))),
1084 },
1085 );
1086 push(
1088 false,
1089 "offset",
1090 modifier.offset.map_or(OwnedAttr::None, |(x, y)| {
1091 let mut bytes = Vec::with_capacity(8);
1092 bytes.extend_from_slice(&x.to_le_bytes());
1093 bytes.extend_from_slice(&y.to_le_bytes());
1094 OwnedAttr::Bytes(bytes)
1095 }),
1096 );
1097 push(
1098 false,
1099 "shadows",
1100 if modifier.shadows[0].is_none() {
1101 OwnedAttr::None
1102 } else {
1103 let mut bytes = Vec::new();
1104 for shadow in modifier.shadows.iter().flatten() {
1105 for value in [shadow.x, shadow.y, shadow.blur, shadow.spread] {
1106 bytes.extend_from_slice(&value.to_le_bytes());
1107 }
1108 bytes.extend_from_slice(&shadow.paint.to_bits().to_le_bytes());
1109 }
1110 OwnedAttr::Bytes(bytes)
1111 },
1112 );
1113 push(
1114 false,
1115 "background_brushes",
1116 if modifier.background_brushes[0].is_none() {
1117 OwnedAttr::None
1118 } else {
1119 let mut bytes = Vec::new();
1120 for layer in modifier.background_brushes.iter().flatten() {
1121 for value in [layer.x, layer.y, layer.width, layer.height] {
1122 bytes.extend_from_slice(&value.to_le_bytes());
1123 }
1124 bytes.extend_from_slice(&layer.paint.to_bits().to_le_bytes());
1125 bytes.extend_from_slice(&u32::from(u16::from(layer.repeat)).to_le_bytes());
1126 bytes.extend_from_slice(&layer.tile_width.to_le_bytes());
1127 bytes.extend_from_slice(&layer.tile_height.to_le_bytes());
1128 bytes.extend_from_slice(&[0; 4]);
1131 }
1132 OwnedAttr::Bytes(bytes)
1133 },
1134 );
1135 push(
1136 false,
1137 "animated_alpha",
1138 modifier.animated_alpha.map_or(OwnedAttr::None, |value| {
1139 OwnedAttr::Bytes(crate::animation::animated_bytes(
1140 &[value.target.to_bits()],
1141 &value.spec,
1142 ))
1143 }),
1144 );
1145 push(
1146 false,
1147 "animated_background",
1148 modifier
1149 .animated_background
1150 .map_or(OwnedAttr::None, |value| {
1151 let bits = value.target.to_bits();
1152 OwnedAttr::Bytes(crate::animation::animated_bytes(
1153 &[bits as u32, (bits >> 32) as u32],
1154 &value.spec,
1155 ))
1156 }),
1157 );
1158 push(
1159 false,
1160 "animated_offset",
1161 modifier.animated_offset.map_or(OwnedAttr::None, |value| {
1162 OwnedAttr::Bytes(crate::animation::animated_bytes(
1163 &[value.target.0.to_bits(), value.target.1.to_bits()],
1164 &value.spec,
1165 ))
1166 }),
1167 );
1168}
1169
1170pub(crate) fn emit(
1176 key: u64,
1177 widget: WidgetKind,
1178 fill: impl FnOnce(&mut Attrs),
1179 listeners: Vec<(&'static str, EventCallback)>,
1180 content: impl FnOnce(),
1181) {
1182 let _group = group_guard(key);
1183 emit_node(widget, fill, listeners, content);
1184}
1185
1186pub(crate) fn emit_node(
1189 widget: WidgetKind,
1190 fill: impl FnOnce(&mut Attrs),
1191 listeners: Vec<(&'static str, EventCallback)>,
1192 content: impl FnOnce(),
1193) {
1194 let node = with_composer_if_idle(|composer| {
1195 if !composer.composing() {
1196 return None;
1197 }
1198 let mut attrs = composer.take_attrs();
1199 fill(&mut attrs);
1200 Some(composer.update_node(widget, attrs, listeners))
1201 })
1202 .flatten();
1203 let Some(node) = node else {
1204 return;
1205 };
1206 with_composer(|composer| composer.push_node_context(node));
1207 let _context = NodeContextGuard;
1208 content();
1209}
1210
1211struct NodeContextGuard;
1213
1214impl Drop for NodeContextGuard {
1215 fn drop(&mut self) {
1216 with_composer_if_idle(|composer| composer.pop_node_context());
1217 }
1218}
1219
1220pub(crate) const fn widget_key(name: &str) -> u64 {
1222 crate::runtime::composer::call_site(name, 0)
1223}
1224
1225#[cfg(test)]
1226mod fr19_9_tests {
1227 use super::*;
1228
1229 #[test]
1230 fn fr19_9_window_drag_builder_preserves_false() {
1231 for enabled in [false, true] {
1232 let mut attrs = Vec::new();
1233 push_modifiers(
1234 &Modifier::new().window_drag(enabled),
1235 Mods::Standard,
1236 &mut attrs,
1237 );
1238 assert!(attrs.iter().any(|(name, value)| *name == "window_drag"
1239 && matches!(value, OwnedAttr::Bool(value) if *value == enabled)));
1240 }
1241 }
1242}
1243
1244#[cfg(test)]
1245mod fr68_label_tests {
1246 use super::*;
1247
1248 #[test]
1249 fn fr68_repeated_labels_do_not_grow_the_table() {
1250 let table = LabelTable::new();
1251 let first = table.intern("Refresh", MAX_LABELS).unwrap();
1252 for _ in 0..10_000 {
1253 let again = table.intern("Refresh", MAX_LABELS).unwrap();
1254 assert!(std::ptr::eq(first, again));
1255 }
1256 assert_eq!(table.len(), 1);
1257 }
1258
1259 #[test]
1260 fn fr68_labels_past_the_cap_are_dropped() {
1261 let table = LabelTable::new();
1262 for n in 0..MAX_LABELS {
1263 assert!(table.intern(&format!("label {n}"), MAX_LABELS).is_some());
1264 }
1265 assert_eq!(table.len(), MAX_LABELS);
1266 assert_eq!(table.intern("one too many", MAX_LABELS), None);
1267 assert_eq!(table.len(), MAX_LABELS);
1268 assert!(table.intern("label 0", MAX_LABELS).is_some());
1270 }
1271}