1use crate::cells::CellBuffer;
2use crate::styles::*;
3use fltk::app;
4use fltk::{
5 draw,
6 enums::{Align, Color, Font, FrameType},
7 group,
8 prelude::*,
9};
10use std::sync::{Arc, Mutex};
11
12#[derive(Clone, Copy, Debug, Default)]
13pub struct Selection {
14 pub start: Option<(usize, usize)>,
15 pub end: Option<(usize, usize)>,
16}
17
18pub struct TermCanvas {
19 f: group::Group,
20 buffer: Arc<Mutex<CellBuffer>>,
21 scroll: Option<group::Scroll>,
22 blink: Arc<Mutex<bool>>,
23 selection: Arc<Mutex<Selection>>,
24}
25
26impl TermCanvas {
27 pub fn new<L: Into<Option<&'static str>>>(x: i32, y: i32, w: i32, h: i32, label: L) -> Self {
28 let mut f = group::Group::new(x, y, w, h, label);
29 f.set_frame(FrameType::FlatBox);
30 let default_bg = BLACK;
31 let default_fg = WHITE;
32 Self {
33 f,
34 buffer: Arc::new(Mutex::new(CellBuffer::new(2000, default_bg, default_fg))),
35 scroll: None,
36 blink: Arc::new(Mutex::new(true)),
37 selection: Arc::new(Mutex::new(Selection::default())),
38 }
39 }
40
41 pub fn set_size(&mut self, w: i32, h: i32) {
42 self.f.set_size(w, h);
43 }
44
45 pub fn widget(&self) -> &group::Group {
46 &self.f
47 }
48
49 pub fn set_buffer(&mut self, buffer: Arc<Mutex<CellBuffer>>) {
50 self.buffer = buffer.clone();
51 let blink_state = self.blink.clone();
52 let selection = self.selection.clone();
53 self.f.draw(move |f| {
55 let x = f.x();
56 let y = f.y();
57 let w = f.w();
58 let h = f.h();
59
60 draw::set_draw_color(BLACK);
61 draw::draw_rectf(x, y, w, h);
62 let mut font = Font::Courier;
63 let font_size = 14;
64 draw::set_font(font, font_size);
65 let line_h = draw::height().max(14);
66 let pad_x = 6;
67 let mut yy = y + line_h;
68 let char_w = ((draw::width("M") as f32).ceil() as i32).max(1);
69 let cols = ((w - 2 * pad_x).max(1)) / char_w.max(1);
70
71 let sel = {
73 let se = selection.lock().ok();
74 let (s, e) = if let Some(ref g) = se {
75 (g.start, g.end)
76 } else {
77 (None, None)
78 };
79 match (s, e) {
80 (Some(mut a), Some(mut b)) => {
81 if b < a {
82 std::mem::swap(&mut a, &mut b);
83 }
84 Some((a, b))
85 }
86 _ => None,
87 }
88 };
89
90 if let Ok(buf) = buffer.lock() {
91 draw::set_draw_color(buf.default_bg);
92 draw::draw_rectf(x, y, w, h);
93 let mut vline_idx: usize = 0;
94 for line in buf.snapshot().iter() {
95 let total_cols = line.len() as i32;
97 let mut col_used = 0i32;
98 let mut run: String = String::new();
99 let mut cur_fg = WHITE;
100 let mut cur_bg = BLACK;
101 let mut cur_bold = false;
102 let mut cur_underline = false;
103 let mut first = true;
104 let mut xx = x + pad_x;
105 let mut run_start_col: i32 = 0; if let Some(((ls, cs), (le, ce))) = sel {
108 if vline_idx >= ls && vline_idx <= le {
109 let start_col = if vline_idx == ls { cs as i32 } else { 0 };
110 let end_col = if vline_idx == le { ce as i32 } else { cols - 1 };
111 if end_col >= start_col {
112 let sx = x + pad_x + start_col * char_w;
113 let sw = (end_col - start_col + 1) * char_w;
114 draw::set_draw_color(BLUE);
115 draw::draw_rect(sx, yy - line_h, sw, line_h);
116 }
117 }
118 }
119 let mut draw_run = |run: &str,
120 fg: Color,
121 bg: Color,
122 bold: bool,
123 underline: bool,
124 xx: &mut i32,
125 yy: i32,
126 w: i32,
127 col_pos: i32,
128 vline: usize| {
129 if run.is_empty() {
130 return;
131 }
132 if *xx == x + pad_x {
133 draw::set_draw_color(buf.default_bg);
135 draw::draw_rectf(x + pad_x, yy - line_h, w - 2 * pad_x, line_h);
136 }
137 let (tw, _th) = draw::measure(run, false);
138 draw::set_draw_color(bg);
140 draw::draw_rectf(*xx, yy - line_h, tw, line_h);
141 if let Some(((ls, cs), (le, ce))) = sel {
143 if vline >= ls && vline <= le {
144 let sel_start = if vline == ls { cs as i32 } else { 0 };
145 let sel_end = if vline == le { ce as i32 } else { cols - 1 };
146 let run_cols = run.chars().count() as i32;
147 let overlap_start = sel_start.max(col_pos);
148 let overlap_end = sel_end.min(col_pos + run_cols - 1);
149 if overlap_end >= overlap_start {
150 let sx = x + pad_x + overlap_start * char_w;
151 let sw = (overlap_end - overlap_start + 1) * char_w;
152 draw::set_draw_color(BLUE);
153 draw::draw_rectf(sx, yy - line_h, sw, line_h);
154 }
155 }
156 }
157 font = if bold {
159 Font::CourierBold
160 } else {
161 Font::Courier
162 };
163 draw::set_draw_color(fg);
164 draw::draw_text2(run, *xx, yy - line_h, w - *xx, line_h, Align::Left);
165 if underline {
166 let underline_y = yy - 2;
167 draw::set_draw_color(fg);
168 draw::draw_line(*xx, underline_y, *xx + tw, underline_y);
169 }
170 *xx += tw;
171 };
172
173 for c in line.iter() {
174 let (fg, bg) = (c.style.fg, c.style.bg);
175 let bold = c.style.bold;
176 let underline = c.style.underline;
177 let (fg_eff, bg_eff) = if c.style.inverse { (bg, fg) } else { (fg, bg) };
178 if first
179 || fg_eff != cur_fg
180 || bg_eff != cur_bg
181 || bold != cur_bold
182 || underline != cur_underline
183 {
184 if !first {
185 draw_run(
186 &run,
187 cur_fg,
188 cur_bg,
189 cur_bold,
190 cur_underline,
191 &mut xx,
192 yy,
193 w,
194 run_start_col,
195 vline_idx,
196 );
197 run.clear();
198 }
199 cur_fg = fg_eff;
200 cur_bg = bg_eff;
201 cur_bold = bold;
202 cur_underline = underline;
203 first = false;
204 run_start_col = (col_used % cols) as i32;
206 }
207 run.push(c.ch);
208 col_used += 1;
209 if cols > 0 && col_used % cols == 0 {
211 draw_run(
213 &run,
214 cur_fg,
215 cur_bg,
216 cur_bold,
217 cur_underline,
218 &mut xx,
219 yy,
220 w,
221 run_start_col,
222 vline_idx,
223 );
224 run.clear();
225 yy += line_h;
226 vline_idx += 1;
227 run_start_col = 0;
228 xx = x + pad_x;
229 if yy > y + h {
230 break;
231 }
232 }
233 }
234 if !run.is_empty() && yy <= y + h {
235 draw_run(
236 &run,
237 cur_fg,
238 cur_bg,
239 cur_bold,
240 cur_underline,
241 &mut xx,
242 yy,
243 w,
244 run_start_col,
245 vline_idx,
246 );
247 }
248 if total_cols == 0 {
249 draw::set_draw_color(buf.default_bg);
251 draw::draw_rectf(x + pad_x, yy - line_h, w - 2 * pad_x, line_h);
252 yy += line_h;
253 vline_idx += 1;
254 } else if total_cols % cols != 0 {
255 yy += line_h;
256 vline_idx += 1;
257 }
258 if yy > y + h {
259 break;
260 }
261 }
262 if let Ok(b) = blink_state.lock() {
264 if *b {
265 let (crow, ccol) = buf.cursor();
266 let cols = ((w - 2 * pad_x).max(1))
268 / ((draw::width("M") as f32).ceil() as i32).max(1);
269 let before_lines = buf
270 .snapshot()
271 .iter()
272 .take(crow)
273 .map(|l| (l.len().max(1) as i32 + cols - 1) / cols)
274 .sum::<i32>();
275 let seg = (ccol as i32) / cols;
276 let col = (ccol as i32) % cols;
277 let cx = x + pad_x + col * ((draw::width("M") as f32).ceil() as i32);
278 let cy = y + line_h + (before_lines + seg) * line_h;
279 if cy - line_h >= y && cy <= y + h {
280 draw::set_draw_color(buf.default_fg);
281 draw::draw_rectf(
282 cx,
283 cy - line_h,
284 (draw::width("M") as f32).ceil() as i32,
285 line_h,
286 );
287 }
288 }
289 }
290 return;
291 }
292
293 draw::set_draw_color(WHITE);
295 });
296 }
297
298 pub fn set_scroll(&mut self, scroll: group::Scroll) {
299 self.scroll = Some(scroll.clone());
300 let buf = self.buffer.clone();
302 self.set_buffer(buf);
303 }
304
305 pub fn start_blink(&mut self, interval: f64) {
306 let blink_state = self.blink.clone();
307 let mut frame = self.f.clone();
308 app::add_timeout3(interval, move |h| {
309 if let Ok(mut b) = blink_state.lock() {
310 *b = !*b;
311 }
312 frame.redraw();
313 app::repeat_timeout3(interval, h);
314 });
315 }
316
317 fn mouse_to_vpos(&self, x: i32, y: i32) -> (usize, usize) {
318 let line_h = draw::height().max(14);
319 let char_w = ((draw::width("M") as f32).ceil() as i32).max(1);
320 let pad_x = 6;
321 let col = ((x - self.f.x() - pad_x).max(0) / char_w) as usize;
322 let row = ((y - self.f.y()).max(0) / line_h) as usize;
323 (row, col)
324 }
325
326 pub fn begin_selection_at(&mut self, x: i32, y: i32) {
327 let vpos = self.mouse_to_vpos(x, y);
328 if let Ok(mut sel) = self.selection.lock() {
329 sel.start = Some(vpos);
330 sel.end = Some(vpos);
331 }
332 self.f.redraw();
333 }
334
335 pub fn update_selection_at(&mut self, x: i32, y: i32) {
336 let vpos = self.mouse_to_vpos(x, y);
337 if let Ok(mut sel) = self.selection.lock() {
338 sel.end = Some(vpos);
339 }
340 self.f.redraw();
341 }
342
343 pub fn clear_selection(&mut self) {
344 if let Ok(mut sel) = self.selection.lock() {
345 sel.start = None;
346 sel.end = None;
347 }
348 self.f.redraw();
349 }
350
351 pub fn copy_selection_to_clipboard(&self) {
352 let buf_arc = self.buffer.clone();
353 if let Ok(buf) = buf_arc.lock() {
354 let snap = buf.snapshot();
355 let char_w = ((draw::width("M") as f32).ceil() as i32).max(1);
356 let pad_x = 6;
357 let cols = ((self.f.w() - 2 * pad_x).max(1) / char_w).max(1) as usize;
358 let mut visual: Vec<Vec<char>> = Vec::new();
360 for line in snap.iter() {
361 let row: Vec<char> = line.iter().map(|c| c.ch).collect();
362 if row.is_empty() {
363 visual.push(Vec::new());
364 continue;
365 }
366 for chunk in row.chunks(cols) {
367 visual.push(chunk.to_vec());
368 }
369 }
370 let (s, e) = if let Ok(sel) = self.selection.lock() {
371 (sel.start, sel.end)
372 } else {
373 (None, None)
374 };
375 if let (Some(mut a), Some(mut b)) = (s, e) {
376 if b < a {
377 std::mem::swap(&mut a, &mut b);
378 }
379 let mut out = String::new();
380 for v in a.0..=b.0 {
381 if v >= visual.len() {
382 break;
383 }
384 let line = &visual[v];
385 let start = if v == a.0 { a.1.min(line.len()) } else { 0 };
386 let end = if v == b.0 {
387 (b.1 + 1).min(line.len())
388 } else {
389 line.len()
390 };
391 if start < end {
392 for ch in &line[start..end] {
393 out.push(*ch);
394 }
395 }
396 if v != b.0 {
397 out.push('\n');
398 }
399 }
400 if !out.is_empty() {
401 app::copy(&out);
402 }
403 }
404 };
405 }
406
407 #[allow(clippy::type_complexity)]
408 pub fn selection_handle(&self) -> Arc<Mutex<Selection>> {
409 self.selection.clone()
410 }
411
412 pub fn buffer_arc(&self) -> Arc<Mutex<CellBuffer>> {
413 self.buffer.clone()
414 }
415}
416
417fltk::widget_extends!(TermCanvas, group::Group, f);