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