base data structures
[fanfix.git] / src / jexer / io / Screen.java
CommitLineData
df8de03f
KL
1/**
2 * Jexer - Java Text User Interface
3 *
4 * Version: $Id$
5 *
6 * Author: Kevin Lamonte, <a href="mailto:kevin.lamonte@gmail.com">kevin.lamonte@gmail.com</a>
7 *
8 * License: LGPLv3 or later
9 *
10 * Copyright: This module is licensed under the GNU Lesser General
11 * Public License Version 3. Please see the file "COPYING" in this
12 * directory for more information about the GNU Lesser General Public
13 * License Version 3.
14 *
15 * Copyright (C) 2015 Kevin Lamonte
16 *
17 * This program is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public License
19 * as published by the Free Software Foundation; either version 3 of
20 * the License, or (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this program; if not, see
29 * http://www.gnu.org/licenses/, or write to the Free Software
30 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31 * 02110-1301 USA
32 */
33package jexer.io;
34
35import jexer.bits.Cell;
36import jexer.bits.CellAttributes;
37import jexer.bits.GraphicsChars;
38
39/**
40 * This class represents a text-based screen. Drawing operations write to a
41 * logical screen.
42 */
43public abstract class Screen {
44
45 /**
46 * Emit debugging to stderr
47 */
48 public boolean debugToStderr;
49
50 /**
51 * Width of the visible window
52 */
53 protected int width;
54
55 /**
56 * Height of the visible window
57 */
58 protected int height;
59
60 /**
61 * Drawing offset for x
62 */
63 public int offsetX;
64
65 /**
66 * Drawing offset for y
67 */
68 public int offsetY;
69
70 /**
71 * Ignore anything drawn right of clipRight
72 */
73 public int clipRight;
74
75 /**
76 * Ignore anything drawn below clipBottom
77 */
78 public int clipBottom;
79
80 /**
81 * Ignore anything drawn left of clipLeft
82 */
83 public int clipLeft;
84
85 /**
86 * Ignore anything drawn above clipTop
87 */
88 public int clipTop;
89
90 /**
91 * The physical screen last sent out on flush()
92 */
93 protected Cell [][] physical;
94
95 /**
96 * The logical screen being rendered to
97 */
98 protected Cell [][] logical;
99
100 /**
101 * When true, logical != physical
102 */
103 public boolean dirty;
104
105 /**
106 * Set if the user explicitly wants to redraw everything starting with a
107 * ECMATerminal.clearAll()
108 */
109 protected boolean reallyCleared;
110
111 /**
112 * If true, the cursor is visible and should be placed onscreen at
113 * (cursorX, cursorY) during a call to flushPhysical()
114 */
115 protected boolean cursorVisible;
116
117 /**
118 * Cursor X position if visible
119 */
120 protected int cursorX;
121
122 /**
123 * Cursor Y position if visible
124 */
125 protected int cursorY;
126
127 /**
128 * Get the attributes at one location.
129 *
130 * @param x column coordinate. 0 is the left-most column.
131 * @param y row coordinate. 0 is the top-most row.
132 * @return attributes at (x, y)
133 */
134 public CellAttributes getAttrXY(int x, int y) {
135 CellAttributes attr = new CellAttributes();
136 attr.setTo(logical[x][y]);
137 return attr;
138 }
139
140 /**
141 * Set the attributes at one location.
142 *
143 * @param x column coordinate. 0 is the left-most column.
144 * @param y row coordinate. 0 is the top-most row.
145 * @param attr attributes to use (bold, foreColor, backColor)
146 * @param clip if true, honor clipping/offset
147 */
148 public void putAttrXY(int x, int y, CellAttributes attr) {
149 putAttrXY(x, y, attr, true);
150 }
151
152 /**
153 * Set the attributes at one location.
154 *
155 * @param x column coordinate. 0 is the left-most column.
156 * @param y row coordinate. 0 is the top-most row.
157 * @param attr attributes to use (bold, foreColor, backColor)
158 * @param clip if true, honor clipping/offset
159 */
160 public void putAttrXY(int x, int y, CellAttributes attr, boolean clip) {
161
162 int X = x;
163 int Y = y;
164
165 if (clip) {
166 if ((x < clipLeft) || (x >= clipRight) ||
167 (y < clipTop) || (y >= clipBottom)) {
168 return;
169 }
170 X += offsetX;
171 Y += offsetY;
172 }
173
174 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
175 dirty = true;
176 logical[X][Y].foreColor = attr.foreColor;
177 logical[X][Y].backColor = attr.backColor;
178 logical[X][Y].bold = attr.bold;
179 logical[X][Y].blink = attr.blink;
180 logical[X][Y].reverse = attr.reverse;
181 logical[X][Y].underline = attr.underline;
182 logical[X][Y].protect = attr.protect;
183 }
184 }
185
186 /**
187 * Fill the entire screen with one character with attributes.
188 *
189 * @param ch character to draw
190 * @param attr attributes to use (bold, foreColor, backColor)
191 */
192 public void putAll(char ch, CellAttributes attr) {
193 for (int x = 0; x < width; x++) {
194 for (int y = 0; y < height; y++) {
195 putCharXY(x, y, ch, attr);
196 }
197 }
198 }
199
200 /**
201 * Render one character with attributes.
202 *
203 * @param x column coordinate. 0 is the left-most column.
204 * @param y row coordinate. 0 is the top-most row.
205 * @param ch character + attributes to draw
206 */
207 public void putCharXY(int x, int y, Cell ch) {
208 putCharXY(x, y, ch.ch, ch);
209 }
210
211 /**
212 * Render one character with attributes.
213 *
214 * @param x column coordinate. 0 is the left-most column.
215 * @param y row coordinate. 0 is the top-most row.
216 * @param ch character to draw
217 * @param attr attributes to use (bold, foreColor, backColor)
218 */
219 public void putCharXY(int x, int y, char ch, CellAttributes attr) {
220 if ((x < clipLeft) || (x >= clipRight) ||
221 (y < clipTop) || (y >= clipBottom)) {
222 return;
223 }
224
225 int X = x + offsetX;
226 int Y = y + offsetY;
227
228 // stderr.writefln("putCharXY: %d, %d, %c", X, Y, ch);
229
230 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
231 dirty = true;
232
233 // Do not put control characters on the display
234 assert(ch >= 0x20);
235 assert(ch != 0x7F);
236
237 logical[X][Y].ch = ch;
238 logical[X][Y].foreColor = attr.foreColor;
239 logical[X][Y].backColor = attr.backColor;
240 logical[X][Y].bold = attr.bold;
241 logical[X][Y].blink = attr.blink;
242 logical[X][Y].reverse = attr.reverse;
243 logical[X][Y].underline = attr.underline;
244 logical[X][Y].protect = attr.protect;
245 }
246 }
247
248 /**
249 * Render one character without changing the underlying attributes.
250 *
251 * @param x column coordinate. 0 is the left-most column.
252 * @param y row coordinate. 0 is the top-most row.
253 * @param ch character to draw
254 */
255 public void putCharXY(int x, int y, char ch) {
256 if ((x < clipLeft) || (x >= clipRight) ||
257 (y < clipTop) || (y >= clipBottom)) {
258 return;
259 }
260
261 int X = x + offsetX;
262 int Y = y + offsetY;
263
264 // stderr.writefln("putCharXY: %d, %d, %c", X, Y, ch);
265
266 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
267 dirty = true;
268 logical[X][Y].ch = ch;
269 }
270 }
271
272 /**
273 * Render a string. Does not wrap if the string exceeds the line.
274 *
275 * @param x column coordinate. 0 is the left-most column.
276 * @param y row coordinate. 0 is the top-most row.
277 * @param str string to draw
278 * @param attr attributes to use (bold, foreColor, backColor)
279 */
280 public void putStrXY(int x, int y, String str, CellAttributes attr) {
281 int i = x;
282 for (int j = 0; j < str.length(); j++) {
283 char ch = str.charAt(j);
284 putCharXY(i, y, ch, attr);
285 i++;
286 if (i == width) {
287 break;
288 }
289 }
290 }
291
292 /**
293 * Render a string without changing the underlying attribute. Does not
294 * wrap if the string exceeds the line.
295 *
296 * @param x column coordinate. 0 is the left-most column.
297 * @param y row coordinate. 0 is the top-most row.
298 * @param str string to draw
299 */
300 public void putStrXY(int x, int y, String str) {
301 int i = x;
302 for (int j = 0; j < str.length(); j++) {
303 char ch = str.charAt(j);
304 putCharXY(i, y, ch);
305 i++;
306 if (i == width) {
307 break;
308 }
309 }
310 }
311
312 /**
313 * Draw a vertical line from (x, y) to (x, y + n)
314 *
315 * @param x column coordinate. 0 is the left-most column.
316 * @param y row coordinate. 0 is the top-most row.
317 * @param n number of characters to draw
318 * @param ch character to draw
319 * @param attr attributes to use (bold, foreColor, backColor)
320 */
321 public void vLineXY(int x, int y, int n, char ch, CellAttributes attr) {
322 for (int i = y; i < y + n; i++) {
323 putCharXY(x, i, ch, attr);
324 }
325 }
326
327 /**
328 * Draw a horizontal line from (x, y) to (x + n, y)
329 *
330 * @param x column coordinate. 0 is the left-most column.
331 * @param y row coordinate. 0 is the top-most row.
332 * @param n number of characters to draw
333 * @param ch character to draw
334 * @param attr attributes to use (bold, foreColor, backColor)
335 */
336 public void hLineXY(int x, int y, int n, char ch, CellAttributes attr) {
337 for (int i = x; i < x + n; i++) {
338 putCharXY(i, y, ch, attr);
339 }
340 }
341
342 /**
343 * Reallocate screen buffers.
344 *
345 * @param width new width
346 * @param height new height
347 */
348 private void reallocate(int width, int height) {
349 if (logical != null) {
350 for (int row = 0; row < this.height; row++) {
351 for (int col = 0; col < this.width; col++) {
352 logical[col][row] = null;
353 }
354 }
355 logical = null;
356 }
357 logical = new Cell[width][height];
358 if (physical != null) {
359 for (int row = 0; row < this.height; row++) {
360 for (int col = 0; col < this.width; col++) {
361 physical[col][row] = null;
362 }
363 }
364 physical = null;
365 }
366 physical = new Cell[width][height];
367
368 for (int row = 0; row < height; row++) {
369 for (int col = 0; col < width; col++) {
370 physical[col][row] = new Cell();
371 logical[col][row] = new Cell();
372 }
373 }
374
375 this.width = width;
376 this.height = height;
377
378 clipLeft = 0;
379 clipTop = 0;
380 clipRight = width;
381 clipBottom = height;
382
383 reallyCleared = true;
384 dirty = true;
385 }
386
387 /**
388 * Change the width. Everything on-screen will be destroyed and must be
389 * redrawn.
390 *
391 * @param width new screen width
392 */
393 public void setWidth(int width) {
394 reallocate(width, this.height);
395 }
396
397 /**
398 * Change the height. Everything on-screen will be destroyed and must be
399 * redrawn.
400 *
401 * @param height new screen height
402 */
403 public void setHeight(int height) {
404 reallocate(this.width, height);
405 }
406
407 /**
408 * Change the width and height. Everything on-screen will be destroyed
409 * and must be redrawn.
410 *
411 * @param width new screen width
412 * @param height new screen height
413 */
414 public void setDimensions(int width, int height) {
415 reallocate(width, height);
416 }
417
418 /**
419 * Get the height.
420 *
421 * @return current screen height
422 */
423 public int getHeight() {
424 return this.height;
425 }
426
427 /**
428 * Get the width.
429 *
430 * @return current screen width
431 */
432 public int getWidth() {
433 return this.width;
434 }
435
436 /**
437 * Public constructor. Sets everything to not-bold, white-on-black.
438 */
439 public Screen() {
440 debugToStderr = false;
441
442 offsetX = 0;
443 offsetY = 0;
444 width = 80;
445 height = 24;
446 logical = null;
447 physical = null;
448 reallocate(width, height);
449 }
450
451 /**
452 * Reset screen to not-bold, white-on-black. Also flushes the offset and
453 * clip variables.
454 */
455 public void reset() {
456 dirty = true;
457 for (int row = 0; row < height; row++) {
458 for (int col = 0; col < width; col++) {
459 logical[col][row].reset();
460 }
461 }
462 resetClipping();
463 }
464
465 /**
466 * Flush the offset and clip variables.
467 */
468 public void resetClipping() {
469 offsetX = 0;
470 offsetY = 0;
471 clipLeft = 0;
472 clipTop = 0;
473 clipRight = width;
474 clipBottom = height;
475 }
476
477 /**
478 * Force the screen to be fully cleared and redrawn on the next flush().
479 */
480 public void clear() {
481 reset();
482 }
483
484 /**
485 * Draw a box with a border and empty background.
486 *
487 * @param left left column of box. 0 is the left-most row.
488 * @param top top row of the box. 0 is the top-most row.
489 * @param right right column of box
490 * @param bottom bottom row of the box
491 * @param border attributes to use for the border (bold, foreColor, backColor)
492 * @param background attributes to use for the background
493 */
494 public void drawBox(int left, int top, int right, int bottom,
495 CellAttributes border, CellAttributes background) {
496 drawBox(left, top, right, bottom, border, background, 1, false);
497 }
498
499 /**
500 * Draw a box with a border and empty background.
501 *
502 * @param left left column of box. 0 is the left-most row.
503 * @param top top row of the box. 0 is the top-most row.
504 * @param right right column of box
505 * @param bottom bottom row of the box
506 * @param border attributes to use for the border (bold, foreColor, backColor)
507 * @param background attributes to use for the background
508 * @param borderType = 1: single-line border
509 * 2: double-line borders
510 * 3: double-line top/bottom edges and single-line left/right edges
511 * @param shadow if true, draw a "shadow" on the box
512 */
513 public void drawBox(int left, int top, int right, int bottom,
514 CellAttributes border, CellAttributes background, int borderType,
515 boolean shadow) {
516
517 int boxTop = top;
518 int boxLeft = left;
519 int boxWidth = right - left;
520 int boxHeight = bottom - top;
521
522 char cTopLeft;
523 char cTopRight;
524 char cBottomLeft;
525 char cBottomRight;
526 char cHSide;
527 char cVSide;
528
529 switch (borderType) {
530 case 1:
531 cTopLeft = GraphicsChars.ULCORNER;
532 cTopRight = GraphicsChars.URCORNER;
533 cBottomLeft = GraphicsChars.LLCORNER;
534 cBottomRight = GraphicsChars.LRCORNER;
535 cHSide = GraphicsChars.SINGLE_BAR;
536 cVSide = GraphicsChars.WINDOW_SIDE;
537 break;
538
539 case 2:
540 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
541 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
542 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
543 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
544 cHSide = GraphicsChars.DOUBLE_BAR;
545 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
546 break;
547
548 case 3:
549 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
550 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
551 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
552 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
553 cHSide = GraphicsChars.WINDOW_TOP;
554 cVSide = GraphicsChars.WINDOW_SIDE;
555 break;
556 default:
557 throw new IllegalArgumentException("Invalid border type: " + borderType);
558 }
559
560 // Place the corner characters
561 putCharXY(left, top, cTopLeft, border);
562 putCharXY(left + boxWidth - 1, top, cTopRight, border);
563 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
564 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
565 border);
566
567 // Draw the box lines
568 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
569 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
570 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
571 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
572
573 // Fill in the interior background
574 for (int i = 1; i < boxHeight - 1; i++) {
575 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
576 }
577
578 if (shadow) {
579 // Draw a shadow
580 drawBoxShadow(left, top, right, bottom);
581 }
582 }
583
584 /**
585 * Draw a box shadow
586 *
587 * @param left left column of box. 0 is the left-most row.
588 * @param top top row of the box. 0 is the top-most row.
589 * @param right right column of box
590 * @param bottom bottom row of the box
591 */
592 public void drawBoxShadow(int left, int top, int right, int bottom) {
593
594 int boxTop = top;
595 int boxLeft = left;
596 int boxWidth = right - left;
597 int boxHeight = bottom - top;
598 CellAttributes shadowAttr = new CellAttributes();
599
600 // Shadows do not honor clipping but they DO honor offset.
601 int oldClipRight = clipRight;
602 int oldClipBottom = clipBottom;
603 /*
604 clipRight = boxWidth + 2;
605 clipBottom = boxHeight + 1;
606 */
607 clipRight = width;
608 clipBottom = height;
609
610 for (int i = 0; i < boxHeight; i++) {
611 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
612 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
613 }
614 for (int i = 0; i < boxWidth; i++) {
615 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
616 }
617 clipRight = oldClipRight;
618 clipBottom = oldClipBottom;
619 }
620
621 /**
622 * Subclasses must provide an implementation to push the logical screen
623 * to the physical device.
624 */
625 abstract public void flushPhysical();
626
627 /**
628 * Put the cursor at (x,y).
629 *
630 * @param visible if true, the cursor should be visible
631 * @param x column coordinate to put the cursor on
632 * @param y row coordinate to put the cursor on
633 */
634 public void putCursor(boolean visible, int x, int y) {
635 cursorVisible = visible;
636 cursorX = x;
637 cursorY = y;
638 }
639
640 /**
641 * Hide the cursor
642 */
643 public void hideCursor() {
644 cursorVisible = false;
645 }
646}