Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_tui/src/lib_tui/render.rs

14.7 KiB, 19 runs

created by r1870400018:1199, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::lib_tui::{
2 cfg::{
3 style::StyleLibrary,
4 },
5 style::{
6 Colour,
7 Style,
8 },
9};
10
11use oxedyne_fe2o3_core::prelude::*;
12use oxedyne_fe2o3_geom::{
13 dim::Coord,
14 rect::{
15 AbsRect,
16 AbsSize,
17 },
18};
19
20use crossterm;
21
22use std::{
23 io::{
24 self,
25 Write,
26 },
27 marker::PhantomData,
28};
29
30
31/// A target for terminal output.
32pub trait Sink: Write {
33 fn flush(&mut self) -> Outcome<()>;
34}
35
36/// Implement the Sink trait for io::Stdout
37/// ```ignore
38/// fn main() -> Outcome<()> {
39/// let mut stdout = io::stdout();
40/// res!(clear_canvas(&mut stdout));
41/// Ok(())
42/// }
43/// ```
44impl Sink for io::Stdout {
45 fn flush(&mut self) -> Outcome<()> {
46 res!(io::Write::flush(self));
47 Ok(())
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq)]
52pub enum When {
53 Later,
54 Now,
55}
56
57pub trait Drawable {
58 fn render<S: Sink, R: Renderer<S>>(
59 &mut self,
60 drawer: &mut Drawer<S, R>,
61 when: When,
62 )
63 -> Outcome<()>;
64}
65
66pub struct Drawer<S: Sink, R: Renderer<S>> {
67 pub rend: R,
68 pub lib: StyleLibrary,
69 pub mouse_on: bool,
70 phantom: PhantomData<S>,
71}
72
73impl<S: Sink, R: Renderer<S>> Drawer<S, R> {
74
75 pub fn new(
76 rend: R,
77 lib: StyleLibrary,
78 mouse_on: bool,
79 )
80 -> Self
81 {
82 Self {
83 rend,
84 lib,
85 mouse_on,
86 phantom: PhantomData,
87 }
88 }
89
90 pub fn sink(&mut self) -> &mut S { self.rend.sink() }
91
92 pub fn on(&mut self) -> Outcome<()> {
93 res!(self.rend.on());
94 res!(self.rend.mouse_capture(self.mouse_on));
95 Ok(())
96 }
97
98 pub fn off(&mut self) -> Outcome<()> {
99 res!(self.rend.off());
100 res!(self.rend.mouse_capture(!self.mouse_on));
101 Ok(())
102 }
103}
104
105#[derive(Clone, Copy, Debug, Default)]
106pub enum CursorStyle {
107 DefaultUserShape,
108 #[default]
109 BlinkingBlock,
110 SteadyBlock,
111 BlinkingUnderScore,
112 SteadyUnderScore,
113 BlinkingBar,
114 SteadyBar,
115}
116
117impl CursorStyle {
118 pub fn to_crossterm(&self) -> crossterm::cursor::SetCursorStyle {
119 match self {
120 Self::DefaultUserShape => crossterm::cursor::SetCursorStyle::DefaultUserShape,
121 Self::BlinkingBlock => crossterm::cursor::SetCursorStyle::BlinkingBlock,
122 Self::SteadyBlock => crossterm::cursor::SetCursorStyle::SteadyBlock,
123 Self::BlinkingUnderScore => crossterm::cursor::SetCursorStyle::BlinkingUnderScore,
124 Self::SteadyUnderScore => crossterm::cursor::SetCursorStyle::SteadyUnderScore,
125 Self::BlinkingBar => crossterm::cursor::SetCursorStyle::BlinkingBar,
126 Self::SteadyBar => crossterm::cursor::SetCursorStyle::SteadyBar,
127 }
128 }
129}
130
131pub trait Renderer<S: Sink>: Write {
132 // Cursor
133 fn hide_cursor(&mut self, when: When) -> Outcome<()>;
134 fn show_cursor(&mut self, when: When) -> Outcome<()>;
135 fn get_cursor(&mut self) -> Outcome<Coord>;
136 fn set_cursor(&mut self, c: Coord, when: When) -> Outcome<()>;
137 fn set_cursor_style(&mut self, style: CursorStyle, when: When) -> Outcome<()>;
138 // Terminal
139 fn clear(&mut self) -> Outcome<()>;
140 fn clear_rect(&mut self, rect: AbsRect) -> Outcome<()>;
141 fn off(&mut self) -> Outcome<()>;
142 fn on(&mut self) -> Outcome<()>;
143 fn size(&self) -> Outcome<AbsSize>;
144 // Output
145 fn print(&mut self, text: &str, when: When) -> Outcome<()>;
146 fn sink(&mut self) -> &mut S;
147 // Mouse
148 fn mouse_capture(&mut self, capture_on: bool) -> Outcome<()>;
149 // Colours
150 fn reset_colour(&mut self, when: When) -> Outcome<()>;
151 fn set_back_colour(&mut self, col: &Colour, when: When) -> Outcome<()>;
152 fn set_fore_colour(&mut self, col: &Colour, when: When) -> Outcome<()>;
153 // Style
154 fn set_style(&mut self, style: &Style, when: When) -> Outcome<()>;
155 fn reset_style(&mut self, when: When) -> Outcome<()>;
156}
157
158pub struct CrosstermRenderer<S: Sink> {
159 pub sink: S,
160}
161
162impl<S: Sink> CrosstermRenderer<S> {
163
164 pub fn new(sink: S) -> Self {
165 Self { sink }
166 }
167
168 pub fn sink(&mut self) -> &mut S { &mut self.sink }
169}
170
171impl<S: Sink> Write for CrosstermRenderer<S> {
172
173 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
174 self.sink.write(buf)
175 }
176
177 fn flush(&mut self) -> io::Result<()> {
178 io::Write::flush(&mut self.sink)
179 }
180}
181
182impl<S: Sink> Renderer<S> for CrosstermRenderer<S> {
183
184 // Cursor
185
186 fn hide_cursor(&mut self, when: When) -> Outcome<()> {
187 match when {
188 When::Later => res!(crossterm::queue!(
189 self.sink,
190 crossterm::cursor::Hide,
191 )),
192 When::Now => res!(crossterm::execute!(
193 self.sink,
194 crossterm::cursor::Hide,
195 )),
196 }
197 Ok(())
198 }
199
200 fn show_cursor(&mut self, when: When) -> Outcome<()> {
201 match when {
202 When::Later => res!(crossterm::queue!(
203 self.sink,
204 crossterm::cursor::Show,
205 )),
206 When::Now => res!(crossterm::execute!(
207 self.sink,
208 crossterm::cursor::Show,
209 )),
210 }
211 Ok(())
212 }
213
214 fn get_cursor(&mut self) -> Outcome<Coord> {
215 let (x, y) = res!(crossterm::cursor::position());
216 Ok(Coord::from((x as usize, y as usize)))
217 }
218
219 fn set_cursor(&mut self, c: Coord, when: When) -> Outcome<()> {
220 let (x, y) = (try_into!(u16, c.x.as_usize()), try_into!(u16, c.y.as_usize()));
221 match when {
222 When::Later =>
223 res!(crossterm::queue!(
224 self.sink,
225 crossterm::cursor::MoveTo(x, y),
226 )),
227 When::Now =>
228 res!(crossterm::execute!(
229 self.sink,
230 crossterm::cursor::MoveTo(x, y),
231 )),
232 }
233 Ok(())
234 }
235
236 fn set_cursor_style(&mut self, style: CursorStyle, when: When) -> Outcome<()> {
237 let style = style.to_crossterm();
238 match when {
239 When::Later => res!(crossterm::queue!(self.sink, style)),
240 When::Now => res!(crossterm::execute!(self.sink, style)),
241 }
242 Ok(())
243 }
244
245 // Terminal
246
247 fn clear(&mut self) -> Outcome<()> {
248 res!(crossterm::execute!(
249 self.sink,
250 crossterm::terminal::Clear(crossterm::terminal::ClearType::All),
251 ));
252 Ok(())
253 }
254
255 fn clear_rect(&mut self, rect: AbsRect) -> Outcome<()> {
256 let (x0, y0, w, h) = rect.tup();
257 let empty_line = " ".repeat(w.0 as usize);
258
259 let x0 = x0.0 as u16;
260 let y0 = y0.0 as u16;
261 let h = h.0 as u16;
262
263 for y in y0..y0+h {
264 res!(crossterm::queue!(
265 self.sink,
266 crossterm::cursor::MoveTo(x0, y),
267 crossterm::style::SetAttributes(crossterm::style::Attributes::default()),
268 crossterm::style::ResetColor,
269 crossterm::style::Print(&empty_line)
270 ));
271 }
272
273 res!(self.flush());
274 Ok(())
275 }
276
277 fn off(&mut self) -> Outcome<()> {
278 res!(crossterm::execute!(
279 self.sink,
280 crossterm::cursor::Show,
281 crossterm::terminal::LeaveAlternateScreen,
282 ));
283 res!(crossterm::terminal::disable_raw_mode());
284 Ok(())
285 }
286
287 fn on(&mut self) -> Outcome<()> {
288 res!(crossterm::terminal::enable_raw_mode());
289 res!(crossterm::execute!(
290 self.sink,
291 crossterm::terminal::EnterAlternateScreen,
292 //crossterm::cursor::Hide,
293 //crossterm::cursor::SetCursorStyle::BlinkingUnderScore,
294 ));
295 Ok(())
296 }
297
298 fn size(&self) -> Outcome<AbsSize> {
299 Ok(AbsSize::from(res!(crossterm::terminal::size())))
300 }
301
302 // Output
303
304 fn sink(&mut self) -> &mut S { &mut self.sink }
305
306 fn print(&mut self, text: &str, when: When) -> Outcome<()> {
307 match when {
308 When::Later => res!(crossterm::queue!(
309 self.sink,
310 crossterm::style::Print(text),
311 )),
312 When::Now => res!(crossterm::execute!(
313 self.sink,
314 crossterm::style::Print(text),
315 )),
316 }
317 Ok(())
318 }
319
320 // Mouse
321
322 fn mouse_capture(&mut self, capture_on: bool) -> Outcome<()> {
323 if capture_on {
324 res!(crossterm::execute!(
325 self.sink(),
326 crossterm::event::EnableMouseCapture,
327 ));
328 } else {
329 res!(crossterm::execute!(
330 self.sink(),
331 crossterm::event::DisableMouseCapture,
332 ));
333 }
334 Ok(())
335 }
336
337 // Colours
338
339 fn reset_colour(&mut self, when: When) -> Outcome<()> {
340 match when {
341 When::Later => res!(crossterm::queue!(
342 self.sink,
343 crossterm::style::ResetColor,
344 )),
345 When::Now => res!(crossterm::execute!(
346 self.sink,
347 crossterm::style::ResetColor,
348 )),
349 }
350 Ok(())
351 }
352
353 fn set_back_colour(&mut self, col: &Colour, when: When) -> Outcome<()> {
354 match when {
355 When::Later =>
356 res!(crossterm::queue!(
357 self.sink,
358 crossterm::style::SetBackgroundColor((*col).into()),
359 )),
360 When::Now =>
361 res!(crossterm::execute!(
362 self.sink,
363 crossterm::style::SetBackgroundColor((*col).into()),
364 )),
365 }
366 Ok(())
367 }
368
369 fn set_fore_colour(&mut self, col: &Colour, when: When) -> Outcome<()> {
370 match when {
371 When::Later =>
372 res!(crossterm::queue!(
373 self.sink,
374 crossterm::style::SetForegroundColor((*col).into()),
375 )),
376 When::Now =>
377 res!(crossterm::execute!(
378 self.sink,
379 crossterm::style::SetForegroundColor((*col).into()),
380 )),
381 }
382 Ok(())
383 }
384
385 fn set_style(&mut self, style: &Style, when: When) -> Outcome<()> {
386 // Set foreground colour
387 if let Some(col) = style.cols.fore {
388 res!(self.set_fore_colour(&col, when));
389 }
390
391 // Set background colour
392 if let Some(col) = style.cols.back {
393 res!(self.set_back_colour(&col, when));
394 }
395
396 // Set attributes
397 for attr in &style.attr {
398 let attr: crossterm::style::Attribute = (*attr).into();
399 match when {
400 When::Later => {
401 res!(crossterm::queue!(self.sink, crossterm::style::SetAttribute(attr)));
402 }
403 When::Now => {
404 res!(crossterm::execute!(self.sink, crossterm::style::SetAttribute(attr)));
405 }
406 }
407 }
408
409 Ok(())
410 }
411
412 fn reset_style(&mut self, when: When) -> Outcome<()> {
413 res!(self.reset_colour(when));
414 match when {
415 When::Later => {
416 res!(crossterm::queue!(
417 self.sink,
418 crossterm::style::SetAttribute(crossterm::style::Attribute::Reset)
419 ));
420 }
421 When::Now => {
422 res!(crossterm::execute!(
423 self.sink,
424 crossterm::style::SetAttribute(crossterm::style::Attribute::Reset)
425 ));
426 }
427 }
428 Ok(())
429 }
430
431}
432
433/// Useful for debugging, all commands are displayed in the log but not executed in the terminal.
434pub struct DebugCrosstermRenderer<S: Sink> {
435 pub sink: S,
436}
437
438impl<S: Sink> DebugCrosstermRenderer<S> {
439
440 pub fn new(sink: S) -> Self {
441 Self { sink }
442 }
443
444 pub fn sink(&mut self) -> &mut S { &mut self.sink }
445}
446
447impl<S: Sink> Write for DebugCrosstermRenderer<S> {
448
449 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
450 self.sink.write(buf)
451 }
452
453 fn flush(&mut self) -> io::Result<()> {
454 io::Write::flush(&mut self.sink)
455 }
456}
457
458impl<S: Sink> Renderer<S> for DebugCrosstermRenderer<S> {
459
460 // Cursor
461
462 fn hide_cursor(&mut self, when: When) -> Outcome<()> {
463 debug!("hide_cursor {:?}", when);
464 Ok(())
465 }
466
467 fn show_cursor(&mut self, when: When) -> Outcome<()> {
468 debug!("show_cursor {:?}", when);
469 Ok(())
470 }
471
472 fn get_cursor(&mut self) -> Outcome<Coord> {
473 let (x, y) = res!(crossterm::cursor::position());
474 debug!("get_cursor: (x, y) = ({}, {})", x, y);
475 Ok(Coord::from((x as usize, y as usize)))
476 }
477
478 fn set_cursor(&mut self, c: Coord, when: When) -> Outcome<()> {
479 let (x, y) = (try_into!(u16, c.x.as_usize()), try_into!(u16, c.y.as_usize()));
480 debug!("set_cursor {:?}: (x, y) = ({}, {})", when, x, y);
481 Ok(())
482 }
483
484 fn set_cursor_style(&mut self, style: CursorStyle, when: When) -> Outcome<()> {
485 debug!("set_cursor_style {:?}: style = {:?}", when, style);
486 Ok(())
487 }
488
489 // Terminal
490
491 fn clear(&mut self) -> Outcome<()> {
492 debug!("clear");
493 Ok(())
494 }
495
496 fn clear_rect(&mut self, rect: AbsRect) -> Outcome<()> {
497 debug!("clear_rect {:?}", rect);
498 Ok(())
499 }
500
501 fn off(&mut self) -> Outcome<()> {
502 debug!("off");
503 Ok(())
504 }
505
506 fn on(&mut self) -> Outcome<()> {
507 debug!("on");
508 Ok(())
509 }
510
511 fn size(&self) -> Outcome<AbsSize> {
512 let size = res!(crossterm::terminal::size());
513 debug!("size: {:?}", size);
514 Ok(AbsSize::from(size))
515 }
516
517 // Output
518
519 fn sink(&mut self) -> &mut S { &mut self.sink }
520
521 fn print(&mut self, text: &str, when: When) -> Outcome<()> {
522 debug!("print {:?}: text = '{}'", when, text);
523 Ok(())
524 }
525
526 // Mouse
527
528 fn mouse_capture(&mut self, capture_on: bool) -> Outcome<()> {
529 debug!("mouse_capture {}", capture_on);
530 Ok(())
531 }
532
533 // Colours
534
535 fn reset_colour(&mut self, when: When) -> Outcome<()> {
536 debug!("reset_colour {:?}", when);
537 Ok(())
538 }
539
540 fn set_back_colour(&mut self, col: &Colour, when: When) -> Outcome<()> {
541 debug!("set_back_colour {:?}: {:?}", when, col);
542 Ok(())
543 }
544
545 fn set_fore_colour(&mut self, col: &Colour, when: When) -> Outcome<()> {
546 debug!("set_fore_colour {:?}: {:?}", when, col);
547 Ok(())
548 }
549
550 // Style
551
552 fn set_style(&mut self, style: &Style, when: When) -> Outcome<()> {
553 debug!("set_style {:?}: {:?}", when, style);
554 Ok(())
555 }
556
557 fn reset_style(&mut self, when: When) -> Outcome<()> {
558 debug!("reset_style {:?}", when);
559 Ok(())
560 }
561
562}