Skip to main content

slowshell_plugin_abi/
types.rs

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}