2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2017 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.backend
;
31 import jexer
.bits
.Cell
;
32 import jexer
.bits
.CellAttributes
;
33 import jexer
.bits
.GraphicsChars
;
36 * A logical screen composed of a 2D array of Cells.
38 public class LogicalScreen
implements Screen
{
41 * Width of the visible window.
46 * Height of the visible window.
51 * Drawing offset for x.
56 * Set drawing offset for x.
58 * @param offsetX new drawing offset
60 public final void setOffsetX(final int offsetX
) {
61 this.offsetX
= offsetX
;
65 * Drawing offset for y.
70 * Set drawing offset for y.
72 * @param offsetY new drawing offset
74 public final void setOffsetY(final int offsetY
) {
75 this.offsetY
= offsetY
;
79 * Ignore anything drawn right of clipRight.
81 private int clipRight
;
84 * Get right drawing clipping boundary.
86 * @return drawing boundary
88 public final int getClipRight() {
93 * Set right drawing clipping boundary.
95 * @param clipRight new boundary
97 public final void setClipRight(final int clipRight
) {
98 this.clipRight
= clipRight
;
102 * Ignore anything drawn below clipBottom.
104 private int clipBottom
;
107 * Get bottom drawing clipping boundary.
109 * @return drawing boundary
111 public final int getClipBottom() {
116 * Set bottom drawing clipping boundary.
118 * @param clipBottom new boundary
120 public final void setClipBottom(final int clipBottom
) {
121 this.clipBottom
= clipBottom
;
125 * Ignore anything drawn left of clipLeft.
127 private int clipLeft
;
130 * Get left drawing clipping boundary.
132 * @return drawing boundary
134 public final int getClipLeft() {
139 * Set left drawing clipping boundary.
141 * @param clipLeft new boundary
143 public final void setClipLeft(final int clipLeft
) {
144 this.clipLeft
= clipLeft
;
148 * Ignore anything drawn above clipTop.
153 * Get top drawing clipping boundary.
155 * @return drawing boundary
157 public final int getClipTop() {
162 * Set top drawing clipping boundary.
164 * @param clipTop new boundary
166 public final void setClipTop(final int clipTop
) {
167 this.clipTop
= clipTop
;
171 * The physical screen last sent out on flush().
173 protected Cell
[][] physical
;
176 * The logical screen being rendered to.
178 protected Cell
[][] logical
;
181 * When true, logical != physical.
183 protected volatile boolean dirty
;
188 * @return if true, the logical screen is not in sync with the physical
191 public final boolean isDirty() {
196 * Set if the user explicitly wants to redraw everything starting with a
197 * ECMATerminal.clearAll().
199 protected boolean reallyCleared
;
202 * If true, the cursor is visible and should be placed onscreen at
203 * (cursorX, cursorY) during a call to flushPhysical().
205 protected boolean cursorVisible
;
208 * Cursor X position if visible.
210 protected int cursorX
;
213 * Cursor Y position if visible.
215 protected int cursorY
;
218 * Get the attributes at one location.
220 * @param x column coordinate. 0 is the left-most column.
221 * @param y row coordinate. 0 is the top-most row.
222 * @return attributes at (x, y)
224 public final CellAttributes
getAttrXY(final int x
, final int y
) {
225 CellAttributes attr
= new CellAttributes();
226 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
227 attr
.setTo(logical
[x
][y
]);
233 * Get the cell at one location.
235 * @param x column coordinate. 0 is the left-most column.
236 * @param y row coordinate. 0 is the top-most row.
237 * @return the character + attributes
239 public Cell
getCharXY(final int x
, final int y
) {
240 Cell cell
= new Cell();
241 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
242 cell
.setTo(logical
[x
][y
]);
248 * Set the attributes at one location.
250 * @param x column coordinate. 0 is the left-most column.
251 * @param y row coordinate. 0 is the top-most row.
252 * @param attr attributes to use (bold, foreColor, backColor)
254 public final void putAttrXY(final int x
, final int y
,
255 final CellAttributes attr
) {
257 putAttrXY(x
, y
, attr
, true);
261 * Set the attributes at one location.
263 * @param x column coordinate. 0 is the left-most column.
264 * @param y row coordinate. 0 is the top-most row.
265 * @param attr attributes to use (bold, foreColor, backColor)
266 * @param clip if true, honor clipping/offset
268 public final void putAttrXY(final int x
, final int y
,
269 final CellAttributes attr
, final boolean clip
) {
286 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
288 logical
[X
][Y
].setForeColor(attr
.getForeColor());
289 logical
[X
][Y
].setBackColor(attr
.getBackColor());
290 logical
[X
][Y
].setBold(attr
.isBold());
291 logical
[X
][Y
].setBlink(attr
.isBlink());
292 logical
[X
][Y
].setReverse(attr
.isReverse());
293 logical
[X
][Y
].setUnderline(attr
.isUnderline());
294 logical
[X
][Y
].setProtect(attr
.isProtect());
299 * Fill the entire screen with one character with attributes.
301 * @param ch character to draw
302 * @param attr attributes to use (bold, foreColor, backColor)
304 public final void putAll(final char ch
, final CellAttributes attr
) {
306 for (int x
= 0; x
< width
; x
++) {
307 for (int y
= 0; y
< height
; y
++) {
308 putCharXY(x
, y
, ch
, attr
);
314 * Render one character with attributes.
316 * @param x column coordinate. 0 is the left-most column.
317 * @param y row coordinate. 0 is the top-most row.
318 * @param ch character + attributes to draw
320 public final void putCharXY(final int x
, final int y
, final Cell ch
) {
321 putCharXY(x
, y
, ch
.getChar(), ch
);
325 * Render one character with attributes.
327 * @param x column coordinate. 0 is the left-most column.
328 * @param y row coordinate. 0 is the top-most row.
329 * @param ch character to draw
330 * @param attr attributes to use (bold, foreColor, backColor)
332 public final void putCharXY(final int x
, final int y
, final char ch
,
333 final CellAttributes attr
) {
346 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
348 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
351 // Do not put control characters on the display
355 logical
[X
][Y
].setChar(ch
);
356 logical
[X
][Y
].setForeColor(attr
.getForeColor());
357 logical
[X
][Y
].setBackColor(attr
.getBackColor());
358 logical
[X
][Y
].setBold(attr
.isBold());
359 logical
[X
][Y
].setBlink(attr
.isBlink());
360 logical
[X
][Y
].setReverse(attr
.isReverse());
361 logical
[X
][Y
].setUnderline(attr
.isUnderline());
362 logical
[X
][Y
].setProtect(attr
.isProtect());
367 * Render one character without changing the underlying attributes.
369 * @param x column coordinate. 0 is the left-most column.
370 * @param y row coordinate. 0 is the top-most row.
371 * @param ch character to draw
373 public final void putCharXY(final int x
, final int y
, final char ch
) {
386 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
388 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
390 logical
[X
][Y
].setChar(ch
);
395 * Render a string. Does not wrap if the string exceeds the line.
397 * @param x column coordinate. 0 is the left-most column.
398 * @param y row coordinate. 0 is the top-most row.
399 * @param str string to draw
400 * @param attr attributes to use (bold, foreColor, backColor)
402 public final void putStringXY(final int x
, final int y
, final String str
,
403 final CellAttributes attr
) {
406 for (int j
= 0; j
< str
.length(); j
++) {
407 char ch
= str
.charAt(j
);
408 putCharXY(i
, y
, ch
, attr
);
417 * Render a string without changing the underlying attribute. Does not
418 * wrap if the string exceeds the line.
420 * @param x column coordinate. 0 is the left-most column.
421 * @param y row coordinate. 0 is the top-most row.
422 * @param str string to draw
424 public final void putStringXY(final int x
, final int y
, final String str
) {
427 for (int j
= 0; j
< str
.length(); j
++) {
428 char ch
= str
.charAt(j
);
438 * Draw a vertical line from (x, y) to (x, y + n).
440 * @param x column coordinate. 0 is the left-most column.
441 * @param y row coordinate. 0 is the top-most row.
442 * @param n number of characters to draw
443 * @param ch character to draw
444 * @param attr attributes to use (bold, foreColor, backColor)
446 public final void vLineXY(final int x
, final int y
, final int n
,
447 final char ch
, final CellAttributes attr
) {
449 for (int i
= y
; i
< y
+ n
; i
++) {
450 putCharXY(x
, i
, ch
, attr
);
455 * Draw a horizontal line from (x, y) to (x + n, y).
457 * @param x column coordinate. 0 is the left-most column.
458 * @param y row coordinate. 0 is the top-most row.
459 * @param n number of characters to draw
460 * @param ch character to draw
461 * @param attr attributes to use (bold, foreColor, backColor)
463 public final void hLineXY(final int x
, final int y
, final int n
,
464 final char ch
, final CellAttributes attr
) {
466 for (int i
= x
; i
< x
+ n
; i
++) {
467 putCharXY(i
, y
, ch
, attr
);
472 * Reallocate screen buffers.
474 * @param width new width
475 * @param height new height
477 private synchronized void reallocate(final int width
, final int height
) {
478 if (logical
!= null) {
479 for (int row
= 0; row
< this.height
; row
++) {
480 for (int col
= 0; col
< this.width
; col
++) {
481 logical
[col
][row
] = null;
486 logical
= new Cell
[width
][height
];
487 if (physical
!= null) {
488 for (int row
= 0; row
< this.height
; row
++) {
489 for (int col
= 0; col
< this.width
; col
++) {
490 physical
[col
][row
] = null;
495 physical
= new Cell
[width
][height
];
497 for (int row
= 0; row
< height
; row
++) {
498 for (int col
= 0; col
< width
; col
++) {
499 physical
[col
][row
] = new Cell();
500 logical
[col
][row
] = new Cell();
505 this.height
= height
;
512 reallyCleared
= true;
517 * Change the width. Everything on-screen will be destroyed and must be
520 * @param width new screen width
522 public final synchronized void setWidth(final int width
) {
523 reallocate(width
, this.height
);
527 * Change the height. Everything on-screen will be destroyed and must be
530 * @param height new screen height
532 public final synchronized void setHeight(final int height
) {
533 reallocate(this.width
, height
);
537 * Change the width and height. Everything on-screen will be destroyed
538 * and must be redrawn.
540 * @param width new screen width
541 * @param height new screen height
543 public final void setDimensions(final int width
, final int height
) {
544 reallocate(width
, height
);
550 * @return current screen height
552 public final synchronized int getHeight() {
559 * @return current screen width
561 public final synchronized int getWidth() {
566 * Public constructor. Sets everything to not-bold, white-on-black.
568 protected LogicalScreen() {
575 reallocate(width
, height
);
579 * Reset screen to not-bold, white-on-black. Also flushes the offset and
582 public final synchronized void reset() {
584 for (int row
= 0; row
< height
; row
++) {
585 for (int col
= 0; col
< width
; col
++) {
586 logical
[col
][row
].reset();
593 * Flush the offset and clip variables.
595 public final void resetClipping() {
605 * Clear the logical screen.
607 public final void clear() {
612 * Clear the physical screen.
614 public final void clearPhysical() {
616 for (int row
= 0; row
< height
; row
++) {
617 for (int col
= 0; col
< width
; col
++) {
618 physical
[col
][row
].reset();
624 * Draw a box with a border and empty background.
626 * @param left left column of box. 0 is the left-most row.
627 * @param top top row of the box. 0 is the top-most row.
628 * @param right right column of box
629 * @param bottom bottom row of the box
630 * @param border attributes to use for the border
631 * @param background attributes to use for the background
633 public final void drawBox(final int left
, final int top
,
634 final int right
, final int bottom
,
635 final CellAttributes border
, final CellAttributes background
) {
637 drawBox(left
, top
, right
, bottom
, border
, background
, 1, false);
641 * Draw a box with a border and empty background.
643 * @param left left column of box. 0 is the left-most row.
644 * @param top top row of the box. 0 is the top-most row.
645 * @param right right column of box
646 * @param bottom bottom row of the box
647 * @param border attributes to use for the border
648 * @param background attributes to use for the background
649 * @param borderType if 1, draw a single-line border; if 2, draw a
650 * double-line border; if 3, draw double-line top/bottom edges and
651 * single-line left/right edges (like Qmodem)
652 * @param shadow if true, draw a "shadow" on the box
654 public final void drawBox(final int left
, final int top
,
655 final int right
, final int bottom
,
656 final CellAttributes border
, final CellAttributes background
,
657 final int borderType
, final boolean shadow
) {
659 int boxWidth
= right
- left
;
660 int boxHeight
= bottom
- top
;
669 switch (borderType
) {
671 cTopLeft
= GraphicsChars
.ULCORNER
;
672 cTopRight
= GraphicsChars
.URCORNER
;
673 cBottomLeft
= GraphicsChars
.LLCORNER
;
674 cBottomRight
= GraphicsChars
.LRCORNER
;
675 cHSide
= GraphicsChars
.SINGLE_BAR
;
676 cVSide
= GraphicsChars
.WINDOW_SIDE
;
680 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP_DOUBLE
;
681 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP_DOUBLE
;
682 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM_DOUBLE
;
683 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM_DOUBLE
;
684 cHSide
= GraphicsChars
.DOUBLE_BAR
;
685 cVSide
= GraphicsChars
.WINDOW_SIDE_DOUBLE
;
689 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP
;
690 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP
;
691 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM
;
692 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM
;
693 cHSide
= GraphicsChars
.WINDOW_TOP
;
694 cVSide
= GraphicsChars
.WINDOW_SIDE
;
697 throw new IllegalArgumentException("Invalid border type: "
701 // Place the corner characters
702 putCharXY(left
, top
, cTopLeft
, border
);
703 putCharXY(left
+ boxWidth
- 1, top
, cTopRight
, border
);
704 putCharXY(left
, top
+ boxHeight
- 1, cBottomLeft
, border
);
705 putCharXY(left
+ boxWidth
- 1, top
+ boxHeight
- 1, cBottomRight
,
708 // Draw the box lines
709 hLineXY(left
+ 1, top
, boxWidth
- 2, cHSide
, border
);
710 vLineXY(left
, top
+ 1, boxHeight
- 2, cVSide
, border
);
711 hLineXY(left
+ 1, top
+ boxHeight
- 1, boxWidth
- 2, cHSide
, border
);
712 vLineXY(left
+ boxWidth
- 1, top
+ 1, boxHeight
- 2, cVSide
, border
);
714 // Fill in the interior background
715 for (int i
= 1; i
< boxHeight
- 1; i
++) {
716 hLineXY(1 + left
, i
+ top
, boxWidth
- 2, ' ', background
);
721 drawBoxShadow(left
, top
, right
, bottom
);
728 * @param left left column of box. 0 is the left-most row.
729 * @param top top row of the box. 0 is the top-most row.
730 * @param right right column of box
731 * @param bottom bottom row of the box
733 public final void drawBoxShadow(final int left
, final int top
,
734 final int right
, final int bottom
) {
738 int boxWidth
= right
- left
;
739 int boxHeight
= bottom
- top
;
740 CellAttributes shadowAttr
= new CellAttributes();
742 // Shadows do not honor clipping but they DO honor offset.
743 int oldClipRight
= clipRight
;
744 int oldClipBottom
= clipBottom
;
746 clipRight = boxWidth + 2;
747 clipBottom = boxHeight + 1;
752 for (int i
= 0; i
< boxHeight
; i
++) {
753 putAttrXY(boxLeft
+ boxWidth
, boxTop
+ 1 + i
, shadowAttr
);
754 putAttrXY(boxLeft
+ boxWidth
+ 1, boxTop
+ 1 + i
, shadowAttr
);
756 for (int i
= 0; i
< boxWidth
; i
++) {
757 putAttrXY(boxLeft
+ 2 + i
, boxTop
+ boxHeight
, shadowAttr
);
759 clipRight
= oldClipRight
;
760 clipBottom
= oldClipBottom
;
764 * Default implementation does nothing.
766 public void flushPhysical() {}
769 * Put the cursor at (x,y).
771 * @param visible if true, the cursor should be visible
772 * @param x column coordinate to put the cursor on
773 * @param y row coordinate to put the cursor on
775 public void putCursor(final boolean visible
, final int x
, final int y
) {
777 cursorVisible
= visible
;
785 public final void hideCursor() {
786 cursorVisible
= false;
790 * Get the cursor visibility.
792 * @return true if the cursor is visible
794 public boolean isCursorVisible() {
795 return cursorVisible
;
799 * Get the cursor X position.
801 * @return the cursor x column position
803 public int getCursorX() {
808 * Get the cursor Y position.
810 * @return the cursor y row position
812 public int getCursorY() {
817 * Set the window title. Default implementation does nothing.
819 * @param title the new title
821 public void setTitle(final String title
) {}