class

TermBuf::Painter

Inherits Reference < Object

Stability: internal

Works out the operations that bring the terminal from what it is showing to what the application has drawn.

Two passes. The first looks for whole bands of rows that merely moved, and asks the terminal to scroll them rather than sending them again. The second walks the rows that are still different and emits the changed runs.

The painter mutates the sink's front grid as it extracts scrolls, so that the second pass diffs against the post-scroll state. That means the bytes it returns have to actually reach the terminal; a caller that throws them away must call Sink#invalidate before painting again.

It holds nothing about which screen it is diffing: the front grid, the damage and the scroll hints all come from the Sink it is handed, so one painter is one output rather than one buffer.

Constants

ERASE_THRESHOLD = 4

A trailing run of blanks longer than this is cheaper to erase than to write spaces over.

MOVE_COST = 4

What moving the cursor a short distance within a row costs. A gap of unchanged cells cheaper than this to reprint is not worth skipping.

ROW_COST = 12

Redrawing a row costs at least this much, so a scroll starts paying for itself once it saves this many rows.

SCROLL_OVERHEAD = 24

Roughly what a scroll costs in bytes: margins, the scroll itself, and releasing the margins again.

STYLE_COST = 6

What changing style costs, near enough.

Constructors

new(capabilities : Capabilities)
Source

Instance methods

capabilities

What the terminal can do, which decides whether a scroll or an erase is available at all.

Source
capabilities=(capabilities : Capabilities) : Capabilities

Changes what the painter may use. The next paint should be forced, since what is on the terminal was drawn under the old mask.

Source
clear_overhang=(clear_overhang : Bool)

Whether to write the cell after a glyph that may have painted outside its own columns, even when nothing in that cell changed.

On by default, and there is nothing yet that would turn it off: both terminals measured draw over a neighbouring cell without repainting it first, so ink left there stays there. Nothing predicts which clusters overhang — that is font coverage, and it moved between two fonts covering different scripts — so the cheap conservative rule wins: after anything that is not plain ASCII, write the next cell again. It costs one cell per such glyph per frame and nothing at all on ASCII text.

An application that knows its terminal repaints what it draws over, or that would rather have the bytes, can turn it off.

Source
clear_overhang?

Whether to write the cell after a glyph that may have painted outside its own columns, even when nothing in that cell changed.

On by default, and there is nothing yet that would turn it off: both terminals measured draw over a neighbouring cell without repainting it first, so ink left there stays there. Nothing predicts which clusters overhang — that is font coverage, and it moved between two fonts covering different scripts — so the cheap conservative rule wins: after anything that is not plain ASCII, write the next cell again. It costs one cell per such glyph per frame and nothing at all on ASCII text.

An application that knows its terminal repaints what it draws over, or that would rather have the bytes, can turn it off.

Source
composed_drift

A cluster the terminal will put in the wrong place, noticed on its way out, or nil while none has been. Read and cleared by the driver, which is what turns it into a warning.

Only watched for under Quirk::PerCodePointColumns, and only until the first one: after that the application has been told, and the cost of looking goes back to a boolean nothing reads.

Source
hardware_cursor

Where to leave the terminal's own cursor when the frame ends, or nil to leave it hidden.

A frame that changes no cells is still worth sending when this has moved: the cursor is what tells someone where they are typing.

Source
hardware_cursor=(hardware_cursor : Tuple(Int32, Int32) | Nil)

Where to leave the terminal's own cursor when the frame ends, or nil to leave it hidden.

A frame that changes no cells is still worth sending when this has moved: the cursor is what tells someone where they are typing.

Source
paint(buffer : Buffer, sink : Sink) : Array(Op)

The operations that bring sink's terminal up to date with buffer. Empty when there is nothing to do. The caller writes them out and then calls Sink#commit.

Source
reset_state

Forgets what the terminal was last told about its own cursor, so the next frame says it again. For a forced repaint, where nothing on the screen can be taken on trust.

Source
take_composed_drift

Forgets the cluster that was found, so the next one is reported too.

Source
watch_composed_drift=(watch_composed_drift : Bool)

Whether to look. False on every terminal that measures clusters the way this does, and false again once one has been found.

Source
watch_composed_drift?

Whether to look. False on every terminal that measures clusters the way this does, and false again once one has been found.

Source