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]
31 import jexer
.bits
.Cell
;
32 import jexer
.bits
.CellAttributes
;
33 import jexer
.bits
.GraphicsChars
;
36 * This class represents a text-based screen. Drawing operations write to a
39 public abstract class Screen
{
42 * Width of the visible window.
47 * Height of the visible window.
52 * Drawing offset for x.
57 * Set drawing offset for x.
59 * @param offsetX new drawing offset
61 public final void setOffsetX(final int offsetX
) {
62 this.offsetX
= offsetX
;
66 * Drawing offset for y.
71 * Set drawing offset for y.
73 * @param offsetY new drawing offset
75 public final void setOffsetY(final int offsetY
) {
76 this.offsetY
= offsetY
;
80 * Ignore anything drawn right of clipRight.
82 private int clipRight
;
85 * Get right drawing clipping boundary.
87 * @return drawing boundary
89 public final int getClipRight() {
94 * Set right drawing clipping boundary.
96 * @param clipRight new boundary
98 public final void setClipRight(final int clipRight
) {
99 this.clipRight
= clipRight
;
103 * Ignore anything drawn below clipBottom.
105 private int clipBottom
;
108 * Get bottom drawing clipping boundary.
110 * @return drawing boundary
112 public final int getClipBottom() {
117 * Set bottom drawing clipping boundary.
119 * @param clipBottom new boundary
121 public final void setClipBottom(final int clipBottom
) {
122 this.clipBottom
= clipBottom
;
126 * Ignore anything drawn left of clipLeft.
128 private int clipLeft
;
131 * Get left drawing clipping boundary.
133 * @return drawing boundary
135 public final int getClipLeft() {
140 * Set left drawing clipping boundary.
142 * @param clipLeft new boundary
144 public final void setClipLeft(final int clipLeft
) {
145 this.clipLeft
= clipLeft
;
149 * Ignore anything drawn above clipTop.
154 * Get top drawing clipping boundary.
156 * @return drawing boundary
158 public final int getClipTop() {
163 * Set top drawing clipping boundary.
165 * @param clipTop new boundary
167 public final void setClipTop(final int clipTop
) {
168 this.clipTop
= clipTop
;
172 * The physical screen last sent out on flush().
174 protected Cell
[][] physical
;
177 * The logical screen being rendered to.
179 protected Cell
[][] logical
;
182 * When true, logical != physical.
184 protected volatile boolean dirty
;
189 * @return if true, the logical screen is not in sync with the physical
192 public final boolean isDirty() {
197 * Set if the user explicitly wants to redraw everything starting with a
198 * ECMATerminal.clearAll().
200 protected boolean reallyCleared
;
203 * If true, the cursor is visible and should be placed onscreen at
204 * (cursorX, cursorY) during a call to flushPhysical().
206 protected boolean cursorVisible
;
209 * Cursor X position if visible.
211 protected int cursorX
;
214 * Cursor Y position if visible.
216 protected int cursorY
;
219 * Get the attributes at one location.
221 * @param x column coordinate. 0 is the left-most column.
222 * @param y row coordinate. 0 is the top-most row.
223 * @return attributes at (x, y)
225 public final CellAttributes
getAttrXY(final int x
, final int y
) {
226 CellAttributes attr
= new CellAttributes();
227 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
228 attr
.setTo(logical
[x
][y
]);
234 * Set the attributes at one location.
236 * @param x column coordinate. 0 is the left-most column.
237 * @param y row coordinate. 0 is the top-most row.
238 * @param attr attributes to use (bold, foreColor, backColor)
240 public final void putAttrXY(final int x
, final int y
,
241 final CellAttributes attr
) {
243 putAttrXY(x
, y
, attr
, true);
247 * Set the attributes at one location.
249 * @param x column coordinate. 0 is the left-most column.
250 * @param y row coordinate. 0 is the top-most row.
251 * @param attr attributes to use (bold, foreColor, backColor)
252 * @param clip if true, honor clipping/offset
254 public final void putAttrXY(final int x
, final int y
,
255 final CellAttributes attr
, final boolean clip
) {
272 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
274 logical
[X
][Y
].setForeColor(attr
.getForeColor());
275 logical
[X
][Y
].setBackColor(attr
.getBackColor());
276 logical
[X
][Y
].setBold(attr
.isBold());
277 logical
[X
][Y
].setBlink(attr
.isBlink());
278 logical
[X
][Y
].setReverse(attr
.isReverse());
279 logical
[X
][Y
].setUnderline(attr
.isUnderline());
280 logical
[X
][Y
].setProtect(attr
.isProtect());
285 * Fill the entire screen with one character with attributes.
287 * @param ch character to draw
288 * @param attr attributes to use (bold, foreColor, backColor)
290 public final void putAll(final char ch
, final CellAttributes attr
) {
292 for (int x
= 0; x
< width
; x
++) {
293 for (int y
= 0; y
< height
; y
++) {
294 putCharXY(x
, y
, ch
, attr
);
300 * Render one character with attributes.
302 * @param x column coordinate. 0 is the left-most column.
303 * @param y row coordinate. 0 is the top-most row.
304 * @param ch character + attributes to draw
306 public final void putCharXY(final int x
, final int y
, final Cell ch
) {
307 putCharXY(x
, y
, ch
.getChar(), ch
);
311 * Render one character with attributes.
313 * @param x column coordinate. 0 is the left-most column.
314 * @param y row coordinate. 0 is the top-most row.
315 * @param ch character to draw
316 * @param attr attributes to use (bold, foreColor, backColor)
318 public final void putCharXY(final int x
, final int y
, final char ch
,
319 final CellAttributes attr
) {
332 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
334 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
337 // Do not put control characters on the display
341 logical
[X
][Y
].setChar(ch
);
342 logical
[X
][Y
].setForeColor(attr
.getForeColor());
343 logical
[X
][Y
].setBackColor(attr
.getBackColor());
344 logical
[X
][Y
].setBold(attr
.isBold());
345 logical
[X
][Y
].setBlink(attr
.isBlink());
346 logical
[X
][Y
].setReverse(attr
.isReverse());
347 logical
[X
][Y
].setUnderline(attr
.isUnderline());
348 logical
[X
][Y
].setProtect(attr
.isProtect());
353 * Render one character without changing the underlying attributes.
355 * @param x column coordinate. 0 is the left-most column.
356 * @param y row coordinate. 0 is the top-most row.
357 * @param ch character to draw
359 public final void putCharXY(final int x
, final int y
, final char ch
) {
372 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
374 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
376 logical
[X
][Y
].setChar(ch
);
381 * Render a string. Does not wrap if the string exceeds the line.
383 * @param x column coordinate. 0 is the left-most column.
384 * @param y row coordinate. 0 is the top-most row.
385 * @param str string to draw
386 * @param attr attributes to use (bold, foreColor, backColor)
388 public final void putStringXY(final int x
, final int y
, final String str
,
389 final CellAttributes attr
) {
392 for (int j
= 0; j
< str
.length(); j
++) {
393 char ch
= str
.charAt(j
);
394 putCharXY(i
, y
, ch
, attr
);
403 * Render a string without changing the underlying attribute. Does not
404 * wrap if the string exceeds the line.
406 * @param x column coordinate. 0 is the left-most column.
407 * @param y row coordinate. 0 is the top-most row.
408 * @param str string to draw
410 public final void putStringXY(final int x
, final int y
, final String str
) {
413 for (int j
= 0; j
< str
.length(); j
++) {
414 char ch
= str
.charAt(j
);
424 * Draw a vertical line from (x, y) to (x, y + n).
426 * @param x column coordinate. 0 is the left-most column.
427 * @param y row coordinate. 0 is the top-most row.
428 * @param n number of characters to draw
429 * @param ch character to draw
430 * @param attr attributes to use (bold, foreColor, backColor)
432 public final void vLineXY(final int x
, final int y
, final int n
,
433 final char ch
, final CellAttributes attr
) {
435 for (int i
= y
; i
< y
+ n
; i
++) {
436 putCharXY(x
, i
, ch
, attr
);
441 * Draw a horizontal line from (x, y) to (x + n, y).
443 * @param x column coordinate. 0 is the left-most column.
444 * @param y row coordinate. 0 is the top-most row.
445 * @param n number of characters to draw
446 * @param ch character to draw
447 * @param attr attributes to use (bold, foreColor, backColor)
449 public final void hLineXY(final int x
, final int y
, final int n
,
450 final char ch
, final CellAttributes attr
) {
452 for (int i
= x
; i
< x
+ n
; i
++) {
453 putCharXY(i
, y
, ch
, attr
);
458 * Reallocate screen buffers.
460 * @param width new width
461 * @param height new height
463 private synchronized void reallocate(final int width
, final int height
) {
464 if (logical
!= null) {
465 for (int row
= 0; row
< this.height
; row
++) {
466 for (int col
= 0; col
< this.width
; col
++) {
467 logical
[col
][row
] = null;
472 logical
= new Cell
[width
][height
];
473 if (physical
!= null) {
474 for (int row
= 0; row
< this.height
; row
++) {
475 for (int col
= 0; col
< this.width
; col
++) {
476 physical
[col
][row
] = null;
481 physical
= new Cell
[width
][height
];
483 for (int row
= 0; row
< height
; row
++) {
484 for (int col
= 0; col
< width
; col
++) {
485 physical
[col
][row
] = new Cell();
486 logical
[col
][row
] = new Cell();
491 this.height
= height
;
498 reallyCleared
= true;
503 * Change the width. Everything on-screen will be destroyed and must be
506 * @param width new screen width
508 public final synchronized void setWidth(final int width
) {
509 reallocate(width
, this.height
);
513 * Change the height. Everything on-screen will be destroyed and must be
516 * @param height new screen height
518 public final synchronized void setHeight(final int height
) {
519 reallocate(this.width
, height
);
523 * Change the width and height. Everything on-screen will be destroyed
524 * and must be redrawn.
526 * @param width new screen width
527 * @param height new screen height
529 public final void setDimensions(final int width
, final int height
) {
530 reallocate(width
, height
);
536 * @return current screen height
538 public final synchronized int getHeight() {
545 * @return current screen width
547 public final synchronized int getWidth() {
552 * Public constructor. Sets everything to not-bold, white-on-black.
561 reallocate(width
, height
);
565 * Reset screen to not-bold, white-on-black. Also flushes the offset and
568 public final synchronized void reset() {
570 for (int row
= 0; row
< height
; row
++) {
571 for (int col
= 0; col
< width
; col
++) {
572 logical
[col
][row
].reset();
579 * Flush the offset and clip variables.
581 public final void resetClipping() {
591 * Clear the logical screen.
593 public final void clear() {
598 * Clear the physical screen.
600 public final void clearPhysical() {
602 for (int row
= 0; row
< height
; row
++) {
603 for (int col
= 0; col
< width
; col
++) {
604 physical
[col
][row
].reset();
610 * Draw a box with a border and empty background.
612 * @param left left column of box. 0 is the left-most row.
613 * @param top top row of the box. 0 is the top-most row.
614 * @param right right column of box
615 * @param bottom bottom row of the box
616 * @param border attributes to use for the border
617 * @param background attributes to use for the background
619 public final void drawBox(final int left
, final int top
,
620 final int right
, final int bottom
,
621 final CellAttributes border
, final CellAttributes background
) {
623 drawBox(left
, top
, right
, bottom
, border
, background
, 1, false);
627 * Draw a box with a border and empty background.
629 * @param left left column of box. 0 is the left-most row.
630 * @param top top row of the box. 0 is the top-most row.
631 * @param right right column of box
632 * @param bottom bottom row of the box
633 * @param border attributes to use for the border
634 * @param background attributes to use for the background
635 * @param borderType if 1, draw a single-line border; if 2, draw a
636 * double-line border; if 3, draw double-line top/bottom edges and
637 * single-line left/right edges (like Qmodem)
638 * @param shadow if true, draw a "shadow" on the box
640 public final void drawBox(final int left
, final int top
,
641 final int right
, final int bottom
,
642 final CellAttributes border
, final CellAttributes background
,
643 final int borderType
, final boolean shadow
) {
645 int boxWidth
= right
- left
;
646 int boxHeight
= bottom
- top
;
655 switch (borderType
) {
657 cTopLeft
= GraphicsChars
.ULCORNER
;
658 cTopRight
= GraphicsChars
.URCORNER
;
659 cBottomLeft
= GraphicsChars
.LLCORNER
;
660 cBottomRight
= GraphicsChars
.LRCORNER
;
661 cHSide
= GraphicsChars
.SINGLE_BAR
;
662 cVSide
= GraphicsChars
.WINDOW_SIDE
;
666 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP_DOUBLE
;
667 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP_DOUBLE
;
668 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM_DOUBLE
;
669 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM_DOUBLE
;
670 cHSide
= GraphicsChars
.DOUBLE_BAR
;
671 cVSide
= GraphicsChars
.WINDOW_SIDE_DOUBLE
;
675 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP
;
676 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP
;
677 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM
;
678 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM
;
679 cHSide
= GraphicsChars
.WINDOW_TOP
;
680 cVSide
= GraphicsChars
.WINDOW_SIDE
;
683 throw new IllegalArgumentException("Invalid border type: "
687 // Place the corner characters
688 putCharXY(left
, top
, cTopLeft
, border
);
689 putCharXY(left
+ boxWidth
- 1, top
, cTopRight
, border
);
690 putCharXY(left
, top
+ boxHeight
- 1, cBottomLeft
, border
);
691 putCharXY(left
+ boxWidth
- 1, top
+ boxHeight
- 1, cBottomRight
,
694 // Draw the box lines
695 hLineXY(left
+ 1, top
, boxWidth
- 2, cHSide
, border
);
696 vLineXY(left
, top
+ 1, boxHeight
- 2, cVSide
, border
);
697 hLineXY(left
+ 1, top
+ boxHeight
- 1, boxWidth
- 2, cHSide
, border
);
698 vLineXY(left
+ boxWidth
- 1, top
+ 1, boxHeight
- 2, cVSide
, border
);
700 // Fill in the interior background
701 for (int i
= 1; i
< boxHeight
- 1; i
++) {
702 hLineXY(1 + left
, i
+ top
, boxWidth
- 2, ' ', background
);
707 drawBoxShadow(left
, top
, right
, bottom
);
714 * @param left left column of box. 0 is the left-most row.
715 * @param top top row of the box. 0 is the top-most row.
716 * @param right right column of box
717 * @param bottom bottom row of the box
719 public final void drawBoxShadow(final int left
, final int top
,
720 final int right
, final int bottom
) {
724 int boxWidth
= right
- left
;
725 int boxHeight
= bottom
- top
;
726 CellAttributes shadowAttr
= new CellAttributes();
728 // Shadows do not honor clipping but they DO honor offset.
729 int oldClipRight
= clipRight
;
730 int oldClipBottom
= clipBottom
;
732 clipRight = boxWidth + 2;
733 clipBottom = boxHeight + 1;
738 for (int i
= 0; i
< boxHeight
; i
++) {
739 putAttrXY(boxLeft
+ boxWidth
, boxTop
+ 1 + i
, shadowAttr
);
740 putAttrXY(boxLeft
+ boxWidth
+ 1, boxTop
+ 1 + i
, shadowAttr
);
742 for (int i
= 0; i
< boxWidth
; i
++) {
743 putAttrXY(boxLeft
+ 2 + i
, boxTop
+ boxHeight
, shadowAttr
);
745 clipRight
= oldClipRight
;
746 clipBottom
= oldClipBottom
;
750 * Subclasses must provide an implementation to push the logical screen
751 * to the physical device.
753 public abstract void flushPhysical();
756 * Put the cursor at (x,y).
758 * @param visible if true, the cursor should be visible
759 * @param x column coordinate to put the cursor on
760 * @param y row coordinate to put the cursor on
762 public void putCursor(final boolean visible
, final int x
, final int y
) {
764 cursorVisible
= visible
;
772 public final void hideCursor() {
773 cursorVisible
= false;