Skip to main content

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}