1#![feature(
2 iter_collect_into,
3 let_chains,
4 if_let_guard,
5 decl_macro,
6 debug_closure_helpers
7)]
8use std::{
9 fmt::Debug,
10 io::{self, Write},
11 sync::{Mutex, mpsc},
12 time::Duration,
13};
14
15pub use area::{Area, Coords};
16use crossterm::{
17 cursor,
18 event::{self, PopKeyboardEnhancementFlags},
19 execute,
20 style::ContentStyle,
21 terminal::{self, ClearType},
22};
23use duat_core::{
24 DuatError,
25 data::RwData,
26 form::Color,
27 text::err,
28 ui::{self, Sender, UiEvent},
29};
30
31use self::{layout::Layout, print::Printer};
32pub use self::{
33 print::{Brush, Frame},
34 rules::{VertRule, VertRuleCfg},
35};
36
37mod area;
38mod layout;
39mod print;
40mod rules;
41
42pub struct Ui {
43 windows: Vec<(Area, RwData<Printer>)>,
44 layouts: RwData<Vec<Layout>>,
45 win: usize,
46 fr: Frame,
47 printer_fn: fn() -> RwData<Printer>,
48}
49
50impl Ui {
51 fn cur_printer(&self) -> &RwData<Printer> {
52 if let Some((_, printer)) = self.windows.get(self.win) {
53 printer
54 } else {
55 unreachable!("Started printing before a window was created");
56 }
57 }
58}
59
60impl Default for Ui {
61 fn default() -> Self {
62 Self {
63 windows: Vec::new(),
64 layouts: RwData::default(),
65 win: 0,
66 fr: Frame::default(),
67 printer_fn: RwData::default,
68 }
69 }
70}
71
72impl ui::Ui for Ui {
73 type Area = Area;
74 type MetaStatics = Mutex<Ui>;
75
76 fn open(ms: &'static Self::MetaStatics, tx: Sender, rx: mpsc::Receiver<UiEvent>) {
77 use crossterm::event::{KeyboardEnhancementFlags as KEF, PushKeyboardEnhancementFlags};
79 execute!(
80 io::stdout(),
81 terminal::EnterAlternateScreen,
82 terminal::DisableLineWrap
83 )
84 .unwrap();
85 if terminal::supports_keyboard_enhancement().is_ok() {
88 execute!(
89 io::stdout(),
90 PushKeyboardEnhancementFlags(KEF::DISAMBIGUATE_ESCAPE_CODES)
91 )
92 .unwrap()
93 }
94 terminal::enable_raw_mode().unwrap();
95
96 let thread = std::thread::Builder::new().name("print loop".to_string());
98 let _ = thread.spawn(move || {
99 'outer: loop {
100 let Ok(UiEvent::ResumePrinting) = rx.recv() else {
101 break;
102 };
103
104 let printer = ms.lock().unwrap().cur_printer().clone();
105
106 loop {
107 if let Ok(true) = event::poll(Duration::from_millis(13)) {
108 let res = match event::read().unwrap() {
109 event::Event::Key(key) => tx.send_key(key),
110 event::Event::Resize(..) => {
111 printer.write().update(true);
112 tx.send_resize()
113 }
114 event::Event::FocusGained
115 | event::Event::FocusLost
116 | event::Event::Mouse(_)
117 | event::Event::Paste(_) => Ok(()),
118 };
119 if res.is_err() {
120 break;
121 }
122 }
123
124 printer.try_inspect(|p| p.print());
125
126 if let Ok(event) = rx.try_recv() {
127 match event {
128 UiEvent::ResumePrinting => {
129 unreachable!("Wrong order")
130 }
131 UiEvent::PausePrinting => break,
132 UiEvent::Quit => break 'outer,
133 }
134 }
135 }
136 }
137 });
138 }
139
140 fn close(_ms: &'static Self::MetaStatics) {
141 terminal::disable_raw_mode().unwrap();
142 execute!(
143 io::stdout(),
144 terminal::Clear(ClearType::All),
145 terminal::LeaveAlternateScreen,
146 terminal::EnableLineWrap,
147 cursor::Show,
148 PopKeyboardEnhancementFlags
149 )
150 .unwrap();
151 }
152
153 fn load(_ms: &'static Self::MetaStatics) {
154 std::panic::set_hook(Box::new(|info| {
156 let trace = std::backtrace::Backtrace::capture();
157 terminal::disable_raw_mode().unwrap();
158 queue!(
159 io::stdout(),
160 terminal::Clear(ClearType::All),
161 terminal::LeaveAlternateScreen,
162 cursor::MoveToColumn(0),
163 terminal::Clear(ClearType::FromCursorDown),
164 terminal::EnableLineWrap,
165 cursor::Show,
166 PopKeyboardEnhancementFlags
167 );
168 if let std::backtrace::BacktraceStatus::Captured = trace.status() {
169 for line in trace.to_string().lines() {
170 println!("{line}");
171 queue!(io::stdout(), cursor::MoveToColumn(0));
172 }
173 }
174 for line in info.to_string().lines() {
175 println!("{line}");
176 queue!(io::stdout(), cursor::MoveToColumn(0));
177 }
178 }));
179 }
180
181 fn new_root(
182 ms: &'static Self::MetaStatics,
183 cache: <Self::Area as ui::Area>::Cache,
184 ) -> Self::Area {
185 let mut ui = ms.lock().unwrap();
186 let printer = (ui.printer_fn)();
187
188 let root = ui.layouts.mutate(|layouts| {
189 let layout = Layout::new(ui.fr, printer.clone(), cache);
190 let main_id = layout.main_id();
191 layouts.push(layout);
192
193 Area::new(main_id, ui.layouts.clone())
194 });
195 let area = root.clone();
196
197 ui.windows.push((root, printer));
198
199 area
200 }
201
202 fn switch_window(ms: &'static Self::MetaStatics, win: usize) {
203 ms.lock().unwrap().win = win;
204 }
205
206 fn flush_layout(ms: &'static Self::MetaStatics) {
207 let ui = ms.lock().unwrap();
208 ui.cur_printer().write().flush_equalities().unwrap();
209 }
210
211 fn unload(ms: &'static Self::MetaStatics) {
212 let mut ui = ms.lock().unwrap();
213 ui.windows = Vec::new();
214 *ui.layouts.write() = Vec::new();
215 ui.win = 0;
216 }
217
218 fn remove_window(ms: &'static Self::MetaStatics, win: usize) {
219 let mut ui = ms.lock().unwrap();
220 ui.windows.remove(win);
221 ui.layouts.write().remove(win);
222 if ui.win > win {
223 ui.win -= 1;
224 }
225 }
226}
227
228pub enum ConstraintErr {
229 NoParent,
230 Impossible,
231}
232
233impl std::fmt::Display for ConstraintErr {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 <Self as std::fmt::Debug>::fmt(self, f)
236 }
237}
238
239impl std::fmt::Debug for ConstraintErr {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 match self {
242 ConstraintErr::NoParent => {
244 write!(f, "No parents, so its constraint can't be changed.")
245 }
246 ConstraintErr::Impossible => {
247 write!(f, "The constraint change is impossible.")
248 }
249 }
250 }
251}
252
253impl DuatError for ConstraintErr {
254 fn into_text(self) -> Box<duat_core::text::Text> {
255 Box::new(match self {
256 ConstraintErr::NoParent => {
257 err!("The constraints of the master node " [*a] "can't" [] " change.")
258 }
259 ConstraintErr::Impossible => {
260 err!("The requested constraint change is impossible.")
261 }
262 })
263 }
264}
265
266#[derive(Debug)]
267pub enum Anchor {
268 TopLeft,
269 TopRight,
270 BottomLeft,
271 BottomRight,
272}
273
274impl std::error::Error for ConstraintErr {}
275
276#[derive(Clone, Copy, PartialEq)]
277pub struct AreaId(usize);
278
279impl AreaId {
280 fn new() -> Self {
282 use std::sync::atomic::{AtomicUsize, Ordering};
283 static INDEX_COUNTER: AtomicUsize = AtomicUsize::new(0);
284
285 AreaId(INDEX_COUNTER.fetch_add(1, Ordering::Relaxed))
286 }
287}
288
289impl std::fmt::Debug for AreaId {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 f.write_fmt(format_args!("{}", self.0))
292 }
293}
294
295type Equality = cassowary::Constraint;
296
297fn print_style(w: &mut impl Write, style: ContentStyle) {
298 use crossterm::style::Attribute::{self, *};
299 const ATTRIBUTES: [(Attribute, &[u8]); 10] = [
300 (Reset, b"0"),
301 (Bold, b"1"),
302 (Dim, b"2"),
303 (Italic, b"3"),
304 (Underlined, b"4"),
305 (DoubleUnderlined, b"4;2"),
306 (Undercurled, b"4;3"),
307 (Underdotted, b"4;4"),
308 (Underdashed, b"4;5"),
309 (Reverse, b"7"),
310 ];
311 color_values!(U8, "", "");
312 color_values!(U8_SC, "", ";");
313 color_values!(U8_FG_RGB, "38;2;", ";");
314 color_values!(U8_FG_ANSI, "38;5;", ";");
315 color_values!(U8_BG_RGB, "48;2;", ";");
316 color_values!(U8_BG_ANSI, "48;5;", ";");
317 color_values!(U8_UL_RGB, "58;2;", ";");
318 color_values!(U8_UL_ANSI, "58;5;", ";");
319
320 if style == ContentStyle::default() {
321 return;
322 }
323 let _ = w.write_all(b"\x1b[");
324
325 let mut semicolon = false;
326 if !style.attributes.is_empty() {
327 for (attr, ansi) in ATTRIBUTES {
328 if style.attributes.has(attr) {
329 if semicolon {
330 w.write_all(b";").unwrap()
331 }
332 w.write_all(ansi).unwrap();
333 semicolon = true;
334 }
335 }
336 }
337
338 let semicolon = if let Some(color) = style.foreground_color {
339 if semicolon {
340 w.write_all(b";").unwrap()
341 }
342 let _ = match color {
343 Color::Reset => w.write_all(b"39"),
344 Color::Black => w.write_all(b"30"),
345 Color::DarkRed => w.write_all(b"31"),
346 Color::DarkGreen => w.write_all(b"32"),
347 Color::DarkYellow => w.write_all(b"33"),
348 Color::DarkBlue => w.write_all(b"34"),
349 Color::DarkMagenta => w.write_all(b"35"),
350 Color::DarkCyan => w.write_all(b"36"),
351 Color::Grey => w.write_all(b"37"),
352 Color::DarkGrey => w.write_all(b"90"),
353 Color::Red => w.write_all(b"91"),
354 Color::Green => w.write_all(b"92"),
355 Color::Yellow => w.write_all(b"93"),
356 Color::Blue => w.write_all(b"94"),
357 Color::Magenta => w.write_all(b"95"),
358 Color::Cyan => w.write_all(b"96"),
359 Color::White => w.write_all(b"97"),
360 Color::Rgb { r, g, b } => {
361 let _ = w.write_all(U8_FG_RGB[r as usize].as_bytes());
362 let _ = w.write_all(U8_SC[g as usize].as_bytes());
363 w.write_all(U8[b as usize].as_bytes())
364 }
365 Color::AnsiValue(ansi) => w.write_all(U8_FG_ANSI[ansi as usize].as_bytes()),
366 };
367 true
368 } else {
369 semicolon
370 };
371
372 let semicolon = if let Some(color) = style.background_color {
373 if semicolon {
374 w.write_all(b";").unwrap()
375 }
376 match color {
377 Color::Reset => w.write_all(b"49").unwrap(),
378 Color::Black => w.write_all(b"40").unwrap(),
379 Color::DarkRed => w.write_all(b"41").unwrap(),
380 Color::DarkGreen => w.write_all(b"42").unwrap(),
381 Color::DarkYellow => w.write_all(b"43").unwrap(),
382 Color::DarkBlue => w.write_all(b"44").unwrap(),
383 Color::DarkMagenta => w.write_all(b"45").unwrap(),
384 Color::DarkCyan => w.write_all(b"46").unwrap(),
385 Color::Grey => w.write_all(b"47").unwrap(),
386 Color::DarkGrey => w.write_all(b"100").unwrap(),
387 Color::Red => w.write_all(b"101").unwrap(),
388 Color::Green => w.write_all(b"102").unwrap(),
389 Color::Yellow => w.write_all(b"103").unwrap(),
390 Color::Blue => w.write_all(b"104").unwrap(),
391 Color::Magenta => w.write_all(b"105").unwrap(),
392 Color::Cyan => w.write_all(b"106").unwrap(),
393 Color::White => w.write_all(b"107").unwrap(),
394 Color::Rgb { r, g, b } => {
395 w.write_all(U8_BG_RGB[r as usize].as_bytes()).unwrap();
396 w.write_all(U8_SC[g as usize].as_bytes()).unwrap();
397 w.write_all(U8[b as usize].as_bytes()).unwrap()
398 }
399 Color::AnsiValue(ansi) => w.write_all(U8_BG_ANSI[ansi as usize].as_bytes()).unwrap(),
400 };
401 true
402 } else {
403 semicolon
404 };
405
406 if let Some(color) = style.underline_color {
407 if semicolon {
408 w.write_all(b";").unwrap()
409 }
410 match color {
411 Color::Reset => w.write_all(b"59").unwrap(),
412 Color::Black => w.write_all(b"58;0").unwrap(),
413 Color::DarkRed => w.write_all(b"58;1").unwrap(),
414 Color::DarkGreen => w.write_all(b"58;2").unwrap(),
415 Color::DarkYellow => w.write_all(b"58;3").unwrap(),
416 Color::DarkBlue => w.write_all(b"58;4").unwrap(),
417 Color::DarkMagenta => w.write_all(b"58;5").unwrap(),
418 Color::DarkCyan => w.write_all(b"58;6").unwrap(),
419 Color::Grey => w.write_all(b"58;7").unwrap(),
420 Color::DarkGrey => w.write_all(b"58;8").unwrap(),
421 Color::Red => w.write_all(b"58;9").unwrap(),
422 Color::Green => w.write_all(b"58;10").unwrap(),
423 Color::Yellow => w.write_all(b"58;11").unwrap(),
424 Color::Blue => w.write_all(b"58;12").unwrap(),
425 Color::Magenta => w.write_all(b"58;13").unwrap(),
426 Color::Cyan => w.write_all(b"58;14").unwrap(),
427 Color::White => w.write_all(b"58;15").unwrap(),
428 Color::Rgb { r, g, b } => {
429 w.write_all(U8_UL_RGB[r as usize].as_bytes()).unwrap();
430 w.write_all(U8_SC[g as usize].as_bytes()).unwrap();
431 w.write_all(U8[b as usize].as_bytes()).unwrap()
432 }
433 Color::AnsiValue(ansi) => w.write_all(U8_UL_ANSI[ansi as usize].as_bytes()).unwrap(),
434 };
435 }
436
437 w.write_all(b"m").unwrap();
438}
439
440macro queue($writer:expr $(, $command:expr)* $(,)?) {
441 crossterm::queue!($writer $(, $command)*).unwrap()
442}
443
444macro style($lines:expr, $style:expr) {{
445 #[cfg(unix)]
446 print_style(&mut $lines, $style);
447 #[cfg(not(unix))]
448 queue!(
449 $lines,
450 crossterm::style::ResetColor,
451 crossterm::style::SetStyle($style)
452 );
453}}
454
455#[rustfmt::skip]
456macro color_values($name:ident, $p:literal, $s:literal) {
457 macro c($n:literal) {
458 concat!($p, $n, $s)
459 }
460 const $name: [&str; 256] = [
461 c!(0), c!(1), c!(2), c!(3), c!(4), c!(5), c!(6), c!(7), c!(8), c!(9), c!(10), c!(11),
462 c!(12), c!(13), c!(14), c!(15), c!(16), c!(17), c!(18), c!(19), c!(20), c!(21), c!(22),
463 c!(23), c!(24), c!(25), c!(26), c!(27), c!(28), c!(29), c!(30), c!(31), c!(32), c!(33),
464 c!(34), c!(35), c!(36), c!(37), c!(38), c!(39), c!(40), c!(41), c!(42), c!(43), c!(44),
465 c!(45), c!(46), c!(47), c!(48), c!(49), c!(50), c!(51), c!(52), c!(53), c!(54), c!(55),
466 c!(56), c!(57), c!(58), c!(59), c!(60), c!(61), c!(62), c!(63), c!(64), c!(65), c!(66),
467 c!(67), c!(68), c!(69), c!(70), c!(71), c!(72), c!(73), c!(74), c!(75), c!(76), c!(77),
468 c!(78), c!(79), c!(80), c!(81), c!(82), c!(83), c!(84), c!(85), c!(86), c!(87), c!(88),
469 c!(89), c!(90), c!(91), c!(92), c!(93), c!(94), c!(95), c!(96), c!(97), c!(98), c!(99),
470 c!(100), c!(101), c!(102), c!(103), c!(104), c!(105), c!(106), c!(107), c!(108), c!(109),
471 c!(110), c!(111), c!(112), c!(113), c!(114), c!(115), c!(116), c!(117), c!(118), c!(119),
472 c!(120), c!(121), c!(122), c!(123), c!(124), c!(125), c!(126), c!(127), c!(128), c!(129),
473 c!(130), c!(131), c!(132), c!(133), c!(134), c!(135), c!(136), c!(137), c!(138), c!(139),
474 c!(140), c!(141), c!(142), c!(143), c!(144), c!(145), c!(146), c!(147), c!(148), c!(149),
475 c!(150), c!(151), c!(152), c!(153), c!(154), c!(155), c!(156), c!(157), c!(158), c!(159),
476 c!(160), c!(161), c!(162), c!(163), c!(164), c!(165), c!(166), c!(167), c!(168), c!(169),
477 c!(170), c!(171), c!(172), c!(173), c!(174), c!(175), c!(176), c!(177), c!(178), c!(179),
478 c!(180), c!(181), c!(182), c!(183), c!(184), c!(185), c!(186), c!(187), c!(188), c!(189),
479 c!(190), c!(191), c!(192), c!(193), c!(194), c!(195), c!(196), c!(197), c!(198), c!(199),
480 c!(200), c!(201), c!(202), c!(203), c!(204), c!(205), c!(206), c!(207), c!(208), c!(209),
481 c!(210), c!(211), c!(212), c!(213), c!(214), c!(215), c!(216), c!(217), c!(218), c!(219),
482 c!(220), c!(221), c!(222), c!(223), c!(224), c!(225), c!(226), c!(227), c!(228), c!(229),
483 c!(230), c!(231), c!(232), c!(233), c!(234), c!(235), c!(236), c!(237), c!(238), c!(239),
484 c!(240), c!(241), c!(242), c!(243), c!(244), c!(245), c!(246), c!(247), c!(248), c!(249),
485 c!(250), c!(251), c!(252), c!(253), c!(254), c!(255),
486 ];
487}