Skip to main content

pfx_editor/
script.rs

1use std::path::Path;
2
3use pfx_editor_shell::{Script, drive};
4
5use crate::editor::{Editor, Layout};
6
7pub fn place(text: &str, layout: &Layout) -> Result<String, String> {
8    let mut lines = Vec::new();
9    for (index, line) in text.lines().enumerate() {
10        if line.trim_start().starts_with('#') {
11            lines.push(line.to_string());
12            continue;
13        }
14        let mut words = Vec::new();
15        for word in line.split_whitespace() {
16            match word.strip_prefix('@') {
17                Some(name) => {
18                    let [x, y] = layout.find(name).ok_or_else(|| {
19                        format!("line {}: nothing on screen is named {name}", index + 1)
20                    })?;
21                    words.push(format!("{x} {y}"));
22                }
23                None => words.push(word.to_string()),
24            }
25        }
26        lines.push(words.join(" "));
27    }
28    Ok(lines.join("\n"))
29}
30
31pub fn script(text: &str, layout: &Layout) -> Result<Script, String> {
32    Script::parse(&place(text, layout)?).map_err(|error| error.to_string())
33}
34
35pub fn resolve(path: &Path, size: [u32; 2], text: &str) -> Result<Script, String> {
36    let mut done: Vec<String> = Vec::new();
37    for line in text.lines() {
38        if !line.contains('@') || line.trim_start().starts_with('#') {
39            done.push(line.to_string());
40            continue;
41        }
42        let mut editor = Editor::open(path)?;
43        let ctx = egui::Context::default();
44        pfx_editor_style::theme::apply_theme(&ctx, &crate::theme::pfx());
45        let before = Script::parse(&done.join("\n")).map_err(|error| error.to_string())?;
46        drive(&ctx, size, &before, |ctx| editor.ui(ctx));
47        done.push(place(line, &editor.layout)?);
48    }
49    Script::parse(&done.join("\n")).map_err(|error| error.to_string())
50}
51
52pub fn frames(count: usize) -> String {
53    "frame\n".repeat(count)
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn named_points_become_coordinates_and_an_unknown_name_names_its_line() {
62        let mut layout = Layout::default();
63        layout.buttons.insert(
64            "move",
65            egui::Rect::from_min_size(egui::pos2(10.0, 20.0), egui::vec2(20.0, 10.0)),
66        );
67        layout.viewport = Some(egui::Rect::from_min_size(
68            egui::pos2(100.0, 0.0),
69            egui::vec2(200.0, 100.0),
70        ));
71        assert_eq!(
72            place(
73                "# @button:move stays a comment\nclick @button:move\ndrag @viewport @viewport:0.1,0.9 middle\nkey ctrl+Z",
74                &layout
75            )
76            .unwrap(),
77            "# @button:move stays a comment\nclick 20 25\ndrag 200 50 120 90 middle\nkey ctrl+Z"
78        );
79        let error = place("frame\nclick @button:gone", &layout).unwrap_err();
80        assert!(error.starts_with("line 2:"), "{error}");
81        assert_eq!(
82            script("click @button:move", &layout).unwrap().steps.len(),
83            1
84        );
85        assert!(script("wiggle 3", &layout).is_err());
86        assert_eq!(frames(2), "frame\nframe\n");
87    }
88}