Skip to main content

yog_cli/yui_vm/
mod.rs

1//! View-model screens: a `.yui` whose `<Screen>` has `x:ViewModel="MyVm"`
2//! compiles into a `yog_kit::View` — the static widget tree (built by
3//! [`crate::yui`]'s generator), compiled `{x:Bind ...}` bindings patching
4//! it, commands, two-way text boxes and toggles, lists with item templates,
5//! and the `register`/`open`/`close` functions. See `docs/mvvm.md` in
6//! Yog-IDLE for the format.
7
8mod bind;
9mod binding;
10mod element;
11mod expr;
12mod list;
13mod state;
14mod target;
15
16use std::fmt::Write as _;
17
18pub(crate) use binding::compile_bind;
19
20use crate::rust_code::{f32_lit, fill};
21use crate::yui::{self, Element, UI_USE, err};
22use expr::Scope;
23use state::{Ctx, SCREEN_OWNER, VIEW_FIELDS, VIEW_TREE, VmGen};
24
25use yog_data::Diagnostic;
26/// The codegen version checked by `yog_kit::__codegen::require`.
27use yog_ids::versions::UI_CODEGEN as CODEGEN;
28
29/// What a binding's apply code calls the new value.
30const NEW_VALUE: &str = "__v";
31/// What generated code calls the widget a binding sets.
32const WIDGET: &str = "__w";
33
34/// The generated code of a view-model screen.
35const TEMPLATE: &str = include_str!("view.g.rs.in");
36
37/// What a view-model screen's root says: its id, view model, how it's made.
38struct Root {
39    id: String,
40    vm: String,
41    create: String,
42}
43
44/// The root's id and view model (and how the view model is made: its
45/// `x:Create`, else its `Default`).
46fn read_root(root: &Element) -> Result<Root, Diagnostic> {
47    if root.name != yog_ids::yui::SCREEN {
48        return err(root, "a view-model screen's root must be <Screen>");
49    }
50    let id = root
51        .attr(yog_ids::yui_attr::ID)
52        .filter(|s| !s.trim().is_empty())
53        .ok_or_else(|| {
54            Diagnostic::from("<Screen> needs an id (the UI id, e.g. \"mymod:main\")".to_string())
55                .at_line(root.line)
56        })?
57        .to_string();
58    let vm = root
59        .attr(yog_ids::yui_attr::X_VIEW_MODEL)
60        .unwrap_or_default()
61        .trim()
62        .to_string();
63    if vm.is_empty() {
64        return err(root, "x:ViewModel names the view-model type");
65    }
66    let create = root.attr(yog_ids::yui_attr::X_CREATE).map_or_else(
67        || format!("<{vm} as ::std::default::Default>::default()"),
68        str::to_string,
69    );
70    Ok(Root { id, vm, create })
71}
72
73/// Generates a view-model screen's `.g.rs` (see the module docs).
74pub fn generate(
75    root: &Element,
76    name: &str,
77    mod_id: Option<&str>,
78) -> Result<(String, Vec<Diagnostic>), Diagnostic> {
79    let Root { id, vm, create } = read_root(root)?;
80    let frame = yui::screen_frame(root)?;
81    let mut g = VmGen::new(format!("{name}.yui"), mod_id, yui::screen_theme(root)?);
82    for c in root
83        .children
84        .iter()
85        .filter(|c| c.name == yog_ids::yui_vm::SCREEN_RESOURCES)
86    {
87        g.collect_resources(c)?;
88    }
89    let mut content = root.clone();
90    content.attrs.retain(|(k, _)| k != yog_ids::yui_attr::ID);
91    let mut ctx = Ctx::new(Scope::Screen, VIEW_FIELDS, VIEW_TREE, SCREEN_OWNER);
92    let tree = g.element(&mut ctx, &content, &[], None)?;
93    let (expr, ids) = yui::static_widget(&tree, false, 0, mod_id)?;
94    g.absorb(ids);
95    let mut view_fields = String::new();
96    let mut view_inits = String::new();
97    for (n, ty, init) in &g.view_fields {
98        let _ = writeln!(view_fields, "    {n}: {ty},");
99        let _ = writeln!(view_inits, "            {n}: {init},");
100    }
101    let dispatch: String = g
102        .dispatch
103        .iter()
104        .map(|(pat, body)| {
105            format!("            {pat} => {{\n                {body}\n            }}\n")
106        })
107        .collect();
108    let values = [
109        ("NAME", name.to_string()),
110        ("VM", vm),
111        ("CODEGEN", CODEGEN.to_string()),
112        ("ID_LIT", format!("{id:?}")),
113        ("WIDTH", f32_lit(frame.width)),
114        ("HEIGHT", f32_lit(frame.height)),
115        ("IDS", yui::str_consts(g.ids.as_slice())),
116        ("TEMPLATES", std::mem::take(&mut g.templates)),
117        ("VIEW_FIELDS", view_fields),
118        ("ANCHOR", yui::pascal(frame.anchor)),
119        ("MODAL", frame.modal.to_string()),
120        ("PAUSE", frame.pause.to_string()),
121        ("LIVE", frame.live.to_string()),
122        ("UI_USE", UI_USE.to_string()),
123        ("EXPR", expr),
124        ("PARTS", std::mem::take(&mut g.parts)),
125        ("VIEW_INITS", view_inits),
126        ("UPDATE", ctx.update),
127        ("DISPATCH", dispatch),
128        ("CREATE", create),
129    ];
130    Ok((fill(TEMPLATE, &values), Vec::new()))
131}
132
133#[cfg(test)]
134mod tests;