diff --git a/src/main/java/com/gregor/jprototerm/Compositor.java b/src/main/java/com/gregor/jprototerm/Compositor.java index b9d2b54..d8a8ea0 100644 --- a/src/main/java/com/gregor/jprototerm/Compositor.java +++ b/src/main/java/com/gregor/jprototerm/Compositor.java @@ -365,7 +365,10 @@ public final class Compositor { double ey = localY(event.getY(), target); KeyModifiers modifiers = modifiers(event); for (int i = 0; i < rows; i++) { - sent |= send(pane, target, MouseInput.press(wheelButton, ex, ey, modifiers), mouseButtonPressed, event); + if (!send(pane, target, MouseInput.press(wheelButton, ex, ey, modifiers), mouseButtonPressed, event)) { + break; + } + sent = true; } } if (!sent) { diff --git a/src/main/java/com/gregor/jprototerm/TerminalPane.java b/src/main/java/com/gregor/jprototerm/TerminalPane.java index df2683d..ac3318e 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPane.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPane.java @@ -14,6 +14,7 @@ import dev.jlibghostty.Terminal; import dev.jlibghostty.TerminalOptions; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; /** * One terminal: owns its ghostty {@link Terminal}, the {@link ShellSession}/pty driving it, @@ -41,8 +42,8 @@ public final class TerminalPane implements AutoCloseable { private int rows; private int pixelWidth; private int pixelHeight; - private long contentVersion; - private long snapshotVersion = -1; + private final AtomicLong contentVersion = new AtomicLong(); + private volatile long snapshotVersion = -1; private TerminalPane(Terminal terminal, TerminalMetrics metrics, boolean kittyEnabled, Runnable onContentChange, int columns, int rows) { @@ -157,16 +158,17 @@ public final class TerminalPane implements AutoCloseable { private RenderStateSnapshot takeSnapshot(boolean full) { synchronized (terminal) { + long version = contentVersion.get(); if (full) { renderState.update(terminal); cachedSnapshot = renderState.snapshot(); renderState.resetDirty(); - snapshotVersion = contentVersion; - } else if (snapshotVersion != contentVersion) { + snapshotVersion = version; + } else if (snapshotVersion != version) { renderState.update(terminal); cachedSnapshot = renderState.snapshotIncremental(); renderState.resetDirty(); - snapshotVersion = contentVersion; + snapshotVersion = version; } return cachedSnapshot; } @@ -180,15 +182,11 @@ public final class TerminalPane implements AutoCloseable { /** This pane's own content revision, bumped on every change (see {@link #refresh()}). */ public long contentVersion() { - synchronized (terminal) { - return contentVersion; - } + return contentVersion.get(); } long snapshotVersion() { - synchronized (terminal) { - return snapshotVersion; - } + return snapshotVersion; } public boolean kittyEnabled() { @@ -256,7 +254,7 @@ public final class TerminalPane implements AutoCloseable { // Mark this pane's content dirty (the snapshot is computed lazily in the paint path, // so a burst of writes collapses into one snapshot per frame) and tell the owning tab // one of its panes changed. - contentVersion++; + contentVersion.incrementAndGet(); onContentChange.run(); } diff --git a/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java b/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java index 5e5d3f2..7b91015 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPaneNode.java @@ -603,6 +603,27 @@ final class TerminalPaneNode extends Region { return cellBackgroundColor(firstCell ? cells.get(0) : cells.get(cells.size() - 1)); } + private static Color cellBackgroundOverride(RenderCell cell) { + if (cell.inverse()) { + var fg = cell.foreground(); + return fg.isPresent() ? toFxColor(fg.get()) : DEFAULT_FOREGROUND; + } + var bg = cell.background(); + return bg.isPresent() ? toFxColor(bg.get()) : null; + } + + private static Color cellForegroundColor(RenderCell cell) { + var fgOpt = cell.foreground(); + var bgOpt = cell.background(); + Color fg = fgOpt.isPresent() ? toFxColor(fgOpt.get()) : DEFAULT_FOREGROUND; + Color bg = bgOpt.isPresent() ? toFxColor(bgOpt.get()) : null; + + if (cell.inverse()) { + return (bg != null) ? bg : PANE_BACKGROUND; + } + return fg; + } + private static Color toFxColor(RenderColor color) { int key = (color.red() << 16) | (color.green() << 8) | color.blue(); Color cached = COLOR_CACHE.get(key); @@ -709,37 +730,61 @@ final class TerminalPaneNode extends Region { double contentTop = TerminalMetrics.PADDING + row.row() * lineHeight; double localCellTop = contentTop - rowTop; double baseline = TerminalMetrics.PADDING + metrics.baselineOffset() + row.row() * lineHeight - rowTop; + drawRowBackgrounds(gc, row, localCellTop, cellWidth, lineHeight); + drawRowText(gc, row, baseline, cellWidth); + } + + private void drawRowBackgrounds(GraphicsContext gc, RenderRow row, double localCellTop, 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, localCellTop, cellWidth, lineHeight, runStartColumn, previousColumn); + runBackground = null; + previousColumn = -1; continue; } - double x = TerminalMetrics.PADDING + cell.column() * cellWidth; - var fgOpt = cell.foreground(); - var bgOpt = cell.background(); - Color fg = fgOpt.isPresent() ? toFxColor(fgOpt.get()) : DEFAULT_FOREGROUND; - Color bg = bgOpt.isPresent() ? toFxColor(bgOpt.get()) : null; - - if (cell.inverse()) { - Color swappedBg = fg; - fg = (bg != null) ? bg : PANE_BACKGROUND; - bg = swappedBg; - } - - if (bg != null) { - gc.setFill(bg); - gc.fillRect(x, localCellTop, cellWidth, lineHeight); - } - if (cell.selected()) { - gc.setFill(SELECTED_BACKGROUND); - gc.fillRect(x, localCellTop, cellWidth, lineHeight); - } - if (cell.codepoints().length == 0) { + Color background = cell.selected() ? SELECTED_BACKGROUND : cellBackgroundOverride(cell); + if (background == null) { + flushBackgroundRun(gc, runBackground, localCellTop, cellWidth, lineHeight, runStartColumn, previousColumn); + runBackground = null; + previousColumn = -1; continue; } - gc.setFill(fg); - gc.fillText(cell.text(), x, baseline); + if (runBackground == null || background != runBackground || cell.column() != previousColumn + 1) { + flushBackgroundRun(gc, runBackground, localCellTop, cellWidth, lineHeight, runStartColumn, previousColumn); + runBackground = background; + runStartColumn = cell.column(); + } + previousColumn = cell.column(); + } + flushBackgroundRun(gc, runBackground, localCellTop, cellWidth, lineHeight, runStartColumn, previousColumn); + } + + private void flushBackgroundRun(GraphicsContext gc, Color background, double localCellTop, + double cellWidth, double lineHeight, int startColumn, int endColumn) { + if (background == null || endColumn < startColumn) { + return; + } + gc.setFill(background); + gc.fillRect( + TerminalMetrics.PADDING + startColumn * cellWidth, + localCellTop, + (endColumn - startColumn + 1) * cellWidth, + lineHeight); + } + + private void drawRowText(GraphicsContext gc, RenderRow row, double baseline, double cellWidth) { + for (RenderCell cell : row.cells()) { + if (cell.kittyPlaceholder().isPresent() || cell.codepoints().length == 0) { + continue; + } + + gc.setFill(cellForegroundColor(cell)); + gc.fillText(cell.text(), TerminalMetrics.PADDING + cell.column() * cellWidth, baseline); } } }