Skip to main content

nifty_runtime/reactive/
template.rs

1//! What an app's windows are made of, and the [`Logic`] that gives their dynamic parts
2//! their values.
3
4use std::fmt;
5use std::rc::Rc;
6
7use crate::geometry::Size;
8use crate::menu::{Menu, MenuEntry};
9use crate::state::StateSet;
10use crate::style::{Backdrop, ButtonStyle, Icon, ImageSize, LabelStyle, StackStyle, ToggleStyle};
11
12/// The program behind an app's UI. Templates hold its handles to values, bindings, actions
13/// and regions, and the engine asks it to evaluate them against a frame of locals.
14///
15/// The interpreter implements it by walking checked code, and compiled apps by calling
16/// compiled functions.
17pub trait Logic: Sized + 'static {
18    /// The locals visible to a piece of UI, such as loop variables and view parameters.
19    type Frame: 'static;
20    /// A value the UI shows, or reads when the user acts, such as a label's text or a nav
21    /// item's value. Each is read as the type the checker gave it.
22    type Value: Clone + fmt::Debug + 'static;
23    /// A place a text field or checkbox shows, and writes the user's edits to.
24    type Binding: Clone + fmt::Debug + 'static;
25    /// What a button does when clicked.
26    type Action: Clone + fmt::Debug + 'static;
27    /// An `if`, `for` or view call: UI whose shape depends on state.
28    type Region: fmt::Debug + 'static;
29    type Error: 'static;
30
31    /// # Errors
32    /// When evaluating the text fails.
33    fn text(&self, text: &Self::Value, frame: &Self::Frame) -> Result<String, Self::Error>;
34
35    /// # Errors
36    /// When evaluating the number fails.
37    fn number(&self, number: &Self::Value, frame: &Self::Frame) -> Result<i64, Self::Error>;
38
39    /// # Errors
40    /// When evaluating the flag fails.
41    fn flag(&self, flag: &Self::Value, frame: &Self::Frame) -> Result<bool, Self::Error>;
42
43    /// # Errors
44    /// When evaluating the list fails.
45    fn texts(&self, texts: &Self::Value, frame: &Self::Frame) -> Result<Vec<String>, Self::Error>;
46
47    /// # Errors
48    /// When reading the bound place fails.
49    fn read_text(
50        &self,
51        binding: &Self::Binding,
52        frame: &Self::Frame,
53    ) -> Result<String, Self::Error>;
54
55    /// # Errors
56    /// When reading the bound place fails.
57    fn read_checked(
58        &self,
59        binding: &Self::Binding,
60        frame: &Self::Frame,
61    ) -> Result<bool, Self::Error>;
62
63    /// # Errors
64    /// When reading the bound place fails.
65    fn read_number(&self, binding: &Self::Binding, frame: &Self::Frame)
66    -> Result<i64, Self::Error>;
67
68    /// # Errors
69    /// When writing the bound place fails.
70    fn write_text(
71        &self,
72        binding: &Self::Binding,
73        frame: &Self::Frame,
74        text: &str,
75    ) -> Result<(), Self::Error>;
76
77    /// # Errors
78    /// When writing the bound place fails.
79    fn write_checked(
80        &self,
81        binding: &Self::Binding,
82        frame: &Self::Frame,
83        checked: bool,
84    ) -> Result<(), Self::Error>;
85
86    /// # Errors
87    /// When writing the bound place fails.
88    fn write_number(
89        &self,
90        binding: &Self::Binding,
91        frame: &Self::Frame,
92        number: i64,
93    ) -> Result<(), Self::Error>;
94
95    /// # Errors
96    /// When writing the bound place fails.
97    fn write_texts(
98        &self,
99        binding: &Self::Binding,
100        frame: &Self::Frame,
101        texts: Vec<String>,
102    ) -> Result<(), Self::Error>;
103
104    /// # Errors
105    /// When the action fails part-way.
106    fn run(&self, action: &Self::Action, frame: &Self::Frame) -> Result<(), Self::Error>;
107
108    /// What `region` shows now, in order: lists of templates, each with its own frame.
109    ///
110    /// # Errors
111    /// When evaluating what decides the region's contents fails.
112    fn expand(
113        &self,
114        region: &Self::Region,
115        frame: &Rc<Self::Frame>,
116    ) -> Result<Vec<Shown<Self>>, Self::Error>;
117
118    /// The states changed since the last call.
119    fn take_changed(&self) -> StateSet;
120
121    /// What code asked of the app's windows since the last call, in order.
122    fn take_window_requests(&self) -> Vec<WindowRequest>;
123}
124
125/// What code asks of a window. `repr(u32)` because compiled apps pass it to the runtime,
126/// which every platform's C calling convention passes without widening.
127#[derive(Clone, Copy, Debug, PartialEq, Eq)]
128#[repr(u32)]
129pub enum WindowAction {
130    /// Opens the window, or brings it to the front if it is open.
131    Open,
132    /// Closes the window, if it is open.
133    Close,
134}
135
136/// A request for the app's window at `window`, its position among the app's windows.
137#[derive(Clone, Copy, Debug, PartialEq, Eq)]
138pub struct WindowRequest {
139    pub action: WindowAction,
140    pub window: usize,
141}
142
143pub type Templates<L> = Rc<[Template<L>]>;
144
145/// One element of a window's UI, before it is shown.
146pub enum Template<L: Logic> {
147    /// Its children scroll up and down inside it when `scrolls` and they are too tall.
148    Stack {
149        style: StackStyle,
150        children: Templates<L>,
151        scrolls: bool,
152    },
153    /// Updated in place when `deps` change.
154    Widget { widget: Widget<L>, deps: StateSet },
155    /// Rebuilt in place when `deps` change.
156    Region { region: L::Region, deps: StateSet },
157}
158
159pub enum Widget<L: Logic> {
160    Label {
161        text: L::Value,
162        style: LabelStyle,
163    },
164    /// Shown as selected while `selected` holds, when it has one.
165    Button {
166        title: L::Value,
167        style: ButtonStyle,
168        icon: Icon,
169        selected: Option<L::Value>,
170        action: L::Action,
171    },
172    /// Edits text on one line, or on `lines` lines when more. A one-line field with an
173    /// `action` runs it on Return.
174    TextField {
175        value: L::Binding,
176        placeholder: String,
177        lines: u32,
178        action: Option<L::Action>,
179    },
180    Checkbox {
181        checked: L::Binding,
182        title: Option<L::Value>,
183        style: ToggleStyle,
184    },
185    /// Shows the number at `value`, from `min` to `max`, and writes the user's pick there.
186    Slider {
187        value: L::Binding,
188        min: i64,
189        max: i64,
190    },
191    /// Shows `selection` among `options`, a list of text, and writes the user's pick there.
192    Picker {
193        selection: L::Binding,
194        options: L::Value,
195    },
196    /// Selected while `selection` holds `value`; picking it writes `value` there.
197    NavItem {
198        title: L::Value,
199        value: L::Value,
200        selection: L::Binding,
201        icon: Icon,
202        badge: Option<L::Value>,
203    },
204    /// A right-click menu for the stack it is in, whose items run their actions.
205    ContextMenu {
206        entries: Vec<MenuEntry<L::Action>>,
207    },
208    /// Takes files and links dropped on the stack it is in, writing them to `dropped`,
209    /// then runs `action`, if it has one.
210    DropTarget {
211        dropped: L::Binding,
212        action: Option<L::Action>,
213    },
214    /// The image in the file at `path`, shown at `size`.
215    Image {
216        path: L::Value,
217        size: ImageSize,
218    },
219    /// A progress bar at `value` percent, or busy without one.
220    Progress {
221        value: Option<L::Value>,
222    },
223    Spacer,
224    Divider,
225}
226
227/// Part of what a region shows: `templates`, with `frame` as their locals.
228pub struct Shown<L: Logic> {
229    pub templates: Templates<L>,
230    pub frame: Rc<L::Frame>,
231}
232
233/// Everything an app shows: its windows, and the menus of its menu bar.
234pub struct AppTemplate<L: Logic> {
235    pub windows: Vec<WindowTemplate<L>>,
236    pub menu_bar: MenuBar<L>,
237}
238
239/// The app's menus, whose items run their actions with `frame` as their locals.
240pub struct MenuBar<L: Logic> {
241    pub menus: Vec<Menu<L::Action>>,
242    pub frame: L::Frame,
243}
244
245/// A window and the column it shows its content in.
246pub struct WindowTemplate<L: Logic> {
247    pub title: String,
248    pub size: Size,
249    pub backdrop: Backdrop,
250    pub style: StackStyle,
251    pub children: Templates<L>,
252    /// The locals its content starts with, each time it opens.
253    pub frame: Rc<L::Frame>,
254    /// Whether it opens when the app starts, rather than when code opens it.
255    pub starts_open: bool,
256}
257
258impl<L: Logic> fmt::Debug for Template<L> {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        match self {
261            Self::Stack {
262                style,
263                children,
264                scrolls,
265            } => f
266                .debug_struct("Stack")
267                .field("style", style)
268                .field("children", children)
269                .field("scrolls", scrolls)
270                .finish(),
271            Self::Widget { widget, deps } => f
272                .debug_struct("Widget")
273                .field("widget", widget)
274                .field("deps", deps)
275                .finish(),
276            Self::Region { region, deps } => f
277                .debug_struct("Region")
278                .field("region", region)
279                .field("deps", deps)
280                .finish(),
281        }
282    }
283}
284
285impl<L: Logic> fmt::Debug for Widget<L> {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        match self {
288            Self::Label { text, style } => f
289                .debug_struct("Label")
290                .field("text", text)
291                .field("style", style)
292                .finish(),
293            Self::Button {
294                title,
295                style,
296                icon,
297                selected,
298                action,
299            } => f
300                .debug_struct("Button")
301                .field("title", title)
302                .field("style", style)
303                .field("icon", icon)
304                .field("selected", selected)
305                .field("action", action)
306                .finish(),
307            Self::TextField {
308                value,
309                placeholder,
310                lines,
311                action,
312            } => f
313                .debug_struct("TextField")
314                .field("value", value)
315                .field("placeholder", placeholder)
316                .field("lines", lines)
317                .field("action", action)
318                .finish(),
319            Self::Checkbox {
320                checked,
321                title,
322                style,
323            } => f
324                .debug_struct("Checkbox")
325                .field("checked", checked)
326                .field("title", title)
327                .field("style", style)
328                .finish(),
329            Self::Slider { value, min, max } => f
330                .debug_struct("Slider")
331                .field("value", value)
332                .field("min", min)
333                .field("max", max)
334                .finish(),
335            Self::Picker { selection, options } => f
336                .debug_struct("Picker")
337                .field("selection", selection)
338                .field("options", options)
339                .finish(),
340            Self::NavItem {
341                title,
342                value,
343                selection,
344                icon,
345                badge,
346            } => f
347                .debug_struct("NavItem")
348                .field("title", title)
349                .field("value", value)
350                .field("selection", selection)
351                .field("icon", icon)
352                .field("badge", badge)
353                .finish(),
354            Self::ContextMenu { entries } => f
355                .debug_struct("ContextMenu")
356                .field("entries", entries)
357                .finish(),
358            Self::DropTarget { dropped, action } => f
359                .debug_struct("DropTarget")
360                .field("dropped", dropped)
361                .field("action", action)
362                .finish(),
363            Self::Image { path, size } => f
364                .debug_struct("Image")
365                .field("path", path)
366                .field("size", size)
367                .finish(),
368            Self::Progress { value } => f.debug_struct("Progress").field("value", value).finish(),
369            Self::Spacer => f.write_str("Spacer"),
370            Self::Divider => f.write_str("Divider"),
371        }
372    }
373}
374
375impl<L: Logic> fmt::Debug for Shown<L> {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        f.debug_struct("Shown")
378            .field("templates", &self.templates)
379            .finish_non_exhaustive()
380    }
381}
382
383impl<L: Logic> fmt::Debug for AppTemplate<L> {
384    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385        f.debug_struct("AppTemplate")
386            .field("windows", &self.windows)
387            .field("menu_bar", &self.menu_bar)
388            .finish()
389    }
390}
391
392impl<L: Logic> fmt::Debug for MenuBar<L> {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        f.debug_struct("MenuBar")
395            .field("menus", &self.menus)
396            .finish_non_exhaustive()
397    }
398}
399
400impl<L: Logic> fmt::Debug for WindowTemplate<L> {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        f.debug_struct("WindowTemplate")
403            .field("title", &self.title)
404            .field("size", &self.size)
405            .field("backdrop", &self.backdrop)
406            .field("style", &self.style)
407            .field("children", &self.children)
408            .finish_non_exhaustive()
409    }
410}