X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fio%2FSwingScreen.java;h=79ba8163ddb50308c63ba635946841e2e91b8f9b;hb=a2018e9964f6c58742cd1e6dd0a0c63e244a89d6;hp=3c949c1b8016866d4e36641d1afe1c0e1837a2c6;hpb=8582f35a3ffb8212463076217eb89278f42331d4;p=nikiroo-utils.git diff --git a/src/jexer/io/SwingScreen.java b/src/jexer/io/SwingScreen.java index 3c949c1..79ba816 100644 --- a/src/jexer/io/SwingScreen.java +++ b/src/jexer/io/SwingScreen.java @@ -1,29 +1,27 @@ /* * Jexer - Java Text User Interface * - * License: LGPLv3 or later + * The MIT License (MIT) * - * This module is licensed under the GNU Lesser General Public License - * Version 3. Please see the file "COPYING" in this directory for more - * information about the GNU Lesser General Public License Version 3. + * Copyright (C) 2017 Kevin Lamonte * - * Copyright (C) 2015 Kevin Lamonte + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 3 of - * the License, or (at your option) any later version. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, see - * http://www.gnu.org/licenses/, or write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. * * @author Kevin Lamonte [kevin.lamonte@gmail.com] * @version 1 @@ -35,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; @@ -44,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; @@ -57,9 +57,9 @@ import jexer.session.SwingSessionInfo; public final class SwingScreen extends Screen { /** - * If true, use double buffering thread. + * If true, use triple buffering thread. */ - private static final boolean doubleBuffer = true; + private static final boolean tripleBuffer = true; /** * Cursor style to draw. @@ -144,15 +144,32 @@ public final class SwingScreen extends Screen { private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf"; /** - * The BufferStrategy object needed for double-buffering. + * The BufferStrategy object needed for triple-buffering. */ 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. */ SwingScreen screen; + /** + * If true, we were successful getting Terminus. + */ + private boolean gotTerminus = false; + /** * Width of a character cell. */ @@ -255,7 +272,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()); } /** @@ -282,7 +300,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()); } /** @@ -295,8 +314,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; @@ -311,11 +330,13 @@ 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); + Font terminus = terminusRoot.deriveFont(Font.PLAIN, 20); setFont(terminus); + gotTerminus = true; } catch (Exception e) { e.printStackTrace(); // setFont(new Font("Liberation Mono", Font.PLAIN, 24)); @@ -338,10 +359,14 @@ public final class SwingScreen extends Screen { // Save the text cell width/height getFontDimensions(); - // Setup double-buffering - if (SwingScreen.doubleBuffer) { + // Cache glyphs as they are rendered + glyphCacheBlink = new HashMap(); + glyphCache = new HashMap(); + + // Setup triple-buffering + if (SwingScreen.tripleBuffer) { setIgnoreRepaint(true); - createBufferStrategy(2); + createBufferStrategy(3); bufferStrategy = getBufferStrategy(); } } @@ -356,15 +381,24 @@ public final class SwingScreen extends Screen { Rectangle2D bounds = fm.getMaxCharBounds(gr); int leading = fm.getLeading(); textWidth = (int)Math.round(bounds.getWidth()); - textHeight = (int)Math.round(bounds.getHeight()) - maxDescent; - // This also produces the same number, but works better for ugly + // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent; + + // This produces the same number, but works better for ugly // monospace. textHeight = fm.getMaxAscent() + maxDescent - leading; + if (gotTerminus == true) { + textHeight++; + } + if (System.getProperty("os.name").startsWith("Windows")) { textAdjustY = -1; textAdjustX = 0; } + if (System.getProperty("os.name").startsWith("Mac")) { + textAdjustY = -1; + textAdjustX = 0; + } } /** @@ -393,6 +427,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. * @@ -471,67 +609,14 @@ public final class SwingScreen extends Screen { || lCell.isBlink() || reallyCleared) { - Cell lCellColor = new Cell(); - 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) { - 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; @@ -571,7 +656,7 @@ public final class SwingScreen extends Screen { SwingScreen.this.frame.resizeToScreen(); SwingScreen.this.frame.setVisible(true); } - } ); + }); } catch (Exception e) { e.printStackTrace(); } @@ -597,17 +682,24 @@ public final class SwingScreen extends Screen { @Override public void flushPhysical() { - if (reallyCleared) { - // Really refreshed, do it all - if (SwingScreen.doubleBuffer) { - Graphics gr = frame.bufferStrategy.getDrawGraphics(); - frame.paint(gr); - gr.dispose(); - frame.bufferStrategy.show(); - Toolkit.getDefaultToolkit().sync(); - } else { - frame.repaint(); - } + /* + System.err.printf("flushPhysical(): reallyCleared %s dirty %s\n", + reallyCleared, dirty); + */ + + // If reallyCleared is set, we have to draw everything. + if ((frame.bufferStrategy != null) && (reallyCleared == true)) { + // Triple-buffering: we have to redraw everything on this thread. + Graphics gr = frame.bufferStrategy.getDrawGraphics(); + frame.paint(gr); + gr.dispose(); + frame.bufferStrategy.show(); + // sync() doesn't seem to help the tearing for me. + // Toolkit.getDefaultToolkit().sync(); + return; + } else if ((frame.bufferStrategy == null) && (reallyCleared == true)) { + // Repaint everything on the Swing thread. + frame.repaint(); return; } @@ -616,8 +708,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(); @@ -664,9 +796,13 @@ public final class SwingScreen extends Screen { } // Repaint the desired area - // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, - // yMin, yMax); - if (SwingScreen.doubleBuffer) { + /* + System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, + 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); @@ -674,8 +810,10 @@ public final class SwingScreen extends Screen { frame.paint(gr); gr.dispose(); frame.bufferStrategy.show(); - Toolkit.getDefaultToolkit().sync(); + // 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); } } @@ -734,4 +872,13 @@ public final class SwingScreen extends Screen { return ((y - frame.top) / frame.textHeight); } + /** + * Set the window title. + * + * @param title the new title + */ + public void setTitle(final String title) { + frame.setTitle(title); + } + }