1use 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
16pub(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
27pub(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
89fn 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
200fn 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}