Skip to main content

crabshell_ui/
prop.rs

1//! Widget properties that take either a plain value or a closure.
2
3use std::cell::RefCell;
4use std::path::{Path, PathBuf};
5use std::rc::Rc;
6
7use crate::menu::MenuEntry;
8use crate::style::Style;
9use crate::widgets::{Align, IconSource, Justify};
10use crate::window::{Anchors, Edge, Layer};
11
12/// A widget property: either a value set once, or a binding that reruns
13/// whenever a signal it read changes.
14///
15/// You never build one yourself. Widget methods take `impl IntoProp<T>`, so
16/// `.height(30)` sets a value and `.height(move || ...)` makes a binding.
17pub struct Prop<T>(pub(crate) PropKind<T>);
18
19impl<T: Clone + 'static> Prop<T> {
20  /// The property as a closure that can be called again and shared, as a
21  /// widget built from other widgets hands one property to many of them.
22  /// A binding stays one: whatever calls it subscribes to the signals it
23  /// reads.
24  pub fn shared(self) -> Rc<dyn Fn() -> T> {
25    match self.0 {
26      PropKind::Value(value) => Rc::new(move || value.clone()),
27      PropKind::Binding(compute) => {
28        let compute = RefCell::new(compute);
29        Rc::new(move || (compute.borrow_mut())())
30      }
31    }
32  }
33}
34
35pub(crate) enum PropKind<T> {
36  Value(T),
37  Binding(Box<dyn FnMut() -> T>),
38}
39
40/// Anything a widget property accepts: a value of type `T`, or a closure
41/// that returns one.
42///
43/// Plain values work for the property types this crate uses, and a string
44/// slice works where a `String` is expected. Any `FnMut() -> T` closure
45/// works too: it runs in an effect, so it reruns when a signal it read
46/// changes.
47pub trait IntoProp<T> {
48  /// Wraps the value or closure.
49  fn into_prop(self) -> Prop<T>;
50}
51
52// A closure is a binding. This impl can sit next to the value impls below
53// because Rust knows that no plain type like `u32` implements `FnMut`.
54impl<T, F> IntoProp<T> for F
55where
56  F: FnMut() -> T + 'static,
57{
58  fn into_prop(self) -> Prop<T> {
59    Prop(PropKind::Binding(Box::new(self)))
60  }
61}
62
63/// Lets each listed type be passed as a plain value.
64macro_rules! value_props {
65    ($($ty:ty),* $(,)?) => {
66        $(
67            impl IntoProp<$ty> for $ty {
68                fn into_prop(self) -> Prop<$ty> {
69                    Prop(PropKind::Value(self))
70                }
71            }
72        )*
73    };
74}
75
76value_props!(
77  bool,
78  u32,
79  i32,
80  String,
81  Style,
82  Align,
83  Justify,
84  Anchors,
85  Layer,
86  IconSource,
87  Vec<MenuEntry>,
88);
89
90impl IntoProp<String> for &str {
91  fn into_prop(self) -> Prop<String> {
92    Prop(PropKind::Value(self.to_owned()))
93  }
94}
95
96impl IntoProp<Anchors> for Edge {
97  fn into_prop(self) -> Prop<Anchors> {
98    Prop(PropKind::Value(Anchors::from(self)))
99  }
100}
101
102/// An icon name, such as `"audio-volume-high"`.
103impl IntoProp<IconSource> for &str {
104  fn into_prop(self) -> Prop<IconSource> {
105    Prop(PropKind::Value(IconSource::Name(self.to_owned())))
106  }
107}
108
109/// An icon name, as tray items send them.
110impl IntoProp<IconSource> for String {
111  fn into_prop(self) -> Prop<IconSource> {
112    Prop(PropKind::Value(IconSource::Name(self)))
113  }
114}
115
116/// An SVG or PNG file.
117impl IntoProp<IconSource> for &Path {
118  fn into_prop(self) -> Prop<IconSource> {
119    Prop(PropKind::Value(IconSource::File(self.to_owned())))
120  }
121}
122
123/// An SVG or PNG file.
124impl IntoProp<IconSource> for PathBuf {
125  fn into_prop(self) -> Prop<IconSource> {
126    Prop(PropKind::Value(IconSource::File(self)))
127  }
128}