diff --git a/src/main/java/com/gregor/jprototerm/GhosttyTerminalRenderer.java b/src/main/java/com/gregor/jprototerm/GhosttyTerminalRenderer.java index e40993d..a9bf76f 100644 --- a/src/main/java/com/gregor/jprototerm/GhosttyTerminalRenderer.java +++ b/src/main/java/com/gregor/jprototerm/GhosttyTerminalRenderer.java @@ -118,106 +118,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer { return kittyImageNodes; } - // Incremental render: repaint only the rows ghostty flagged dirty, then restore the - // cursor and border. The local band tracks the repainted span only so the border redraw - // can be limited to it. - private void drawDirtyRows( - GraphicsContext gc, - RenderStateSnapshot snapshot, - double px, - double py, - double pw, - double ph, - boolean active - ) { - double cellWidth = metrics.cellWidth(); - double lineHeight = metrics.lineHeight(); - gc.setFontSmoothingType(FontSmoothingType.LCD); - gc.setFont(metrics.font()); - double left = px + TerminalMetrics.PADDING; - double top = py + TerminalMetrics.PADDING; - double baseline = top + metrics.baselineOffset(); - - double contentBottom = top + snapshot.rows() * lineHeight; - int lastRow = snapshot.rows() - 1; - List rows = snapshot.renderRows(); - boolean allRowsDirty = allRowsDirty(snapshot, rows); - if (allRowsDirty) { - gc.setFill(PANE_BACKGROUND); - gc.fillRect(px, py, pw, ph); - } - - boolean cursorRowDirty = false; - double bandMin = Double.POSITIVE_INFINITY; - double bandMax = Double.NEGATIVE_INFINITY; - for (RenderRow row : rows) { - if (!row.dirty()) { - continue; - } - // Snap the row band to integer pixels and paint opaque: a fractional-height fill - // would leave sub-pixel seams between rows. - double y0 = Math.floor(top + (row.row() * lineHeight)); - double y1 = Math.ceil(top + ((row.row() + 1) * lineHeight)); - if (!allRowsDirty) { - gc.setFill(PANE_BACKGROUND); - gc.fillRect(px, y0, pw, y1 - y0); - } - paintSidePadding(gc, row, px, pw, left, cellWidth, y0, y1 - y0); - drawRow(gc, row, left, top, baseline, cellWidth, lineHeight); - bandMin = Math.min(bandMin, y0); - bandMax = Math.max(bandMax, y1); - // Edge rows also own the top/bottom padding strip; repaint it and extend the - // band so panes stacked above get restored over it too. - if (row.row() == 0) { - gc.setFill(rowEdgeBackground(row, true)); - gc.fillRect(px, py, pw, top - py); - bandMin = Math.min(bandMin, py); - } - if (row.row() == lastRow) { - gc.setFill(rowEdgeBackground(row, true)); - gc.fillRect(px, contentBottom, pw, py + ph - contentBottom); - bandMax = Math.max(bandMax, py + ph); - } - if (snapshot.cursorViewportHasValue() && row.row() == snapshot.cursorViewportY()) { - cursorRowDirty = true; - } - } - if (bandMin > bandMax) { - return; - } - - // The cursor overlays its cell; redraw it only when its row was repainted, so we - // neither leave a stale cursor nor stack the translucent overlay on itself. - if (cursorRowDirty) { - drawCursor(gc, snapshot, left, top, cellWidth, lineHeight); - } - // Repainting rows clears the side borders within the band; restore just those - // segments, clipped to the band so we don't redraw the whole outline. - gc.save(); - clipRect(gc, px, bandMin, pw, bandMax - bandMin); - drawBorder(gc, px, py, pw, ph, active); - gc.restore(); - } - - private static boolean allRowsDirty(RenderStateSnapshot snapshot, List rows) { - if (rows.size() != snapshot.rows()) { - return false; - } - for (int i = 0; i < rows.size(); i++) { - RenderRow row = rows.get(i); - if (!row.dirty() || row.row() != i) { - return false; - } - } - return true; - } - - private void drawBorder(GraphicsContext gc, double x, double y, double width, double height, boolean active) { - gc.setStroke(active ? ACTIVE_BORDER : INACTIVE_BORDER); - gc.setLineWidth(active ? 2.0 : 1.0); - gc.strokeRect(x + 0.5, y + 0.5, width - 1.0, height - 1.0); - } - // Effective background colour of a cell as it is drawn (reverse video swaps fg/bg, an // unset colour falls back to the defaults). private static Color cellBackgroundColor(RenderCell cell) { @@ -237,124 +137,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer { return cellBackgroundColor(firstCell ? cells.get(0) : cells.get(cells.size() - 1)); } - // Extend the row's edge-cell backgrounds into the left/right padding (the margin and the - // right-edge rounding sliver), so the unused space matches the rendered content. - private void paintSidePadding(GraphicsContext gc, RenderRow row, double paneX, double paneWidth, - double contentLeft, double cellWidth, double yTop, double bandHeight) { - int columns = row.cells().size(); - if (columns == 0) { - return; - } - double contentRight = contentLeft + (columns * cellWidth); - gc.setFill(rowEdgeBackground(row, true)); - gc.fillRect(paneX, yTop, contentLeft - paneX, bandHeight); - gc.setFill(rowEdgeBackground(row, false)); - gc.fillRect(contentRight, yTop, paneX + paneWidth - contentRight, bandHeight); - } - - // Fill the top/bottom padding strips with the top/bottom row's edge colour. - private void fillVerticalPadding(GraphicsContext gc, RenderStateSnapshot snapshot, - double paneX, double paneY, double paneWidth, double paneHeight, double contentTop, double contentBottom) { - List rows = snapshot.renderRows(); - if (rows.isEmpty()) { - return; - } - gc.setFill(rowEdgeBackground(rows.get(0), true)); - gc.fillRect(paneX, paneY, paneWidth, contentTop - paneY); - gc.setFill(rowEdgeBackground(rows.get(rows.size() - 1), true)); - gc.fillRect(paneX, contentBottom, paneWidth, paneY + paneHeight - contentBottom); - } - - private void drawRow( - GraphicsContext gc, - RenderRow row, - double left, - double top, - double baseline, - double cellWidth, - double lineHeight - ) { - drawRowBackgrounds(gc, row, left, top, cellWidth, lineHeight); - drawRowText(gc, row, left, baseline, cellWidth, lineHeight); - } - - private static void drawRowBackgrounds( - GraphicsContext gc, - RenderRow row, - double left, - double top, - double cellWidth, - double lineHeight - ) { - Color runBackground = null; - int runStartColumn = 0; - int previousColumn = -1; - for (RenderCell cell : row.cells()) { - if (cell.kittyPlaceholder().isPresent()) { - flushBackgroundRun(gc, runBackground, left, top, cellWidth, lineHeight, row.row(), runStartColumn, previousColumn); - runBackground = null; - previousColumn = -1; - continue; - } - - Color bg = cell.selected() ? SELECTED_BACKGROUND : cellBackgroundOverride(cell); - if (bg == null) { - flushBackgroundRun(gc, runBackground, left, top, cellWidth, lineHeight, row.row(), runStartColumn, previousColumn); - runBackground = null; - previousColumn = -1; - continue; - } - - if (runBackground == null || bg != runBackground || cell.column() != previousColumn + 1) { - flushBackgroundRun(gc, runBackground, left, top, cellWidth, lineHeight, row.row(), runStartColumn, previousColumn); - runBackground = bg; - runStartColumn = cell.column(); - } - previousColumn = cell.column(); - } - flushBackgroundRun(gc, runBackground, left, top, cellWidth, lineHeight, row.row(), runStartColumn, previousColumn); - } - - private static void flushBackgroundRun( - GraphicsContext gc, - Color background, - double left, - double top, - double cellWidth, - double lineHeight, - int row, - int startColumn, - int endColumn - ) { - if (background == null || endColumn < startColumn) { - return; - } - gc.setFill(background); - gc.fillRect( - left + (startColumn * cellWidth), - top + (row * lineHeight), - (endColumn - startColumn + 1) * cellWidth, - lineHeight); - } - - private void drawRowText( - GraphicsContext gc, - RenderRow row, - double left, - double baseline, - double cellWidth, - double lineHeight - ) { - for (RenderCell cell : row.cells()) { - if (cell.kittyPlaceholder().isPresent() || cell.codepoints().length == 0) { - continue; - } - - gc.setFill(cellForegroundColor(cell)); - gc.fillText(cell.text(), left + (cell.column() * cellWidth), baseline + (row.row() * lineHeight)); - } - } - // Background override for a cell: null means the pane default background already covers it. private static Color cellBackgroundOverride(RenderCell cell) { if (cell.inverse()) { @@ -392,29 +174,6 @@ final class GhosttyTerminalRenderer extends TerminalRenderer { return created; } - private static void drawCursor(GraphicsContext gc, RenderStateSnapshot snapshot, double left, double top, double cellWidth, double lineHeight) { - if (!snapshot.cursorVisible() || !snapshot.cursorViewportHasValue()) { - return; - } - - double x = left + (snapshot.cursorViewportX() * cellWidth); - double y = top + (snapshot.cursorViewportY() * lineHeight); - gc.setStroke(DEFAULT_FOREGROUND); - gc.setFill(CURSOR_FILL); - gc.setLineWidth(1.5); - - RenderCursorStyle style = snapshot.cursorStyle(); - if (style == RenderCursorStyle.BAR) { - gc.strokeLine(x + 0.5, y + 2.0, x + 0.5, y + lineHeight - 2.0); - } else if (style == RenderCursorStyle.UNDERLINE) { - gc.strokeLine(x + 1.0, y + lineHeight - 2.0, x + cellWidth - 1.0, y + lineHeight - 2.0); - } else if (style == RenderCursorStyle.BLOCK) { - gc.fillRect(x + 0.5, y + 1.0, Math.max(1.0, cellWidth - 1.0), Math.max(1.0, lineHeight - 2.0)); - } else { - gc.strokeRect(x + 0.5, y + 1.0, Math.max(1.0, cellWidth - 1.0), Math.max(1.0, lineHeight - 2.0)); - } - } - // ---- Kitty graphics -------------------------------------------------------------- private static boolean hasKittyGraphics(RenderTarget target) {