From c8165631ed92c58eb90b94b80390b5bfaf9b1b6e Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Wed, 15 Mar 2017 11:24:11 -0400 Subject: [PATCH] Cache glyphs, fix vttest test --- src/jexer/io/SwingScreen.java | 242 +++++++++++++++++++++++--------- src/jexer/tterminal/ECMA48.java | 16 ++- 2 files changed, 186 insertions(+), 72 deletions(-) diff --git a/src/jexer/io/SwingScreen.java b/src/jexer/io/SwingScreen.java index 4a602b3..b7c8410 100644 --- a/src/jexer/io/SwingScreen.java +++ b/src/jexer/io/SwingScreen.java @@ -33,6 +33,7 @@ import java.awt.Cursor; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; @@ -42,6 +43,7 @@ import java.awt.image.BufferedImage; import java.awt.image.BufferStrategy; import java.io.InputStream; import java.util.Date; +import java.util.HashMap; import javax.swing.JFrame; import javax.swing.SwingUtilities; @@ -146,6 +148,18 @@ public final class SwingScreen extends Screen { */ private BufferStrategy bufferStrategy; + /** + * A cache of previously-rendered glyphs for blinking text, when it + * is not visible. + */ + private HashMap glyphCacheBlink; + + /** + * A cache of previously-rendered glyphs for non-blinking, or + * blinking-and-visible, text. + */ + private HashMap glyphCache; + /** * The TUI Screen data. */ @@ -253,7 +267,8 @@ public final class SwingScreen extends Screen { return MYWHITE; } } - throw new IllegalArgumentException("Invalid color: " + attr.getForeColor().getValue()); + throw new IllegalArgumentException("Invalid color: " + + attr.getForeColor().getValue()); } /** @@ -280,7 +295,8 @@ public final class SwingScreen extends Screen { } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) { return MYWHITE; } - throw new IllegalArgumentException("Invalid color: " + attr.getBackColor().getValue()); + throw new IllegalArgumentException("Invalid color: " + + attr.getBackColor().getValue()); } /** @@ -293,8 +309,8 @@ public final class SwingScreen extends Screen { setDOSColors(); // Figure out my cursor style - String cursorStyleString = System.getProperty("jexer.Swing.cursorStyle", - "underline").toLowerCase(); + String cursorStyleString = System.getProperty( + "jexer.Swing.cursorStyle", "underline").toLowerCase(); if (cursorStyleString.equals("underline")) { cursorStyle = CursorStyle.UNDERLINE; @@ -309,7 +325,8 @@ public final class SwingScreen extends Screen { try { // Always try to use Terminus, the one decent font. - ClassLoader loader = Thread.currentThread().getContextClassLoader(); + ClassLoader loader = Thread.currentThread(). + getContextClassLoader(); InputStream in = loader.getResourceAsStream(FONTFILE); Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in); Font terminus = terminusRoot.deriveFont(Font.PLAIN, 22); @@ -336,6 +353,10 @@ public final class SwingScreen extends Screen { // Save the text cell width/height getFontDimensions(); + // Cache glyphs as they are rendered + glyphCacheBlink = new HashMap(); + glyphCache = new HashMap(); + // Setup triple-buffering if (SwingScreen.tripleBuffer) { setIgnoreRepaint(true); @@ -391,6 +412,110 @@ public final class SwingScreen extends Screen { paint(gr); } + /** + * Draw one glyph to the screen. + * + * @param gr the Swing Graphics context + * @param cell the Cell to draw + * @param xPixel the x-coordinate to render to. 0 means the + * left-most pixel column. + * @param yPixel the y-coordinate to render to. 0 means the top-most + * pixel row. + */ + private void drawGlyph(final Graphics gr, final Cell cell, + final int xPixel, final int yPixel) { + + BufferedImage image = null; + if (cell.isBlink() && !cursorBlinkVisible) { + image = glyphCacheBlink.get(cell); + } else { + image = glyphCache.get(cell); + } + if (image != null) { + gr.drawImage(image, xPixel, yPixel, this); + return; + } + + // Generate glyph and draw it. + + image = new BufferedImage(textWidth, textHeight, + BufferedImage.TYPE_INT_ARGB); + Graphics2D gr2 = image.createGraphics(); + gr2.setFont(getFont()); + + Cell cellColor = new Cell(); + cellColor.setTo(cell); + + // Check for reverse + if (cell.isReverse()) { + cellColor.setForeColor(cell.getBackColor()); + cellColor.setBackColor(cell.getForeColor()); + } + + // Draw the background rectangle, then the foreground character. + gr2.setColor(attrToBackgroundColor(cellColor)); + gr2.fillRect(0, 0, textWidth, textHeight); + + // Handle blink and underline + if (!cell.isBlink() + || (cell.isBlink() && cursorBlinkVisible) + ) { + gr2.setColor(attrToForegroundColor(cellColor)); + char [] chars = new char[1]; + chars[0] = cell.getChar(); + gr2.drawChars(chars, 0, 1, 0 + textAdjustX, + 0 + textHeight - maxDescent + textAdjustY); + + if (cell.isUnderline()) { + gr2.fillRect(0, 0 + textHeight - 2, textWidth, 2); + } + } + gr2.dispose(); + + // We need a new key that will not be mutated by invertCell(). + Cell key = new Cell(); + key.setTo(cell); + if (cell.isBlink() && !cursorBlinkVisible) { + glyphCacheBlink.put(key, image); + } else { + glyphCache.put(key, image); + } + + gr.drawImage(image, xPixel, yPixel, this); + } + + /** + * Check if the cursor is visible, and if so draw it. + * + * @param gr the Swing Graphics context + */ + private void drawCursor(final Graphics gr) { + + if (cursorVisible + && (cursorY <= screen.height - 1) + && (cursorX <= screen.width - 1) + && cursorBlinkVisible + ) { + int xPixel = cursorX * textWidth + left; + int yPixel = cursorY * textHeight + top; + Cell lCell = screen.logical[cursorX][cursorY]; + gr.setColor(attrToForegroundColor(lCell)); + switch (cursorStyle) { + default: + // Fall through... + case UNDERLINE: + gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2); + break; + case BLOCK: + gr.fillRect(xPixel, yPixel, textWidth, textHeight); + break; + case OUTLINE: + gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1); + break; + } + } + } + /** * Paint redraws the whole screen. * @@ -451,12 +576,10 @@ public final class SwingScreen extends Screen { // Prevent updates to the screen's data from the TApplication // threads. synchronized (screen) { - /* System.err.printf("bounds %s X %d %d Y %d %d\n", bounds, xCellMin, xCellMax, yCellMin, yCellMax); */ - Cell lCellColor = new Cell(); for (int y = yCellMin; y < yCellMax; y++) { for (int x = xCellMin; x < xCellMax; x++) { @@ -471,68 +594,14 @@ public final class SwingScreen extends Screen { || lCell.isBlink() || reallyCleared) { - lCellColor.setTo(lCell); - - // Check for reverse - if (lCell.isReverse()) { - lCellColor.setForeColor(lCell.getBackColor()); - lCellColor.setBackColor(lCell.getForeColor()); - } - - // Draw the background rectangle, then the - // foreground character. - gr.setColor(attrToBackgroundColor(lCellColor)); - gr.fillRect(xPixel, yPixel, textWidth, textHeight); - - // Handle blink and underline - if (!lCell.isBlink() - || (lCell.isBlink() && cursorBlinkVisible) - ) { - gr.setColor(attrToForegroundColor(lCellColor)); - char [] chars = new char[1]; - chars[0] = lCell.getChar(); - gr.drawChars(chars, 0, 1, xPixel + textAdjustX, - yPixel + textHeight - maxDescent - + textAdjustY); - - if (lCell.isUnderline()) { - gr.fillRect(xPixel, yPixel + textHeight - 2, - textWidth, 2); - } - } + drawGlyph(gr, lCell, xPixel, yPixel); // Physical is always updated physical[x][y].setTo(lCell); } } } - - // Draw the cursor if it is visible - if (cursorVisible - && (cursorY <= screen.height - 1) - && (cursorX <= screen.width - 1) - && cursorBlinkVisible - ) { - int xPixel = cursorX * textWidth + left; - int yPixel = cursorY * textHeight + top; - Cell lCell = screen.logical[cursorX][cursorY]; - gr.setColor(attrToForegroundColor(lCell)); - switch (cursorStyle) { - default: - // Fall through... - case UNDERLINE: - gr.fillRect(xPixel, yPixel + textHeight - 2, - textWidth, 2); - break; - case BLOCK: - gr.fillRect(xPixel, yPixel, textWidth, textHeight); - break; - case OUTLINE: - gr.drawRect(xPixel, yPixel, textWidth - 1, - textHeight - 1); - break; - } - } + drawCursor(gr); dirty = false; reallyCleared = false; @@ -624,8 +693,48 @@ public final class SwingScreen extends Screen { return; } - // Request a repaint, let the frame's repaint/update methods do the - // right thing. + if (frame.bufferStrategy != null) { + // See if it is time to flip the blink time. + long nowTime = (new Date()).getTime(); + if (nowTime > frame.blinkMillis + frame.lastBlinkTime) { + frame.lastBlinkTime = nowTime; + frame.cursorBlinkVisible = !frame.cursorBlinkVisible; + } + + Graphics gr = frame.bufferStrategy.getDrawGraphics(); + + synchronized (this) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell lCell = logical[x][y]; + Cell pCell = physical[x][y]; + + int xPixel = x * frame.textWidth + frame.left; + int yPixel = y * frame.textHeight + frame.top; + + if (!lCell.equals(pCell) + || ((x == cursorX) + && (y == cursorY) + && cursorVisible) + || (lCell.isBlink()) + ) { + frame.drawGlyph(gr, lCell, xPixel, yPixel); + physical[x][y].setTo(lCell); + } + } + } + frame.drawCursor(gr); + } // synchronized (this) + + gr.dispose(); + frame.bufferStrategy.show(); + // sync() doesn't seem to help the tearing for me. + // Toolkit.getDefaultToolkit().sync(); + return; + } + + // Swing thread version: request a repaint, but limit it to the area + // that has changed. // Find the minimum-size damaged region. int xMin = frame.getWidth(); @@ -677,6 +786,8 @@ public final class SwingScreen extends Screen { yMin, yMax); */ if (frame.bufferStrategy != null) { + // This path should never be taken, but is left here for + // completeness. Graphics gr = frame.bufferStrategy.getDrawGraphics(); Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin); @@ -687,6 +798,7 @@ public final class SwingScreen extends Screen { // sync() doesn't seem to help the tearing for me. // Toolkit.getDefaultToolkit().sync(); } else { + // Repaint on the Swing thread. frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin); } } diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index 7869829..f657eaf 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -129,15 +129,13 @@ public class ECMA48 implements Runnable { return "\033[?6c"; case VT220: + case XTERM: // "I am a VT220" - 7 bit version if (!s8c1t) { return "\033[?62;1;6c"; } // "I am a VT220" - 8 bit version return "\u009b?62;1;6c"; - case XTERM: - // "I am a VT100 with advanced video option" (often VT102) - return "\033[?1;2c"; default: throw new IllegalArgumentException("Invalid device type: " + type); } @@ -3760,6 +3758,7 @@ public class ECMA48 implements Runnable { */ private void dsr() { boolean decPrivateModeFlag = false; + int row = currentState.cursorY; for (int i = 0; i < collectBuffer.length(); i++) { if (collectBuffer.charAt(i) == '?') { @@ -3787,15 +3786,18 @@ public class ECMA48 implements Runnable { case 6: // Request cursor position. Respond with current position. + if (currentState.originMode == true) { + row -= scrollRegionTop; + } String str = ""; if (((type == DeviceType.VT220) || (type == DeviceType.XTERM)) && (s8c1t == true) ) { - str = String.format("\u009b%d;%dR", - currentState.cursorY + 1, currentState.cursorX + 1); + str = String.format("\u009b%d;%dR", row + 1, + currentState.cursorX + 1); } else { - str = String.format("\033[%d;%dR", - currentState.cursorY + 1, currentState.cursorX + 1); + str = String.format("\033[%d;%dR", row + 1, + currentState.cursorX + 1); } // Send string directly to remote side -- 2.27.0