no image caching, no transparency for performance
This commit is contained in:
@@ -41,12 +41,17 @@ public final class TerminalCanvasView {
|
|||||||
// The default cell background (used for cells with no explicit bg, and as the foreground
|
// The default cell background (used for cells with no explicit bg, and as the foreground
|
||||||
// for reverse-video cells whose background is the terminal default).
|
// for reverse-video cells whose background is the terminal default).
|
||||||
private static final Color PANE_BACKGROUND = Color.rgb(9, 10, 12);
|
private static final Color PANE_BACKGROUND = Color.rgb(9, 10, 12);
|
||||||
|
// Canvas background shown wherever no pane covers (gaps, behind nothing once tiled panes
|
||||||
|
// fill the canvas). Painted on a full recomposite.
|
||||||
|
private static final Color GAP_BACKGROUND = Color.rgb(16, 16, 18);
|
||||||
|
|
||||||
private final Canvas canvas = new Canvas();
|
private final Canvas canvas = new Canvas();
|
||||||
private final TerminalWorkspace workspace;
|
private final TerminalWorkspace workspace;
|
||||||
private final AppConfig config;
|
private final AppConfig config;
|
||||||
private final Map<KittyImageKey, Image> kittyImageCache = new HashMap<>();
|
private final Map<KittyImageKey, Image> kittyImageCache = new HashMap<>();
|
||||||
private final Map<TerminalPane, PaneRenderCache> paneRenderCache = new HashMap<>();
|
// Last content version drawn to the canvas per pane, so a content frame repaints only
|
||||||
|
// the panes that actually changed.
|
||||||
|
private final Map<TerminalPane, Long> paneContentVersion = new HashMap<>();
|
||||||
private String fontFamily;
|
private String fontFamily;
|
||||||
private double fontSize;
|
private double fontSize;
|
||||||
private Font cachedFont;
|
private Font cachedFont;
|
||||||
@@ -85,22 +90,28 @@ public final class TerminalCanvasView {
|
|||||||
this.fontSize = size;
|
this.fontSize = size;
|
||||||
cachedFont = null;
|
cachedFont = null;
|
||||||
cachedMetrics = null;
|
cachedMetrics = null;
|
||||||
paneRenderCache.clear();
|
paneContentVersion.clear();
|
||||||
lastWidth = -1.0; // force a redraw on the next frame
|
lastWidth = -1.0; // force a redraw on the next frame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GhosttyRenderStateDirty values (stable C ABI; see ghostty/vt/render.h).
|
||||||
|
private static final int DIRTY_PARTIAL = 1;
|
||||||
|
private static final int DIRTY_FULL = 2;
|
||||||
|
|
||||||
public void render() {
|
public void render() {
|
||||||
double width = canvas.getWidth();
|
double width = canvas.getWidth();
|
||||||
double height = canvas.getHeight();
|
double height = canvas.getHeight();
|
||||||
|
|
||||||
// Geometry is a pure function of (size, workspace version); content changes bump the
|
|
||||||
// global render tick. If none moved, nothing can have changed visually, so bail out
|
|
||||||
// before doing any layout/pane-list/render-key work — this runs ~60x/s while idle.
|
|
||||||
long workspaceVersion = workspace.version();
|
long workspaceVersion = workspace.version();
|
||||||
long renderTick = TerminalPane.renderTick();
|
long renderTick = TerminalPane.renderTick();
|
||||||
if (width == lastWidth && height == lastHeight
|
|
||||||
&& fontSize == lastFontSize && java.util.Objects.equals(fontFamily, lastFontFamily)
|
// Two kinds of change: a layout change (size, font, active pane, pane set / z-order)
|
||||||
&& workspaceVersion == lastWorkspaceVersion && renderTick == lastRenderTick) {
|
// forces a full recomposite; a content change (renderTick) only repaints the panes
|
||||||
|
// whose terminal content changed. Idle frames — neither — bail out immediately.
|
||||||
|
boolean layoutChanged = width != lastWidth || height != lastHeight
|
||||||
|
|| fontSize != lastFontSize || !java.util.Objects.equals(fontFamily, lastFontFamily)
|
||||||
|
|| workspaceVersion != lastWorkspaceVersion;
|
||||||
|
boolean contentChanged = renderTick != lastRenderTick;
|
||||||
|
if (!layoutChanged && !contentChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastWidth = width;
|
lastWidth = width;
|
||||||
@@ -115,105 +126,129 @@ public final class TerminalCanvasView {
|
|||||||
FontMetrics metrics = currentFontMetrics();
|
FontMetrics metrics = currentFontMetrics();
|
||||||
List<TerminalPane> panes = workspace.panes();
|
List<TerminalPane> panes = workspace.panes();
|
||||||
|
|
||||||
|
// Apply terminal resizes up front so snapshots reflect current geometry (a no-op
|
||||||
|
// when the grid is unchanged).
|
||||||
|
for (TerminalPane pane : panes) {
|
||||||
|
applyResize(pane, metrics);
|
||||||
|
}
|
||||||
|
|
||||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||||
gc.setFill(Color.rgb(16, 16, 18));
|
|
||||||
gc.fillRect(0, 0, width, height);
|
|
||||||
gc.setFontSmoothingType(FontSmoothingType.LCD);
|
gc.setFontSmoothingType(FontSmoothingType.LCD);
|
||||||
|
|
||||||
paneRenderCache.keySet().removeIf(pane -> !panes.contains(pane));
|
if (layoutChanged) {
|
||||||
for (TerminalPane pane : panes) {
|
// Recomposite everything onto the retained canvas: clear, then paint panes
|
||||||
drawPane(gc, pane, font, metrics);
|
// bottom-to-top (workspace.panes() puts the active floating pane last == on top).
|
||||||
}
|
paneContentVersion.keySet().retainAll(panes);
|
||||||
}
|
gc.setFill(GAP_BACKGROUND);
|
||||||
|
gc.fillRect(0, 0, width, height);
|
||||||
// GhosttyRenderStateDirty values (stable C ABI; see ghostty/vt/render.h).
|
for (TerminalPane pane : panes) {
|
||||||
private static final int DIRTY_PARTIAL = 1;
|
paintPane(gc, pane, font, metrics, pane.renderSnapshotFull());
|
||||||
private static final int DIRTY_FULL = 2;
|
paneContentVersion.put(pane, pane.renderVersion());
|
||||||
|
|
||||||
private void drawPane(GraphicsContext gc, TerminalPane pane, Font font, FontMetrics metrics) {
|
|
||||||
// Resize up front so a geometry change is reflected as a FULL-dirty snapshot (with
|
|
||||||
// all cells) on this same frame, before we fetch the snapshot below.
|
|
||||||
int columns = Math.max(1, (int) ((pane.width() - 24.0) / metrics.cellWidth));
|
|
||||||
int rows = Math.max(1, (int) ((pane.height() - 24.0) / metrics.lineHeight));
|
|
||||||
pane.resize(columns, rows, (int) Math.round(metrics.cellWidth), (int) Math.round(metrics.lineHeight));
|
|
||||||
|
|
||||||
if (config.kittyGraphics() && paneHasKittyGraphics(pane)) {
|
|
||||||
// Panes with kitty images redraw fully each frame (images compose with text),
|
|
||||||
// so they bypass the incremental offscreen cache and need every cell.
|
|
||||||
paneRenderCache.remove(pane);
|
|
||||||
gc.save();
|
|
||||||
if (pane.floating()) {
|
|
||||||
gc.setGlobalAlpha(0.96);
|
|
||||||
}
|
}
|
||||||
gc.beginPath();
|
|
||||||
gc.rect(pane.x(), pane.y(), pane.width(), pane.height());
|
|
||||||
gc.clip();
|
|
||||||
drawPaneContent(gc, pane, font, metrics, pane.renderSnapshotFull(),
|
|
||||||
pane.x(), pane.y(), pane.width(), pane.height(), true);
|
|
||||||
gc.restore();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PaneRenderCache cache = paneRenderCache.computeIfAbsent(pane, ignored -> new PaneRenderCache());
|
// Content-only frame: repaint just the panes whose content changed, directly on the
|
||||||
int imageWidth = Math.max(1, (int) Math.ceil(pane.width()));
|
// retained canvas, then restore any panes stacked above where they overlap.
|
||||||
int imageHeight = Math.max(1, (int) Math.ceil(pane.height()));
|
for (int i = 0; i < panes.size(); i++) {
|
||||||
|
TerminalPane pane = panes.get(i);
|
||||||
// Reuse the offscreen buffers; only reallocate when the pane size changes.
|
Long drawn = paneContentVersion.get(pane);
|
||||||
boolean sizeChanged = cache.canvas == null || cache.image == null
|
if (drawn != null && drawn == pane.renderVersion()) {
|
||||||
|| cache.imageWidth != imageWidth || cache.imageHeight != imageHeight;
|
continue;
|
||||||
if (sizeChanged) {
|
}
|
||||||
cache.canvas = new Canvas(imageWidth, imageHeight);
|
repaintPaneContent(gc, panes, i, font, metrics);
|
||||||
cache.image = new WritableImage(imageWidth, imageHeight);
|
paneContentVersion.put(pane, pane.renderVersion());
|
||||||
cache.imageWidth = imageWidth;
|
|
||||||
cache.imageHeight = imageHeight;
|
|
||||||
cache.layoutKey = null;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String layoutKey = paneLayoutKey(pane, metrics);
|
private void applyResize(TerminalPane pane, FontMetrics metrics) {
|
||||||
boolean firstDraw = sizeChanged || cache.layoutKey == null;
|
int columns = Math.max(1, (int) ((pane.width() - 24.0) / metrics.cellWidth));
|
||||||
boolean layoutChanged = !layoutKey.equals(cache.layoutKey);
|
int rows = Math.max(1, (int) ((pane.height() - 24.0) / metrics.lineHeight));
|
||||||
boolean contentChanged = pane.renderVersion() != cache.contentVersion;
|
pane.resize(columns, rows, (int) Math.round(metrics.cellWidth), (int) Math.round(metrics.lineHeight));
|
||||||
|
}
|
||||||
|
|
||||||
GraphicsContext cacheGc = cache.canvas.getGraphicsContext2D();
|
// Paint a pane's whole body, clipped to its rect. Used for full recomposites.
|
||||||
boolean imageChanged = false;
|
private void paintPane(GraphicsContext gc, TerminalPane pane, Font font, FontMetrics metrics, RenderStateSnapshot snapshot) {
|
||||||
|
double px = Math.round(pane.x());
|
||||||
|
double py = Math.round(pane.y());
|
||||||
|
gc.save();
|
||||||
|
clipRect(gc, px, py, pane.width(), pane.height());
|
||||||
|
drawPaneContent(gc, pane, font, metrics, snapshot, px, py, pane.width(), pane.height(),
|
||||||
|
config.kittyGraphics() && paneHasKittyGraphics(pane));
|
||||||
|
gc.restore();
|
||||||
|
}
|
||||||
|
|
||||||
if (firstDraw || contentChanged) {
|
// Repaint one pane whose content changed, then restore the (opaque) panes stacked above
|
||||||
|
// it wherever they overlap the repainted region, so the z-order stays correct.
|
||||||
|
private void repaintPaneContent(GraphicsContext gc, List<TerminalPane> panes, int index, Font font, FontMetrics metrics) {
|
||||||
|
TerminalPane pane = panes.get(index);
|
||||||
|
double px = Math.round(pane.x());
|
||||||
|
double py = Math.round(pane.y());
|
||||||
|
double pw = pane.width();
|
||||||
|
double ph = pane.height();
|
||||||
|
boolean kitty = config.kittyGraphics() && paneHasKittyGraphics(pane);
|
||||||
|
|
||||||
|
double regionY0;
|
||||||
|
double regionY1;
|
||||||
|
gc.save();
|
||||||
|
clipRect(gc, px, py, pw, ph);
|
||||||
|
if (kitty) {
|
||||||
|
drawPaneContent(gc, pane, font, metrics, pane.renderSnapshotFull(), px, py, pw, ph, true);
|
||||||
|
regionY0 = py;
|
||||||
|
regionY1 = py + ph;
|
||||||
|
} else {
|
||||||
RenderStateSnapshot snapshot = pane.renderSnapshot();
|
RenderStateSnapshot snapshot = pane.renderSnapshot();
|
||||||
int dirty = snapshot == null ? DIRTY_FULL : snapshot.dirty();
|
int dirty = snapshot == null ? DIRTY_FULL : snapshot.dirty();
|
||||||
if (firstDraw || dirty == DIRTY_FULL) {
|
if (dirty == DIRTY_FULL) {
|
||||||
cacheGc.clearRect(0.0, 0.0, imageWidth, imageHeight);
|
drawPaneContent(gc, pane, font, metrics, snapshot, px, py, pw, ph, false);
|
||||||
drawPaneContent(cacheGc, pane, font, metrics, snapshot, 0.0, 0.0, imageWidth, imageHeight, false);
|
regionY0 = py;
|
||||||
imageChanged = true;
|
regionY1 = py + ph;
|
||||||
} else if (dirty == DIRTY_PARTIAL) {
|
} else if (dirty == DIRTY_PARTIAL) {
|
||||||
drawDirtyRows(cacheGc, pane, font, metrics, snapshot, imageWidth, imageHeight);
|
double[] band = drawDirtyRows(gc, pane, font, metrics, snapshot, px, py, pw, ph);
|
||||||
imageChanged = true;
|
gc.restore();
|
||||||
|
if (band == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
restoreStackedAbove(gc, panes, index, font, metrics, px, band[0], pw, band[1] - band[0]);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
gc.restore();
|
||||||
|
return; // dirty == FALSE: nothing visible changed.
|
||||||
}
|
}
|
||||||
// dirty == FALSE: the write produced no visible change; keep the buffer.
|
|
||||||
}
|
}
|
||||||
if (!imageChanged && layoutChanged) {
|
gc.restore();
|
||||||
// Only the active-border state changed; repaint the border over retained content.
|
restoreStackedAbove(gc, panes, index, font, metrics, px, regionY0, pw, regionY1 - regionY0);
|
||||||
drawBorder(cacheGc, pane, 0.0, 0.0, imageWidth, imageHeight);
|
}
|
||||||
imageChanged = true;
|
|
||||||
}
|
|
||||||
if (imageChanged) {
|
|
||||||
cache.canvas.snapshot(null, cache.image);
|
|
||||||
}
|
|
||||||
cache.contentVersion = pane.renderVersion();
|
|
||||||
cache.layoutKey = layoutKey;
|
|
||||||
|
|
||||||
// Blit on integer pixels: a fractional destination resamples the whole buffer and
|
// Redraw any panes above `index` in z-order that intersect the given screen rect, so a
|
||||||
// smears the (now pixel-aligned) content back into seams.
|
// lower pane's repaint doesn't leak through a pane stacked on top of it.
|
||||||
double destX = Math.round(pane.x());
|
private void restoreStackedAbove(GraphicsContext gc, List<TerminalPane> panes, int index,
|
||||||
double destY = Math.round(pane.y());
|
Font font, FontMetrics metrics, double rx, double ry, double rw, double rh) {
|
||||||
if (pane.floating()) {
|
for (int j = index + 1; j < panes.size(); j++) {
|
||||||
gc.setGlobalAlpha(0.96);
|
TerminalPane above = panes.get(j);
|
||||||
gc.drawImage(cache.image, destX, destY);
|
double ax = Math.round(above.x());
|
||||||
gc.setGlobalAlpha(1.0);
|
double ay = Math.round(above.y());
|
||||||
} else {
|
double ox0 = Math.max(rx, ax);
|
||||||
gc.drawImage(cache.image, destX, destY);
|
double oy0 = Math.max(ry, ay);
|
||||||
|
double ox1 = Math.min(rx + rw, ax + above.width());
|
||||||
|
double oy1 = Math.min(ry + rh, ay + above.height());
|
||||||
|
if (ox1 <= ox0 || oy1 <= oy0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
gc.save();
|
||||||
|
clipRect(gc, ox0, oy0, ox1 - ox0, oy1 - oy0);
|
||||||
|
drawPaneContent(gc, above, font, metrics, above.renderSnapshotFull(), ax, ay, above.width(), above.height(),
|
||||||
|
config.kittyGraphics() && paneHasKittyGraphics(above));
|
||||||
|
gc.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void clipRect(GraphicsContext gc, double x, double y, double width, double height) {
|
||||||
|
gc.beginPath();
|
||||||
|
gc.rect(x, y, width, height);
|
||||||
|
gc.clip();
|
||||||
|
}
|
||||||
|
|
||||||
// Full content render: background, border, all rows, cursor, and (when enabled) kitty
|
// Full content render: background, border, all rows, cursor, and (when enabled) kitty
|
||||||
// graphics. Used by the kitty direct path and by full offscreen redraws.
|
// graphics. Used by the kitty direct path and by full offscreen redraws.
|
||||||
private void drawPaneContent(
|
private void drawPaneContent(
|
||||||
@@ -261,48 +296,64 @@ public final class TerminalCanvasView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incremental render: repaint only the rows ghostty flagged dirty in the offscreen
|
// Incremental render: repaint only the rows ghostty flagged dirty, at the pane's screen
|
||||||
// buffer (origin 0,0), then restore the cursor and border.
|
// origin, then restore the cursor and border. Returns the screen-space [minY, maxY] band
|
||||||
private void drawDirtyRows(
|
// that was repainted (for restoring panes stacked above), or null if nothing was dirty.
|
||||||
|
private double[] drawDirtyRows(
|
||||||
GraphicsContext gc,
|
GraphicsContext gc,
|
||||||
TerminalPane pane,
|
TerminalPane pane,
|
||||||
Font font,
|
Font font,
|
||||||
FontMetrics metrics,
|
FontMetrics metrics,
|
||||||
RenderStateSnapshot snapshot,
|
RenderStateSnapshot snapshot,
|
||||||
double width,
|
double px,
|
||||||
double height
|
double py,
|
||||||
|
double pw,
|
||||||
|
double ph
|
||||||
) {
|
) {
|
||||||
gc.setFontSmoothingType(FontSmoothingType.LCD);
|
gc.setFontSmoothingType(FontSmoothingType.LCD);
|
||||||
gc.setFont(font);
|
gc.setFont(font);
|
||||||
double left = 12.0;
|
double left = px + 12.0;
|
||||||
double top = 12.0;
|
double top = py + 12.0;
|
||||||
double baseline = top + metrics.baselineOffset;
|
double baseline = top + metrics.baselineOffset;
|
||||||
|
|
||||||
boolean cursorRowDirty = false;
|
boolean cursorRowDirty = false;
|
||||||
|
double bandMin = Double.POSITIVE_INFINITY;
|
||||||
|
double bandMax = Double.NEGATIVE_INFINITY;
|
||||||
for (RenderRow row : snapshot.renderRows()) {
|
for (RenderRow row : snapshot.renderRows()) {
|
||||||
if (!row.dirty()) {
|
if (!row.dirty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Snap the row band to integer pixels and paint opaque (no clearRect): a
|
// Snap the row band to integer pixels and paint opaque: a fractional-height fill
|
||||||
// fractional-height fill would leave sub-pixel-transparent seams between rows,
|
// would leave sub-pixel seams between rows.
|
||||||
// which the floating-pane alpha compositing reveals as faint horizontal lines.
|
|
||||||
double y0 = Math.floor(top + (row.row() * metrics.lineHeight));
|
double y0 = Math.floor(top + (row.row() * metrics.lineHeight));
|
||||||
double y1 = Math.ceil(top + ((row.row() + 1) * metrics.lineHeight));
|
double y1 = Math.ceil(top + ((row.row() + 1) * metrics.lineHeight));
|
||||||
gc.setFill(Color.rgb(9, 10, 12));
|
gc.setFill(PANE_BACKGROUND);
|
||||||
gc.fillRect(0.0, y0, width, y1 - y0);
|
gc.fillRect(px, y0, pw, y1 - y0);
|
||||||
drawRow(gc, row, left, top, baseline, metrics.cellWidth, metrics.lineHeight);
|
drawRow(gc, row, left, top, baseline, metrics.cellWidth, metrics.lineHeight);
|
||||||
|
bandMin = Math.min(bandMin, y0);
|
||||||
|
bandMax = Math.max(bandMax, y1);
|
||||||
if (snapshot.cursorViewportHasValue() && row.row() == snapshot.cursorViewportY()) {
|
if (snapshot.cursorViewportHasValue() && row.row() == snapshot.cursorViewportY()) {
|
||||||
cursorRowDirty = true;
|
cursorRowDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (bandMin > bandMax) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// The cursor overlays its cell; redraw it only when its row was repainted, so we
|
// 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.
|
// neither leave a stale cursor nor stack the translucent overlay on itself.
|
||||||
if (cursorRowDirty) {
|
if (cursorRowDirty) {
|
||||||
drawCursor(gc, snapshot, left, top, metrics.cellWidth, metrics.lineHeight);
|
drawCursor(gc, snapshot, left, top, metrics.cellWidth, metrics.lineHeight);
|
||||||
}
|
}
|
||||||
// Repainting rows clears the full width, erasing the side borders; restore the frame.
|
// Repainting rows clears the side borders within the band; restore just those
|
||||||
drawBorder(gc, pane, 0.0, 0.0, width, height);
|
// segments. Clipping to the band is important: the full border rectangle extends
|
||||||
|
// outside the repainted region, and only the band gets restored over panes stacked
|
||||||
|
// above — an unclipped stroke would leave this pane's outline on top of them.
|
||||||
|
gc.save();
|
||||||
|
clipRect(gc, px, bandMin, pw, bandMax - bandMin);
|
||||||
|
drawBorder(gc, pane, px, py, pw, ph);
|
||||||
|
gc.restore();
|
||||||
|
return new double[] {bandMin, bandMax};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawBorder(GraphicsContext gc, TerminalPane pane, double x, double y, double width, double height) {
|
private void drawBorder(GraphicsContext gc, TerminalPane pane, double x, double y, double width, double height) {
|
||||||
@@ -347,17 +398,6 @@ public final class TerminalCanvasView {
|
|||||||
// Layout identity of a pane: everything that forces a redraw EXCEPT terminal content
|
// Layout identity of a pane: everything that forces a redraw EXCEPT terminal content
|
||||||
// (which is tracked separately by renderVersion). Deliberately omits renderVersion so
|
// (which is tracked separately by renderVersion). Deliberately omits renderVersion so
|
||||||
// content changes go through the incremental dirty-row path instead of a full redraw.
|
// content changes go through the incremental dirty-row path instead of a full redraw.
|
||||||
private String paneLayoutKey(TerminalPane pane, FontMetrics metrics) {
|
|
||||||
return workspace.isActive(pane)
|
|
||||||
+ ":" + pane.width()
|
|
||||||
+ ":" + pane.height()
|
|
||||||
+ ":" + fontFamily
|
|
||||||
+ ":" + fontSize
|
|
||||||
+ ":" + metrics.cellWidth
|
|
||||||
+ ":" + metrics.lineHeight
|
|
||||||
+ ":" + config.kittyGraphics();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<KittyPlaceholderKey, KittyPlaceholderBounds> kittyPlaceholderBounds(RenderStateSnapshot snapshot) {
|
private static Map<KittyPlaceholderKey, KittyPlaceholderBounds> kittyPlaceholderBounds(RenderStateSnapshot snapshot) {
|
||||||
if (snapshot == null) {
|
if (snapshot == null) {
|
||||||
return Map.of();
|
return Map.of();
|
||||||
@@ -903,13 +943,4 @@ public final class TerminalCanvasView {
|
|||||||
return maxSourceColumn - minSourceColumn + 1;
|
return maxSourceColumn - minSourceColumn + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class PaneRenderCache {
|
|
||||||
private Canvas canvas;
|
|
||||||
private WritableImage image;
|
|
||||||
private int imageWidth;
|
|
||||||
private int imageHeight;
|
|
||||||
private String layoutKey;
|
|
||||||
private long contentVersion = Long.MIN_VALUE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user