immutable TMouseEvent
[fanfix.git] / src / jexer / io / Screen.java
CommitLineData
df8de03f
KL
1/**
2 * Jexer - Java Text User Interface
3 *
df8de03f
KL
4 * License: LGPLv3 or later
5 *
7b5261bc
KL
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.
df8de03f
KL
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
7b5261bc
KL
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
df8de03f
KL
30 */
31package jexer.io;
32
33import jexer.bits.Cell;
34import jexer.bits.CellAttributes;
35import jexer.bits.GraphicsChars;
36
37/**
38 * This class represents a text-based screen. Drawing operations write to a
39 * logical screen.
40 */
41public abstract class Screen {
42
43 /**
7b5261bc 44 * Width of the visible window.
df8de03f
KL
45 */
46 protected int width;
47
48 /**
7b5261bc 49 * Height of the visible window.
df8de03f
KL
50 */
51 protected int height;
52
53 /**
7b5261bc 54 * Drawing offset for x.
df8de03f
KL
55 */
56 public int offsetX;
57
58 /**
7b5261bc 59 * Drawing offset for y.
df8de03f
KL
60 */
61 public int offsetY;
62
63 /**
7b5261bc 64 * Ignore anything drawn right of clipRight.
df8de03f
KL
65 */
66 public int clipRight;
67
68 /**
7b5261bc 69 * Ignore anything drawn below clipBottom.
df8de03f
KL
70 */
71 public int clipBottom;
72
73 /**
7b5261bc 74 * Ignore anything drawn left of clipLeft.
df8de03f
KL
75 */
76 public int clipLeft;
77
78 /**
7b5261bc 79 * Ignore anything drawn above clipTop.
df8de03f
KL
80 */
81 public int clipTop;
82
83 /**
7b5261bc 84 * The physical screen last sent out on flush().
df8de03f
KL
85 */
86 protected Cell [][] physical;
87
88 /**
7b5261bc 89 * The logical screen being rendered to.
df8de03f
KL
90 */
91 protected Cell [][] logical;
92
93 /**
7b5261bc 94 * When true, logical != physical.
df8de03f
KL
95 */
96 public boolean dirty;
97
98 /**
99 * Set if the user explicitly wants to redraw everything starting with a
7b5261bc 100 * ECMATerminal.clearAll().
df8de03f
KL
101 */
102 protected boolean reallyCleared;
103
104 /**
105 * If true, the cursor is visible and should be placed onscreen at
7b5261bc 106 * (cursorX, cursorY) during a call to flushPhysical().
df8de03f
KL
107 */
108 protected boolean cursorVisible;
109
110 /**
7b5261bc 111 * Cursor X position if visible.
df8de03f
KL
112 */
113 protected int cursorX;
114
115 /**
7b5261bc 116 * Cursor Y position if visible.
df8de03f
KL
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 */
7b5261bc
KL
127 public CellAttributes getAttrXY(final int x, final int y) {
128 CellAttributes attr = new CellAttributes();
129 attr.setTo(logical[x][y]);
130 return attr;
df8de03f
KL
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)
df8de03f 139 */
7b5261bc
KL
140 public void putAttrXY(final int x, final int y, final CellAttributes attr) {
141 putAttrXY(x, y, attr, true);
df8de03f 142 }
7b5261bc 143
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f
KL
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 */
7b5261bc
KL
203 public void putCharXY(final int x, final int y, final Cell ch) {
204 putCharXY(x, y, ch.getChar(), ch);
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f 264
7b5261bc
KL
265 int X = x + offsetX;
266 int Y = y + offsetY;
df8de03f 267
7b5261bc 268 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
df8de03f 269
7b5261bc
KL
270 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
271 dirty = true;
272 logical[X][Y].setChar(ch);
273 }
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f
KL
316 }
317
318 /**
7b5261bc 319 * Draw a vertical line from (x, y) to (x, y + n).
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f
KL
333 }
334
335 /**
7b5261bc 336 * Draw a horizontal line from (x, y) to (x + n, y).
df8de03f
KL
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 */
7b5261bc
KL
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 }
df8de03f
KL
350 }
351
352 /**
353 * Reallocate screen buffers.
354 *
355 * @param width new width
356 * @param height new height
357 */
7b5261bc
KL
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;
df8de03f
KL
395 }
396
397 /**
398 * Change the width. Everything on-screen will be destroyed and must be
399 * redrawn.
7b5261bc 400 *
df8de03f
KL
401 * @param width new screen width
402 */
7b5261bc
KL
403 public void setWidth(final int width) {
404 reallocate(width, this.height);
df8de03f
KL
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 */
7b5261bc
KL
413 public void setHeight(final int height) {
414 reallocate(this.width, height);
df8de03f
KL
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 */
7b5261bc
KL
424 public void setDimensions(final int width, final int height) {
425 reallocate(width, height);
df8de03f
KL
426 }
427
428 /**
429 * Get the height.
430 *
431 * @return current screen height
432 */
433 public int getHeight() {
7b5261bc 434 return this.height;
df8de03f
KL
435 }
436
437 /**
438 * Get the width.
439 *
440 * @return current screen width
441 */
442 public int getWidth() {
7b5261bc 443 return this.width;
df8de03f
KL
444 }
445
446 /**
447 * Public constructor. Sets everything to not-bold, white-on-black.
448 */
449 public Screen() {
7b5261bc
KL
450 offsetX = 0;
451 offsetY = 0;
452 width = 80;
453 height = 24;
454 logical = null;
455 physical = null;
456 reallocate(width, height);
df8de03f
KL
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() {
7b5261bc
KL
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();
df8de03f
KL
471 }
472
473 /**
474 * Flush the offset and clip variables.
475 */
476 public void resetClipping() {
7b5261bc
KL
477 offsetX = 0;
478 offsetY = 0;
479 clipLeft = 0;
480 clipTop = 0;
481 clipRight = width;
482 clipBottom = height;
df8de03f
KL
483 }
484
485 /**
486 * Force the screen to be fully cleared and redrawn on the next flush().
487 */
488 public void clear() {
7b5261bc 489 reset();
df8de03f
KL
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
7b5261bc 499 * @param border attributes to use for the border
df8de03f
KL
500 * @param background attributes to use for the background
501 */
7b5261bc
KL
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);
df8de03f
KL
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
7b5261bc 516 * @param border attributes to use for the border
df8de03f 517 * @param background attributes to use for the background
7b5261bc
KL
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)
df8de03f
KL
521 * @param shadow if true, draw a "shadow" on the box
522 */
7b5261bc
KL
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 }
df8de03f
KL
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 */
7b5261bc
KL
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;
df8de03f
KL
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 */
7b5261bc
KL
647 public void putCursor(final boolean visible, final int x, final int y) {
648 cursorVisible = visible;
649 cursorX = x;
650 cursorY = y;
df8de03f
KL
651 }
652
653 /**
654 * Hide the cursor
655 */
656 public void hideCursor() {
7b5261bc 657 cursorVisible = false;
df8de03f
KL
658 }
659}