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