81dabd246a8f607a4c87994c70a65880482eda0a
[nikiroo-utils.git] / src / jexer / io / Screen.java
1 /**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
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.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
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.
16 *
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.
21 *
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
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer.io;
32
33 import jexer.bits.Cell;
34 import jexer.bits.CellAttributes;
35 import jexer.bits.GraphicsChars;
36
37 /**
38 * This class represents a text-based screen. Drawing operations write to a
39 * logical screen.
40 */
41 public abstract class Screen {
42
43 /**
44 * Width of the visible window.
45 */
46 protected int width;
47
48 /**
49 * Height of the visible window.
50 */
51 protected int height;
52
53 /**
54 * Drawing offset for x.
55 */
56 private int offsetX;
57
58 /**
59 * Set drawing offset for x.
60 *
61 * @param offsetX new drawing offset
62 */
63 public final void setOffsetX(final int offsetX) {
64 this.offsetX = offsetX;
65 }
66
67 /**
68 * Drawing offset for y.
69 */
70 private int offsetY;
71
72 /**
73 * Set drawing offset for y.
74 *
75 * @param offsetY new drawing offset
76 */
77 public final void setOffsetY(final int offsetY) {
78 this.offsetY = offsetY;
79 }
80
81 /**
82 * Ignore anything drawn right of clipRight.
83 */
84 private int clipRight;
85
86 /**
87 * Get right drawing clipping boundary.
88 *
89 * @return drawing boundary
90 */
91 public final int getClipRight() {
92 return clipRight;
93 }
94
95 /**
96 * Set right drawing clipping boundary.
97 *
98 * @param clipRight new boundary
99 */
100 public final void setClipRight(final int clipRight) {
101 this.clipRight = clipRight;
102 }
103
104 /**
105 * Ignore anything drawn below clipBottom.
106 */
107 private int clipBottom;
108
109 /**
110 * Get bottom drawing clipping boundary.
111 *
112 * @return drawing boundary
113 */
114 public final int getClipBottom() {
115 return clipBottom;
116 }
117
118 /**
119 * Set bottom drawing clipping boundary.
120 *
121 * @param clipBottom new boundary
122 */
123 public final void setClipBottom(final int clipBottom) {
124 this.clipBottom = clipBottom;
125 }
126
127 /**
128 * Ignore anything drawn left of clipLeft.
129 */
130 private int clipLeft;
131
132 /**
133 * Get left drawing clipping boundary.
134 *
135 * @return drawing boundary
136 */
137 public final int getClipLeft() {
138 return clipLeft;
139 }
140
141 /**
142 * Set left drawing clipping boundary.
143 *
144 * @param clipLeft new boundary
145 */
146 public final void setClipLeft(final int clipLeft) {
147 this.clipLeft = clipLeft;
148 }
149
150 /**
151 * Ignore anything drawn above clipTop.
152 */
153 private int clipTop;
154
155 /**
156 * Get top drawing clipping boundary.
157 *
158 * @return drawing boundary
159 */
160 public final int getClipTop() {
161 return clipTop;
162 }
163
164 /**
165 * Set top drawing clipping boundary.
166 *
167 * @param clipTop new boundary
168 */
169 public final void setClipTop(final int clipTop) {
170 this.clipTop = clipTop;
171 }
172
173 /**
174 * The physical screen last sent out on flush().
175 */
176 protected Cell [][] physical;
177
178 /**
179 * The logical screen being rendered to.
180 */
181 protected Cell [][] logical;
182
183 /**
184 * When true, logical != physical.
185 */
186 protected boolean dirty;
187
188 /**
189 * Set if the user explicitly wants to redraw everything starting with a
190 * ECMATerminal.clearAll().
191 */
192 protected boolean reallyCleared;
193
194 /**
195 * If true, the cursor is visible and should be placed onscreen at
196 * (cursorX, cursorY) during a call to flushPhysical().
197 */
198 protected boolean cursorVisible;
199
200 /**
201 * Cursor X position if visible.
202 */
203 protected int cursorX;
204
205 /**
206 * Cursor Y position if visible.
207 */
208 protected int cursorY;
209
210 /**
211 * Get the attributes at one location.
212 *
213 * @param x column coordinate. 0 is the left-most column.
214 * @param y row coordinate. 0 is the top-most row.
215 * @return attributes at (x, y)
216 */
217 public final CellAttributes getAttrXY(final int x, final int y) {
218
219 CellAttributes attr = new CellAttributes();
220 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
221 attr.setTo(logical[x][y]);
222 }
223 return attr;
224 }
225
226 /**
227 * Set the attributes at one location.
228 *
229 * @param x column coordinate. 0 is the left-most column.
230 * @param y row coordinate. 0 is the top-most row.
231 * @param attr attributes to use (bold, foreColor, backColor)
232 */
233 public final void putAttrXY(final int x, final int y,
234 final CellAttributes attr) {
235
236 putAttrXY(x, y, attr, true);
237 }
238
239 /**
240 * Set the attributes at one location.
241 *
242 * @param x column coordinate. 0 is the left-most column.
243 * @param y row coordinate. 0 is the top-most row.
244 * @param attr attributes to use (bold, foreColor, backColor)
245 * @param clip if true, honor clipping/offset
246 */
247 public final void putAttrXY(final int x, final int y
248 , final CellAttributes attr, final boolean clip) {
249
250 int X = x;
251 int Y = y;
252
253 if (clip) {
254 if ((x < clipLeft)
255 || (x >= clipRight)
256 || (y < clipTop)
257 || (y >= clipBottom)
258 ) {
259 return;
260 }
261 X += offsetX;
262 Y += offsetY;
263 }
264
265 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
266 dirty = true;
267 logical[X][Y].setForeColor(attr.getForeColor());
268 logical[X][Y].setBackColor(attr.getBackColor());
269 logical[X][Y].setBold(attr.getBold());
270 logical[X][Y].setBlink(attr.getBlink());
271 logical[X][Y].setReverse(attr.getReverse());
272 logical[X][Y].setUnderline(attr.getUnderline());
273 logical[X][Y].setProtect(attr.getProtect());
274 }
275 }
276
277 /**
278 * Fill the entire screen with one character with attributes.
279 *
280 * @param ch character to draw
281 * @param attr attributes to use (bold, foreColor, backColor)
282 */
283 public final void putAll(final char ch, final CellAttributes attr) {
284
285 for (int x = 0; x < width; x++) {
286 for (int y = 0; y < height; y++) {
287 putCharXY(x, y, ch, attr);
288 }
289 }
290 }
291
292 /**
293 * Render one character with attributes.
294 *
295 * @param x column coordinate. 0 is the left-most column.
296 * @param y row coordinate. 0 is the top-most row.
297 * @param ch character + attributes to draw
298 */
299 public final void putCharXY(final int x, final int y, final Cell ch) {
300 putCharXY(x, y, ch.getChar(), ch);
301 }
302
303 /**
304 * Render one character with attributes.
305 *
306 * @param x column coordinate. 0 is the left-most column.
307 * @param y row coordinate. 0 is the top-most row.
308 * @param ch character to draw
309 * @param attr attributes to use (bold, foreColor, backColor)
310 */
311 public final void putCharXY(final int x, final int y, final char ch,
312 final CellAttributes attr) {
313
314 if ((x < clipLeft)
315 || (x >= clipRight)
316 || (y < clipTop)
317 || (y >= clipBottom)
318 ) {
319 return;
320 }
321
322 int X = x + offsetX;
323 int Y = y + offsetY;
324
325 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
326
327 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
328 dirty = true;
329
330 // Do not put control characters on the display
331 assert (ch >= 0x20);
332 assert (ch != 0x7F);
333
334 logical[X][Y].setChar(ch);
335 logical[X][Y].setForeColor(attr.getForeColor());
336 logical[X][Y].setBackColor(attr.getBackColor());
337 logical[X][Y].setBold(attr.getBold());
338 logical[X][Y].setBlink(attr.getBlink());
339 logical[X][Y].setReverse(attr.getReverse());
340 logical[X][Y].setUnderline(attr.getUnderline());
341 logical[X][Y].setProtect(attr.getProtect());
342 }
343 }
344
345 /**
346 * Render one character without changing the underlying attributes.
347 *
348 * @param x column coordinate. 0 is the left-most column.
349 * @param y row coordinate. 0 is the top-most row.
350 * @param ch character to draw
351 */
352 public final void putCharXY(final int x, final int y, final char ch) {
353
354 if ((x < clipLeft)
355 || (x >= clipRight)
356 || (y < clipTop)
357 || (y >= clipBottom)
358 ) {
359 return;
360 }
361
362 int X = x + offsetX;
363 int Y = y + offsetY;
364
365 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
366
367 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
368 dirty = true;
369 logical[X][Y].setChar(ch);
370 }
371 }
372
373 /**
374 * Render a string. Does not wrap if the string exceeds the line.
375 *
376 * @param x column coordinate. 0 is the left-most column.
377 * @param y row coordinate. 0 is the top-most row.
378 * @param str string to draw
379 * @param attr attributes to use (bold, foreColor, backColor)
380 */
381 public final void putStrXY(final int x, final int y, final String str,
382 final CellAttributes attr) {
383
384 int i = x;
385 for (int j = 0; j < str.length(); j++) {
386 char ch = str.charAt(j);
387 putCharXY(i, y, ch, attr);
388 i++;
389 if (i == width) {
390 break;
391 }
392 }
393 }
394
395 /**
396 * Render a string without changing the underlying attribute. Does not
397 * wrap if the string exceeds the line.
398 *
399 * @param x column coordinate. 0 is the left-most column.
400 * @param y row coordinate. 0 is the top-most row.
401 * @param str string to draw
402 */
403 public final void putStrXY(final int x, final int y, final String str) {
404
405 int i = x;
406 for (int j = 0; j < str.length(); j++) {
407 char ch = str.charAt(j);
408 putCharXY(i, y, ch);
409 i++;
410 if (i == width) {
411 break;
412 }
413 }
414 }
415
416 /**
417 * Draw a vertical line from (x, y) to (x, y + n).
418 *
419 * @param x column coordinate. 0 is the left-most column.
420 * @param y row coordinate. 0 is the top-most row.
421 * @param n number of characters to draw
422 * @param ch character to draw
423 * @param attr attributes to use (bold, foreColor, backColor)
424 */
425 public final void vLineXY(final int x, final int y, final int n,
426 final char ch, final CellAttributes attr) {
427
428 for (int i = y; i < y + n; i++) {
429 putCharXY(x, i, ch, attr);
430 }
431 }
432
433 /**
434 * Draw a horizontal line from (x, y) to (x + n, y).
435 *
436 * @param x column coordinate. 0 is the left-most column.
437 * @param y row coordinate. 0 is the top-most row.
438 * @param n number of characters to draw
439 * @param ch character to draw
440 * @param attr attributes to use (bold, foreColor, backColor)
441 */
442 public final void hLineXY(final int x, final int y, final int n,
443 final char ch, final CellAttributes attr) {
444
445 for (int i = x; i < x + n; i++) {
446 putCharXY(i, y, ch, attr);
447 }
448 }
449
450 /**
451 * Reallocate screen buffers.
452 *
453 * @param width new width
454 * @param height new height
455 */
456 private synchronized void reallocate(final int width, final int height) {
457 if (logical != null) {
458 for (int row = 0; row < this.height; row++) {
459 for (int col = 0; col < this.width; col++) {
460 logical[col][row] = null;
461 }
462 }
463 logical = null;
464 }
465 logical = new Cell[width][height];
466 if (physical != null) {
467 for (int row = 0; row < this.height; row++) {
468 for (int col = 0; col < this.width; col++) {
469 physical[col][row] = null;
470 }
471 }
472 physical = null;
473 }
474 physical = new Cell[width][height];
475
476 for (int row = 0; row < height; row++) {
477 for (int col = 0; col < width; col++) {
478 physical[col][row] = new Cell();
479 logical[col][row] = new Cell();
480 }
481 }
482
483 this.width = width;
484 this.height = height;
485
486 clipLeft = 0;
487 clipTop = 0;
488 clipRight = width;
489 clipBottom = height;
490
491 reallyCleared = true;
492 dirty = true;
493 }
494
495 /**
496 * Change the width. Everything on-screen will be destroyed and must be
497 * redrawn.
498 *
499 * @param width new screen width
500 */
501 public final synchronized void setWidth(final int width) {
502 reallocate(width, this.height);
503 }
504
505 /**
506 * Change the height. Everything on-screen will be destroyed and must be
507 * redrawn.
508 *
509 * @param height new screen height
510 */
511 public final synchronized void setHeight(final int height) {
512 reallocate(this.width, height);
513 }
514
515 /**
516 * Change the width and height. Everything on-screen will be destroyed
517 * and must be redrawn.
518 *
519 * @param width new screen width
520 * @param height new screen height
521 */
522 public final void setDimensions(final int width, final int height) {
523 reallocate(width, height);
524 }
525
526 /**
527 * Get the height.
528 *
529 * @return current screen height
530 */
531 public final synchronized int getHeight() {
532 return this.height;
533 }
534
535 /**
536 * Get the width.
537 *
538 * @return current screen width
539 */
540 public final synchronized int getWidth() {
541 return this.width;
542 }
543
544 /**
545 * Public constructor. Sets everything to not-bold, white-on-black.
546 */
547 protected Screen() {
548 offsetX = 0;
549 offsetY = 0;
550 width = 80;
551 height = 24;
552 logical = null;
553 physical = null;
554 reallocate(width, height);
555 }
556
557 /**
558 * Reset screen to not-bold, white-on-black. Also flushes the offset and
559 * clip variables.
560 */
561 public final synchronized void reset() {
562 dirty = true;
563 for (int row = 0; row < height; row++) {
564 for (int col = 0; col < width; col++) {
565 logical[col][row].reset();
566 }
567 }
568 resetClipping();
569 }
570
571 /**
572 * Flush the offset and clip variables.
573 */
574 public final void resetClipping() {
575 offsetX = 0;
576 offsetY = 0;
577 clipLeft = 0;
578 clipTop = 0;
579 clipRight = width;
580 clipBottom = height;
581 }
582
583 /**
584 * Force the screen to be fully cleared and redrawn on the next flush().
585 */
586 public final void clear() {
587 reset();
588 }
589
590 /**
591 * Draw a box with a border and empty background.
592 *
593 * @param left left column of box. 0 is the left-most row.
594 * @param top top row of the box. 0 is the top-most row.
595 * @param right right column of box
596 * @param bottom bottom row of the box
597 * @param border attributes to use for the border
598 * @param background attributes to use for the background
599 */
600 public final void drawBox(final int left, final int top,
601 final int right, final int bottom,
602 final CellAttributes border, final CellAttributes background) {
603
604 drawBox(left, top, right, bottom, border, background, 1, false);
605 }
606
607 /**
608 * Draw a box with a border and empty background.
609 *
610 * @param left left column of box. 0 is the left-most row.
611 * @param top top row of the box. 0 is the top-most row.
612 * @param right right column of box
613 * @param bottom bottom row of the box
614 * @param border attributes to use for the border
615 * @param background attributes to use for the background
616 * @param borderType if 1, draw a single-line border; if 2, draw a
617 * double-line border; if 3, draw double-line top/bottom edges and
618 * single-line left/right edges (like Qmodem)
619 * @param shadow if true, draw a "shadow" on the box
620 */
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,
624 final int borderType, final boolean shadow) {
625
626 int boxTop = top;
627 int boxLeft = left;
628 int boxWidth = right - left;
629 int boxHeight = bottom - top;
630
631 char cTopLeft;
632 char cTopRight;
633 char cBottomLeft;
634 char cBottomRight;
635 char cHSide;
636 char cVSide;
637
638 switch (borderType) {
639 case 1:
640 cTopLeft = GraphicsChars.ULCORNER;
641 cTopRight = GraphicsChars.URCORNER;
642 cBottomLeft = GraphicsChars.LLCORNER;
643 cBottomRight = GraphicsChars.LRCORNER;
644 cHSide = GraphicsChars.SINGLE_BAR;
645 cVSide = GraphicsChars.WINDOW_SIDE;
646 break;
647
648 case 2:
649 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
650 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
651 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
652 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
653 cHSide = GraphicsChars.DOUBLE_BAR;
654 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
655 break;
656
657 case 3:
658 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
659 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
660 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
661 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
662 cHSide = GraphicsChars.WINDOW_TOP;
663 cVSide = GraphicsChars.WINDOW_SIDE;
664 break;
665 default:
666 throw new IllegalArgumentException("Invalid border type: "
667 + borderType);
668 }
669
670 // Place the corner characters
671 putCharXY(left, top, cTopLeft, border);
672 putCharXY(left + boxWidth - 1, top, cTopRight, border);
673 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
674 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
675 border);
676
677 // Draw the box lines
678 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
679 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
680 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
681 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
682
683 // Fill in the interior background
684 for (int i = 1; i < boxHeight - 1; i++) {
685 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
686 }
687
688 if (shadow) {
689 // Draw a shadow
690 drawBoxShadow(left, top, right, bottom);
691 }
692 }
693
694 /**
695 * Draw a box shadow.
696 *
697 * @param left left column of box. 0 is the left-most row.
698 * @param top top row of the box. 0 is the top-most row.
699 * @param right right column of box
700 * @param bottom bottom row of the box
701 */
702 public final void drawBoxShadow(final int left, final int top,
703 final int right, final int bottom) {
704
705 int boxTop = top;
706 int boxLeft = left;
707 int boxWidth = right - left;
708 int boxHeight = bottom - top;
709 CellAttributes shadowAttr = new CellAttributes();
710
711 // Shadows do not honor clipping but they DO honor offset.
712 int oldClipRight = clipRight;
713 int oldClipBottom = clipBottom;
714 /*
715 clipRight = boxWidth + 2;
716 clipBottom = boxHeight + 1;
717 */
718 clipRight = width;
719 clipBottom = height;
720
721 for (int i = 0; i < boxHeight; i++) {
722 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
723 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
724 }
725 for (int i = 0; i < boxWidth; i++) {
726 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
727 }
728 clipRight = oldClipRight;
729 clipBottom = oldClipBottom;
730 }
731
732 /**
733 * Subclasses must provide an implementation to push the logical screen
734 * to the physical device.
735 */
736 public abstract void flushPhysical();
737
738 /**
739 * Put the cursor at (x,y).
740 *
741 * @param visible if true, the cursor should be visible
742 * @param x column coordinate to put the cursor on
743 * @param y row coordinate to put the cursor on
744 */
745 public void putCursor(final boolean visible, final int x, final int y) {
746
747 cursorVisible = visible;
748 cursorX = x;
749 cursorY = y;
750 }
751
752 /**
753 * Hide the cursor.
754 */
755 public final void hideCursor() {
756 cursorVisible = false;
757 }
758 }