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