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  String,
84  Style,
85  Align,
86  Justify,
87  Anchors,
88  Layer,
89  Margins,
90  KeyboardFocus,
91  Option<Region>,
92  PathBuf,
93  Fit,
94  Option<usize>,
95  (u32, u32),
96  Adjust,
97  IconSource,
98  CaptureSource,
99  Vec<MenuEntry>,
100);
101
102impl IntoProp<String> for &str {
103  fn into_prop(self) -> Prop<String> {
104    Prop(PropKind::Value(self.to_owned()))
105  }
106}
107
108impl IntoProp<Option<Region>> for Region {
109  fn into_prop(self) -> Prop<Option<Region>> {
110    Prop(PropKind::Value(Some(self)))
111  }
112}
113
114impl IntoProp<Anchors> for Edge {
115  fn into_prop(self) -> Prop<Anchors> {
116    Prop(PropKind::Value(Anchors::from(self)))
117  }
118}
119
120/// An icon name, such as `"audio-volume-high"`.
121impl IntoProp<IconSource> for &str {
122  fn into_prop(self) -> Prop<IconSource> {
123    Prop(PropKind::Value(IconSource::Name(self.to_owned())))
124  }
125}
126
127/// An icon name, as tray items send them.
128impl IntoProp<IconSource> for String {
129  fn into_prop(self) -> Prop<IconSource> {
130    Prop(PropKind::Value(IconSource::Name(self)))
131  }
132}
133
134impl IntoProp<PathBuf> for &Path {
135  fn into_prop(self) -> Prop<PathBuf> {
136    Prop(PropKind::Value(self.to_owned()))
137  }
138}
139
140impl IntoProp<PathBuf> for &str {
141  fn into_prop(self) -> Prop<PathBuf> {
142    Prop(PropKind::Value(PathBuf::from(self)))
143  }
144}
145
146impl IntoProp<PathBuf> for String {
147  fn into_prop(self) -> Prop<PathBuf> {
148    Prop(PropKind::Value(PathBuf::from(self)))
149  }
150}
151
152/// An SVG or PNG file.
153impl IntoProp<IconSource> for &Path {
154  fn into_prop(self) -> Prop<IconSource> {
155    Prop(PropKind::Value(IconSource::File(self.to_owned())))
156  }
157}
158
159/// An SVG or PNG file.
160impl IntoProp<IconSource> for PathBuf {
161  fn into_prop(self) -> Prop<IconSource> {
162    Prop(PropKind::Value(IconSource::File(self)))
163  }
164}