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, cy: usize,
16 index: usize, unsaved: bool, 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 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 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}