render method
void
render()
Composites all active layers into a single flattened terminal output and writes it to the terminal.
Implementation
void render() {
_isRendering = true;
try {
_renderScheduled = false;
Tracer.record(_traceRenderId, Phase.begin, TraceCategory.compositor);
try {
_cleanOrphanedLayers();
for (final layer in layers) {
final r = layer.renderer;
if (r is ListenableSceneRenderer && r.isDirty) {
r.render();
}
}
final size = terminal.backend.size;
var width = size.x;
var height = size.y;
if (width <= 0 || height <= 0) {
width = 80;
height = 24;
}
final renderer = _renderer ??= Renderer(
width,
height,
mode: renderingMode,
);
final layeredBuffers = <LayeredBuffer>[];
for (final layer in layers) {
if (layer.sizing == LayerSizing.fullscreen) {
if (layer.width != width || layer.height != height) {
layer.width = width;
layer.height = height;
_resizeRenderer(layer.renderer, width, height);
}
} else {
final buf = layer.renderer.currentBuffer;
if (buf == null ||
buf.width != layer.width ||
buf.height != layer.height) {
if (layer.width != null && layer.height != null) {
_resizeRenderer(layer.renderer, layer.width!, layer.height!);
}
}
}
final buffer = layer.renderer.currentBuffer;
if (buffer == null) continue;
layeredBuffers.add(
LayeredBuffer(
buffer: buffer,
x: layer.x,
y: layer.y,
zIndex: layer.zIndex,
),
);
if (debugPaintLayerBordersEnabled) {
final w = buffer.width;
final h = buffer.height;
final style = const Style(
foreground: Colors.yellow,
modifiers: Modifier.bold,
);
// Top border
final top = Buffer(w + 2, 1);
top.writeString(0, 0, '┌${'─' * w}┐', style);
layeredBuffers.add(
LayeredBuffer(
buffer: top,
x: layer.x - 1,
y: layer.y - 1,
zIndex: layer.zIndex,
),
);
// Bottom border
final bottom = Buffer(w + 2, 1);
bottom.writeString(0, 0, '└${'─' * w}┘', style);
layeredBuffers.add(
LayeredBuffer(
buffer: bottom,
x: layer.x - 1,
y: layer.y + h,
zIndex: layer.zIndex,
),
);
// Left border (height is h, not h+2, because corners are in top/bottom)
final left = Buffer(1, h);
for (var y = 0; y < h; y++) {
left.writeString(0, y, '│', style);
}
layeredBuffers.add(
LayeredBuffer(
buffer: left,
x: layer.x - 1,
y: layer.y,
zIndex: layer.zIndex,
),
);
// Right border
final right = Buffer(1, h);
for (var y = 0; y < h; y++) {
right.writeString(0, y, '│', style);
}
layeredBuffers.add(
LayeredBuffer(
buffer: right,
x: layer.x + w,
y: layer.y,
zIndex: layer.zIndex,
),
);
}
}
final target = _targetBuffer ??= Buffer(width, height);
if (target.width != width || target.height != height) {
target.resize(width, height);
}
target.clear();
// The Compositor natively handles occlusion culling and off-screen
// effect layer resolution using recursive saveLayer/backdrop filter patterns.
_compositor.composite(target: target, layers: layeredBuffers);
final globalMouseX = _globalMouseX;
final globalMouseY = _globalMouseY;
if (debugMouseCursorEnabled &&
globalMouseX != null &&
globalMouseY != null) {
final cursorStyle = Style(
foreground: _isGlobalMouseDown
? const Color(255, 0, 0)
: const Color(0, 255, 255),
modifiers: Modifier.bold,
);
target.writeString(globalMouseX, globalMouseY, '⦿', cursorStyle);
}
_renderDebugOverlays(target);
final sb = StringBuffer();
renderer.render(target, sb);
final backend = terminal.backend;
if (backend is BufferedTerminalBackend) {
backend.buffer = target;
}
backend.write(sb.toString());
onFrameRedrawn?.call(target);
// Hardware state sync based on focused layer's requests.
final focused = focusedLayer;
final req = focused?.renderer;
final showsCursor = req?.showsCursor ?? false;
final wantsMouseTracking =
enableMouseTracking ||
(req?.wantsMouseTracking ?? false) ||
layers.any((layer) => layer.draggable) ||
debugMouseCursorEnabled;
var effectiveShowsCursor = showsCursor;
int? absX;
int? absY;
if (showsCursor) {
final pos = req?.requestedCursorPosition;
if (pos != null) {
absX = (focused?.x ?? 0) + pos.x;
absY = (focused?.y ?? 0) + pos.y;
if (absX < 0 || absX >= width || absY < 0 || absY >= height) {
effectiveShowsCursor = false;
}
} else {
effectiveShowsCursor = false;
}
}
if (effectiveShowsCursor != _lastShowsCursor) {
if (effectiveShowsCursor) {
terminal.showCursor();
} else {
terminal.hideCursor();
}
_lastShowsCursor = effectiveShowsCursor;
}
if (wantsMouseTracking != _lastWantsMouseTracking) {
if (wantsMouseTracking) {
terminal.enableMouseTracking();
} else {
terminal.disableMouseTracking();
}
_lastWantsMouseTracking = wantsMouseTracking;
}
if (effectiveShowsCursor && absX != null && absY != null) {
terminal.goto(x: absX + 1, y: absY + 1);
}
} finally {
Tracer.record(_traceRenderId, Phase.end, TraceCategory.compositor);
}
} finally {
_isRendering = false;
}
}