Skip to main content

fontedit/
app.rs

1use crate::Font;
2use stakker::{ActorOwn, CX, StopCause, actor, call, fail, fwd_to, ret_some_to, stop};
3use stakker_tui::{Key, Region, TermShare, Terminal, Tile, sizer::SimpleSizer};
4use std::collections::VecDeque;
5use std::io::Write as IoWrite;
6
7pub struct App {
8    term: ActorOwn<Terminal>,
9    tsh: Option<TermShare>,
10    param_tile: Tile,
11    cells_tile: Tile,
12    mini_tile: Tile,
13    font: Font,
14    cx: usize, // Cursor position
15    cy: usize,
16    index: usize,  // Current character index
17    unsaved: bool, // Needs saving
18    save_error: Option<String>,
19    mode: Mode,
20    clip: Vec<bool>,
21    undo: VecDeque<Font>,
22}
23
24#[derive(Copy, Clone)]
25enum Mode {
26    Move,
27    Draw,
28    Roll,
29}
30
31impl Mode {
32    fn caps(self) -> &'static str {
33        match self {
34            Self::Move => "MOVE",
35            Self::Draw => "DRAW",
36            Self::Roll => "ROLL",
37        }
38    }
39    fn cursor(self) -> &'static str {
40        match self {
41            Self::Move => "<>",
42            Self::Draw => "[]",
43            Self::Roll => "><",
44        }
45    }
46}
47
48impl App {
49    pub fn init(cx: CX![], font: Font) -> Option<Self> {
50        let term = actor!(
51            cx,
52            Terminal::init(
53                SimpleSizer::new(),
54                fwd_to!([cx], resize() as (Option<TermShare>)),
55                fwd_to!([cx], input() as (Key))
56            ),
57            ret_some_to!([cx], |_, cx, cause: StopCause| {
58                println!("Terminal actor failed: {cause}");
59                stop!(cx);
60            })
61        );
62        Some(Self {
63            term,
64            tsh: None,
65            param_tile: Tile::new(),
66            cells_tile: Tile::new(),
67            mini_tile: Tile::new(),
68            font,
69            cx: 0,
70            cy: 0,
71            index: 0,
72            unsaved: false,
73            save_error: None,
74            mode: Mode::Move,
75            clip: Vec::new(),
76            undo: VecDeque::new(),
77        })
78    }
79
80    fn resize(&mut self, cx: CX![], tsh: Option<TermShare>) {
81        self.tsh = tsh;
82        self.redraw(cx);
83    }
84
85    fn redraw(&mut self, cx: CX![]) {
86        if let Some(ref tsh) = self.tsh {
87            let out = tsh.output(cx);
88            out.attr_99().cursor_show().scroll_up().save_cleanup();
89            out.cursor_hide().clear_all_99_and_remote();
90
91            // Draw fixed stuff here, then break out and store
92            // sub-tiles for areas that need to be updated later
93            let tile = tsh.tile(cx);
94            let sx = tile.sx();
95            let sy = tile.sy();
96            let split_y1 = sy - self.font.sy as i32;
97            let split_y0 = split_y1.min(10);
98            let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99            let spare = sx.saturating_sub(80);
100            let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101                (spare / 2, 0)
102            } else {
103                (0, spare)
104            };
105            let (_, rest) = top.split_x(ox);
106            let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108            if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109                r.hfb(172).clear_all();
110                let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120                r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121            }
122            self.cells_tile = cells_tile;
123            self.param_tile = param_tile;
124            self.mini_tile = mini_tile;
125
126            self.draw_cells(cx);
127            self.draw_mini(cx);
128            self.draw_param(cx);
129        }
130    }
131
132    fn draw_param(&mut self, cx: CX![]) {
133        if let Some(mut r) = self.param_tile.full(cx) {
134            r.hfb(172).clear_all();
135
136            // Use sub-region to clip the filename if it's too long
137            r.region(0, 0, 1, 39)
138                .hfb(172)
139                .text("File:  ")
140                .text(&self.font.path.to_string_lossy());
141
142            let count = self.font.index_limit();
143            let index = self.index;
144
145            let state = if let Some(ref err) = self.save_error {
146                err
147            } else if self.unsaved {
148                "UNSAVED"
149            } else {
150                ""
151            };
152            let cp = self.font.chars[index].cp;
153            let ch = match cp {
154                32..=126 | 160.. => char::from_u32(cp).unwrap_or('?'),
155                _ => '?',
156            };
157            let sx = self.font.sx;
158            let sy = self.font.sy;
159
160            r.at(1, 0);
161            let _ = writeln!(r, "Size:  {sx} x {sy}, {count} glyphs");
162            let _ = writeln!(r, "Index: {index} U+{cp:04x} '{ch}'");
163            let _ = writeln!(r, "Mode:  {}", self.mode.caps());
164            let _ = writeln!(r, "State: {}", state);
165        }
166    }
167
168    fn draw_cells(&mut self, cx: CX![]) {
169        if let Some(mut full) = self.cells_tile.full(cx) {
170            let sx = full.sx();
171            full.clear_all_99();
172
173            let font_sx = self.font.sx;
174            let font_sy = self.font.sy;
175            let cell_wid = font_sx * 2;
176            let r = full.region(
177                0,
178                (sx - cell_wid as i32) / 2,
179                font_sy as i32,
180                cell_wid as i32,
181            );
182            draw_cell(
183                &self.font,
184                r,
185                self.index,
186                171,
187                7,
188                Some((self.cy, self.cx, self.mode)),
189            );
190            let max = self.font.index_limit();
191            for i in 1..=self.index {
192                let r = full.region(
193                    0,
194                    (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195                    font_sy as i32,
196                    cell_wid as i32,
197                );
198                if !r.is_visible() {
199                    break;
200                }
201                draw_cell(&self.font, r, self.index - i, 70, 6, None);
202            }
203            for i in 1..(max - self.index) {
204                let r = full.region(
205                    0,
206                    (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207                    font_sy as i32,
208                    cell_wid as i32,
209                );
210                if !r.is_visible() {
211                    break;
212                }
213                draw_cell(&self.font, r, self.index + i, 70, 6, None);
214            }
215        }
216    }
217
218    fn draw_mini(&mut self, cx: CX![]) {
219        if let Some(mut full) = self.mini_tile.full(cx) {
220            full.clear_all_99();
221            let sx = full.sx() * 2;
222            let sy = full.sy() * 4;
223            let font_sx = self.font.sx as i32;
224            let font_sy = self.font.sy as i32;
225            let oy = sy.saturating_sub(font_sy) / 2;
226            let mut ox = (sx - font_sx) / 2;
227            let mut index = self.index;
228            while ox > 0 && index > 0 {
229                ox -= font_sx;
230                index -= 1;
231            }
232            while ox < sx {
233                draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234                ox += font_sx;
235                index += 1;
236            }
237        }
238    }
239
240    fn draw_all(&mut self, cx: CX![]) {
241        self.draw_cells(cx);
242        self.draw_mini(cx);
243        self.draw_param(cx);
244    }
245
246    fn input(&mut self, cx: CX![], key: Key) {
247        match key {
248            Key::Ctrl('L') => self.redraw(cx),
249            Key::Ctrl('C') => stop!(cx),
250            Key::Left => {
251                if self.cx > 0 {
252                    self.move_or_draw(cx, 0, -1)
253                }
254            }
255            Key::Right => {
256                if self.cx + 1 < self.font.sx {
257                    self.move_or_draw(cx, 0, 1)
258                }
259            }
260            Key::Up => {
261                if self.cy > 0 {
262                    self.move_or_draw(cx, -1, 0)
263                }
264            }
265            Key::Down => {
266                if self.cy + 1 < self.font.sy {
267                    self.move_or_draw(cx, 1, 0)
268                }
269            }
270            Key::PgUp => {
271                if self.index > 0 {
272                    self.index -= 1;
273                    self.draw_all(cx);
274                }
275            }
276            Key::PgDn => {
277                if self.index + 1 < self.font.index_limit() {
278                    self.index += 1;
279                    self.draw_all(cx);
280                }
281            }
282            Key::Home => {
283                self.index = 0;
284                self.draw_all(cx);
285            }
286            Key::End => {
287                self.index = self.font.index_limit() - 1;
288                self.draw_all(cx);
289            }
290            Key::Pr(' ') => {
291                self.undo_push();
292                self.unsaved = true;
293                let curr = self.font.get(self.index, self.cy, self.cx);
294                self.font.set(self.index, self.cy, self.cx, !curr);
295                self.draw_all(cx);
296            }
297            Key::Pr('d') => {
298                self.mode = match self.mode {
299                    Mode::Draw => Mode::Move,
300                    _ => Mode::Draw,
301                };
302                self.draw_all(cx);
303            }
304            Key::Pr('r') => {
305                self.mode = match self.mode {
306                    Mode::Roll => Mode::Move,
307                    _ => Mode::Roll,
308                };
309                self.draw_all(cx);
310            }
311            Key::Pr('f') => {
312                self.undo_push();
313                self.unsaved = true;
314                self.font.fill(self.index, self.cy, self.cx, true, true);
315                self.draw_all(cx);
316            }
317            Key::Pr('h') => {
318                self.undo_push();
319                self.unsaved = true;
320                self.font.fill(self.index, self.cy, self.cx, false, true);
321                self.draw_all(cx);
322            }
323            Key::Pr('v') => {
324                self.undo_push();
325                self.unsaved = true;
326                self.font.fill(self.index, self.cy, self.cx, true, false);
327                self.draw_all(cx);
328            }
329            Key::Pr('u') => {
330                if let Some(font) = self.undo.pop_front() {
331                    self.font = font;
332                    let limit = self.font.index_limit();
333                    self.index = self.index.min(limit - 1);
334                    self.draw_all(cx);
335                }
336            }
337            Key::Pr('l') => {
338                self.font.rule_bm ^= 1 << self.cy;
339                self.unsaved = true;
340                self.draw_all(cx);
341            }
342            Key::Pr('n') => {
343                self.undo_push();
344                self.unsaved = true;
345                self.index = self.font.index_limit();
346                self.font.add_glyph();
347                self.draw_param(cx);
348                self.draw_cells(cx);
349            }
350            Key::Pr('c') => {
351                self.clip = self.font.get_glyph(self.index);
352            }
353            Key::Pr('P') => {
354                if !self.clip.is_empty() {
355                    self.undo_push();
356                    self.unsaved = true;
357                    self.font.set_glyph(self.index, &self.clip);
358                    self.draw_all(cx);
359                }
360            }
361            Key::Pr('S') => {
362                if let Err(e) = self.font.save() {
363                    self.save_error = Some(e.to_string());
364                } else {
365                    self.save_error = None;
366                    self.unsaved = false;
367                }
368                self.draw_param(cx);
369            }
370            Key::Pr('Q') => {
371                if let Err(e) = self.font.save() {
372                    fail!(cx, e);
373                } else {
374                    stop!(cx);
375                }
376            }
377            _ => call!([self.term], bell()),
378        }
379    }
380
381    fn move_or_draw(&mut self, cx: CX![], dy: i32, dx: i32) {
382        match self.mode {
383            Mode::Move => {
384                self.cx = (self.cx as i32 + dx) as usize;
385                self.cy = (self.cy as i32 + dy) as usize;
386                self.draw_cells(cx);
387            }
388            Mode::Draw => {
389                self.undo_push();
390                self.unsaved = true;
391                let curr = self.font.get(self.index, self.cy, self.cx);
392                self.cx = (self.cx as i32 + dx) as usize;
393                self.cy = (self.cy as i32 + dy) as usize;
394                self.font.set(self.index, self.cy, self.cx, curr);
395                self.draw_all(cx);
396            }
397            Mode::Roll => {
398                self.undo_push();
399                if dx != 0 {
400                    self.font.roll_horiz(self.index, dx);
401                }
402                if dy != 0 {
403                    self.font.roll_vert(self.index, dy);
404                }
405                self.draw_all(cx);
406            }
407        }
408    }
409
410    fn undo_push(&mut self) {
411        self.undo.push_front(self.font.clone());
412        while self.undo.len() > 50 {
413            self.undo.pop_back();
414        }
415    }
416}
417
418fn draw_cell(
419    font: &Font,
420    mut r: Region,
421    index: usize,
422    black: u16,
423    white: u16,
424    curs: Option<(usize, usize, Mode)>,
425) {
426    let form = &font.chars[index].form;
427    for y in 0..font.sy {
428        let rule = ((font.rule_bm >> y) & 1) != 0;
429        for x in 0..font.sx {
430            r.hfb(if form[x + y * font.sx] { white } else { black });
431            let mut text = if rule { "--" } else { "  " };
432            if let Some((cy, cx, mode)) = curs
433                && cy == y
434                && cx == x
435            {
436                text = mode.cursor();
437            }
438            r.text(text);
439        }
440    }
441}
442
443fn draw_mini_cell(font: &Font, mut r: Region, oy: i32, ox: i32, index: usize, hfb: u16) {
444    if index < font.chars.len() {
445        let form = &font.chars[index].form;
446        for y in 0..font.sy {
447            for x in 0..font.sx {
448                if form[x + y * font.sx] {
449                    r.braille_plot(oy + y as i32, ox + x as i32, hfb, true);
450                }
451            }
452        }
453    }
454}