Prep for 2019 release
[fanfix.git] / src / jexer / backend / LogicalScreen.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 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.backend;
30
31 import jexer.bits.Cell;
32 import jexer.bits.CellAttributes;
33 import jexer.bits.GraphicsChars;
34
35 /**
36 * A logical screen composed of a 2D array of Cells.
37 */
38 public class LogicalScreen implements Screen {
39
40 // ------------------------------------------------------------------------
41 // Variables --------------------------------------------------------------
42 // ------------------------------------------------------------------------
43
44 /**
45 * Width of the visible window.
46 */
47 protected int width;
48
49 /**
50 * Height of the visible window.
51 */
52 protected int height;
53
54 /**
55 * Drawing offset for x.
56 */
57 private int offsetX;
58
59 /**
60 * Drawing offset for y.
61 */
62 private int offsetY;
63
64 /**
65 * Ignore anything drawn right of clipRight.
66 */
67 private int clipRight;
68
69 /**
70 * Ignore anything drawn below clipBottom.
71 */
72 private int clipBottom;
73
74 /**
75 * Ignore anything drawn left of clipLeft.
76 */
77 private int clipLeft;
78
79 /**
80 * Ignore anything drawn above clipTop.
81 */
82 private int clipTop;
83
84 /**
85 * The physical screen last sent out on flush().
86 */
87 protected Cell [][] physical;
88
89 /**
90 * The logical screen being rendered to.
91 */
92 protected Cell [][] logical;
93
94 /**
95 * Set if the user explicitly wants to redraw everything starting with a
96 * ECMATerminal.clearAll().
97 */
98 protected boolean reallyCleared;
99
100 /**
101 * If true, the cursor is visible and should be placed onscreen at
102 * (cursorX, cursorY) during a call to flushPhysical().
103 */
104 protected boolean cursorVisible;
105
106 /**
107 * Cursor X position if visible.
108 */
109 protected int cursorX;
110
111 /**
112 * Cursor Y position if visible.
113 */
114 protected int cursorY;
115
116 // ------------------------------------------------------------------------
117 // Constructors -----------------------------------------------------------
118 // ------------------------------------------------------------------------
119
120 /**
121 * Public constructor. Sets everything to not-bold, white-on-black.
122 */
123 protected LogicalScreen() {
124 offsetX = 0;
125 offsetY = 0;
126 width = 80;
127 height = 24;
128 logical = null;
129 physical = null;
130 reallocate(width, height);
131 }
132
133 // ------------------------------------------------------------------------
134 // Screen -----------------------------------------------------------------
135 // ------------------------------------------------------------------------
136
137 /**
138 * Set drawing offset for x.
139 *
140 * @param offsetX new drawing offset
141 */
142 public final void setOffsetX(final int offsetX) {
143 this.offsetX = offsetX;
144 }
145
146 /**
147 * Set drawing offset for y.
148 *
149 * @param offsetY new drawing offset
150 */
151 public final void setOffsetY(final int offsetY) {
152 this.offsetY = offsetY;
153 }
154
155 /**
156 * Get right drawing clipping boundary.
157 *
158 * @return drawing boundary
159 */
160 public final int getClipRight() {
161 return clipRight;
162 }
163
164 /**
165 * Set right drawing clipping boundary.
166 *
167 * @param clipRight new boundary
168 */
169 public final void setClipRight(final int clipRight) {
170 this.clipRight = clipRight;
171 }
172
173 /**
174 * Get bottom drawing clipping boundary.
175 *
176 * @return drawing boundary
177 */
178 public final int getClipBottom() {
179 return clipBottom;
180 }
181
182 /**
183 * Set bottom drawing clipping boundary.
184 *
185 * @param clipBottom new boundary
186 */
187 public final void setClipBottom(final int clipBottom) {
188 this.clipBottom = clipBottom;
189 }
190
191 /**
192 * Get left drawing clipping boundary.
193 *
194 * @return drawing boundary
195 */
196 public final int getClipLeft() {
197 return clipLeft;
198 }
199
200 /**
201 * Set left drawing clipping boundary.
202 *
203 * @param clipLeft new boundary
204 */
205 public final void setClipLeft(final int clipLeft) {
206 this.clipLeft = clipLeft;
207 }
208
209 /**
210 * Get top drawing clipping boundary.
211 *
212 * @return drawing boundary
213 */
214 public final int getClipTop() {
215 return clipTop;
216 }
217
218 /**
219 * Set top drawing clipping boundary.
220 *
221 * @param clipTop new boundary
222 */
223 public final void setClipTop(final int clipTop) {
224 this.clipTop = clipTop;
225 }
226
227 /**
228 * Get dirty flag.
229 *
230 * @return if true, the logical screen is not in sync with the physical
231 * screen
232 */
233 public final boolean isDirty() {
234 for (int x = 0; x < width; x++) {
235 for (int y = 0; y < height; y++) {
236 if (!logical[x][y].equals(physical[x][y])) {
237 return true;
238 }
239 if (logical[x][y].isBlink()) {
240 // Blinking screens are always dirty. There is
241 // opportunity for a Netscape blink tag joke here...
242 return true;
243 }
244 }
245 }
246
247 return false;
248 }
249
250 /**
251 * Get the attributes at one location.
252 *
253 * @param x column coordinate. 0 is the left-most column.
254 * @param y row coordinate. 0 is the top-most row.
255 * @return attributes at (x, y)
256 */
257 public final CellAttributes getAttrXY(final int x, final int y) {
258 CellAttributes attr = new CellAttributes();
259 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
260 attr.setTo(logical[x][y]);
261 }
262 return attr;
263 }
264
265 /**
266 * Get the cell at one location.
267 *
268 * @param x column coordinate. 0 is the left-most column.
269 * @param y row coordinate. 0 is the top-most row.
270 * @return the character + attributes
271 */
272 public Cell getCharXY(final int x, final int y) {
273 Cell cell = new Cell();
274 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
275 cell.setTo(logical[x][y]);
276 }
277 return cell;
278 }
279
280 /**
281 * Set the attributes at one location.
282 *
283 * @param x column coordinate. 0 is the left-most column.
284 * @param y row coordinate. 0 is the top-most row.
285 * @param attr attributes to use (bold, foreColor, backColor)
286 */
287 public final void putAttrXY(final int x, final int y,
288 final CellAttributes attr) {
289
290 putAttrXY(x, y, attr, true);
291 }
292
293 /**
294 * Set the attributes at one location.
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 attr attributes to use (bold, foreColor, backColor)
299 * @param clip if true, honor clipping/offset
300 */
301 public final void putAttrXY(final int x, final int y,
302 final CellAttributes attr, final boolean clip) {
303
304 int X = x;
305 int Y = y;
306
307 if (clip) {
308 if ((x < clipLeft)
309 || (x >= clipRight)
310 || (y < clipTop)
311 || (y >= clipBottom)
312 ) {
313 return;
314 }
315 X += offsetX;
316 Y += offsetY;
317 }
318
319 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
320 logical[X][Y].setTo(attr);
321
322 // If this happens to be the cursor position, make the position
323 // dirty.
324 if ((cursorX == X) && (cursorY == Y)) {
325 physical[cursorX][cursorY].unset();
326 unsetImageRow(cursorY);
327 }
328 }
329 }
330
331 /**
332 * Fill the entire screen with one character with attributes.
333 *
334 * @param ch character to draw
335 * @param attr attributes to use (bold, foreColor, backColor)
336 */
337 public final void putAll(final char ch, final CellAttributes attr) {
338
339 for (int x = 0; x < width; x++) {
340 for (int y = 0; y < height; y++) {
341 putCharXY(x, y, ch, attr);
342 }
343 }
344 }
345
346 /**
347 * Render one character with attributes.
348 *
349 * @param x column coordinate. 0 is the left-most column.
350 * @param y row coordinate. 0 is the top-most row.
351 * @param ch character + attributes to draw
352 */
353 public final void putCharXY(final int x, final int y, final Cell ch) {
354 if ((x < clipLeft)
355 || (x >= clipRight)
356 || (y < clipTop)
357 || (y >= clipBottom)
358 ) {
359 return;
360 }
361
362 int X = x + offsetX;
363 int Y = y + offsetY;
364
365 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
366
367 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
368
369 // Do not put control characters on the display
370 if (!ch.isImage()) {
371 assert (ch.getChar() >= 0x20);
372 assert (ch.getChar() != 0x7F);
373 }
374 logical[X][Y].setTo(ch);
375
376 // If this happens to be the cursor position, make the position
377 // dirty.
378 if ((cursorX == X) && (cursorY == Y)) {
379 physical[cursorX][cursorY].unset();
380 unsetImageRow(cursorY);
381 }
382 }
383 }
384
385 /**
386 * Render one character with attributes.
387 *
388 * @param x column coordinate. 0 is the left-most column.
389 * @param y row coordinate. 0 is the top-most row.
390 * @param ch character to draw
391 * @param attr attributes to use (bold, foreColor, backColor)
392 */
393 public final void putCharXY(final int x, final int y, final char ch,
394 final CellAttributes attr) {
395
396 if ((x < clipLeft)
397 || (x >= clipRight)
398 || (y < clipTop)
399 || (y >= clipBottom)
400 ) {
401 return;
402 }
403
404 int X = x + offsetX;
405 int Y = y + offsetY;
406
407 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
408
409 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
410
411 // Do not put control characters on the display
412 assert (ch >= 0x20);
413 assert (ch != 0x7F);
414
415 logical[X][Y].setTo(attr);
416 logical[X][Y].setChar(ch);
417
418 // If this happens to be the cursor position, make the position
419 // dirty.
420 if ((cursorX == X) && (cursorY == Y)) {
421 physical[cursorX][cursorY].unset();
422 unsetImageRow(cursorY);
423 }
424 }
425 }
426
427 /**
428 * Render one character without changing the underlying attributes.
429 *
430 * @param x column coordinate. 0 is the left-most column.
431 * @param y row coordinate. 0 is the top-most row.
432 * @param ch character to draw
433 */
434 public final void putCharXY(final int x, final int y, final char ch) {
435
436 if ((x < clipLeft)
437 || (x >= clipRight)
438 || (y < clipTop)
439 || (y >= clipBottom)
440 ) {
441 return;
442 }
443
444 int X = x + offsetX;
445 int Y = y + offsetY;
446
447 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
448
449 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
450 logical[X][Y].setChar(ch);
451
452 // If this happens to be the cursor position, make the position
453 // dirty.
454 if ((cursorX == X) && (cursorY == Y)) {
455 physical[cursorX][cursorY].unset();
456 unsetImageRow(cursorY);
457 }
458 }
459 }
460
461 /**
462 * Render a string. Does not wrap if the string exceeds the line.
463 *
464 * @param x column coordinate. 0 is the left-most column.
465 * @param y row coordinate. 0 is the top-most row.
466 * @param str string to draw
467 * @param attr attributes to use (bold, foreColor, backColor)
468 */
469 public final void putStringXY(final int x, final int y, final String str,
470 final CellAttributes attr) {
471
472 int i = x;
473 for (int j = 0; j < str.length(); j++) {
474 char ch = str.charAt(j);
475 putCharXY(i, y, ch, attr);
476 i++;
477 if (i == width) {
478 break;
479 }
480 }
481 }
482
483 /**
484 * Render a string without changing the underlying attribute. Does not
485 * wrap if the string exceeds the line.
486 *
487 * @param x column coordinate. 0 is the left-most column.
488 * @param y row coordinate. 0 is the top-most row.
489 * @param str string to draw
490 */
491 public final void putStringXY(final int x, final int y, final String str) {
492
493 int i = x;
494 for (int j = 0; j < str.length(); j++) {
495 char ch = str.charAt(j);
496 putCharXY(i, y, ch);
497 i++;
498 if (i == width) {
499 break;
500 }
501 }
502 }
503
504 /**
505 * Draw a vertical line from (x, y) to (x, y + n).
506 *
507 * @param x column coordinate. 0 is the left-most column.
508 * @param y row coordinate. 0 is the top-most row.
509 * @param n number of characters to draw
510 * @param ch character to draw
511 * @param attr attributes to use (bold, foreColor, backColor)
512 */
513 public final void vLineXY(final int x, final int y, final int n,
514 final char ch, final CellAttributes attr) {
515
516 for (int i = y; i < y + n; i++) {
517 putCharXY(x, i, ch, attr);
518 }
519 }
520
521 /**
522 * Draw a horizontal line from (x, y) to (x + n, y).
523 *
524 * @param x column coordinate. 0 is the left-most column.
525 * @param y row coordinate. 0 is the top-most row.
526 * @param n number of characters to draw
527 * @param ch character to draw
528 * @param attr attributes to use (bold, foreColor, backColor)
529 */
530 public final void hLineXY(final int x, final int y, final int n,
531 final char ch, final CellAttributes attr) {
532
533 for (int i = x; i < x + n; i++) {
534 putCharXY(i, y, ch, attr);
535 }
536 }
537
538 /**
539 * Change the width. Everything on-screen will be destroyed and must be
540 * redrawn.
541 *
542 * @param width new screen width
543 */
544 public final synchronized void setWidth(final int width) {
545 reallocate(width, this.height);
546 }
547
548 /**
549 * Change the height. Everything on-screen will be destroyed and must be
550 * redrawn.
551 *
552 * @param height new screen height
553 */
554 public final synchronized void setHeight(final int height) {
555 reallocate(this.width, height);
556 }
557
558 /**
559 * Change the width and height. Everything on-screen will be destroyed
560 * and must be redrawn.
561 *
562 * @param width new screen width
563 * @param height new screen height
564 */
565 public final void setDimensions(final int width, final int height) {
566 reallocate(width, height);
567 }
568
569 /**
570 * Get the height.
571 *
572 * @return current screen height
573 */
574 public final synchronized int getHeight() {
575 return this.height;
576 }
577
578 /**
579 * Get the width.
580 *
581 * @return current screen width
582 */
583 public final synchronized int getWidth() {
584 return this.width;
585 }
586
587 /**
588 * Reset screen to not-bold, white-on-black. Also flushes the offset and
589 * clip variables.
590 */
591 public final synchronized void reset() {
592 for (int row = 0; row < height; row++) {
593 for (int col = 0; col < width; col++) {
594 logical[col][row].reset();
595 }
596 }
597 resetClipping();
598 }
599
600 /**
601 * Flush the offset and clip variables.
602 */
603 public final void resetClipping() {
604 offsetX = 0;
605 offsetY = 0;
606 clipLeft = 0;
607 clipTop = 0;
608 clipRight = width;
609 clipBottom = height;
610 }
611
612 /**
613 * Clear the logical screen.
614 */
615 public final void clear() {
616 reset();
617 }
618
619 /**
620 * Draw a box with a border and empty background.
621 *
622 * @param left left column of box. 0 is the left-most column.
623 * @param top top row of the box. 0 is the top-most row.
624 * @param right right column of box
625 * @param bottom bottom row of the box
626 * @param border attributes to use for the border
627 * @param background attributes to use for the background
628 */
629 public final void drawBox(final int left, final int top,
630 final int right, final int bottom,
631 final CellAttributes border, final CellAttributes background) {
632
633 drawBox(left, top, right, bottom, border, background, 1, false);
634 }
635
636 /**
637 * Draw a box with a border and empty background.
638 *
639 * @param left left column of box. 0 is the left-most column.
640 * @param top top row of the box. 0 is the top-most row.
641 * @param right right column of box
642 * @param bottom bottom row of the box
643 * @param border attributes to use for the border
644 * @param background attributes to use for the background
645 * @param borderType if 1, draw a single-line border; if 2, draw a
646 * double-line border; if 3, draw double-line top/bottom edges and
647 * single-line left/right edges (like Qmodem)
648 * @param shadow if true, draw a "shadow" on the box
649 */
650 public final void drawBox(final int left, final int top,
651 final int right, final int bottom,
652 final CellAttributes border, final CellAttributes background,
653 final int borderType, final boolean shadow) {
654
655 int boxWidth = right - left;
656 int boxHeight = bottom - top;
657
658 char cTopLeft;
659 char cTopRight;
660 char cBottomLeft;
661 char cBottomRight;
662 char cHSide;
663 char cVSide;
664
665 switch (borderType) {
666 case 1:
667 cTopLeft = GraphicsChars.ULCORNER;
668 cTopRight = GraphicsChars.URCORNER;
669 cBottomLeft = GraphicsChars.LLCORNER;
670 cBottomRight = GraphicsChars.LRCORNER;
671 cHSide = GraphicsChars.SINGLE_BAR;
672 cVSide = GraphicsChars.WINDOW_SIDE;
673 break;
674
675 case 2:
676 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
677 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
678 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
679 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
680 cHSide = GraphicsChars.DOUBLE_BAR;
681 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
682 break;
683
684 case 3:
685 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
686 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
687 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
688 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
689 cHSide = GraphicsChars.WINDOW_TOP;
690 cVSide = GraphicsChars.WINDOW_SIDE;
691 break;
692 default:
693 throw new IllegalArgumentException("Invalid border type: "
694 + borderType);
695 }
696
697 // Place the corner characters
698 putCharXY(left, top, cTopLeft, border);
699 putCharXY(left + boxWidth - 1, top, cTopRight, border);
700 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
701 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
702 border);
703
704 // Draw the box lines
705 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
706 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
707 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
708 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
709
710 // Fill in the interior background
711 for (int i = 1; i < boxHeight - 1; i++) {
712 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
713 }
714
715 if (shadow) {
716 // Draw a shadow
717 drawBoxShadow(left, top, right, bottom);
718 }
719 }
720
721 /**
722 * Draw a box shadow.
723 *
724 * @param left left column of box. 0 is the left-most column.
725 * @param top top row of the box. 0 is the top-most row.
726 * @param right right column of box
727 * @param bottom bottom row of the box
728 */
729 public final void drawBoxShadow(final int left, final int top,
730 final int right, final int bottom) {
731
732 int boxTop = top;
733 int boxLeft = left;
734 int boxWidth = right - left;
735 int boxHeight = bottom - top;
736 CellAttributes shadowAttr = new CellAttributes();
737
738 // Shadows do not honor clipping but they DO honor offset.
739 int oldClipRight = clipRight;
740 int oldClipBottom = clipBottom;
741 // When offsetX or offsetY go negative, we need to increase the clip
742 // bounds.
743 clipRight = width - offsetX;
744 clipBottom = height - offsetY;
745
746 for (int i = 0; i < boxHeight; i++) {
747 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
748 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
749 }
750 for (int i = 0; i < boxWidth; i++) {
751 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
752 }
753 clipRight = oldClipRight;
754 clipBottom = oldClipBottom;
755 }
756
757 /**
758 * Default implementation does nothing.
759 */
760 public void flushPhysical() {}
761
762 /**
763 * Put the cursor at (x,y).
764 *
765 * @param visible if true, the cursor should be visible
766 * @param x column coordinate to put the cursor on
767 * @param y row coordinate to put the cursor on
768 */
769 public void putCursor(final boolean visible, final int x, final int y) {
770 if ((cursorY >= 0)
771 && (cursorX >= 0)
772 && (cursorY <= height - 1)
773 && (cursorX <= width - 1)
774 ) {
775 // Make the current cursor position dirty
776 physical[cursorX][cursorY].unset();
777 unsetImageRow(cursorY);
778 }
779
780 cursorVisible = visible;
781 cursorX = x;
782 cursorY = y;
783 }
784
785 /**
786 * Hide the cursor.
787 */
788 public final void hideCursor() {
789 cursorVisible = false;
790 }
791
792 /**
793 * Get the cursor visibility.
794 *
795 * @return true if the cursor is visible
796 */
797 public boolean isCursorVisible() {
798 return cursorVisible;
799 }
800
801 /**
802 * Get the cursor X position.
803 *
804 * @return the cursor x column position
805 */
806 public int getCursorX() {
807 return cursorX;
808 }
809
810 /**
811 * Get the cursor Y position.
812 *
813 * @return the cursor y row position
814 */
815 public int getCursorY() {
816 return cursorY;
817 }
818
819 /**
820 * Set the window title. Default implementation does nothing.
821 *
822 * @param title the new title
823 */
824 public void setTitle(final String title) {}
825
826 // ------------------------------------------------------------------------
827 // LogicalScreen ----------------------------------------------------------
828 // ------------------------------------------------------------------------
829
830 /**
831 * Reallocate screen buffers.
832 *
833 * @param width new width
834 * @param height new height
835 */
836 private synchronized void reallocate(final int width, final int height) {
837 if (logical != null) {
838 for (int row = 0; row < this.height; row++) {
839 for (int col = 0; col < this.width; col++) {
840 logical[col][row] = null;
841 }
842 }
843 logical = null;
844 }
845 logical = new Cell[width][height];
846 if (physical != null) {
847 for (int row = 0; row < this.height; row++) {
848 for (int col = 0; col < this.width; col++) {
849 physical[col][row] = null;
850 }
851 }
852 physical = null;
853 }
854 physical = new Cell[width][height];
855
856 for (int row = 0; row < height; row++) {
857 for (int col = 0; col < width; col++) {
858 physical[col][row] = new Cell();
859 logical[col][row] = new Cell();
860 }
861 }
862
863 this.width = width;
864 this.height = height;
865
866 clipLeft = 0;
867 clipTop = 0;
868 clipRight = width;
869 clipBottom = height;
870
871 reallyCleared = true;
872 }
873
874 /**
875 * Clear the physical screen.
876 */
877 public final void clearPhysical() {
878 for (int row = 0; row < height; row++) {
879 for (int col = 0; col < width; col++) {
880 physical[col][row].unset();
881 }
882 }
883 }
884
885 /**
886 * Unset every image cell on one row of the physical screen, forcing
887 * images on that row to be redrawn.
888 *
889 * @param y row coordinate. 0 is the top-most row.
890 */
891 public final void unsetImageRow(final int y) {
892 for (int x = 0; x < width; x++) {
893 if (logical[x][y].isImage()) {
894 physical[x][y].unset();
895 }
896 }
897 }
898
899 }