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