1pub mod chop;
8pub mod compose;
9pub mod cuts;
10pub mod layer;
11pub mod zorder;
12
13pub use layer::{CompositorError, CompositorRegion, Layer};
14
15use crate::buffer::ScreenBuffer;
16use crate::cell::Cell;
17use crate::geometry::{Rect, Size};
18use crate::segment::Segment;
19use unicode_segmentation::UnicodeSegmentation;
20use unicode_width::UnicodeWidthStr;
21
22pub struct Compositor {
24 layers: Vec<Layer>,
25 screen_width: u16,
26 screen_height: u16,
27}
28
29impl Compositor {
30 pub fn new(width: u16, height: u16) -> Self {
32 Self {
33 layers: Vec::new(),
34 screen_width: width,
35 screen_height: height,
36 }
37 }
38
39 pub fn clear(&mut self) {
41 self.layers.clear();
42 }
43
44 pub fn add_layer(&mut self, layer: Layer) {
46 self.layers.push(layer);
47 }
48
49 pub fn add_widget(
53 &mut self,
54 widget_id: u64,
55 region: Rect,
56 z_index: i32,
57 lines: Vec<Vec<Segment>>,
58 ) {
59 let layer = Layer::new(widget_id, region, z_index, lines);
60 self.add_layer(layer);
61 }
62
63 pub fn layer_count(&self) -> usize {
65 self.layers.len()
66 }
67
68 pub fn screen_size(&self) -> Size {
70 Size::new(self.screen_width, self.screen_height)
71 }
72
73 pub fn resize(&mut self, width: u16, height: u16) {
77 self.screen_width = width;
78 self.screen_height = height;
79 self.layers.clear();
80 }
81
82 pub fn layers(&self) -> &[Layer] {
84 &self.layers
85 }
86
87 pub fn compose(&self, buf: &mut ScreenBuffer) {
92 for row in 0..self.screen_height {
93 let segments = compose::compose_line(&self.layers, row, self.screen_width);
94 self.write_segments_to_buffer(buf, row, &segments);
95 }
96 }
97
98 fn write_segments_to_buffer(&self, buf: &mut ScreenBuffer, row: u16, segments: &[Segment]) {
101 let mut x = 0;
102
103 for segment in segments {
104 if segment.is_control {
106 continue;
107 }
108
109 for grapheme in segment.text.graphemes(true) {
111 if x >= self.screen_width {
112 return; }
114
115 let width = UnicodeWidthStr::width(grapheme);
116 let cell = Cell::new(grapheme, segment.style.clone());
117 buf.set(x, row, cell);
118 x += width as u16;
119 }
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::geometry::Rect;
128 use crate::segment::Segment;
129
130 #[test]
131 fn new_compositor_empty() {
132 let compositor = Compositor::new(80, 24);
133 assert!(compositor.layer_count() == 0);
134 }
135
136 #[test]
137 fn add_layer_increases_count() {
138 let mut compositor = Compositor::new(80, 24);
139 let region = Rect::new(0, 0, 10, 5);
140 let layer = Layer::new(1, region, 0, vec![]);
141
142 compositor.add_layer(layer);
143 assert!(compositor.layer_count() == 1);
144 }
145
146 #[test]
147 fn add_multiple_layers() {
148 let mut compositor = Compositor::new(80, 24);
149 let region1 = Rect::new(0, 0, 10, 5);
150 let region2 = Rect::new(10, 10, 20, 10);
151 let region3 = Rect::new(30, 5, 15, 8);
152
153 compositor.add_layer(Layer::new(1, region1, 0, vec![]));
154 compositor.add_layer(Layer::new(2, region2, 1, vec![]));
155 compositor.add_layer(Layer::new(3, region3, 2, vec![]));
156
157 assert!(compositor.layer_count() == 3);
158 }
159
160 #[test]
161 fn add_widget_convenience() {
162 let mut compositor = Compositor::new(80, 24);
163 let region = Rect::new(5, 10, 20, 15);
164 let lines = vec![vec![Segment::new("test")]];
165
166 compositor.add_widget(42, region, 5, lines);
167
168 assert!(compositor.layer_count() == 1);
169 let layer_slice = compositor.layers();
170 assert!(layer_slice.len() == 1);
171 let layer = match layer_slice.first() {
172 Some(l) => l,
173 None => unreachable!(),
174 };
175 assert!(layer.widget_id == 42);
176 assert!(layer.z_index == 5);
177 assert!(layer.region == region);
178 }
179
180 #[test]
181 fn clear_removes_all() {
182 let mut compositor = Compositor::new(80, 24);
183 let region1 = Rect::new(0, 0, 10, 5);
184 let region2 = Rect::new(10, 10, 20, 10);
185
186 compositor.add_layer(Layer::new(1, region1, 0, vec![]));
187 compositor.add_layer(Layer::new(2, region2, 1, vec![]));
188 assert!(compositor.layer_count() == 2);
189
190 compositor.clear();
191 assert!(compositor.layer_count() == 0);
192 }
193
194 #[test]
195 fn screen_size_accessible() {
196 let compositor = Compositor::new(100, 50);
197 let size = compositor.screen_size();
198 assert!(size.width == 100);
199 assert!(size.height == 50);
200 }
201
202 #[test]
203 fn layers_accessible() {
204 let mut compositor = Compositor::new(80, 24);
205 let region1 = Rect::new(0, 0, 10, 5);
206 let region2 = Rect::new(10, 10, 20, 10);
207
208 compositor.add_layer(Layer::new(1, region1, 0, vec![]));
209 compositor.add_layer(Layer::new(2, region2, 1, vec![]));
210
211 let layers = compositor.layers();
212 assert!(layers.len() == 2);
213 assert!(layers[0].widget_id == 1);
214 assert!(layers[1].widget_id == 2);
215 }
216
217 #[test]
218 fn compose_single_layer_to_buffer() {
219 use crate::geometry::Size;
220
221 let mut compositor = Compositor::new(80, 10);
222 let region = Rect::new(0, 0, 80, 10);
223 let lines = vec![vec![Segment::new("Hello, World!")]];
224 compositor.add_layer(Layer::new(1, region, 0, lines));
225
226 let mut buf = ScreenBuffer::new(Size::new(80, 10));
227 compositor.compose(&mut buf);
228
229 assert!(buf.get(0, 0).is_some());
231 match buf.get(0, 0) {
232 Some(cell) => {
233 assert!(cell.grapheme == "H");
234 }
235 None => unreachable!(),
236 }
237
238 match buf.get(1, 0) {
240 Some(cell) => {
241 assert!(cell.grapheme == "e");
242 }
243 None => unreachable!(),
244 }
245 }
246
247 #[test]
248 fn compose_overlapping_layers_to_buffer() {
249 use crate::geometry::Size;
250
251 let mut compositor = Compositor::new(80, 10);
252
253 let bg_region = Rect::new(0, 0, 80, 10);
255 let bg_lines = vec![vec![Segment::new("Background")]];
256 compositor.add_layer(Layer::new(1, bg_region, 0, bg_lines));
257
258 let overlay_region = Rect::new(5, 0, 20, 10);
260 let overlay_lines = vec![vec![Segment::new("Overlay")]];
261 compositor.add_layer(Layer::new(2, overlay_region, 10, overlay_lines));
262
263 let mut buf = ScreenBuffer::new(Size::new(80, 10));
264 compositor.compose(&mut buf);
265
266 match buf.get(0, 0) {
268 Some(cell) => {
269 assert!(cell.grapheme == "B");
270 }
271 None => unreachable!(),
272 }
273
274 match buf.get(5, 0) {
276 Some(cell) => {
277 assert!(cell.grapheme == "O");
278 }
279 None => unreachable!(),
280 }
281 }
282
283 #[test]
284 fn compose_correct_cell_styles() {
285 use crate::color::{Color, NamedColor};
286 use crate::geometry::Size;
287 use crate::style::Style;
288
289 let mut compositor = Compositor::new(80, 10);
290 let style = Style {
291 fg: Some(Color::Named(NamedColor::Red)),
292 bold: true,
293 ..Default::default()
294 };
295
296 let mut seg = Segment::new("Styled");
297 seg.style = style.clone();
298
299 let region = Rect::new(0, 0, 20, 10);
300 let lines = vec![vec![seg]];
301 compositor.add_layer(Layer::new(1, region, 0, lines));
302
303 let mut buf = ScreenBuffer::new(Size::new(80, 10));
304 compositor.compose(&mut buf);
305
306 match buf.get(0, 0) {
308 Some(cell) => {
309 assert!(cell.style.bold);
310 assert!(matches!(cell.style.fg, Some(Color::Named(NamedColor::Red))));
311 }
312 None => unreachable!(),
313 }
314 }
315
316 #[test]
317 fn compose_empty_compositor_all_blank() {
318 use crate::geometry::Size;
319
320 let compositor = Compositor::new(80, 10);
321 let mut buf = ScreenBuffer::new(Size::new(80, 10));
322
323 compositor.compose(&mut buf);
324
325 for y in 0..10 {
327 for x in 0..80 {
328 match buf.get(x, y) {
329 Some(cell) => {
330 assert!(cell.is_blank());
331 }
332 None => unreachable!(),
333 }
334 }
335 }
336 }
337
338 #[test]
339 fn compose_wide_characters() {
340 use crate::geometry::Size;
341
342 let mut compositor = Compositor::new(80, 10);
343 let region = Rect::new(0, 0, 20, 10);
344 let lines = vec![vec![Segment::new("\u{4e16}界")]]; compositor.add_layer(Layer::new(1, region, 0, lines));
347
348 let mut buf = ScreenBuffer::new(Size::new(80, 10));
349 compositor.compose(&mut buf);
350
351 match buf.get(0, 0) {
353 Some(cell) => {
354 assert!(cell.grapheme == "\u{4e16}");
355 assert!(cell.width == 2);
356 }
357 None => unreachable!(),
358 }
359
360 match buf.get(1, 0) {
362 Some(cell) => {
363 assert!(cell.width == 0);
364 }
365 None => unreachable!(),
366 }
367
368 match buf.get(2, 0) {
370 Some(cell) => {
371 assert!(cell.grapheme == "界");
372 assert!(cell.width == 2);
373 }
374 None => unreachable!(),
375 }
376 }
377}
378
379#[cfg(test)]
380mod integration_tests {
381 use super::*;
382 use crate::color::{Color, NamedColor};
383 use crate::geometry::{Rect, Size};
384 use crate::segment::Segment;
385 use crate::style::Style;
386
387 #[test]
388 fn integration_chat_layout() {
389 let mut compositor = Compositor::new(80, 24);
390
391 let header_region = Rect::new(0, 0, 80, 1);
393 let header_lines = vec![vec![Segment::new("Chat App Header")]];
394 compositor.add_layer(Layer::new(1, header_region, 0, header_lines));
395
396 let messages_region = Rect::new(0, 1, 80, 20);
398 let messages_lines = vec![vec![Segment::new("Message 1")]];
399 compositor.add_layer(Layer::new(2, messages_region, 0, messages_lines));
400
401 let input_region = Rect::new(0, 21, 80, 3);
403 let input_lines = vec![vec![Segment::new("Type here...")]];
404 compositor.add_layer(Layer::new(3, input_region, 0, input_lines));
405
406 let modal_region = Rect::new(20, 8, 40, 8);
408 let modal_lines = vec![vec![Segment::new("Modal Dialog")]];
409 compositor.add_layer(Layer::new(4, modal_region, 10, modal_lines));
410
411 let mut buf = ScreenBuffer::new(Size::new(80, 24));
412 compositor.compose(&mut buf);
413
414 match buf.get(0, 0) {
416 Some(cell) => {
417 assert!(cell.grapheme == "C");
418 }
419 None => unreachable!(),
420 }
421
422 match buf.get(20, 8) {
424 Some(cell) => {
425 assert!(cell.grapheme == "M");
426 }
427 None => unreachable!(),
428 }
429
430 match buf.get(0, 21) {
432 Some(cell) => {
433 assert!(cell.grapheme == "T");
434 }
435 None => unreachable!(),
436 }
437 }
438
439 #[test]
440 fn integration_three_overlapping_windows() {
441 let mut compositor = Compositor::new(80, 24);
442
443 let window1_region = Rect::new(0, 0, 40, 20);
445 let window1_lines = vec![vec![Segment::new("Window 1")]];
446 compositor.add_layer(Layer::new(1, window1_region, 0, window1_lines));
447
448 let window2_region = Rect::new(20, 5, 40, 15);
450 let window2_lines = vec![vec![Segment::new("Window 2")]];
451 compositor.add_layer(Layer::new(2, window2_region, 5, window2_lines));
452
453 let window3_region = Rect::new(30, 10, 30, 10);
455 let window3_lines = vec![vec![Segment::new("Window 3")]];
456 compositor.add_layer(Layer::new(3, window3_region, 10, window3_lines));
457
458 let mut buf = ScreenBuffer::new(Size::new(80, 24));
459 compositor.compose(&mut buf);
460
461 match buf.get(0, 0) {
463 Some(cell) => {
464 assert!(cell.grapheme == "W");
465 }
466 None => unreachable!(),
467 }
468
469 match buf.get(20, 5) {
471 Some(cell) => {
472 assert!(cell.grapheme == "W");
473 }
474 None => unreachable!(),
475 }
476
477 match buf.get(30, 10) {
479 Some(cell) => {
480 assert!(cell.grapheme == "W");
481 }
482 None => unreachable!(),
483 }
484 }
485
486 #[test]
487 fn integration_styled_segments_preserved() {
488 let mut compositor = Compositor::new(80, 24);
489
490 let red_style = Style {
491 fg: Some(Color::Named(NamedColor::Red)),
492 bold: true,
493 ..Default::default()
494 };
495
496 let blue_style = Style {
497 fg: Some(Color::Named(NamedColor::Blue)),
498 italic: true,
499 ..Default::default()
500 };
501
502 let mut red_seg = Segment::new("Red ");
503 red_seg.style = red_style.clone();
504
505 let mut blue_seg = Segment::new("Blue");
506 blue_seg.style = blue_style.clone();
507
508 let region = Rect::new(0, 0, 40, 10);
509 let lines = vec![vec![red_seg, blue_seg]];
510 compositor.add_layer(Layer::new(1, region, 0, lines));
511
512 let mut buf = ScreenBuffer::new(Size::new(80, 24));
513 compositor.compose(&mut buf);
514
515 match buf.get(0, 0) {
517 Some(cell) => {
518 assert!(cell.style.bold);
519 assert!(matches!(cell.style.fg, Some(Color::Named(NamedColor::Red))));
520 }
521 None => unreachable!(),
522 }
523
524 match buf.get(4, 0) {
526 Some(cell) => {
527 assert!(cell.style.italic);
528 assert!(matches!(
529 cell.style.fg,
530 Some(Color::Named(NamedColor::Blue))
531 ));
532 }
533 None => unreachable!(),
534 }
535 }
536
537 #[test]
538 fn integration_resize_recompose() {
539 let mut compositor1 = Compositor::new(80, 24);
541 let region1 = Rect::new(0, 0, 40, 10);
542 let lines1 = vec![vec![Segment::new("Test")]];
543 compositor1.add_layer(Layer::new(1, region1, 0, lines1));
544
545 let mut buf1 = ScreenBuffer::new(Size::new(80, 24));
546 compositor1.compose(&mut buf1);
547
548 match buf1.get(0, 0) {
549 Some(cell) => {
550 assert!(cell.grapheme == "T");
551 }
552 None => unreachable!(),
553 }
554
555 let mut compositor2 = Compositor::new(120, 30);
557 let region2 = Rect::new(0, 0, 60, 15);
558 let lines2 = vec![vec![Segment::new("Resized")]];
559 compositor2.add_layer(Layer::new(1, region2, 0, lines2));
560
561 let mut buf2 = ScreenBuffer::new(Size::new(120, 30));
562 compositor2.compose(&mut buf2);
563
564 match buf2.get(0, 0) {
565 Some(cell) => {
566 assert!(cell.grapheme == "R");
567 }
568 None => unreachable!(),
569 }
570
571 assert!(buf1.width() == 80);
573 assert!(buf2.width() == 120);
574 }
575}
576
577#[cfg(test)]
578mod advanced_integration_tests {
579 use super::*;
580 use crate::color::{Color, NamedColor};
581 use crate::geometry::{Rect, Size};
582 use crate::segment::Segment;
583 use crate::style::Style;
584
585 #[test]
587 fn syntax_highlighted_code() {
588 let mut compositor = Compositor::new(40, 5);
589
590 let keyword_style = Style::new().fg(Color::Named(NamedColor::Blue)).bold(true);
591 let ident_style = Style::new().fg(Color::Named(NamedColor::White));
592 let paren_style = Style::new().fg(Color::Named(NamedColor::Yellow));
593
594 let segments = vec![
595 Segment::styled("fn", keyword_style.clone()),
596 Segment::styled(" ", Style::default()),
597 Segment::styled("main", ident_style.clone()),
598 Segment::styled("()", paren_style.clone()),
599 ];
600
601 let region = Rect::new(0, 0, 40, 5);
602 compositor.add_layer(Layer::new(1, region, 0, vec![segments]));
603
604 let mut buf = ScreenBuffer::new(Size::new(40, 5));
605 compositor.compose(&mut buf);
606
607 match buf.get(0, 0) {
609 Some(cell) => {
610 assert!(cell.grapheme == "f");
611 assert!(cell.style.bold);
612 assert!(matches!(
613 cell.style.fg,
614 Some(Color::Named(NamedColor::Blue))
615 ));
616 }
617 None => unreachable!(),
618 }
619 match buf.get(1, 0) {
620 Some(cell) => {
621 assert!(cell.grapheme == "n");
622 assert!(cell.style.bold);
623 }
624 None => unreachable!(),
625 }
626
627 match buf.get(2, 0) {
629 Some(cell) => {
630 assert!(cell.grapheme == " ");
631 }
632 None => unreachable!(),
633 }
634
635 match buf.get(3, 0) {
637 Some(cell) => {
638 assert!(cell.grapheme == "m");
639 assert!(matches!(
640 cell.style.fg,
641 Some(Color::Named(NamedColor::White))
642 ));
643 }
644 None => unreachable!(),
645 }
646
647 match buf.get(7, 0) {
649 Some(cell) => {
650 assert!(cell.grapheme == "(");
651 assert!(matches!(
652 cell.style.fg,
653 Some(Color::Named(NamedColor::Yellow))
654 ));
655 }
656 None => unreachable!(),
657 }
658 }
659
660 #[test]
662 fn overlapping_styled_windows() {
663 let mut compositor = Compositor::new(20, 5);
664
665 let green_bg = Style::new().bg(Color::Named(NamedColor::Green));
667 let green_line = vec![Segment::styled("GGGGGGGGGGGGGGGGGGG", green_bg.clone())];
668 let bottom_region = Rect::new(0, 0, 20, 5);
669 let bottom_lines = vec![green_line.clone(); 5];
670 compositor.add_layer(Layer::new(1, bottom_region, 0, bottom_lines));
671
672 let blue_bg = Style::new().bg(Color::Named(NamedColor::Blue));
674 let blue_line = vec![Segment::styled("BBBBB", blue_bg.clone())];
675 let top_region = Rect::new(5, 1, 10, 3);
676 let top_lines = vec![blue_line.clone(); 3];
677 compositor.add_layer(Layer::new(2, top_region, 5, top_lines));
678
679 let mut buf = ScreenBuffer::new(Size::new(20, 5));
680 compositor.compose(&mut buf);
681
682 match buf.get(5, 0) {
684 Some(cell) => {
685 assert!(matches!(
686 cell.style.bg,
687 Some(Color::Named(NamedColor::Green))
688 ));
689 }
690 None => unreachable!(),
691 }
692
693 match buf.get(5, 1) {
695 Some(cell) => {
696 assert!(matches!(
697 cell.style.bg,
698 Some(Color::Named(NamedColor::Blue))
699 ));
700 }
701 None => unreachable!(),
702 }
703
704 match buf.get(0, 1) {
706 Some(cell) => {
707 assert!(matches!(
708 cell.style.bg,
709 Some(Color::Named(NamedColor::Green))
710 ));
711 }
712 None => unreachable!(),
713 }
714 }
715
716 #[test]
718 fn cjk_text_in_compositor() {
719 let mut compositor = Compositor::new(20, 3);
720 let region = Rect::new(0, 0, 20, 3);
721 let lines = vec![vec![Segment::new("\u{4e16}\u{754c}")]];
723 compositor.add_layer(Layer::new(1, region, 0, lines));
724
725 let mut buf = ScreenBuffer::new(Size::new(20, 3));
726 compositor.compose(&mut buf);
727
728 match buf.get(0, 0) {
730 Some(cell) => {
731 assert!(cell.grapheme == "\u{4e16}");
732 assert!(cell.width == 2);
733 }
734 None => unreachable!(),
735 }
736
737 match buf.get(1, 0) {
739 Some(cell) => {
740 assert!(cell.width == 0);
741 }
742 None => unreachable!(),
743 }
744
745 match buf.get(2, 0) {
747 Some(cell) => {
748 assert!(cell.grapheme == "\u{754c}");
749 assert!(cell.width == 2);
750 }
751 None => unreachable!(),
752 }
753
754 match buf.get(3, 0) {
756 Some(cell) => {
757 assert!(cell.width == 0);
758 }
759 None => unreachable!(),
760 }
761 }
762
763 #[test]
765 fn multiple_rows_in_layer() {
766 let mut compositor = Compositor::new(40, 10);
767 let region = Rect::new(0, 0, 40, 10);
768 let lines = vec![
769 vec![Segment::new("Row Zero")],
770 vec![Segment::new("Row One")],
771 vec![Segment::new("Row Two")],
772 ];
773 compositor.add_layer(Layer::new(1, region, 0, lines));
774
775 let mut buf = ScreenBuffer::new(Size::new(40, 10));
776 compositor.compose(&mut buf);
777
778 match buf.get(0, 0) {
780 Some(cell) => {
781 assert!(cell.grapheme == "R");
782 }
783 None => unreachable!(),
784 }
785 match buf.get(4, 0) {
786 Some(cell) => {
787 assert!(cell.grapheme == "Z");
788 }
789 None => unreachable!(),
790 }
791
792 match buf.get(0, 1) {
794 Some(cell) => {
795 assert!(cell.grapheme == "R");
796 }
797 None => unreachable!(),
798 }
799 match buf.get(4, 1) {
800 Some(cell) => {
801 assert!(cell.grapheme == "O");
802 }
803 None => unreachable!(),
804 }
805
806 match buf.get(0, 2) {
808 Some(cell) => {
809 assert!(cell.grapheme == "R");
810 }
811 None => unreachable!(),
812 }
813 match buf.get(4, 2) {
814 Some(cell) => {
815 assert!(cell.grapheme == "T");
816 }
817 None => unreachable!(),
818 }
819 }
820
821 #[test]
823 fn layer_partially_off_screen() {
824 let mut compositor = Compositor::new(10, 5);
826
827 let region = Rect::new(7, 0, 10, 3);
829 let lines = vec![vec![Segment::new("ABCDEFGHIJ")]];
830 compositor.add_layer(Layer::new(1, region, 0, lines));
831
832 let mut buf = ScreenBuffer::new(Size::new(10, 5));
833 compositor.compose(&mut buf);
834
835 match buf.get(7, 0) {
837 Some(cell) => {
838 assert!(cell.grapheme == "A");
839 }
840 None => unreachable!(),
841 }
842
843 match buf.get(9, 0) {
845 Some(cell) => {
846 assert!(cell.grapheme == "C");
847 }
848 None => unreachable!(),
849 }
850
851 assert!(buf.get(10, 0).is_none());
853 }
854
855 #[test]
857 fn zero_layer_compositor_all_blank() {
858 let compositor = Compositor::new(20, 10);
859 let mut buf = ScreenBuffer::new(Size::new(20, 10));
860 compositor.compose(&mut buf);
861
862 for y in 0..10 {
863 for x in 0..20 {
864 match buf.get(x, y) {
865 Some(cell) => {
866 assert!(cell.is_blank());
867 }
868 None => unreachable!(),
869 }
870 }
871 }
872 }
873
874 #[test]
876 fn styled_segments_split_by_overlay() {
877 let mut compositor = Compositor::new(20, 3);
878
879 let bg_region = Rect::new(0, 0, 20, 3);
881 let bg_lines = vec![vec![Segment::new("Hello World")]];
882 compositor.add_layer(Layer::new(1, bg_region, 0, bg_lines));
883
884 let overlay_region = Rect::new(3, 0, 5, 3);
886 let overlay_lines = vec![vec![Segment::new("XXXXX")]];
887 compositor.add_layer(Layer::new(2, overlay_region, 10, overlay_lines));
888
889 let mut buf = ScreenBuffer::new(Size::new(20, 3));
890 compositor.compose(&mut buf);
891
892 match buf.get(0, 0) {
894 Some(cell) => {
895 assert!(cell.grapheme == "H");
896 }
897 None => unreachable!(),
898 }
899 match buf.get(1, 0) {
900 Some(cell) => {
901 assert!(cell.grapheme == "e");
902 }
903 None => unreachable!(),
904 }
905 match buf.get(2, 0) {
906 Some(cell) => {
907 assert!(cell.grapheme == "l");
908 }
909 None => unreachable!(),
910 }
911
912 match buf.get(3, 0) {
914 Some(cell) => {
915 assert!(cell.grapheme == "X");
916 }
917 None => unreachable!(),
918 }
919 match buf.get(7, 0) {
920 Some(cell) => {
921 assert!(cell.grapheme == "X");
922 }
923 None => unreachable!(),
924 }
925
926 match buf.get(8, 0) {
934 Some(cell) => {
935 assert!(cell.grapheme == "r");
936 }
937 None => unreachable!(),
938 }
939 }
940
941 #[test]
943 fn many_layers_topmost_wins() {
944 let mut compositor = Compositor::new(20, 5);
945
946 for i in 0u64..25 {
949 let ch = char::from(b'A' + (i as u8) % 26);
950 let region = Rect::new(0, 0, 20, 5);
951 let lines = vec![vec![Segment::new(ch.to_string())]];
952 compositor.add_layer(Layer::new(i + 1, region, i as i32, lines));
953 }
954
955 let mut buf = ScreenBuffer::new(Size::new(20, 5));
956 compositor.compose(&mut buf);
957
958 match buf.get(0, 0) {
960 Some(cell) => {
961 assert!(cell.grapheme == "Y");
962 }
963 None => unreachable!(),
964 }
965 }
966}
967
968#[cfg(test)]
969mod unicode_pipeline_tests {
970 use super::*;
971 use crate::color::{Color, NamedColor};
972 use crate::geometry::{Rect, Size};
973 use crate::segment::Segment;
974 use crate::style::Style;
975
976 #[test]
978 fn cjk_text_layer_correct_cells() {
979 let mut compositor = Compositor::new(20, 3);
980 let region = Rect::new(0, 0, 20, 3);
981 let lines = vec![vec![Segment::new("\u{4e16}\u{754c}\u{4eba}")]];
983 compositor.add_layer(Layer::new(1, region, 0, lines));
984
985 let mut buf = ScreenBuffer::new(Size::new(20, 3));
986 compositor.compose(&mut buf);
987
988 match buf.get(0, 0) {
990 Some(c) => {
991 assert_eq!(c.grapheme, "\u{4e16}");
992 assert_eq!(c.width, 2);
993 }
994 None => unreachable!(),
995 }
996 match buf.get(1, 0) {
998 Some(c) => assert_eq!(c.width, 0),
999 None => unreachable!(),
1000 }
1001 match buf.get(2, 0) {
1003 Some(c) => {
1004 assert_eq!(c.grapheme, "\u{754c}");
1005 assert_eq!(c.width, 2);
1006 }
1007 None => unreachable!(),
1008 }
1009 match buf.get(3, 0) {
1011 Some(c) => assert_eq!(c.width, 0),
1012 None => unreachable!(),
1013 }
1014 match buf.get(4, 0) {
1016 Some(c) => {
1017 assert_eq!(c.grapheme, "\u{4eba}");
1018 assert_eq!(c.width, 2);
1019 }
1020 None => unreachable!(),
1021 }
1022 match buf.get(5, 0) {
1024 Some(c) => assert_eq!(c.width, 0),
1025 None => unreachable!(),
1026 }
1027 match buf.get(6, 0) {
1029 Some(c) => assert!(c.is_blank()),
1030 None => unreachable!(),
1031 }
1032 }
1033
1034 #[test]
1036 fn emoji_text_layer_correct_cells() {
1037 let mut compositor = Compositor::new(20, 3);
1038 let region = Rect::new(0, 0, 20, 3);
1039 let lines = vec![vec![Segment::new("\u{1f600}\u{1f389}")]];
1041 compositor.add_layer(Layer::new(1, region, 0, lines));
1042
1043 let mut buf = ScreenBuffer::new(Size::new(20, 3));
1044 compositor.compose(&mut buf);
1045
1046 match buf.get(0, 0) {
1048 Some(c) => {
1049 assert_eq!(c.grapheme, "\u{1f600}");
1050 assert_eq!(c.width, 2);
1051 }
1052 None => unreachable!(),
1053 }
1054 match buf.get(1, 0) {
1056 Some(c) => assert_eq!(c.width, 0),
1057 None => unreachable!(),
1058 }
1059 match buf.get(2, 0) {
1061 Some(c) => {
1062 assert_eq!(c.grapheme, "\u{1f389}");
1063 assert_eq!(c.width, 2);
1064 }
1065 None => unreachable!(),
1066 }
1067 match buf.get(3, 0) {
1069 Some(c) => assert_eq!(c.width, 0),
1070 None => unreachable!(),
1071 }
1072 }
1073
1074 #[test]
1076 fn mixed_latin_cjk_emoji_widths() {
1077 let mut compositor = Compositor::new(20, 3);
1078 let region = Rect::new(0, 0, 20, 3);
1079 let lines = vec![vec![Segment::new("Hi\u{4e16}\u{1f600}")]];
1081 compositor.add_layer(Layer::new(1, region, 0, lines));
1082
1083 let mut buf = ScreenBuffer::new(Size::new(20, 3));
1084 compositor.compose(&mut buf);
1085
1086 match buf.get(0, 0) {
1088 Some(c) => {
1089 assert_eq!(c.grapheme, "H");
1090 assert_eq!(c.width, 1);
1091 }
1092 None => unreachable!(),
1093 }
1094 match buf.get(1, 0) {
1095 Some(c) => {
1096 assert_eq!(c.grapheme, "i");
1097 assert_eq!(c.width, 1);
1098 }
1099 None => unreachable!(),
1100 }
1101 match buf.get(2, 0) {
1103 Some(c) => {
1104 assert_eq!(c.grapheme, "\u{4e16}");
1105 assert_eq!(c.width, 2);
1106 }
1107 None => unreachable!(),
1108 }
1109 match buf.get(3, 0) {
1110 Some(c) => assert_eq!(c.width, 0),
1111 None => unreachable!(),
1112 }
1113 match buf.get(4, 0) {
1115 Some(c) => {
1116 assert_eq!(c.grapheme, "\u{1f600}");
1117 assert_eq!(c.width, 2);
1118 }
1119 None => unreachable!(),
1120 }
1121 match buf.get(5, 0) {
1122 Some(c) => assert_eq!(c.width, 0),
1123 None => unreachable!(),
1124 }
1125 }
1126
1127 #[test]
1129 fn wide_char_at_screen_right_edge_clipped() {
1130 let mut compositor = Compositor::new(5, 1);
1132 let region = Rect::new(0, 0, 5, 1);
1133 let lines = vec![vec![Segment::new("ABCD\u{4e16}")]];
1138 compositor.add_layer(Layer::new(1, region, 0, lines));
1139
1140 let mut buf = ScreenBuffer::new(Size::new(5, 1));
1141 compositor.compose(&mut buf);
1142
1143 match buf.get(0, 0) {
1145 Some(c) => assert_eq!(c.grapheme, "A"),
1146 None => unreachable!(),
1147 }
1148 match buf.get(3, 0) {
1149 Some(c) => assert_eq!(c.grapheme, "D"),
1150 None => unreachable!(),
1151 }
1152 match buf.get(4, 0) {
1154 Some(c) => {
1155 assert!(c.is_blank());
1157 }
1158 None => unreachable!(),
1159 }
1160 assert!(buf.get(5, 0).is_none());
1162 }
1163
1164 #[test]
1166 fn combining_marks_preserved_in_buffer() {
1167 let mut compositor = Compositor::new(20, 3);
1168 let region = Rect::new(0, 0, 20, 3);
1169 let lines = vec![vec![Segment::new("e\u{0301}X")]];
1171 compositor.add_layer(Layer::new(1, region, 0, lines));
1172
1173 let mut buf = ScreenBuffer::new(Size::new(20, 3));
1174 compositor.compose(&mut buf);
1175
1176 match buf.get(0, 0) {
1178 Some(c) => {
1179 assert_eq!(c.grapheme, "e\u{0301}");
1180 assert_eq!(c.width, 1);
1181 }
1182 None => unreachable!(),
1183 }
1184 match buf.get(1, 0) {
1186 Some(c) => assert_eq!(c.grapheme, "X"),
1187 None => unreachable!(),
1188 }
1189 }
1190
1191 #[test]
1193 fn styled_wide_chars_preserved() {
1194 let mut compositor = Compositor::new(20, 3);
1195 let region = Rect::new(0, 0, 20, 3);
1196 let style = Style::new().fg(Color::Named(NamedColor::Red)).bold(true);
1197 let lines = vec![vec![Segment::styled("\u{4e16}\u{754c}", style.clone())]];
1199 compositor.add_layer(Layer::new(1, region, 0, lines));
1200
1201 let mut buf = ScreenBuffer::new(Size::new(20, 3));
1202 compositor.compose(&mut buf);
1203
1204 match buf.get(0, 0) {
1206 Some(c) => {
1207 assert_eq!(c.grapheme, "\u{4e16}");
1208 assert!(c.style.bold);
1209 assert!(matches!(c.style.fg, Some(Color::Named(NamedColor::Red))));
1210 }
1211 None => unreachable!(),
1212 }
1213 match buf.get(2, 0) {
1215 Some(c) => {
1216 assert_eq!(c.grapheme, "\u{754c}");
1217 assert!(c.style.bold);
1218 assert!(matches!(c.style.fg, Some(Color::Named(NamedColor::Red))));
1219 }
1220 None => unreachable!(),
1221 }
1222 }
1223
1224 #[test]
1226 fn overlapping_unicode_scripts_topmost_wins() {
1227 let mut compositor = Compositor::new(20, 3);
1228
1229 let bottom_region = Rect::new(0, 0, 20, 3);
1231 let bottom_lines = vec![vec![Segment::new("\u{4e16}\u{754c}\u{4eba}\u{6c11}")]];
1232 compositor.add_layer(Layer::new(1, bottom_region, 0, bottom_lines));
1233
1234 let top_region = Rect::new(0, 0, 4, 3);
1236 let top_lines = vec![vec![Segment::new("ABCD")]];
1237 compositor.add_layer(Layer::new(2, top_region, 10, top_lines));
1238
1239 let mut buf = ScreenBuffer::new(Size::new(20, 3));
1240 compositor.compose(&mut buf);
1241
1242 match buf.get(0, 0) {
1244 Some(c) => assert_eq!(c.grapheme, "A"),
1245 None => unreachable!(),
1246 }
1247 match buf.get(1, 0) {
1248 Some(c) => assert_eq!(c.grapheme, "B"),
1249 None => unreachable!(),
1250 }
1251 match buf.get(2, 0) {
1252 Some(c) => assert_eq!(c.grapheme, "C"),
1253 None => unreachable!(),
1254 }
1255 match buf.get(3, 0) {
1256 Some(c) => assert_eq!(c.grapheme, "D"),
1257 None => unreachable!(),
1258 }
1259
1260 match buf.get(4, 0) {
1264 Some(c) => {
1265 assert_eq!(c.grapheme, "\u{4eba}");
1266 assert_eq!(c.width, 2);
1267 }
1268 None => unreachable!(),
1269 }
1270 }
1271
1272 #[test]
1274 fn multiple_rows_cjk_text() {
1275 let mut compositor = Compositor::new(20, 5);
1276 let region = Rect::new(0, 0, 20, 5);
1277 let lines = vec![
1278 vec![Segment::new("\u{4e16}\u{754c}")], vec![Segment::new("\u{4eba}\u{6c11}")], vec![Segment::new("\u{5927}\u{5b66}")], ];
1282 compositor.add_layer(Layer::new(1, region, 0, lines));
1283
1284 let mut buf = ScreenBuffer::new(Size::new(20, 5));
1285 compositor.compose(&mut buf);
1286
1287 match buf.get(0, 0) {
1289 Some(c) => {
1290 assert_eq!(c.grapheme, "\u{4e16}");
1291 assert_eq!(c.width, 2);
1292 }
1293 None => unreachable!(),
1294 }
1295 match buf.get(2, 0) {
1296 Some(c) => {
1297 assert_eq!(c.grapheme, "\u{754c}");
1298 assert_eq!(c.width, 2);
1299 }
1300 None => unreachable!(),
1301 }
1302
1303 match buf.get(0, 1) {
1305 Some(c) => {
1306 assert_eq!(c.grapheme, "\u{4eba}");
1307 assert_eq!(c.width, 2);
1308 }
1309 None => unreachable!(),
1310 }
1311 match buf.get(2, 1) {
1312 Some(c) => {
1313 assert_eq!(c.grapheme, "\u{6c11}");
1314 assert_eq!(c.width, 2);
1315 }
1316 None => unreachable!(),
1317 }
1318
1319 match buf.get(0, 2) {
1321 Some(c) => {
1322 assert_eq!(c.grapheme, "\u{5927}");
1323 assert_eq!(c.width, 2);
1324 }
1325 None => unreachable!(),
1326 }
1327 match buf.get(2, 2) {
1328 Some(c) => {
1329 assert_eq!(c.grapheme, "\u{5b66}");
1330 assert_eq!(c.width, 2);
1331 }
1332 None => unreachable!(),
1333 }
1334
1335 match buf.get(0, 3) {
1337 Some(c) => assert!(c.is_blank()),
1338 None => unreachable!(),
1339 }
1340 }
1341}