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