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;
  }
}