AWT minimally working
[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 CellAttributes attr = new CellAttributes();
219 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
220 attr.setTo(logical[x][y]);
221 }
222 return attr;
223 }
224
225 /**
226 * Set the attributes at one location.
227 *
228 * @param x column coordinate. 0 is the left-most column.
229 * @param y row coordinate. 0 is the top-most row.
230 * @param attr attributes to use (bold, foreColor, backColor)
231 */
232 public final void putAttrXY(final int x, final int y,
233 final CellAttributes attr) {
234
235 putAttrXY(x, y, attr, true);
236 }
237
238 /**
239 * Set the attributes at one location.
240 *
241 * @param x column coordinate. 0 is the left-most column.
242 * @param y row coordinate. 0 is the top-most row.
243 * @param attr attributes to use (bold, foreColor, backColor)
244 * @param clip if true, honor clipping/offset
245 */
246 public final void putAttrXY(final int x, final int y,
247 final CellAttributes attr, final boolean clip) {
248
249 int X = x;
250 int Y = y;
251
252 if (clip) {
253 if ((x < clipLeft)
254 || (x >= clipRight)
255 || (y < clipTop)
256 || (y >= clipBottom)
257 ) {
258 return;
259 }
260 X += offsetX;
261 Y += offsetY;
262 }
263
264 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
265 dirty = true;
266 logical[X][Y].setForeColor(attr.getForeColor());
267 logical[X][Y].setBackColor(attr.getBackColor());
268 logical[X][Y].setBold(attr.getBold());
269 logical[X][Y].setBlink(attr.getBlink());
270 logical[X][Y].setReverse(attr.getReverse());
271 logical[X][Y].setUnderline(attr.getUnderline());
272 logical[X][Y].setProtect(attr.getProtect());
273 }
274 }
275
276 /**
277 * Fill the entire screen with one character with attributes.
278 *
279 * @param ch character to draw
280 * @param attr attributes to use (bold, foreColor, backColor)
281 */
282 public final void putAll(final char ch, final CellAttributes attr) {
283 for (int x = 0; x < width; x++) {
284 for (int y = 0; y < height; y++) {
285 putCharXY(x, y, ch, attr);
286 }
287 }
288 }
289
290 /**
291 * Render one character with attributes.
292 *
293 * @param x column coordinate. 0 is the left-most column.
294 * @param y row coordinate. 0 is the top-most row.
295 * @param ch character + attributes to draw
296 */
297 public final void putCharXY(final int x, final int y, final Cell ch) {
298 putCharXY(x, y, ch.getChar(), ch);
299 }
300
301 /**
302 * Render one character with attributes.
303 *
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 to draw
307 * @param attr attributes to use (bold, foreColor, backColor)
308 */
309 public final void putCharXY(final int x, final int y, final char ch,
310 final CellAttributes attr) {
311
312 if ((x < clipLeft)
313 || (x >= clipRight)
314 || (y < clipTop)
315 || (y >= clipBottom)
316 ) {
317 return;
318 }
319
320 int X = x + offsetX;
321 int Y = y + offsetY;
322
323 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
324
325 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
326 dirty = true;
327
328 // Do not put control characters on the display
329 assert (ch >= 0x20);
330 assert (ch != 0x7F);
331
332 logical[X][Y].setChar(ch);
333 logical[X][Y].setForeColor(attr.getForeColor());
334 logical[X][Y].setBackColor(attr.getBackColor());
335 logical[X][Y].setBold(attr.getBold());
336 logical[X][Y].setBlink(attr.getBlink());
337 logical[X][Y].setReverse(attr.getReverse());
338 logical[X][Y].setUnderline(attr.getUnderline());
339 logical[X][Y].setProtect(attr.getProtect());
340 }
341 }
342
343 /**
344 * Render one character without changing the underlying attributes.
345 *
346 * @param x column coordinate. 0 is the left-most column.
347 * @param y row coordinate. 0 is the top-most row.
348 * @param ch character to draw
349 */
350 public final void putCharXY(final int x, final int y, final char ch) {
351 if ((x < clipLeft)
352 || (x >= clipRight)
353 || (y < clipTop)
354 || (y >= clipBottom)
355 ) {
356 return;
357 }
358
359 int X = x + offsetX;
360 int Y = y + offsetY;
361
362 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
363
364 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
365 dirty = true;
366 logical[X][Y].setChar(ch);
367 }
368 }
369
370 /**
371 * Render a string. Does not wrap if the string exceeds the line.
372 *
373 * @param x column coordinate. 0 is the left-most column.
374 * @param y row coordinate. 0 is the top-most row.
375 * @param str string to draw
376 * @param attr attributes to use (bold, foreColor, backColor)
377 */
378 public final void putStrXY(final int x, final int y, final String str,
379 final CellAttributes attr) {
380
381 int i = x;
382 for (int j = 0; j < str.length(); j++) {
383 char ch = str.charAt(j);
384 putCharXY(i, y, ch, attr);
385 i++;
386 if (i == width) {
387 break;
388 }
389 }
390 }
391
392 /**
393 * Render a string without changing the underlying attribute. Does not
394 * wrap if the string exceeds the line.
395 *
396 * @param x column coordinate. 0 is the left-most column.
397 * @param y row coordinate. 0 is the top-most row.
398 * @param str string to draw
399 */
400 public final void putStrXY(final int x, final int y, final String str) {
401 int i = x;
402 for (int j = 0; j < str.length(); j++) {
403 char ch = str.charAt(j);
404 putCharXY(i, y, ch);
405 i++;
406 if (i == width) {
407 break;
408 }
409 }
410 }
411
412 /**
413 * Draw a vertical line from (x, y) to (x, y + n).
414 *
415 * @param x column coordinate. 0 is the left-most column.
416 * @param y row coordinate. 0 is the top-most row.
417 * @param n number of characters to draw
418 * @param ch character to draw
419 * @param attr attributes to use (bold, foreColor, backColor)
420 */
421 public final void vLineXY(final int x, final int y, final int n,
422 final char ch, final CellAttributes attr) {
423
424 for (int i = y; i < y + n; i++) {
425 putCharXY(x, i, ch, attr);
426 }
427 }
428
429 /**
430 * Draw a horizontal line from (x, y) to (x + n, y).
431 *
432 * @param x column coordinate. 0 is the left-most column.
433 * @param y row coordinate. 0 is the top-most row.
434 * @param n number of characters to draw
435 * @param ch character to draw
436 * @param attr attributes to use (bold, foreColor, backColor)
437 */
438 public final void hLineXY(final int x, final int y, final int n,
439 final char ch, final CellAttributes attr) {
440
441 for (int i = x; i < x + n; i++) {
442 putCharXY(i, y, ch, attr);
443 }
444 }
445
446 /**
447 * Reallocate screen buffers.
448 *
449 * @param width new width
450 * @param height new height
451 */
452 private void reallocate(final int width, final int height) {
453 if (logical != null) {
454 for (int row = 0; row < this.height; row++) {
455 for (int col = 0; col < this.width; col++) {
456 logical[col][row] = null;
457 }
458 }
459 logical = null;
460 }
461 logical = new Cell[width][height];
462 if (physical != null) {
463 for (int row = 0; row < this.height; row++) {
464 for (int col = 0; col < this.width; col++) {
465 physical[col][row] = null;
466 }
467 }
468 physical = null;
469 }
470 physical = new Cell[width][height];
471
472 for (int row = 0; row < height; row++) {
473 for (int col = 0; col < width; col++) {
474 physical[col][row] = new Cell();
475 logical[col][row] = new Cell();
476 }
477 }
478
479 this.width = width;
480 this.height = height;
481
482 clipLeft = 0;
483 clipTop = 0;
484 clipRight = width;
485 clipBottom = height;
486
487 reallyCleared = true;
488 dirty = true;
489 }
490
491 /**
492 * Change the width. Everything on-screen will be destroyed and must be
493 * redrawn.
494 *
495 * @param width new screen width
496 */
497 public final void setWidth(final int width) {
498 reallocate(width, this.height);
499 }
500
501 /**
502 * Change the height. Everything on-screen will be destroyed and must be
503 * redrawn.
504 *
505 * @param height new screen height
506 */
507 public final void setHeight(final int height) {
508 reallocate(this.width, height);
509 }
510
511 /**
512 * Change the width and height. Everything on-screen will be destroyed
513 * and must be redrawn.
514 *
515 * @param width new screen width
516 * @param height new screen height
517 */
518 public final void setDimensions(final int width, final int height) {
519 reallocate(width, height);
520 }
521
522 /**
523 * Get the height.
524 *
525 * @return current screen height
526 */
527 public final int getHeight() {
528 return this.height;
529 }
530
531 /**
532 * Get the width.
533 *
534 * @return current screen width
535 */
536 public final int getWidth() {
537 return this.width;
538 }
539
540 /**
541 * Public constructor. Sets everything to not-bold, white-on-black.
542 */
543 protected Screen() {
544 offsetX = 0;
545 offsetY = 0;
546 width = 80;
547 height = 24;
548 logical = null;
549 physical = null;
550 reallocate(width, height);
551 }
552
553 /**
554 * Reset screen to not-bold, white-on-black. Also flushes the offset and
555 * clip variables.
556 */
557 public final void reset() {
558 dirty = true;
559 for (int row = 0; row < height; row++) {
560 for (int col = 0; col < width; col++) {
561 logical[col][row].reset();
562 }
563 }
564 resetClipping();
565 }
566
567 /**
568 * Flush the offset and clip variables.
569 */
570 public final void resetClipping() {
571 offsetX = 0;
572 offsetY = 0;
573 clipLeft = 0;
574 clipTop = 0;
575 clipRight = width;
576 clipBottom = height;
577 }
578
579 /**
580 * Force the screen to be fully cleared and redrawn on the next flush().
581 */
582 public final void clear() {
583 reset();
584 }
585
586 /**
587 * Draw a box with a border and empty background.
588 *
589 * @param left left column of box. 0 is the left-most row.
590 * @param top top row of the box. 0 is the top-most row.
591 * @param right right column of box
592 * @param bottom bottom row of the box
593 * @param border attributes to use for the border
594 * @param background attributes to use for the background
595 */
596 public final void drawBox(final int left, final int top,
597 final int right, final int bottom,
598 final CellAttributes border, final CellAttributes background) {
599
600 drawBox(left, top, right, bottom, border, background, 1, false);
601 }
602
603 /**
604 * Draw a box with a border and empty background.
605 *
606 * @param left left column of box. 0 is the left-most row.
607 * @param top top row of the box. 0 is the top-most row.
608 * @param right right column of box
609 * @param bottom bottom row of the box
610 * @param border attributes to use for the border
611 * @param background attributes to use for the background
612 * @param borderType if 1, draw a single-line border; if 2, draw a
613 * double-line border; if 3, draw double-line top/bottom edges and
614 * single-line left/right edges (like Qmodem)
615 * @param shadow if true, draw a "shadow" on the box
616 */
617 public final void drawBox(final int left, final int top,
618 final int right, final int bottom,
619 final CellAttributes border, final CellAttributes background,
620 final int borderType, final boolean shadow) {
621
622 int boxTop = top;
623 int boxLeft = left;
624 int boxWidth = right - left;
625 int boxHeight = bottom - top;
626
627 char cTopLeft;
628 char cTopRight;
629 char cBottomLeft;
630 char cBottomRight;
631 char cHSide;
632 char cVSide;
633
634 switch (borderType) {
635 case 1:
636 cTopLeft = GraphicsChars.ULCORNER;
637 cTopRight = GraphicsChars.URCORNER;
638 cBottomLeft = GraphicsChars.LLCORNER;
639 cBottomRight = GraphicsChars.LRCORNER;
640 cHSide = GraphicsChars.SINGLE_BAR;
641 cVSide = GraphicsChars.WINDOW_SIDE;
642 break;
643
644 case 2:
645 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
646 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
647 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
648 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
649 cHSide = GraphicsChars.DOUBLE_BAR;
650 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
651 break;
652
653 case 3:
654 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
655 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
656 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
657 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
658 cHSide = GraphicsChars.WINDOW_TOP;
659 cVSide = GraphicsChars.WINDOW_SIDE;
660 break;
661 default:
662 throw new IllegalArgumentException("Invalid border type: "
663 + borderType);
664 }
665
666 // Place the corner characters
667 putCharXY(left, top, cTopLeft, border);
668 putCharXY(left + boxWidth - 1, top, cTopRight, border);
669 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
670 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
671 border);
672
673 // Draw the box lines
674 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
675 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
676 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
677 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
678
679 // Fill in the interior background
680 for (int i = 1; i < boxHeight - 1; i++) {
681 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
682 }
683
684 if (shadow) {
685 // Draw a shadow
686 drawBoxShadow(left, top, right, bottom);
687 }
688 }
689
690 /**
691 * Draw a box shadow.
692 *
693 * @param left left column of box. 0 is the left-most row.
694 * @param top top row of the box. 0 is the top-most row.
695 * @param right right column of box
696 * @param bottom bottom row of the box
697 */
698 public final void drawBoxShadow(final int left, final int top,
699 final int right, final int bottom) {
700
701 int boxTop = top;
702 int boxLeft = left;
703 int boxWidth = right - left;
704 int boxHeight = bottom - top;
705 CellAttributes shadowAttr = new CellAttributes();
706
707 // Shadows do not honor clipping but they DO honor offset.
708 int oldClipRight = clipRight;
709 int oldClipBottom = clipBottom;
710 /*
711 clipRight = boxWidth + 2;
712 clipBottom = boxHeight + 1;
713 */
714 clipRight = width;
715 clipBottom = height;
716
717 for (int i = 0; i < boxHeight; i++) {
718 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
719 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
720 }
721 for (int i = 0; i < boxWidth; i++) {
722 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
723 }
724 clipRight = oldClipRight;
725 clipBottom = oldClipBottom;
726 }
727
728 /**
729 * Subclasses must provide an implementation to push the logical screen
730 * to the physical device.
731 */
732 public abstract void flushPhysical();
733
734 /**
735 * Put the cursor at (x,y).
736 *
737 * @param visible if true, the cursor should be visible
738 * @param x column coordinate to put the cursor on
739 * @param y row coordinate to put the cursor on
740 */
741 public void putCursor(final boolean visible, final int x, final int y) {
742
743 cursorVisible = visible;
744 cursorX = x;
745 cursorY = y;
746 }
747
748 /**
749 * Hide the cursor.
750 */
751 public final void hideCursor() {
752 cursorVisible = false;
753 }
754 }