.. Copyright (c) 2026 Tobias Erbsland - Erbsland DEV. https://erbsland.dev SPDX-License-Identifier: Apache-2.0 ****** Buffer ****** The buffer classes represent rendered terminal content in memory before it is written to the screen. :cpp:any:`ReadableBuffer ` provides a read-only inspection API, :cpp:any:`WritableBuffer ` extends this with mutation and drawing operations, and :cpp:any:`Buffer ` is the concrete 2D storage type used in most applications. For more specialized use cases, :cpp:any:`RemappedBuffer ` adds efficient row- and column-based reordering. This is ideal for editors, scrollback views, or any workload that frequently inserts, deletes, or moves whole lines. Building on top of that, :cpp:any:`CursorBuffer ` provides a VT-style cursor-writing interface via :cpp:any:`CursorWriter `. Use these types whenever you want to build frames off-screen, compare frames, copy content between buffers, or derive masks from rendered characters. Usage ===== Reading and Writing Through Buffer Interfaces --------------------------------------------- :cpp:any:`ReadableBuffer ` and :cpp:any:`WritableBuffer ` allow helper functions to operate on terminal content without depending on a specific implementation. .. code-block:: cpp auto renderStatusPanel(WritableBuffer &target, Rectangle panel) -> void { target.fill(panel, Char{" ", Color{fg::Inherited, bg::Blue}}); target.drawText( "Status", panel.insetBy(Margins{1}), Alignment::TopLeft, Color{fg::BrightWhite, bg::Blue}); } auto screen = Buffer{Size{80, 24}}; renderStatusPanel(screen, Rectangle{2, 2, 24, 8}); Use :cpp:any:`ReadableBuffer ` when your function only needs to inspect content, count differences, or derive masks. Use :cpp:any:`WritableBuffer ` when your function should modify the target buffer without caring whether it operates on a standalone :cpp:any:`Buffer ` or another writable implementation. Cloning, Copying, and Resizing ------------------------------ :cpp:any:`Buffer ` supports the typical frame-management tasks required by interactive terminal applications: creating new frames, cloning the current state, and resizing buffers when the terminal size changes. .. code-block:: cpp auto current = Buffer{Size{80, 24}}; current.fill(Char{" ", Color{fg::Inherited, bg::Black}}); const auto previous = current.clone(); current.resize(Size{100, 30}, true, Char::space()); current.setFrom(*previous, Char{" ", Color{fg::Inherited, bg::Black}}); ``clone()`` returns a writable copy through the abstract interface. This makes it easy to store previous frames for diffing, animation steps, or rollback logic. When resizing a concrete :cpp:any:`Buffer `, use the reorder overload if you want to preserve existing content while expanding or cropping the canvas. Working with Remapped Buffers ----------------------------- :cpp:any:`RemappedBuffer ` is designed for workloads where the content remains logically grid-based, but rows or columns are frequently reshuffled. Instead of rewriting every affected cell, the buffer maintains remapping tables and only updates rows or columns that become newly visible. This keeps operations like scrolling or line insertion efficient, even for large buffers. .. code-block:: cpp auto history = RemappedBuffer{Size{80, 2'000}, Orientation::Vertical}; history.fill(Char::space()); history.eraseRows(0, Char::space(), 1); // Scroll everything up by one row. history.set(Position{0, 1'999}, String{"new log line"}); history.resize(Size{100, 2'000}, true, Char::space()); Use the plain :cpp:any:`RemappedBuffer::resize() ` overload when you want maximum performance and plan to redraw the content anyway. Use the reorder overload when the visible order must remain stable while expanding or cropping the buffer. Streaming Scrollback with CursorBuffer -------------------------------------- :cpp:any:`CursorBuffer ` is the right choice when text is appended over time, as if it were written directly to a terminal. It tracks a cursor position, maintains an active color, and supports streaming writes via :cpp:any:`CursorWriter `. When the cursor reaches the bottom edge, it can wrap, scroll, or grow vertically depending on the configured overflow mode. Newly created cells are initialized using ``fillChar()``, allowing you to keep a consistent background color or placeholder glyph as the buffer grows. .. code-block:: cpp auto logHistory = CursorBuffer{ Size{120, 10}, CursorBuffer::OverflowMode::ExpandThenShift, Size{120, 500}, Char{" ", Color{fg::Default, bg::Black}}}; logHistory.setColor(Color{fg::BrightBlue, bg::Black}); logHistory.printParagraph("2026-03-26 09:02:23 INF Request completed in 43 ms"); logHistory.setColor(Color{fg::BrightYellow, bg::Black}); logHistory.printParagraph("2026-03-26 09:03:04 WRN Cache refresh is still pending"); const auto visibleTop = std::max(0, logHistory.size().height() - 20); auto view = BufferConstRefView{logHistory, Rectangle{0, visibleTop, 120, 20}}; terminal.updateScreen(view); This pattern works especially well for log viewers, REPL-style tools, dashboards, or any application that needs a growing history buffer with a live viewport onto the most recent content. If your fill strategy changes later, update it with :cpp:any:`CursorBuffer::setFillChar `. For details about the streaming API itself—such as ``print()``, ``printLine()``, and cursor movement—see :doc:`Cursor Output `. .. important:: For an efficient render loop, keep a persistent instance of :cpp:any:`Buffer ` and simply resize it when the terminal size changes. Reusing the same buffer avoids unnecessary memory allocations and helps keep rendering predictable and fast. A typical render loop might look like this: .. code-block:: cpp struct MyApp { void renderLoop() { for (;;) { _terminal.testScreenSize(); _buffer.resize(_terminal.size()); // Render the current frame into the buffer. _terminal.updateScreen(_buffer); // Handle key presses or other input. } } Terminal _terminal; Buffer _buffer; }; Building Buffers from Text Lines -------------------------------- For status panels, generated reports, or static UI elements, :cpp:any:`Buffer ` can be constructed directly from line-oriented text. .. code-block:: cpp const auto help = Buffer::fromLinesInString(String{ "Q Quit\n" "R Refresh\n" "H Toggle help"}); auto screen = Buffer{Size{40, 12}}; screen.setFrom(help, Char::space()); This is often the fastest way to turn preformatted terminal text into a buffer that can later be positioned within a larger layout. Comparing Frames and Deriving Masks ----------------------------------- :cpp:any:`ReadableBuffer ` also provides analysis helpers that are useful for tests, animation pipelines, and bitmap-based effects. .. code-block:: cpp const auto changedCells = previous->countDifferencesTo(current); const auto frameMask = current.toMask({U'|', U'-', U'+', U'┌', U'┐', U'└', U'┘'}); if (changedCells > 0 && frameMask.size().contains(Position{0, 0})) { // React to the changed frame content. } Use ``toMask()`` when you want to reason about the *structure* of rendered content (for example, line art or borders) instead of raw character or color data. Interface ========= .. doxygenclass:: erbsland::cterm::ReadableBuffer :members: .. doxygenclass:: erbsland::cterm::WritableBuffer :members: .. doxygenclass:: erbsland::cterm::Buffer :members: .. doxygenclass:: erbsland::cterm::RemappedBuffer :members: .. doxygenclass:: erbsland::cterm::CursorBuffer :members: