1use std::{
7 fmt::Debug,
8 io::{self, Write},
9 sync::{
10 Arc, Mutex,
11 atomic::{AtomicBool, Ordering},
12 mpsc,
13 },
14 time::Duration,
15};
16
17pub use area::{Area, Coords};
18use crossterm::{
19 cursor,
20 event::{self, Event as CtEvent, poll as ct_poll, read as ct_read},
21 execute, queue,
22 style::ContentStyle,
23 terminal::{self, ClearType},
24};
25use duat_core::{
26 MainThreadOnly,
27 form::{self, Color},
28 session::{DuatSender, UiMouseEvent},
29 ui::{
30 self,
31 traits::{RawArea, RawUi},
32 },
33};
34
35use self::printer::Printer;
36pub use self::{
37 printer::{Brush, Frame},
38 rules::{SepChar, VertRule, VertRuleBuilder},
39};
40use crate::layout::Layouts;
41
42mod area;
43mod layout;
44mod printer;
45mod rules;
46
47pub struct Ui(Mutex<InnerUi>);
48
49struct InnerUi {
50 windows: Vec<(Area, Arc<Printer>)>,
51 layouts: MainThreadOnly<Layouts>,
52 win: usize,
53 frame: Frame,
54 printer_fn: fn() -> Arc<Printer>,
55 rx: Option<mpsc::Receiver<Event>>,
56 tx: mpsc::Sender<Event>,
57}
58
59unsafe impl Send for InnerUi {}
62
63impl RawUi for Ui {
64 type Area = Area;
65
66 fn get_once() -> Option<&'static Self> {
67 static GOT: AtomicBool = AtomicBool::new(false);
68 let (tx, rx) = mpsc::channel();
69
70 (!GOT.fetch_or(true, Ordering::Relaxed)).then(|| {
71 Box::leak(Box::new(Self(Mutex::new(InnerUi {
72 windows: Vec::new(),
73 layouts: MainThreadOnly::default(),
74 win: 0,
75 frame: Frame::default(),
76 printer_fn: || Arc::new(Printer::new()),
77 rx: Some(rx),
78 tx,
79 })))) as &'static Self
80 })
81 }
82
83 fn open(&self, duat_tx: DuatSender) {
84 use event::{KeyboardEnhancementFlags as KEF, PushKeyboardEnhancementFlags};
85
86 form::set_weak("rule.upper", "default.VertRule");
87 form::set_weak("rule.lower", "default.VertRule");
88
89 let term_rx = self.0.lock().unwrap().rx.take().unwrap();
90 let term_tx = self.0.lock().unwrap().tx.clone();
91
92 let print_thread = std::thread::Builder::new().name("print loop".to_string());
93 let _ = print_thread.spawn(move || {
94 let Ok(Event::NewPrinter(mut printer)) = term_rx.recv() else {
97 unreachable!("Failed to load the Ui");
98 };
99
100 terminal::enable_raw_mode().unwrap();
101
102 execute!(
106 io::stdout(),
107 terminal::EnterAlternateScreen,
108 terminal::Clear(ClearType::All),
109 terminal::DisableLineWrap,
110 event::EnableBracketedPaste,
111 event::EnableFocusChange,
112 event::EnableMouseCapture
113 )
114 .unwrap();
115
116 if let Ok(true) = terminal::supports_keyboard_enhancement() {
117 execute!(
118 io::stdout(),
119 PushKeyboardEnhancementFlags(
120 KEF::DISAMBIGUATE_ESCAPE_CODES | KEF::REPORT_ALTERNATE_KEYS
121 )
122 )
123 .unwrap();
124 }
125
126 loop {
127 match term_rx.recv() {
128 Ok(Event::Print) => printer.print(),
129 Ok(Event::UpdatePrinter) => printer.update(true, true),
130 Ok(Event::NewPrinter(new_printer)) => printer = new_printer,
131 Ok(Event::Quit) => break,
132 Err(_) => {}
133 }
134 }
135 });
136
137 let _ = std::thread::Builder::new()
138 .name("crossterm".to_string())
139 .spawn(move || {
140 loop {
141 let Ok(true) = ct_poll(Duration::from_millis(20)) else {
142 continue;
143 };
144
145 match ct_read() {
146 Ok(CtEvent::Key(key)) => {
147 if !key.kind.is_release() {
148 duat_tx.send_key(key).unwrap();
149 }
150 }
151 Ok(CtEvent::Resize(..)) => {
152 term_tx.send(Event::UpdatePrinter).unwrap();
153 duat_tx.send_resize().unwrap();
154 }
155 Ok(CtEvent::FocusGained) => duat_tx.send_focused().unwrap(),
156 Ok(CtEvent::FocusLost) => duat_tx.send_unfocused().unwrap(),
157 Ok(CtEvent::Mouse(event)) => duat_tx
158 .send_mouse(UiMouseEvent {
159 coord: ui::Coord {
160 x: event.column as f32,
161 y: event.row as f32,
162 },
163 kind: event.kind,
164 modifiers: event.modifiers,
165 })
166 .unwrap(),
167 Ok(CtEvent::Paste(_)) => {}
168 Err(_) => {}
169 }
170 }
171 });
172 }
173
174 fn close(&self) {
175 self.0.lock().unwrap().tx.send(Event::Quit).unwrap();
176
177 if let Ok(true) = terminal::supports_keyboard_enhancement() {
178 queue!(io::stdout(), event::PopKeyboardEnhancementFlags).unwrap();
179 }
180
181 execute!(
182 io::stdout(),
183 terminal::Clear(ClearType::All),
184 terminal::LeaveAlternateScreen,
185 cursor::MoveToColumn(0),
186 terminal::Clear(ClearType::FromCursorDown),
187 terminal::EnableLineWrap,
188 event::DisableBracketedPaste,
189 event::DisableFocusChange,
190 event::DisableMouseCapture,
191 cursor::Show,
192 )
193 .unwrap();
194
195 terminal::disable_raw_mode().unwrap();
196 }
197
198 fn new_root(&self, cache: <Self::Area as RawArea>::Cache) -> Self::Area {
199 let mut ui = self.0.lock().unwrap();
200 let printer = (ui.printer_fn)();
201
202 let main_id = unsafe { ui.layouts.get() }.new_layout(printer.clone(), ui.frame, cache);
205
206 let root = Area::new(main_id, unsafe { ui.layouts.get() }.clone());
207 ui.windows.push((root.clone(), printer.clone()));
208 if ui.windows.len() == 1 {
209 ui.tx.send(Event::NewPrinter(printer)).unwrap();
210 }
211
212 root
213 }
214
215 fn new_dyn_spawned(
216 &self,
217 id: ui::SpawnId,
218 specs: ui::DynSpawnSpecs,
219 cache: <Self::Area as RawArea>::Cache,
220 win: usize,
221 ) -> Self::Area {
222 let ui = self.0.lock().unwrap();
223 let id = unsafe { ui.layouts.get() }.spawn_on_text(id, specs, cache, win);
224
225 Area::new(id, unsafe { ui.layouts.get() }.clone())
226 }
227
228 fn new_static_spawned(
229 &self,
230 id: ui::SpawnId,
231 specs: ui::StaticSpawnSpecs,
232 cache: <Self::Area as RawArea>::Cache,
233 win: usize,
234 ) -> Self::Area {
235 let ui = self.0.lock().unwrap();
236 let id = unsafe { ui.layouts.get() }.spawn_static(id, specs, cache, win);
237
238 Area::new(id, unsafe { ui.layouts.get() }.clone())
239 }
240
241 fn switch_window(&self, win: usize) {
242 let mut ui = self.0.lock().unwrap();
243 ui.win = win;
244 let printer = ui.windows[win].1.clone();
245 printer.update(true, true);
246 ui.tx.send(Event::NewPrinter(printer)).unwrap()
247 }
248
249 fn flush_layout(&self) {
250 let ui = self.0.lock().unwrap();
251 if let Some((_, printer)) = ui.windows.get(ui.win) {
252 printer.update(false, false);
253 }
254 }
255
256 fn print(&self) {
257 self.0.lock().unwrap().tx.send(Event::Print).unwrap();
258 }
259
260 fn load(&'static self) {
261 std::panic::set_hook(Box::new(|info| {
263 self.close();
264 println!("{}", std::backtrace::Backtrace::force_capture());
265 println!("{info}");
266 }));
267 }
268
269 fn unload(&self) {
270 let mut ui = self.0.lock().unwrap();
271 ui.windows = Vec::new();
272 unsafe { ui.layouts.get() }.reset();
275 ui.win = 0;
276 }
277
278 fn remove_window(&self, win: usize) {
279 let mut ui = self.0.lock().unwrap();
280 ui.windows.remove(win);
281 unsafe { ui.layouts.get() }.remove_window(win);
284 if ui.win > win {
285 ui.win -= 1;
286 }
287 }
288
289 fn size(&'static self) -> ui::Coord {
290 let ui = self.0.lock().unwrap();
291 ui.windows[0].1.update(false, false);
292 let coord = ui.windows[0].1.max_value();
293 ui::Coord { x: coord.x as f32, y: coord.y as f32 }
294 }
295}
296
297#[derive(Debug)]
298pub enum Anchor {
299 TopLeft,
300 TopRight,
301 BottomLeft,
302 BottomRight,
303}
304
305enum Event {
306 Print,
307 UpdatePrinter,
308 NewPrinter(Arc<Printer>),
309 Quit,
310}
311
312impl Eq for Event {}
313
314impl PartialEq for Event {
315 fn eq(&self, other: &Self) -> bool {
316 core::mem::discriminant(self) == core::mem::discriminant(other)
317 }
318}
319
320#[derive(Clone, Copy, PartialEq)]
321pub struct AreaId(usize);
322
323impl AreaId {
324 fn new() -> Self {
326 use std::sync::atomic::{AtomicUsize, Ordering};
327 static INDEX_COUNTER: AtomicUsize = AtomicUsize::new(0);
328
329 AreaId(INDEX_COUNTER.fetch_add(1, Ordering::Relaxed))
330 }
331}
332
333impl std::fmt::Debug for AreaId {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 f.write_fmt(format_args!("{}", self.0))
336 }
337}
338
339type Equality = kasuari::Constraint;
340
341#[rustfmt::skip]
342macro_rules! color_values {
343 ($name:ident, $p:literal, $s:literal) => {
344 macro_rules! c {
345 ($n:literal) => {
346 concat!($p, $n, $s)
347 }
348 }
349
350 const $name: [&str; 256] = [
351 c!(0), c!(1), c!(2), c!(3), c!(4), c!(5), c!(6), c!(7), c!(8), c!(9), c!(10), c!(11),
352 c!(12), c!(13), c!(14), c!(15), c!(16), c!(17), c!(18), c!(19), c!(20), c!(21), c!(22),
353 c!(23), c!(24), c!(25), c!(26), c!(27), c!(28), c!(29), c!(30), c!(31), c!(32), c!(33),
354 c!(34), c!(35), c!(36), c!(37), c!(38), c!(39), c!(40), c!(41), c!(42), c!(43), c!(44),
355 c!(45), c!(46), c!(47), c!(48), c!(49), c!(50), c!(51), c!(52), c!(53), c!(54), c!(55),
356 c!(56), c!(57), c!(58), c!(59), c!(60), c!(61), c!(62), c!(63), c!(64), c!(65), c!(66),
357 c!(67), c!(68), c!(69), c!(70), c!(71), c!(72), c!(73), c!(74), c!(75), c!(76), c!(77),
358 c!(78), c!(79), c!(80), c!(81), c!(82), c!(83), c!(84), c!(85), c!(86), c!(87), c!(88),
359 c!(89), c!(90), c!(91), c!(92), c!(93), c!(94), c!(95), c!(96), c!(97), c!(98), c!(99),
360 c!(100), c!(101), c!(102), c!(103), c!(104), c!(105), c!(106), c!(107), c!(108),
361 c!(109), c!(110), c!(111), c!(112), c!(113), c!(114), c!(115), c!(116), c!(117),
362 c!(118), c!(119), c!(120), c!(121), c!(122), c!(123), c!(124), c!(125), c!(126),
363 c!(127), c!(128), c!(129), c!(130), c!(131), c!(132), c!(133), c!(134), c!(135),
364 c!(136), c!(137), c!(138), c!(139), c!(140), c!(141), c!(142), c!(143), c!(144),
365 c!(145), c!(146), c!(147), c!(148), c!(149), c!(150), c!(151), c!(152), c!(153),
366 c!(154), c!(155), c!(156), c!(157), c!(158), c!(159), c!(160), c!(161), c!(162),
367 c!(163), c!(164), c!(165), c!(166), c!(167), c!(168), c!(169), c!(170), c!(171),
368 c!(172), c!(173), c!(174), c!(175), c!(176), c!(177), c!(178), c!(179), c!(180),
369 c!(181), c!(182), c!(183), c!(184), c!(185), c!(186), c!(187), c!(188), c!(189),
370 c!(190), c!(191), c!(192), c!(193), c!(194), c!(195), c!(196), c!(197), c!(198),
371 c!(199), c!(200), c!(201), c!(202), c!(203), c!(204), c!(205), c!(206), c!(207),
372 c!(208), c!(209), c!(210), c!(211), c!(212), c!(213), c!(214), c!(215), c!(216),
373 c!(217), c!(218), c!(219), c!(220), c!(221), c!(222), c!(223), c!(224), c!(225),
374 c!(226), c!(227), c!(228), c!(229), c!(230), c!(231), c!(232), c!(233), c!(234),
375 c!(235), c!(236), c!(237), c!(238), c!(239), c!(240), c!(241), c!(242), c!(243),
376 c!(244), c!(245), c!(246), c!(247), c!(248), c!(249), c!(250), c!(251), c!(252),
377 c!(253), c!(254), c!(255),
378 ];
379 }
380}
381
382fn print_style(
383 w: &mut impl Write,
384 style: ContentStyle,
385 ansi_codes: &mut micromap::Map<CStyle, String, 16>,
386) {
387 if let Some(ansi) = ansi_codes.get(&CStyle(style)) {
388 w.write_all(ansi.as_bytes()).unwrap();
389 } else if style != ContentStyle::default() {
390 let ansi = {
391 let mut ansi = String::new();
392 use crossterm::style::Attribute::{self, *};
393 const ATTRIBUTES: [(Attribute, &str); 10] = [
394 (Reset, "0"),
395 (Bold, "1"),
396 (Dim, "2"),
397 (Italic, "3"),
398 (Underlined, "4"),
399 (DoubleUnderlined, "4;2"),
400 (Undercurled, "4;3"),
401 (Underdotted, "4;4"),
402 (Underdashed, "4;5"),
403 (Reverse, "7"),
404 ];
405 color_values!(U8, "", "");
406 color_values!(U8_SC, "", ";");
407 color_values!(U8_FG_RGB, "38;2;", ";");
408 color_values!(U8_FG_ANSI, "38;5;", ";");
409 color_values!(U8_BG_RGB, "48;2;", ";");
410 color_values!(U8_BG_ANSI, "48;5;", ";");
411 color_values!(U8_UL_RGB, "58;2;", ";");
412 color_values!(U8_UL_ANSI, "58;5;", ";");
413
414 ansi.push_str("\x1b[");
415
416 let mut semicolon = false;
417 if !style.attributes.is_empty() {
418 for (attr, code) in ATTRIBUTES {
419 if style.attributes.has(attr) {
420 if semicolon {
421 ansi.push(';')
422 }
423 ansi.push_str(code);
424 semicolon = true;
425 }
426 }
427 }
428
429 let semicolon = if let Some(color) = style.foreground_color {
430 if semicolon {
431 ansi.push(';');
432 }
433 match color {
434 Color::Reset => ansi.push_str("39"),
435 Color::Black => ansi.push_str("30"),
436 Color::DarkRed => ansi.push_str("31"),
437 Color::DarkGreen => ansi.push_str("32"),
438 Color::DarkYellow => ansi.push_str("33"),
439 Color::DarkBlue => ansi.push_str("34"),
440 Color::DarkMagenta => ansi.push_str("35"),
441 Color::DarkCyan => ansi.push_str("36"),
442 Color::Grey => ansi.push_str("37"),
443 Color::DarkGrey => ansi.push_str("90"),
444 Color::Red => ansi.push_str("91"),
445 Color::Green => ansi.push_str("92"),
446 Color::Yellow => ansi.push_str("93"),
447 Color::Blue => ansi.push_str("94"),
448 Color::Magenta => ansi.push_str("95"),
449 Color::Cyan => ansi.push_str("96"),
450 Color::White => ansi.push_str("97"),
451 Color::Rgb { r, g, b } => {
452 ansi.push_str(U8_FG_RGB[r as usize]);
453 ansi.push_str(U8_SC[g as usize]);
454 ansi.push_str(U8[b as usize])
455 }
456 Color::AnsiValue(val) => ansi.push_str(U8_FG_ANSI[val as usize]),
457 };
458 true
459 } else {
460 semicolon
461 };
462
463 let semicolon = if let Some(color) = style.background_color {
464 if semicolon {
465 ansi.push(';');
466 }
467 match color {
468 Color::Reset => ansi.push_str("49"),
469 Color::Black => ansi.push_str("40"),
470 Color::DarkRed => ansi.push_str("41"),
471 Color::DarkGreen => ansi.push_str("42"),
472 Color::DarkYellow => ansi.push_str("43"),
473 Color::DarkBlue => ansi.push_str("44"),
474 Color::DarkMagenta => ansi.push_str("45"),
475 Color::DarkCyan => ansi.push_str("46"),
476 Color::Grey => ansi.push_str("47"),
477 Color::DarkGrey => ansi.push_str("100"),
478 Color::Red => ansi.push_str("101"),
479 Color::Green => ansi.push_str("102"),
480 Color::Yellow => ansi.push_str("103"),
481 Color::Blue => ansi.push_str("104"),
482 Color::Magenta => ansi.push_str("105"),
483 Color::Cyan => ansi.push_str("106"),
484 Color::White => ansi.push_str("107"),
485 Color::Rgb { r, g, b } => {
486 ansi.push_str(U8_BG_RGB[r as usize]);
487 ansi.push_str(U8_SC[g as usize]);
488 ansi.push_str(U8[b as usize]);
489 }
490 Color::AnsiValue(val) => ansi.push_str(U8_BG_ANSI[val as usize]),
491 };
492 true
493 } else {
494 semicolon
495 };
496
497 if let Some(color) = style.underline_color {
498 if semicolon {
499 ansi.push(';');
500 }
501 match color {
502 Color::Reset => ansi.push_str("59"),
503 Color::Black => ansi.push_str("58;0"),
504 Color::DarkRed => ansi.push_str("58;1"),
505 Color::DarkGreen => ansi.push_str("58;2"),
506 Color::DarkYellow => ansi.push_str("58;3"),
507 Color::DarkBlue => ansi.push_str("58;4"),
508 Color::DarkMagenta => ansi.push_str("58;5"),
509 Color::DarkCyan => ansi.push_str("58;6"),
510 Color::Grey => ansi.push_str("58;7"),
511 Color::DarkGrey => ansi.push_str("58;8"),
512 Color::Red => ansi.push_str("58;9"),
513 Color::Green => ansi.push_str("58;10"),
514 Color::Yellow => ansi.push_str("58;11"),
515 Color::Blue => ansi.push_str("58;12"),
516 Color::Magenta => ansi.push_str("58;13"),
517 Color::Cyan => ansi.push_str("58;14"),
518 Color::White => ansi.push_str("58;15"),
519 Color::Rgb { r, g, b } => {
520 ansi.push_str(U8_UL_RGB[r as usize]);
521 ansi.push_str(U8_SC[g as usize]);
522 ansi.push_str(U8[b as usize])
523 }
524 Color::AnsiValue(val) => ansi.push_str(U8_UL_ANSI[val as usize]),
525 };
526 }
527
528 ansi.push('m');
529
530 ansi
531 };
532
533 w.write_all(ansi.as_bytes()).unwrap();
534
535 ansi_codes.checked_insert(CStyle(style), ansi);
536 }
537}
538
539#[derive(Clone, Copy, PartialEq, Eq)]
540pub struct CStyle(ContentStyle);
541
542impl std::hash::Hash for CStyle {
543 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
544 self.0.foreground_color.hash(state);
545 self.0.background_color.hash(state);
546 self.0.foreground_color.hash(state);
547 let attr: u32 = unsafe { std::mem::transmute(self.0.attributes) };
548 attr.hash(state);
549 }
550}
551
552const EDGE_PRIO: kasuari::Strength = kasuari::Strength::REQUIRED;
554const MANUAL_LEN_PRIO: kasuari::Strength = kasuari::Strength::new(11.0);
556const LEN_PRIO: kasuari::Strength = kasuari::Strength::new(10.0);
558const FRAME_PRIO: kasuari::Strength = kasuari::Strength::new(9.0);
560const HIDDEN_PRIO: kasuari::Strength = kasuari::Strength::new(8.0);
562const DYN_SPAWN_POS_PRIO: kasuari::Strength = kasuari::Strength::new(7.0);
564const SPAWN_DIMS_PRIO: kasuari::Strength = kasuari::Strength::new(6.0);
566const SPAWN_LEN_PRIO: kasuari::Strength = kasuari::Strength::new(5.0);
568const CONS_SPAWN_LEN_PRIO: kasuari::Strength = kasuari::Strength::new(4.0);
570const STATIC_SPAWN_POS_PRIO: kasuari::Strength = kasuari::Strength::new(3.0);
572const SPAWN_ALIGN_PRIO: kasuari::Strength = kasuari::Strength::new(2.0);
574const EQ_LEN_PRIO: kasuari::Strength = kasuari::Strength::new(1.0);