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