Skip to main content

nifty_interp/
lower.rs

1//! Turns a checked program's windows and views into the runtime's templates, whose dynamic
2//! parts point back into the checked code for the interpreter to evaluate.
3
4use std::rc::Rc;
5
6use nifty_check::hir::{
7    Binding, Block, Dynamic, Expr, NavItem, Node, Nodes, Program, StateSet, ViewDef, WindowDef,
8};
9use nifty_runtime::{
10    AppTemplate, MenuBar, Template, Templates, ToggleStyle, Widget, WindowTemplate,
11};
12
13use crate::logic::{Interpreter, Region, ViewTemplate};
14use crate::machine::new_frame;
15
16/// The program's windows and menus.
17pub(crate) fn app(program: &Program) -> AppTemplate<Interpreter> {
18    AppTemplate {
19        windows: program.windows.iter().map(window).collect(),
20        menu_bar: MenuBar {
21            menus: program.menu_bar.menus.clone(),
22            frame: new_frame(program.menu_bar.locals.len()),
23        },
24    }
25}
26
27/// Indexed by `ViewId`, so a view call finds its view's templates.
28pub(crate) fn views(program: &Program) -> Vec<ViewTemplate> {
29    program.views.iter().map(view).collect()
30}
31
32fn window(window: &WindowDef) -> WindowTemplate<Interpreter> {
33    WindowTemplate {
34        title: window.title.clone(),
35        size: window.size,
36        backdrop: window.backdrop,
37        style: window.style,
38        children: nodes(&window.children),
39        frame: Rc::new(new_frame(window.locals.len())),
40        starts_open: window.starts_open,
41    }
42}
43
44fn view(view: &ViewDef) -> ViewTemplate {
45    ViewTemplate {
46        body: nodes(&view.body),
47        locals: view.locals.len(),
48    }
49}
50
51fn nodes(nodes: &Nodes) -> Templates<Interpreter> {
52    nodes.iter().map(node).collect()
53}
54
55fn node(node: &Node) -> Template<Interpreter> {
56    match node {
57        Node::Stack {
58            style,
59            children,
60            scrolls,
61        } => Template::Stack {
62            style: *style,
63            children: nodes(children),
64            scrolls: *scrolls,
65        },
66        Node::If {
67            branches,
68            otherwise,
69            deps,
70        } => region(if_region(branches, otherwise), deps),
71        Node::For { head, body, deps } => region(
72            Region::For {
73                head: Rc::clone(head),
74                body: nodes(body),
75            },
76            deps,
77        ),
78        Node::View { view, args, deps } => region(
79            Region::View {
80                view: *view,
81                args: Rc::clone(args),
82            },
83            deps,
84        ),
85        control => widget_template(control),
86    }
87}
88
89/// A control's template, with the state what it shows depends on.
90fn widget_template(node: &Node) -> Template<Interpreter> {
91    match node {
92        Node::Label { text, style } => widget(
93            Widget::Label {
94                text: Rc::clone(text),
95                style: *style,
96            },
97            text.deps.clone(),
98        ),
99        Node::Button {
100            title,
101            style,
102            icon,
103            selected,
104            action,
105        } => {
106            let mut deps = title.deps.clone();
107            if let Some(selected) = selected {
108                deps.extend(&selected.deps);
109            }
110            widget(
111                Widget::Button {
112                    title: Rc::clone(title),
113                    style: *style,
114                    icon: *icon,
115                    selected: selected.clone(),
116                    action: Rc::clone(action),
117                },
118                deps,
119            )
120        }
121        Node::TextField {
122            value,
123            placeholder,
124            lines,
125            action,
126        } => widget(
127            Widget::TextField {
128                value: Rc::clone(value),
129                placeholder: placeholder.clone(),
130                lines: *lines,
131                action: action.clone(),
132            },
133            value.deps.clone(),
134        ),
135        Node::Checkbox {
136            checked,
137            title,
138            style,
139        } => checkbox(checked, title.as_ref(), *style),
140        Node::Slider { value, min, max } => widget(
141            Widget::Slider {
142                value: Rc::clone(value),
143                min: *min,
144                max: *max,
145            },
146            value.deps.clone(),
147        ),
148        Node::Picker {
149            selection,
150            options,
151            deps,
152        } => widget(
153            Widget::Picker {
154                selection: Rc::clone(selection),
155                options: Rc::clone(options),
156            },
157            deps.clone(),
158        ),
159        Node::NavItem(item) => nav_item(item),
160        Node::Image { path, size } => widget(
161            Widget::Image {
162                path: Rc::clone(path),
163                size: *size,
164            },
165            path.deps.clone(),
166        ),
167        Node::Progress { value } => widget(
168            Widget::Progress {
169                value: value.clone(),
170            },
171            value
172                .as_ref()
173                .map_or_else(StateSet::default, |value| value.deps.clone()),
174        ),
175        Node::Spacer => widget(Widget::Spacer, StateSet::default()),
176        Node::Divider => widget(Widget::Divider, StateSet::default()),
177        Node::ContextMenu { entries } => widget(
178            Widget::ContextMenu {
179                entries: entries.clone(),
180            },
181            StateSet::default(),
182        ),
183        Node::DropTarget { dropped, action } => drop_target(dropped, action.as_ref()),
184        Node::Stack { .. } | Node::If { .. } | Node::For { .. } | Node::View { .. } => {
185            unreachable!("`node` lowers stacks and regions itself")
186        }
187    }
188}
189
190fn if_region(branches: &[(Rc<Expr>, Nodes)], otherwise: &Nodes) -> Region {
191    Region::If {
192        branches: branches
193            .iter()
194            .map(|(condition, body)| (Rc::clone(condition), nodes(body)))
195            .collect(),
196        otherwise: nodes(otherwise),
197    }
198}
199
200/// A drop target, which shows nothing, so no state changes it.
201fn drop_target(dropped: &Rc<Binding>, action: Option<&Block>) -> Template<Interpreter> {
202    widget(
203        Widget::DropTarget {
204            dropped: Rc::clone(dropped),
205            action: action.cloned(),
206        },
207        StateSet::default(),
208    )
209}
210
211fn checkbox(
212    checked: &Rc<Binding>,
213    title: Option<&Rc<Dynamic>>,
214    style: ToggleStyle,
215) -> Template<Interpreter> {
216    let mut deps = checked.deps.clone();
217    if let Some(title) = title {
218        deps.extend(&title.deps);
219    }
220    widget(
221        Widget::Checkbox {
222            checked: Rc::clone(checked),
223            title: title.cloned(),
224            style,
225        },
226        deps,
227    )
228}
229
230fn nav_item(item: &NavItem) -> Template<Interpreter> {
231    widget(
232        Widget::NavItem {
233            title: Rc::clone(&item.title),
234            value: Rc::clone(&item.value),
235            selection: Rc::clone(&item.selection),
236            icon: item.icon,
237            badge: item.badge.clone(),
238        },
239        item.deps.clone(),
240    )
241}
242
243fn widget(widget: Widget<Interpreter>, deps: StateSet) -> Template<Interpreter> {
244    Template::Widget { widget, deps }
245}
246
247fn region(region: Region, deps: &StateSet) -> Template<Interpreter> {
248    Template::Region {
249        region,
250        deps: deps.clone(),
251    }
252}