Cache glyphs, fix vttest test
authorKevin Lamonte <kevin.lamonte@gmail.com>
Wed, 15 Mar 2017 15:24:11 +0000 (11:24 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Wed, 15 Mar 2017 15:24:11 +0000 (11:24 -0400)
src/jexer/io/SwingScreen.java
src/jexer/tterminal/ECMA48.java

index 4a602b3c272742e20de3ac02cdf9d00f8282379e..b7c8410993ce8647e23eacc8c5f8aad3545ece8f 100644 (file)
@@ -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<Cell, BufferedImage> glyphCacheBlink;
+
+        /**
+         * A cache of previously-rendered glyphs for non-blinking, or
+         * blinking-and-visible, text.
+         */
+        private HashMap<Cell, BufferedImage> 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<Cell, BufferedImage>();
+            glyphCache = new HashMap<Cell, BufferedImage>();
+
             // 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);
         }
     }
index 7869829753df190c64306b51583731f3d812faec..f657eaffa1255bdf0ee5a391ca0cfcdaaec0aa0f 100644 (file)
@@ -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