Skip to main content

yog_book/look/
markup.rs

1//! Book texts are Yog text (`yog_ui::rich`: `{b|…}`, `{#rrggbb|…}`, …)
2//! with words of the book's own, turned into Yog text's when the book is
3//! prepared:
4//!
5//! | Write | Shows |
6//! |---|---|
7//! | `{accent\|term}` | a term, in the book's accent color |
8//! | `{link=entry\|text}` | a link to an entry: underlined, in the link color |
9//! | `{key\|sprint}` | the key a binding is bound to (`sprint`, a mod's `mymod:xray`) |
10
11use yog_sync::RwLock;
12
13/// Links' color.
14const LINK: &str = "#2f5fbf";
15/// A key's span, up to its binding: `{key|sprint}`.
16const KEY_OPEN: &str = "{key|";
17const CLOSE: char = '}';
18
19/// The key a binding is bound to, as the game's controls show it; none
20/// when there's no such binding (or no game).
21pub type KeyNamer = fn(&str) -> Option<String>;
22
23static KEY_NAMER: RwLock<Option<KeyNamer>> = RwLock::new(None);
24
25/// Where `{key|…}` gets its keys' names (the game's; the binding itself without one).
26pub fn set_key_namer(f: KeyNamer) {
27    *KEY_NAMER.write() = Some(f);
28}
29
30/// `text` with each `{key|binding}` the key that binding is bound to.
31fn keys(text: &str) -> String {
32    let mut out = String::with_capacity(text.len());
33    let mut rest = text;
34    while let Some(at) = rest.find(KEY_OPEN) {
35        let after = &rest[at + KEY_OPEN.len()..];
36        let Some(end) = after.find(CLOSE) else { break };
37        let binding = &after[..end];
38        let name = (*KEY_NAMER.read())
39            .and_then(|f| f(binding))
40            .unwrap_or_else(|| binding.to_owned());
41        out.push_str(&rest[..at]);
42        out.push_str(&name.replace('{', "{{").replace('}', "}}"));
43        rest = &after[end + 1..];
44    }
45    out.push_str(rest);
46    out
47}
48
49/// `text` with the book's words (`accent`, `link=…`, `key`) as Yog text's.
50pub fn resolve(text: &str, accent: &str) -> String {
51    yog_ui::rich::map_attrs(&keys(text), |w| match w {
52        "accent" => Some(accent.to_string()),
53        w if w.starts_with("link=") => Some(format!("u {LINK}")),
54        _ => None,
55    })
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn resolves_the_books_words() {
64        let r = resolve(
65            "{accent|Media} is {b|{link=hexcasting:staff|staves}} {{x}}",
66            "#8d6acc",
67        );
68        assert_eq!(r, "{#8d6acc|Media} is {b|{u #2f5fbf|staves}} {{x}}");
69        let runs = yog_ui::rich::runs(&r);
70        assert_eq!(runs[0].1.color, Some(0x8d6acc));
71        assert!(
72            runs.iter()
73                .any(|(t, s)| t == "staves" && s.bold && s.underline)
74        );
75    }
76
77    #[test]
78    fn a_key_is_named_as_the_game_binds_it() {
79        assert_eq!(resolve("hold {key|sprint}.", "#000000"), "hold sprint.");
80        set_key_namer(|b| (b == "sprint").then(|| "Left Control".to_owned()));
81        assert_eq!(
82            resolve("hold {key|sprint}.", "#000000"),
83            "hold Left Control."
84        );
85    }
86}