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, 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 transform: Option<[f32; 8]>,
119 animated_alpha: Option<crate::Animated<f32>>,
120 animated_background: Option<crate::Animated<Paint>>,
121 animated_offset: Option<crate::Animated<(f32, f32)>>,
122 cursor: Option<crate::Cursor>,
123 user_select: Option<crate::UserSelect>,
124 clip: bool,
125 offset: Option<(f32, f32)>,
126 shadows: [Option<Shadow>; MAX_SHADOWS],
127 fill_max_width: bool,
128 fill_max_height: bool,
129}
130
131#[allow(non_upper_case_globals)]
133pub const Modifier: Modifier = Modifier::new();
134
135impl Modifier {
136 pub const fn new() -> Self {
138 Self {
139 observe_size: None,
140 motion: None,
141 material: None,
142 weight: None,
143 width: None,
144 height: None,
145 padding: None,
146 padding_role: None,
147 background: None,
148 shape_role: None,
149 corner_radius: None,
150 corner_radii: None,
151 border_width: None,
152 border_color: None,
153 elevation: None,
154 alpha: None,
155 transform: None,
156 animated_alpha: None,
157 animated_background: None,
158 animated_offset: None,
159 cursor: None,
160 user_select: None,
161 clip: false,
162 offset: None,
163 shadows: [None; MAX_SHADOWS],
164 fill_max_width: false,
165 fill_max_height: false,
166 }
167 }
168
169 pub const fn weight(mut self, weight: f32) -> Self {
171 self.weight = Some(weight);
172 self
173 }
174
175 pub const fn width(mut self, dp: f32) -> Self {
176 self.width = Some(dp);
177 self
178 }
179
180 pub const fn height(mut self, dp: f32) -> Self {
181 self.height = Some(dp);
182 self
183 }
184
185 pub const fn size(self, width: f32, height: f32) -> Self {
187 self.width(width).height(height)
188 }
189
190 pub const fn width_if(mut self, dp: Option<f32>) -> Self {
192 self.width = dp;
193 self
194 }
195
196 pub const fn fill_max_width(mut self) -> Self {
197 self.fill_max_width = true;
198 self
199 }
200
201 pub const fn fill_max_height(mut self) -> Self {
202 self.fill_max_height = true;
203 self
204 }
205
206 pub const fn fill_max_size(self) -> Self {
207 self.fill_max_width().fill_max_height()
208 }
209
210 pub const fn fill_max_width_if(mut self, fill: bool) -> Self {
212 self.fill_max_width = fill;
213 self
214 }
215
216 pub const fn padding(mut self, dp: f32) -> Self {
218 self.padding = Some(dp);
219 self
220 }
221
222 pub const fn padding_role(mut self, role: SpaceRole) -> Self {
224 self.padding_role = Some(role);
225 self
226 }
227
228 pub const fn padding_role_if(mut self, role: Option<SpaceRole>) -> Self {
230 self.padding_role = role;
231 self
232 }
233
234 pub const fn background(mut self, paint: Paint) -> Self {
235 self.background = Some(paint);
236 self
237 }
238
239 pub const fn background_if(mut self, paint: Option<Paint>) -> Self {
241 self.background = paint;
242 self
243 }
244
245 pub const fn shape_role(mut self, role: ShapeRole) -> Self {
247 self.shape_role = Some(role);
248 self
249 }
250
251 pub const fn corner_radius(mut self, dp: f32) -> Self {
253 self.corner_radius = Some(dp);
254 self.corner_radii = None;
255 self
256 }
257
258 pub const fn corner_radii(mut self, radii: [f32; 4]) -> Self {
261 self.corner_radii = Some(radii);
262 self.corner_radius = None;
263 self
264 }
265
266 pub const fn clip(mut self, clip: bool) -> Self {
268 self.clip = clip;
269 self
270 }
271
272 pub const fn border(mut self, width: f32, paint: Paint) -> Self {
273 self.border_width = Some(width);
274 self.border_color = Some(paint);
275 self
276 }
277
278 pub const fn elevation(mut self, dp: f32) -> Self {
280 self.elevation = Some(dp);
281 self
282 }
283
284 pub const fn observe_size(mut self, token: i64) -> Self {
287 self.observe_size = Some(token);
288 self
289 }
290
291 pub const fn motion(mut self, role: MotionRole) -> Self {
293 self.motion = Some(role);
294 self
295 }
296
297 pub const fn material(mut self, role: MaterialRole) -> Self {
299 self.material = Some(role);
300 self
301 }
302
303 pub const fn offset(mut self, x: f32, y: f32) -> Self {
305 self.offset = Some((x, y));
306 self
307 }
308
309 pub const fn shadow(mut self, shadow: Shadow) -> Self {
312 let mut index = 0;
313 while index < MAX_SHADOWS {
314 if self.shadows[index].is_none() {
315 self.shadows[index] = Some(shadow);
316 break;
317 }
318 index += 1;
319 }
320 self
321 }
322
323 pub fn shadows(mut self, shadows: impl IntoIterator<Item = Shadow>) -> Self {
325 for shadow in shadows {
326 self = self.shadow(shadow);
327 }
328 self
329 }
330
331 pub const fn alpha(mut self, alpha: f32) -> Self {
333 self.alpha = Some(alpha);
334 self
335 }
336
337 pub fn graphics_layer(mut self, layer: crate::GraphicsLayer) -> Self {
341 if let crate::schema::Modifier::Transform {
342 a,
343 b,
344 c,
345 d,
346 e,
347 f,
348 origin_x,
349 origin_y,
350 } = layer.transform()
351 {
352 self.transform = Some([a, b, c, d, e, f, origin_x, origin_y]);
353 }
354 self.alpha = Some(layer.alpha);
355 self
356 }
357
358 pub const fn animated_alpha(mut self, value: crate::Animated<f32>) -> Self {
361 self.animated_alpha = Some(value);
362 self
363 }
364
365 pub const fn animated_background(mut self, value: crate::Animated<Paint>) -> Self {
368 self.animated_background = Some(value);
369 self
370 }
371
372 pub const fn animated_offset(mut self, value: crate::Animated<(f32, f32)>) -> Self {
375 self.animated_offset = Some(value);
376 self
377 }
378
379 pub const fn cursor(mut self, cursor: crate::Cursor) -> Self {
383 self.cursor = Some(cursor);
384 self
385 }
386
387 pub const fn user_select(mut self, user_select: crate::UserSelect) -> Self {
390 self.user_select = Some(user_select);
391 self
392 }
393}
394
395#[derive(Clone, Copy, PartialEq, Eq)]
398pub(crate) enum Mods {
399 Container,
401 Standard,
403 Chip,
405 Scaffold,
407 None,
409}
410
411pub(crate) type Attrs = Vec<(&'static str, OwnedAttr)>;
412
413fn float(value: Option<f32>) -> OwnedAttr {
414 value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
415}
416
417fn role(value: Option<impl Into<u16>>) -> OwnedAttr {
418 value.map_or(OwnedAttr::None, |value| {
419 OwnedAttr::Int(i64::from(value.into()))
420 })
421}
422
423fn paint(value: Option<Paint>) -> OwnedAttr {
424 value.map_or(OwnedAttr::None, |paint| {
425 OwnedAttr::Int(paint.to_bits() as i64)
426 })
427}
428
429pub(crate) fn role_or_zero(value: Option<impl Into<u16>>) -> OwnedAttr {
431 OwnedAttr::Int(value.map_or(0, |value| i64::from(value.into())))
432}
433
434pub(crate) fn dp_or_zero(value: Option<f32>) -> OwnedAttr {
436 OwnedAttr::Float(f64::from(value.unwrap_or(0.0)))
437}
438
439pub(crate) fn paint_or_zero(value: Option<Paint>) -> OwnedAttr {
441 OwnedAttr::Int(value.map_or(0, |paint| paint.to_bits() as i64))
442}
443
444pub(crate) fn optional_text(value: Option<String>) -> OwnedAttr {
445 value.map_or(OwnedAttr::None, OwnedAttr::Text)
446}
447
448pub(crate) fn push_modifiers(modifier: &Modifier, mods: Mods, out: &mut Attrs) {
450 let container = mods == Mods::Container;
451 let standard = container || mods == Mods::Standard;
452 let chip = standard || mods == Mods::Chip;
453 let scaffold = mods == Mods::Scaffold;
454 let mut push = |always: bool, name: &'static str, value: OwnedAttr| {
455 let set = !matches!(value, OwnedAttr::None | OwnedAttr::Bool(false));
456 if always || set {
457 out.push((name, value));
458 }
459 };
460 push(
461 container,
462 "observe_size",
463 modifier
464 .observe_size
465 .map_or(OwnedAttr::None, OwnedAttr::Int),
466 );
467 push(container, "motion", role(modifier.motion));
468 push(container || scaffold, "material", role(modifier.material));
469 push(chip, "weight", float(modifier.weight));
470 push(chip, "width", float(modifier.width));
471 push(chip, "height", float(modifier.height));
472 push(chip, "padding", float(modifier.padding));
473 push(chip, "padding_role", role(modifier.padding_role));
474 push(
476 standard || scaffold,
477 "background",
478 paint(if modifier.animated_background.is_some() {
479 None
480 } else {
481 modifier.background
482 }),
483 );
484 push(standard, "shape_role", role(modifier.shape_role));
485 push(standard, "corner_radius", float(modifier.corner_radius));
486 push(
487 false,
488 "corner_radii",
489 modifier.corner_radii.map_or(OwnedAttr::None, |values| {
490 OwnedAttr::Bytes(
491 values
492 .iter()
493 .flat_map(|value| value.to_le_bytes())
494 .collect(),
495 )
496 }),
497 );
498 push(false, "clip", OwnedAttr::Bool(modifier.clip));
499 push(standard, "border_width", float(modifier.border_width));
500 push(standard, "border_color", paint(modifier.border_color));
501 push(standard, "elevation", float(modifier.elevation));
502 push(
503 chip,
504 "fill_max_width",
505 OwnedAttr::Bool(modifier.fill_max_width),
506 );
507 push(
508 standard,
509 "fill_max_height",
510 OwnedAttr::Bool(modifier.fill_max_height),
511 );
512 push(
515 false,
516 "alpha",
517 float(if modifier.animated_alpha.is_some() {
518 None
519 } else {
520 modifier.alpha
521 }),
522 );
523 push(
524 false,
525 "transform",
526 match (modifier.animated_offset, modifier.transform) {
527 (None, Some(values)) => OwnedAttr::Bytes(
528 values
529 .iter()
530 .flat_map(|value| value.to_le_bytes())
531 .collect(),
532 ),
533 _ => OwnedAttr::None,
534 },
535 );
536 push(
538 false,
539 "cursor",
540 match modifier.cursor {
541 Some(crate::Cursor::Auto) | None => OwnedAttr::None,
542 Some(cursor) => OwnedAttr::Int(i64::from(u16::from(cursor))),
543 },
544 );
545 push(
546 false,
547 "user_select",
548 match modifier.user_select {
549 Some(crate::UserSelect::Auto) | None => OwnedAttr::None,
550 Some(select) => OwnedAttr::Int(i64::from(u16::from(select))),
551 },
552 );
553 push(
555 false,
556 "offset",
557 modifier.offset.map_or(OwnedAttr::None, |(x, y)| {
558 let mut bytes = Vec::with_capacity(8);
559 bytes.extend_from_slice(&x.to_le_bytes());
560 bytes.extend_from_slice(&y.to_le_bytes());
561 OwnedAttr::Bytes(bytes)
562 }),
563 );
564 push(
565 false,
566 "shadows",
567 if modifier.shadows[0].is_none() {
568 OwnedAttr::None
569 } else {
570 let mut bytes = Vec::new();
571 for shadow in modifier.shadows.iter().flatten() {
572 for value in [shadow.x, shadow.y, shadow.blur, shadow.spread] {
573 bytes.extend_from_slice(&value.to_le_bytes());
574 }
575 bytes.extend_from_slice(&shadow.paint.to_bits().to_le_bytes());
576 }
577 OwnedAttr::Bytes(bytes)
578 },
579 );
580 push(
581 false,
582 "animated_alpha",
583 modifier.animated_alpha.map_or(OwnedAttr::None, |value| {
584 OwnedAttr::Bytes(crate::animation::animated_bytes(
585 &[value.target.to_bits()],
586 &value.spec,
587 ))
588 }),
589 );
590 push(
591 false,
592 "animated_background",
593 modifier
594 .animated_background
595 .map_or(OwnedAttr::None, |value| {
596 let bits = value.target.to_bits();
597 OwnedAttr::Bytes(crate::animation::animated_bytes(
598 &[bits as u32, (bits >> 32) as u32],
599 &value.spec,
600 ))
601 }),
602 );
603 push(
604 false,
605 "animated_offset",
606 modifier.animated_offset.map_or(OwnedAttr::None, |value| {
607 OwnedAttr::Bytes(crate::animation::animated_bytes(
608 &[value.target.0.to_bits(), value.target.1.to_bits()],
609 &value.spec,
610 ))
611 }),
612 );
613}
614
615pub(crate) fn emit(
621 key: u64,
622 widget: WidgetKind,
623 fill: impl FnOnce(&mut Attrs),
624 listeners: Vec<(&'static str, EventCallback)>,
625 content: impl FnOnce(),
626) {
627 let _group = group_guard(key);
628 emit_node(widget, fill, listeners, content);
629}
630
631pub(crate) fn emit_node(
634 widget: WidgetKind,
635 fill: impl FnOnce(&mut Attrs),
636 listeners: Vec<(&'static str, EventCallback)>,
637 content: impl FnOnce(),
638) {
639 let node = with_composer_if_idle(|composer| {
640 if !composer.composing() {
641 return None;
642 }
643 let mut attrs = composer.take_attrs();
644 fill(&mut attrs);
645 Some(composer.update_node(widget, attrs, listeners))
646 })
647 .flatten();
648 let Some(node) = node else {
649 return;
650 };
651 with_composer(|composer| composer.push_node_context(node));
652 let _context = NodeContextGuard;
653 content();
654}
655
656struct NodeContextGuard;
658
659impl Drop for NodeContextGuard {
660 fn drop(&mut self) {
661 with_composer_if_idle(|composer| composer.pop_node_context());
662 }
663}
664
665pub(crate) const fn widget_key(name: &str) -> u64 {
667 crate::runtime::composer::call_site(name, 0)
668}