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