1mod lazy;
30mod widgets;
31
32pub use crate::boundary::LaunchBuilder;
33pub use crate::brush::{Brush, Stop, brush};
34pub use crate::drawing::{DrawCommand, DrawList, DrawListBuilder};
35pub use crate::message::{Message, show_message};
36pub use crate::runtime::{application, launch};
37pub use crate::schema::{
38 Alignment, Arrangement, ButtonKind, ButtonVariant, Color, ColorRole, ColorScheme, DesignSystem,
39 IconRole, Key, MaterialRole, MessageDuration, MotionRole, Paint, ShapeRole, SpaceRole,
40 TextAlign, TextOverflow, Theme, TypeRole, WindowHeightClass, WindowSizeClass,
41};
42pub use crate::spans::TextSpans;
43pub use crate::theme::{ThemeHandle, current_theme};
44
45pub fn theme() -> ThemeHandle {
48 ThemeHandle::default()
49}
50pub use crate::window::{NodeSize, WindowSafeArea, WindowSize};
51pub use crate::{
52 Cursor, FileDrop, KeyEvent, PointerEvent, PointerKind, PointerKinds, RangeRequest, UserSelect,
53 asset, pointer_button,
54};
55pub use lazy::{LazyColumn, LazyGrid, LazyList, LazyListScope, LazyRow};
56pub use widgets::*;
57
58use crate::runtime::composer::{EventCallback, OwnedAttr};
59use crate::runtime::{group_guard, with_composer, with_composer_if_idle};
60use crate::schema::WidgetKind;
61
62pub const MAX_SHADOWS: usize = 4;
64
65#[derive(Clone, Copy, Debug, PartialEq)]
68pub struct Shadow {
69 pub x: f32,
70 pub y: f32,
71 pub blur: f32,
72 pub spread: f32,
73 pub paint: Paint,
74}
75
76impl Shadow {
77 pub const fn new(x: f32, y: f32, blur: f32, spread: f32, paint: Paint) -> Self {
78 Self {
79 x,
80 y,
81 blur,
82 spread,
83 paint,
84 }
85 }
86}
87
88#[derive(Clone, Copy, Debug, Default, PartialEq)]
101pub struct Modifier {
102 observe_size: Option<i64>,
103 motion: Option<MotionRole>,
104 material: Option<MaterialRole>,
105 weight: Option<f32>,
106 width: Option<f32>,
107 height: Option<f32>,
108 padding: Option<f32>,
109 padding_role: Option<SpaceRole>,
110 background: Option<Paint>,
111 shape_role: Option<ShapeRole>,
112 corner_radius: Option<f32>,
113 corner_radii: Option<[f32; 4]>,
114 border_width: Option<f32>,
115 border_color: Option<Paint>,
116 elevation: Option<f32>,
117 alpha: Option<f32>,
118 press_feedback: Option<(f32, f32)>,
119 transform: Option<[f32; 8]>,
120 animated_alpha: Option<crate::Animated<f32>>,
121 animated_background: Option<crate::Animated<Paint>>,
122 animated_offset: Option<crate::Animated<(f32, f32)>>,
123 cursor: Option<crate::Cursor>,
124 user_select: Option<crate::UserSelect>,
125 clip: bool,
126 window_drag: Option<bool>,
127 offset: Option<(f32, f32)>,
128 shadows: [Option<Shadow>; MAX_SHADOWS],
129 fill_max_width: bool,
130 match_parent_size: bool,
131 fill_max_height: bool,
132}
133
134#[allow(non_upper_case_globals)]
136pub const Modifier: Modifier = Modifier::new();
137
138impl Modifier {
139 pub const fn new() -> Self {
141 Self {
142 observe_size: None,
143 motion: None,
144 material: None,
145 weight: None,
146 width: None,
147 height: None,
148 padding: None,
149 padding_role: None,
150 background: None,
151 shape_role: None,
152 corner_radius: None,
153 corner_radii: None,
154 border_width: None,
155 border_color: None,
156 elevation: None,
157 alpha: None,
158 press_feedback: None,
159 transform: None,
160 animated_alpha: None,
161 animated_background: None,
162 animated_offset: None,
163 cursor: None,
164 user_select: None,
165 clip: false,
166 window_drag: None,
167 offset: None,
168 shadows: [None; MAX_SHADOWS],
169 fill_max_width: false,
170 match_parent_size: false,
171 fill_max_height: false,
172 }
173 }
174
175 pub const fn weight(mut self, weight: f32) -> Self {
177 self.weight = Some(weight);
178 self
179 }
180
181 pub const fn width(mut self, dp: f32) -> Self {
182 self.width = Some(dp);
183 self
184 }
185
186 pub const fn height(mut self, dp: f32) -> Self {
187 self.height = Some(dp);
188 self
189 }
190
191 pub const fn size(self, width: f32, height: f32) -> Self {
193 self.width(width).height(height)
194 }
195
196 pub const fn width_if(mut self, dp: Option<f32>) -> Self {
198 self.width = dp;
199 self
200 }
201
202 pub const fn match_parent_size(mut self) -> Self {
204 self.match_parent_size = true;
205 self
206 }
207
208 pub const fn fill_max_width(mut self) -> Self {
209 self.fill_max_width = true;
210 self
211 }
212
213 pub const fn fill_max_height(mut self) -> Self {
214 self.fill_max_height = true;
215 self
216 }
217
218 pub const fn fill_max_size(self) -> Self {
219 self.fill_max_width().fill_max_height()
220 }
221
222 pub const fn fill_max_width_if(mut self, fill: bool) -> Self {
224 self.fill_max_width = fill;
225 self
226 }
227
228 pub const fn padding(mut self, dp: f32) -> Self {
230 self.padding = Some(dp);
231 self
232 }
233
234 pub const fn padding_role(mut self, role: SpaceRole) -> Self {
236 self.padding_role = Some(role);
237 self
238 }
239
240 pub const fn padding_role_if(mut self, role: Option<SpaceRole>) -> Self {
242 self.padding_role = role;
243 self
244 }
245
246 pub const fn background(mut self, paint: Paint) -> Self {
247 self.background = Some(paint);
248 self
249 }
250
251 pub const fn background_if(mut self, paint: Option<Paint>) -> Self {
253 self.background = paint;
254 self
255 }
256
257 pub const fn shape_role(mut self, role: ShapeRole) -> Self {
259 self.shape_role = Some(role);
260 self
261 }
262
263 pub const fn corner_radius(mut self, dp: f32) -> Self {
265 self.corner_radius = Some(dp);
266 self.corner_radii = None;
267 self
268 }
269
270 pub const fn corner_radii(mut self, radii: [f32; 4]) -> Self {
273 self.corner_radii = Some(radii);
274 self.corner_radius = None;
275 self
276 }
277
278 pub const fn clip(mut self, clip: bool) -> Self {
280 self.clip = clip;
281 self
282 }
283
284 pub const fn window_drag(mut self, enabled: bool) -> Self {
286 self.window_drag = Some(enabled);
287 self
288 }
289
290 pub const fn border(mut self, width: f32, paint: Paint) -> Self {
291 self.border_width = Some(width);
292 self.border_color = Some(paint);
293 self
294 }
295
296 pub const fn elevation(mut self, dp: f32) -> Self {
298 self.elevation = Some(dp);
299 self
300 }
301
302 pub const fn observe_size(mut self, token: i64) -> Self {
305 self.observe_size = Some(token);
306 self
307 }
308
309 pub const fn motion(mut self, role: MotionRole) -> Self {
311 self.motion = Some(role);
312 self
313 }
314
315 pub const fn material(mut self, role: MaterialRole) -> Self {
317 self.material = Some(role);
318 self
319 }
320
321 pub const fn offset(mut self, x: f32, y: f32) -> Self {
323 self.offset = Some((x, y));
324 self
325 }
326
327 pub const fn shadow(mut self, shadow: Shadow) -> Self {
330 let mut index = 0;
331 while index < MAX_SHADOWS {
332 if self.shadows[index].is_none() {
333 self.shadows[index] = Some(shadow);
334 break;
335 }
336 index += 1;
337 }
338 self
339 }
340
341 pub fn shadows(mut self, shadows: impl IntoIterator<Item = Shadow>) -> Self {
343 for shadow in shadows {
344 self = self.shadow(shadow);
345 }
346 self
347 }
348
349 pub const fn alpha(mut self, alpha: f32) -> Self {
351 self.alpha = Some(alpha);
352 self
353 }
354
355 pub const fn press_feedback(mut self, scale: f32, tint_alpha: f32) -> Self {
357 self.press_feedback = Some((scale, tint_alpha));
358 self
359 }
360
361 pub fn graphics_layer(mut self, layer: crate::GraphicsLayer) -> Self {
365 if let crate::schema::Modifier::Transform {
366 a,
367 b,
368 c,
369 d,
370 e,
371 f,
372 origin_x,
373 origin_y,
374 } = layer.transform()
375 {
376 self.transform = Some([a, b, c, d, e, f, origin_x, origin_y]);
377 }
378 self.alpha = Some(layer.alpha);
379 self
380 }
381
382 pub const fn animated_alpha(mut self, value: crate::Animated<f32>) -> Self {
385 self.animated_alpha = Some(value);
386 self
387 }
388
389 pub const fn animated_background(mut self, value: crate::Animated<Paint>) -> Self {
392 self.animated_background = Some(value);
393 self
394 }
395
396 pub const fn animated_offset(mut self, value: crate::Animated<(f32, f32)>) -> Self {
399 self.animated_offset = Some(value);
400 self
401 }
402
403 pub const fn cursor(mut self, cursor: crate::Cursor) -> Self {
407 self.cursor = Some(cursor);
408 self
409 }
410
411 pub const fn user_select(mut self, user_select: crate::UserSelect) -> Self {
414 self.user_select = Some(user_select);
415 self
416 }
417}
418
419#[derive(Clone, Copy, PartialEq, Eq)]
422pub(crate) enum Mods {
423 Container,
425 Standard,
427 Chip,
429 Scaffold,
431 None,
433}
434
435pub(crate) type Attrs = Vec<(&'static str, OwnedAttr)>;
436
437fn float(value: Option<f32>) -> OwnedAttr {
438 value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
439}
440
441fn role(value: Option<impl Into<u16>>) -> OwnedAttr {
442 value.map_or(OwnedAttr::None, |value| {
443 OwnedAttr::Int(i64::from(value.into()))
444 })
445}
446
447fn paint(value: Option<Paint>) -> OwnedAttr {
448 value.map_or(OwnedAttr::None, |paint| {
449 OwnedAttr::Int(paint.to_bits() as i64)
450 })
451}
452
453pub(crate) fn role_or_zero(value: Option<impl Into<u16>>) -> OwnedAttr {
455 OwnedAttr::Int(value.map_or(0, |value| i64::from(value.into())))
456}
457
458pub(crate) fn dp_or_zero(value: Option<f32>) -> OwnedAttr {
460 OwnedAttr::Float(f64::from(value.unwrap_or(0.0)))
461}
462
463pub(crate) fn paint_or_zero(value: Option<Paint>) -> OwnedAttr {
465 OwnedAttr::Int(value.map_or(0, |paint| paint.to_bits() as i64))
466}
467
468pub(crate) fn optional_text(value: Option<String>) -> OwnedAttr {
469 value.map_or(OwnedAttr::None, OwnedAttr::Text)
470}
471
472pub(crate) fn push_modifiers(modifier: &Modifier, mods: Mods, out: &mut Attrs) {
474 let container = mods == Mods::Container;
475 let standard = container || mods == Mods::Standard;
476 let chip = standard || mods == Mods::Chip;
477 let scaffold = mods == Mods::Scaffold;
478 let mut push = |always: bool, name: &'static str, value: OwnedAttr| {
479 let set = !matches!(value, OwnedAttr::None | OwnedAttr::Bool(false));
480 if always || set {
481 out.push((name, value));
482 }
483 };
484 push(
485 container,
486 "observe_size",
487 modifier
488 .observe_size
489 .map_or(OwnedAttr::None, OwnedAttr::Int),
490 );
491 push(container, "motion", role(modifier.motion));
492 push(container || scaffold, "material", role(modifier.material));
493 push(chip, "weight", float(modifier.weight));
494 push(chip, "width", float(modifier.width));
495 push(chip, "height", float(modifier.height));
496 push(chip, "padding", float(modifier.padding));
497 push(chip, "padding_role", role(modifier.padding_role));
498 push(
500 standard || scaffold,
501 "background",
502 paint(if modifier.animated_background.is_some() {
503 None
504 } else {
505 modifier.background
506 }),
507 );
508 push(standard, "shape_role", role(modifier.shape_role));
509 push(standard, "corner_radius", float(modifier.corner_radius));
510 push(
511 false,
512 "corner_radii",
513 modifier.corner_radii.map_or(OwnedAttr::None, |values| {
514 OwnedAttr::Bytes(
515 values
516 .iter()
517 .flat_map(|value| value.to_le_bytes())
518 .collect(),
519 )
520 }),
521 );
522 push(false, "clip", OwnedAttr::Bool(modifier.clip));
523 push(
524 true,
525 "window_drag",
526 modifier
527 .window_drag
528 .map_or(OwnedAttr::None, OwnedAttr::Bool),
529 );
530 push(standard, "border_width", float(modifier.border_width));
531 push(standard, "border_color", paint(modifier.border_color));
532 push(standard, "elevation", float(modifier.elevation));
533 push(
534 false,
535 "match_parent_size",
536 OwnedAttr::Bool(modifier.match_parent_size),
537 );
538 push(
539 chip,
540 "fill_max_width",
541 OwnedAttr::Bool(modifier.fill_max_width),
542 );
543 push(
544 standard,
545 "fill_max_height",
546 OwnedAttr::Bool(modifier.fill_max_height),
547 );
548 push(
551 false,
552 "press_feedback",
553 modifier
554 .press_feedback
555 .map_or(OwnedAttr::None, |(scale, alpha)| {
556 OwnedAttr::Bytes([scale.to_le_bytes(), alpha.to_le_bytes()].concat())
557 }),
558 );
559 push(
560 false,
561 "alpha",
562 float(if modifier.animated_alpha.is_some() {
563 None
564 } else {
565 modifier.alpha
566 }),
567 );
568 push(
569 false,
570 "transform",
571 match (modifier.animated_offset, modifier.transform) {
572 (None, Some(values)) => OwnedAttr::Bytes(
573 values
574 .iter()
575 .flat_map(|value| value.to_le_bytes())
576 .collect(),
577 ),
578 _ => OwnedAttr::None,
579 },
580 );
581 push(
583 false,
584 "cursor",
585 match modifier.cursor {
586 Some(crate::Cursor::Auto) | None => OwnedAttr::None,
587 Some(cursor) => OwnedAttr::Int(i64::from(u16::from(cursor))),
588 },
589 );
590 push(
591 false,
592 "user_select",
593 match modifier.user_select {
594 Some(crate::UserSelect::Auto) | None => OwnedAttr::None,
595 Some(select) => OwnedAttr::Int(i64::from(u16::from(select))),
596 },
597 );
598 push(
600 false,
601 "offset",
602 modifier.offset.map_or(OwnedAttr::None, |(x, y)| {
603 let mut bytes = Vec::with_capacity(8);
604 bytes.extend_from_slice(&x.to_le_bytes());
605 bytes.extend_from_slice(&y.to_le_bytes());
606 OwnedAttr::Bytes(bytes)
607 }),
608 );
609 push(
610 false,
611 "shadows",
612 if modifier.shadows[0].is_none() {
613 OwnedAttr::None
614 } else {
615 let mut bytes = Vec::new();
616 for shadow in modifier.shadows.iter().flatten() {
617 for value in [shadow.x, shadow.y, shadow.blur, shadow.spread] {
618 bytes.extend_from_slice(&value.to_le_bytes());
619 }
620 bytes.extend_from_slice(&shadow.paint.to_bits().to_le_bytes());
621 }
622 OwnedAttr::Bytes(bytes)
623 },
624 );
625 push(
626 false,
627 "animated_alpha",
628 modifier.animated_alpha.map_or(OwnedAttr::None, |value| {
629 OwnedAttr::Bytes(crate::animation::animated_bytes(
630 &[value.target.to_bits()],
631 &value.spec,
632 ))
633 }),
634 );
635 push(
636 false,
637 "animated_background",
638 modifier
639 .animated_background
640 .map_or(OwnedAttr::None, |value| {
641 let bits = value.target.to_bits();
642 OwnedAttr::Bytes(crate::animation::animated_bytes(
643 &[bits as u32, (bits >> 32) as u32],
644 &value.spec,
645 ))
646 }),
647 );
648 push(
649 false,
650 "animated_offset",
651 modifier.animated_offset.map_or(OwnedAttr::None, |value| {
652 OwnedAttr::Bytes(crate::animation::animated_bytes(
653 &[value.target.0.to_bits(), value.target.1.to_bits()],
654 &value.spec,
655 ))
656 }),
657 );
658}
659
660pub(crate) fn emit(
666 key: u64,
667 widget: WidgetKind,
668 fill: impl FnOnce(&mut Attrs),
669 listeners: Vec<(&'static str, EventCallback)>,
670 content: impl FnOnce(),
671) {
672 let _group = group_guard(key);
673 emit_node(widget, fill, listeners, content);
674}
675
676pub(crate) fn emit_node(
679 widget: WidgetKind,
680 fill: impl FnOnce(&mut Attrs),
681 listeners: Vec<(&'static str, EventCallback)>,
682 content: impl FnOnce(),
683) {
684 let node = with_composer_if_idle(|composer| {
685 if !composer.composing() {
686 return None;
687 }
688 let mut attrs = composer.take_attrs();
689 fill(&mut attrs);
690 Some(composer.update_node(widget, attrs, listeners))
691 })
692 .flatten();
693 let Some(node) = node else {
694 return;
695 };
696 with_composer(|composer| composer.push_node_context(node));
697 let _context = NodeContextGuard;
698 content();
699}
700
701struct NodeContextGuard;
703
704impl Drop for NodeContextGuard {
705 fn drop(&mut self) {
706 with_composer_if_idle(|composer| composer.pop_node_context());
707 }
708}
709
710pub(crate) const fn widget_key(name: &str) -> u64 {
712 crate::runtime::composer::call_site(name, 0)
713}
714
715#[cfg(test)]
716mod fr19_9_tests {
717 use super::*;
718
719 #[test]
720 fn fr19_9_window_drag_builder_preserves_false() {
721 for enabled in [false, true] {
722 let mut attrs = Vec::new();
723 push_modifiers(
724 &Modifier::new().window_drag(enabled),
725 Mods::Standard,
726 &mut attrs,
727 );
728 assert!(attrs.iter().any(|(name, value)| *name == "window_drag"
729 && matches!(value, OwnedAttr::Bool(value) if *value == enabled)));
730 }
731 }
732}