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::narrow("x"));
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};
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/// Wide cells are stored as a primary [`Cell`] followed
96/// by one continuation cell. 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 /// This is the implementation behind [`SurfaceMut::set_cell`] for
306 /// `Buffer`. Use it instead of [`Buffer::cell_mut`] whenever the write
307 /// might affect the width or role of neighboring cells.
308 pub fn set(&mut self, pos: impl Into<Position>, cell: &Cell) {
309 let pos = pos.into();
310 let y = pos.y as usize;
311 let x = pos.x as usize;
312 let width = self.width as usize;
313
314 if pos.y >= self.height || x >= width {
315 return;
316 }
317
318 let line = &mut self.cells[y * width..(y + 1) * width];
319
320 // If we're overwriting a wide cell's continuation, blank the primary
321 // cell that owns it — but only when the *incoming* cell is not itself
322 // a continuation. A continuation-to-continuation write is a no-op on
323 // the cell's identity (e.g. it happens when a render buffer mirrors
324 // a model buffer cell-by-cell after the primary wide cell has just
325 // been written one column to the left) and must not destroy the
326 // wide cell we are in the middle of mirroring.
327 if x > 0 && line[x].is_continuation() && !cell.is_continuation() {
328 // Walk backward to find the primary cell
329 let mut pc = x - 1;
330 while pc > 0 && line[pc].is_continuation() {
331 pc -= 1;
332 }
333 // Verify we found a wide primary; only Wide cells own
334 // the continuation slot to their right.
335 if line[pc].is_wide() {
336 let pw = line[pc].width() as usize;
337 let end = (pc + pw).min(width);
338 // Preserve the broken wide cell's bg/attributes on the
339 // blanks so clearing it doesn't punch a hole in a colored
340 // background.
341 let blank = Cell::BLANK.style(line[pc].style.clone());
342 line[pc..end].fill(blank);
343 } else if line[pc].is_continuation() {
344 // Buffer is corrupted — just blank the single cell
345 line[x] = Cell::BLANK;
346 }
347 }
348
349 // If we're overwriting the primary cell of a wide char, blank continuations
350 if line[x].is_wide() {
351 let w = line[x].width() as usize;
352 let end = (x + w).min(width);
353 let blank = Cell::BLANK.style(line[x].style.clone());
354 line[x + 1..end].fill(blank);
355 }
356
357 let cell_width = cell.width() as usize;
358
359 // If the new cell is wide, blank cells it will cover
360 if cell.is_wide() {
361 for i in x + 1..x + cell_width {
362 if i < width {
363 // If we'd overwrite a wide cell's primary, blank its continuations
364 if line[i].is_wide() {
365 let w = line[i].width() as usize;
366 let end = (i + w).min(width);
367 let blank = Cell::BLANK.style(line[i].style.clone());
368 line[i + 1..end].fill(blank);
369 }
370 // Continuations inherit the wide primary's style so the
371 // cell's bg/attributes are coherent across both columns.
372 line[i] = Cell::continuation().style(cell.style.clone());
373 }
374 }
375
376 // Truncate at end of line: if wide cell doesn't fit, replace with
377 // a blank that keeps the wide cell's bg/attributes.
378 if x + cell_width > width {
379 line[x] = Cell::BLANK.style(cell.style.clone());
380 return;
381 }
382 }
383
384 line[x] = cell.clone();
385 }
386
387 /// Resize the buffer, preserving the top-left intersection.
388 ///
389 /// # Parameters
390 ///
391 /// - `width`: new row width in terminal cell columns.
392 /// - `height`: new height in terminal cell rows.
393 ///
394 /// # Behavior
395 ///
396 /// Cells inside the intersection of the old and new bounds keep their
397 /// row and column. Newly exposed cells are filled with [`Cell::BLANK`],
398 /// and cells outside the new bounds are discarded.
399 ///
400 /// # Panics
401 ///
402 /// Panics if the resized backing store cannot be allocated.
403 ///
404 /// # Usage notes
405 ///
406 /// Resizing copies cells structurally and does not reflow wide cells. If
407 /// the new right edge cuts through a wide grapheme, the copied
408 /// continuation or primary remains as stored until later writes or draws
409 /// normalize the affected edge.
410 pub fn resize(&mut self, width: u16, height: u16) {
411 if width == self.width && height == self.height {
412 return;
413 }
414
415 let new_total = (width as usize) * (height as usize);
416 let mut new_cells = vec![Cell::BLANK; new_total];
417
418 let copy_w = width.min(self.width) as usize;
419 let copy_h = height.min(self.height) as usize;
420 let old_w = self.width as usize;
421 let new_w = width as usize;
422 for y in 0..copy_h {
423 let src = y * old_w;
424 let dst = y * new_w;
425 new_cells[dst..dst + copy_w].clone_from_slice(&self.cells[src..src + copy_w]);
426 }
427
428 self.cells = new_cells;
429 self.width = width;
430 self.height = height;
431 }
432}
433
434impl Bounded for Buffer {
435 fn bounds(&self) -> Rect {
436 Rect::new(0, 0, self.width, self.height)
437 }
438}
439
440impl Surface for Buffer {
441 fn cell(&self, pos: Position) -> Option<&Cell> {
442 if pos.y >= self.height || pos.x >= self.width {
443 return None;
444 }
445 let w = self.width as usize;
446 Some(&self.cells[(pos.y as usize) * w + (pos.x as usize)])
447 }
448}
449
450impl SurfaceMut for Buffer {
451 fn set_cell(&mut self, pos: Position, cell: &Cell) {
452 self.set(pos, cell);
453 }
454
455 fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell> {
456 Buffer::cell_mut(self, pos)
457 }
458
459 /// Fill the clipped intersection of `rect` with `cell`. For
460 /// width-1 fills this collapses the trait default's per-cell
461 /// `set_cell` loop into one `slice::fill` per row, with explicit
462 /// wide-cell edge-straddle cleanup at the left and right boundaries
463 /// so any wide cell crossing the fill region leaves no orphan
464 /// primary or continuation behind. Wide fills (`cell.width() > 1`)
465 /// stay on the stepped `set_cell` path so primary/continuation
466 /// pairing and the trailing-partial-slot blank are placed by the
467 /// same wide-cell handling that `set` already implements.
468 fn fill_rect(&mut self, rect: Rect, cell: &Cell) {
469 let clipped = self.bounds().intersection(rect);
470 if clipped.is_empty() {
471 return;
472 }
473
474 let step = (cell.width() as u16).max(1);
475 if step > 1 {
476 // Stepped wide-cell fill: identical to the trait default.
477 // Inlined here so the SurfaceMut::fill_rect dispatch goes
478 // through this impl in both arms.
479 for y in clipped.top()..clipped.bottom() {
480 let mut x = clipped.left();
481 while x + step <= clipped.right() {
482 self.set(Position::new(x, y), cell);
483 x += step;
484 }
485 while x < clipped.right() {
486 self.set(Position::new(x, y), &Cell::BLANK);
487 x += 1;
488 }
489 }
490 return;
491 }
492
493 let lo = clipped.left() as usize;
494 let hi = clipped.right() as usize;
495 let row_width = self.width as usize;
496
497 for y in clipped.top()..clipped.bottom() {
498 let row_start = (y as usize) * row_width;
499 let line = &mut self.cells[row_start..row_start + row_width];
500
501 // Left-edge straddle: a wide cell whose primary sits
502 // before `lo` and whose continuation falls at `lo`. Walk
503 // back to the primary and blank it (and any siblings
504 // outside the fill region) before the bulk fill so the
505 // primary isn't left dangling once its continuations get
506 // overwritten.
507 if lo > 0 && line[lo].is_continuation() {
508 let mut p = lo;
509 while p > 0 && line[p].is_continuation() {
510 p -= 1;
511 }
512 if !line[p].is_continuation() {
513 let pw = line[p].width() as usize;
514 let end = (p + pw).min(lo);
515 for slot in &mut line[p..end] {
516 *slot = Cell::BLANK;
517 }
518 }
519 }
520
521 // Right-edge straddle: a wide cell whose primary sits at
522 // some `p` in `[lo, hi)` and whose continuations extend
523 // past `hi - 1`. The primary itself will be overwritten by
524 // the bulk fill, but the orphan continuations at
525 // `[hi, p + pw)` need explicit blanking.
526 if hi < row_width && line[hi].is_continuation() {
527 let mut p = hi - 1;
528 while p > lo && line[p].is_continuation() {
529 p -= 1;
530 }
531 if !line[p].is_continuation() && p + (line[p].width() as usize) > hi {
532 let pw = line[p].width() as usize;
533 let end = (p + pw).min(row_width);
534 for slot in &mut line[hi..end] {
535 *slot = Cell::BLANK;
536 }
537 }
538 }
539
540 line[lo..hi].fill(cell.clone());
541 }
542 }
543
544 fn insert_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
545 Buffer::insert_lines(self, y, n, bounds_bottom, fill);
546 }
547
548 fn delete_lines(&mut self, y: u16, n: u16, bounds_bottom: u16, fill: &Cell) {
549 Buffer::delete_lines(self, y, n, bounds_bottom, fill);
550 }
551
552 fn insert_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
553 Buffer::insert_cells(self, pos, n, bounds_right, fill);
554 }
555
556 fn delete_cells(&mut self, pos: Position, n: u16, bounds_right: u16, fill: &Cell) {
557 Buffer::delete_cells(self, pos, n, bounds_right, fill);
558 }
559}