flywheel/widget/
terminal.rs1use crate::buffer::{Buffer, Cell, Rgb};
8use crate::layout::Rect;
9use crate::actor::InputEvent;
10use crate::widget::Widget;
11use std::sync::{Arc, Mutex};
12
13pub struct Terminal {
15 bounds: Rect,
16 parser: Arc<Mutex<vt100::Parser>>,
17 needs_redraw: bool,
18}
19
20impl Terminal {
21 pub fn new(bounds: Rect) -> Self {
23 Self {
24 bounds,
25 parser: Arc::new(Mutex::new(vt100::Parser::new(bounds.height, bounds.width, 0))),
26 needs_redraw: true,
27 }
28 }
29
30 pub fn write(&mut self, data: &[u8]) {
32 if let Ok(mut parser) = self.parser.lock() {
33 parser.process(data);
34 self.needs_redraw = true;
35 }
36 }
37
38 pub fn clear(&mut self) {
40 if let Ok(mut parser) = self.parser.lock() {
41 *parser = vt100::Parser::new(self.bounds.height, self.bounds.width, 0);
42 self.needs_redraw = true;
43 }
44 }
45}
46
47impl Widget for Terminal {
48 fn bounds(&self) -> Rect {
49 self.bounds
50 }
51
52 fn set_bounds(&mut self, bounds: Rect) {
53 if bounds != self.bounds {
54 self.bounds = bounds;
55 if let Ok(mut parser) = self.parser.lock() {
56 parser.set_size(bounds.height, bounds.width);
57 }
58 self.needs_redraw = true;
59 }
60 }
61
62 #[allow(clippy::cast_possible_truncation)]
63 fn render(&self, buffer: &mut Buffer) {
64 let Ok(parser) = self.parser.lock() else { return };
65 let screen = parser.screen();
66
67 for y in 0..self.bounds.height {
68 for x in 0..self.bounds.width {
69 if let Some(cell) = screen.cell(y, x) {
70 let mut fl_cell = Cell::from_char(cell.contents().chars().next().unwrap_or(' '));
71
72 match cell.fgcolor() {
74 vt100::Color::Rgb(r, g, b) => {
75 fl_cell.set_fg(Rgb::new(r, g, b));
76 }
77 vt100::Color::Idx(i) => {
78 fl_cell.set_fg(ansi_to_rgb(i));
79 }
80 vt100::Color::Default => {}
81 }
82
83 match cell.bgcolor() {
85 vt100::Color::Rgb(r, g, b) => {
86 fl_cell.set_bg(Rgb::new(r, g, b));
87 }
88 vt100::Color::Idx(i) => {
89 fl_cell.set_bg(ansi_to_rgb(i));
90 }
91 vt100::Color::Default => {}
92 }
93
94 buffer.set(self.bounds.x + x, self.bounds.y + y, fl_cell);
95 }
96 }
97 }
98 }
99
100 fn handle_input(&mut self, _event: &InputEvent) -> bool {
101 false
105 }
106
107 fn needs_redraw(&self) -> bool {
108 self.needs_redraw
109 }
110
111 fn clear_redraw(&mut self) {
112 self.needs_redraw = false;
113 }
114}
115
116const fn ansi_to_rgb(idx: u8) -> Rgb {
118 match idx {
119 0 => Rgb::new(0, 0, 0),
120 1 => Rgb::new(128, 0, 0),
121 2 => Rgb::new(0, 128, 0),
122 3 => Rgb::new(128, 128, 0),
123 4 => Rgb::new(0, 0, 128),
124 5 => Rgb::new(128, 0, 128),
125 6 => Rgb::new(0, 128, 128),
126 7 => Rgb::new(192, 192, 192),
127 8 => Rgb::new(128, 128, 128),
128 9 => Rgb::new(255, 0, 0),
129 10 => Rgb::new(0, 255, 0),
130 11 => Rgb::new(255, 255, 0),
131 12 => Rgb::new(0, 0, 255),
132 13 => Rgb::new(255, 0, 255),
133 14 => Rgb::new(0, 255, 255),
134 15 => Rgb::new(255, 255, 255),
135 16..=231 => {
136 let i = idx - 16;
137 let r = (i / 36) % 6;
138 let g = (i / 6) % 6;
139 let b = i % 6;
140 Rgb::new(
141 if r == 0 { 0 } else { r * 40 + 55 },
142 if g == 0 { 0 } else { g * 40 + 55 },
143 if b == 0 { 0 } else { b * 40 + 55 },
144 )
145 }
146 232..=255 => {
147 let v = (idx - 232) * 10 + 8;
148 Rgb::new(v, v, v)
149 }
150 }
151}