uncurses/buffer/mod.rs
1//! Cell-grid storage and surface traits.
2//!
3//! This module provides [`Buffer`], row helpers, rectangular views, and
4//! owned [`Window`]s for working with terminal cells. Use it when code
5//! needs an off-screen grid, a clipped view of an existing grid, or a
6//! common [`Surface`] / [`SurfaceMut`] API that drawing code can target.
7//!
8//! ## Surface trait family
9//!
10//! The surface traits describe terminal-cell grids in layers:
11//!
12//! - [`Bounded`] exposes the rectangular extent of a grid.
13//! - [`Surface`] adds read-only cell access and the default
14//! [`draw`](Surface::draw) blit operation.
15//! - [`SurfaceMut`] adds mutation, clearing, rectangular fills, and
16//! terminal-style insert/delete operations.
17//!
18//! Code written against [`Surface`] or [`SurfaceMut`] can operate on a
19//! [`Buffer`], [`TextBuffer`], [`Window`], [`View`], or
20//! [`Screen`](crate::screen::Screen) without caring where the cells are
21//! stored. The shared contract is a rectangular coordinate space of
22//! [`Cell`] values addressed by
23//! [`Position`].
24//!
25//! ```rust,ignore
26//! use uncurses::buffer::{Buffer, Surface, SurfaceMut};
27//! use uncurses::cell::Cell;
28//! use uncurses::layout::Position;
29//!
30//! let mut buf = Buffer::new(4, 2);
31//! buf.set_cell(Position::new(0, 0), &Cell::new("x", 1));
32//! assert_eq!(buf.cell(Position::new(0, 0)).unwrap().content(), "x");
33//! ```
34//!
35//! ## Buffer storage
36//!
37//! [`Buffer`] stores a fixed-size grid in one row-major `Vec<Cell>`.
38//! Column `x` and row `y` map to `cells[y * width + x]`, so each row is a
39//! contiguous slice and cloning the buffer requires only one allocation.
40//! Resizing allocates a new row-major backing store, copies the
41//! intersection of the old and new extents, and fills new slots with
42//! [`Cell::BLANK`](crate::cell::Cell::BLANK).
43//!
44//! ```text
45//! col: 0 1 2 3
46//! ┌───┬───┬───────┬───┐
47//! row 0 (y=0) │ H │ i │ 日 │ ! │
48//! └───┴───┴───────┴───┘
49//! ▲
50//! └─ wide primary at x=2 covers columns 2 and 3
51//! ```
52//!
53//! ## Wide cells and drawing
54//!
55//! A two-column grapheme is represented by a wide primary cell followed by
56//! a continuation placeholder in the next column. [`Buffer::set`] writes
57//! that placeholder automatically, blanks stale halves when overwriting an
58//! existing wide cell, and replaces a wide cell with a blank when it would
59//! not fit at the end of a row. The default [`Surface::draw`] implementation
60//! preserves the same invariant when blitting between surfaces: orphan
61//! continuations and clipped wide primaries are emitted as blanks.
62
63mod ops;
64mod surface;
65mod view;
66mod window;
67
68mod line;
69#[cfg(test)]
70mod tests;
71mod text_buffer;
72
73pub(crate) use line::fill_line_into;
74pub use line::{Line, blank_line, fill_line};
75pub use surface::{Bounded, Surface, SurfaceMut};
76pub use text_buffer::TextBuffer;
77pub use view::View;
78pub use window::Window;
79
80use crate::cell::Cell;
81use crate::layout::{Position, Rect, overruns};
82
83/// Off-screen storage for a rectangular grid of terminal cells.
84///
85/// A `Buffer` owns `width * height` [`Cell`] values in row-major order.
86/// Row `y` lives at `cells[y * width..(y + 1) * width]`, and column `x`
87/// within that row is addressed by [`Position::new`](crate::layout::Position::new)
88/// through the [`Surface`] and [`SurfaceMut`] APIs.
89///
90/// Use a buffer when rendering into memory before presenting the result to
91/// another surface, when composing with [`Window`]s, or when tests need a
92/// deterministic cell grid. Writes outside the buffer bounds are ignored;
93/// reads outside the bounds return `None`.
94///
95/// A wide primary [`Cell`] owns one continuation cell per additional column,
96/// for a total of `width - 1` continuations. Prefer [`Buffer::set`] or
97/// [`SurfaceMut::set_cell`] for writes so that continuation slots are kept
98/// consistent.
99#[derive(Debug, Clone)]
100pub struct Buffer {
101 cells: Vec<Cell>,
102 width: u16,
103 height: u16,
104}
105
106impl Buffer {
107 /// Create a new blank buffer.
108 ///
109 /// The returned buffer has `width * height` cells, all initialized to
110 /// [`Cell::BLANK`]. `width` and `height` are measured in terminal cell
111 /// columns and rows, not bytes or grapheme clusters.
112 ///
113 /// # Parameters
114 ///
115 /// - `width`: number of columns in each row.
116 /// - `height`: number of rows.
117 ///
118 /// # Returns
119 ///
120 /// A row-major [`Buffer`] with bounds `Rect::new(0, 0, width, height)`.
121 ///
122 /// # Panics
123 ///
124 /// Panics if `width * height` cannot be allocated.
125 ///
126 /// # Usage notes
127 ///
128 /// Zero-sized dimensions are valid. Accessors will then return empty
129 /// rows or `None` according to the resulting bounds.
130 pub fn new(width: u16, height: u16) -> Self {
131 let total = (width as usize) * (height as usize);
132 let cells = vec![Cell::BLANK; total];
133 Self {
134 cells,
135 width,
136 height,
137 }
138 }
139
140 /// Return the buffer width in terminal cell columns.
141 ///
142 /// # Returns
143 ///
144 /// The number of columns in each row.
145 ///
146 /// # Panics
147 ///
148 /// Never panics.
149 ///
150 /// # Usage notes
151 ///
152 /// This is the inherent accessor for the stored width. The [`Bounded`]
153 /// implementation reports the same value through [`Bounded::width`].
154 pub fn width(&self) -> u16 {
155 self.width
156 }
157
158 /// Return the buffer height in terminal cell rows.
159 ///
160 /// # Returns
161 ///
162 /// The number of rows in the buffer.
163 ///
164 /// # Panics
165 ///
166 /// Never panics.
167 ///
168 /// # Usage notes
169 ///
170 /// This is the inherent accessor for the stored height. The [`Bounded`]
171 /// implementation reports the same value through [`Bounded::height`].
172 pub fn height(&self) -> u16 {
173 self.height
174 }
175
176 /// Borrow one row as a contiguous slice.
177 ///
178 /// # Parameters
179 ///
180 /// - `y`: zero-based row index in buffer coordinates.
181 ///
182 /// # Returns
183 ///
184 /// `Some(&[Cell])` with length [`Buffer::width`] when `y` is inside the
185 /// buffer, or `None` when `y >= height`.
186 ///
187 /// # Panics
188 ///
189 /// Never panics.
190 ///
191 /// # Usage notes
192 ///
193 /// The returned slice may contain continuation cells that belong to
194 /// wide primaries earlier in the same row. Do not assume every slice
195 /// element starts an independent grapheme.
196 #[inline]
197 pub fn line(&self, y: u16) -> Option<&[Cell]> {
198 if y >= self.height {
199 return None;
200 }
201 let w = self.width as usize;
202 let start = (y as usize) * w;
203 Some(&self.cells[start..start + w])
204 }
205
206 /// Mutably borrow one row as a contiguous slice.
207 ///
208 /// # Parameters
209 ///
210 /// - `y`: zero-based row index in buffer coordinates.
211 ///
212 /// # Returns
213 ///
214 /// `Some(&mut [Cell])` with length [`Buffer::width`] when `y` is inside
215 /// the buffer, or `None` when `y >= height`.
216 ///
217 /// # Panics
218 ///
219 /// Never panics.
220 ///
221 /// # Usage notes
222 ///
223 /// Direct slice mutation bypasses the wide-cell accounting performed by
224 /// [`Buffer::set`]. It is appropriate for whole-row helpers that manage
225 /// continuations themselves; prefer [`Buffer::set`] for ordinary writes.
226 #[inline]
227 pub fn line_mut(&mut self, y: u16) -> Option<&mut [Cell]> {
228 if y >= self.height {
229 return None;
230 }
231 let w = self.width as usize;
232 let start = (y as usize) * w;
233 Some(&mut self.cells[start..start + w])
234 }
235
236 /// Swap the contents of rows `a` and `b` in place.
237 fn swap_rows(&mut self, a: usize, b: usize) {
238 if a == b {
239 return;
240 }
241 let w = self.width as usize;
242 if w == 0 {
243 return;
244 }
245 let (lo, hi) = if a < b { (a, b) } else { (b, a) };
246 let split = hi * w;
247 let (left, right) = self.cells.split_at_mut(split);
248 left[lo * w..(lo + 1) * w].swap_with_slice(&mut right[..w]);
249 }
250
251 /// Borrow one cell mutably.
252 ///
253 /// # Parameters
254 ///
255 /// - `pos`: zero-based cell coordinate in buffer coordinates.
256 ///
257 /// # Returns
258 ///
259 /// `Some(&mut Cell)` for an in-bounds position, or `None` when `pos`
260 /// lies outside the buffer.
261 ///
262 /// # Panics
263 ///
264 /// Never panics.
265 ///
266 /// # Usage notes
267 ///
268 /// Mutating a cell through this handle does not update neighboring
269 /// continuation cells. Do not change a cell from narrow to wide, wide to
270 /// narrow, or continuation to primary through this handle; use
271 /// [`Buffer::set`] or [`SurfaceMut::set_cell`] when the cell width may
272 /// change.
273 pub fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell> {
274 if pos.y >= self.height || pos.x >= self.width {
275 return None;
276 }
277 let w = self.width as usize;
278 Some(&mut self.cells[(pos.y as usize) * w + (pos.x as usize)])
279 }
280
281 /// Set a cell at a position while preserving wide-cell invariants.
282 ///
283 /// # Parameters
284 ///
285 /// - `pos`: zero-based destination coordinate. Any type convertible into
286 /// [`Position`] is accepted.
287 /// - `cell`: cell to clone into the destination.
288 ///
289 /// # Behavior
290 ///
291 /// In-bounds narrow writes replace exactly one column, blanking any stale
292 /// wide-cell halves they overwrite. In-bounds wide writes place `cell` at
293 /// `pos` and write continuation placeholders into the columns it covers.
294 /// If a wide cell would extend past the right edge of the row, the
295 /// destination column is set to [`Cell::BLANK`] instead.
296 ///
297 /// Out-of-bounds writes are ignored.
298 ///
299 /// # Panics
300 ///
301 /// Never panics.
302 ///
303 /// # Usage notes
304 ///
305 /// A continuation is placed by the wide cell that owns it, as part of
306 /// that cell's own write, so a continuation passed here is ignored and
307 /// the column keeps the value its owner gave it.
308 ///
309 /// This is the implementation behind [`SurfaceMut::set_cell`] for
310 /// `Buffer`. Use it instead of [`Buffer::cell_mut`] whenever the write
311 /// might affect the width or role of neighboring cells.
312 pub fn set(&mut self, pos: impl Into<Position>, cell: &Cell) {
313 let pos = pos.into();
314 let y = pos.y as usize;
315 let x = pos.x as usize;
316 let width = self.width as usize;
317
318 if pos.y >= self.height || x >= width {
319 return;
320 }
321
322 let line = &mut self.cells[y * width..(y + 1) * width];
323
324 // A continuation is placed by the wide cell to its left, as part of
325 // that cell's own write, so one arriving here has nothing to say
326 // that the owning write did not already say. Ignoring it keeps the
327 // column exactly as its owner left it, which is what a caller
328 // mirroring a buffer cell by cell wants: it walks continuations
329 // because they are in the grid, not because it means to move them.
330 //
331 // Ignoring is also the only safe answer. Storing the cell would let
332 // a column carry a style its owner never had. Blanking it would put
333 // a one-column cell where the owner accounts for two, so the row
334 // would emit more columns than it holds. Refusing loudly would make
335 // a redundant write in an otherwise correct mirror fatal.
336 if cell.is_continuation() {
337 return;
338 }
339
340 // If we're overwriting a wide cell's continuation, blank the primary
341 // cell that owns it, so the wide grapheme does not survive with one
342 // half replaced. The branch above already returned for an incoming
343 // continuation, so the cell arriving here is narrow or wide.
344 if x > 0 && line[x].is_continuation() {
345 // Walk backward to find the primary cell
346 let mut pc = x - 1;
347 while pc > 0 && line[pc].is_continuation() {
348 pc -= 1;
349 }
350 // Verify we found a wide primary; only Wide cells own
351 // the continuation slot to their right.
352 if line[pc].is_wide() {
353 let pw = line[pc].width() as usize;
354 let end = (pc + pw).min(width);
355 // Preserve the broken wide cell's bg/attributes on the
356 // blanks so clearing it doesn't punch a hole in a colored
357 // background.
358 let blank = Cell::BLANK.style(line[pc].style.clone());
359 line[pc..end].fill(blank);
360 } else if line[pc].is_continuation() {
361 // Buffer is corrupted — just blank the single cell
362 line[x] = Cell::BLANK;
363 }
364 }
365
366 // If we're overwriting the primary cell of a wide char, blank continuations
367 if line[x].is_wide() {
368 let w = line[x].width() as usize;
369 let end = (x + w).min(width);
370 let blank = Cell::BLANK.style(line[x].style.clone());
371 line[x + 1..end].fill(blank);
372 }
373
374 let cell_width = cell.width() as usize;
375
376 // If the new cell is wide, blank cells it will cover
377 if cell.is_wide() {
378 // Truncate at end of line: if the wide cell doesn't fit, replace
379 // it with a blank that keeps the wide cell's bg/attributes.
380 //
381 // This has to be settled before any continuation is written. A
382 // cell can claim more columns than the row has left, and laying
383 // the continuations down first would leave the row holding the
384 // tail of a cell whose primary this branch then replaces.
385 if x + cell_width > width {
386 line[x] = Cell::BLANK.style(cell.style.clone());
387 return;
388 }
389
390 for i in x + 1..x + cell_width {
391 // If we'd overwrite a wide cell's primary, blank its continuations
392 if line[i].is_wide() {
393 let w = line[i].width() as usize;
394 let end = (i + w).min(width);
395 let blank = Cell::BLANK.style(line[i].style.clone());
396 line[i + 1..end].fill(blank);
397 }
398 // Continuations inherit the wide primary's style so the
399 // cell's bg/attributes are coherent across both columns.
400 line[i] = Cell::CONTINUATION.style(cell.style.clone());
401 }
402 }
403
404 line[x] = cell.clone();
405 }
406
407 /// Resize the buffer, preserving the top-left intersection.
408 ///
409 /// # Parameters
410 ///
411 /// - `width`: new row width in terminal cell columns.
412 /// - `height`: new height in terminal cell rows.
413 ///
414 /// # Behavior
415 ///
416 /// Cells inside the intersection of the old and new bounds keep their
417 /// row and column. Newly exposed cells are filled with [`Cell::BLANK`],
418 /// and cells outside the new bounds are discarded.
419 ///
420 /// # Panics
421 ///
422 /// Panics if the resized backing store cannot be allocated.
423 ///
424 /// # Usage notes
425 ///
426 /// Resizing copies cells structurally and does not reflow wide cells. If
427 /// the new right edge cuts through a wide grapheme, the copied
428 /// continuation or primary remains as stored until later writes or draws
429 /// normalize the affected edge.
430 pub fn resize(&mut self, width: u16, height: u16) {
431 if width == self.width && height == self.height {
432 return;
433 }
434
435 let new_total = (width as usize) * (height as usize);
436 let mut new_cells = vec![Cell::BLANK; new_total];
437
438 let copy_w = width.min(self.width) as usize;
439 let copy_h = height.min(self.height) as usize;
440 let old_w = self.width as usize;
441 let new_w = width as usize;
442 for y in 0..copy_h {
443 let src = y * old_w;
444 let dst = y * new_w;
445 new_cells[dst..dst + copy_w].clone_from_slice(&self.cells[src..src + copy_w]);
446 }
447
448 self.cells = new_cells;
449 self.width = width;
450 self.height = height;
451 }
452}
453
454impl Bounded for Buffer {
455 fn bounds(&self) -> Rect {
456 Rect::new(0, 0, self.width, self.height)
457 }
458}
459
460impl Surface for Buffer {
461 fn cell(&self, pos: Position) -> Option<&Cell> {
462 if pos.y >= self.height || pos.x >= self.width {
463 return None;
464 }
465 let w = self.width as usize;
466 Some(&self.cells[(pos.y as usize) * w + (pos.x as usize)])
467 }
468}
469
470impl SurfaceMut for Buffer {
471 fn set_cell(&mut self, pos: Position, cell: &Cell) {
472 self.set(pos, cell);
473 }
474
475 fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell> {
476 Buffer::cell_mut(self, pos)
477 }
478
479 /// Fill the clipped intersection of `rect` with `cell`. For
480 /// width-1 fills this collapses the trait default's per-cell
481 /// `set_cell` loop into one `slice::fill` per row, with explicit
482 /// wide-cell cleanup around the fill so any cell overlapping the
483 /// region leaves no orphan primary or continuation behind. A cell
484 /// wide enough can hold the whole region, in which case the cleanup
485 /// reaches past both edges of the fill. Wide fills (`cell.width() > 1`)
486 /// stay on the stepped `set_cell` path so primary/continuation
487 /// pairing and the trailing-partial-slot blank are placed by the
488 /// same wide-cell handling that `set` already implements. Partial slots
489 /// retain the fill style. Cleanup outside the rectangle retains the
490 /// old primary's style.
491 fn fill_rect(&mut self, rect: Rect, cell: &Cell) {
492 let clipped = self.bounds().intersection(rect);
493 if clipped.is_empty() {
494 return;
495 }
496
497 let step = (cell.width() as u16).max(1);
498 if step > 1 {
499 let blank = Cell::BLANK.style(cell.style.clone());
500 // Stepped wide-cell fill: identical to the trait default.
501 // Inlined here so the SurfaceMut::fill_rect dispatch goes
502 // through this impl in both arms.
503 for y in clipped.top()..clipped.bottom() {
504 let mut x = clipped.left();
505 while !overruns(x, step, clipped.right()) {
506 self.set(Position::new(x, y), cell);
507 x += step;
508 }
509 while x < clipped.right() {
510 self.set(Position::new(x, y), &blank);
511 x += 1;
512 }
513 }
514 return;
515 }
516
517 let lo = clipped.left() as usize;
518 let hi = clipped.right() as usize;
519 let row_width = self.width as usize;
520
521 for y in clipped.top()..clipped.bottom() {
522 let row_start = (y as usize) * row_width;
523 let line = &mut self.cells[row_start..row_start + row_width];
524
525 // Left-edge straddle: a wide cell whose primary sits
526 // before `lo` and whose continuation falls at `lo`. Walk
527 // back to the primary and blank it (and any siblings
528 // outside the fill region) before the bulk fill so the
529 // primary isn't left dangling once its continuations get
530 // overwritten.
531 if lo > 0 && line[lo].is_continuation() {
532 let mut p = lo;
533 while p > 0 && line[p].is_continuation() {
534 p -= 1;
535 }
536 if !line[p].is_continuation() {
537 let pw = line[p].width() as usize;
538 let blank = Cell::BLANK.style(line[p].style.clone());
539 let end = (p + pw).min(lo);
540 line[p..end].fill(blank.clone());
541 // The same cell can also reach past `hi`, which is what
542 // happens when the fill lands wholly inside it. Those
543 // columns just lost the primary that owned them, so
544 // they cannot stay continuations. The right-edge pass
545 // below will not reach them: it walks back only as far
546 // as `lo`, and this primary sits before that.
547 if p + pw > hi {
548 let tail = (p + pw).min(row_width);
549 line[hi..tail].fill(blank);
550 }
551 }
552 }
553
554 // Right-edge straddle: a wide cell whose primary sits at
555 // some `p` in `[lo, hi)` and whose continuations extend
556 // past `hi - 1`. The primary itself will be overwritten by
557 // the bulk fill, but the orphan continuations at
558 // `[hi, p + pw)` need explicit blanking.
559 if hi < row_width && line[hi].is_continuation() {
560 let mut p = hi - 1;
561 while p > lo && line[p].is_continuation() {
562 p -= 1;
563 }
564 if !line[p].is_continuation() && p + (line[p].width() as usize) > hi {
565 let pw = line[p].width() as usize;
566 let end = (p + pw).min(row_width);
567 let blank = Cell::BLANK.style(line[p].style.clone());
568 line[hi..end].fill(blank);
569 }
570 }
571
572 line[lo..hi].fill(cell.clone());
573 }
574 }
575
576 fn insert_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
577 Buffer::insert_lines(self, y, n, bounds_bottom, fill);
578 }
579
580 fn delete_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
581 Buffer::delete_lines(self, y, n, bounds_bottom, fill);
582 }
583
584 fn insert_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
585 Buffer::insert_cells(self, pos, n, bounds_right, fill);
586 }
587
588 fn delete_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
589 Buffer::delete_cells(self, pos, n, bounds_right, fill);
590 }
591}