Skip to main content

rsynko_memory/
screen.rs

1//! The reference renderer: a screen stated as the lines a reader would read.
2
3use rsynko_ui::{Emphasis, Gauge, ScreenSyntax};
4
5/// Renders screens as plain text.
6///
7/// A renderer states weight however its medium can. Plain text has no weight to state, so this
8/// interpreter keeps every word and drops every emphasis, which is exactly what makes it the one
9/// to read a screen's meaning back from.
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub struct TextScreen;
12
13impl ScreenSyntax for TextScreen {
14    type Text = String;
15    type Line = String;
16    type Row = Vec<String>;
17    type Body = Vec<String>;
18    type Screen = Vec<String>;
19
20    fn text(&self, content: impl Into<String>, _emphasis: Emphasis) -> Self::Text {
21        content.into()
22    }
23
24    fn gauge(&self, gauge: Gauge) -> Self::Text {
25        gauge.text()
26    }
27
28    fn line(&self, runs: impl Iterator<Item = Self::Text>) -> Self::Line {
29        runs.collect()
30    }
31
32    fn edited_line(
33        &self,
34        prefix: impl Into<String>,
35        text: &str,
36        cursor: usize,
37        _emphasis: Emphasis,
38    ) -> Self::Line {
39        // The cursor is stated where a reader would see it, since text has no cursor of its own.
40        let (before, after) = text.split_at(cursor.min(text.len()));
41        format!("{}{before}|{after}", prefix.into())
42    }
43
44    fn row(&self, lines: impl Iterator<Item = Self::Line>) -> Self::Row {
45        lines.collect()
46    }
47
48    fn rows(
49        &self,
50        title: impl Into<String>,
51        _focused: Option<usize>,
52        rows: impl Iterator<Item = Self::Row>,
53    ) -> Self::Body {
54        [title.into()].into_iter().chain(rows.flatten()).collect()
55    }
56
57    fn message(
58        &self,
59        title: impl Into<String>,
60        message: impl Into<String>,
61        _emphasis: Emphasis,
62    ) -> Self::Body {
63        vec![title.into(), message.into()]
64    }
65
66    fn draft(
67        &self,
68        title: impl Into<String>,
69        placeholder: impl Into<String>,
70        text: &str,
71        cursor: usize,
72        examples: impl Iterator<Item = (String, String)>,
73    ) -> Self::Body {
74        let content = if text.is_empty() {
75            placeholder.into()
76        } else {
77            self.edited_line("", text, cursor, Emphasis::Plain)
78        };
79        [title.into(), content]
80            .into_iter()
81            .chain(examples.map(|(shape, means)| format!("{shape}  {means}")))
82            .collect()
83    }
84
85    fn record(&self, title: impl Into<String>, notes: impl Iterator<Item = String>) -> Self::Body {
86        [title.into()].into_iter().chain(notes).collect()
87    }
88
89    fn verbatim(&self, text: impl Into<String>) -> Self::Body {
90        vec![text.into()]
91    }
92
93    fn screen(
94        &self,
95        header: Self::Line,
96        body: Self::Body,
97        status: impl Into<String>,
98        footer: Self::Line,
99    ) -> Self::Screen {
100        [header]
101            .into_iter()
102            .chain(body)
103            .chain([status.into(), footer])
104            .collect()
105    }
106
107    fn screen_text(&self, screen: &Self::Screen) -> impl Iterator<Item = String> {
108        screen.clone().into_iter()
109    }
110}