Prep for 2019 release
[fanfix.git] / src / jexer / backend / LogicalScreen.java
index bfe1c7226700df9993d0dd25010b13ebe9c7e54f..b7648313d0d15513584dddf4659f42a841e8f47e 100644 (file)
@@ -3,7 +3,7 @@
  *
  * The MIT License (MIT)
  *
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -37,6 +37,10 @@ import jexer.bits.GraphicsChars;
  */
 public class LogicalScreen implements Screen {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Width of the visible window.
      */
@@ -52,6 +56,84 @@ public class LogicalScreen implements Screen {
      */
     private int offsetX;
 
+    /**
+     * Drawing offset for y.
+     */
+    private int offsetY;
+
+    /**
+     * Ignore anything drawn right of clipRight.
+     */
+    private int clipRight;
+
+    /**
+     * Ignore anything drawn below clipBottom.
+     */
+    private int clipBottom;
+
+    /**
+     * Ignore anything drawn left of clipLeft.
+     */
+    private int clipLeft;
+
+    /**
+     * Ignore anything drawn above clipTop.
+     */
+    private int clipTop;
+
+    /**
+     * The physical screen last sent out on flush().
+     */
+    protected Cell [][] physical;
+
+    /**
+     * The logical screen being rendered to.
+     */
+    protected Cell [][] logical;
+
+    /**
+     * Set if the user explicitly wants to redraw everything starting with a
+     * ECMATerminal.clearAll().
+     */
+    protected boolean reallyCleared;
+
+    /**
+     * If true, the cursor is visible and should be placed onscreen at
+     * (cursorX, cursorY) during a call to flushPhysical().
+     */
+    protected boolean cursorVisible;
+
+    /**
+     * Cursor X position if visible.
+     */
+    protected int cursorX;
+
+    /**
+     * Cursor Y position if visible.
+     */
+    protected int cursorY;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.  Sets everything to not-bold, white-on-black.
+     */
+    protected LogicalScreen() {
+        offsetX  = 0;
+        offsetY  = 0;
+        width    = 80;
+        height   = 24;
+        logical  = null;
+        physical = null;
+        reallocate(width, height);
+    }
+
+    // ------------------------------------------------------------------------
+    // Screen -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Set drawing offset for x.
      *
@@ -61,11 +143,6 @@ public class LogicalScreen implements Screen {
         this.offsetX = offsetX;
     }
 
-    /**
-     * Drawing offset for y.
-     */
-    private int offsetY;
-
     /**
      * Set drawing offset for y.
      *
@@ -75,11 +152,6 @@ public class LogicalScreen implements Screen {
         this.offsetY = offsetY;
     }
 
-    /**
-     * Ignore anything drawn right of clipRight.
-     */
-    private int clipRight;
-
     /**
      * Get right drawing clipping boundary.
      *
@@ -98,11 +170,6 @@ public class LogicalScreen implements Screen {
         this.clipRight = clipRight;
     }
 
-    /**
-     * Ignore anything drawn below clipBottom.
-     */
-    private int clipBottom;
-
     /**
      * Get bottom drawing clipping boundary.
      *
@@ -121,11 +188,6 @@ public class LogicalScreen implements Screen {
         this.clipBottom = clipBottom;
     }
 
-    /**
-     * Ignore anything drawn left of clipLeft.
-     */
-    private int clipLeft;
-
     /**
      * Get left drawing clipping boundary.
      *
@@ -144,11 +206,6 @@ public class LogicalScreen implements Screen {
         this.clipLeft = clipLeft;
     }
 
-    /**
-     * Ignore anything drawn above clipTop.
-     */
-    private int clipTop;
-
     /**
      * Get top drawing clipping boundary.
      *
@@ -167,21 +224,6 @@ public class LogicalScreen implements Screen {
         this.clipTop = clipTop;
     }
 
-    /**
-     * The physical screen last sent out on flush().
-     */
-    protected Cell [][] physical;
-
-    /**
-     * The logical screen being rendered to.
-     */
-    protected Cell [][] logical;
-
-    /**
-     * When true, logical != physical.
-     */
-    protected volatile boolean dirty;
-
     /**
      * Get dirty flag.
      *
@@ -189,30 +231,21 @@ public class LogicalScreen implements Screen {
      * screen
      */
     public final boolean isDirty() {
-        return dirty;
-    }
-
-    /**
-     * Set if the user explicitly wants to redraw everything starting with a
-     * ECMATerminal.clearAll().
-     */
-    protected boolean reallyCleared;
-
-    /**
-     * If true, the cursor is visible and should be placed onscreen at
-     * (cursorX, cursorY) during a call to flushPhysical().
-     */
-    protected boolean cursorVisible;
-
-    /**
-     * Cursor X position if visible.
-     */
-    protected int cursorX;
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                if (!logical[x][y].equals(physical[x][y])) {
+                    return true;
+                }
+                if (logical[x][y].isBlink()) {
+                    // Blinking screens are always dirty.  There is
+                    // opportunity for a Netscape blink tag joke here...
+                    return true;
+                }
+            }
+        }
 
-    /**
-     * Cursor Y position if visible.
-     */
-    protected int cursorY;
+        return false;
+    }
 
     /**
      * Get the attributes at one location.
@@ -284,14 +317,14 @@ public class LogicalScreen implements Screen {
         }
 
         if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
-            dirty = true;
-            logical[X][Y].setForeColor(attr.getForeColor());
-            logical[X][Y].setBackColor(attr.getBackColor());
-            logical[X][Y].setBold(attr.isBold());
-            logical[X][Y].setBlink(attr.isBlink());
-            logical[X][Y].setReverse(attr.isReverse());
-            logical[X][Y].setUnderline(attr.isUnderline());
-            logical[X][Y].setProtect(attr.isProtect());
+            logical[X][Y].setTo(attr);
+
+            // If this happens to be the cursor position, make the position
+            // dirty.
+            if ((cursorX == X) && (cursorY == Y)) {
+                physical[cursorX][cursorY].unset();
+                unsetImageRow(cursorY);
+            }
         }
     }
 
@@ -318,7 +351,35 @@ public class LogicalScreen implements Screen {
      * @param ch character + attributes to draw
      */
     public final void putCharXY(final int x, final int y, final Cell ch) {
-        putCharXY(x, y, ch.getChar(), ch);
+        if ((x < clipLeft)
+            || (x >= clipRight)
+            || (y < clipTop)
+            || (y >= clipBottom)
+        ) {
+            return;
+        }
+
+        int X = x + offsetX;
+        int Y = y + offsetY;
+
+        // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
+
+        if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
+
+            // Do not put control characters on the display
+            if (!ch.isImage()) {
+                assert (ch.getChar() >= 0x20);
+                assert (ch.getChar() != 0x7F);
+            }
+            logical[X][Y].setTo(ch);
+
+            // If this happens to be the cursor position, make the position
+            // dirty.
+            if ((cursorX == X) && (cursorY == Y)) {
+                physical[cursorX][cursorY].unset();
+                unsetImageRow(cursorY);
+            }
+        }
     }
 
     /**
@@ -346,20 +407,20 @@ public class LogicalScreen implements Screen {
         // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
 
         if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
-            dirty = true;
 
             // Do not put control characters on the display
             assert (ch >= 0x20);
             assert (ch != 0x7F);
 
+            logical[X][Y].setTo(attr);
             logical[X][Y].setChar(ch);
-            logical[X][Y].setForeColor(attr.getForeColor());
-            logical[X][Y].setBackColor(attr.getBackColor());
-            logical[X][Y].setBold(attr.isBold());
-            logical[X][Y].setBlink(attr.isBlink());
-            logical[X][Y].setReverse(attr.isReverse());
-            logical[X][Y].setUnderline(attr.isUnderline());
-            logical[X][Y].setProtect(attr.isProtect());
+
+            // If this happens to be the cursor position, make the position
+            // dirty.
+            if ((cursorX == X) && (cursorY == Y)) {
+                physical[cursorX][cursorY].unset();
+                unsetImageRow(cursorY);
+            }
         }
     }
 
@@ -386,8 +447,14 @@ public class LogicalScreen implements Screen {
         // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
 
         if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
-            dirty = true;
             logical[X][Y].setChar(ch);
+
+            // If this happens to be the cursor position, make the position
+            // dirty.
+            if ((cursorX == X) && (cursorY == Y)) {
+                physical[cursorX][cursorY].unset();
+                unsetImageRow(cursorY);
+            }
         }
     }
 
@@ -468,51 +535,6 @@ public class LogicalScreen implements Screen {
         }
     }
 
-    /**
-     * Reallocate screen buffers.
-     *
-     * @param width new width
-     * @param height new height
-     */
-    private synchronized void reallocate(final int width, final int height) {
-        if (logical != null) {
-            for (int row = 0; row < this.height; row++) {
-                for (int col = 0; col < this.width; col++) {
-                    logical[col][row] = null;
-                }
-            }
-            logical = null;
-        }
-        logical = new Cell[width][height];
-        if (physical != null) {
-            for (int row = 0; row < this.height; row++) {
-                for (int col = 0; col < this.width; col++) {
-                    physical[col][row] = null;
-                }
-            }
-            physical = null;
-        }
-        physical = new Cell[width][height];
-
-        for (int row = 0; row < height; row++) {
-            for (int col = 0; col < width; col++) {
-                physical[col][row] = new Cell();
-                logical[col][row] = new Cell();
-            }
-        }
-
-        this.width = width;
-        this.height = height;
-
-        clipLeft = 0;
-        clipTop = 0;
-        clipRight = width;
-        clipBottom = height;
-
-        reallyCleared = true;
-        dirty = true;
-    }
-
     /**
      * Change the width.  Everything on-screen will be destroyed and must be
      * redrawn.
@@ -562,25 +584,11 @@ public class LogicalScreen implements Screen {
         return this.width;
     }
 
-    /**
-     * Public constructor.  Sets everything to not-bold, white-on-black.
-     */
-    protected LogicalScreen() {
-        offsetX  = 0;
-        offsetY  = 0;
-        width    = 80;
-        height   = 24;
-        logical  = null;
-        physical = null;
-        reallocate(width, height);
-    }
-
     /**
      * Reset screen to not-bold, white-on-black.  Also flushes the offset and
      * clip variables.
      */
     public final synchronized void reset() {
-        dirty = true;
         for (int row = 0; row < height; row++) {
             for (int col = 0; col < width; col++) {
                 logical[col][row].reset();
@@ -608,22 +616,10 @@ public class LogicalScreen implements Screen {
         reset();
     }
 
-    /**
-     * Clear the physical screen.
-     */
-    public final void clearPhysical() {
-        dirty = true;
-        for (int row = 0; row < height; row++) {
-            for (int col = 0; col < width; col++) {
-                physical[col][row].reset();
-            }
-        }
-    }
-
     /**
      * Draw a box with a border and empty background.
      *
-     * @param left left column of box.  0 is the left-most row.
+     * @param left left column of box.  0 is the left-most column.
      * @param top top row of the box.  0 is the top-most row.
      * @param right right column of box
      * @param bottom bottom row of the box
@@ -640,7 +636,7 @@ public class LogicalScreen implements Screen {
     /**
      * Draw a box with a border and empty background.
      *
-     * @param left left column of box.  0 is the left-most row.
+     * @param left left column of box.  0 is the left-most column.
      * @param top top row of the box.  0 is the top-most row.
      * @param right right column of box
      * @param bottom bottom row of the box
@@ -725,7 +721,7 @@ public class LogicalScreen implements Screen {
     /**
      * Draw a box shadow.
      *
-     * @param left left column of box.  0 is the left-most row.
+     * @param left left column of box.  0 is the left-most column.
      * @param top top row of the box.  0 is the top-most row.
      * @param right right column of box
      * @param bottom bottom row of the box
@@ -742,12 +738,10 @@ public class LogicalScreen implements Screen {
         // Shadows do not honor clipping but they DO honor offset.
         int oldClipRight = clipRight;
         int oldClipBottom = clipBottom;
-        /*
-        clipRight = boxWidth + 2;
-        clipBottom = boxHeight + 1;
-        */
-        clipRight = width;
-        clipBottom = height;
+        // When offsetX or offsetY go negative, we need to increase the clip
+        // bounds.
+        clipRight = width - offsetX;
+        clipBottom = height - offsetY;
 
         for (int i = 0; i < boxHeight; i++) {
             putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
@@ -773,6 +767,15 @@ public class LogicalScreen implements Screen {
      * @param y row coordinate to put the cursor on
      */
     public void putCursor(final boolean visible, final int x, final int y) {
+        if ((cursorY >= 0)
+            && (cursorX >= 0)
+            && (cursorY <= height - 1)
+            && (cursorX <= width - 1)
+        ) {
+            // Make the current cursor position dirty
+            physical[cursorX][cursorY].unset();
+            unsetImageRow(cursorY);
+        }
 
         cursorVisible = visible;
         cursorX = x;
@@ -820,4 +823,77 @@ public class LogicalScreen implements Screen {
      */
     public void setTitle(final String title) {}
 
+    // ------------------------------------------------------------------------
+    // LogicalScreen ----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Reallocate screen buffers.
+     *
+     * @param width new width
+     * @param height new height
+     */
+    private synchronized void reallocate(final int width, final int height) {
+        if (logical != null) {
+            for (int row = 0; row < this.height; row++) {
+                for (int col = 0; col < this.width; col++) {
+                    logical[col][row] = null;
+                }
+            }
+            logical = null;
+        }
+        logical = new Cell[width][height];
+        if (physical != null) {
+            for (int row = 0; row < this.height; row++) {
+                for (int col = 0; col < this.width; col++) {
+                    physical[col][row] = null;
+                }
+            }
+            physical = null;
+        }
+        physical = new Cell[width][height];
+
+        for (int row = 0; row < height; row++) {
+            for (int col = 0; col < width; col++) {
+                physical[col][row] = new Cell();
+                logical[col][row] = new Cell();
+            }
+        }
+
+        this.width = width;
+        this.height = height;
+
+        clipLeft = 0;
+        clipTop = 0;
+        clipRight = width;
+        clipBottom = height;
+
+        reallyCleared = true;
+    }
+
+    /**
+     * Clear the physical screen.
+     */
+    public final void clearPhysical() {
+        for (int row = 0; row < height; row++) {
+            for (int col = 0; col < width; col++) {
+                physical[col][row].unset();
+            }
+        }
+    }
+
+    /**
+     * Unset every image cell on one row of the physical screen, forcing
+     * images on that row to be redrawn.
+     *
+     * @param y row coordinate.  0 is the top-most row.
+     */
+    public final void unsetImageRow(final int y) {
+        for (int x = 0; x < width; x++) {
+            if (logical[x][y].isImage()) {
+                physical[x][y].unset();
+            }
+        }
+    }
+
 }