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