uncurses/buffer/surface.rs
1//! Cell-grid surface traits.
2//!
3//! Buffer-like types ([`Buffer`](super::Buffer), [`Window`](super::Window),
4//! [`View`](super::View), [`TextBuffer`](super::TextBuffer), and
5//! [`Screen`](crate::screen::Screen)) all expose a rectangular region of
6//! [`Cell`]s. This module defines the traits that let drawing code use
7//! those regions without depending on a concrete storage type.
8//!
9//! ## Trait layers
10//!
11//! | trait | adds | required methods |
12//! |----------------|---------------------------------|------------------------------------------|
13//! | [`Bounded`] | rectangular extent | [`bounds`](Bounded::bounds) |
14//! | [`Surface`] | read-only cell access | [`cell`](Surface::cell) |
15//! | [`SurfaceMut`] | writes and terminal operations | [`set_cell`](SurfaceMut::set_cell), [`cell_mut`](SurfaceMut::cell_mut) |
16//!
17//! [`Surface`] extends [`Bounded`], and [`SurfaceMut`] extends
18//! [`Surface`]. Default methods supply common behavior such as
19//! [`Surface::draw`], [`SurfaceMut::fill_rect`],
20//! [`SurfaceMut::insert_lines`], and [`SurfaceMut::delete_cells`].
21//! Implementations may override defaults for storage-specific fast paths
22//! as long as they preserve the same visible semantics.
23//!
24//! ## Coordinates and bounds
25//!
26//! Coordinates are terminal-cell coordinates in the implementer's own
27//! coordinate space. A surface reports the rectangle where reads and writes
28//! are meaningful through [`Bounded::bounds`]; attempts outside that region
29//! should either return `None` or have no effect, as documented by each
30//! method.
31//!
32//! ```text
33//! Rect::new(2, 1, 4, 2)
34//!
35//! y=1 x=2 x=3 x=4 x=5
36//! ┌───┬───┬───┬───┐
37//! │ │ │ │ │
38//! y=2 ├───┼───┼───┼───┤
39//! │ │ │ │ │
40//! └───┴───┴───┴───┘
41//! ```
42//!
43//! ## Wide-cell invariants
44//!
45//! A wide grapheme occupies a primary cell plus a continuation placeholder
46//! in the following column. Methods that copy or fill cells step by the
47//! primary cell's display width and substitute blanks when a wide grapheme
48//! would be split by a source slice, a destination edge, or a fill region.
49
50use crate::cell::Cell;
51use crate::layout::{Position, Rect};
52
53/// A value with a rectangular extent in terminal-cell coordinates.
54///
55/// `Bounded` is the base trait for readable and writable surfaces. It does
56/// not imply that cells can be inspected or modified; it only describes the
57/// region where a value exists in its own coordinate space.
58///
59/// Implement this trait for types that can answer "what rectangle do I
60/// cover?" and then add [`Surface`] or [`SurfaceMut`] when cell access is
61/// available.
62pub trait Bounded {
63 /// Return the valid region in this value's own coordinate space.
64 ///
65 /// # Returns
66 ///
67 /// A [`Rect`] whose `x`/`y` form the top-left coordinate and whose
68 /// `width`/`height` form the extent in terminal cells.
69 ///
70 /// # Panics
71 ///
72 /// Implementations should not panic.
73 ///
74 /// # Usage notes
75 ///
76 /// For most storage-backed surfaces the origin is `(0, 0)`. Clipping
77 /// adaptors may report non-zero origins when they expose a sub-rectangle
78 /// in the wrapped surface's coordinate space.
79 fn bounds(&self) -> Rect;
80
81 /// Return the width of [`Self::bounds`] in terminal cell columns.
82 ///
83 /// # Returns
84 ///
85 /// `self.bounds().width`.
86 ///
87 /// # Panics
88 ///
89 /// Never panics unless [`Self::bounds`] panics.
90 ///
91 /// # Usage notes
92 ///
93 /// This is a convenience method; override only if computing full bounds
94 /// is meaningfully more expensive than returning the width.
95 fn width(&self) -> u16 {
96 self.bounds().width
97 }
98
99 /// Return the height of [`Self::bounds`] in terminal cell rows.
100 ///
101 /// # Returns
102 ///
103 /// `self.bounds().height`.
104 ///
105 /// # Panics
106 ///
107 /// Never panics unless [`Self::bounds`] panics.
108 ///
109 /// # Usage notes
110 ///
111 /// This is a convenience method; override only if computing full bounds
112 /// is meaningfully more expensive than returning the height.
113 fn height(&self) -> u16 {
114 self.bounds().height
115 }
116
117 /// Test whether a position lies inside [`Self::bounds`].
118 ///
119 /// # Parameters
120 ///
121 /// - `pos`: coordinate in this value's coordinate space.
122 ///
123 /// # Returns
124 ///
125 /// `true` when `pos` is inside the half-open rectangle described by
126 /// [`Self::bounds`], otherwise `false`.
127 ///
128 /// # Panics
129 ///
130 /// Never panics unless [`Self::bounds`] panics.
131 ///
132 /// # Usage notes
133 ///
134 /// Surfaces commonly use this to clip reads and writes before touching
135 /// their backing storage.
136 fn contains(&self, pos: Position) -> bool {
137 self.bounds().contains(pos)
138 }
139}
140
141/// A rectangular cell grid that can be read.
142///
143/// `Surface` adds immutable cell access to [`Bounded`]. It is the trait to
144/// use for render sources, snapshots, and helpers that only need to inspect
145/// cells or copy them into another surface.
146///
147/// The provided [`Surface::draw`] method is intentionally conservative
148/// about wide cells: it avoids producing orphan continuation columns and
149/// blanks wide primaries that would be split by source or destination
150/// bounds.
151pub trait Surface: Bounded {
152 /// Read the cell at a position.
153 ///
154 /// # Parameters
155 ///
156 /// - `pos`: coordinate in this surface's coordinate space.
157 ///
158 /// # Returns
159 ///
160 /// `Some(&Cell)` when `pos` is readable, or `None` when it is outside
161 /// [`Bounded::bounds`] or otherwise unavailable.
162 ///
163 /// # Panics
164 ///
165 /// Implementations should not panic for out-of-bounds positions.
166 ///
167 /// # Usage notes
168 ///
169 /// A returned cell may be a wide primary or a continuation placeholder.
170 /// Callers that walk rows should advance by `cell.width().max(1)` for
171 /// primary cells and handle continuations explicitly.
172 fn cell(&self, pos: Position) -> Option<&Cell>;
173
174 /// Copy `self`'s cells into `target`, mapping the top-left of
175 /// `self.bounds()` to `at` in target coordinates.
176 ///
177 /// # Parameters
178 ///
179 /// - `target`: writable destination surface.
180 /// - `at`: destination coordinate for the source bounds' top-left
181 /// corner.
182 ///
183 /// # Behavior
184 ///
185 /// The default walks the source row by row and emits one
186 /// [`SurfaceMut::set_cell`] call per source cell. Wide-cell
187 /// semantics are preserved:
188 ///
189 /// * Wide-primary cells advance the column cursor by their full
190 /// `width` so the source's continuation cells are not written
191 /// over independently.
192 /// * If the source has been sliced through the middle of a wide
193 /// grapheme (a leading continuation with no primary in view, or
194 /// a trailing primary with no room for its continuation), the
195 /// default substitutes a blank rather than emitting an orphan
196 /// half. The same blank substitution applies when a wide primary
197 /// would land at the right edge of `target` with no room for its
198 /// continuation.
199 ///
200 /// Implementations may override for a faster path (e.g. bulk line
201 /// copies), but must preserve the wide-cell invariants above.
202 ///
203 /// # Panics
204 ///
205 /// The default implementation does not panic unless the destination's
206 /// [`SurfaceMut::set_cell`] implementation panics.
207 ///
208 /// # Usage notes
209 ///
210 /// Destination clipping is delegated to `target`. Cells whose
211 /// destination coordinate lies outside the target bounds are passed to
212 /// `set_cell`, which is expected to ignore them.
213 fn draw<T: SurfaceMut + ?Sized>(&self, target: &mut T, at: Position) {
214 let b = self.bounds();
215 let tb = target.bounds();
216 let t_right = tb.x.saturating_add(tb.width);
217 for dy in 0..b.height {
218 let dst_y = at.y.saturating_add(dy);
219 let mut dx: u16 = 0;
220 while dx < b.width {
221 let src = Position::new(b.x + dx, b.y + dy);
222 let dst = Position::new(at.x.saturating_add(dx), dst_y);
223 let Some(cell) = self.cell(src) else {
224 dx += 1;
225 continue;
226 };
227
228 // Leading continuation with no primary in view: the
229 // source's left edge sliced a wide grapheme in half.
230 // Substitute a blank to avoid writing an orphan
231 // continuation into target.
232 if cell.is_continuation() {
233 target.set_cell(dst, &Cell::BLANK);
234 dx += 1;
235 continue;
236 }
237
238 let w = (cell.width() as u16).max(1);
239
240 // Wide primary that won't fit — either source's right
241 // edge sliced through its continuation, or target's
242 // right edge cuts it off. Emit a blank instead.
243 let fits_in_src = dx + w <= b.width;
244 let fits_in_dst = dst.x.saturating_add(w) <= t_right;
245 if w > 1 && (!fits_in_src || !fits_in_dst) {
246 target.set_cell(dst, &Cell::BLANK);
247 dx += 1;
248 continue;
249 }
250
251 target.set_cell(dst, cell);
252 dx += w;
253 }
254 }
255 }
256}
257
258/// A rectangular cell grid that can be modified.
259///
260/// `SurfaceMut` is the trait to use for render targets. It includes a
261/// primitive cell write plus higher-level operations used by terminal
262/// emulation and drawing code: fills, clears, line insertion/deletion, and
263/// cell insertion/deletion within a row.
264///
265/// Implementations are responsible for preserving the same wide-cell
266/// invariants as [`Buffer`](super::Buffer): a wide primary owns the
267/// following continuation slot, continuations should not become visible
268/// without their primary, and clipped wide writes should leave blanks
269/// rather than half a grapheme.
270pub trait SurfaceMut: Surface {
271 /// Place `cell` at `pos`. Implementations are responsible for
272 /// wide-cell semantics (continuation markers, blanking covered
273 /// cells) and any dirty tracking they care to do. Taking `&Cell`
274 /// lets implementations skip the clone when the destination
275 /// already matches.
276 ///
277 /// # Parameters
278 ///
279 /// - `pos`: destination coordinate in this surface's coordinate space.
280 /// - `cell`: cell to write.
281 ///
282 /// # Returns
283 ///
284 /// Nothing.
285 ///
286 /// # Panics
287 ///
288 /// Implementations should not panic for out-of-bounds positions.
289 ///
290 /// # Usage notes
291 ///
292 /// Out-of-bounds writes should be ignored. Use this method instead of
293 /// [`Self::cell_mut`] whenever changing content could affect display
294 /// width or neighboring continuation slots.
295 fn set_cell(&mut self, pos: Position, cell: &Cell);
296
297 /// Mutable handle to the cell at `pos`. Returns `None` for
298 /// out-of-bounds positions.
299 ///
300 /// # Parameters
301 ///
302 /// - `pos`: coordinate in this surface's coordinate space.
303 ///
304 /// # Returns
305 ///
306 /// `Some(&mut Cell)` for an in-bounds cell, or `None` when `pos` is not
307 /// writable.
308 ///
309 /// # Panics
310 ///
311 /// Implementations should not panic for out-of-bounds positions.
312 ///
313 /// # Usage notes
314 ///
315 /// Implementations that track dirty state must mark the cell as
316 /// touched eagerly — the caller may mutate any field through this
317 /// handle and the surface has no way to observe a write. Callers
318 /// must not change the cell's display width through this handle; use
319 /// [`Self::set_cell`] for wide-cell writes that need
320 /// continuation-column accounting.
321 fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell>;
322
323 /// Fill the entire surface bounds with `cell`.
324 ///
325 /// # Parameters
326 ///
327 /// - `cell`: fill cell to write repeatedly.
328 ///
329 /// # Returns
330 ///
331 /// Nothing.
332 ///
333 /// # Panics
334 ///
335 /// Does not panic unless [`Self::fill_rect`] panics.
336 ///
337 /// # Usage notes
338 ///
339 /// This delegates to [`Self::fill_rect`] with [`Bounded::bounds`].
340 /// Wide fills are handled by `fill_rect`.
341 fn fill(&mut self, cell: &Cell) {
342 let b = self.bounds();
343 self.fill_rect(b, cell);
344 }
345
346 /// Fill the intersection of `rect` and [`Bounded::bounds`] with
347 /// `cell`.
348 ///
349 /// # Parameters
350 ///
351 /// - `rect`: requested fill rectangle in this surface's coordinate
352 /// space.
353 /// - `cell`: fill cell to write.
354 ///
355 /// # Behavior
356 ///
357 /// Stepped by `cell.width()` so wide cells lay down clean
358 /// primary/continuation pairs; a trailing partial slot at the
359 /// right edge falls back to a blank. Implementations may override
360 /// for a bulk-blit fast path.
361 ///
362 /// # Panics
363 ///
364 /// The default implementation does not panic unless [`Self::set_cell`]
365 /// panics.
366 ///
367 /// # Usage notes
368 ///
369 /// Empty intersections are no-ops. A wide fill into an odd-width region
370 /// leaves the final single column blank because a two-column grapheme
371 /// cannot fit there.
372 fn fill_rect(&mut self, rect: Rect, cell: &Cell) {
373 let clipped = self.bounds().intersection(rect);
374 let step = (cell.width() as u16).max(1);
375 for y in clipped.top()..clipped.bottom() {
376 let mut x = clipped.left();
377 while x + step <= clipped.right() {
378 self.set_cell(Position::new(x, y), cell);
379 x += step;
380 }
381 while x < clipped.right() {
382 self.set_cell(Position::new(x, y), &Cell::BLANK);
383 x += 1;
384 }
385 }
386 }
387
388 /// Clear the entire surface bounds to [`Cell::BLANK`].
389 ///
390 /// # Returns
391 ///
392 /// Nothing.
393 ///
394 /// # Panics
395 ///
396 /// Does not panic unless [`Self::fill`] panics.
397 ///
398 /// # Usage notes
399 ///
400 /// This is equivalent to `self.fill(&Cell::BLANK)`.
401 fn clear(&mut self) {
402 self.fill(&Cell::BLANK);
403 }
404
405 /// Clear a rectangle to [`Cell::BLANK`].
406 ///
407 /// # Parameters
408 ///
409 /// - `rect`: requested clear rectangle in this surface's coordinate
410 /// space.
411 ///
412 /// # Returns
413 ///
414 /// Nothing.
415 ///
416 /// # Panics
417 ///
418 /// Does not panic unless [`Self::fill_rect`] panics.
419 ///
420 /// # Usage notes
421 ///
422 /// Only the intersection of `rect` and [`Bounded::bounds`] is modified.
423 fn clear_rect(&mut self, rect: Rect) {
424 self.fill_rect(rect, &Cell::BLANK);
425 }
426
427 /// Insert `n` blank rows at `y`, pushing existing rows down within
428 /// `[y, bounds_bottom)`. Rows pushed past `bounds_bottom` are lost.
429 /// Freed top rows are filled with `fill`.
430 ///
431 /// # Parameters
432 ///
433 /// - `y`: first row to insert at, in this surface's coordinate space.
434 /// - `n`: number of rows to insert.
435 /// - `bounds_bottom`: exclusive lower row bound for the affected region.
436 /// - `fill`: cell used to fill the newly opened rows.
437 ///
438 /// # Behavior
439 ///
440 /// The default implementation copies cells through [`Self::set_cell`]
441 /// row-by-row from the bottom up, advancing along each source row by
442 /// the source cell's width so wide-primary cells move as a unit and
443 /// their continuation columns are not independently rewritten.
444 /// Wide primaries that no longer fit are replaced by a blank.
445 /// Implementations backed by contiguous row storage may override
446 /// with a row-swap fast path.
447 ///
448 /// # Panics
449 ///
450 /// The default implementation does not panic unless [`Self::set_cell`]
451 /// panics.
452 ///
453 /// # Usage notes
454 ///
455 /// `bounds_bottom` is clamped to the surface height. Calls with
456 /// `n == 0` or `y >= bounds_bottom` are no-ops.
457 fn insert_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
458 let h = self.bounds().height;
459 let bottom = bounds_bottom.min(h);
460 if y >= bottom || n == 0 {
461 return;
462 }
463 let n = n.min(bottom - y);
464 let width = self.bounds().width;
465 let mut dst = bottom;
466 while dst > y + n {
467 dst -= 1;
468 let src = dst - n;
469 copy_row(self, src, dst, width);
470 }
471 for row in y..y + n {
472 blank_row(self, row, width, fill);
473 }
474 }
475
476 /// Delete `n` rows at `y`, pulling existing rows up within
477 /// `[y, bounds_bottom)`. The bottom `n` rows of the window are
478 /// filled with `fill`.
479 ///
480 /// # Parameters
481 ///
482 /// - `y`: first row to delete, in this surface's coordinate space.
483 /// - `n`: number of rows to delete.
484 /// - `bounds_bottom`: exclusive lower row bound for the affected region.
485 /// - `fill`: cell used to fill the freed bottom rows.
486 ///
487 /// # Behavior
488 ///
489 /// The default implementation copies cells through [`Self::set_cell`]
490 /// row-by-row from top down, advancing along each source row by the
491 /// source cell's width so wide-primary cells move as a unit and
492 /// their continuation columns are not independently rewritten.
493 /// Wide primaries that no longer fit are replaced by a blank.
494 /// Implementations backed by contiguous row storage may override
495 /// with a row-swap fast path.
496 ///
497 /// # Panics
498 ///
499 /// The default implementation does not panic unless [`Self::set_cell`]
500 /// panics.
501 ///
502 /// # Usage notes
503 ///
504 /// `bounds_bottom` is clamped to the surface height. Calls with
505 /// `n == 0` or `y >= bounds_bottom` are no-ops.
506 fn delete_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
507 let h = self.bounds().height;
508 let bottom = bounds_bottom.min(h);
509 if y >= bottom || n == 0 {
510 return;
511 }
512 let n = n.min(bottom - y);
513 let width = self.bounds().width;
514 for dst in y..bottom - n {
515 let src = dst + n;
516 copy_row(self, src, dst, width);
517 }
518 for row in bottom - n..bottom {
519 blank_row(self, row, width, fill);
520 }
521 }
522
523 /// Insert `n` blank cells at `pos`, pushing cells in
524 /// `[pos.x, bounds_right)` right within row `pos.y`. Cells pushed
525 /// past `bounds_right` are lost. The freed slots `[pos.x, pos.x + n)`
526 /// are filled with `fill`.
527 ///
528 /// # Parameters
529 ///
530 /// - `pos`: row and starting column for insertion.
531 /// - `n`: number of cells to insert.
532 /// - `bounds_right`: exclusive right column bound for the affected row
533 /// region.
534 /// - `fill`: cell used to fill the newly opened slots.
535 ///
536 /// # Behavior
537 ///
538 /// The default implementation snapshots the row's primary cells in
539 /// the affected window through [`Surface::cell`], blanks the window
540 /// via [`Self::set_cell`], then replays each surviving primary at
541 /// its shifted position. Wide primaries whose new footprint would
542 /// cross `bounds_right` are dropped. Implementations backed by a
543 /// contiguous row slice may override with an in-place rotate.
544 ///
545 /// # Panics
546 ///
547 /// The default implementation does not panic unless [`Self::set_cell`]
548 /// panics.
549 ///
550 /// # Usage notes
551 ///
552 /// `bounds_right` is clamped to the surface width. Calls with `n == 0`,
553 /// an out-of-bounds row, or `pos.x >= bounds_right` are no-ops.
554 fn insert_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
555 let bounds = self.bounds();
556 if pos.y >= bounds.height || pos.x >= bounds.width || n == 0 {
557 return;
558 }
559 let right = bounds_right.min(bounds.width);
560 if pos.x >= right {
561 return;
562 }
563 let n = n.min(right - pos.x);
564
565 let primaries = collect_primaries(self, pos.y, pos.x, right);
566 blank_span(self, pos.y, pos.x, right);
567
568 for (src_x, cell) in primaries.iter().rev() {
569 let dst_x = src_x.saturating_add(n);
570 let cw = (cell.width() as u16).max(1);
571 if dst_x >= right || dst_x + cw > right {
572 continue;
573 }
574 self.set_cell(Position::new(dst_x, pos.y), cell);
575 }
576
577 fill_span(self, pos.y, pos.x, pos.x + n, fill);
578 }
579
580 /// Delete `n` cells at `pos`, pulling cells in
581 /// `[pos.x + n, bounds_right)` left within row `pos.y`. The freed
582 /// slots `[bounds_right - n, bounds_right)` are filled with `fill`.
583 ///
584 /// # Parameters
585 ///
586 /// - `pos`: row and starting column for deletion.
587 /// - `n`: number of cells to delete.
588 /// - `bounds_right`: exclusive right column bound for the affected row
589 /// region.
590 /// - `fill`: cell used to fill the freed right-edge slots.
591 ///
592 /// # Behavior
593 ///
594 /// The default implementation snapshots the row's primary cells in
595 /// the affected window through [`Surface::cell`], blanks the window
596 /// via [`Self::set_cell`], then replays each surviving primary at
597 /// its shifted position. Primaries whose source column falls inside
598 /// `[pos.x, pos.x + n)` are dropped. Implementations backed by a
599 /// contiguous row slice may override with an in-place rotate.
600 ///
601 /// # Panics
602 ///
603 /// The default implementation does not panic unless [`Self::set_cell`]
604 /// panics.
605 ///
606 /// # Usage notes
607 ///
608 /// `bounds_right` is clamped to the surface width. Calls with `n == 0`,
609 /// an out-of-bounds row, or `pos.x >= bounds_right` are no-ops.
610 fn delete_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
611 let bounds = self.bounds();
612 if pos.y >= bounds.height || pos.x >= bounds.width || n == 0 {
613 return;
614 }
615 let right = bounds_right.min(bounds.width);
616 if pos.x >= right {
617 return;
618 }
619 let n = n.min(right - pos.x);
620
621 let primaries = collect_primaries(self, pos.y, pos.x, right);
622 blank_span(self, pos.y, pos.x, right);
623
624 let cut = pos.x + n;
625 for (src_x, cell) in primaries.iter() {
626 if *src_x < cut {
627 continue;
628 }
629 let dst_x = src_x - n;
630 let cw = (cell.width() as u16).max(1);
631 if dst_x + cw > right {
632 continue;
633 }
634 self.set_cell(Position::new(dst_x, pos.y), cell);
635 }
636
637 fill_span(self, pos.y, right - n, right, fill);
638 }
639}
640
641fn collect_primaries<S: SurfaceMut + ?Sized>(
642 s: &S,
643 y: u16,
644 left: u16,
645 right: u16,
646) -> Vec<(u16, Cell)> {
647 let mut out = Vec::new();
648 let mut x = left;
649 while x < right {
650 if let Some(cell) = s.cell(Position::new(x, y))
651 && !cell.is_continuation()
652 {
653 let cw = (cell.width() as u16).max(1);
654 out.push((x, cell.clone()));
655 x = x.saturating_add(cw);
656 continue;
657 }
658 x = x.saturating_add(1);
659 }
660 out
661}
662
663fn blank_span<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, left: u16, right: u16) {
664 for x in left..right {
665 s.set_cell(Position::new(x, y), &Cell::BLANK);
666 }
667}
668
669fn fill_span<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, left: u16, right: u16, fill: &Cell) {
670 if left >= right {
671 return;
672 }
673 let fill_w = (fill.width() as u16).max(1);
674 let mut col = left;
675 while col + fill_w <= right {
676 s.set_cell(Position::new(col, y), fill);
677 col += fill_w;
678 }
679 while col < right {
680 s.set_cell(Position::new(col, y), &Cell::BLANK);
681 col += 1;
682 }
683}
684
685fn copy_row<S: SurfaceMut + ?Sized>(s: &mut S, src_y: u16, dst_y: u16, width: u16) {
686 let mut x: u16 = 0;
687 while x < width {
688 let src = Position::new(x, src_y);
689 let dst = Position::new(x, dst_y);
690 let Some(cell) = s.cell(src).cloned() else {
691 x += 1;
692 continue;
693 };
694 if cell.is_continuation() {
695 s.set_cell(dst, &Cell::BLANK);
696 x += 1;
697 continue;
698 }
699 let cw = (cell.width() as u16).max(1);
700 if cw > 1 && x + cw > width {
701 s.set_cell(dst, &Cell::BLANK);
702 x += 1;
703 } else {
704 s.set_cell(dst, &cell);
705 x += cw;
706 }
707 }
708}
709
710fn blank_row<S: SurfaceMut + ?Sized>(s: &mut S, y: u16, width: u16, fill: &Cell) {
711 for x in 0..width {
712 s.set_cell(Position::new(x, y), fill);
713 }
714}
715
716impl Bounded for Rect {
717 fn bounds(&self) -> Rect {
718 *self
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use crate::buffer::Buffer;
726
727 fn wide(s: &str) -> Cell {
728 Cell::wide(s)
729 }
730
731 fn cont() -> Cell {
732 Cell::continuation()
733 }
734
735 #[test]
736 fn draw_copies_normal_cells() {
737 let mut src = Buffer::new(2, 1);
738 src.set((0, 0), &Cell::narrow("A"));
739 src.set((1, 0), &Cell::narrow("B"));
740 let mut dst = Buffer::new(4, 1);
741 src.draw(&mut dst, Position::new(1, 0));
742 assert_eq!(dst.cell(Position::new(0, 0)).unwrap().content(), " ");
743 assert_eq!(dst.cell(Position::new(1, 0)).unwrap().content(), "A");
744 assert_eq!(dst.cell(Position::new(2, 0)).unwrap().content(), "B");
745 }
746
747 #[test]
748 fn draw_preserves_wide_pair() {
749 let mut src = Buffer::new(2, 1);
750 src.set((0, 0), &wide("世"));
751 // Buffer::set already wrote the continuation at (1,0).
752 let mut dst = Buffer::new(2, 1);
753 src.draw(&mut dst, Position::new(0, 0));
754 let primary = dst.cell(Position::new(0, 0)).unwrap();
755 let cont_cell = dst.cell(Position::new(1, 0)).unwrap();
756 assert_eq!(primary.content(), "世");
757 assert_eq!(primary.width(), 2);
758 assert!(cont_cell.is_continuation());
759 }
760
761 #[test]
762 fn draw_blanks_leading_continuation_in_source_slice() {
763 // Construct a source whose first column is the orphan
764 // continuation half of a wide cell that lives outside the
765 // slice. The default must not propagate the orphan.
766 let mut src = Buffer::new(2, 1);
767 src.set((0, 0), &cont());
768 src.set((1, 0), &Cell::narrow("X"));
769
770 let mut dst = Buffer::new(2, 1);
771 // Pre-seed target with an unrelated wide cell to make sure
772 // we'd notice a corruption.
773 dst.set((0, 0), &Cell::narrow("Y"));
774 dst.set((1, 0), &Cell::narrow("Z"));
775
776 src.draw(&mut dst, Position::new(0, 0));
777
778 // The orphan continuation became a blank — no spurious
779 // continuation marker carried over.
780 assert!(!dst.cell(Position::new(0, 0)).unwrap().is_continuation());
781 assert_eq!(dst.cell(Position::new(0, 0)).unwrap().content(), " ");
782 assert_eq!(dst.cell(Position::new(1, 0)).unwrap().content(), "X");
783 }
784
785 #[test]
786 fn draw_blanks_wide_primary_with_no_room_in_target() {
787 // Wide primary lands at the last column of the target — its
788 // continuation would fall outside target bounds. Substitute
789 // a blank rather than emitting a half-drawn grapheme.
790 let mut src = Buffer::new(2, 1);
791 src.set((0, 0), &wide("世"));
792
793 let mut dst = Buffer::new(2, 1);
794 src.draw(&mut dst, Position::new(1, 0));
795
796 let landed = dst.cell(Position::new(1, 0)).unwrap();
797 assert_eq!(landed.content(), " ");
798 assert_eq!(landed.width(), 1);
799 }
800
801 // The remaining tests exercise the SurfaceMut default implementations
802 // of insert/delete lines and insert/delete cells via Window, which
803 // does not override them.
804 use crate::buffer::Window;
805
806 fn window_with(content: &[&str]) -> Window {
807 let w = content[0].chars().count() as u16;
808 let h = content.len() as u16;
809 let mut win = Window::new(w, h);
810 for (y, row) in content.iter().enumerate() {
811 for (x, ch) in row.chars().enumerate() {
812 win.set_cell(
813 Position::new(x as u16, y as u16),
814 &Cell::narrow(ch.to_string()),
815 );
816 }
817 }
818 win
819 }
820
821 fn row_content(win: &Window, y: u16) -> String {
822 (0..win.bounds().width)
823 .map(|x| {
824 win.cell(Position::new(x, y))
825 .map(|c| c.content().to_string())
826 .unwrap_or_default()
827 })
828 .collect()
829 }
830
831 #[test]
832 fn default_insert_lines_shifts_down_and_blanks_top() {
833 let mut win = window_with(&["AAA", "BBB", "CCC", "DDD"]);
834 SurfaceMut::insert_lines(&mut win, 1, 1, 4, &Cell::BLANK);
835 assert_eq!(row_content(&win, 0), "AAA");
836 assert_eq!(row_content(&win, 1), " ");
837 assert_eq!(row_content(&win, 2), "BBB");
838 assert_eq!(row_content(&win, 3), "CCC");
839 }
840
841 #[test]
842 fn default_delete_lines_pulls_up_and_blanks_bottom() {
843 let mut win = window_with(&["AAA", "BBB", "CCC", "DDD"]);
844 SurfaceMut::delete_lines(&mut win, 1, 1, 4, &Cell::BLANK);
845 assert_eq!(row_content(&win, 0), "AAA");
846 assert_eq!(row_content(&win, 1), "CCC");
847 assert_eq!(row_content(&win, 2), "DDD");
848 assert_eq!(row_content(&win, 3), " ");
849 }
850
851 #[test]
852 fn default_insert_cells_shifts_right_and_blanks_left() {
853 let mut win = window_with(&["ABCDE"]);
854 SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 2, 5, &Cell::BLANK);
855 assert_eq!(row_content(&win, 0), "A BC");
856 }
857
858 #[test]
859 fn default_delete_cells_pulls_left_and_blanks_right() {
860 let mut win = window_with(&["ABCDE"]);
861 SurfaceMut::delete_cells(&mut win, Position::new(1, 0), 2, 5, &Cell::BLANK);
862 assert_eq!(row_content(&win, 0), "ADE ");
863 }
864
865 #[test]
866 fn default_insert_cells_drops_wide_that_no_longer_fits() {
867 // Row: A 世(prim) 世(cont) B, then insert 1 at col 1 with right=4.
868 // 世's new primary would be at col 2, continuation at col 3.
869 // That still fits (col 3 < right=4). So 世 is preserved.
870 let mut win = Window::new(4, 1);
871 win.set_cell(Position::new(0, 0), &Cell::narrow("A"));
872 win.set_cell(Position::new(1, 0), &wide("世"));
873 win.set_cell(Position::new(3, 0), &Cell::narrow("B"));
874 SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 1, 4, &Cell::BLANK);
875
876 assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
877 assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), " ");
878 assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), "世");
879 assert!(win.cell(Position::new(3, 0)).unwrap().is_continuation());
880 }
881
882 #[test]
883 fn default_insert_cells_blanks_wide_that_overflows() {
884 // Row: A B 世(prim) 世(cont), then insert 1 at col 1 with right=4.
885 // 世 would move from col 2 to col 3, continuation to col 4 (out).
886 // So 世 must be dropped, leaving a blank at col 3.
887 let mut win = Window::new(4, 1);
888 win.set_cell(Position::new(0, 0), &Cell::narrow("A"));
889 win.set_cell(Position::new(1, 0), &Cell::narrow("B"));
890 win.set_cell(Position::new(2, 0), &wide("世"));
891 SurfaceMut::insert_cells(&mut win, Position::new(1, 0), 1, 4, &Cell::BLANK);
892
893 assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
894 assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), " ");
895 assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), "B");
896 assert_eq!(win.cell(Position::new(3, 0)).unwrap().content(), " ");
897 }
898
899 #[test]
900 fn default_delete_cells_drops_primary_in_deletion_window() {
901 // Row: A 世(prim) 世(cont) B, delete 2 at col 1 with right=4.
902 // 世's primary falls inside [1, 3) → dropped entirely.
903 let mut win = Window::new(4, 1);
904 win.set_cell(Position::new(0, 0), &Cell::narrow("A"));
905 win.set_cell(Position::new(1, 0), &wide("世"));
906 win.set_cell(Position::new(3, 0), &Cell::narrow("B"));
907 SurfaceMut::delete_cells(&mut win, Position::new(1, 0), 2, 4, &Cell::BLANK);
908
909 assert_eq!(win.cell(Position::new(0, 0)).unwrap().content(), "A");
910 assert_eq!(win.cell(Position::new(1, 0)).unwrap().content(), "B");
911 assert_eq!(win.cell(Position::new(2, 0)).unwrap().content(), " ");
912 assert_eq!(win.cell(Position::new(3, 0)).unwrap().content(), " ");
913 }
914}