LICENSE CHANGED TO MIT
[nikiroo-utils.git] / src / jexer / io / Screen.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2016 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.io;
30
31 import jexer.bits.Cell;
32 import jexer.bits.CellAttributes;
33 import jexer.bits.GraphicsChars;
34
35 /**
36 * This class represents a text-based screen. Drawing operations write to a
37 * logical screen.
38 */
39 public abstract class Screen {
40
41 /**
42 * Width of the visible window.
43 */
44 protected int width;
45
46 /**
47 * Height of the visible window.
48 */
49 protected int height;
50
51 /**
52 * Drawing offset for x.
53 */
54 private int offsetX;
55
56 /**
57 * Set drawing offset for x.
58 *
59 * @param offsetX new drawing offset
60 */
61 public final void setOffsetX(final int offsetX) {
62 this.offsetX = offsetX;
63 }
64
65 /**
66 * Drawing offset for y.
67 */
68 private int offsetY;
69
70 /**
71 * Set drawing offset for y.
72 *
73 * @param offsetY new drawing offset
74 */
75 public final void setOffsetY(final int offsetY) {
76 this.offsetY = offsetY;
77 }
78
79 /**
80 * Ignore anything drawn right of clipRight.
81 */
82 private int clipRight;
83
84 /**
85 * Get right drawing clipping boundary.
86 *
87 * @return drawing boundary
88 */
89 public final int getClipRight() {
90 return clipRight;
91 }
92
93 /**
94 * Set right drawing clipping boundary.
95 *
96 * @param clipRight new boundary
97 */
98 public final void setClipRight(final int clipRight) {
99 this.clipRight = clipRight;
100 }
101
102 /**
103 * Ignore anything drawn below clipBottom.
104 */
105 private int clipBottom;
106
107 /**
108 * Get bottom drawing clipping boundary.
109 *
110 * @return drawing boundary
111 */
112 public final int getClipBottom() {
113 return clipBottom;
114 }
115
116 /**
117 * Set bottom drawing clipping boundary.
118 *
119 * @param clipBottom new boundary
120 */
121 public final void setClipBottom(final int clipBottom) {
122 this.clipBottom = clipBottom;
123 }
124
125 /**
126 * Ignore anything drawn left of clipLeft.
127 */
128 private int clipLeft;
129
130 /**
131 * Get left drawing clipping boundary.
132 *
133 * @return drawing boundary
134 */
135 public final int getClipLeft() {
136 return clipLeft;
137 }
138
139 /**
140 * Set left drawing clipping boundary.
141 *
142 * @param clipLeft new boundary
143 */
144 public final void setClipLeft(final int clipLeft) {
145 this.clipLeft = clipLeft;
146 }
147
148 /**
149 * Ignore anything drawn above clipTop.
150 */
151 private int clipTop;
152
153 /**
154 * Get top drawing clipping boundary.
155 *
156 * @return drawing boundary
157 */
158 public final int getClipTop() {
159 return clipTop;
160 }
161
162 /**
163 * Set top drawing clipping boundary.
164 *
165 * @param clipTop new boundary
166 */
167 public final void setClipTop(final int clipTop) {
168 this.clipTop = clipTop;
169 }
170
171 /**
172 * The physical screen last sent out on flush().
173 */
174 protected Cell [][] physical;
175
176 /**
177 * The logical screen being rendered to.
178 */
179 protected Cell [][] logical;
180
181 /**
182 * When true, logical != physical.
183 */
184 protected volatile boolean dirty;
185
186 /**
187 * Get dirty flag.
188 *
189 * @return if true, the logical screen is not in sync with the physical
190 * screen
191 */
192 public final boolean isDirty() {
193 return dirty;
194 }
195
196 /**
197 * Set if the user explicitly wants to redraw everything starting with a
198 * ECMATerminal.clearAll().
199 */
200 protected boolean reallyCleared;
201
202 /**
203 * If true, the cursor is visible and should be placed onscreen at
204 * (cursorX, cursorY) during a call to flushPhysical().
205 */
206 protected boolean cursorVisible;
207
208 /**
209 * Cursor X position if visible.
210 */
211 protected int cursorX;
212
213 /**
214 * Cursor Y position if visible.
215 */
216 protected int cursorY;
217
218 /**
219 * Get the attributes at one location.
220 *
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)
224 */
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]);
229 }
230 return attr;
231 }
232
233 /**
234 * Set the attributes at one location.
235 *
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)
239 */
240 public final void putAttrXY(final int x, final int y,
241 final CellAttributes attr) {
242
243 putAttrXY(x, y, attr, true);
244 }
245
246 /**
247 * Set the attributes at one location.
248 *
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
253 */
254 public final void putAttrXY(final int x, final int y
255 , final CellAttributes attr, final boolean clip) {
256
257 int X = x;
258 int Y = y;
259
260 if (clip) {
261 if ((x < clipLeft)
262 || (x >= clipRight)
263 || (y < clipTop)
264 || (y >= clipBottom)
265 ) {
266 return;
267 }
268 X += offsetX;
269 Y += offsetY;
270 }
271
272 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
273 dirty = true;
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());
281 }
282 }
283
284 /**
285 * Fill the entire screen with one character with attributes.
286 *
287 * @param ch character to draw
288 * @param attr attributes to use (bold, foreColor, backColor)
289 */
290 public final void putAll(final char ch, final CellAttributes attr) {
291
292 for (int x = 0; x < width; x++) {
293 for (int y = 0; y < height; y++) {
294 putCharXY(x, y, ch, attr);
295 }
296 }
297 }
298
299 /**
300 * Render one character with attributes.
301 *
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
305 */
306 public final void putCharXY(final int x, final int y, final Cell ch) {
307 putCharXY(x, y, ch.getChar(), ch);
308 }
309
310 /**
311 * Render one character with attributes.
312 *
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)
317 */
318 public final void putCharXY(final int x, final int y, final char ch,
319 final CellAttributes attr) {
320
321 if ((x < clipLeft)
322 || (x >= clipRight)
323 || (y < clipTop)
324 || (y >= clipBottom)
325 ) {
326 return;
327 }
328
329 int X = x + offsetX;
330 int Y = y + offsetY;
331
332 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
333
334 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
335 dirty = true;
336
337 // Do not put control characters on the display
338 assert (ch >= 0x20);
339 assert (ch != 0x7F);
340
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());
349 }
350 }
351
352 /**
353 * Render one character without changing the underlying attributes.
354 *
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
358 */
359 public final void putCharXY(final int x, final int y, final char ch) {
360
361 if ((x < clipLeft)
362 || (x >= clipRight)
363 || (y < clipTop)
364 || (y >= clipBottom)
365 ) {
366 return;
367 }
368
369 int X = x + offsetX;
370 int Y = y + offsetY;
371
372 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
373
374 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
375 dirty = true;
376 logical[X][Y].setChar(ch);
377 }
378 }
379
380 /**
381 * Render a string. Does not wrap if the string exceeds the line.
382 *
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)
387 */
388 public final void putStringXY(final int x, final int y, final String str,
389 final CellAttributes attr) {
390
391 int i = x;
392 for (int j = 0; j < str.length(); j++) {
393 char ch = str.charAt(j);
394 putCharXY(i, y, ch, attr);
395 i++;
396 if (i == width) {
397 break;
398 }
399 }
400 }
401
402 /**
403 * Render a string without changing the underlying attribute. Does not
404 * wrap if the string exceeds the line.
405 *
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
409 */
410 public final void putStringXY(final int x, final int y, final String str) {
411
412 int i = x;
413 for (int j = 0; j < str.length(); j++) {
414 char ch = str.charAt(j);
415 putCharXY(i, y, ch);
416 i++;
417 if (i == width) {
418 break;
419 }
420 }
421 }
422
423 /**
424 * Draw a vertical line from (x, y) to (x, y + n).
425 *
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)
431 */
432 public final void vLineXY(final int x, final int y, final int n,
433 final char ch, final CellAttributes attr) {
434
435 for (int i = y; i < y + n; i++) {
436 putCharXY(x, i, ch, attr);
437 }
438 }
439
440 /**
441 * Draw a horizontal line from (x, y) to (x + n, y).
442 *
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)
448 */
449 public final void hLineXY(final int x, final int y, final int n,
450 final char ch, final CellAttributes attr) {
451
452 for (int i = x; i < x + n; i++) {
453 putCharXY(i, y, ch, attr);
454 }
455 }
456
457 /**
458 * Reallocate screen buffers.
459 *
460 * @param width new width
461 * @param height new height
462 */
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;
468 }
469 }
470 logical = null;
471 }
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;
477 }
478 }
479 physical = null;
480 }
481 physical = new Cell[width][height];
482
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();
487 }
488 }
489
490 this.width = width;
491 this.height = height;
492
493 clipLeft = 0;
494 clipTop = 0;
495 clipRight = width;
496 clipBottom = height;
497
498 reallyCleared = true;
499 dirty = true;
500 }
501
502 /**
503 * Change the width. Everything on-screen will be destroyed and must be
504 * redrawn.
505 *
506 * @param width new screen width
507 */
508 public final synchronized void setWidth(final int width) {
509 reallocate(width, this.height);
510 }
511
512 /**
513 * Change the height. Everything on-screen will be destroyed and must be
514 * redrawn.
515 *
516 * @param height new screen height
517 */
518 public final synchronized void setHeight(final int height) {
519 reallocate(this.width, height);
520 }
521
522 /**
523 * Change the width and height. Everything on-screen will be destroyed
524 * and must be redrawn.
525 *
526 * @param width new screen width
527 * @param height new screen height
528 */
529 public final void setDimensions(final int width, final int height) {
530 reallocate(width, height);
531 }
532
533 /**
534 * Get the height.
535 *
536 * @return current screen height
537 */
538 public final synchronized int getHeight() {
539 return this.height;
540 }
541
542 /**
543 * Get the width.
544 *
545 * @return current screen width
546 */
547 public final synchronized int getWidth() {
548 return this.width;
549 }
550
551 /**
552 * Public constructor. Sets everything to not-bold, white-on-black.
553 */
554 protected Screen() {
555 offsetX = 0;
556 offsetY = 0;
557 width = 80;
558 height = 24;
559 logical = null;
560 physical = null;
561 reallocate(width, height);
562 }
563
564 /**
565 * Reset screen to not-bold, white-on-black. Also flushes the offset and
566 * clip variables.
567 */
568 public final synchronized void reset() {
569 dirty = true;
570 for (int row = 0; row < height; row++) {
571 for (int col = 0; col < width; col++) {
572 logical[col][row].reset();
573 }
574 }
575 resetClipping();
576 }
577
578 /**
579 * Flush the offset and clip variables.
580 */
581 public final void resetClipping() {
582 offsetX = 0;
583 offsetY = 0;
584 clipLeft = 0;
585 clipTop = 0;
586 clipRight = width;
587 clipBottom = height;
588 }
589
590 /**
591 * Clear the logical screen.
592 */
593 public final void clear() {
594 reset();
595 }
596
597 /**
598 * Clear the physical screen.
599 */
600 public final void clearPhysical() {
601 dirty = true;
602 for (int row = 0; row < height; row++) {
603 for (int col = 0; col < width; col++) {
604 physical[col][row].reset();
605 }
606 }
607 }
608
609 /**
610 * Draw a box with a border and empty background.
611 *
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
618 */
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) {
622
623 drawBox(left, top, right, bottom, border, background, 1, false);
624 }
625
626 /**
627 * Draw a box with a border and empty background.
628 *
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
639 */
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) {
644
645 int boxWidth = right - left;
646 int boxHeight = bottom - top;
647
648 char cTopLeft;
649 char cTopRight;
650 char cBottomLeft;
651 char cBottomRight;
652 char cHSide;
653 char cVSide;
654
655 switch (borderType) {
656 case 1:
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;
663 break;
664
665 case 2:
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;
672 break;
673
674 case 3:
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;
681 break;
682 default:
683 throw new IllegalArgumentException("Invalid border type: "
684 + borderType);
685 }
686
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,
692 border);
693
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);
699
700 // Fill in the interior background
701 for (int i = 1; i < boxHeight - 1; i++) {
702 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
703 }
704
705 if (shadow) {
706 // Draw a shadow
707 drawBoxShadow(left, top, right, bottom);
708 }
709 }
710
711 /**
712 * Draw a box shadow.
713 *
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
718 */
719 public final void drawBoxShadow(final int left, final int top,
720 final int right, final int bottom) {
721
722 int boxTop = top;
723 int boxLeft = left;
724 int boxWidth = right - left;
725 int boxHeight = bottom - top;
726 CellAttributes shadowAttr = new CellAttributes();
727
728 // Shadows do not honor clipping but they DO honor offset.
729 int oldClipRight = clipRight;
730 int oldClipBottom = clipBottom;
731 /*
732 clipRight = boxWidth + 2;
733 clipBottom = boxHeight + 1;
734 */
735 clipRight = width;
736 clipBottom = height;
737
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);
741 }
742 for (int i = 0; i < boxWidth; i++) {
743 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
744 }
745 clipRight = oldClipRight;
746 clipBottom = oldClipBottom;
747 }
748
749 /**
750 * Subclasses must provide an implementation to push the logical screen
751 * to the physical device.
752 */
753 public abstract void flushPhysical();
754
755 /**
756 * Put the cursor at (x,y).
757 *
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
761 */
762 public void putCursor(final boolean visible, final int x, final int y) {
763
764 cursorVisible = visible;
765 cursorX = x;
766 cursorY = y;
767 }
768
769 /**
770 * Hide the cursor.
771 */
772 public final void hideCursor() {
773 cursorVisible = false;
774 }
775 }