2 * Jexer - Java Text User Interface
4 * License: LGPLv3 or later
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
10 * Copyright (C) 2015 Kevin Lamonte
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
33 import jexer
.bits
.Cell
;
34 import jexer
.bits
.CellAttributes
;
35 import jexer
.bits
.GraphicsChars
;
38 * This class represents a text-based screen. Drawing operations write to a
41 public abstract class Screen
{
44 * Width of the visible window.
49 * Height of the visible window.
54 * Drawing offset for x.
59 * Set drawing offset for x.
61 * @param offsetX new drawing offset
63 public final void setOffsetX(final int offsetX
) {
64 this.offsetX
= offsetX
;
68 * Drawing offset for y.
73 * Set drawing offset for y.
75 * @param offsetY new drawing offset
77 public final void setOffsetY(final int offsetY
) {
78 this.offsetY
= offsetY
;
82 * Ignore anything drawn right of clipRight.
84 private int clipRight
;
87 * Get right drawing clipping boundary.
89 * @return drawing boundary
91 public final int getClipRight() {
96 * Set right drawing clipping boundary.
98 * @param clipRight new boundary
100 public final void setClipRight(final int clipRight
) {
101 this.clipRight
= clipRight
;
105 * Ignore anything drawn below clipBottom.
107 private int clipBottom
;
110 * Get bottom drawing clipping boundary.
112 * @return drawing boundary
114 public final int getClipBottom() {
119 * Set bottom drawing clipping boundary.
121 * @param clipBottom new boundary
123 public final void setClipBottom(final int clipBottom
) {
124 this.clipBottom
= clipBottom
;
128 * Ignore anything drawn left of clipLeft.
130 private int clipLeft
;
133 * Get left drawing clipping boundary.
135 * @return drawing boundary
137 public final int getClipLeft() {
142 * Set left drawing clipping boundary.
144 * @param clipLeft new boundary
146 public final void setClipLeft(final int clipLeft
) {
147 this.clipLeft
= clipLeft
;
151 * Ignore anything drawn above clipTop.
156 * Get top drawing clipping boundary.
158 * @return drawing boundary
160 public final int getClipTop() {
165 * Set top drawing clipping boundary.
167 * @param clipTop new boundary
169 public final void setClipTop(final int clipTop
) {
170 this.clipTop
= clipTop
;
174 * The physical screen last sent out on flush().
176 protected Cell
[][] physical
;
179 * The logical screen being rendered to.
181 protected Cell
[][] logical
;
184 * When true, logical != physical.
186 protected volatile boolean dirty
;
191 * @return if true, the logical screen is not in sync with the physical
194 public final boolean isDirty() {
199 * Set if the user explicitly wants to redraw everything starting with a
200 * ECMATerminal.clearAll().
202 protected boolean reallyCleared
;
205 * If true, the cursor is visible and should be placed onscreen at
206 * (cursorX, cursorY) during a call to flushPhysical().
208 protected boolean cursorVisible
;
211 * Cursor X position if visible.
213 protected int cursorX
;
216 * Cursor Y position if visible.
218 protected int cursorY
;
221 * Get the attributes at one location.
223 * @param x column coordinate. 0 is the left-most column.
224 * @param y row coordinate. 0 is the top-most row.
225 * @return attributes at (x, y)
227 public final CellAttributes
getAttrXY(final int x
, final int y
) {
228 CellAttributes attr
= new CellAttributes();
229 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
230 attr
.setTo(logical
[x
][y
]);
236 * Set the attributes at one location.
238 * @param x column coordinate. 0 is the left-most column.
239 * @param y row coordinate. 0 is the top-most row.
240 * @param attr attributes to use (bold, foreColor, backColor)
242 public final void putAttrXY(final int x
, final int y
,
243 final CellAttributes attr
) {
245 putAttrXY(x
, y
, attr
, true);
249 * Set the attributes at one location.
251 * @param x column coordinate. 0 is the left-most column.
252 * @param y row coordinate. 0 is the top-most row.
253 * @param attr attributes to use (bold, foreColor, backColor)
254 * @param clip if true, honor clipping/offset
256 public final void putAttrXY(final int x
, final int y
257 , final CellAttributes attr
, final boolean clip
) {
274 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
276 logical
[X
][Y
].setForeColor(attr
.getForeColor());
277 logical
[X
][Y
].setBackColor(attr
.getBackColor());
278 logical
[X
][Y
].setBold(attr
.isBold());
279 logical
[X
][Y
].setBlink(attr
.isBlink());
280 logical
[X
][Y
].setReverse(attr
.isReverse());
281 logical
[X
][Y
].setUnderline(attr
.isUnderline());
282 logical
[X
][Y
].setProtect(attr
.isProtect());
287 * Fill the entire screen with one character with attributes.
289 * @param ch character to draw
290 * @param attr attributes to use (bold, foreColor, backColor)
292 public final void putAll(final char ch
, final CellAttributes attr
) {
294 for (int x
= 0; x
< width
; x
++) {
295 for (int y
= 0; y
< height
; y
++) {
296 putCharXY(x
, y
, ch
, attr
);
302 * Render one character with attributes.
304 * @param x column coordinate. 0 is the left-most column.
305 * @param y row coordinate. 0 is the top-most row.
306 * @param ch character + attributes to draw
308 public final void putCharXY(final int x
, final int y
, final Cell ch
) {
309 putCharXY(x
, y
, ch
.getChar(), ch
);
313 * Render one character with attributes.
315 * @param x column coordinate. 0 is the left-most column.
316 * @param y row coordinate. 0 is the top-most row.
317 * @param ch character to draw
318 * @param attr attributes to use (bold, foreColor, backColor)
320 public final void putCharXY(final int x
, final int y
, final char ch
,
321 final CellAttributes attr
) {
334 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
336 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
339 // Do not put control characters on the display
343 logical
[X
][Y
].setChar(ch
);
344 logical
[X
][Y
].setForeColor(attr
.getForeColor());
345 logical
[X
][Y
].setBackColor(attr
.getBackColor());
346 logical
[X
][Y
].setBold(attr
.isBold());
347 logical
[X
][Y
].setBlink(attr
.isBlink());
348 logical
[X
][Y
].setReverse(attr
.isReverse());
349 logical
[X
][Y
].setUnderline(attr
.isUnderline());
350 logical
[X
][Y
].setProtect(attr
.isProtect());
355 * Render one character without changing the underlying attributes.
357 * @param x column coordinate. 0 is the left-most column.
358 * @param y row coordinate. 0 is the top-most row.
359 * @param ch character to draw
361 public final void putCharXY(final int x
, final int y
, final char ch
) {
374 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
376 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
378 logical
[X
][Y
].setChar(ch
);
383 * Render a string. Does not wrap if the string exceeds the line.
385 * @param x column coordinate. 0 is the left-most column.
386 * @param y row coordinate. 0 is the top-most row.
387 * @param str string to draw
388 * @param attr attributes to use (bold, foreColor, backColor)
390 public final void putStringXY(final int x
, final int y
, final String str
,
391 final CellAttributes attr
) {
394 for (int j
= 0; j
< str
.length(); j
++) {
395 char ch
= str
.charAt(j
);
396 putCharXY(i
, y
, ch
, attr
);
405 * Render a string without changing the underlying attribute. Does not
406 * wrap if the string exceeds the line.
408 * @param x column coordinate. 0 is the left-most column.
409 * @param y row coordinate. 0 is the top-most row.
410 * @param str string to draw
412 public final void putStringXY(final int x
, final int y
, final String str
) {
415 for (int j
= 0; j
< str
.length(); j
++) {
416 char ch
= str
.charAt(j
);
426 * Draw a vertical line from (x, y) to (x, y + n).
428 * @param x column coordinate. 0 is the left-most column.
429 * @param y row coordinate. 0 is the top-most row.
430 * @param n number of characters to draw
431 * @param ch character to draw
432 * @param attr attributes to use (bold, foreColor, backColor)
434 public final void vLineXY(final int x
, final int y
, final int n
,
435 final char ch
, final CellAttributes attr
) {
437 for (int i
= y
; i
< y
+ n
; i
++) {
438 putCharXY(x
, i
, ch
, attr
);
443 * Draw a horizontal line from (x, y) to (x + n, y).
445 * @param x column coordinate. 0 is the left-most column.
446 * @param y row coordinate. 0 is the top-most row.
447 * @param n number of characters to draw
448 * @param ch character to draw
449 * @param attr attributes to use (bold, foreColor, backColor)
451 public final void hLineXY(final int x
, final int y
, final int n
,
452 final char ch
, final CellAttributes attr
) {
454 for (int i
= x
; i
< x
+ n
; i
++) {
455 putCharXY(i
, y
, ch
, attr
);
460 * Reallocate screen buffers.
462 * @param width new width
463 * @param height new height
465 private synchronized void reallocate(final int width
, final int height
) {
466 if (logical
!= null) {
467 for (int row
= 0; row
< this.height
; row
++) {
468 for (int col
= 0; col
< this.width
; col
++) {
469 logical
[col
][row
] = null;
474 logical
= new Cell
[width
][height
];
475 if (physical
!= null) {
476 for (int row
= 0; row
< this.height
; row
++) {
477 for (int col
= 0; col
< this.width
; col
++) {
478 physical
[col
][row
] = null;
483 physical
= new Cell
[width
][height
];
485 for (int row
= 0; row
< height
; row
++) {
486 for (int col
= 0; col
< width
; col
++) {
487 physical
[col
][row
] = new Cell();
488 logical
[col
][row
] = new Cell();
493 this.height
= height
;
500 reallyCleared
= true;
505 * Change the width. Everything on-screen will be destroyed and must be
508 * @param width new screen width
510 public final synchronized void setWidth(final int width
) {
511 reallocate(width
, this.height
);
515 * Change the height. Everything on-screen will be destroyed and must be
518 * @param height new screen height
520 public final synchronized void setHeight(final int height
) {
521 reallocate(this.width
, height
);
525 * Change the width and height. Everything on-screen will be destroyed
526 * and must be redrawn.
528 * @param width new screen width
529 * @param height new screen height
531 public final void setDimensions(final int width
, final int height
) {
532 reallocate(width
, height
);
538 * @return current screen height
540 public final synchronized int getHeight() {
547 * @return current screen width
549 public final synchronized int getWidth() {
554 * Public constructor. Sets everything to not-bold, white-on-black.
563 reallocate(width
, height
);
567 * Reset screen to not-bold, white-on-black. Also flushes the offset and
570 public final synchronized void reset() {
572 for (int row
= 0; row
< height
; row
++) {
573 for (int col
= 0; col
< width
; col
++) {
574 logical
[col
][row
].reset();
581 * Flush the offset and clip variables.
583 public final void resetClipping() {
593 * Clear the logical screen.
595 public final void clear() {
600 * Clear the physical screen.
602 public final void clearPhysical() {
604 for (int row
= 0; row
< height
; row
++) {
605 for (int col
= 0; col
< width
; col
++) {
606 physical
[col
][row
].reset();
612 * Draw a box with a border and empty background.
614 * @param left left column of box. 0 is the left-most row.
615 * @param top top row of the box. 0 is the top-most row.
616 * @param right right column of box
617 * @param bottom bottom row of the box
618 * @param border attributes to use for the border
619 * @param background attributes to use for the background
621 public final void drawBox(final int left
, final int top
,
622 final int right
, final int bottom
,
623 final CellAttributes border
, final CellAttributes background
) {
625 drawBox(left
, top
, right
, bottom
, border
, background
, 1, false);
629 * Draw a box with a border and empty background.
631 * @param left left column of box. 0 is the left-most row.
632 * @param top top row of the box. 0 is the top-most row.
633 * @param right right column of box
634 * @param bottom bottom row of the box
635 * @param border attributes to use for the border
636 * @param background attributes to use for the background
637 * @param borderType if 1, draw a single-line border; if 2, draw a
638 * double-line border; if 3, draw double-line top/bottom edges and
639 * single-line left/right edges (like Qmodem)
640 * @param shadow if true, draw a "shadow" on the box
642 public final void drawBox(final int left
, final int top
,
643 final int right
, final int bottom
,
644 final CellAttributes border
, final CellAttributes background
,
645 final int borderType
, final boolean shadow
) {
647 int boxWidth
= right
- left
;
648 int boxHeight
= bottom
- top
;
657 switch (borderType
) {
659 cTopLeft
= GraphicsChars
.ULCORNER
;
660 cTopRight
= GraphicsChars
.URCORNER
;
661 cBottomLeft
= GraphicsChars
.LLCORNER
;
662 cBottomRight
= GraphicsChars
.LRCORNER
;
663 cHSide
= GraphicsChars
.SINGLE_BAR
;
664 cVSide
= GraphicsChars
.WINDOW_SIDE
;
668 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP_DOUBLE
;
669 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP_DOUBLE
;
670 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM_DOUBLE
;
671 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM_DOUBLE
;
672 cHSide
= GraphicsChars
.DOUBLE_BAR
;
673 cVSide
= GraphicsChars
.WINDOW_SIDE_DOUBLE
;
677 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP
;
678 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP
;
679 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM
;
680 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM
;
681 cHSide
= GraphicsChars
.WINDOW_TOP
;
682 cVSide
= GraphicsChars
.WINDOW_SIDE
;
685 throw new IllegalArgumentException("Invalid border type: "
689 // Place the corner characters
690 putCharXY(left
, top
, cTopLeft
, border
);
691 putCharXY(left
+ boxWidth
- 1, top
, cTopRight
, border
);
692 putCharXY(left
, top
+ boxHeight
- 1, cBottomLeft
, border
);
693 putCharXY(left
+ boxWidth
- 1, top
+ boxHeight
- 1, cBottomRight
,
696 // Draw the box lines
697 hLineXY(left
+ 1, top
, boxWidth
- 2, cHSide
, border
);
698 vLineXY(left
, top
+ 1, boxHeight
- 2, cVSide
, border
);
699 hLineXY(left
+ 1, top
+ boxHeight
- 1, boxWidth
- 2, cHSide
, border
);
700 vLineXY(left
+ boxWidth
- 1, top
+ 1, boxHeight
- 2, cVSide
, border
);
702 // Fill in the interior background
703 for (int i
= 1; i
< boxHeight
- 1; i
++) {
704 hLineXY(1 + left
, i
+ top
, boxWidth
- 2, ' ', background
);
709 drawBoxShadow(left
, top
, right
, bottom
);
716 * @param left left column of box. 0 is the left-most row.
717 * @param top top row of the box. 0 is the top-most row.
718 * @param right right column of box
719 * @param bottom bottom row of the box
721 public final void drawBoxShadow(final int left
, final int top
,
722 final int right
, final int bottom
) {
726 int boxWidth
= right
- left
;
727 int boxHeight
= bottom
- top
;
728 CellAttributes shadowAttr
= new CellAttributes();
730 // Shadows do not honor clipping but they DO honor offset.
731 int oldClipRight
= clipRight
;
732 int oldClipBottom
= clipBottom
;
734 clipRight = boxWidth + 2;
735 clipBottom = boxHeight + 1;
740 for (int i
= 0; i
< boxHeight
; i
++) {
741 putAttrXY(boxLeft
+ boxWidth
, boxTop
+ 1 + i
, shadowAttr
);
742 putAttrXY(boxLeft
+ boxWidth
+ 1, boxTop
+ 1 + i
, shadowAttr
);
744 for (int i
= 0; i
< boxWidth
; i
++) {
745 putAttrXY(boxLeft
+ 2 + i
, boxTop
+ boxHeight
, shadowAttr
);
747 clipRight
= oldClipRight
;
748 clipBottom
= oldClipBottom
;
752 * Subclasses must provide an implementation to push the logical screen
753 * to the physical device.
755 public abstract void flushPhysical();
758 * Put the cursor at (x,y).
760 * @param visible if true, the cursor should be visible
761 * @param x column coordinate to put the cursor on
762 * @param y row coordinate to put the cursor on
764 public void putCursor(final boolean visible
, final int x
, final int y
) {
766 cursorVisible
= visible
;
774 public final void hideCursor() {
775 cursorVisible
= false;