Skip to main content

rosin_core/
data.rs

1//! Types for UI parameters and text.
2
3use std::{
4    fmt::{self, Write},
5    num::{NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128},
6    ops::Deref,
7    sync::Arc,
8};
9
10use parking_lot::MappedRwLockReadGuard;
11use smallstr::SmallString;
12
13use crate::{prelude::*, reactive::VarReadGuard};
14
15#[doc(hidden)]
16pub type StackString = SmallString<[u8; 64]>;
17
18/// Constructs a [`UIString`] with a custom format string.
19///
20/// Example:
21/// ```rust,ignore
22/// let value: WeakVar<f32> = ...;
23/// let example: UIString = ui_format!(value, "{:.2}");
24/// ```
25#[macro_export]
26macro_rules! ui_format {
27    ($val:expr, $fmt:literal) => {{
28        let var = ($val).clone();
29        UIString::__deferred_stack(move || {
30            use std::fmt::Write;
31            use $crate::data::StackString;
32
33            let v = var.read()?;
34            let mut buf = StackString::new();
35            let _ = write!(&mut buf, $fmt, &*v);
36            Some(buf)
37        })
38    }};
39}
40
41/// A wrapper type that unifies the different storage requirements of a resolved UIString.
42///
43/// It implements `Deref<Target = str>`, so it can be used just like a `&str`.
44pub enum UIStringRef<'a> {
45    /// A static string literal.
46    Borrowed(&'a str),
47    /// A lock guard holding a reference to a resolved localized string.
48    Guard(MappedRwLockReadGuard<'a, str>),
49    /// A lock guard holding a reference to a VarString.
50    VarString(VarReadGuard<'a, String>),
51    /// An owned string on the stack created by formatting.
52    OwnedStackString(StackString),
53    /// An owned string created by user-provided closures.
54    OwnedString(String),
55}
56
57impl<'a> Deref for UIStringRef<'a> {
58    type Target = str;
59
60    fn deref(&self) -> &Self::Target {
61        match self {
62            UIStringRef::Borrowed(s) => s,
63            UIStringRef::Guard(g) => g,
64            UIStringRef::VarString(g) => g.as_str(),
65            UIStringRef::OwnedStackString(s) => s.as_str(),
66            UIStringRef::OwnedString(s) => s.as_str(),
67        }
68    }
69}
70
71impl<'a> From<UIStringRef<'a>> for Box<str> {
72    fn from(value: UIStringRef<'a>) -> Self {
73        match value {
74            UIStringRef::OwnedString(s) => s.into_boxed_str(),
75            UIStringRef::Borrowed(s) => s.to_owned().into_boxed_str(),
76            UIStringRef::Guard(g) => g.to_owned().into_boxed_str(),
77            UIStringRef::VarString(g) => g.to_owned().into_boxed_str(),
78            UIStringRef::OwnedStackString(s) => s.as_str().to_owned().into_boxed_str(),
79        }
80    }
81}
82
83/// A concrete type that represents a string that can be displayed on screen.
84///
85/// Most widgets will accept an `impl Into<UIString>` parameter to receive the text that they should display.
86///
87/// [`Into<UIString>`] has been implemented for:
88///
89/// - `&'static str`, [`String`], [`LocalizedString`]
90/// - [`WeakVar<T>`] where `T` is:
91///
92/// `&'static str`, [`String`], [`f32`], [`f64`], [`bool`], [`char`], [`usize`], [`isize`],
93/// [`u8`], [`u16`], [`u32`], [`u64`], [`u128`],
94/// [`i8`], [`i16`], [`i32`], [`i64`], [`i128`],
95/// [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`],
96/// [`NonZeroU128`], [`NonZeroI8`], [`NonZeroI16`], [`NonZeroI32`],
97/// [`NonZeroI64`], or [`NonZeroI128`].
98///
99/// Every type except `&'static str` and [`String`] will automatically update the screen when changed.
100///
101/// The [`ui_format`] macro can be used to create a [`UIString`] from a value using a custom format string.
102#[derive(Clone, Debug)]
103pub struct UIString(UIStringInner);
104
105#[derive(Clone)]
106enum UIStringInner {
107    Static(&'static str),
108    Owned(String),
109    VarStaticStr(WeakVar<&'static str>),
110    VarString(WeakVar<String>),
111    Localized(LocalizedString),
112    DeferredStack(Arc<dyn Fn() -> Option<StackString> + Send + Sync + 'static>),
113    DeferredHeap(Arc<dyn for<'a> Fn(&'a TranslationMap) -> String + Send + Sync + 'static>),
114}
115
116impl fmt::Debug for UIStringInner {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            UIStringInner::Owned(s) => f.debug_tuple("Owned").field(s).finish(),
120            UIStringInner::Localized(_) => f.debug_tuple("Localized").field(&"<localized>").finish(),
121            UIStringInner::Static(s) => f.debug_tuple("Static").field(s).finish(),
122            UIStringInner::VarString(_) => f.debug_tuple("VarString").field(&"<var>").finish(),
123            UIStringInner::VarStaticStr(_) => f.debug_tuple("VarStaticStr").field(&"<var>").finish(),
124            UIStringInner::DeferredStack(_) => f.debug_tuple("DeferredStack").field(&"<deferred>").finish(),
125            UIStringInner::DeferredHeap(_) => f.debug_tuple("DeferredHeap").field(&"<deferred>").finish(),
126        }
127    }
128}
129
130impl UIString {
131    /// This is used internally to keep [`UIString::resolve`] heap-free for simple string formatting.
132    #[doc(hidden)]
133    pub fn __deferred_stack<F>(f: F) -> Self
134    where
135        F: Fn() -> Option<SmallString<[u8; 64]>> + Send + Sync + 'static,
136    {
137        UIString(UIStringInner::DeferredStack(Arc::new(f)))
138    }
139
140    /// Creates a `UIString` that is computed later during [`UIString::resolve`].
141    pub fn deferred<F>(f: F) -> Self
142    where
143        F: for<'a> Fn(&'a TranslationMap) -> String + Send + Sync + 'static,
144    {
145        UIString(UIStringInner::DeferredHeap(Arc::new(f)))
146    }
147
148    /// Resolves the string to a type that derefs to `&str`.
149    ///
150    /// This keeps the underlying lock active for the lifetime of the returned `UIStringRef`.
151    pub fn resolve<'a>(&'a self, translation_map: &'a TranslationMap) -> Option<UIStringRef<'a>> {
152        match &self.0 {
153            UIStringInner::Owned(s) => Some(UIStringRef::Borrowed(s.as_str())),
154            UIStringInner::Localized(localized_string) => Some(UIStringRef::Guard(localized_string.resolve(translation_map))),
155            UIStringInner::Static(string) => Some(UIStringRef::Borrowed(string)),
156            UIStringInner::VarString(var) => {
157                let guard = var.read()?;
158                Some(UIStringRef::VarString(guard))
159            }
160            UIStringInner::VarStaticStr(var) => {
161                let guard = var.read()?;
162                // Reading registers dependencies; value is 'static so it can be borrowed directly.
163                Some(UIStringRef::Borrowed(*guard))
164            }
165            UIStringInner::DeferredHeap(f) => Some(UIStringRef::OwnedString((f)(translation_map))),
166            UIStringInner::DeferredStack(f) => Some(UIStringRef::OwnedStackString((f)()?)),
167        }
168    }
169}
170
171impl From<&'static str> for UIString {
172    fn from(value: &'static str) -> Self {
173        UIString(UIStringInner::Static(value))
174    }
175}
176
177impl From<String> for UIString {
178    fn from(value: String) -> Self {
179        UIString(UIStringInner::Owned(value))
180    }
181}
182
183impl From<WeakVar<&'static str>> for UIString {
184    fn from(value: WeakVar<&'static str>) -> Self {
185        UIString(UIStringInner::VarStaticStr(value))
186    }
187}
188
189impl From<WeakVar<String>> for UIString {
190    fn from(value: WeakVar<String>) -> Self {
191        UIString(UIStringInner::VarString(value))
192    }
193}
194
195impl From<LocalizedString> for UIString {
196    fn from(value: LocalizedString) -> Self {
197        UIString(UIStringInner::Localized(value))
198    }
199}
200
201/// Sealed trait used to avoid overlapping `From<WeakVar<T>>` impls with the `String` and `&'static str` fast paths.
202mod sealed {
203    pub trait Sealed {}
204}
205
206/// Types that can be converted from `WeakVar<T>` into a formatted `UIString`
207///
208/// This exists to avoid overlap with `WeakVar<String>` and `WeakVar<&'static str>` conversions.
209#[doc(hidden)]
210pub trait UIVarDisplay: sealed::Sealed + fmt::Display + Send + Sync + 'static {}
211impl<T> UIVarDisplay for T where T: sealed::Sealed + fmt::Display + Send + Sync + 'static {}
212
213macro_rules! impl_ui_var_display {
214    ($($ty:ty),* $(,)?) => {
215        $(
216            impl sealed::Sealed for $ty {}
217        )*
218    };
219}
220
221#[rustfmt::skip]
222impl_ui_var_display!(
223    f32, f64,
224    bool, char,
225    usize, isize,
226    u8, u16, u32, u64, u128,
227    i8, i16, i32, i64, i128,
228    NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128,
229    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128,
230);
231
232/// Any `WeakVar<T>` where `T` is in `UIVarDisplay` becomes a fast deferred formatter.
233///
234/// This does one heap alloc when constructing the UIString, and no heap alloc on resolve in
235/// the common case because `SmallString` stays inline for short strings.
236impl<T> From<WeakVar<T>> for UIString
237where
238    T: UIVarDisplay,
239{
240    fn from(var: WeakVar<T>) -> Self {
241        UIString::__deferred_stack(move || {
242            let v = var.read()?;
243            let mut buf = SmallString::<[u8; 64]>::new();
244            let _ = write!(&mut buf, "{}", &*v);
245            Some(buf)
246        })
247    }
248}
249
250/// A type that allows widgets to accept either dynamic or static parameters.
251///
252/// For example:
253///
254/// ```ignore
255/// fn example(param: impl Into<UIParam<bool>>) { ... }
256///
257/// // `example` can be called with a constant
258/// example(true);
259///
260/// // ... or with a reactive variable.
261/// let my_var = Var::new(true);
262/// example(*my_var);
263/// ```
264#[derive(Copy, Clone)]
265pub enum UIParam<T: Send + Sync + 'static> {
266    Static(T),
267    Dynamic(WeakVar<T>),
268}
269
270impl<T: Send + Sync + 'static> From<T> for UIParam<T> {
271    fn from(value: T) -> Self {
272        UIParam::Static(value)
273    }
274}
275
276impl<T: Send + Sync + 'static> From<WeakVar<T>> for UIParam<T> {
277    fn from(var: WeakVar<T>) -> Self {
278        UIParam::Dynamic(var)
279    }
280}
281
282impl<T: Clone + PartialEq + Send + Sync + 'static> UIParam<T> {
283    pub fn with_mut<R>(&mut self, func: impl FnOnce(&mut T) -> R) -> Option<R> {
284        match self {
285            UIParam::Static(value) => Some(func(value)),
286            UIParam::Dynamic(var) => {
287                let mut guard = var.write()?;
288                Some(func(&mut guard))
289            }
290        }
291    }
292
293    pub fn get(&self) -> Option<T> {
294        match self {
295            UIParam::Static(value) => Some(value.clone()),
296            UIParam::Dynamic(var) => var.get(),
297        }
298    }
299
300    pub fn get_or(&self, default: T) -> T {
301        match self {
302            UIParam::Static(value) => value.clone(),
303            UIParam::Dynamic(var) => var.get().unwrap_or(default),
304        }
305    }
306}