1mod template;
9mod windows;
10
11use std::cell::RefCell;
12use std::fmt;
13use std::rc::{Rc, Weak};
14
15pub use template::{
16 AppTemplate, Logic, MenuBar, Shown, Template, Templates, Widget, WindowAction, WindowRequest,
17 WindowTemplate,
18};
19
20use crate::backend::{
21 Backend, ClickHandler, DropHandler, MenuHandler, NumberHandler, TextHandler, ToggleHandler,
22};
23use crate::menu::{Menu, MenuEntry};
24use crate::state::StateSet;
25use crate::ui::{Node, Position, Ui};
26use windows::{MountedWindow, ReusedWindow};
27
28pub type ErrorHandler<E> = Rc<dyn Fn(E)>;
29
30pub struct Engine<L: Logic, B: Backend> {
32 logic: L,
33 ui: Ui<B>,
34 templates: Vec<WindowTemplate<L>>,
36 windows: RefCell<Vec<Option<MountedWindow<L>>>>,
38 on_error: ErrorHandler<L::Error>,
39 this: Weak<Self>,
40}
41
42struct TemplateRef<L: Logic> {
44 templates: Templates<L>,
45 index: usize,
46}
47
48impl<L: Logic> TemplateRef<L> {
49 fn template(&self) -> &Template<L> {
50 &self.templates[self.index]
51 }
52}
53
54impl<L: Logic> Clone for TemplateRef<L> {
55 fn clone(&self) -> Self {
56 Self {
57 templates: Rc::clone(&self.templates),
58 index: self.index,
59 }
60 }
61}
62
63enum Mounted<L: Logic> {
64 Widget {
66 node: Node,
67 live: Option<(TemplateRef<L>, Rc<L::Frame>)>,
68 },
69 Stack {
70 node: Node,
71 children: Vec<Mounted<L>>,
72 },
73 Region {
75 template: TemplateRef<L>,
76 frame: Rc<L::Frame>,
77 children: Vec<Mounted<L>>,
78 },
79}
80
81impl<L: Logic, B: Backend> Engine<L, B> {
82 pub fn start(
89 ui: Ui<B>,
90 app: AppTemplate<L>,
91 on_error: ErrorHandler<L::Error>,
92 make_logic: impl FnOnce(&Ui<B>, Weak<Self>) -> L,
93 ) -> Result<Rc<Self>, L::Error> {
94 Self::open(ui, app, on_error, make_logic, &[])
95 }
96
97 pub fn reload(
104 &self,
105 app: AppTemplate<L>,
106 make_logic: impl FnOnce(&Ui<B>, Weak<Self>) -> L,
107 ) -> Result<Rc<Self>, L::Error> {
108 let reused: Vec<Option<ReusedWindow>> = self
109 .windows
110 .borrow()
111 .iter()
112 .map(|mounted| mounted.as_ref().map(ReusedWindow::of))
113 .collect();
114 let next = Self::open(
115 self.ui.clone(),
116 app,
117 Rc::clone(&self.on_error),
118 make_logic,
119 &reused,
120 )?;
121
122 self.windows.borrow_mut().clear();
124 let kept = next.templates.len();
125 for surplus in reused.iter().skip(kept).flatten() {
126 self.ui.close(surplus.window());
127 }
128 Ok(next)
129 }
130
131 #[must_use]
132 pub fn logic(&self) -> &L {
133 &self.logic
134 }
135
136 pub fn handle_event(&self, event: impl FnOnce(&L) -> Result<(), L::Error>) {
138 if let Err(error) = event(&self.logic).and_then(|()| self.refresh()) {
139 (self.on_error)(error);
140 }
141 }
142
143 pub fn run(&self) {
145 self.ui.run();
146 }
147
148 fn open(
152 ui: Ui<B>,
153 AppTemplate { windows, menu_bar }: AppTemplate<L>,
154 on_error: ErrorHandler<L::Error>,
155 make_logic: impl FnOnce(&Ui<B>, Weak<Self>) -> L,
156 reused: &[Option<ReusedWindow>],
157 ) -> Result<Rc<Self>, L::Error> {
158 let engine = Rc::new_cyclic(|this: &Weak<Self>| Self {
159 logic: make_logic(&ui, this.clone()),
160 ui,
161 windows: RefCell::new(windows.iter().map(|_| None).collect()),
162 templates: windows,
163 on_error,
164 this: this.clone(),
165 });
166
167 let mut prepared = Vec::new();
168 for (index, opens) in engine.windows_to_open(reused).into_iter().enumerate() {
169 if !opens {
170 continue;
171 }
172 let reused = reused.get(index).copied().flatten();
173 match engine.prepare_window(index, reused) {
174 Ok(window) => prepared.push(window),
175 Err(error) => {
176 engine.discard(prepared);
177 return Err(error);
178 }
179 }
180 }
181
182 engine.show_menus(menu_bar);
184 for window in prepared {
185 engine.show_prepared(window);
186 }
187 engine.ui.set_close_handler(engine.on_close());
188 engine.ui.flush();
189 Ok(engine)
190 }
191
192 fn show_menus(&self, menu_bar: MenuBar<L>) {
194 let mut actions = Vec::new();
195 let menus: Vec<Menu<usize>> = menu_bar
196 .menus
197 .into_iter()
198 .map(|menu| menu.number(&mut actions))
199 .collect();
200 let on_select = self.on_menu(actions, Rc::new(menu_bar.frame));
201 self.ui.set_menu_bar(&menus, on_select);
202 }
203
204 fn on_menu(&self, actions: Vec<L::Action>, frame: Rc<L::Frame>) -> MenuHandler {
206 let engine = self.this.clone();
207 Box::new(move |item| send(&engine, |logic| logic.run(&actions[item], &frame)))
208 }
209
210 fn mount_list(
211 &self,
212 templates: &Templates<L>,
213 frame: &Rc<L::Frame>,
214 parent: Node,
215 offset: &mut usize,
216 ) -> Result<Vec<Mounted<L>>, L::Error> {
217 (0..templates.len())
218 .map(|index| {
219 let template = TemplateRef {
220 templates: Rc::clone(templates),
221 index,
222 };
223 self.mount(template, frame, parent, offset)
224 })
225 .collect()
226 }
227
228 fn mount(
231 &self,
232 template: TemplateRef<L>,
233 frame: &Rc<L::Frame>,
234 parent: Node,
235 offset: &mut usize,
236 ) -> Result<Mounted<L>, L::Error> {
237 match template.template() {
238 Template::Stack {
239 style,
240 children,
241 scrolls,
242 } => {
243 let at = next(parent, offset);
244 let node = if *scrolls {
245 self.ui.scroll(at, style)
246 } else {
247 self.ui.stack(at, style)
248 };
249 let children = self.mount_list(children, frame, node, &mut 0)?;
250 Ok(Mounted::Stack { node, children })
251 }
252 Template::Region { region, .. } => {
253 let children = self.mount_region(region, frame, parent, offset)?;
254 Ok(Mounted::Region {
255 template,
256 frame: Rc::clone(frame),
257 children,
258 })
259 }
260 Template::Widget { widget, deps } => {
261 let node = self.create_widget(next(parent, offset), widget, frame)?;
262 let live = (!deps.is_empty()).then(|| (template, Rc::clone(frame)));
263 Ok(Mounted::Widget { node, live })
264 }
265 }
266 }
267
268 fn create_widget(
269 &self,
270 at: Position,
271 widget: &Widget<L>,
272 frame: &Rc<L::Frame>,
273 ) -> Result<Node, L::Error> {
274 Ok(match widget {
275 Widget::Label { text, style } => {
276 self.ui.label(at, &self.logic.text(text, frame)?, *style)
277 }
278 Widget::Button {
279 title,
280 style,
281 icon,
282 selected,
283 action,
284 } => {
285 let title = self.logic.text(title, frame)?;
286 let selected = selected
287 .as_ref()
288 .map(|selected| self.logic.flag(selected, frame))
289 .transpose()?;
290 let on_click = self.on_click(action.clone(), Rc::clone(frame));
291 self.ui
292 .button(at, &title, (*style, *icon), selected, on_click)
293 }
294 Widget::TextField {
295 value,
296 placeholder,
297 lines,
298 action,
299 } => {
300 let text = self.logic.read_text(value, frame)?;
301 let on_change = self.on_edit(value.clone(), Rc::clone(frame));
302 let on_submit = action
303 .as_ref()
304 .map(|action| self.on_click(action.clone(), Rc::clone(frame)));
305 self.ui
306 .text_field(at, &text, (placeholder, *lines), on_change, on_submit)
307 }
308 Widget::Checkbox {
309 checked,
310 title,
311 style,
312 } => {
313 let is_checked = self.logic.read_checked(checked, frame)?;
314 let title = match title {
315 Some(title) => self.logic.text(title, frame)?,
316 None => String::new(),
317 };
318 let on_toggle = self.on_toggle(checked.clone(), Rc::clone(frame));
319 self.ui.checkbox(at, &title, is_checked, *style, on_toggle)
320 }
321 Widget::Slider { value, min, max } => {
322 let current = self.logic.read_number(value, frame)?;
323 let on_change = self.on_number(value.clone(), Rc::clone(frame));
324 self.ui.slider(at, current, (*min, *max), on_change)
325 }
326 Widget::Picker { selection, options } => {
327 let options = self.logic.texts(options, frame)?;
328 let selected = self.logic.read_text(selection, frame)?;
329 let on_pick = self.on_edit(selection.clone(), Rc::clone(frame));
330 self.ui.picker(at, &options, &selected, on_pick)
331 }
332 Widget::NavItem {
333 title,
334 value,
335 selection,
336 icon,
337 badge,
338 } => {
339 let title = self.logic.text(title, frame)?;
340 let badge = self.badge(badge.as_ref(), frame)?;
341 let selected = self.is_selected(value, selection, frame)?;
342 let on_click = self.on_select(selection.clone(), value.clone(), Rc::clone(frame));
343 self.ui
344 .nav_item(at, &title, *icon, &badge, selected, on_click)
345 }
346 Widget::Image { path, size } => {
347 self.ui.image(at, &self.logic.text(path, frame)?, *size)
348 }
349 Widget::ContextMenu { entries } => {
350 let mut actions = Vec::new();
351 let entries = MenuEntry::number_all(entries.clone(), &mut actions);
352 let on_select = self.on_menu(actions, Rc::clone(frame));
353 self.ui.context_menu(at, &entries, on_select)
354 }
355 Widget::DropTarget { dropped, action } => {
356 let on_drop = self.on_drop(dropped.clone(), action.clone(), Rc::clone(frame));
357 self.ui.drop_target(at, on_drop)
358 }
359 Widget::Progress { value } => {
360 let percent = value
361 .as_ref()
362 .map(|value| self.logic.number(value, frame))
363 .transpose()?;
364 self.ui.progress(at, percent)
365 }
366 Widget::Spacer => self.ui.spacer(at),
367 Widget::Divider => self.ui.divider(at),
368 })
369 }
370
371 fn mount_region(
373 &self,
374 region: &L::Region,
375 frame: &Rc<L::Frame>,
376 parent: Node,
377 offset: &mut usize,
378 ) -> Result<Vec<Mounted<L>>, L::Error> {
379 let mut children = Vec::new();
380 for shown in self.logic.expand(region, frame)? {
381 children.extend(self.mount_list(&shown.templates, &shown.frame, parent, offset)?);
382 }
383 Ok(children)
384 }
385
386 fn refresh(&self) -> Result<(), L::Error> {
389 let changed = self.logic.take_changed();
390 if !changed.is_empty() {
391 let mut windows = self.windows.borrow_mut();
392 for mounted in windows.iter_mut().flatten() {
393 self.refresh_list(&mut mounted.children, mounted.content, &changed, &mut 0)?;
394 }
395 }
396 self.apply_window_requests(&self.logic.take_window_requests())?;
397 self.ui.flush();
398 Ok(())
399 }
400
401 fn refresh_list(
402 &self,
403 items: &mut [Mounted<L>],
404 parent: Node,
405 changed: &StateSet,
406 offset: &mut usize,
407 ) -> Result<(), L::Error> {
408 for item in items {
409 match item {
410 Mounted::Widget { node, live } => {
411 if let Some((template, frame)) = live
412 && let Template::Widget { widget, deps } = template.template()
413 && deps.intersects(changed)
414 {
415 self.update_widget(*node, widget, frame)?;
416 }
417 *offset += 1;
418 }
419 Mounted::Stack { node, children } => {
420 self.refresh_list(children, *node, changed, &mut 0)?;
421 *offset += 1;
422 }
423 Mounted::Region {
424 template,
425 frame,
426 children,
427 } => match template.template() {
428 Template::Region { region, deps } if deps.intersects(changed) => {
429 for child in children.drain(..) {
430 self.unmount(child);
431 }
432 *children = self.mount_region(region, frame, parent, offset)?;
433 }
434 _ => self.refresh_list(children, parent, changed, offset)?,
435 },
436 }
437 }
438 Ok(())
439 }
440
441 fn unmount(&self, mounted: Mounted<L>) {
442 match mounted {
443 Mounted::Widget { node, .. } | Mounted::Stack { node, .. } => self.ui.remove(node),
444 Mounted::Region { children, .. } => {
445 for child in children {
446 self.unmount(child);
447 }
448 }
449 }
450 }
451
452 fn update_widget(
453 &self,
454 node: Node,
455 widget: &Widget<L>,
456 frame: &L::Frame,
457 ) -> Result<(), L::Error> {
458 match widget {
459 Widget::Label { text, .. } => {
460 self.ui.set_text(node, &self.logic.text(text, frame)?);
461 }
462 Widget::Button {
463 title, selected, ..
464 } => {
465 self.ui.set_text(node, &self.logic.text(title, frame)?);
466 if let Some(selected) = selected {
467 self.ui.set_checked(node, self.logic.flag(selected, frame)?);
468 }
469 }
470 Widget::TextField { value, .. } => {
471 self.ui.set_text(node, &self.logic.read_text(value, frame)?);
472 }
473 Widget::Checkbox { checked, title, .. } => {
474 self.ui
475 .set_checked(node, self.logic.read_checked(checked, frame)?);
476 if let Some(title) = title {
477 self.ui.set_text(node, &self.logic.text(title, frame)?);
478 }
479 }
480 Widget::Slider { value, .. } => {
481 self.ui
482 .set_value(node, self.logic.read_number(value, frame)?);
483 }
484 Widget::Picker { selection, options } => {
485 self.ui
486 .set_options(node, &self.logic.texts(options, frame)?);
487 self.ui
488 .set_text(node, &self.logic.read_text(selection, frame)?);
489 }
490 Widget::NavItem {
491 title,
492 value,
493 selection,
494 badge,
495 ..
496 } => {
497 self.ui.set_text(node, &self.logic.text(title, frame)?);
498 self.ui.set_badge(node, &self.badge(badge.as_ref(), frame)?);
499 self.ui
500 .set_checked(node, self.is_selected(value, selection, frame)?);
501 }
502 Widget::Image { path, .. } => {
503 self.ui.set_image(node, &self.logic.text(path, frame)?);
504 }
505 Widget::Progress { value } => {
506 if let Some(value) = value {
507 self.ui.set_progress(node, self.logic.number(value, frame)?);
508 }
509 }
510 Widget::Spacer
511 | Widget::Divider
512 | Widget::ContextMenu { .. }
513 | Widget::DropTarget { .. } => {}
514 }
515 Ok(())
516 }
517
518 fn badge(&self, badge: Option<&L::Value>, frame: &L::Frame) -> Result<String, L::Error> {
519 badge.map_or_else(|| Ok(String::new()), |badge| self.logic.text(badge, frame))
520 }
521
522 fn is_selected(
523 &self,
524 value: &L::Value,
525 selection: &L::Binding,
526 frame: &L::Frame,
527 ) -> Result<bool, L::Error> {
528 Ok(self.logic.read_text(selection, frame)? == self.logic.text(value, frame)?)
529 }
530
531 fn on_click(&self, action: L::Action, frame: Rc<L::Frame>) -> ClickHandler {
532 let engine = self.this.clone();
533 Box::new(move || send(&engine, |logic| logic.run(&action, &frame)))
534 }
535
536 fn on_edit(&self, binding: L::Binding, frame: Rc<L::Frame>) -> TextHandler {
537 let engine = self.this.clone();
538 Box::new(move |text| send(&engine, |logic| logic.write_text(&binding, &frame, text)))
539 }
540
541 fn on_select(
543 &self,
544 selection: L::Binding,
545 value: L::Value,
546 frame: Rc<L::Frame>,
547 ) -> ClickHandler {
548 let engine = self.this.clone();
549 Box::new(move || {
550 send(&engine, |logic| {
551 let value = logic.text(&value, &frame)?;
552 logic.write_text(&selection, &frame, &value)
553 });
554 })
555 }
556
557 fn on_number(&self, binding: L::Binding, frame: Rc<L::Frame>) -> NumberHandler {
558 let engine = self.this.clone();
559 Box::new(move |number| {
560 send(&engine, |logic| {
561 logic.write_number(&binding, &frame, number)
562 });
563 })
564 }
565
566 fn on_drop(
568 &self,
569 dropped: L::Binding,
570 action: Option<L::Action>,
571 frame: Rc<L::Frame>,
572 ) -> DropHandler {
573 let engine = self.this.clone();
574 Box::new(move |items| {
575 send(&engine, |logic| {
576 logic.write_texts(&dropped, &frame, items)?;
577 action
578 .as_ref()
579 .map_or(Ok(()), |action| logic.run(action, &frame))
580 });
581 })
582 }
583
584 fn on_toggle(&self, binding: L::Binding, frame: Rc<L::Frame>) -> ToggleHandler {
585 let engine = self.this.clone();
586 Box::new(move |checked| {
587 send(&engine, |logic| {
588 logic.write_checked(&binding, &frame, checked)
589 });
590 })
591 }
592}
593
594fn send<L: Logic, B: Backend>(
597 engine: &Weak<Engine<L, B>>,
598 event: impl FnOnce(&L) -> Result<(), L::Error>,
599) {
600 if let Some(engine) = engine.upgrade() {
601 engine.handle_event(event);
602 }
603}
604
605fn next(parent: Node, offset: &mut usize) -> Position {
607 let at = Position {
608 parent,
609 index: *offset,
610 };
611 *offset += 1;
612 at
613}
614
615impl<L: Logic, B: Backend> fmt::Debug for Engine<L, B> {
616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617 f.debug_struct("Engine")
618 .field("windows", &self.templates.len())
619 .finish_non_exhaustive()
620 }
621}
622
623#[cfg(test)]
624mod tests;