X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fio%2FAWTScreen.java;h=f56f4238c765353ec325c5cbf080ed4558a3cb89;hb=87a17f3ca4b2602c396afdbb13cccb4c1e7cbd38;hp=e10e0ec67579af69a65c77dd37a74da7cb7b8ab8;hpb=1ac2ccb131cfab3a72dad856c67e2f4fd87aa143;p=nikiroo-utils.git diff --git a/src/jexer/io/AWTScreen.java b/src/jexer/io/AWTScreen.java index e10e0ec..f56f423 100644 --- a/src/jexer/io/AWTScreen.java +++ b/src/jexer/io/AWTScreen.java @@ -32,6 +32,7 @@ package jexer.io; import jexer.bits.Cell; import jexer.bits.CellAttributes; +import jexer.session.AWTSessionInfo; import java.awt.Color; import java.awt.Cursor; @@ -39,18 +40,76 @@ import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.InputStream; /** * This Screen implementation draws to a Java AWT Frame. */ public final class AWTScreen extends Screen { + private static Color MYBLACK; + private static Color MYRED; + private static Color MYGREEN; + private static Color MYYELLOW; + private static Color MYBLUE; + private static Color MYMAGENTA; + private static Color MYCYAN; + private static Color MYWHITE; + + private static Color MYBOLD_BLACK; + private static Color MYBOLD_RED; + private static Color MYBOLD_GREEN; + private static Color MYBOLD_YELLOW; + private static Color MYBOLD_BLUE; + private static Color MYBOLD_MAGENTA; + private static Color MYBOLD_CYAN; + private static Color MYBOLD_WHITE; + + private static boolean dosColors = false; + + /** + * Setup AWT colors to match DOS color palette. + */ + private static void setDOSColors() { + if (dosColors) { + return; + } + MYBLACK = new Color(0x00, 0x00, 0x00); + MYRED = new Color(0xa8, 0x00, 0x00); + MYGREEN = new Color(0x00, 0xa8, 0x00); + MYYELLOW = new Color(0xa8, 0x54, 0x00); + MYBLUE = new Color(0x00, 0x00, 0xa8); + MYMAGENTA = new Color(0xa8, 0x00, 0xa8); + MYCYAN = new Color(0x00, 0xa8, 0xa8); + MYWHITE = new Color(0xa8, 0xa8, 0xa8); + MYBOLD_BLACK = new Color(0x54, 0x54, 0x54); + MYBOLD_RED = new Color(0xfc, 0x54, 0x54); + MYBOLD_GREEN = new Color(0x54, 0xfc, 0x54); + MYBOLD_YELLOW = new Color(0xfc, 0xfc, 0x54); + MYBOLD_BLUE = new Color(0x54, 0x54, 0xfc); + MYBOLD_MAGENTA = new Color(0xfc, 0x54, 0xfc); + MYBOLD_CYAN = new Color(0x54, 0xfc, 0xfc); + MYBOLD_WHITE = new Color(0xfc, 0xfc, 0xfc); + + dosColors = true; + } + /** * AWTFrame is our top-level hook into the AWT system. */ class AWTFrame extends Frame { + /** + * The terminus font resource filename. + */ + private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf"; + /** * The TUI Screen data. */ @@ -66,41 +125,188 @@ public final class AWTScreen extends Screen { */ private int textHeight = 1; + /** + * Descent of a character cell. + */ + private int maxDescent = 0; + /** * Top pixel value. */ private int top = 30; - + /** * Left pixel value. */ private int left = 30; - + + /** + * Convert a CellAttributes foreground color to an AWT Color. + * + * @param attr the text attributes + * @return the AWT Color + */ + private Color attrToForegroundColor(final CellAttributes attr) { + /* + * TODO: + * reverse + * blink + * underline + */ + if (attr.getBold()) { + if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) { + return MYBOLD_BLACK; + } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) { + return MYBOLD_RED; + } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) { + return MYBOLD_BLUE; + } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) { + return MYBOLD_GREEN; + } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) { + return MYBOLD_YELLOW; + } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) { + return MYBOLD_CYAN; + } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) { + return MYBOLD_MAGENTA; + } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) { + return MYBOLD_WHITE; + } + } else { + if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) { + return MYBLACK; + } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) { + return MYRED; + } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) { + return MYBLUE; + } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) { + return MYGREEN; + } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) { + return MYYELLOW; + } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) { + return MYCYAN; + } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) { + return MYMAGENTA; + } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) { + return MYWHITE; + } + } + throw new IllegalArgumentException("Invalid color: " + attr.getForeColor().getValue()); + } + + /** + * Convert a CellAttributes background color to an AWT Color. + * + * @param attr the text attributes + * @return the AWT Color + */ + private Color attrToBackgroundColor(final CellAttributes attr) { + /* + * TODO: + * reverse + * blink + * underline + */ + if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) { + return MYBLACK; + } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) { + return MYRED; + } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) { + return MYBLUE; + } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) { + return MYGREEN; + } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) { + return MYYELLOW; + } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) { + return MYCYAN; + } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) { + return MYMAGENTA; + } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) { + return MYWHITE; + } + throw new IllegalArgumentException("Invalid color: " + attr.getBackColor().getValue()); + } + /** * Public constructor. + * + * @param screen the Screen that Backend talks to */ - public AWTFrame() { + public AWTFrame(final AWTScreen screen) { + this.screen = screen; + setDOSColors(); + setTitle("Jexer Application"); setBackground(java.awt.Color.black); - setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); - setFont(new Font("Liberation Mono", Font.BOLD, 16)); + // setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + // setFont(new Font("Liberation Mono", Font.BOLD, 16)); // setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16)); - setSize(100, 100); + + try { + // Always try to use Terminus, the one decent font. + 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); + setFont(terminus); + } catch (Exception e) { + e.printStackTrace(); + // setFont(new Font("Liberation Mono", Font.PLAIN, 24)); + setFont(new Font(Font.MONOSPACED, Font.PLAIN, 24)); + } setVisible(true); + resizeToScreen(); + + // Kill the X11 cursor + // Transparent 16 x 16 pixel cursor image. + BufferedImage cursorImg = new BufferedImage(16, 16, + BufferedImage.TYPE_INT_ARGB); + + // Create a new blank cursor. + Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor( + cursorImg, new Point(0, 0), "blank cursor"); + setCursor(blankCursor); } /** * Resize to font dimensions. */ public void resizeToScreen() { - Graphics gr = getGraphics(); - FontMetrics fm = gr.getFontMetrics(); - textWidth = fm.charWidth('m'); - textHeight = fm.getHeight(); - setSize((textWidth + 1) * screen.width + (2 * left), - (textHeight + 1) * screen.height + (2 * top)); - - System.err.printf("W: %d H: %d\n", textWidth, textHeight); + Graphics gr = getGraphics(); + FontMetrics fm = gr.getFontMetrics(); + maxDescent = fm.getMaxDescent(); + 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 + // monospace. + textHeight = fm.getMaxAscent() + maxDescent - leading; + + // Figure out the thickness of borders and use that to set the + // final size. + Insets insets = getInsets(); + left = insets.left; + top = insets.top; + + setSize(textWidth * screen.width + insets.left + insets.right, + textHeight * screen.height + insets.top + insets.bottom); + + /* + System.err.printf("W: %d H: %d MD: %d L: %d\n", textWidth, + textHeight, maxDescent, leading); + */ + } + + /** + * Update redraws the whole screen. + * + * @param gr the AWT Graphics context + */ + @Override + public void update(final Graphics gr) { + // The default update clears the area. Don't do that, instead + // just paint it directly. + paint(gr); } /** @@ -109,81 +315,120 @@ public final class AWTScreen extends Screen { * @param gr the AWT Graphics context */ @Override - public void paint(Graphics gr) { - - for (int y = 0; y < screen.height; y++) { - for (int x = 0; x < screen.width; x++) { - Cell lCell = screen.logical[x][y]; - Cell pCell = screen.physical[x][y]; - - int xPixel = x * (textWidth + 1) + left; - int yPixel = y * (textHeight + 1) + top - y; - - if (!lCell.equals(pCell)) { - // Draw the background rectangle, then the foreground - // character. - if (lCell.getBackColor().equals(jexer.bits.Color.BLACK)) { - gr.setColor(Color.black); - } else if (lCell.getBackColor().equals(jexer.bits.Color.RED)) { - gr.setColor(Color.red); - } else if (lCell.getBackColor().equals(jexer.bits.Color.BLUE)) { - gr.setColor(Color.blue); - } else if (lCell.getBackColor().equals(jexer.bits.Color.GREEN)) { - gr.setColor(Color.green); - } else if (lCell.getBackColor().equals(jexer.bits.Color.YELLOW)) { - gr.setColor(Color.yellow); - } else if (lCell.getBackColor().equals(jexer.bits.Color.CYAN)) { - gr.setColor(Color.cyan); - } else if (lCell.getBackColor().equals(jexer.bits.Color.MAGENTA)) { - gr.setColor(Color.magenta); - } else if (lCell.getBackColor().equals(jexer.bits.Color.WHITE)) { - gr.setColor(Color.white); - } - gr.fillRect(xPixel, yPixel, textWidth + 1, - textHeight + 2); - - if (lCell.getForeColor().equals(jexer.bits.Color.BLACK)) { - gr.setColor(Color.black); - } else if (lCell.getForeColor().equals(jexer.bits.Color.RED)) { - gr.setColor(Color.red); - } else if (lCell.getForeColor().equals(jexer.bits.Color.BLUE)) { - gr.setColor(Color.blue); - } else if (lCell.getForeColor().equals(jexer.bits.Color.GREEN)) { - gr.setColor(Color.green); - } else if (lCell.getForeColor().equals(jexer.bits.Color.YELLOW)) { - gr.setColor(Color.yellow); - } else if (lCell.getForeColor().equals(jexer.bits.Color.CYAN)) { - gr.setColor(Color.cyan); - } else if (lCell.getForeColor().equals(jexer.bits.Color.MAGENTA)) { - gr.setColor(Color.magenta); - } else if (lCell.getForeColor().equals(jexer.bits.Color.WHITE)) { - gr.setColor(Color.white); - } - char [] chars = new char[1]; - chars[0] = lCell.getChar(); - gr.drawChars(chars, 0, 1, xPixel, - yPixel + textHeight - 2); + public void paint(final Graphics gr) { + // Do nothing until the screen reference has been set. + if (screen == null) { + return; + } + if (screen.frame == null) { + return; + } - // Physical is always updated - physical[x][y].setTo(lCell); - } + int xCellMin = 0; + int xCellMax = screen.width; + int yCellMin = 0; + int yCellMax = screen.height; + + Rectangle bounds = gr.getClipBounds(); + if (bounds != null) { + // Only update what is in the bounds + xCellMin = screen.textColumn(bounds.x); + xCellMax = screen.textColumn(bounds.x + bounds.width) + 1; + if (xCellMax > screen.width) { + xCellMax = screen.width; + } + if (xCellMin >= xCellMax) { + xCellMin = xCellMax - 2; + } + if (xCellMin < 0) { + xCellMin = 0; + } + yCellMin = screen.textRow(bounds.y); + yCellMax = screen.textRow(bounds.y + bounds.height) + 1; + if (yCellMax > screen.height) { + yCellMax = screen.height; + } + if (yCellMin >= yCellMax) { + yCellMin = yCellMax - 2; + } + if (yCellMin < 0) { + yCellMin = 0; } } + + // 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); + */ + + for (int y = yCellMin; y < yCellMax; y++) { + for (int x = xCellMin; x < xCellMax; x++) { + + int xPixel = x * textWidth + left; + int yPixel = y * textHeight + top; + + Cell lCell = screen.logical[x][y]; + Cell pCell = screen.physical[x][y]; + + if (!lCell.equals(pCell) || reallyCleared) { + // Draw the background rectangle, then the + // foreground character. + gr.setColor(attrToBackgroundColor(lCell)); + gr.fillRect(xPixel, yPixel, textWidth, textHeight); + gr.setColor(attrToForegroundColor(lCell)); + char [] chars = new char[1]; + chars[0] = lCell.getChar(); + gr.drawChars(chars, 0, 1, xPixel, + yPixel + textHeight - maxDescent); + + // 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) + ) { + int xPixel = cursorX * textWidth + left; + int yPixel = cursorY * textHeight + top; + Cell lCell = screen.logical[cursorX][cursorY]; + gr.setColor(attrToForegroundColor(lCell)); + gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2); + } + + dirty = false; + reallyCleared = false; + } // synchronized (screen) } } /** - * The raw AWT Frame. + * The raw AWT Frame. Note package private access. */ - private AWTFrame frame; + AWTFrame frame; /** * Public constructor. */ public AWTScreen() { - frame = new AWTFrame(); - frame.screen = this; - frame.resizeToScreen(); + frame = new AWTFrame(this); + } + + /** + * Create the AWTSessionInfo. Note package private access. + * + * @return the sessionInfo + */ + AWTSessionInfo getSessionInfo() { + AWTSessionInfo sessionInfo = new AWTSessionInfo(frame, frame.textWidth, + frame.textHeight); + return sessionInfo; } /** @@ -191,7 +436,111 @@ public final class AWTScreen extends Screen { */ @Override public void flushPhysical() { - Graphics gr = frame.getGraphics(); - frame.paint(gr); + + if (reallyCleared) { + // Really refreshed, do it all + frame.repaint(); + return; + } + + // Do nothing if nothing happened. + if (!dirty) { + return; + } + + // Request a repaint, let the frame's repaint/update methods do the + // right thing. + + // Find the minimum-size damaged region. + int xMin = frame.getWidth(); + int xMax = 0; + int yMin = frame.getHeight(); + int yMax = 0; + + 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) + ) { + if (xPixel < xMin) { + xMin = xPixel; + } + if (xPixel + frame.textWidth > xMax) { + xMax = xPixel + frame.textWidth; + } + if (yPixel < yMin) { + yMin = yPixel; + } + if (yPixel + frame.textHeight > yMax) { + yMax = yPixel + frame.textHeight; + } + } + } + } + } + if (xMin + frame.textWidth >= xMax) { + xMax += frame.textWidth; + } + if (yMin + frame.textHeight >= yMax) { + yMax += frame.textHeight; + } + + // Repaint the desired area + frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin); + // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, yMin, yMax); } + + /** + * Put the cursor at (x,y). + * + * @param visible if true, the cursor should be visible + * @param x column coordinate to put the cursor on + * @param y row coordinate to put the cursor on + */ + @Override + public void putCursor(final boolean visible, final int x, final int y) { + if ((cursorVisible) + && (cursorY <= height - 1) + && (cursorX <= width - 1) + ) { + // Make the current cursor position dirty + if (physical[cursorX][cursorY].getChar() == 'Q') { + physical[cursorX][cursorY].setChar('X'); + } else { + physical[cursorX][cursorY].setChar('Q'); + } + } + + super.putCursor(visible, x, y); + } + + /** + * Convert pixel column position to text cell column position. + * + * @param x pixel column position + * @return text cell column position + */ + public int textColumn(final int x) { + return ((x - frame.left) / frame.textWidth); + } + + /** + * Convert pixel row position to text cell row position. + * + * @param y pixel row position + * @return text cell row position + */ + public int textRow(final int y) { + return ((y - frame.top) / frame.textHeight); + } + }