Phosphor::Renderer
Computes frame-to-frame diffs and writes only the changed cells to the terminal.
Maintains @last_buffer across calls. On the first draw, and on any draw
right after #invalidate (screen transitions, resize), there's no
previous buffer to diff against — every cell is written unconditionally
via Buffer#full_redraw instead, so cells the new frame leaves at their
own default don't leave stale content from whatever was on the real
terminal before.
Constants
How many recent #draw calls #fps averages over. 30 is enough to
smooth out one-off frame hitches without lagging far behind the
actual current rate.
Constructors
Instance methods
The theme's background color as an explicit Style, recomputed on
every #draw call from theme's :background style. Exposed so
painting that happens outside the normal #draw → Screen#render
path — View#transition_widget, which runs between real screen
renders and has no Theme of its own — can fill a region with the
same background color a real draw would use.
fg is always Color::Default, never the background color itself —
Buffer#set_string merges a written Style over whatever the target
cell already carries, so a widget that paints text with no explicit
fg of its own would inherit this fill's fg underneath it. Setting
fg to match bg would make that inherited text invisible against
its own background; Color::Default keeps it readable while bg
still overrides the terminal's own default background. Same tradeoff
ConfirmMock::Props#bg_style already makes.
Renders one frame: allocates a fresh Buffer, asks screen to fill it,
diffs against the previous frame, and writes only changed cells to terminal.
Cell::CONTINUATION slots are skipped in the write loop — the wide character's
primary cluster already occupies two terminal columns when written.
dark_background and color_profile are forwarded to the new Buffer
so set_string can resolve AdaptiveColor and downsample colors a
widget sets — default to true/TrueColor, matching App's own
stubbed defaults.
theme's :background style fills every cell in frame before
screen renders into it, so an untouched cell (an empty area below a
short list, a status bar that doesn't span the full width) carries an
explicit background rather than whatever a terminal emulator defaults
to. Defaults to Theme.new, whose :background is Color::Default
— i.e. no explicit color, matching the pre-existing behavior for
callers that don't pass a theme. Also cached on #bg_style (with
both fg and bg set explicitly) for callers painting outside this
method entirely — see #bg_style's own doc comment.
debug_overlay, if given, is called with the same buf and frame
right after screen renders into it — so its cells paint over the
screen's own content and are included in the diff written to
terminal, the same as any other content. Used by App for the
App.run(debug: true) overlay (Phase 20); nil in every other case.
#stats is recomputed at the end of every call, unconditionally —
this happens regardless of App.run(stats: true), which only gates
whether the debug overlay displays the numbers. Collection has to
stay unconditional or the very act of turning the overlay on would
change the timings it's trying to show.
min_interval, if given and nonzero, caps draw frequency: a call
arriving less than min_interval after the previous completed draw
returns immediately without rendering anything (no Buffer
allocation, no diff, no write — and #fps/#stats are left exactly
as the last real draw left them, since a skipped call isn't a frame).
App.run(max_fps: ...) computes this from 1.second / max_fps and
passes it on every call; defaults to Time::Span.zero (never skips)
for direct callers — App#print, App#inline_resize, and specs —
that don't go through the main loop's cap.
Generic (forall S, M) rather than declared on a Renderer(S, M)
class — Renderer itself carries no app-state-shaped data, only
#draw needs to know S/M, and a forall method lets one
Renderer instance draw screens for any S/M rather than binding
it at construction time.
Writes buf to terminal as a full redraw — the flush primitive
View#transition_widget uses to paint each intermediate transition
step live, rather than only ever showing the widget's final state.
Invalidates before writing (not after) so this call's own write is
itself a full redraw, not a diff against whatever @last_buffer held
coming in (the last real screen render, or the previous transition
step) — successive transition steps aren't meaningfully diffable
against each other the way ordinary re-renders are. This mirrors
App#play_transition's own renderer.invalidate placement exactly;
see that method's doc comment for the same reasoning applied to
whole-screen transitions.
frame isn't used yet — accepted for a future sub-region write. For now writing all of buf is correct: the caller already composited this transition step into the right region of buf before calling this.
Frames rendered per second, as a rolling average over the last
FPS_SAMPLE_SIZE #draw calls. 0.0 until at least two draws have
happened — one timestamp alone has no interval to measure from.
Discards the cached previous frame, so the next #draw call treats
every cell as changed and repaints the whole frame from scratch.
Needed after something moves the frame's absolute position outside of
#draw itself — e.g. App#print scrolling an Inline block down a
row. #draw only diffs by cell content, so with unchanged content it
can't tell the target position moved and would write nothing.
The most recent #draw call's cost — cells changed, bytes written,
and rolling average draw time. Collection is unconditional (see the
doc comment above #draw); App.run(stats: true) only controls
whether the debug overlay displays this, not whether it's tracked.
Diffs buf against @last_buffer (or does a full redraw if there is
none) and writes only the changed cells to terminal — the second
half of #draw, split out so a already-painted Buffer that didn't
come from a Screen#render call can go straight to the terminal the
same way. Transition playback in App#push_screen is the other
caller: each transition frame is a Buffer a Transition's own Proc
paints directly, with no Screen#render step to call.
draw_start backdates #fps/#stats#avg_draw_time_ms tracking to
when the caller actually started this frame (#draw passes its own
draw_start, from before screen.render ran); defaults to #now for
callers with no earlier timestamp of their own.