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