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, cache::{Decode, Encode}},
12 form::{CONTROL_CHAR_ID, Painter},
13 opts::PrintOpts,
14 session::TwoPointsPlace,
15 text::{Item, Part, Text, TwoPoints, txt},
16 ui::{
17 self, Caret, DynSpawnSpecs, PrintedLine, PushSpecs, SpawnId,
18 traits::{RawArea, UiPass},
19 },
20};
21use iter::{print_iter, rev_print_iter};
22
23pub use self::print_info::PrintInfo;
24use crate::{
25 AreaId, Mutex,
26 layout::{Frame, Layouts},
27 print_style,
28 printer::Lines,
29};
30
31#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
32#[bincode(crate = "duat_core::context::cache::bincode")]
33pub struct Coord {
34 pub x: u32,
35 pub y: u32,
36}
37
38impl std::fmt::Debug for Coord {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y))
41 }
42}
43
44impl Coord {
45 pub fn new(x: u32, y: u32) -> Coord {
46 Coord { x, y }
47 }
48}
49
50#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
51#[bincode(crate = "duat_core::context::cache::bincode")]
52pub struct Coords {
53 pub tl: Coord,
54 pub br: Coord,
55}
56
57impl Coords {
58 pub fn new(tl: Coord, br: Coord) -> Self {
59 Coords { tl, br }
60 }
61
62 pub fn width(&self) -> u32 {
63 self.br.x - self.tl.x
64 }
65
66 pub fn height(&self) -> u32 {
67 self.br.y - self.tl.y
68 }
69
70 pub fn area(&self) -> u32 {
71 self.width() * self.height()
72 }
73
74 pub fn intersects(&self, other: Self) -> bool {
75 self.tl.x < other.br.x
76 && self.br.x > other.tl.x
77 && self.tl.y < other.br.y
78 && self.br.y > other.tl.y
79 }
80
81 pub fn x_range(&self) -> std::ops::Range<u32> {
82 self.tl.x..self.br.x
83 }
84
85 pub fn y_range(&self) -> std::ops::Range<u32> {
86 self.tl.y..self.br.y
87 }
88}
89
90#[derive(Clone)]
91pub struct Area {
92 prev_print_info: Arc<Mutex<PrintInfo>>,
93 layouts: Layouts,
94 id: AreaId,
95 set_frame: fn(&mut Self, Frame),
96}
97
98impl PartialEq for Area {
99 fn eq(&self, other: &Self) -> bool {
100 self.id == other.id
101 }
102}
103
104impl Area {
105 pub(crate) fn new(id: AreaId, layouts: Layouts) -> Self {
107 Self {
108 prev_print_info: Arc::new(Mutex::default()),
109 layouts,
110 id,
111 set_frame: |area, frame| {
112 if !area.layouts.set_frame(area.id, frame) {
113 context::warn!("This Area was already deleted");
114 }
115 },
116 }
117 }
118
119 pub fn set_frame(&mut self, frame: Frame) {
124 (self.set_frame)(self, frame)
125 }
126
127 fn print(&self, text: &Text, opts: PrintOpts, painter: Painter) {
129 let Some(coords) = self.layouts.coords_of(self.id, true) else {
130 context::warn!("This Area was already deleted");
131 return;
132 };
133
134 let max = self
135 .layouts
136 .inspect(self.id, |_, layout| layout.max_value())
137 .unwrap();
138
139 if coords.width() == 0 || coords.height() == 0 {
140 return;
141 }
142
143 let (s_points, x_shift) = {
144 let mut info = self.layouts.get_info_of(self.id).unwrap();
145 let s_points = info.start_points(coords, text, opts);
146 *self.prev_print_info.lock().unwrap() = info;
147 self.layouts.set_info_of(self.id, info);
148 (s_points, info.x_shift())
149 };
150
151 let is_active = self.id == self.layouts.get_active_id();
152
153 let Some((lines, observed_spawns)) = print_text(
154 (text, opts, painter),
155 (coords, max),
156 (is_active, s_points, x_shift),
157 |lines, len| lines.write_all(&SPACES[..len as usize]).unwrap(),
158 |lines, len, max_x| {
159 if lines.coords().br.x == max_x {
160 lines.write_all(b"\x1b[0K").unwrap();
161 } else {
162 lines
163 .write_all(&SPACES[..(lines.coords().width() - len) as usize])
164 .unwrap();
165 }
166 lines.flush().unwrap();
167 },
168 |lines, spacer_len| lines.write_all(&SPACES[..spacer_len as usize]).unwrap(),
169 ) else {
170 return;
171 };
172
173 let spawns = text.get_spawned_ids();
174
175 self.layouts
176 .send_lines(self.id, lines, spawns, &observed_spawns);
177 }
178}
179
180impl RawArea for Area {
181 type Cache = PrintInfo;
182 type PrintInfo = PrintInfo;
183
184 fn push(
187 &self,
188 _: UiPass,
189 specs: PushSpecs,
190 on_files: bool,
191 cache: PrintInfo,
192 ) -> Option<(Area, Option<Area>)> {
193 let (child, parent) = self.layouts.push(self.id, specs, on_files, cache)?;
194
195 Some((
196 Self::new(child, self.layouts.clone()),
197 parent.map(|parent| Self::new(parent, self.layouts.clone())),
198 ))
199 }
200
201 fn delete(&self, _: UiPass) -> (bool, Vec<Self>) {
202 let (do_rm_window, rm_areas) = self.layouts.delete(self.id);
203 (
204 do_rm_window,
205 rm_areas
206 .into_iter()
207 .map(|id| Self::new(id, self.layouts.clone()))
208 .collect(),
209 )
210 }
211
212 fn swap(&self, _: UiPass, rhs: &Self) -> bool {
213 self.layouts.swap(self.id, rhs.id)
214 }
215
216 fn spawn(
217 &self,
218 _: UiPass,
219 spawn_id: SpawnId,
220 specs: DynSpawnSpecs,
221 cache: Self::Cache,
222 ) -> Option<Self> {
223 Some(Self::new(
224 self.layouts
225 .spawn_on_widget(self.id, spawn_id, specs, cache)?,
226 self.layouts.clone(),
227 ))
228 }
229
230 fn set_width(&self, _: UiPass, width: f32) -> Result<(), Text> {
231 if self
232 .layouts
233 .set_constraints(self.id, Some(width), None, None)
234 {
235 Ok(())
236 } else {
237 Err(txt!("This Area was already deleted"))
238 }
239 }
240
241 fn set_height(&self, _: UiPass, height: f32) -> Result<(), Text> {
242 if self
243 .layouts
244 .set_constraints(self.id, None, Some(height), None)
245 {
246 Ok(())
247 } else {
248 Err(txt!("This Area was already deleted"))
249 }
250 }
251
252 fn hide(&self, _: UiPass) -> Result<(), Text> {
253 if self
254 .layouts
255 .set_constraints(self.id, None, None, Some(true))
256 {
257 Ok(())
258 } else {
259 Err(txt!("This Area was already deleted"))
260 }
261 }
262
263 fn reveal(&self, _: UiPass) -> Result<(), Text> {
264 if self
265 .layouts
266 .set_constraints(self.id, None, None, Some(false))
267 {
268 Ok(())
269 } else {
270 Err(txt!("This Area was already deleted"))
271 }
272 }
273
274 fn size_of_text(&self, _: UiPass, opts: PrintOpts, text: &Text) -> Result<ui::Coord, Text> {
275 let max = self
276 .layouts
277 .inspect(self.id, |_, layout| layout.max_value())
278 .ok_or_else(|| txt!("This Area was already deleted"))?;
279
280 let iter = iter::print_iter(text, TwoPoints::default(), max.x, opts);
281
282 let mut max_x = 0;
283 let mut max_y = 0;
284 let mut width = 0;
285
286 for (caret, item) in iter {
287 if caret.wrap {
288 if max_y == max.y {
289 break;
290 }
291 max_x = width.max(max_x);
292 max_y += 1;
293 width = 0;
294 }
295 if item.part.is_char() {
296 width += caret.len;
297 }
298 }
299
300 Ok(ui::Coord::new(max_x.max(width) as f32, max_y as f32))
301 }
302
303 fn scroll_ver(&self, _: UiPass, text: &Text, by: i32, opts: PrintOpts) {
304 if by == 0 {
305 return;
306 }
307
308 let Some(coords) = self.layouts.coords_of(self.id, false) else {
309 context::warn!("This Area was already deleted");
310 return;
311 };
312
313 if coords.width() == 0 || coords.height() == 0 {
314 return;
315 }
316
317 let mut info = self.layouts.get_info_of(self.id).unwrap();
318 info.scroll_ver(by, coords, text, opts);
319 self.layouts.set_info_of(self.id, info);
320 }
321
322 fn scroll_around_points(&self, _: UiPass, text: &Text, points: TwoPoints, opts: PrintOpts) {
325 let Some(coords) = self.layouts.coords_of(self.id, false) else {
326 context::warn!("This Area was already deleted");
327 return;
328 };
329
330 if coords.width() == 0 || coords.height() == 0 {
331 return;
332 }
333
334 let mut info = self.layouts.get_info_of(self.id).unwrap();
335 info.scroll_around(points.real, coords, text, opts);
336 self.layouts.set_info_of(self.id, info);
337 }
338
339 fn scroll_to_points(&self, _: UiPass, text: &Text, points: TwoPoints, opts: PrintOpts) {
340 let Some(coords) = self.layouts.coords_of(self.id, false) else {
341 context::warn!("This Area was already deleted");
342 return;
343 };
344
345 if coords.width() == 0 || coords.height() == 0 {
346 return;
347 }
348
349 let mut info = self.layouts.get_info_of(self.id).unwrap();
350 info.scroll_to_points(points, coords, text, opts);
351 self.layouts.set_info_of(self.id, info);
352 }
353
354 fn set_as_active(&self, _: UiPass) {
355 self.layouts.set_active_id(self.id);
356 }
357
358 fn print(&self, _: UiPass, text: &Text, opts: PrintOpts, painter: Painter) {
359 self.print(text, opts, painter)
360 }
361
362 fn get_print_info(&self, _: UiPass) -> Self::PrintInfo {
365 self.layouts.get_info_of(self.id).unwrap_or_default()
366 }
367
368 fn set_print_info(&self, _: UiPass, info: Self::PrintInfo) {
369 self.layouts.set_info_of(self.id, info);
370 }
371
372 fn get_printed_lines(
373 &self,
374 pa: UiPass,
375 text: &Text,
376 opts: PrintOpts,
377 ) -> Option<Vec<ui::PrintedLine>> {
378 let coords = self.layouts.coords_of(self.id, true)?;
379 let points = self.start_points(pa, text, opts);
380
381 let mut prev_line = self
382 .rev_print_iter(pa, text, points, opts)
383 .find_map(|(caret, item)| caret.wrap.then_some(item.line()));
384
385 let mut printed_lines = Vec::new();
386 let mut has_wrapped = false;
387 let mut y = coords.tl.y;
388
389 for (caret, item) in print_iter(text, points, coords.width(), opts) {
390 if y == coords.br.y {
391 break;
392 }
393 y += caret.wrap as u32;
394
395 has_wrapped |= caret.wrap;
396 if has_wrapped && item.part.is_char() {
397 has_wrapped = false;
398 let number = item.line();
399 let is_wrapped = prev_line.is_some_and(|ll| ll == number);
400 prev_line = Some(number);
401 printed_lines.push(PrintedLine { number, is_wrapped });
402 }
403 }
404
405 Some(printed_lines)
406 }
407
408 fn print_iter<'a>(
409 &self,
410 ca: UiPass,
411 text: &'a Text,
412 points: TwoPoints,
413 opts: PrintOpts,
414 ) -> impl Iterator<Item = (Caret, Item)> + 'a {
415 let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
416 print_iter(text, points, width, opts)
417 }
418
419 fn rev_print_iter<'a>(
420 &self,
421 ca: UiPass,
422 text: &'a Text,
423 points: TwoPoints,
424 opts: PrintOpts,
425 ) -> impl Iterator<Item = (Caret, Item)> + 'a {
426 let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
427 rev_print_iter(text, points, opts.wrap_width(width).unwrap_or(width), opts)
428 }
429
430 fn has_changed(&self, _: UiPass) -> bool {
431 self.layouts
432 .inspect(self.id, |rect, layout| {
433 rect.has_changed(layout)
434 || rect
435 .print_info()
436 .is_some_and(|info| *info != *self.prev_print_info.lock().unwrap())
437 })
438 .unwrap_or(false)
439 }
440
441 fn is_master_of(&self, _: UiPass, other: &Self) -> bool {
442 if other.id == self.id {
443 return true;
444 }
445
446 let mut parent_id = other.id;
447
448 self.layouts.inspect(self.id, |_, layout| {
449 while let Some((_, parent)) = layout.get_parent(parent_id) {
450 parent_id = parent.id();
451 if parent.id() == self.id {
452 break;
453 }
454 }
455 });
456
457 parent_id == self.id
458 }
459
460 fn get_cluster_master(&self, _: UiPass) -> Option<Self> {
461 let id = self
462 .layouts
463 .inspect(self.id, |_, layout| layout.get_cluster_master(self.id))??;
464
465 Some(Self {
466 prev_print_info: Arc::default(),
467 layouts: self.layouts.clone(),
468 id,
469 set_frame: |area, frame| {
470 if !area.layouts.set_frame(area.id, frame) {
471 context::warn!("This Area was already deleted");
472 }
473 },
474 })
475 }
476
477 fn cache(&self, _: UiPass) -> Option<Self::Cache> {
478 let info = self.layouts.get_info_of(self.id)?.for_caching();
479 Some(info)
480 }
481
482 fn top_left(&self, _: UiPass) -> ui::Coord {
483 self.layouts.update(self.id);
484 self.layouts
485 .coords_of(self.id, false)
486 .map(|coords| ui::Coord {
487 x: coords.tl.x as f32,
488 y: coords.tl.y as f32,
489 })
490 .unwrap_or_default()
491 }
492
493 fn bottom_right(&self, _: UiPass) -> ui::Coord {
494 self.layouts.update(self.id);
495 self.layouts
496 .coords_of(self.id, false)
497 .map(|coords| ui::Coord {
498 x: coords.br.x as f32,
499 y: coords.br.y as f32,
500 })
501 .unwrap_or_default()
502 }
503
504 fn coord_at_points(
505 &self,
506 _: UiPass,
507 text: &Text,
508 points: TwoPoints,
509 opts: PrintOpts,
510 ) -> Option<ui::Coord> {
511 let Some(coords) = self.layouts.coords_of(self.id, false) else {
512 context::warn!("This Area was already deleted");
513 return None;
514 };
515
516 if coords.width() == 0 || coords.height() == 0 {
517 return None;
518 }
519
520 let (s_points, x_shift) = {
521 let mut info = self.layouts.get_info_of(self.id).unwrap();
522 let s_points = info.start_points(coords, text, opts);
523 self.layouts.set_info_of(self.id, info);
524 (s_points, info.x_shift())
525 };
526
527 let mut row = coords.tl.y;
528 for (caret, item) in print_iter(text, s_points, coords.width(), opts) {
529 row += caret.wrap as u32;
530
531 if row > coords.br.y {
532 break;
533 }
534
535 if item.points() == points && item.part.is_char() {
536 if caret.x >= x_shift && caret.x <= x_shift + coords.width() {
537 return Some(ui::Coord {
538 x: (coords.tl.x + caret.x - x_shift) as f32,
539 y: (row - 1) as f32,
540 });
541 } else {
542 break;
543 }
544 }
545 }
546
547 None
548 }
549
550 fn points_at_coord(
551 &self,
552 _: UiPass,
553 text: &Text,
554 coord: ui::Coord,
555 opts: PrintOpts,
556 ) -> Option<TwoPointsPlace> {
557 let Some(coords) = self.layouts.coords_of(self.id, false) else {
558 context::warn!("This Area was already deleted");
559 return None;
560 };
561
562 if coords.width() == 0 || coords.height() == 0 {
563 return None;
564 } else if !(coords.tl.x..coords.br.x).contains(&(coord.x as u32))
565 || !(coords.tl.y..coords.br.y).contains(&(coord.y as u32))
566 {
567 context::warn!("Coordinate not contained in area");
568 return None;
569 }
570
571 let (s_points, x_shift) = {
572 let mut info = self.layouts.get_info_of(self.id).unwrap();
573 let s_points = info.start_points(coords, text, opts);
574 self.layouts.set_info_of(self.id, info);
575 (s_points, info.x_shift())
576 };
577
578 let mut row = coords.tl.y;
579 let mut backup = None;
580 for (caret, item) in print_iter(text, s_points, coords.width(), opts) {
581 row += caret.wrap as u32;
582
583 if row > coord.y as u32 + 1 {
584 return backup;
585 } else if row == coord.y as u32 + 1
586 && let Some(col) = caret.x.checked_sub(x_shift)
587 {
588 if (coords.tl.x + col..coords.tl.x + col + caret.len).contains(&(coord.x as u32)) {
589 return Some(TwoPointsPlace::Within(item.points()));
590 } else if coords.tl.x + col >= coord.x as u32 {
591 return backup;
592 }
593 }
594
595 if item.part.is_char() {
596 backup = Some(TwoPointsPlace::AheadOf(item.points()));
597 }
598 }
599
600 None
601 }
602
603 fn start_points(&self, _: UiPass, text: &Text, opts: PrintOpts) -> TwoPoints {
604 if !self.layouts.update(self.id) {
605 context::warn!("This Area was already deleted");
606 return Default::default();
607 }
608 let coords = self.layouts.coords_of(self.id, false).unwrap();
609
610 let mut info = self.layouts.get_info_of(self.id).unwrap();
611 let start_points = info.start_points(coords, text, opts);
612 self.layouts.set_info_of(self.id, info);
613
614 start_points
615 }
616
617 fn end_points(&self, _: UiPass, text: &Text, opts: PrintOpts) -> TwoPoints {
618 let Some(coords) = self.layouts.coords_of(self.id, false) else {
619 context::warn!("This Area was already deleted");
620 return Default::default();
621 };
622
623 let mut info = self.layouts.get_info_of(self.id).unwrap();
624 let end_points = info.end_points(coords, text, opts);
625 self.layouts.set_info_of(self.id, info);
626
627 end_points
628 }
629
630 fn is_active(&self, _: UiPass) -> bool {
631 self.layouts.get_active_id() == self.id
632 }
633}
634
635const fn get_control_str(char: char) -> Option<&'static str> {
636 match char {
637 '\0' => Some("^@"),
638 '\u{01}' => Some("^A"),
639 '\u{02}' => Some("^B"),
640 '\u{03}' => Some("^C"),
641 '\u{04}' => Some("^D"),
642 '\u{05}' => Some("^E"),
643 '\u{06}' => Some("^F"),
644 '\u{07}' => Some("^G"),
645 '\u{08}' => Some("^H"),
646 '\u{0b}' => Some("^K"),
647 '\u{0c}' => Some("^L"),
648 '\u{0e}' => Some("^N"),
649 '\u{0f}' => Some("^O"),
650 '\u{10}' => Some("^P"),
651 '\u{11}' => Some("^Q"),
652 '\u{12}' => Some("^R"),
653 '\u{13}' => Some("^S"),
654 '\u{14}' => Some("^T"),
655 '\u{15}' => Some("^U"),
656 '\u{16}' => Some("^V"),
657 '\u{17}' => Some("^W"),
658 '\u{18}' => Some("^X"),
659 '\u{19}' => Some("^Y"),
660 '\u{1a}' => Some("^Z"),
661 '\u{1b}' => Some("^["),
662 '\u{1c}' => Some("^\\"),
663 '\u{1d}' => Some("^]"),
664 '\u{1e}' => Some("^^"),
665 '\u{1f}' => Some("^_"),
666 '\u{80}' => Some("<80>"),
667 '\u{81}' => Some("<81>"),
668 '\u{82}' => Some("<82>"),
669 '\u{83}' => Some("<83>"),
670 '\u{84}' => Some("<84>"),
671 '\u{85}' => Some("<85>"),
672 '\u{86}' => Some("<86>"),
673 '\u{87}' => Some("<87>"),
674 '\u{88}' => Some("<88>"),
675 '\u{89}' => Some("<89>"),
676 '\u{8a}' => Some("<8a>"),
677 '\u{8b}' => Some("<8b>"),
678 '\u{8c}' => Some("<8c>"),
679 '\u{8d}' => Some("<8d>"),
680 '\u{8e}' => Some("<8e>"),
681 '\u{8f}' => Some("<8f>"),
682 '\u{90}' => Some("<90>"),
683 '\u{91}' => Some("<91>"),
684 '\u{92}' => Some("<92>"),
685 '\u{93}' => Some("<93>"),
686 '\u{94}' => Some("<94>"),
687 '\u{95}' => Some("<95>"),
688 '\u{96}' => Some("<96>"),
689 '\u{97}' => Some("<97>"),
690 '\u{98}' => Some("<98>"),
691 '\u{99}' => Some("<99>"),
692 '\u{9a}' => Some("<9a>"),
693 '\u{9b}' => Some("<9b>"),
694 '\u{9c}' => Some("<9c>"),
695 '\u{9d}' => Some("<9d>"),
696 '\u{9e}' => Some("<9e>"),
697 '\u{9f}' => Some("<9f>"),
698 _ => None,
699 }
700}
701
702#[allow(clippy::type_complexity)]
704pub fn print_text(
705 (text, opts, mut painter): (&Text, PrintOpts, Painter),
706 (coords, max): (Coords, Coord),
707 (is_active, s_points, x_shift): (bool, TwoPoints, u32),
708 start_line: fn(&mut Lines, u32),
709 end_line: fn(&mut Lines, u32, u32),
710 print_spacer: fn(&mut Lines, u32),
711) -> Option<(Lines, Vec<(SpawnId, Coord, u32)>)> {
712 fn print_end_style(lines: &mut Lines, painter: &Painter) {
713 let mut default = painter.get_default();
714 default.style.foreground_color = None;
715 default.style.underline_color = None;
716 default.style.attributes = Attributes::from(Attribute::Reset);
717 print_style(lines, default.style);
718 }
719
720 let (mut lines, iter, x_shift, max_x) = {
721 if coords.width() == 0 || coords.height() == 0 {
722 return None;
723 }
724
725 let lines = Lines::new(coords);
726 let width = opts.wrap_width(coords.width()).unwrap_or(coords.width());
727 let iter = print_iter(text, s_points, width, opts);
728
729 (lines, iter, x_shift, max.x)
730 };
731
732 let mut observed_spawns = Vec::new();
733
734 let mut style_was_set = false;
735
736 enum Cursor {
737 Main,
738 Extra,
739 }
740
741 let tl_y = lines.coords().tl.y;
743 let mut y = tl_y;
744 let mut cursor = None;
745 let mut spawns_for_next: Vec<SpawnId> = Vec::new();
746 let mut last_len = 0;
747
748 for (caret, item) in iter {
749 let (painter, lines) = (&mut painter, &mut lines);
750
751 let Caret { x, len, wrap } = caret;
752 let Item { part, .. } = item;
753
754 if wrap {
755 if y == lines.coords().br.y {
756 break;
757 }
758 if y > lines.coords().tl.y {
759 print_end_style(lines, painter);
760 end_line(lines, last_len, max_x);
761 }
762 let initial_space = x.saturating_sub(x_shift).min(lines.coords().width());
763 if initial_space > 0 {
764 let mut default_style = painter.get_default().style;
765 default_style.attributes.set(Attribute::Reset);
766 print_style(lines, default_style);
767 start_line(lines, initial_space);
768 }
769 y += 1;
770
771 painter.reset_prev_style();
773 style_was_set = true;
774 last_len = initial_space;
775 }
776
777 let is_contained = x + len > x_shift && x < x_shift + lines.coords().width();
778
779 match part {
780 Part::Char(char) if is_contained => {
781 if let Some(str) = get_control_str(char) {
782 painter.apply(CONTROL_CHAR_ID, 100);
783 if style_was_set && let Some(style) = painter.relative_style() {
784 print_style(lines, style);
785 }
786 lines.write_all(str.as_bytes()).unwrap();
787 painter.remove(CONTROL_CHAR_ID)
788 } else {
789 if style_was_set && let Some(style) = painter.relative_style() {
790 print_style(lines, style);
791 }
792 match char {
793 '\t' => {
794 let truncated_start = x_shift.saturating_sub(x);
795 let truncated_end =
796 (x + len).saturating_sub(lines.coords().width() + x_shift);
797 let tab_len = len - (truncated_start + truncated_end);
798 lines.write_all(&SPACES[..tab_len as usize]).unwrap()
799 }
800 '\n' if len == 1 => lines.write_all(b" ").unwrap(),
801 '\n' | '\r' => {}
802 char => {
803 let mut bytes = [0; 4];
804 char.encode_utf8(&mut bytes);
805 lines.write_all(&bytes[..char.len_utf8()]).unwrap();
806 }
807 }
808 }
809
810 if let Some(cursor) = cursor.take() {
811 match cursor {
812 Cursor::Main => painter.remove_main_caret(),
813 Cursor::Extra => painter.remove_extra_caret(),
814 }
815 if let Some(style) = painter.relative_style() {
816 print_style(lines, style)
817 }
818 }
819 for id in spawns_for_next.drain(..) {
820 observed_spawns.push((
821 id,
822 Coord::new(lines.coords().tl.x + x - x_shift, y - 1),
823 len,
824 ));
825 }
826
827 last_len = x + len - x_shift;
828 style_was_set = false;
829 }
830 Part::Char(_) => {
831 match cursor.take() {
832 Some(Cursor::Main) => painter.remove_main_caret(),
833 Some(Cursor::Extra) => painter.remove_extra_caret(),
834 None => {}
835 }
836 spawns_for_next.clear();
837 }
838 Part::PushForm(id, prio) => {
839 painter.apply(id, prio);
840 style_was_set = true;
841 }
842 Part::PopForm(id) => {
843 painter.remove(id);
844 style_was_set = true;
845 }
846 Part::MainCaret => {
847 if let Some(shape) = painter.main_cursor()
848 && is_active
849 {
850 lines.show_real_cursor();
851 queue!(lines, shape, cursor::SavePosition).unwrap();
852 } else {
853 cursor = Some(Cursor::Main);
854 lines.hide_real_cursor();
855 painter.apply_main_cursor();
856 style_was_set = true;
857 }
858 }
859 Part::ExtraCaret => {
860 cursor = Some(Cursor::Extra);
861 painter.apply_extra_cursor();
862 style_was_set = true;
863 }
864 Part::Spacer => {
865 let truncated_start = x_shift.saturating_sub(x).min(len);
866 let truncated_end = (x + len)
867 .saturating_sub(lines.coords().width().saturating_sub(x_shift))
868 .min(len);
869 let spacer_len = len - (truncated_start + truncated_end);
870 print_spacer(lines, spacer_len);
871 last_len = (x + len)
872 .saturating_sub(x_shift)
873 .min(lines.coords().width());
874 }
875 Part::ResetState => print_style(lines, painter.reset()),
876 Part::SpawnedWidget(id) => spawns_for_next.push(id),
877 Part::ToggleStart(_) | Part::ToggleEnd(_) => {
878 todo!("Toggles have not been implemented yet.")
879 }
880 }
881 }
882
883 print_end_style(&mut lines, &painter);
884 end_line(&mut lines, last_len, max_x);
885
886 for _ in 0..lines.coords().br.y - y {
887 print_end_style(&mut lines, &painter);
888 end_line(&mut lines, 0, max_x);
889 }
890
891 Some((lines, observed_spawns))
892}
893
894const SPACES: &[u8] = &[b' '; 3000];