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