1mod iter;
2mod print_info;
3
4use std::{io::Write, sync::Arc};
5
6use crossterm::{
7 cursor, queue,
8 style::{Attribute, Attributes},
9};
10use duat_core::{
11 context::{self, Decode, Encode},
12 form::{CONTROL_CHAR_ID, Painter},
13 opts::PrintOpts,
14 text::{Item, Part, SpawnId, Text, TwoPoints, txt},
15 ui::{
16 Caret, DynSpawnSpecs, PushSpecs,
17 traits::{CoreAccess, RawArea},
18 },
19};
20use iter::{print_iter, rev_print_iter};
21
22pub use self::print_info::PrintInfo;
23use crate::{AreaId, CStyle, Mutex, layout::Layouts, print_style, printer::Lines};
24
25#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
26#[bincode(crate = "duat_core::context::bincode")]
27pub struct Coord {
28 pub x: u32,
29 pub y: u32,
30}
31
32impl std::fmt::Debug for Coord {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y))
35 }
36}
37
38impl Coord {
39 pub fn new(x: u32, y: u32) -> Coord {
40 Coord { x, y }
41 }
42}
43
44#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
45#[bincode(crate = "duat_core::context::bincode")]
46pub struct Coords {
47 pub tl: Coord,
48 pub br: Coord,
49}
50
51impl Coords {
52 pub fn new(tl: Coord, br: Coord) -> Self {
53 Coords { tl, br }
54 }
55
56 pub fn width(&self) -> u32 {
57 self.br.x - self.tl.x
58 }
59
60 pub fn height(&self) -> u32 {
61 self.br.y - self.tl.y
62 }
63
64 pub fn area(&self) -> u32 {
65 self.width() * self.height()
66 }
67
68 pub fn intersects(&self, other: Self) -> bool {
69 self.tl.x < other.br.x
70 && self.br.x > other.tl.x
71 && self.tl.y < other.br.y
72 && self.br.y > other.tl.y
73 }
74}
75
76#[derive(Clone)]
77pub struct Area {
78 layouts: Layouts,
79 id: AreaId,
80 ansi_codes: Arc<Mutex<micromap::Map<CStyle, String, 16>>>,
81}
82
83impl PartialEq for Area {
84 fn eq(&self, other: &Self) -> bool {
85 self.id == other.id
86 }
87}
88
89impl Area {
90 pub(crate) fn new(id: AreaId, layouts: Layouts) -> Self {
91 Self { layouts, id, ansi_codes: Arc::default() }
92 }
93
94 fn print<'a>(
95 &self,
96 text: &Text,
97 opts: PrintOpts,
98 mut painter: Painter,
99 mut f: impl FnMut(&Caret, &Item) + 'a,
100 ) {
101 const SPACES: &[u8] = &[b' '; 3000];
102
103 fn end_line(
104 lines: &mut Lines,
105 painter: &Painter,
106 ansi_codes: &mut micromap::Map<CStyle, String, 16>,
107 len: u32,
108 max_x: u32,
109 ) {
110 let mut default_style = painter.get_default();
111 default_style.style.foreground_color = None;
112 default_style.style.underline_color = None;
113 default_style.style.attributes = Attributes::from(Attribute::Reset);
114
115 print_style(lines, default_style.style, ansi_codes);
116 if lines.coords().br.x == max_x {
117 lines.write_all(b"\x1b[0K").unwrap();
118 } else {
119 lines
120 .write_all(&SPACES[..(lines.coords().width() - len) as usize])
121 .unwrap();
122 }
123 lines.flush().unwrap();
124 }
125
126 let (mut lines, iter, x_shift, max_x) = {
127 let Some(coords) = self.layouts.coords_of(self.id, true) else {
128 context::warn!("This Area was already deleted");
129 return;
130 };
131
132 let max = self
133 .layouts
134 .inspect(self.id, |_, layout| layout.max_value())
135 .unwrap();
136
137 if coords.width() == 0 || coords.height() == 0 {
138 return;
139 }
140
141 let (s_points, x_shift) = {
142 let mut info = self.layouts.get_info_of(self.id).unwrap();
143 let s_points = info.start_points(coords, text, opts);
144 self.layouts.set_info_of(self.id, info);
145 (s_points, info.x_shift())
146 };
147
148 let lines = Lines::new(coords);
149 let width = opts.wrap_width(coords.width()).unwrap_or(coords.width());
150 let iter = print_iter(text, s_points, width, opts);
151
152 (lines, iter, x_shift, max.x)
153 };
154
155 let mut observed_spawns = Vec::new();
156 let spawn_id = self
157 .layouts
158 .inspect(self.id, |rect, _| rect.spawn_id())
159 .unwrap();
160 let is_active = self.id == self.layouts.get_active_id();
161
162 let mut ansi_codes = self.ansi_codes.lock().unwrap();
163 let mut style_was_set = false;
164
165 enum Cursor {
166 Main,
167 Extra,
168 }
169
170 let tl_y = lines.coords().tl.y;
172 let mut y = tl_y;
173 let mut cursor = None;
174 let mut spawns_for_next: Vec<SpawnId> = Vec::new();
175 let mut last_len = 0;
176
177 for (caret, item) in iter {
178 let (painter, lines, ansi_codes) = (&mut painter, &mut lines, &mut ansi_codes);
179 f(&caret, &item);
180
181 let Caret { x, len, wrap } = caret;
182 let Item { part, .. } = item;
183
184 if wrap {
185 if y == lines.coords().br.y {
186 break;
187 }
188 if y > lines.coords().tl.y {
189 end_line(lines, painter, ansi_codes, last_len, max_x);
190 }
191 let initial_space = x.saturating_sub(x_shift).min(lines.coords().width());
192 if initial_space > 0 {
193 let mut default_style = painter.get_default().style;
194 default_style.attributes.set(Attribute::Reset);
195 print_style(lines, default_style, ansi_codes);
196 lines.write_all(&SPACES[..initial_space as usize]).unwrap();
197 }
198 y += 1;
199
200 painter.reset_prev_style();
202 style_was_set = true;
203 last_len = initial_space;
204 }
205
206 let is_contained = x + len > x_shift && x < x_shift + lines.coords().width();
207
208 match part {
209 Part::Char(char) if is_contained => {
210 if let Some(str) = get_control_str(char) {
211 painter.apply(CONTROL_CHAR_ID, 100);
212 if style_was_set && let Some(style) = painter.relative_style() {
213 print_style(lines, style, ansi_codes);
214 }
215 lines.write_all(str.as_bytes()).unwrap();
216 painter.remove(CONTROL_CHAR_ID)
217 } else {
218 if style_was_set && let Some(style) = painter.relative_style() {
219 print_style(lines, style, ansi_codes);
220 }
221 match char {
222 '\t' => {
223 let truncated_start = x_shift.saturating_sub(x);
224 let truncated_end =
225 (x + len).saturating_sub(lines.coords().width() + x_shift);
226 let tab_len = len - (truncated_start + truncated_end);
227 lines.write_all(&SPACES[..tab_len as usize]).unwrap()
228 }
229 '\n' if opts.print_new_line => lines.write_all(b" ").unwrap(),
230 '\n' | '\r' => {}
231 char => {
232 let mut bytes = [0; 4];
233 char.encode_utf8(&mut bytes);
234 lines.write_all(&bytes[..char.len_utf8()]).unwrap();
235 }
236 }
237 }
238
239 if let Some(cursor) = cursor.take() {
240 match cursor {
241 Cursor::Main => painter.remove_main_caret(),
242 Cursor::Extra => painter.remove_extra_caret(),
243 }
244 if let Some(style) = painter.relative_style() {
245 print_style(lines, style, ansi_codes)
246 }
247 }
248 for id in spawns_for_next.drain(..) {
249 observed_spawns.push(id);
250 self.layouts.move_spawn_to(
251 id,
252 Coord::new(lines.coords().tl.x + x, y - 1),
253 len,
254 );
255 }
256
257 last_len = x + len - x_shift;
258 style_was_set = false;
259 }
260 Part::Char(_) => {
261 match cursor.take() {
262 Some(Cursor::Main) => painter.remove_main_caret(),
263 Some(Cursor::Extra) => painter.remove_extra_caret(),
264 None => {}
265 }
266 spawns_for_next.clear();
267 }
268 Part::PushForm(id, prio) => {
269 painter.apply(id, prio);
270 style_was_set = true;
271 }
272 Part::PopForm(id) => {
273 painter.remove(id);
274 style_was_set = true;
275 }
276 Part::MainCaret => {
277 if let Some(shape) = painter.main_cursor()
278 && is_active
279 {
280 lines.show_real_cursor();
281 queue!(lines, shape, cursor::SavePosition).unwrap();
282 } else {
283 cursor = Some(Cursor::Main);
284 lines.hide_real_cursor();
285 painter.apply_main_cursor();
286 style_was_set = true;
287 }
288 }
289 Part::ExtraCaret => {
290 cursor = Some(Cursor::Extra);
291 painter.apply_extra_cursor();
292 style_was_set = true;
293 }
294 Part::Spacer => {
295 let truncated_start = x_shift.saturating_sub(x).min(len);
296 let truncated_end = (x + len)
297 .saturating_sub(lines.coords().width().saturating_sub(x_shift))
298 .min(len);
299 let spacer_len = len - (truncated_start + truncated_end);
300 lines.write_all(&SPACES[0..spacer_len as usize]).unwrap();
301 last_len = (x + len)
302 .saturating_sub(x_shift)
303 .min(lines.coords().width());
304 }
305 Part::ResetState => print_style(lines, painter.reset(), ansi_codes),
306 Part::SpawnedWidget(id) => spawns_for_next.push(id),
307 Part::ToggleStart(_) | Part::ToggleEnd(_) => {
308 todo!("Toggles have not been implemented yet.")
309 }
310 Part::AlignLeft | Part::AlignCenter | Part::AlignRight => {}
311 }
312 }
313
314 end_line(&mut lines, &painter, &mut ansi_codes, last_len, max_x);
315
316 for _ in 0..lines.coords().br.y - y {
317 end_line(&mut lines, &painter, &mut ansi_codes, 0, max_x);
318 }
319
320 let spawns = text.get_spawned_ids();
321
322 self.layouts
323 .send_lines(self.id, spawn_id, lines, spawns, &observed_spawns);
324 }
325}
326
327impl RawArea for Area {
328 type Cache = PrintInfo;
329 type PrintInfo = PrintInfo;
330
331 fn push(
334 &self,
335 _: CoreAccess,
336 specs: PushSpecs,
337 on_files: bool,
338 cache: PrintInfo,
339 ) -> Option<(Area, Option<Area>)> {
340 let (child, parent) = self.layouts.push(self.id, specs, on_files, cache)?;
341
342 Some((
343 Self::new(child, self.layouts.clone()),
344 parent.map(|parent| Self::new(parent, self.layouts.clone())),
345 ))
346 }
347
348 fn delete(&self, _: CoreAccess) -> (bool, Vec<Self>) {
349 let (do_rm_window, rm_areas) = self.layouts.delete(self.id);
350 (
351 do_rm_window,
352 rm_areas
353 .into_iter()
354 .map(|id| Self::new(id, self.layouts.clone()))
355 .collect(),
356 )
357 }
358
359 fn swap(&self, _: CoreAccess, rhs: &Self) -> bool {
360 self.layouts.swap(self.id, rhs.id)
361 }
362
363 fn spawn(
364 &self,
365 _: CoreAccess,
366 spawn_id: SpawnId,
367 specs: DynSpawnSpecs,
368 cache: Self::Cache,
369 ) -> Option<Self> {
370 Some(Self::new(
371 self.layouts
372 .spawn_on_widget(self.id, spawn_id, specs, cache)?,
373 self.layouts.clone(),
374 ))
375 }
376
377 fn set_width(&self, _: CoreAccess, width: f32) -> Result<(), Text> {
378 if self
379 .layouts
380 .set_constraints(self.id, Some(width), None, None)
381 {
382 Ok(())
383 } else {
384 Err(txt!("Couldn't set the width to [a]{width}"))
385 }
386 }
387
388 fn set_height(&self, _: CoreAccess, height: f32) -> Result<(), Text> {
389 if self
390 .layouts
391 .set_constraints(self.id, None, Some(height), None)
392 {
393 Ok(())
394 } else {
395 Err(txt!("Couldn't set the height to [a]{height}"))
396 }
397 }
398
399 fn hide(&self, _: CoreAccess) -> Result<(), Text> {
400 if self
401 .layouts
402 .set_constraints(self.id, None, None, Some(true))
403 {
404 Ok(())
405 } else {
406 Err(txt!("Couldn't hide the Area"))
407 }
408 }
409
410 fn reveal(&self, _: CoreAccess) -> Result<(), Text> {
411 if self
412 .layouts
413 .set_constraints(self.id, None, None, Some(false))
414 {
415 Ok(())
416 } else {
417 Err(txt!("Couldn't reveal the Area"))
418 }
419 }
420
421 fn width_of_text(&self, _: CoreAccess, opts: PrintOpts, text: &Text) -> Result<f32, Text> {
422 let max = self
423 .layouts
424 .inspect(self.id, |_, layout| layout.max_value())
425 .ok_or_else(|| txt!("This Area was already deleted"))?;
426
427 let iter = iter::print_iter(text, TwoPoints::default(), max.x, opts);
428
429 Ok(iter.map(|(c, _)| c.x + c.len).max().unwrap_or(0) as f32)
431 }
432
433 fn scroll_ver(&self, _: CoreAccess, text: &Text, by: i32, opts: PrintOpts) {
434 if by == 0 {
435 return;
436 }
437
438 let Some(coords) = self.layouts.coords_of(self.id, false) else {
439 context::warn!("This Area was already deleted");
440 return;
441 };
442
443 if coords.width() == 0 || coords.height() == 0 {
444 return;
445 }
446
447 let mut info = self.layouts.get_info_of(self.id).unwrap();
448 info.scroll_ver(by, coords, text, opts);
449 self.layouts.set_info_of(self.id, info);
450 }
451
452 fn scroll_around_points(&self, _: CoreAccess, text: &Text, points: TwoPoints, opts: PrintOpts) {
455 let Some(coords) = self.layouts.coords_of(self.id, false) else {
456 context::warn!("This Area was already deleted");
457 return;
458 };
459
460 if coords.width() == 0 || coords.height() == 0 {
461 return;
462 }
463
464 let mut info = self.layouts.get_info_of(self.id).unwrap();
465 info.scroll_around(points.real, coords, text, opts);
466 self.layouts.set_info_of(self.id, info);
467 }
468
469 fn scroll_to_points(&self, _: CoreAccess, text: &Text, points: TwoPoints, opts: PrintOpts) {
470 let Some(coords) = self.layouts.coords_of(self.id, false) else {
471 context::warn!("This Area was already deleted");
472 return;
473 };
474
475 if coords.width() == 0 || coords.height() == 0 {
476 return;
477 }
478
479 let mut info = self.layouts.get_info_of(self.id).unwrap();
480 info.scroll_to_points(points, coords, text, opts);
481 self.layouts.set_info_of(self.id, info);
482 }
483
484 fn set_as_active(&self, _: CoreAccess) {
485 self.layouts.set_active_id(self.id);
486 }
487
488 fn print(&self, _: CoreAccess, text: &Text, opts: PrintOpts, painter: Painter) {
489 self.print(text, opts, painter, |_, _| {})
490 }
491
492 fn print_with<'a>(
493 &self,
494 _: CoreAccess,
495 text: &Text,
496 opts: PrintOpts,
497 painter: Painter,
498 f: impl FnMut(&Caret, &Item) + 'a,
499 ) {
500 self.print(text, opts, painter, f)
501 }
502
503 fn set_print_info(&self, _: CoreAccess, info: Self::PrintInfo) {
506 self.layouts.set_info_of(self.id, info);
507 }
508
509 fn print_iter<'a>(
510 &self,
511 ca: CoreAccess,
512 text: &'a Text,
513 points: TwoPoints,
514 opts: PrintOpts,
515 ) -> impl Iterator<Item = (Caret, Item)> + 'a {
516 let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
517 print_iter(text, points, width, opts)
518 }
519
520 fn rev_print_iter<'a>(
521 &self,
522 ca: CoreAccess,
523 text: &'a Text,
524 points: TwoPoints,
525 opts: PrintOpts,
526 ) -> impl Iterator<Item = (Caret, Item)> + 'a {
527 let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
528 rev_print_iter(text, points, opts.wrap_width(width).unwrap_or(width), opts)
529 }
530
531 fn has_changed(&self, _: CoreAccess) -> bool {
532 self.layouts
533 .inspect(self.id, |rect, layout| rect.has_changed(layout))
534 .unwrap_or(false)
535 }
536
537 fn is_master_of(&self, _: CoreAccess, other: &Self) -> bool {
538 if other.id == self.id {
539 return true;
540 }
541
542 let mut parent_id = other.id;
543
544 self.layouts.inspect(self.id, |_, layout| {
545 while let Some((_, parent)) = layout.get_parent(parent_id) {
546 parent_id = parent.id();
547 if parent.id() == self.id {
548 break;
549 }
550 }
551 });
552
553 parent_id == self.id
554 }
555
556 fn get_cluster_master(&self, _: CoreAccess) -> Option<Self> {
557 let id = self
558 .layouts
559 .inspect(self.id, |_, layout| layout.get_cluster_master(self.id))??;
560
561 Some(Self {
562 layouts: self.layouts.clone(),
563 id,
564 ansi_codes: Arc::default(),
565 })
566 }
567
568 fn cache(&self, _: CoreAccess) -> Option<Self::Cache> {
569 let info = self.layouts.get_info_of(self.id)?.for_caching();
570 Some(info)
571 }
572
573 fn top_left(&self, _: CoreAccess) -> duat_core::ui::Coord {
574 self.layouts
575 .coords_of(self.id, false)
576 .map(|coords| duat_core::ui::Coord {
577 x: coords.tl.x as f32,
578 y: coords.tl.y as f32,
579 })
580 .unwrap_or_default()
581 }
582
583 fn bottom_right(&self, _: CoreAccess) -> duat_core::ui::Coord {
584 self.layouts
585 .coords_of(self.id, false)
586 .map(|coords| duat_core::ui::Coord {
587 x: coords.br.x as f32,
588 y: coords.br.y as f32,
589 })
590 .unwrap_or_default()
591 }
592
593 fn start_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints {
594 let Some(coords) = self.layouts.coords_of(self.id, false) else {
595 context::warn!("This Area was already deleted");
596 return Default::default();
597 };
598
599 let mut info = self.layouts.get_info_of(self.id).unwrap();
600 let start_points = info.start_points(coords, text, opts);
601 self.layouts.set_info_of(self.id, info);
602
603 start_points
604 }
605
606 fn end_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints {
607 let Some(coords) = self.layouts.coords_of(self.id, false) else {
608 context::warn!("This Area was already deleted");
609 return Default::default();
610 };
611
612 let mut info = self.layouts.get_info_of(self.id).unwrap();
613 let end_points = info.end_points(coords, text, opts);
614 self.layouts.set_info_of(self.id, info);
615
616 end_points
617 }
618
619 fn get_print_info(&self, _: CoreAccess) -> Self::PrintInfo {
620 self.layouts.get_info_of(self.id).unwrap_or_default()
621 }
622
623 fn is_active(&self, _: CoreAccess) -> bool {
624 self.layouts.get_active_id() == self.id
625 }
626}
627
628const fn get_control_str(char: char) -> Option<&'static str> {
629 match char {
630 '\0' => Some("^@"),
631 '\u{01}' => Some("^A"),
632 '\u{02}' => Some("^B"),
633 '\u{03}' => Some("^C"),
634 '\u{04}' => Some("^D"),
635 '\u{05}' => Some("^E"),
636 '\u{06}' => Some("^F"),
637 '\u{07}' => Some("^G"),
638 '\u{08}' => Some("^H"),
639 '\u{0b}' => Some("^K"),
640 '\u{0c}' => Some("^L"),
641 '\u{0e}' => Some("^N"),
642 '\u{0f}' => Some("^O"),
643 '\u{10}' => Some("^P"),
644 '\u{11}' => Some("^Q"),
645 '\u{12}' => Some("^R"),
646 '\u{13}' => Some("^S"),
647 '\u{14}' => Some("^T"),
648 '\u{15}' => Some("^U"),
649 '\u{16}' => Some("^V"),
650 '\u{17}' => Some("^W"),
651 '\u{18}' => Some("^X"),
652 '\u{19}' => Some("^Y"),
653 '\u{1a}' => Some("^Z"),
654 '\u{1b}' => Some("^["),
655 '\u{1c}' => Some("^\\"),
656 '\u{1d}' => Some("^]"),
657 '\u{1e}' => Some("^^"),
658 '\u{1f}' => Some("^_"),
659 '\u{80}' => Some("<80>"),
660 '\u{81}' => Some("<81>"),
661 '\u{82}' => Some("<82>"),
662 '\u{83}' => Some("<83>"),
663 '\u{84}' => Some("<84>"),
664 '\u{85}' => Some("<85>"),
665 '\u{86}' => Some("<86>"),
666 '\u{87}' => Some("<87>"),
667 '\u{88}' => Some("<88>"),
668 '\u{89}' => Some("<89>"),
669 '\u{8a}' => Some("<8a>"),
670 '\u{8b}' => Some("<8b>"),
671 '\u{8c}' => Some("<8c>"),
672 '\u{8d}' => Some("<8d>"),
673 '\u{8e}' => Some("<8e>"),
674 '\u{8f}' => Some("<8f>"),
675 '\u{90}' => Some("<90>"),
676 '\u{91}' => Some("<91>"),
677 '\u{92}' => Some("<92>"),
678 '\u{93}' => Some("<93>"),
679 '\u{94}' => Some("<94>"),
680 '\u{95}' => Some("<95>"),
681 '\u{96}' => Some("<96>"),
682 '\u{97}' => Some("<97>"),
683 '\u{98}' => Some("<98>"),
684 '\u{99}' => Some("<99>"),
685 '\u{9a}' => Some("<9a>"),
686 '\u{9b}' => Some("<9b>"),
687 '\u{9c}' => Some("<9c>"),
688 '\u{9d}' => Some("<9d>"),
689 '\u{9e}' => Some("<9e>"),
690 '\u{9f}' => Some("<9f>"),
691 _ => None,
692 }
693}