1#![cfg_attr(not(feature = "std"), no_std)]
2use crate::line::CellRef;
3use alloc::borrow::Cow;
4use core::cmp::min;
5use finl_unicode::grapheme_clusters::Graphemes;
6#[cfg(feature = "use_serde")]
7use serde::{Deserialize, Serialize};
8use wezterm_cell::color::ColorAttribute;
9#[cfg(feature = "use_image")]
10use wezterm_cell::image::ImageCell;
11use wezterm_cell::{Cell, CellAttributes};
12use wezterm_dynamic::{FromDynamic, ToDynamic};
13
14extern crate alloc;
15use crate::alloc::borrow::ToOwned;
16use crate::alloc::string::ToString;
17use alloc::string::String;
18use alloc::vec;
19use alloc::vec::Vec;
20
21pub mod cellcluster;
22pub mod change;
23pub mod hyperlink;
24pub mod line;
25
26pub use self::change::{Change, LineAttribute};
27#[cfg(feature = "use_image")]
28pub use self::change::{Image, TextureCoordinate};
29pub use self::line::Line;
30
31#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
37#[derive(Debug, Clone, Copy, Eq, PartialEq)]
38pub enum Position {
39 Relative(isize),
41 Absolute(usize),
43 EndRelative(usize),
45}
46
47#[cfg_attr(
48 feature = "use_serde",
49 derive(Serialize, Deserialize, schemars::JsonSchema)
50)]
51#[derive(Debug, Clone, Hash, Copy, PartialEq, Eq, FromDynamic, ToDynamic)]
52pub enum CursorVisibility {
53 Hidden,
54 Visible,
55}
56
57impl Default for CursorVisibility {
58 fn default() -> CursorVisibility {
59 CursorVisibility::Visible
60 }
61}
62
63#[cfg_attr(
64 feature = "use_serde",
65 derive(Serialize, Deserialize, schemars::JsonSchema)
66)]
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromDynamic, ToDynamic)]
68pub enum CursorShape {
69 Default,
70 BlinkingBlock,
71 SteadyBlock,
72 BlinkingUnderline,
73 SteadyUnderline,
74 BlinkingBar,
75 SteadyBar,
76}
77
78impl Default for CursorShape {
79 fn default() -> CursorShape {
80 CursorShape::Default
81 }
82}
83
84impl CursorShape {
85 pub fn is_blinking(self) -> bool {
86 matches!(
87 self,
88 Self::BlinkingBlock | Self::BlinkingUnderline | Self::BlinkingBar
89 )
90 }
91}
92
93pub type SequenceNo = usize;
96pub const SEQ_ZERO: SequenceNo = 0;
97
98#[derive(Default, Clone)]
123pub struct Surface {
124 width: usize,
125 height: usize,
126 lines: Vec<Line>,
127 attributes: CellAttributes,
128 xpos: usize,
129 ypos: usize,
130 seqno: SequenceNo,
131 changes: Vec<Change>,
132 cursor_shape: Option<CursorShape>,
133 cursor_visibility: CursorVisibility,
134 cursor_color: ColorAttribute,
135 title: String,
136 ignore_high_repaint_cost: bool,
137}
138
139#[derive(Default)]
140struct DiffState {
141 changes: Vec<Change>,
142 cursor: Option<(usize, usize)>,
146 attr: Option<CellAttributes>,
151}
152
153impl DiffState {
154 #[inline]
155 fn diff_cells(&mut self, col_num: usize, row_num: usize, cell: CellRef, other_cell: CellRef) {
156 if cell.same_contents(&other_cell) {
157 return;
158 }
159
160 self.set_cell(col_num, row_num, other_cell);
161 }
162
163 #[inline]
164 fn set_cell(&mut self, col_num: usize, row_num: usize, other_cell: CellRef) {
165 self.cursor = match self.cursor.take() {
166 Some((cursor_row, cursor_col)) if cursor_row == row_num && cursor_col == col_num => {
167 Some((row_num, col_num + other_cell.width()))
171 }
172 _ => {
173 self.changes.push(Change::CursorPosition {
175 y: Position::Absolute(row_num),
176 x: Position::Absolute(col_num),
177 });
178 Some((row_num, col_num + other_cell.width()))
180 }
181 };
182
183 self.attr = match self.attr.take() {
187 Some(ref attr) if attr == other_cell.attrs() => {
188 Some(attr.clone())
191 }
192 _ => {
193 self.changes
195 .push(Change::AllAttributes(other_cell.attrs().clone()));
196 Some(other_cell.attrs().clone())
197 }
198 };
199 let result_len = self.changes.len();
202 if result_len > 0 && self.changes[result_len - 1].is_text() {
203 if let Some(Change::Text(ref mut prefix)) = self.changes.get_mut(result_len - 1) {
204 prefix.push_str(other_cell.str());
205 }
206 } else {
207 self.changes
208 .push(Change::Text(other_cell.str().to_string()));
209 }
210 }
211}
212
213impl Surface {
214 pub fn new(width: usize, height: usize) -> Self {
216 let mut scr = Surface {
217 width,
218 height,
219 ..Default::default()
220 };
221 scr.resize(width, height);
222 scr
223 }
224
225 pub fn dimensions(&self) -> (usize, usize) {
227 (self.width, self.height)
228 }
229
230 pub fn cursor_position(&self) -> (usize, usize) {
231 (self.xpos, self.ypos)
232 }
233
234 pub fn cursor_shape(&self) -> Option<CursorShape> {
235 self.cursor_shape
236 }
237
238 pub fn cursor_visibility(&self) -> CursorVisibility {
239 self.cursor_visibility
240 }
241
242 pub fn title(&self) -> &str {
243 &self.title
244 }
245
246 pub fn resize(&mut self, width: usize, height: usize) {
256 if !self.changes.is_empty() {
265 self.seqno += 1;
266 self.changes.clear();
267 }
268
269 self.lines
270 .resize(height, Line::with_width(width, self.seqno));
271 for line in &mut self.lines {
272 line.resize(width, self.seqno);
273 }
274 self.width = width;
275 self.height = height;
276
277 self.xpos = compute_position_change(self.xpos, &Position::Relative(0), self.width);
279 self.ypos = compute_position_change(self.ypos, &Position::Relative(0), self.height);
280 }
281
282 pub fn add_changes(&mut self, mut changes: Vec<Change>) -> SequenceNo {
285 let seq = self.seqno.saturating_sub(1) + changes.len();
286
287 for change in &changes {
288 self.apply_change(&change);
289 }
290
291 self.seqno += changes.len();
292 self.changes.append(&mut changes);
293
294 seq
295 }
296
297 pub fn add_change<C: Into<Change>>(&mut self, change: C) -> SequenceNo {
299 let seq = self.seqno;
300 self.seqno += 1;
301 let change = change.into();
302 self.apply_change(&change);
303 self.changes.push(change);
304 seq
305 }
306
307 fn apply_change(&mut self, change: &Change) {
308 match change {
309 Change::AllAttributes(attr) => self.attributes = attr.clone(),
310 Change::Text(text) => self.print_text(text),
311 Change::Attribute(change) => self.attributes.apply_change(change),
312 Change::CursorPosition { x, y } => self.set_cursor_pos(x, y),
313 Change::ClearScreen(color) => self.clear_screen(*color),
314 Change::ClearToEndOfLine(color) => self.clear_eol(*color),
315 Change::ClearToEndOfScreen(color) => self.clear_eos(*color),
316 Change::CursorColor(color) => self.cursor_color = *color,
317 Change::CursorShape(shape) => self.cursor_shape = Some(*shape),
318 Change::CursorVisibility(visibility) => self.cursor_visibility = *visibility,
319 #[cfg(feature = "use_image")]
320 Change::Image(image) => self.add_image(image),
321 Change::Title(text) => self.title = text.to_owned(),
322 Change::ScrollRegionUp {
323 first_row,
324 region_size,
325 scroll_count,
326 } => self.scroll_region_up(*first_row, *region_size, *scroll_count),
327 Change::ScrollRegionDown {
328 first_row,
329 region_size,
330 scroll_count,
331 } => self.scroll_region_down(*first_row, *region_size, *scroll_count),
332 Change::LineAttribute(attr) => self.line_attribute(attr),
333 }
334 }
335
336 #[cfg(feature = "use_image")]
337 fn add_image(&mut self, image: &Image) {
338 use ordered_float::NotNan;
339
340 let xsize = (image.bottom_right.x - image.top_left.x) / image.width as f32;
341 let ysize = (image.bottom_right.y - image.top_left.y) / image.height as f32;
342
343 if self.ypos + image.height > self.height {
344 let scroll = (self.ypos + image.height) - self.height;
345 for _ in 0..scroll {
346 self.scroll_screen_up();
347 }
348 self.ypos -= scroll;
349 }
350
351 let mut ypos = NotNan::new(0.0).unwrap();
352 for y in 0..image.height {
353 let mut xpos = NotNan::new(0.0).unwrap();
354 for x in 0..image.width {
355 self.lines[self.ypos + y].set_cell(
356 self.xpos + x,
357 Cell::new(
358 ' ',
359 self.attributes
360 .clone()
361 .set_image(Box::new(ImageCell::new(
362 TextureCoordinate::new(
363 image.top_left.x + xpos,
364 image.top_left.y + ypos,
365 ),
366 TextureCoordinate::new(
367 image.top_left.x + xpos + xsize,
368 image.top_left.y + ypos + ysize,
369 ),
370 image.image.clone(),
371 )))
372 .clone(),
373 ),
374 self.seqno,
375 );
376
377 xpos += xsize;
378 }
379 ypos += ysize;
380 }
381
382 self.xpos += image.width;
383 }
384
385 fn clear_screen(&mut self, color: ColorAttribute) {
386 self.attributes = CellAttributes::default().set_background(color).clone();
387 let cleared = Cell::new(' ', self.attributes.clone());
388 for line in &mut self.lines {
389 line.fill_range(0..self.width, &cleared, self.seqno);
390 }
391 self.xpos = 0;
392 self.ypos = 0;
393 }
394
395 fn clear_eos(&mut self, color: ColorAttribute) {
396 self.attributes = CellAttributes::default().set_background(color).clone();
397 let cleared = Cell::new(' ', self.attributes.clone());
398 self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared, self.seqno);
399 for line in &mut self.lines.iter_mut().skip(self.ypos + 1) {
400 line.fill_range(0..self.width, &cleared, self.seqno);
401 }
402 }
403
404 fn clear_eol(&mut self, color: ColorAttribute) {
405 self.attributes = CellAttributes::default().set_background(color).clone();
406 let cleared = Cell::new(' ', self.attributes.clone());
407 self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared, self.seqno);
408 }
409
410 fn scroll_screen_up(&mut self) {
411 self.lines.remove(0);
412 self.lines.push(Line::with_width(self.width, self.seqno));
413 }
414
415 fn scroll_region_up(&mut self, start: usize, size: usize, count: usize) {
416 for index in start..start + min(count, size) {
418 self.lines[index] = Line::with_width(self.width, self.seqno);
419 }
420 if 0 < count && count < size {
422 self.lines[start..start + size].rotate_left(count);
423 }
424 }
425
426 fn scroll_region_down(&mut self, start: usize, size: usize, count: usize) {
427 for index in start + size - min(count, size)..start + size {
429 self.lines[index] = Line::with_width(self.width, self.seqno);
430 }
431 if 0 < count && count < size {
433 self.lines[start..start + size].rotate_right(count);
434 }
435 }
436
437 fn line_attribute(&mut self, attr: &LineAttribute) {
438 let line = &mut self.lines[self.ypos];
439 match attr {
440 LineAttribute::DoubleHeightTopHalfLine => line.set_double_height_top(self.seqno),
441 LineAttribute::DoubleHeightBottomHalfLine => line.set_double_height_bottom(self.seqno),
442 LineAttribute::DoubleWidthLine => line.set_double_width(self.seqno),
443 LineAttribute::SingleWidthLine => line.set_single_width(self.seqno),
444 }
445 }
446
447 fn print_text(&mut self, text: &str) {
448 for g in Graphemes::new(text) {
449 if g == "\r\n" {
450 self.xpos = 0;
451 let new_y = self.ypos + 1;
452 if new_y >= self.height {
453 self.scroll_screen_up();
454 } else {
455 self.ypos = new_y;
456 }
457 continue;
458 }
459
460 if g == "\r" {
461 self.xpos = 0;
462 continue;
463 }
464
465 if g == "\n" {
466 let new_y = self.ypos + 1;
467 if new_y >= self.height {
468 self.scroll_screen_up();
469 } else {
470 self.ypos = new_y;
471 }
472 continue;
473 }
474
475 if self.xpos >= self.width {
476 let new_y = self.ypos + 1;
477 if new_y >= self.height {
478 self.scroll_screen_up();
479 } else {
480 self.ypos = new_y;
481 }
482 self.xpos = 0;
483 }
484
485 let cell = Cell::new_grapheme(g, self.attributes.clone(), None);
486 let width = cell.width().max(1);
492
493 self.lines[self.ypos].set_cell(self.xpos, cell, self.seqno);
494
495 self.xpos += width;
499 }
500 }
501
502 fn set_cursor_pos(&mut self, x: &Position, y: &Position) {
503 self.xpos = compute_position_change(self.xpos, x, self.width);
504 self.ypos = compute_position_change(self.ypos, y, self.height);
505 }
506
507 pub fn screen_chars_to_string(&self) -> String {
512 let mut s = String::new();
513
514 for line in &self.lines {
515 for cell in line.visible_cells() {
516 s.push_str(cell.str());
517 }
518 s.push('\n');
519 }
520
521 s
522 }
523
524 pub fn screen_cells(&mut self) -> Vec<&mut [Cell]> {
527 let mut lines = Vec::new();
528 for line in &mut self.lines {
529 lines.push(line.cells_mut());
530 }
531 lines
532 }
533
534 pub fn make_screen_cells(&mut self) {
535 for line in &mut self.lines {
536 line.make_cells();
537 }
538 }
539
540 pub fn get_screen_cells(&self) -> Vec<&[Cell]> {
541 let mut lines = Vec::new();
542 for line in &self.lines {
543 lines.push(line.cells());
544 }
545 lines
546 }
547
548 pub fn screen_lines(&self) -> Vec<Cow<Line>> {
549 self.lines.iter().map(|line| Cow::Borrowed(line)).collect()
550 }
551
552 pub fn get_changes(&self, seq: SequenceNo) -> (SequenceNo, Cow<[Change]>) {
564 let first = self.seqno.saturating_sub(self.changes.len());
566 if seq == 0 || first > seq || self.seqno == 0 {
567 return (self.seqno, Cow::Owned(self.repaint_all()));
569 }
570
571 let mut is_repaint = false;
572
573 if !self.ignore_high_repaint_cost {
574 let delta_cost = self.seqno - seq;
576 let full_cost = self.estimate_full_paint_cost();
578
579 is_repaint = delta_cost > full_cost
580 }
581
582 if is_repaint {
583 (self.seqno, Cow::Owned(self.repaint_all()))
584 } else {
585 (self.seqno, Cow::Borrowed(&self.changes[seq - first..]))
586 }
587 }
588
589 pub fn has_changes(&self, seq: SequenceNo) -> bool {
590 self.seqno != seq
591 }
592
593 pub fn current_seqno(&self) -> SequenceNo {
594 self.seqno
595 }
596
597 pub fn flush_changes_older_than(&mut self, seq: SequenceNo) {
602 let first = self.seqno.saturating_sub(self.changes.len());
603 let idx = seq.saturating_sub(first);
604 if idx > self.changes.len() {
605 return;
606 }
607 self.changes = self.changes.split_off(idx);
608 }
609
610 fn estimate_full_paint_cost(&self) -> usize {
613 3 + (((self.width * self.height) as f64) * 1.2) as usize
615 }
616
617 fn repaint_all(&self) -> Vec<Change> {
618 let mut result = vec![
619 Change::CursorVisibility(CursorVisibility::Hidden),
622 Change::ClearScreen(Default::default()),
623 ];
624
625 if !self.title.is_empty() {
626 result.push(Change::Title(self.title.to_owned()));
627 }
628
629 let mut attr = CellAttributes::default();
630
631 let crlf = Change::CursorPosition {
632 x: Position::Absolute(0),
633 y: Position::Relative(1),
634 };
635
636 let mut trailing_color = None;
642 let mut trailing_idx = None;
643
644 for (idx, line) in self.lines.iter().rev().enumerate() {
645 let changes = line.changes(&attr);
646 if changes.is_empty() {
647 match trailing_color {
650 Some(other) if other != Default::default() => {
651 break;
654 }
655 Some(_) => continue,
657 None => {
659 trailing_color = Some(Default::default());
660 trailing_idx = Some(idx);
661 continue;
662 }
663 }
664 } else {
665 let last_change = changes.len() - 1;
666 match (&changes[last_change], trailing_color) {
667 (&Change::ClearToEndOfLine(ref color), None) => {
668 trailing_color = Some(*color);
669 trailing_idx = Some(idx);
670 }
671 (&Change::ClearToEndOfLine(ref color), Some(other)) => {
672 if other == *color {
673 trailing_idx = Some(idx);
674 continue;
675 } else {
676 break;
677 }
678 }
679 _ => break,
680 }
681 }
682 }
683
684 for (idx, line) in self.lines.iter().enumerate() {
685 match trailing_idx {
686 Some(t) if self.height - t == idx => {
687 let color =
688 trailing_color.expect("didn't set trailing_color along with trailing_idx");
689
690 let last_result = result.len() - 1;
693 match result[last_result] {
694 Change::ClearToEndOfLine(col) if col == color => {
695 result.remove(last_result);
696 }
697 _ => {}
698 }
699
700 result.push(Change::ClearToEndOfScreen(color));
701 break;
702 }
703 _ => {}
704 }
705
706 let mut changes = line.changes(&attr);
707
708 if idx != 0 {
709 result.push(crlf.clone());
714 }
715
716 result.append(&mut changes);
717 if let Some(c) = line.visible_cells().last() {
718 attr = c.attrs().clone();
719 }
720 }
721
722 loop {
725 let result_len = result.len();
726 if result_len == 0 {
727 break;
728 }
729 match result[result_len - 1] {
730 Change::CursorPosition { .. } => {
731 result.remove(result_len - 1);
732 }
733 _ => break,
734 }
735 }
736
737 let moved_cursor = result.len() != 2;
748 if moved_cursor || self.xpos != 0 || self.ypos != 0 {
749 result.push(Change::CursorPosition {
750 x: Position::Absolute(self.xpos),
751 y: Position::Absolute(self.ypos),
752 });
753 }
754
755 if self.cursor_visibility != CursorVisibility::Hidden {
758 result.push(Change::CursorVisibility(CursorVisibility::Visible));
759 if let Some(shape) = self.cursor_shape {
760 result.push(Change::CursorShape(shape));
761 }
762 }
763
764 result
765 }
766
767 #[allow(clippy::too_many_arguments)]
777 pub fn diff_region(
778 &self,
779 x: usize,
780 y: usize,
781 width: usize,
782 height: usize,
783 other: &Surface,
784 other_x: usize,
785 other_y: usize,
786 ) -> Vec<Change> {
787 let mut diff_state = DiffState::default();
788
789 for ((row_num, line), other_line) in self
790 .lines
791 .iter()
792 .enumerate()
793 .skip(y)
794 .take_while(|(row_num, _)| *row_num < y + height)
795 .zip(other.lines.iter().skip(other_y))
796 {
797 diff_line(
798 &mut diff_state,
799 line,
800 row_num,
801 other_line,
802 x,
803 width,
804 other_x,
805 );
806 }
807
808 diff_state.changes
809 }
810
811 pub fn diff_lines(&self, other_lines: Vec<&Line>) -> Vec<Change> {
812 let mut diff_state = DiffState::default();
813 for ((row_num, line), other_line) in self.lines.iter().enumerate().zip(other_lines.iter()) {
814 diff_line(&mut diff_state, line, row_num, other_line, 0, line.len(), 0);
815 }
816 diff_state.changes
817 }
818
819 pub fn diff_against_numbered_line(&self, row_num: usize, other_line: &Line) -> Vec<Change> {
820 let mut diff_state = DiffState::default();
821 if let Some(line) = self.lines.get(row_num) {
822 diff_line(&mut diff_state, line, row_num, other_line, 0, line.len(), 0);
823 }
824 diff_state.changes
825 }
826
827 pub fn diff_screens(&self, other: &Surface) -> Vec<Change> {
830 self.diff_region(0, 0, self.width, self.height, other, 0, 0)
831 }
832
833 pub fn draw_from_screen(&mut self, other: &Surface, x: usize, y: usize) -> SequenceNo {
840 let attrs = self.attributes.clone();
841 let cursor = (self.xpos, self.ypos);
842 let changes = self.diff_region(x, y, other.width, other.height, other, 0, 0);
843 let seq = self.add_changes(changes);
844 self.xpos = cursor.0;
845 self.ypos = cursor.1;
846 self.attributes = attrs;
847 seq
848 }
849
850 pub fn copy_region(
859 &mut self,
860 src_x: usize,
861 src_y: usize,
862 width: usize,
863 height: usize,
864 dest_x: usize,
865 dest_y: usize,
866 ) -> SequenceNo {
867 let changes = self.diff_region(dest_x, dest_y, width, height, self, src_x, src_y);
868 self.add_changes(changes)
869 }
870
871 pub fn ignore_high_repaint_cost(&mut self, value: bool) {
875 self.ignore_high_repaint_cost = value;
876 }
877}
878
879fn diff_line(
882 diff_state: &mut DiffState,
883 line: &Line,
884 row_num: usize,
885 other_line: &Line,
886 x: usize,
887 width: usize,
888 other_x: usize,
889) {
890 let mut cells = line
891 .visible_cells()
892 .skip_while(|cell| cell.cell_index() < x)
893 .take_while(|cell| cell.cell_index() < x + width)
894 .peekable();
895 let other_cells = other_line
896 .visible_cells()
897 .skip_while(|cell| cell.cell_index() < other_x)
898 .take_while(|cell| cell.cell_index() < other_x + width);
899
900 for other_cell in other_cells {
901 let rel_x = other_cell.cell_index() - other_x;
902 let mut comparison_cell = None;
903
904 while let Some(cell) = cells.peek() {
908 let cell_rel_x = cell.cell_index() - x;
909
910 if cell_rel_x == rel_x {
911 comparison_cell = Some(*cell);
912 break;
913 } else if cell_rel_x > rel_x {
914 break;
915 }
916
917 cells.next();
918 }
919
920 if let Some(comparison_cell) = comparison_cell {
924 diff_state.diff_cells(x + rel_x, row_num, comparison_cell, other_cell);
925 } else {
926 diff_state.set_cell(x + rel_x, row_num, other_cell);
927 }
928 }
929}
930
931fn compute_position_change(current: usize, pos: &Position, limit: usize) -> usize {
934 use self::Position::*;
935 match pos {
936 Relative(delta) => {
937 if *delta >= 0 {
938 min(
939 current.saturating_add(*delta as usize),
940 limit.saturating_sub(1),
941 )
942 } else {
943 current.saturating_sub((*delta).abs() as usize)
944 }
945 }
946 Absolute(abs) => min(*abs, limit.saturating_sub(1)),
947 EndRelative(delta) => limit.saturating_sub(*delta),
948 }
949}
950
951#[cfg(test)]
952mod test {
953 use super::*;
954 use alloc::sync::Arc;
955 use wezterm_cell::color::AnsiColor;
956 use wezterm_cell::image::ImageData;
957 use wezterm_cell::{AttributeChange, Intensity};
958
959 #[test]
964 fn basic_print() {
965 let mut s = Surface::new(4, 3);
966 assert_eq!(
967 s.screen_chars_to_string(),
968 "\x20\x20\x20\x20\n\
969 \x20\x20\x20\x20\n\
970 \x20\x20\x20\x20\n"
971 );
972
973 s.add_change("w00t");
974 assert_eq!(
975 s.screen_chars_to_string(),
976 "w00t\n\
977 \x20\x20\x20\x20\n\
978 \x20\x20\x20\x20\n"
979 );
980
981 s.add_change("foo");
982 assert_eq!(
983 s.screen_chars_to_string(),
984 "w00t\n\
985 foo\x20\n\
986 \x20\x20\x20\x20\n"
987 );
988
989 s.add_change("baar");
990 assert_eq!(
991 s.screen_chars_to_string(),
992 "w00t\n\
993 foob\n\
994 aar\x20\n"
995 );
996
997 s.add_change("baz");
998 assert_eq!(
999 s.screen_chars_to_string(),
1000 "foob\n\
1001 aarb\n\
1002 az\x20\x20\n"
1003 );
1004 }
1005
1006 #[test]
1007 fn newline() {
1008 let mut s = Surface::new(4, 4);
1009 s.add_change("bloo\rwat\n hey\r\nho");
1010 assert_eq!(
1011 s.screen_chars_to_string(),
1012 "wato\n\
1013 \x20\x20\x20\x20\n\
1014 hey \n\
1015 ho \n"
1016 );
1017 }
1018
1019 #[test]
1020 fn clear_screen() {
1021 let mut s = Surface::new(2, 2);
1022 s.add_change("hello");
1023 assert_eq!(s.xpos, 1);
1024 assert_eq!(s.ypos, 1);
1025 s.add_change(Change::ClearScreen(Default::default()));
1026 assert_eq!(s.xpos, 0);
1027 assert_eq!(s.ypos, 0);
1028 assert_eq!(s.screen_chars_to_string(), " \n \n");
1029 }
1030
1031 #[test]
1032 fn clear_eol() {
1033 let mut s = Surface::new(3, 3);
1034 s.add_change("helwowfoo");
1035 s.add_change(Change::ClearToEndOfLine(Default::default()));
1036 assert_eq!(s.screen_chars_to_string(), "hel\nwow\nfoo\n");
1037 s.add_change(Change::CursorPosition {
1038 x: Position::Absolute(0),
1039 y: Position::Absolute(0),
1040 });
1041 s.add_change(Change::ClearToEndOfLine(Default::default()));
1042 assert_eq!(s.screen_chars_to_string(), " \nwow\nfoo\n");
1043 s.add_change(Change::CursorPosition {
1044 x: Position::Absolute(1),
1045 y: Position::Absolute(1),
1046 });
1047 s.add_change(Change::ClearToEndOfLine(Default::default()));
1048 assert_eq!(s.screen_chars_to_string(), " \nw\nfoo\n");
1049 }
1050
1051 #[test]
1052 fn clear_eos() {
1053 let mut s = Surface::new(3, 3);
1054 s.add_change("helwowfoo");
1055 s.add_change(Change::ClearToEndOfScreen(Default::default()));
1056 assert_eq!(s.screen_chars_to_string(), "hel\nwow\nfoo\n");
1057 s.add_change(Change::CursorPosition {
1058 x: Position::Absolute(1),
1059 y: Position::Absolute(1),
1060 });
1061 s.add_change(Change::ClearToEndOfScreen(Default::default()));
1062 assert_eq!(s.screen_chars_to_string(), "hel\nw\n \n");
1063
1064 let (_seq, changes) = s.get_changes(0);
1065 assert_eq!(
1066 &[
1067 Change::CursorVisibility(CursorVisibility::Hidden),
1068 Change::ClearScreen(Default::default()),
1069 Change::Text("hel".into()),
1070 Change::CursorPosition {
1071 x: Position::Absolute(0),
1072 y: Position::Relative(1),
1073 },
1074 Change::Text("w".into()),
1075 Change::CursorPosition {
1076 x: Position::Absolute(1),
1077 y: Position::Absolute(1),
1078 },
1079 Change::CursorVisibility(CursorVisibility::Visible),
1080 ],
1081 &*changes
1082 );
1083 }
1084
1085 #[test]
1086 fn clear_eos_back_color() {
1087 let mut s = Surface::new(3, 3);
1088 s.add_change(Change::ClearScreen(AnsiColor::Red.into()));
1089 s.add_change("helwowfoo");
1090 assert_eq!(s.screen_chars_to_string(), "hel\nwow\nfoo\n");
1091 s.add_change(Change::CursorPosition {
1092 x: Position::Absolute(1),
1093 y: Position::Absolute(1),
1094 });
1095 s.add_change(Change::ClearToEndOfScreen(AnsiColor::Red.into()));
1096 assert_eq!(s.screen_chars_to_string(), "hel\nw \n \n");
1097
1098 let (_seq, changes) = s.get_changes(0);
1099 assert_eq!(
1100 &[
1101 Change::CursorVisibility(CursorVisibility::Hidden),
1102 Change::ClearScreen(Default::default()),
1103 Change::AllAttributes(
1104 CellAttributes::default()
1105 .set_background(AnsiColor::Red)
1106 .clone()
1107 ),
1108 Change::Text("hel".into()),
1109 Change::CursorPosition {
1110 x: Position::Absolute(0),
1111 y: Position::Relative(1),
1112 },
1113 Change::Text("w".into()),
1114 Change::ClearToEndOfScreen(AnsiColor::Red.into()),
1115 Change::CursorPosition {
1116 x: Position::Absolute(1),
1117 y: Position::Absolute(1),
1118 },
1119 Change::CursorVisibility(CursorVisibility::Visible),
1120 ],
1121 &*changes
1122 );
1123 }
1124
1125 #[test]
1126 fn clear_eol_opt() {
1127 let mut s = Surface::new(3, 3);
1128 s.add_change(Change::Attribute(AttributeChange::Background(
1129 AnsiColor::Red.into(),
1130 )));
1131 s.add_change("111 333");
1132 let (_seq, changes) = s.get_changes(0);
1133 assert_eq!(
1134 &[
1135 Change::CursorVisibility(CursorVisibility::Hidden),
1136 Change::ClearScreen(Default::default()),
1137 Change::AllAttributes(
1138 CellAttributes::default()
1139 .set_background(AnsiColor::Red)
1140 .clone()
1141 ),
1142 Change::Text("111".into()),
1143 Change::CursorPosition {
1144 x: Position::Absolute(0),
1145 y: Position::Relative(1),
1146 },
1147 Change::ClearToEndOfLine(AnsiColor::Red.into()),
1148 Change::CursorPosition {
1149 x: Position::Absolute(0),
1150 y: Position::Relative(1),
1151 },
1152 Change::Text("333".into()),
1153 Change::CursorPosition {
1154 x: Position::Absolute(3),
1155 y: Position::Absolute(2),
1156 },
1157 Change::CursorVisibility(CursorVisibility::Visible),
1158 ],
1159 &*changes
1160 );
1161 }
1162
1163 #[test]
1164 fn clear_and_move_cursor() {
1165 let mut s = Surface::new(4, 3);
1166 s.add_change(Change::CursorPosition {
1167 x: Position::Absolute(3),
1168 y: Position::Absolute(2),
1169 });
1170 let (_seq, changes) = s.get_changes(0);
1171 assert_eq!(
1172 &[
1173 Change::CursorVisibility(CursorVisibility::Hidden),
1174 Change::ClearScreen(Default::default()),
1175 Change::CursorPosition {
1176 x: Position::Absolute(3),
1177 y: Position::Absolute(2),
1178 },
1179 Change::CursorVisibility(CursorVisibility::Visible),
1180 ],
1181 &*changes
1182 );
1183 }
1184
1185 #[test]
1186 fn cursor_movement() {
1187 let mut s = Surface::new(4, 3);
1188 s.add_change(Change::CursorPosition {
1189 x: Position::Absolute(3),
1190 y: Position::Absolute(2),
1191 });
1192 s.add_change("X");
1193 assert_eq!(
1194 s.screen_chars_to_string(),
1195 "\x20\x20\x20\x20\n\
1196 \x20\x20\x20\x20\n\
1197 \x20\x20\x20X\n"
1198 );
1199
1200 s.add_change(Change::CursorPosition {
1201 x: Position::Relative(-2),
1202 y: Position::Relative(-1),
1203 });
1204 s.add_change("-");
1205 assert_eq!(
1206 s.screen_chars_to_string(),
1207 "\x20\x20\x20\x20\n\
1208 \x20\x20-\x20\n\
1209 \x20\x20\x20X\n"
1210 );
1211
1212 s.add_change(Change::CursorPosition {
1213 x: Position::Relative(1),
1214 y: Position::Relative(-1),
1215 });
1216 s.add_change("-");
1217 assert_eq!(
1218 s.screen_chars_to_string(),
1219 "\x20\x20\x20-\n\
1220 \x20\x20-\x20\n\
1221 \x20\x20\x20X\n"
1222 );
1223 }
1224
1225 #[test]
1226 fn attribute_setting() {
1227 use wezterm_cell::Intensity;
1228
1229 let mut s = Surface::new(3, 1);
1230 s.add_change("n");
1231 s.add_change(AttributeChange::Intensity(Intensity::Bold));
1232 s.add_change("b");
1233
1234 let mut bold = CellAttributes::default();
1235 bold.set_intensity(Intensity::Bold);
1236
1237 assert_eq!(
1238 s.screen_cells(),
1239 [[
1240 Cell::new('n', CellAttributes::default()),
1241 Cell::new('b', bold),
1242 Cell::default(),
1243 ]]
1244 );
1245 }
1246
1247 #[test]
1248 fn empty_changes() {
1249 let s = Surface::new(4, 3);
1250
1251 let empty = &[
1252 Change::CursorVisibility(CursorVisibility::Hidden),
1253 Change::ClearScreen(Default::default()),
1254 Change::CursorVisibility(CursorVisibility::Visible),
1255 ];
1256
1257 let (seq, changes) = s.get_changes(0);
1258 assert_eq!(seq, 0);
1259 assert_eq!(empty, &*changes);
1260
1261 let (seq, changes) = s.get_changes(1);
1264 assert_eq!(seq, 0);
1265 assert_eq!(empty, &*changes);
1266 }
1267
1268 #[test]
1269 fn add_changes_empty() {
1270 let mut s = Surface::new(2, 2);
1271 let last_seq = s.add_change("foo");
1272 assert_eq!(0, last_seq);
1273 assert_eq!(last_seq, s.add_changes(vec![]));
1274 assert_eq!(last_seq + 1, s.add_changes(vec![Change::Text("a".into())]));
1275 }
1276
1277 #[test]
1278 fn resize_delta_flush() {
1279 let mut s = Surface::new(4, 3);
1280 s.add_change("a");
1281 let (seq, _) = s.get_changes(0);
1282 s.resize(2, 2);
1283
1284 let full = &[
1285 Change::CursorVisibility(CursorVisibility::Hidden),
1286 Change::ClearScreen(Default::default()),
1287 Change::Text("a".to_string()),
1288 Change::CursorPosition {
1289 x: Position::Absolute(1),
1290 y: Position::Absolute(0),
1291 },
1292 Change::CursorVisibility(CursorVisibility::Visible),
1293 ];
1294
1295 let (_seq, changes) = s.get_changes(seq);
1296 assert_eq!(full, &*changes);
1298 }
1299
1300 #[test]
1301 fn dont_lose_first_char_on_attr_change() {
1302 let mut s = Surface::new(2, 2);
1303 s.add_change(Change::Attribute(AttributeChange::Foreground(
1304 AnsiColor::Maroon.into(),
1305 )));
1306 s.add_change("ab");
1307 let (_seq, changes) = s.get_changes(0);
1308 assert_eq!(
1309 &[
1310 Change::CursorVisibility(CursorVisibility::Hidden),
1311 Change::ClearScreen(Default::default()),
1312 Change::AllAttributes(
1313 CellAttributes::default()
1314 .set_foreground(AnsiColor::Maroon)
1315 .clone()
1316 ),
1317 Change::Text("ab".into()),
1318 Change::CursorPosition {
1319 x: Position::Absolute(2),
1320 y: Position::Absolute(0),
1321 },
1322 Change::CursorVisibility(CursorVisibility::Visible),
1323 ],
1324 &*changes
1325 );
1326 }
1327
1328 #[test]
1329 fn resize_cursor_position() {
1330 let mut s = Surface::new(4, 4);
1331
1332 s.add_change(" a");
1333 s.add_change(Change::CursorPosition {
1334 x: Position::Absolute(3),
1335 y: Position::Absolute(3),
1336 });
1337
1338 assert_eq!(s.xpos, 3);
1339 assert_eq!(s.ypos, 3);
1340 s.resize(2, 2);
1341 assert_eq!(s.xpos, 1);
1342 assert_eq!(s.ypos, 1);
1343
1344 let full = &[
1345 Change::CursorVisibility(CursorVisibility::Hidden),
1346 Change::ClearScreen(Default::default()),
1347 Change::Text(" a".to_string()),
1348 Change::CursorPosition {
1349 x: Position::Absolute(1),
1350 y: Position::Absolute(1),
1351 },
1352 Change::CursorVisibility(CursorVisibility::Visible),
1353 ];
1354
1355 let (_seq, changes) = s.get_changes(0);
1356 assert_eq!(full, &*changes);
1357 }
1358
1359 #[test]
1360 fn delta_change() {
1361 let mut s = Surface::new(4, 3);
1362 s.flush_changes_older_than(0);
1364
1365 s.flush_changes_older_than(1);
1367
1368 let initial = &[
1369 Change::CursorVisibility(CursorVisibility::Hidden),
1370 Change::ClearScreen(Default::default()),
1371 Change::Text("a".to_string()),
1372 Change::CursorPosition {
1373 x: Position::Absolute(1),
1374 y: Position::Absolute(0),
1375 },
1376 Change::CursorVisibility(CursorVisibility::Visible),
1377 ];
1378
1379 let seq_pos = {
1380 let next_seq = s.add_change("a");
1381 let (seq, changes) = s.get_changes(0);
1382 assert_eq!(seq, next_seq + 1);
1383 assert_eq!(initial, &*changes);
1384 seq
1385 };
1386
1387 let seq_pos = {
1388 let next_seq = s.add_change("b");
1389 let (seq, changes) = s.get_changes(seq_pos);
1390 assert_eq!(seq, next_seq + 1);
1391 assert_eq!(&[Change::Text("b".to_string())], &*changes);
1392 seq
1393 };
1394
1395 {
1397 s.add_change(Change::Attribute(AttributeChange::Intensity(
1398 Intensity::Bold,
1399 )));
1400 s.add_change("c");
1401 s.add_change(Change::Attribute(AttributeChange::Intensity(
1402 Intensity::Normal,
1403 )));
1404 s.add_change("d");
1405 }
1406
1407 for _ in 0..3 {
1410 {
1411 let (_seq, changes) = s.get_changes(seq_pos);
1412
1413 assert_eq!(
1414 &[
1415 Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
1416 Change::Text("c".to_string()),
1417 Change::Attribute(AttributeChange::Intensity(Intensity::Normal)),
1418 Change::Text("d".to_string()),
1419 ],
1420 &*changes
1421 );
1422 }
1423
1424 s.flush_changes_older_than(seq_pos);
1428 }
1429 }
1430
1431 #[test]
1432 fn diff_screens() {
1433 let mut s = Surface::new(4, 3);
1434 s.add_change("w00t");
1435 s.add_change("foo");
1436 s.add_change("baar");
1437 s.add_change("baz");
1438 assert_eq!(
1439 s.screen_chars_to_string(),
1440 "foob\n\
1441 aarb\n\
1442 az \n"
1443 );
1444
1445 let s2 = Surface::new(2, 2);
1446
1447 {
1448 let changes = s2.diff_region(0, 0, 2, 2, &s, 0, 0);
1450 assert_eq!(
1451 vec![
1452 Change::CursorPosition {
1453 x: Position::Absolute(0),
1454 y: Position::Absolute(0),
1455 },
1456 Change::AllAttributes(CellAttributes::default()),
1457 Change::Text("fo".into()),
1458 Change::CursorPosition {
1459 x: Position::Absolute(0),
1460 y: Position::Absolute(1),
1461 },
1462 Change::Text("aa".into()),
1463 ],
1464 changes
1465 );
1466 }
1467
1468 s.add_change(Change::CursorPosition {
1470 x: Position::Absolute(1),
1471 y: Position::Absolute(1),
1472 });
1473 s.add_change(Change::Attribute(AttributeChange::Intensity(
1474 Intensity::Bold,
1475 )));
1476 s.add_change("XO");
1477
1478 {
1479 let changes = s2.diff_region(0, 0, 2, 2, &s, 1, 1);
1480 assert_eq!(
1481 vec![
1482 Change::CursorPosition {
1483 x: Position::Absolute(0),
1484 y: Position::Absolute(0),
1485 },
1486 Change::AllAttributes(
1487 CellAttributes::default()
1488 .set_intensity(Intensity::Bold)
1489 .clone(),
1490 ),
1491 Change::Text("XO".into()),
1492 Change::CursorPosition {
1493 x: Position::Absolute(0),
1494 y: Position::Absolute(1),
1495 },
1496 Change::AllAttributes(CellAttributes::default()),
1497 Change::Text("z".into()),
1498 ],
1501 changes
1502 );
1503 }
1504 }
1505
1506 #[test]
1507 fn draw_screens() {
1508 let mut s = Surface::new(4, 4);
1509
1510 let mut s1 = Surface::new(2, 2);
1511 s1.add_change("1234");
1512
1513 let mut s2 = Surface::new(2, 2);
1514 s2.add_change("XYZA");
1515
1516 s.draw_from_screen(&s1, 0, 0);
1517 s.draw_from_screen(&s2, 2, 2);
1518
1519 assert_eq!(
1520 s.screen_chars_to_string(),
1521 "12 \n\
1522 34 \n\
1523 \x20\x20XY\n\
1524 \x20\x20ZA\n"
1525 );
1526 }
1527
1528 #[test]
1529 fn draw_colored_region() {
1530 let mut dest = Surface::new(4, 4);
1531 dest.add_change("A");
1532 let mut src = Surface::new(2, 2);
1533 src.add_change(Change::ClearScreen(AnsiColor::Blue.into()));
1534 dest.draw_from_screen(&src, 2, 2);
1535
1536 assert_eq!(
1537 dest.screen_chars_to_string(),
1538 "A \n\
1539 \x20 \n\
1540 \x20 \n\
1541 \x20 \n"
1542 );
1543
1544 let blue_space = Cell::new(
1545 ' ',
1546 CellAttributes::default()
1547 .set_background(AnsiColor::Blue)
1548 .clone(),
1549 );
1550
1551 assert_eq!(
1552 dest.screen_cells(),
1553 [
1554 [
1555 Cell::new('A', CellAttributes::default()),
1556 Cell::default(),
1557 Cell::default(),
1558 Cell::default(),
1559 ],
1560 [
1561 Cell::default(),
1562 Cell::default(),
1563 Cell::default(),
1564 Cell::default(),
1565 ],
1566 [
1567 Cell::default(),
1568 Cell::default(),
1569 blue_space.clone(),
1570 blue_space.clone(),
1571 ],
1572 [
1573 Cell::default(),
1574 Cell::default(),
1575 blue_space.clone(),
1576 blue_space.clone(),
1577 ]
1578 ]
1579 );
1580
1581 assert_eq!(dest.xpos, 1);
1582 assert_eq!(dest.ypos, 0);
1583 assert_eq!(dest.attributes, Default::default());
1584 dest.add_change("B");
1585
1586 assert_eq!(
1587 dest.screen_chars_to_string(),
1588 "AB \n\
1589 \x20 \n\
1590 \x20 \n\
1591 \x20 \n"
1592 );
1593 }
1594
1595 #[test]
1596 fn copy_region() {
1597 let mut s = Surface::new(4, 3);
1598 s.add_change("w00t");
1599 s.add_change("foo");
1600 s.add_change("baar");
1601 s.add_change("baz");
1602 assert_eq!(
1603 s.screen_chars_to_string(),
1604 "foob\n\
1605 aarb\n\
1606 az \n"
1607 );
1608
1609 s.copy_region(0, 0, 2, 2, 2, 1);
1611 assert_eq!(
1612 s.screen_chars_to_string(),
1613 "foob\n\
1614 aafo\n\
1615 azaa\n"
1616 );
1617 }
1618
1619 #[test]
1620 fn double_width() {
1621 let mut s = Surface::new(4, 1);
1622 s.add_change("🤷12");
1623 assert_eq!(s.screen_chars_to_string(), "🤷12\n");
1624 s.add_change(Change::CursorPosition {
1625 x: Position::Absolute(1),
1626 y: Position::Absolute(0),
1627 });
1628 s.add_change("a🤷");
1629 assert_eq!(s.screen_chars_to_string(), " a🤷\n");
1630 s.add_change(Change::CursorPosition {
1631 x: Position::Absolute(2),
1632 y: Position::Absolute(0),
1633 });
1634 s.add_change("x");
1635 assert_eq!(s.screen_chars_to_string(), " ax \n");
1636 }
1637
1638 #[test]
1639 fn draw_double_width() {
1640 let mut s = Surface::new(4, 1);
1641 s.add_change("か a");
1642 assert_eq!(s.screen_chars_to_string(), "か a\n");
1643
1644 let mut s2 = Surface::new(4, 1);
1645 s2.draw_from_screen(&s, 0, 0);
1646 assert_eq!(s2.screen_chars_to_string(), "か a\n");
1649
1650 let s3 = Surface::new(4, 1);
1651 s2.draw_from_screen(&s3, 0, 0);
1652 assert_eq!(s2.screen_chars_to_string(), " \n");
1654
1655 let mut s4 = Surface::new(4, 1);
1656 s4.add_change("abcd");
1657 s.draw_from_screen(&s4, 0, 0);
1658 assert_eq!(s.screen_chars_to_string(), "abcd\n");
1661 }
1662
1663 #[test]
1664 fn diff_cursor_double_width() {
1665 let mut s = Surface::new(3, 1);
1666 s.add_change("かa");
1667
1668 let s2 = Surface::new(3, 1);
1669 let changes = s2.diff_region(0, 0, 3, 1, &s, 0, 0);
1670
1671 assert_eq!(
1672 changes
1673 .iter()
1674 .filter(|change| matches!(change, Change::CursorPosition { .. }))
1675 .count(),
1676 1
1677 );
1678 }
1679
1680 #[test]
1681 fn zero_width() {
1682 let mut s = Surface::new(4, 1);
1683 s.add_change("A\u{200b}B");
1685 assert_eq!(s.screen_chars_to_string(), "A\u{200b}B \n");
1686 }
1687
1688 #[test]
1689 fn images() {
1690 let data = Arc::new(ImageData::with_raw_data(vec![]));
1692 let mut s = Surface::new(2, 2);
1693 s.add_change(Change::Image(Image {
1694 top_left: TextureCoordinate::new_f32(0.0, 0.0),
1695 bottom_right: TextureCoordinate::new_f32(1.0, 1.0),
1696 image: data.clone(),
1697 width: 4,
1698 height: 2,
1699 }));
1700
1701 assert_eq!(
1706 s.screen_cells(),
1707 [
1708 [
1709 Cell::new(
1710 ' ',
1711 CellAttributes::default()
1712 .set_image(Box::new(ImageCell::new(
1713 TextureCoordinate::new_f32(0.0, 0.0),
1714 TextureCoordinate::new_f32(0.25, 0.5),
1715 data.clone()
1716 )))
1717 .clone()
1718 ),
1719 Cell::new(
1720 ' ',
1721 CellAttributes::default()
1722 .set_image(Box::new(ImageCell::new(
1723 TextureCoordinate::new_f32(0.25, 0.0),
1724 TextureCoordinate::new_f32(0.5, 0.5),
1725 data.clone()
1726 )))
1727 .clone()
1728 ),
1729 Cell::new(
1730 ' ',
1731 CellAttributes::default()
1732 .set_image(Box::new(ImageCell::new(
1733 TextureCoordinate::new_f32(0.5, 0.0),
1734 TextureCoordinate::new_f32(0.75, 0.5),
1735 data.clone()
1736 )))
1737 .clone()
1738 ),
1739 Cell::new(
1740 ' ',
1741 CellAttributes::default()
1742 .set_image(Box::new(ImageCell::new(
1743 TextureCoordinate::new_f32(0.75, 0.0),
1744 TextureCoordinate::new_f32(1.0, 0.5),
1745 data.clone()
1746 )))
1747 .clone()
1748 ),
1749 ],
1750 [
1751 Cell::new(
1752 ' ',
1753 CellAttributes::default()
1754 .set_image(Box::new(ImageCell::new(
1755 TextureCoordinate::new_f32(0.0, 0.5),
1756 TextureCoordinate::new_f32(0.25, 1.0),
1757 data.clone()
1758 )))
1759 .clone()
1760 ),
1761 Cell::new(
1762 ' ',
1763 CellAttributes::default()
1764 .set_image(Box::new(ImageCell::new(
1765 TextureCoordinate::new_f32(0.25, 0.5),
1766 TextureCoordinate::new_f32(0.5, 1.0),
1767 data.clone()
1768 )))
1769 .clone()
1770 ),
1771 Cell::new(
1772 ' ',
1773 CellAttributes::default()
1774 .set_image(Box::new(ImageCell::new(
1775 TextureCoordinate::new_f32(0.5, 0.5),
1776 TextureCoordinate::new_f32(0.75, 1.0),
1777 data.clone()
1778 )))
1779 .clone()
1780 ),
1781 Cell::new(
1782 ' ',
1783 CellAttributes::default()
1784 .set_image(Box::new(ImageCell::new(
1785 TextureCoordinate::new_f32(0.75, 0.5),
1786 TextureCoordinate::new_f32(1.0, 1.0),
1787 data.clone()
1788 )))
1789 .clone()
1790 ),
1791 ],
1792 ]
1793 );
1794
1795 let mut other = Surface::new(1, 1);
1798 other.add_change(Change::Image(Image {
1799 top_left: TextureCoordinate::new_f32(0.25, 0.3),
1800 bottom_right: TextureCoordinate::new_f32(0.75, 0.8),
1801 image: data.clone(),
1802 width: 1,
1803 height: 1,
1804 }));
1805 assert_eq!(
1806 other.screen_cells(),
1807 [[Cell::new(
1808 ' ',
1809 CellAttributes::default()
1810 .set_image(Box::new(ImageCell::new(
1811 TextureCoordinate::new_f32(0.25, 0.3),
1812 TextureCoordinate::new_f32(0.75, 0.8),
1813 data.clone()
1814 )))
1815 .clone()
1816 ),]]
1817 );
1818 }
1819}