1use std::sync::{Arc, Mutex};
2
3use abi_stable::{
4 StableAbi,
5 std_types::{RBox, ROption, RString, RVec},
6};
7use slowshell_core::message::ItemMessage;
8
9#[repr(u8)]
10#[derive(StableAbi, Clone, Debug, PartialEq)]
11pub enum NotchStatus {
12 Icon(RString),
13 Text(RString),
14 IconText(RString, RString),
15 Service(RString, RString),
16}
17
18#[repr(C)]
19#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
20pub struct Color {
21 pub r: f32,
22 pub g: f32,
23 pub b: f32,
24 pub a: f32,
25}
26
27impl Color {
28 pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
29 pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
30 pub const TRANSPARENT: Self = Self::rgba(0.0, 0.0, 0.0, 0.0);
31
32 pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
33 Self { r, g, b, a: 1.0 }
34 }
35
36 pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
37 Self { r, g, b, a }
38 }
39}
40
41pub fn parse_hex(hex: &str) -> Option<Color> {
42 let hex = hex.strip_prefix('#').unwrap_or(hex);
43 let channels = match hex.len() {
44 3 | 6 => 3,
45 4 | 8 => 4,
46 _ => return None,
47 };
48 let step = if hex.len() <= 4 { 1 } else { 2 };
49
50 let mut nibbles = [0u8; 8];
51 for (i, byte) in hex.bytes().enumerate() {
52 nibbles[i] = match byte {
53 b'0'..=b'9' => byte - b'0',
54 b'a'..=b'f' => byte - b'a' + 10,
55 b'A'..=b'F' => byte - b'A' + 10,
56 _ => return None,
57 };
58 }
59
60 let mut out = [0u8; 4];
61 out[3] = 255;
62 for i in 0..channels {
63 if step == 1 {
64 out[i] = nibbles[i] * 16 + nibbles[i];
65 } else {
66 out[i] = nibbles[2 * i] * 16 + nibbles[2 * i + 1];
67 }
68 }
69
70 Some(Color {
71 r: out[0] as f32 / 255.0,
72 g: out[1] as f32 / 255.0,
73 b: out[2] as f32 / 255.0,
74 a: out[3] as f32 / 255.0,
75 })
76}
77
78#[repr(C)]
79#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
80pub struct Border {
81 pub width: f32,
82 pub radius: f32,
83 pub color: Color,
84}
85
86#[repr(C)]
87#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
88pub struct Style {
89 pub background: ROption<Color>,
90 pub border: ROption<Border>,
91 pub text_color: ROption<Color>,
92}
93
94impl Style {
95 pub fn bg(color: Color) -> Self {
96 Self {
97 background: ROption::RSome(color),
98 border: ROption::RNone,
99 text_color: ROption::RNone,
100 }
101 }
102
103 pub fn with_border(mut self, width: f32, radius: f32, color: Color) -> Self {
104 self.border = ROption::RSome(Border {
105 width,
106 radius,
107 color,
108 });
109 self
110 }
111}
112
113#[repr(C)]
114#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
115pub struct Padding {
116 pub top: f32,
117 pub right: f32,
118 pub bottom: f32,
119 pub left: f32,
120}
121
122impl Padding {
123 pub const ZERO: Self = Self {
124 top: 0.0,
125 right: 0.0,
126 bottom: 0.0,
127 left: 0.0,
128 };
129
130 pub const fn all(value: f32) -> Self {
131 Self {
132 top: value,
133 right: value,
134 bottom: value,
135 left: value,
136 }
137 }
138
139 pub const fn xy(x: f32, y: f32) -> Self {
140 Self {
141 top: y,
142 right: x,
143 bottom: y,
144 left: x,
145 }
146 }
147
148 pub fn to_array(&self) -> [f32; 4] {
149 [self.top, self.right, self.bottom, self.left]
150 }
151}
152
153#[repr(C)]
154#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
155pub enum Align {
156 #[default]
157 Start,
158 Center,
159 End,
160 Fill,
161}
162
163#[repr(C)]
164#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
165pub enum Length {
166 #[default]
167 Shrink,
168 Fill,
169 Fixed(f32),
170}
171
172#[repr(C)]
173#[derive(StableAbi, Clone, Copy, Debug, PartialEq)]
174pub struct Theme {
175 pub base: Color,
176 pub crust: Color,
177 pub mantle: Color,
178 pub primary: Color,
179 pub secondary: Color,
180 pub green: Color,
181 pub red: Color,
182 pub blue: Color,
183 pub yellow: Color,
184 pub orange: Color,
185 pub text: Color,
186 pub subtext: Color,
187 pub overlay: Color,
188}
189
190impl Default for Theme {
191 fn default() -> Self {
192 let rgb =
193 |r: u8, g: u8, b: u8| Color::rgb(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0);
194 Self {
195 base: rgb(14, 28, 36),
196 crust: rgb(6, 16, 22),
197 mantle: rgb(10, 21, 28),
198 primary: rgb(130, 182, 204),
199 secondary: rgb(162, 211, 226),
200 green: rgb(143, 214, 207),
201 red: rgb(111, 168, 192),
202 blue: rgb(143, 214, 234),
203 yellow: rgb(159, 208, 224),
204 orange: rgb(143, 198, 220),
205 text: rgb(219, 234, 241),
206 subtext: rgb(176, 205, 217),
207 overlay: rgb(143, 179, 194),
208 }
209 }
210}
211
212impl Theme {
213 pub fn color(&self, name: &str) -> Option<Color> {
214 let color = match name {
215 "base" => self.base,
216 "crust" => self.crust,
217 "mantle" => self.mantle,
218 "primary" => self.primary,
219 "secondary" => self.secondary,
220 "green" => self.green,
221 "red" => self.red,
222 "blue" => self.blue,
223 "yellow" => self.yellow,
224 "orange" => self.orange,
225 "text" => self.text,
226 "subtext" => self.subtext,
227 "overlay" => self.overlay,
228 _ if let Some((name, value)) = name.split_once('/') => {
229 let mut color = self.color(name)?;
230 color.a = value.parse::<f32>().ok()?;
231 return Some(color);
232 }
233 _ => return None,
234 };
235 Some(color)
236 }
237}
238
239#[repr(C)]
240#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
241pub struct AnimationOffset {
242 pub x: f32,
243 pub y: f32,
244}
245
246impl AnimationOffset {
247 pub const fn new(x: f32, y: f32) -> Self {
248 Self { x, y }
249 }
250}
251
252impl From<(f32, f32)> for AnimationOffset {
253 fn from((x, y): (f32, f32)) -> Self {
254 Self { x, y }
255 }
256}
257
258impl From<AnimationOffset> for (f32, f32) {
259 fn from(offset: AnimationOffset) -> Self {
260 (offset.x, offset.y)
261 }
262}
263
264#[repr(C)]
265#[derive(StableAbi, Clone, Copy, Debug)]
266pub struct CustomAnimation {
267 pub func: extern "C" fn(f32) -> AnimationOffset,
268}
269
270impl PartialEq for CustomAnimation {
271 fn eq(&self, other: &Self) -> bool {
272 std::ptr::fn_addr_eq(self.func, other.func)
273 }
274}
275
276impl CustomAnimation {
277 pub const fn new(func: extern "C" fn(f32) -> AnimationOffset) -> Self {
278 Self { func }
279 }
280
281 pub fn offset(&self, progress: f32) -> AnimationOffset {
282 (self.func)(progress)
283 }
284}
285
286#[repr(C)]
287#[derive(StableAbi, Clone, Copy, Debug, PartialEq, Eq)]
288pub enum AnimationEdge {
289 Top,
290 Bottom,
291 Left,
292 Right,
293}
294
295impl From<icss::widgets::animation::Edge> for AnimationEdge {
296 fn from(edge: icss::widgets::animation::Edge) -> Self {
297 match edge {
298 icss::widgets::animation::Edge::Top => AnimationEdge::Top,
299 icss::widgets::animation::Edge::Bottom => AnimationEdge::Bottom,
300 icss::widgets::animation::Edge::Left => AnimationEdge::Left,
301 icss::widgets::animation::Edge::Right => AnimationEdge::Right,
302 }
303 }
304}
305
306impl From<AnimationEdge> for icss::widgets::animation::Edge {
307 fn from(edge: AnimationEdge) -> Self {
308 match edge {
309 AnimationEdge::Top => icss::widgets::animation::Edge::Top,
310 AnimationEdge::Bottom => icss::widgets::animation::Edge::Bottom,
311 AnimationEdge::Left => icss::widgets::animation::Edge::Left,
312 AnimationEdge::Right => icss::widgets::animation::Edge::Right,
313 }
314 }
315}
316
317#[repr(C)]
318#[derive(StableAbi, Clone, Copy, Debug, PartialEq)]
319pub enum AnimationKind {
320 None,
321 Slide,
322 FadeIn,
323 FadeOut,
324 SlideIn(AnimationEdge),
325 SlideOut(AnimationEdge),
326 Custom(CustomAnimation),
327}
328
329impl From<icss::widgets::animation::AnimKind> for AnimationKind {
330 fn from(kind: icss::widgets::animation::AnimKind) -> Self {
331 match kind {
332 icss::widgets::animation::AnimKind::FadeIn => AnimationKind::FadeIn,
333 icss::widgets::animation::AnimKind::FadeOut => AnimationKind::FadeOut,
334 icss::widgets::animation::AnimKind::SlideIn(edge) => AnimationKind::SlideIn(edge.into()),
335 icss::widgets::animation::AnimKind::SlideOut(edge) => AnimationKind::SlideOut(edge.into()),
336 }
337 }
338}
339
340#[repr(C)]
341#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
342pub enum Easing {
343 Linear,
344 #[default]
345 Ease,
346 EaseIn,
347 EaseOut,
348 EaseInOut,
349}
350
351#[repr(C)]
352#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
353pub struct CubicBezier {
354 pub x1: f32,
355 pub y1: f32,
356 pub x2: f32,
357 pub y2: f32,
358}
359
360impl CubicBezier {
361 pub const fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
362 Self { x1, y1, x2, y2 }
363 }
364}
365
366impl From<CubicBezier> for iced_anim::transition::bezier::Bezier {
367 fn from(b: CubicBezier) -> Self {
368 iced_anim::transition::bezier::Bezier::new(b.x1, b.y1, b.x2, b.y2)
369 }
370}
371
372#[repr(C)]
373#[derive(StableAbi, Clone, Copy, Debug, Default)]
374pub enum AnimationCurve {
375 Linear,
376 #[default]
377 Ease,
378 EaseIn,
379 EaseOut,
380 EaseInOut,
381 Bezier(CubicBezier),
382 Custom(extern "C" fn(f32) -> f32),
383}
384
385impl PartialEq for AnimationCurve {
386 fn eq(&self, other: &Self) -> bool {
387 match (self, other) {
388 (Self::Linear, Self::Linear) => true,
389 (Self::Ease, Self::Ease) => true,
390 (Self::EaseIn, Self::EaseIn) => true,
391 (Self::EaseOut, Self::EaseOut) => true,
392 (Self::EaseInOut, Self::EaseInOut) => true,
393 (Self::Bezier(a), Self::Bezier(b)) => a == b,
394 (Self::Custom(a), Self::Custom(b)) => std::ptr::fn_addr_eq(*a, *b),
395 _ => false,
396 }
397 }
398}
399
400impl From<Easing> for AnimationCurve {
401 fn from(easing: Easing) -> Self {
402 match easing {
403 Easing::Linear => AnimationCurve::Linear,
404 Easing::Ease => AnimationCurve::Ease,
405 Easing::EaseIn => AnimationCurve::EaseIn,
406 Easing::EaseOut => AnimationCurve::EaseOut,
407 Easing::EaseInOut => AnimationCurve::EaseInOut,
408 }
409 }
410}
411
412impl From<icss::widgets::animation::Easing> for AnimationCurve {
413 fn from(easing: icss::widgets::animation::Easing) -> Self {
414 match easing {
415 icss::widgets::animation::Easing::Linear => AnimationCurve::Linear,
416 icss::widgets::animation::Easing::EaseIn => AnimationCurve::EaseIn,
417 icss::widgets::animation::Easing::EaseOut => AnimationCurve::EaseOut,
418 icss::widgets::animation::Easing::EaseInOut => AnimationCurve::EaseInOut,
419 }
420 }
421}
422
423impl From<icss::widgets::animation::Easing> for Easing {
424 fn from(easing: icss::widgets::animation::Easing) -> Self {
425 match easing {
426 icss::widgets::animation::Easing::Linear => Easing::Linear,
427 icss::widgets::animation::Easing::EaseIn => Easing::EaseIn,
428 icss::widgets::animation::Easing::EaseOut => Easing::EaseOut,
429 icss::widgets::animation::Easing::EaseInOut => Easing::EaseInOut,
430 }
431 }
432}
433
434#[repr(C)]
435#[derive(StableAbi, Clone, Copy, Debug, PartialEq)]
436pub struct SpringMotion {
437 pub damping: f32,
438 pub response_ms: u32,
439}
440
441impl SpringMotion {
442 pub const SMOOTH: Self = Self {
443 damping: 1.0,
444 response_ms: 500,
445 };
446
447 pub const SNAPPY: Self = Self {
448 damping: 0.85,
449 response_ms: 500,
450 };
451
452 pub const BOUNCY: Self = Self {
453 damping: 0.7,
454 response_ms: 500,
455 };
456
457 pub const INSTANT: Self = Self {
458 damping: 1.0,
459 response_ms: 0,
460 };
461
462 pub const fn new(damping: f32, response_ms: u32) -> Self {
463 Self {
464 damping,
465 response_ms,
466 }
467 }
468
469 pub const fn with_duration(mut self, millis: u32) -> Self {
470 self.response_ms = millis;
471 self
472 }
473
474 pub const fn with_damping(mut self, damping: f32) -> Self {
475 self.damping = damping;
476 self
477 }
478}
479
480impl Default for SpringMotion {
481 fn default() -> Self {
482 Self::SMOOTH
483 }
484}
485
486impl From<iced_anim::spring::Motion> for SpringMotion {
487 fn from(motion: iced_anim::spring::Motion) -> Self {
488 Self {
489 damping: motion.damping,
490 response_ms: motion.response.as_millis() as u32,
491 }
492 }
493}
494
495impl From<SpringMotion> for iced_anim::spring::Motion {
496 fn from(m: SpringMotion) -> Self {
497 iced_anim::spring::Motion {
498 damping: m.damping,
499 response: std::time::Duration::from_millis(m.response_ms as u64),
500 }
501 }
502}
503
504#[repr(C)]
505#[derive(StableAbi, Clone, Copy, Debug, PartialEq)]
506pub struct Transition {
507 pub curve: AnimationCurve,
508 pub duration_ms: u32,
509 pub reversible: bool,
510}
511
512impl Default for Transition {
513 fn default() -> Self {
514 Self {
515 curve: AnimationCurve::default(),
516 duration_ms: 130,
517 reversible: false,
518 }
519 }
520}
521
522impl Transition {
523 pub const fn new(curve: AnimationCurve, duration_ms: u32) -> Self {
524 Self {
525 curve,
526 duration_ms,
527 reversible: false,
528 }
529 }
530
531 pub const fn linear(duration_ms: u32) -> Self {
532 Self::new(AnimationCurve::Linear, duration_ms)
533 }
534
535 pub const fn ease(duration_ms: u32) -> Self {
536 Self::new(AnimationCurve::Ease, duration_ms)
537 }
538
539 pub const fn ease_in(duration_ms: u32) -> Self {
540 Self::new(AnimationCurve::EaseIn, duration_ms)
541 }
542
543 pub const fn ease_out(duration_ms: u32) -> Self {
544 Self::new(AnimationCurve::EaseOut, duration_ms)
545 }
546
547 pub const fn ease_in_out(duration_ms: u32) -> Self {
548 Self::new(AnimationCurve::EaseInOut, duration_ms)
549 }
550
551 pub const fn bezier(x1: f32, y1: f32, x2: f32, y2: f32, duration_ms: u32) -> Self {
552 Self::new(
553 AnimationCurve::Bezier(CubicBezier::new(x1, y1, x2, y2)),
554 duration_ms,
555 )
556 }
557
558 pub const fn custom(func: extern "C" fn(f32) -> f32, duration_ms: u32) -> Self {
559 Self::new(AnimationCurve::Custom(func), duration_ms)
560 }
561
562 pub const fn reversible(mut self, rev: bool) -> Self {
563 self.reversible = rev;
564 self
565 }
566}
567
568impl From<iced_anim::transition::Easing> for Transition {
569 fn from(easing: iced_anim::transition::Easing) -> Self {
570 let curve = match easing.curve {
571 iced_anim::transition::Curve::Linear => AnimationCurve::Linear,
572 iced_anim::transition::Curve::Ease => AnimationCurve::Ease,
573 iced_anim::transition::Curve::EaseIn => AnimationCurve::EaseIn,
574 iced_anim::transition::Curve::EaseOut => AnimationCurve::EaseOut,
575 iced_anim::transition::Curve::EaseInOut => AnimationCurve::EaseInOut,
576 iced_anim::transition::Curve::Bezier(_) => AnimationCurve::Ease,
577 iced_anim::transition::Curve::Custom(_) => AnimationCurve::Linear,
578 };
579 Self {
580 curve,
581 duration_ms: easing.duration.as_millis() as u32,
582 reversible: easing.reversible,
583 }
584 }
585}
586
587#[repr(C)]
588#[derive(StableAbi, Clone, Copy, Debug, PartialEq)]
589pub enum AnimationMode {
590 Spring(SpringMotion),
591 Transition(Transition),
592}
593
594impl Default for AnimationMode {
595 fn default() -> Self {
596 AnimationMode::Transition(Transition::default())
597 }
598}
599
600impl From<SpringMotion> for AnimationMode {
601 fn from(motion: SpringMotion) -> Self {
602 AnimationMode::Spring(motion)
603 }
604}
605
606impl From<Transition> for AnimationMode {
607 fn from(transition: Transition) -> Self {
608 AnimationMode::Transition(transition)
609 }
610}
611
612impl From<Easing> for AnimationMode {
613 fn from(easing: Easing) -> Self {
614 AnimationMode::Transition(Transition {
615 curve: easing.into(),
616 duration_ms: 130,
617 reversible: false,
618 })
619 }
620}
621
622impl From<AnimationCurve> for AnimationMode {
623 fn from(curve: AnimationCurve) -> Self {
624 AnimationMode::Transition(Transition {
625 curve,
626 duration_ms: 130,
627 reversible: false,
628 })
629 }
630}
631
632impl From<iced_anim::animated::Mode> for AnimationMode {
633 fn from(mode: iced_anim::animated::Mode) -> Self {
634 match mode {
635 iced_anim::animated::Mode::Spring(m) => AnimationMode::Spring(m.into()),
636 iced_anim::animated::Mode::Transition(e) => AnimationMode::Transition(e.into()),
637 }
638 }
639}
640
641impl From<iced_anim::spring::Motion> for AnimationMode {
642 fn from(motion: iced_anim::spring::Motion) -> Self {
643 AnimationMode::Spring(motion.into())
644 }
645}
646
647impl From<iced_anim::transition::Easing> for AnimationMode {
648 fn from(easing: iced_anim::transition::Easing) -> Self {
649 AnimationMode::Transition(easing.into())
650 }
651}
652
653#[repr(C)]
654#[derive(StableAbi, Clone, Copy, Debug, PartialEq)]
655pub struct Animation {
656 pub kind: AnimationKind,
657 pub duration_ms: u32,
658 pub easing: Easing,
659 pub offset_x: f32,
660 pub offset_y: f32,
661 pub tween: bool,
662 pub mode: AnimationMode,
663 pub key: u64,
664}
665
666impl Animation {
667 pub const fn slide(offset: (f32, f32)) -> Self {
668 Self {
669 kind: AnimationKind::Slide,
670 duration_ms: 130,
671 easing: Easing::EaseOut,
672 offset_x: offset.0,
673 offset_y: offset.1,
674 tween: false,
675 mode: AnimationMode::Transition(Transition::ease_out(130)),
676 key: 0,
677 }
678 }
679
680 pub const fn slide_in_edge(edge: AnimationEdge, distance: f32) -> Self {
681 Self {
682 kind: AnimationKind::SlideIn(edge),
683 duration_ms: 130,
684 easing: Easing::EaseOut,
685 offset_x: distance,
686 offset_y: distance,
687 tween: false,
688 mode: AnimationMode::Transition(Transition::ease_out(130)),
689 key: 0,
690 }
691 }
692
693 pub const fn slide_out_edge(edge: AnimationEdge, distance: f32) -> Self {
694 Self {
695 kind: AnimationKind::SlideOut(edge),
696 duration_ms: 130,
697 easing: Easing::EaseIn,
698 offset_x: distance,
699 offset_y: distance,
700 tween: false,
701 mode: AnimationMode::Transition(Transition::ease_in(130)),
702 key: 0,
703 }
704 }
705
706 pub const fn fade_in() -> Self {
707 Self {
708 kind: AnimationKind::FadeIn,
709 duration_ms: 130,
710 easing: Easing::Ease,
711 offset_x: 0.0,
712 offset_y: 0.0,
713 tween: true,
714 mode: AnimationMode::Transition(Transition::ease(130)),
715 key: 0,
716 }
717 }
718
719 pub const fn fade_out() -> Self {
720 Self {
721 kind: AnimationKind::FadeOut,
722 duration_ms: 130,
723 easing: Easing::Ease,
724 offset_x: 0.0,
725 offset_y: 0.0,
726 tween: true,
727 mode: AnimationMode::Transition(Transition::ease(130)),
728 key: 0,
729 }
730 }
731
732 pub const fn tween() -> Self {
733 Self {
734 kind: AnimationKind::None,
735 duration_ms: 130,
736 easing: Easing::Ease,
737 offset_x: 0.0,
738 offset_y: 0.0,
739 tween: true,
740 mode: AnimationMode::Transition(Transition::ease(130)),
741 key: 0,
742 }
743 }
744
745 pub const fn custom(func: extern "C" fn(f32) -> AnimationOffset) -> Self {
746 Self {
747 kind: AnimationKind::Custom(CustomAnimation::new(func)),
748 duration_ms: 130,
749 easing: Easing::EaseOut,
750 offset_x: 0.0,
751 offset_y: 0.0,
752 tween: false,
753 mode: AnimationMode::Transition(Transition::ease_out(130)),
754 key: 0,
755 }
756 }
757
758 pub fn from_icss(
759 kind: icss::widgets::animation::AnimKind,
760 duration: std::time::Duration,
761 ) -> Self {
762 let abi_kind = AnimationKind::from(kind);
763 let duration_ms = duration.as_millis() as u32;
764 match abi_kind {
765 AnimationKind::SlideIn(edge) => Self::slide_in_edge(edge, 100.0).duration(duration_ms),
766 AnimationKind::SlideOut(edge) => Self::slide_out_edge(edge, 100.0).duration(duration_ms),
767 AnimationKind::FadeIn => Self::fade_in().duration(duration_ms),
768 AnimationKind::FadeOut => Self::fade_out().duration(duration_ms),
769 _ => Self::tween().duration(duration_ms),
770 }
771 }
772
773 pub fn spring(motion: impl Into<SpringMotion>) -> Self {
774 Self::tween().with_spring(motion)
775 }
776
777 pub fn with_spring(mut self, motion: impl Into<SpringMotion>) -> Self {
778 let m = motion.into();
779 self.duration_ms = m.response_ms;
780 self.mode = AnimationMode::Spring(m);
781 self
782 }
783
784 pub fn transition(transition: impl Into<Transition>) -> Self {
785 Self::tween().with_transition(transition)
786 }
787
788 pub fn with_transition(mut self, transition: impl Into<Transition>) -> Self {
789 let t = transition.into();
790 self.duration_ms = t.duration_ms;
791 self.mode = AnimationMode::Transition(t);
792 self
793 }
794
795 pub fn mode(mut self, mode: impl Into<AnimationMode>) -> Self {
796 self.mode = mode.into();
797 if let AnimationMode::Transition(t) = &self.mode {
798 self.duration_ms = t.duration_ms;
799 } else if let AnimationMode::Spring(s) = &self.mode {
800 self.duration_ms = s.response_ms;
801 }
802 self
803 }
804
805 pub const fn smooth(mut self) -> Self {
806 self.duration_ms = SpringMotion::SMOOTH.response_ms;
807 self.mode = AnimationMode::Spring(SpringMotion::SMOOTH);
808 self
809 }
810
811 pub const fn snappy(mut self) -> Self {
812 self.duration_ms = SpringMotion::SNAPPY.response_ms;
813 self.mode = AnimationMode::Spring(SpringMotion::SNAPPY);
814 self
815 }
816
817 pub const fn bouncy(mut self) -> Self {
818 self.duration_ms = SpringMotion::BOUNCY.response_ms;
819 self.mode = AnimationMode::Spring(SpringMotion::BOUNCY);
820 self
821 }
822
823 pub const fn instant(mut self) -> Self {
824 self.duration_ms = SpringMotion::INSTANT.response_ms;
825 self.mode = AnimationMode::Spring(SpringMotion::INSTANT);
826 self
827 }
828
829 pub const fn bezier(mut self, x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
830 self.mode = AnimationMode::Transition(Transition {
831 curve: AnimationCurve::Bezier(CubicBezier::new(x1, y1, x2, y2)),
832 duration_ms: self.duration_ms,
833 reversible: false,
834 });
835 self
836 }
837
838 pub const fn custom_curve(mut self, func: extern "C" fn(f32) -> f32) -> Self {
839 self.mode = AnimationMode::Transition(Transition {
840 curve: AnimationCurve::Custom(func),
841 duration_ms: self.duration_ms,
842 reversible: false,
843 });
844 self
845 }
846
847 pub const fn duration(mut self, millis: u32) -> Self {
848 self.duration_ms = millis;
849 match self.mode {
850 AnimationMode::Transition(mut t) => {
851 t.duration_ms = millis;
852 self.mode = AnimationMode::Transition(t);
853 }
854 AnimationMode::Spring(mut s) => {
855 s.response_ms = millis;
856 self.mode = AnimationMode::Spring(s);
857 }
858 }
859 self
860 }
861
862 pub const fn easing(mut self, easing: Easing) -> Self {
863 self.easing = easing;
864 self.mode = AnimationMode::Transition(Transition {
865 curve: match easing {
866 Easing::Linear => AnimationCurve::Linear,
867 Easing::Ease => AnimationCurve::Ease,
868 Easing::EaseIn => AnimationCurve::EaseIn,
869 Easing::EaseOut => AnimationCurve::EaseOut,
870 Easing::EaseInOut => AnimationCurve::EaseInOut,
871 },
872 duration_ms: self.duration_ms,
873 reversible: false,
874 });
875 self
876 }
877
878 pub const fn curve(mut self, curve: AnimationCurve) -> Self {
879 self.mode = AnimationMode::Transition(Transition {
880 curve,
881 duration_ms: self.duration_ms,
882 reversible: false,
883 });
884 self
885 }
886}
887
888#[repr(C)]
889#[derive(StableAbi, Clone, Copy, Debug)]
890pub struct CanvasRef {
891 pub ptr: *mut u8,
892 pub len: usize,
893 pub id: u64,
894}
895
896#[repr(C)]
897#[derive(StableAbi, Clone, Debug, PartialEq)]
898pub enum Image {
899 Rgba {
900 cache_key: u64,
901 width: u32,
902 height: u32,
903 pixels: RVec<u8>,
904 },
905 Encoded {
906 cache_key: u64,
907 bytes: RVec<u8>,
908 },
909 Path(RString),
910}
911
912impl Image {
913 pub fn rgba(width: u32, height: u32, pixels: impl Into<Vec<u8>>) -> Image {
914 Image::Rgba {
915 cache_key: 0,
916 width,
917 height,
918 pixels: pixels.into().into(),
919 }
920 }
921
922 pub fn rgba_cached(cache_key: u64, width: u32, height: u32, pixels: impl Into<Vec<u8>>) -> Image {
923 Image::Rgba {
924 cache_key,
925 width,
926 height,
927 pixels: pixels.into().into(),
928 }
929 }
930
931 pub fn encoded(bytes: impl Into<Vec<u8>>) -> Image {
932 Image::Encoded {
933 cache_key: 0,
934 bytes: bytes.into().into(),
935 }
936 }
937
938 pub fn encoded_cached(cache_key: u64, bytes: impl Into<Vec<u8>>) -> Image {
939 Image::Encoded {
940 cache_key,
941 bytes: bytes.into().into(),
942 }
943 }
944
945 pub fn path(path: impl Into<String>) -> Image {
946 Image::Path(path.into().into())
947 }
948}
949
950#[repr(C)]
951#[derive(StableAbi, Clone)]
952#[sabi(unsafe_opaque_fields)]
953pub struct UnstableElement {
954 pub(crate) builder: Arc<dyn Fn() -> iced::Element<'static, ItemMessage> + Send + Sync>,
955 pub classes: RVec<RString>,
956}
957
958impl std::fmt::Debug for UnstableElement {
959 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
960 f.debug_struct("UnstableElement")
961 .field("classes", &self.classes)
962 .finish()
963 }
964}
965
966struct SendCell(Mutex<Option<iced::Element<'static, ItemMessage>>>);
967unsafe impl Send for SendCell {}
968unsafe impl Sync for SendCell {}
969
970impl UnstableElement {
971 pub fn new<F>(builder: F) -> Self
972 where
973 F: Fn() -> iced::Element<'static, ItemMessage> + Send + Sync + 'static,
974 {
975 Self {
976 builder: Arc::new(builder),
977 classes: RVec::new(),
978 }
979 }
980
981 pub fn from_element(element: iced::Element<'static, ItemMessage>) -> Self {
982 let cell = Arc::new(SendCell(Mutex::new(Some(element))));
983 Self {
984 builder: Arc::new(move || {
985 cell
986 .0
987 .lock()
988 .ok()
989 .and_then(|mut guard| guard.take())
990 .unwrap_or_else(|| iced::widget::Space::new().into())
991 }),
992 classes: RVec::new(),
993 }
994 }
995
996 pub fn build(&self) -> iced::Element<'static, ItemMessage> {
997 (self.builder)()
998 }
999
1000 pub fn classes(mut self, classes: &[&str]) -> Self {
1001 self.classes = classes.iter().map(|c| RString::from(*c)).collect();
1002 self
1003 }
1004}
1005
1006impl From<iced::Element<'static, ItemMessage>> for UnstableElement {
1007 fn from(element: iced::Element<'static, ItemMessage>) -> Self {
1008 Self::from_element(element)
1009 }
1010}
1011
1012#[repr(C)]
1013#[derive(StableAbi, Clone, Debug)]
1014pub enum Node {
1015 Row {
1016 children: RVec<Node>,
1017 spacing: f32,
1018 align: Align,
1019 classes: RVec<RString>,
1020 },
1021 Column {
1022 children: RVec<Node>,
1023 spacing: f32,
1024 align: Align,
1025 classes: RVec<RString>,
1026 },
1027 Container {
1028 child: RBox<Node>,
1029 padding: Padding,
1030 align_x: Align,
1031 align_y: Align,
1032 style: ROption<Style>,
1033 width: Length,
1034 height: Length,
1035 classes: RVec<RString>,
1036 },
1037 Clickable {
1038 child: RBox<Node>,
1039 action: ROption<RString>,
1040 effect: ROption<[usize; 4]>,
1041 classes: RVec<RString>,
1042 },
1043 Scrollable {
1044 child: RBox<Node>,
1045 width: Length,
1046 height: Length,
1047 report_scroll: bool,
1048 classes: RVec<RString>,
1049 },
1050 Icon {
1051 name: RString,
1052 size: f32,
1053 color: ROption<Color>,
1054 classes: RVec<RString>,
1055 },
1056 Text {
1057 content: RString,
1058 size: f32,
1059 color: ROption<Color>,
1060 classes: RVec<RString>,
1061 },
1062 Progress {
1063 value: f32,
1064 color: ROption<Color>,
1065 width: Length,
1066 height: Length,
1067 classes: RVec<RString>,
1068 },
1069 Image {
1070 image: Image,
1071 width: Length,
1072 height: Length,
1073 border_radius: f32,
1074 rotation: f32,
1075 classes: RVec<RString>,
1076 },
1077 Slider {
1078 min: f32,
1079 max: f32,
1080 value: f32,
1081 step: f32,
1082 action: ROption<RString>,
1083 width: Length,
1084 classes: RVec<RString>,
1085 },
1086 Stack {
1087 children: RVec<Node>,
1088 width: Length,
1089 height: Length,
1090 classes: RVec<RString>,
1091 },
1092 Canvas {
1093 cache_key: u64,
1094 pixel_width: u32,
1095 pixel_height: u32,
1096 width: Length,
1097 height: Length,
1098 pixels: RVec<u8>,
1099 pool_id: u64,
1100 classes: RVec<RString>,
1101 },
1102 TextInput {
1103 id: RString,
1104 value: RString,
1105 placeholder: RString,
1106 secure: bool,
1107 size: f32,
1108 color: ROption<Color>,
1109 width: Length,
1110 classes: RVec<RString>,
1111 },
1112 EventWrapper {
1113 child: RBox<Node>,
1114 id: RString,
1115 classes: RVec<RString>,
1116 },
1117 Animated {
1118 child: RBox<Node>,
1119 animation: Animation,
1120 },
1121 UnstableElement(UnstableElement),
1122}
1123
1124impl From<UnstableElement> for Node {
1125 fn from(element: UnstableElement) -> Self {
1126 Node::UnstableElement(element)
1127 }
1128}
1129
1130impl From<iced::Element<'static, ItemMessage>> for Node {
1131 fn from(element: iced::Element<'static, ItemMessage>) -> Self {
1132 Node::UnstableElement(UnstableElement::from_element(element))
1133 }
1134}
1135
1136impl Node {
1137 pub fn row(children: impl IntoIterator<Item = Node>) -> Node {
1138 Node::Row {
1139 children: children.into_iter().collect(),
1140 spacing: 4.0,
1141 align: Align::Start,
1142 classes: RVec::new(),
1143 }
1144 }
1145
1146 pub fn column(children: impl IntoIterator<Item = Node>) -> Node {
1147 Node::Column {
1148 children: children.into_iter().collect(),
1149 spacing: 4.0,
1150 align: Align::Start,
1151 classes: RVec::new(),
1152 }
1153 }
1154
1155 pub fn container(child: Node) -> Node {
1156 Node::Container {
1157 child: RBox::new(child),
1158 padding: Padding::ZERO,
1159 align_x: Align::Start,
1160 align_y: Align::Start,
1161 style: ROption::RNone,
1162 width: Length::Shrink,
1163 height: Length::Shrink,
1164 classes: RVec::new(),
1165 }
1166 }
1167
1168 pub fn spacing(mut self, s: f32) -> Self {
1169 match &mut self {
1170 Node::Row { spacing, .. } | Node::Column { spacing, .. } => *spacing = s,
1171 _ => {}
1172 }
1173 self
1174 }
1175
1176 pub fn align(mut self, a: Align) -> Self {
1177 match &mut self {
1178 Node::Row { align, .. } | Node::Column { align, .. } => *align = a,
1179 _ => {}
1180 }
1181 self
1182 }
1183
1184 pub fn align_x(mut self, a: Align) -> Self {
1185 if let Node::Container { align_x, .. } = &mut self {
1186 *align_x = a;
1187 }
1188 self
1189 }
1190
1191 pub fn align_y(mut self, a: Align) -> Self {
1192 if let Node::Container { align_y, .. } = &mut self {
1193 *align_y = a;
1194 }
1195 self
1196 }
1197
1198 pub fn padding(mut self, p: Padding) -> Self {
1199 if let Node::Container { padding, .. } = &mut self {
1200 *padding = p;
1201 }
1202 self
1203 }
1204
1205 pub fn style(mut self, s: Style) -> Self {
1206 if let Node::Container { style, .. } = &mut self {
1207 *style = ROption::RSome(s);
1208 }
1209 self
1210 }
1211
1212 pub fn scrollable(child: Node) -> Node {
1213 Node::Scrollable {
1214 child: RBox::new(child),
1215 width: Length::Shrink,
1216 height: Length::Shrink,
1217 report_scroll: false,
1218 classes: RVec::new(),
1219 }
1220 }
1221
1222 pub fn clickable(child: Node) -> Node {
1223 Node::Clickable {
1224 child: RBox::new(child),
1225 action: ROption::RNone,
1226 effect: ROption::RNone,
1227 classes: RVec::new(),
1228 }
1229 }
1230
1231 pub fn icon(name: impl Into<RString>) -> Node {
1232 Node::Icon {
1233 name: name.into(),
1234 size: 16.0,
1235 color: ROption::RNone,
1236 classes: RVec::new(),
1237 }
1238 }
1239
1240 pub fn text(content: impl Into<RString>) -> Node {
1241 Node::Text {
1242 content: content.into(),
1243 size: 12.0,
1244 color: ROption::RNone,
1245 classes: RVec::new(),
1246 }
1247 }
1248
1249 pub fn text_input(id: impl Into<RString>, value: impl Into<RString>) -> Node {
1250 Node::TextInput {
1251 id: id.into(),
1252 value: value.into(),
1253 placeholder: RString::new(),
1254 secure: false,
1255 size: 12.0,
1256 color: ROption::RNone,
1257 width: Length::Fill,
1258 classes: RVec::new(),
1259 }
1260 }
1261
1262 pub fn event_wrapper(child: Node, id: impl Into<RString>) -> Node {
1263 Node::EventWrapper {
1264 child: RBox::new(child),
1265 id: id.into(),
1266 classes: RVec::new(),
1267 }
1268 }
1269
1270 pub fn progress(value: f32) -> Node {
1271 Node::Progress {
1272 value,
1273 color: ROption::RNone,
1274 width: Length::Shrink,
1275 height: Length::Fixed(4.0),
1276 classes: RVec::new(),
1277 }
1278 }
1279
1280 pub fn image(image: Image) -> Node {
1281 Node::Image {
1282 image,
1283 width: Length::Shrink,
1284 height: Length::Shrink,
1285 border_radius: 0.0,
1286 rotation: 0.0,
1287 classes: RVec::new(),
1288 }
1289 }
1290
1291 pub fn slider(min: f32, max: f32, value: f32, action: impl Into<String>) -> Node {
1292 Node::Slider {
1293 min,
1294 max,
1295 value,
1296 step: 1.0,
1297 action: ROption::RSome(action.into().into()),
1298 width: Length::Shrink,
1299 classes: RVec::new(),
1300 }
1301 }
1302
1303 pub fn stack(children: impl IntoIterator<Item = Node>) -> Node {
1304 Node::Stack {
1305 children: children.into_iter().collect(),
1306 width: Length::Shrink,
1307 height: Length::Shrink,
1308 classes: RVec::new(),
1309 }
1310 }
1311
1312 pub fn border_radius(mut self, radius: f32) -> Self {
1313 if let Node::Image { border_radius, .. } = &mut self {
1314 *border_radius = radius;
1315 }
1316 self
1317 }
1318
1319 pub fn circle(self) -> Self {
1320 self.border_radius(9999.0)
1321 }
1322
1323 pub fn rotation(mut self, radians: f32) -> Self {
1324 if let Node::Image { rotation, .. } = &mut self {
1325 *rotation = radians;
1326 }
1327 self
1328 }
1329
1330 pub fn step(mut self, s: f32) -> Self {
1331 if let Node::Slider { step, .. } = &mut self {
1332 *step = s;
1333 }
1334 self
1335 }
1336
1337 pub fn canvas(width: u32, height: u32, draw: impl FnOnce(&mut [u8])) -> Node {
1338 Self::canvas_inner(0, width, height, draw)
1339 }
1340
1341 pub fn canvas_cached(
1342 cache_key: u64,
1343 width: u32,
1344 height: u32,
1345 draw: impl FnOnce(&mut [u8]),
1346 ) -> Node {
1347 Self::canvas_inner(cache_key, width, height, draw)
1348 }
1349
1350 fn canvas_inner(cache_key: u64, width: u32, height: u32, draw: impl FnOnce(&mut [u8])) -> Node {
1351 let mut pixels = vec![0u8; width as usize * height as usize * 4];
1352 draw(&mut pixels);
1353 Node::Canvas {
1354 cache_key,
1355 pixel_width: width,
1356 pixel_height: height,
1357 width: Length::Shrink,
1358 height: Length::Shrink,
1359 pixels: pixels.into(),
1360 pool_id: 0,
1361 classes: RVec::new(),
1362 }
1363 }
1364
1365 pub fn scroll_events(mut self) -> Node {
1366 if let Node::Scrollable { report_scroll, .. } = &mut self {
1367 *report_scroll = true;
1368 }
1369 self
1370 }
1371
1372 pub fn unstable_element(element: impl Into<UnstableElement>) -> Node {
1373 Node::UnstableElement(element.into())
1374 }
1375
1376 pub fn unstable(
1377 builder: impl Fn() -> iced::Element<'static, ItemMessage> + Send + Sync + 'static,
1378 ) -> Node {
1379 Node::UnstableElement(UnstableElement::new(builder))
1380 }
1381
1382 pub fn placeholder(mut self, placeholder: impl Into<RString>) -> Node {
1383 let placeholder = placeholder.into();
1384 if let Node::TextInput {
1385 placeholder: target,
1386 ..
1387 } = &mut self
1388 {
1389 *target = placeholder;
1390 }
1391 self
1392 }
1393
1394 pub fn secure(mut self, secure: bool) -> Node {
1395 if let Node::TextInput { secure: target, .. } = &mut self {
1396 *target = secure;
1397 }
1398 self
1399 }
1400
1401 pub fn value(mut self, value: impl Into<RString>) -> Node {
1402 let value = value.into();
1403 if let Node::TextInput { value: target, .. } = &mut self {
1404 *target = value;
1405 }
1406 self
1407 }
1408
1409 pub fn classes(mut self, classes: &[&str]) -> Node {
1410 let owned: RVec<RString> = classes.iter().map(|c| RString::from(*c)).collect();
1411 match &mut self {
1412 Node::Row { classes: c, .. }
1413 | Node::Column { classes: c, .. }
1414 | Node::Container { classes: c, .. }
1415 | Node::Clickable { classes: c, .. }
1416 | Node::Scrollable { classes: c, .. }
1417 | Node::Icon { classes: c, .. }
1418 | Node::Text { classes: c, .. }
1419 | Node::Progress { classes: c, .. }
1420 | Node::Image { classes: c, .. }
1421 | Node::Slider { classes: c, .. }
1422 | Node::Stack { classes: c, .. }
1423 | Node::Canvas { classes: c, .. }
1424 | Node::TextInput { classes: c, .. }
1425 | Node::EventWrapper { classes: c, .. } => *c = owned,
1426 Node::UnstableElement(unstable) => unstable.classes = owned,
1427 Node::Animated { child, .. } => {
1428 let child = std::mem::replace(child, RBox::new(Node::text(RString::new())));
1429 return RBox::into_inner(child).classes(classes);
1430 }
1431 }
1432 self
1433 }
1434
1435 pub fn own_classes(&self) -> RVec<RString> {
1436 match self {
1437 Node::Row { classes, .. }
1438 | Node::Column { classes, .. }
1439 | Node::Container { classes, .. }
1440 | Node::Clickable { classes, .. }
1441 | Node::Scrollable { classes, .. }
1442 | Node::Icon { classes, .. }
1443 | Node::Text { classes, .. }
1444 | Node::Progress { classes, .. }
1445 | Node::Image { classes, .. }
1446 | Node::Slider { classes, .. }
1447 | Node::Stack { classes, .. }
1448 | Node::Canvas { classes, .. }
1449 | Node::TextInput { classes, .. }
1450 | Node::EventWrapper { classes, .. } => classes.clone(),
1451 Node::UnstableElement(unstable) => unstable.classes.clone(),
1452 Node::Animated { child, .. } => child.own_classes(),
1453 }
1454 }
1455
1456 pub fn cache_key(mut self, key: u64) -> Node {
1457 match &mut self {
1458 Node::Canvas { cache_key, .. } => *cache_key = key,
1459 Node::Image {
1460 image: Image::Rgba { cache_key, .. } | Image::Encoded { cache_key, .. },
1461 ..
1462 } => *cache_key = key,
1463 _ => {}
1464 }
1465 self
1466 }
1467
1468 pub fn size(mut self, size: f32) -> Node {
1469 match &mut self {
1470 Node::Icon { size: target, .. }
1471 | Node::Text { size: target, .. }
1472 | Node::TextInput { size: target, .. } => *target = size,
1473 _ => {}
1474 }
1475 self
1476 }
1477
1478 pub fn color(mut self, color: Color) -> Node {
1479 match &mut self {
1480 Node::Icon { color: target, .. }
1481 | Node::Text { color: target, .. }
1482 | Node::TextInput { color: target, .. }
1483 | Node::Progress { color: target, .. } => *target = ROption::RSome(color),
1484 _ => {}
1485 }
1486 self
1487 }
1488
1489 pub fn width(mut self, width: Length) -> Node {
1490 match &mut self {
1491 Node::Container { width: target, .. }
1492 | Node::Scrollable { width: target, .. }
1493 | Node::Image { width: target, .. }
1494 | Node::Progress { width: target, .. }
1495 | Node::Slider { width: target, .. }
1496 | Node::Stack { width: target, .. }
1497 | Node::TextInput { width: target, .. }
1498 | Node::Canvas { width: target, .. } => *target = width,
1499 _ => {}
1500 }
1501 self
1502 }
1503
1504 pub fn height(mut self, height: Length) -> Node {
1505 match &mut self {
1506 Node::Container { height: target, .. }
1507 | Node::Scrollable { height: target, .. }
1508 | Node::Image { height: target, .. }
1509 | Node::Progress { height: target, .. }
1510 | Node::Stack { height: target, .. }
1511 | Node::Canvas { height: target, .. } => *target = height,
1512 _ => {}
1513 }
1514 self
1515 }
1516
1517 pub fn action(self, command: impl Into<RString>) -> Node {
1518 match self {
1519 Node::Clickable {
1520 child,
1521 effect,
1522 action: _,
1523 classes,
1524 } => Node::Clickable {
1525 child,
1526 action: ROption::RSome(command.into()),
1527 effect,
1528 classes,
1529 },
1530 other => {
1531 let classes = other.own_classes();
1532 Node::Clickable {
1533 child: RBox::new(other),
1534 action: ROption::RSome(command.into()),
1535 effect: ROption::RNone,
1536 classes,
1537 }
1538 }
1539 }
1540 }
1541
1542 pub fn effect(self, value: [usize; 4]) -> Node {
1543 match self {
1544 Node::Clickable {
1545 child,
1546 action,
1547 effect: _,
1548 classes,
1549 } => Node::Clickable {
1550 child,
1551 action,
1552 effect: ROption::RSome(value),
1553 classes,
1554 },
1555 other => {
1556 let classes = other.own_classes();
1557 Node::Clickable {
1558 child: RBox::new(other),
1559 action: ROption::RNone,
1560 effect: ROption::RSome(value),
1561 classes,
1562 }
1563 }
1564 }
1565 }
1566
1567 pub fn animated(self, animation: Animation) -> Node {
1568 Node::Animated {
1569 child: RBox::new(self),
1570 animation,
1571 }
1572 }
1573
1574 pub fn slide_in(self, offset: (f32, f32)) -> Node {
1575 self.animated(Animation::slide(offset))
1576 }
1577
1578 pub fn slide_in_edge(self, edge: AnimationEdge, distance: f32) -> Node {
1579 self.animated(Animation::slide_in_edge(edge, distance))
1580 }
1581
1582 pub fn slide_out_edge(self, edge: AnimationEdge, distance: f32) -> Node {
1583 self.animated(Animation::slide_out_edge(edge, distance))
1584 }
1585
1586 pub fn fade_in(self) -> Node {
1587 self.animated(Animation::fade_in())
1588 }
1589
1590 pub fn fade_out(self) -> Node {
1591 self.animated(Animation::fade_out())
1592 }
1593
1594 pub fn tween(self) -> Node {
1595 self.animated(Animation::tween())
1596 }
1597
1598 pub fn custom_animation(self, func: extern "C" fn(f32) -> AnimationOffset) -> Node {
1599 self.animated(Animation::custom(func))
1600 }
1601
1602 pub fn spring(mut self, motion: impl Into<SpringMotion>) -> Node {
1603 if let Node::Animated { animation, .. } = &mut self {
1604 *animation = animation.with_spring(motion);
1605 }
1606 self
1607 }
1608
1609 pub fn transition(mut self, transition: impl Into<Transition>) -> Node {
1610 if let Node::Animated { animation, .. } = &mut self {
1611 *animation = animation.with_transition(transition);
1612 }
1613 self
1614 }
1615
1616 pub fn duration(mut self, millis: u32) -> Node {
1617 if let Node::Animated { animation, .. } = &mut self {
1618 *animation = animation.duration(millis);
1619 }
1620 self
1621 }
1622
1623 pub fn bezier(mut self, bezier: CubicBezier, millis: u32) -> Node {
1624 if let Node::Animated { animation, .. } = &mut self {
1625 *animation = animation
1626 .duration(millis)
1627 .curve(AnimationCurve::Bezier(bezier));
1628 }
1629 self
1630 }
1631
1632 pub fn custom_curve(mut self, func: extern "C" fn(f32) -> f32, millis: u32) -> Node {
1633 if let Node::Animated { animation, .. } = &mut self {
1634 *animation = animation.duration(millis).custom_curve(func);
1635 }
1636 self
1637 }
1638
1639 pub fn animation_key(mut self, key: u64) -> Node {
1640 if let Node::Animated { animation, .. } = &mut self {
1641 animation.key = key;
1642 }
1643 self
1644 }
1645}
1646
1647#[repr(C)]
1648#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq)]
1649pub struct RenderableSettings {
1650 pub wrap_popup: bool,
1651 pub width: f32,
1652 pub height: f32,
1653}
1654
1655impl RenderableSettings {
1656 pub const fn popup(width: f32, height: f32) -> Self {
1657 Self {
1658 wrap_popup: true,
1659 width,
1660 height,
1661 }
1662 }
1663}
1664
1665#[repr(C)]
1666#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
1667pub enum Layer {
1668 Background,
1669 Bottom,
1670 #[default]
1671 Top,
1672 Overlay,
1673}
1674
1675#[repr(C)]
1676#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
1677pub struct Anchor(pub u32);
1678
1679impl Anchor {
1680 pub const TOP: Self = Self(1);
1681 pub const BOTTOM: Self = Self(2);
1682 pub const LEFT: Self = Self(4);
1683 pub const RIGHT: Self = Self(8);
1684 pub const NONE: Self = Self(0);
1685
1686 pub fn contains(self, other: Self) -> bool {
1687 self.0 & other.0 == other.0
1688 }
1689
1690 pub fn union(self, other: Self) -> Self {
1691 Self(self.0 | other.0)
1692 }
1693}
1694
1695impl core::ops::BitOr for Anchor {
1696 type Output = Anchor;
1697
1698 fn bitor(self, rhs: Anchor) -> Anchor {
1699 Self(self.0 | rhs.0)
1700 }
1701}
1702
1703#[repr(C)]
1704#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
1705pub enum KeyboardInteractivity {
1706 #[default]
1707 None,
1708 OnDemand,
1709 Exclusive,
1710}
1711
1712#[repr(C)]
1713#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
1714pub enum Visibility {
1715 #[default]
1716 Visible,
1717 Transient,
1718 Toggleable(bool),
1719}
1720
1721#[repr(C)]
1722#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
1723pub enum UpdateWhen {
1724 #[default]
1725 OnDemand,
1726 EveryFrame,
1727 OnEvent,
1728}
1729
1730#[repr(C)]
1731#[derive(StableAbi, Clone, Copy, Debug, Default, PartialEq, Eq)]
1732pub enum BlurOption {
1733 #[default]
1734 None,
1735 Full,
1736}
1737
1738#[repr(C)]
1739#[derive(StableAbi, Clone, Debug, PartialEq)]
1740pub struct DesktopSettings {
1741 pub layer: Layer,
1742 pub anchor: Anchor,
1743 pub width: u32,
1744 pub height: u32,
1745 pub margin: [i32; 4],
1746 pub exclusive_zone: i32,
1747 pub events_transparent: bool,
1748 pub keyboard_interactivity: KeyboardInteractivity,
1749 pub per_monitor: bool,
1750 pub monitor: RString,
1751 pub namespace: RString,
1752 pub visibility: Visibility,
1753 pub update_when: UpdateWhen,
1754 pub blur: BlurOption,
1755}
1756
1757impl Default for DesktopSettings {
1758 fn default() -> Self {
1759 Self {
1760 layer: Layer::Top,
1761 anchor: Anchor::NONE,
1762 width: 220,
1763 height: 80,
1764 margin: [0; 4],
1765 exclusive_zone: 0,
1766 events_transparent: false,
1767 keyboard_interactivity: KeyboardInteractivity::None,
1768 per_monitor: false,
1769 monitor: RString::new(),
1770 namespace: RString::new(),
1771 visibility: Visibility::Visible,
1772 update_when: UpdateWhen::OnDemand,
1773 blur: BlurOption::None,
1774 }
1775 }
1776}
1777
1778#[repr(C)]
1779#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1780pub struct Monitor {
1781 pub name: RString,
1782 pub width: u32,
1783 pub height: u32,
1784 pub scale: f32,
1785}
1786
1787#[repr(C)]
1788#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1789pub struct Workspace {
1790 pub id: u64,
1791 pub idx: u8,
1792 pub name: ROption<RString>,
1793 pub output: ROption<RString>,
1794 pub is_active: bool,
1795 pub is_focused: bool,
1796 pub is_urgent: bool,
1797}
1798
1799#[repr(C)]
1800#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1801pub struct Window {
1802 pub id: u64,
1803 pub title: RString,
1804 pub class: RString,
1805}
1806
1807#[repr(C)]
1808#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1809pub struct CompositorState {
1810 pub monitors: RVec<Monitor>,
1811 pub workspaces: RVec<Workspace>,
1812 pub active_window: ROption<Window>,
1813 pub overview_active: bool,
1814}
1815
1816#[repr(C)]
1817#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1818pub struct AudioSink {
1819 pub id: u32,
1820 pub name: RString,
1821 pub description: RString,
1822 pub volume: f32,
1823 pub muted: bool,
1824 pub is_default: bool,
1825}
1826
1827#[repr(C)]
1828#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1829pub struct MprisPlayer {
1830 pub identity: RString,
1831 pub title: RString,
1832 pub artist: RString,
1833 pub album: RString,
1834 pub art_url: ROption<RString>,
1835 pub playback_status: RString,
1836 pub can_play_pause: bool,
1837 pub can_go_next: bool,
1838 pub can_go_previous: bool,
1839}
1840
1841#[repr(C)]
1842#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1843pub struct AudioState {
1844 pub volume: f32,
1845 pub muted: bool,
1846 pub default_sink: ROption<RString>,
1847 pub sinks: RVec<AudioSink>,
1848 pub player: ROption<MprisPlayer>,
1849}
1850
1851#[repr(C)]
1852#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1853pub struct BluetoothDevice {
1854 pub address: RString,
1855 pub name: RString,
1856 pub icon: ROption<RString>,
1857 pub paired: bool,
1858 pub connected: bool,
1859 pub trusted: bool,
1860 pub battery: ROption<u8>,
1861 pub rssi: ROption<i16>,
1862}
1863
1864#[repr(C)]
1865#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1866pub struct BluetoothState {
1867 pub powered: bool,
1868 pub discovering: bool,
1869 pub adapter_name: ROption<RString>,
1870 pub devices: RVec<BluetoothDevice>,
1871}
1872
1873#[repr(C)]
1874#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1875pub struct AccessPoint {
1876 pub ssid: RString,
1877 pub signal: u8,
1878 pub secured: bool,
1879 pub in_use: bool,
1880 pub saved: bool,
1881}
1882
1883#[repr(C)]
1884#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1885pub struct Ethernet {
1886 pub iface: RString,
1887 pub speed: u32,
1888 pub carrier: bool,
1889 pub connected: bool,
1890}
1891
1892#[repr(C)]
1893#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1894pub struct ConnectionInfo {
1895 pub label: RString,
1896 pub mac: RString,
1897 pub is_wifi: bool,
1898}
1899
1900#[repr(C)]
1901#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1902pub struct NetworkState {
1903 pub wifi_enabled: bool,
1904 pub wifi_hardware_enabled: bool,
1905 pub scanning: bool,
1906 pub busy: bool,
1907 pub wifi_connected: ROption<AccessPoint>,
1908 pub ethernet: ROption<Ethernet>,
1909 pub networks: RVec<AccessPoint>,
1910 pub connected: ROption<ConnectionInfo>,
1911}
1912
1913#[repr(C)]
1914#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1915pub struct ProcessInfo {
1916 pub pid: u32,
1917 pub name: RString,
1918 pub cpu_usage: f32,
1919 pub memory: u64,
1920}
1921
1922#[repr(C)]
1923#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1924pub struct SystemState {
1925 pub cpu_usage: f32,
1926 pub mem_usage: f32,
1927 pub mem_total: u64,
1928 pub mem_used: u64,
1929 pub temperature: ROption<f32>,
1930 pub load: [f32; 3],
1931 pub network_state: u16,
1932 pub network_rx: u64,
1933 pub network_tx: u64,
1934 pub processes: RVec<ProcessInfo>,
1935}
1936
1937#[repr(C)]
1938#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1939pub struct TrayPixmap {
1940 pub width: u32,
1941 pub height: u32,
1942 pub data: RVec<u8>,
1943}
1944
1945#[repr(C)]
1946#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1947pub struct TrayItem {
1948 pub address: RString,
1949 pub title: RString,
1950 pub icon_name: ROption<RString>,
1951 pub menu_path: ROption<RString>,
1952 pub pixmap: ROption<TrayPixmap>,
1953}
1954
1955#[repr(C)]
1956#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1957pub struct TrayState {
1958 pub items: RVec<TrayItem>,
1959}
1960
1961#[repr(C)]
1962#[derive(StableAbi, Clone, Debug, Default, PartialEq)]
1963pub struct PowerState {
1964 pub percent: ROption<u8>,
1965 pub charging: bool,
1966 pub status: RString,
1967 pub health: ROption<u8>,
1968 pub energy_now_wh: ROption<f32>,
1969 pub energy_full_wh: ROption<f32>,
1970 pub power_w: ROption<f32>,
1971 pub time_remaining: ROption<RString>,
1972 pub active_profile: ROption<RString>,
1973 pub available_profiles: RVec<RString>,
1974 pub brightness_percent: u8,
1975 pub brightness_max: u32,
1976 pub brightness_current: u32,
1977 pub device_name: RString,
1978}
1979
1980#[repr(C)]
1981#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
1982pub struct NotificationAction {
1983 pub label: RString,
1984 pub command: RString,
1985}
1986
1987#[repr(C)]
1988#[derive(StableAbi, Clone, Debug, PartialEq, Eq)]
1989pub struct Notification {
1990 pub summary: RString,
1991 pub body: RString,
1992 pub app_name: ROption<RString>,
1993 pub app_icon: ROption<RString>,
1994 pub urgency: u8,
1995 pub timeout_ms: i32,
1996 pub actions: RVec<NotificationAction>,
1997}
1998
1999impl Default for Notification {
2000 fn default() -> Self {
2001 Self {
2002 summary: RString::new(),
2003 body: RString::new(),
2004 app_name: ROption::RNone,
2005 app_icon: ROption::RNone,
2006 urgency: 1,
2007 timeout_ms: -1,
2008 actions: RVec::new(),
2009 }
2010 }
2011}
2012
2013impl Notification {
2014 pub fn new(summary: impl Into<RString>, body: impl Into<RString>) -> Self {
2015 Self {
2016 summary: summary.into(),
2017 body: body.into(),
2018 ..Default::default()
2019 }
2020 }
2021
2022 pub fn app_name(mut self, app_name: impl Into<RString>) -> Self {
2023 self.app_name = ROption::RSome(app_name.into());
2024 self
2025 }
2026
2027 pub fn icon(mut self, icon: impl Into<RString>) -> Self {
2028 self.app_icon = ROption::RSome(icon.into());
2029 self
2030 }
2031
2032 pub fn urgency(mut self, urgency: u8) -> Self {
2033 self.urgency = urgency;
2034 self
2035 }
2036
2037 pub fn timeout(mut self, millis: i32) -> Self {
2038 self.timeout_ms = millis;
2039 self
2040 }
2041
2042 pub fn action(mut self, label: impl Into<RString>, command: impl Into<RString>) -> Self {
2043 self.actions.push(NotificationAction {
2044 label: label.into(),
2045 command: command.into(),
2046 });
2047 self
2048 }
2049}
2050
2051#[repr(C)]
2052#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
2053pub struct SpotlightResult {
2054 pub icon: ROption<RString>,
2055 pub title: RString,
2056 pub subtitle: ROption<RString>,
2057 pub action_label: ROption<RString>,
2058 pub image: ROption<RString>,
2059 pub tags: RVec<RString>,
2060}
2061
2062pub mod display_style {
2063 pub const LIST: u32 = 1;
2064 pub const GRID: u32 = 2;
2065 pub const IMAGE_LIST: u32 = 4;
2066}
2067
2068#[repr(C)]
2069#[derive(StableAbi, Clone, Copy, Debug, PartialEq, Eq)]
2070pub enum DisplayStyle {
2071 List,
2072 Grid,
2073 ImageList,
2074}
2075
2076impl DisplayStyle {
2077 pub const fn bit(self) -> u32 {
2078 match self {
2079 Self::List => display_style::LIST,
2080 Self::Grid => display_style::GRID,
2081 Self::ImageList => display_style::IMAGE_LIST,
2082 }
2083 }
2084}
2085
2086#[repr(C)]
2087#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
2088pub enum TileAction {
2089 #[default]
2090 None,
2091 Command(RString),
2092 Commands(RVec<RString>),
2093 Menu(RString),
2094 Expand,
2095}
2096
2097#[repr(C)]
2098#[derive(StableAbi, Clone, Debug, Default, PartialEq, Eq)]
2099pub struct QuickSettingsTile {
2100 pub id: RString,
2101 pub icon: RString,
2102 pub title: RString,
2103 pub status: RString,
2104 pub detail: RString,
2105 pub active: bool,
2106 pub accent: RString,
2107 pub on_click: TileAction,
2108 pub order: i32,
2109}
2110
2111impl QuickSettingsTile {
2112 pub fn new(id: impl Into<RString>, icon: impl Into<RString>, title: impl Into<RString>) -> Self {
2113 Self {
2114 id: id.into(),
2115 icon: icon.into(),
2116 title: title.into(),
2117 order: 100,
2118 ..Default::default()
2119 }
2120 }
2121
2122 pub fn status(mut self, status: impl Into<RString>) -> Self {
2123 self.status = status.into();
2124 self
2125 }
2126
2127 pub fn active(mut self, active: bool) -> Self {
2128 self.active = active;
2129 self
2130 }
2131
2132 pub fn action(mut self, command: impl Into<RString>) -> Self {
2133 self.on_click = TileAction::Command(command.into());
2134 self
2135 }
2136
2137 pub fn commands<I, S>(mut self, commands: I) -> Self
2138 where
2139 I: IntoIterator<Item = S>,
2140 S: Into<RString>,
2141 {
2142 self.on_click = TileAction::Commands(commands.into_iter().map(Into::into).collect());
2143 self
2144 }
2145
2146 pub fn menu(mut self, menu: impl Into<RString>) -> Self {
2147 self.on_click = TileAction::Menu(menu.into());
2148 self
2149 }
2150
2151 pub fn expandable(mut self) -> Self {
2152 self.on_click = TileAction::Expand;
2153 self
2154 }
2155
2156 pub fn accent(mut self, key: impl Into<RString>) -> Self {
2157 self.accent = key.into();
2158 self
2159 }
2160
2161 pub fn order(mut self, order: i32) -> Self {
2162 self.order = order;
2163 self
2164 }
2165}
2166
2167#[cfg(test)]
2168mod tests {
2169 use super::*;
2170
2171 extern "C" fn sample_custom_offset(progress: f32) -> AnimationOffset {
2172 AnimationOffset::new(progress * 10.0, (1.0 - progress) * 20.0)
2173 }
2174
2175 extern "C" fn sample_custom_curve(progress: f32) -> f32 {
2176 progress * progress
2177 }
2178
2179 #[test]
2180 fn test_slide_animation() {
2181 let anim = Animation::slide((15.0, 25.0));
2182 assert_eq!(anim.kind, AnimationKind::Slide);
2183 assert_eq!(anim.offset_x, 15.0);
2184 assert_eq!(anim.offset_y, 25.0);
2185 assert!(matches!(anim.mode, AnimationMode::Transition(_)));
2186 }
2187
2188 #[test]
2189 fn test_iced_anim_spring_selection() {
2190 let anim = Animation::slide((0.0, -30.0)).with_spring(iced_anim::spring::Motion::BOUNCY);
2191 match anim.mode {
2192 AnimationMode::Spring(motion) => {
2193 assert_eq!(motion.damping, 0.7);
2194 assert_eq!(motion.response_ms, 500);
2195 }
2196 _ => panic!("Expected spring mode"),
2197 }
2198
2199 let spring_direct = Animation::spring(iced_anim::spring::Motion::BOUNCY);
2200 assert!(matches!(spring_direct.mode, AnimationMode::Spring(_)));
2201
2202 let smooth_anim = Animation::tween().smooth();
2203 match smooth_anim.mode {
2204 AnimationMode::Spring(motion) => {
2205 assert_eq!(motion.damping, 1.0);
2206 }
2207 _ => panic!("Expected spring mode"),
2208 }
2209
2210 let snappy_anim = Animation::tween().snappy();
2211 match snappy_anim.mode {
2212 AnimationMode::Spring(motion) => {
2213 assert_eq!(motion.damping, 0.85);
2214 }
2215 _ => panic!("Expected spring mode"),
2216 }
2217 }
2218
2219 #[test]
2220 fn test_iced_anim_transition_and_bezier() {
2221 let anim = Animation::tween().bezier(0.2, 0.1, 0.3, 0.9);
2222 match anim.mode {
2223 AnimationMode::Transition(trans) => match trans.curve {
2224 AnimationCurve::Bezier(b) => {
2225 assert_eq!(b.x1, 0.2);
2226 assert_eq!(b.y1, 0.1);
2227 assert_eq!(b.x2, 0.3);
2228 assert_eq!(b.y2, 0.9);
2229 }
2230 _ => panic!("Expected bezier curve"),
2231 },
2232 _ => panic!("Expected transition mode"),
2233 }
2234
2235 let ease_anim = Animation::transition(iced_anim::transition::Easing::EASE_IN_OUT);
2236 match ease_anim.mode {
2237 AnimationMode::Transition(trans) => {
2238 assert_eq!(trans.curve, AnimationCurve::EaseInOut);
2239 }
2240 _ => panic!("Expected transition mode"),
2241 }
2242 }
2243
2244 #[test]
2245 fn test_icss_animations() {
2246 let from_icss = Animation::from_icss(
2247 icss::widgets::animation::AnimKind::SlideIn(icss::widgets::animation::Edge::Bottom),
2248 std::time::Duration::from_millis(250),
2249 );
2250 assert_eq!(
2251 from_icss.kind,
2252 AnimationKind::SlideIn(AnimationEdge::Bottom)
2253 );
2254 assert_eq!(from_icss.duration_ms, 250);
2255
2256 let fade_in = Animation::fade_in();
2257 assert_eq!(fade_in.kind, AnimationKind::FadeIn);
2258
2259 let fade_out = Animation::fade_out();
2260 assert_eq!(fade_out.kind, AnimationKind::FadeOut);
2261
2262 let edge_slide = Animation::slide_out_edge(AnimationEdge::Right, 80.0);
2263 assert_eq!(
2264 edge_slide.kind,
2265 AnimationKind::SlideOut(AnimationEdge::Right)
2266 );
2267 assert_eq!(edge_slide.offset_x, 80.0);
2268 }
2269
2270 #[test]
2271 fn test_custom_plugin_animations() {
2272 let custom = CustomAnimation::new(sample_custom_offset);
2273 let off = custom.offset(0.5);
2274 assert_eq!(off.x, 5.0);
2275 assert_eq!(off.y, 10.0);
2276
2277 let anim = Animation::custom(sample_custom_offset);
2278 match anim.kind {
2279 AnimationKind::Custom(c) => {
2280 assert_eq!(c.offset(1.0).x, 10.0);
2281 }
2282 _ => panic!("Expected custom animation"),
2283 }
2284
2285 let custom_curve_anim = Animation::tween().custom_curve(sample_custom_curve);
2286 match custom_curve_anim.mode {
2287 AnimationMode::Transition(t) => match t.curve {
2288 AnimationCurve::Custom(f) => {
2289 assert_eq!(f(0.5), 0.25);
2290 }
2291 _ => panic!("Expected custom curve"),
2292 },
2293 _ => panic!("Expected transition mode"),
2294 }
2295 }
2296
2297 #[test]
2298 fn test_node_animation_helpers() {
2299 let node = Node::text("hello").fade_in();
2300 match node {
2301 Node::Animated { animation, .. } => {
2302 assert_eq!(animation.kind, AnimationKind::FadeIn);
2303 }
2304 _ => panic!("Expected animated node"),
2305 }
2306
2307 let node2 = Node::icon("gear").slide_in_edge(AnimationEdge::Top, 120.0);
2308 match node2 {
2309 Node::Animated { animation, .. } => {
2310 assert_eq!(animation.kind, AnimationKind::SlideIn(AnimationEdge::Top));
2311 assert_eq!(animation.offset_y, 120.0);
2312 }
2313 _ => panic!("Expected animated node"),
2314 }
2315
2316 let node3 = Node::container(Node::text("custom")).custom_animation(sample_custom_offset);
2317 match node3 {
2318 Node::Animated { animation, .. } => match animation.kind {
2319 AnimationKind::Custom(c) => {
2320 assert_eq!(c.offset(0.0).y, 20.0);
2321 }
2322 _ => panic!("Expected custom animation"),
2323 },
2324 _ => panic!("Expected animated node"),
2325 }
2326 }
2327
2328 #[test]
2329 fn test_unstable_element_from_element() {
2330 let elem: iced::Element<'static, ItemMessage> = iced::widget::text("hello").into();
2331 let node = Node::unstable_element(elem).classes(&["test-class"]);
2332
2333 match &node {
2334 Node::UnstableElement(unstable) => {
2335 assert_eq!(unstable.classes.len(), 1);
2336 assert_eq!(unstable.classes[0].as_str(), "test-class");
2337 let built = unstable.build();
2338 let _ = built;
2339 }
2340 _ => panic!("Expected UnstableElement variant"),
2341 }
2342 assert_eq!(node.own_classes().len(), 1);
2343 }
2344
2345 #[test]
2346 fn test_unstable_element_builder() {
2347 let node = Node::unstable(|| iced::widget::text("builder").into());
2348 match &node {
2349 Node::UnstableElement(unstable) => {
2350 let first = unstable.build();
2351 let second = unstable.build();
2352 let _ = (first, second);
2353 }
2354 _ => panic!("Expected UnstableElement variant"),
2355 }
2356 }
2357
2358 #[test]
2359 fn test_unstable_element_conversions() {
2360 let elem: iced::Element<'static, ItemMessage> = iced::widget::text("conversion").into();
2361 let from_elem: Node = elem.into();
2362 assert!(matches!(from_elem, Node::UnstableElement(_)));
2363
2364 let unstable_elem = UnstableElement::new(|| iced::widget::text("closure").into());
2365 let from_unstable: Node = unstable_elem.into();
2366 assert!(matches!(from_unstable, Node::UnstableElement(_)));
2367 }
2368}