1 package com
.googlecode
.lanterna
.screen
;
3 import com
.googlecode
.lanterna
.*;
4 import com
.googlecode
.lanterna
.graphics
.TextGraphics
;
5 import com
.googlecode
.lanterna
.input
.KeyStroke
;
6 import com
.googlecode
.lanterna
.input
.KeyType
;
8 import java
.io
.IOException
;
11 * VirtualScreen wraps a normal screen and presents it as a screen that has a configurable minimum size; if the real
12 * screen is smaller than this size, the presented screen will add scrolling to get around it. To anyone using this
13 * class, it will appear and behave just as a normal screen. Scrolling is done by using CTRL + arrow keys.
15 * The use case for this class is to allow you to set a minimum size that you can count on be honored, no matter how
16 * small the user makes the terminal. This should make programming GUIs easier.
19 public class VirtualScreen
extends AbstractScreen
{
20 private final Screen realScreen
;
21 private final FrameRenderer frameRenderer
;
22 private TerminalSize minimumSize
;
23 private TerminalPosition viewportTopLeft
;
24 private TerminalSize viewportSize
;
27 * Creates a new VirtualScreen that wraps a supplied Screen. The screen passed in here should be the real screen
28 * that is created on top of the real {@code Terminal}, it will have the correct size and content for what's
29 * actually displayed to the user, but this class will present everything as one view with a fixed minimum size,
30 * no matter what size the real terminal has.
32 * The initial minimum size will be the current size of the screen.
33 * @param screen Real screen that will be used when drawing the whole or partial virtual screen
35 public VirtualScreen(Screen screen
) {
36 super(screen
.getTerminalSize());
37 this.frameRenderer
= new DefaultFrameRenderer();
38 this.realScreen
= screen
;
39 this.minimumSize
= screen
.getTerminalSize();
40 this.viewportTopLeft
= TerminalPosition
.TOP_LEFT_CORNER
;
41 this.viewportSize
= minimumSize
;
45 * Sets the minimum size we want the virtual screen to have. If the user resizes the real terminal to something
46 * smaller than this, the virtual screen will refuse to make it smaller and add scrollbars to the view.
47 * @param minimumSize Minimum size we want the screen to have
49 public void setMinimumSize(TerminalSize minimumSize
) {
50 this.minimumSize
= minimumSize
;
51 TerminalSize virtualSize
= minimumSize
.max(realScreen
.getTerminalSize());
52 if(!minimumSize
.equals(virtualSize
)) {
53 addResizeRequest(virtualSize
);
54 super.doResizeIfNecessary();
56 calculateViewport(realScreen
.getTerminalSize());
60 * Returns the minimum size this virtual screen can have. If the real terminal is made smaller than this, the
61 * virtual screen will draw scrollbars and implement scrolling
62 * @return Minimum size configured for this virtual screen
64 public TerminalSize
getMinimumSize() {
69 public void startScreen() throws IOException
{
70 realScreen
.startScreen();
74 public void stopScreen() throws IOException
{
75 realScreen
.stopScreen();
79 public TextCharacter
getFrontCharacter(TerminalPosition position
) {
84 public void setCursorPosition(TerminalPosition position
) {
85 super.setCursorPosition(position
);
86 if(position
== null) {
87 realScreen
.setCursorPosition(null);
90 position
= position
.withRelativeColumn(-viewportTopLeft
.getColumn()).withRelativeRow(-viewportTopLeft
.getRow());
91 if(position
.getColumn() >= 0 && position
.getColumn() < viewportSize
.getColumns() &&
92 position
.getRow() >= 0 && position
.getRow() < viewportSize
.getRows()) {
93 realScreen
.setCursorPosition(position
);
96 realScreen
.setCursorPosition(null);
101 public synchronized TerminalSize
doResizeIfNecessary() {
102 TerminalSize underlyingSize
= realScreen
.doResizeIfNecessary();
103 if(underlyingSize
== null) {
107 TerminalSize newVirtualSize
= calculateViewport(underlyingSize
);
108 if(!getTerminalSize().equals(newVirtualSize
)) {
109 addResizeRequest(newVirtualSize
);
110 return super.doResizeIfNecessary();
112 return newVirtualSize
;
115 private TerminalSize
calculateViewport(TerminalSize realTerminalSize
) {
116 TerminalSize newVirtualSize
= minimumSize
.max(realTerminalSize
);
117 if(newVirtualSize
.equals(realTerminalSize
)) {
118 viewportSize
= realTerminalSize
;
119 viewportTopLeft
= TerminalPosition
.TOP_LEFT_CORNER
;
122 TerminalSize newViewportSize
= frameRenderer
.getViewportSize(realTerminalSize
, newVirtualSize
);
123 if(newViewportSize
.getRows() > viewportSize
.getRows()) {
124 viewportTopLeft
= viewportTopLeft
.withRow(Math
.max(0, viewportTopLeft
.getRow() - (newViewportSize
.getRows() - viewportSize
.getRows())));
126 if(newViewportSize
.getColumns() > viewportSize
.getColumns()) {
127 viewportTopLeft
= viewportTopLeft
.withColumn(Math
.max(0, viewportTopLeft
.getColumn() - (newViewportSize
.getColumns() - viewportSize
.getColumns())));
129 viewportSize
= newViewportSize
;
131 return newVirtualSize
;
135 public void refresh(RefreshType refreshType
) throws IOException
{
136 setCursorPosition(getCursorPosition()); //Make sure the cursor is at the correct position
137 if(!viewportSize
.equals(realScreen
.getTerminalSize())) {
138 frameRenderer
.drawFrame(
139 realScreen
.newTextGraphics(),
140 realScreen
.getTerminalSize(),
146 TerminalPosition viewportOffset
= frameRenderer
.getViewportOffset();
147 if(realScreen
instanceof AbstractScreen
) {
148 AbstractScreen asAbstractScreen
= (AbstractScreen
)realScreen
;
149 getBackBuffer().copyTo(
150 asAbstractScreen
.getBackBuffer(),
151 viewportTopLeft
.getRow(),
152 viewportSize
.getRows(),
153 viewportTopLeft
.getColumn(),
154 viewportSize
.getColumns(),
155 viewportOffset
.getRow(),
156 viewportOffset
.getColumn());
159 for(int y
= 0; y
< viewportSize
.getRows(); y
++) {
160 for(int x
= 0; x
< viewportSize
.getColumns(); x
++) {
161 realScreen
.setCharacter(
162 x
+ viewportOffset
.getColumn(),
163 y
+ viewportOffset
.getRow(),
164 getBackBuffer().getCharacterAt(
165 x
+ viewportTopLeft
.getColumn(),
166 y
+ viewportTopLeft
.getRow()));
170 realScreen
.refresh(refreshType
);
174 public KeyStroke
pollInput() throws IOException
{
175 return filter(realScreen
.pollInput());
179 public KeyStroke
readInput() throws IOException
{
180 return filter(realScreen
.readInput());
183 private KeyStroke
filter(KeyStroke keyStroke
) throws IOException
{
184 if(keyStroke
== null) {
187 else if(keyStroke
.isAltDown() && keyStroke
.getKeyType() == KeyType
.ArrowLeft
) {
188 if(viewportTopLeft
.getColumn() > 0) {
189 viewportTopLeft
= viewportTopLeft
.withRelativeColumn(-1);
194 else if(keyStroke
.isAltDown() && keyStroke
.getKeyType() == KeyType
.ArrowRight
) {
195 if(viewportTopLeft
.getColumn() + viewportSize
.getColumns() < getTerminalSize().getColumns()) {
196 viewportTopLeft
= viewportTopLeft
.withRelativeColumn(1);
201 else if(keyStroke
.isAltDown() && keyStroke
.getKeyType() == KeyType
.ArrowUp
) {
202 if(viewportTopLeft
.getRow() > 0) {
203 viewportTopLeft
= viewportTopLeft
.withRelativeRow(-1);
204 realScreen
.scrollLines(0,viewportSize
.getRows()-1,-1);
209 else if(keyStroke
.isAltDown() && keyStroke
.getKeyType() == KeyType
.ArrowDown
) {
210 if(viewportTopLeft
.getRow() + viewportSize
.getRows() < getTerminalSize().getRows()) {
211 viewportTopLeft
= viewportTopLeft
.withRelativeRow(1);
212 realScreen
.scrollLines(0,viewportSize
.getRows()-1,1);
221 public void scrollLines(int firstLine
, int lastLine
, int distance
) {
222 // do base class stuff (scroll own back buffer)
223 super.scrollLines(firstLine
, lastLine
, distance
);
224 // vertical range visible in realScreen:
225 int vpFirst
= viewportTopLeft
.getRow(),
226 vpRows
= viewportSize
.getRows();
227 // adapt to realScreen range:
228 firstLine
= Math
.max(0, firstLine
- vpFirst
);
229 lastLine
= Math
.min(vpRows
- 1, lastLine
- vpFirst
);
230 // if resulting range non-empty: scroll that range in realScreen:
231 if (firstLine
<= lastLine
) {
232 realScreen
.scrollLines(firstLine
, lastLine
, distance
);
237 * Interface for rendering the virtual screen's frame when the real terminal is too small for the virtual screen
239 public interface FrameRenderer
{
241 * Given the size of the real terminal and the current size of the virtual screen, how large should the viewport
242 * where the screen content is drawn be?
243 * @param realSize Size of the real terminal
244 * @param virtualSize Size of the virtual screen
245 * @return Size of the viewport, according to this FrameRenderer
247 TerminalSize
getViewportSize(TerminalSize realSize
, TerminalSize virtualSize
);
250 * Where in the virtual screen should the top-left position of the viewport be? To draw the viewport from the
251 * top-left position of the screen, return 0x0 (or TerminalPosition.TOP_LEFT_CORNER) here.
252 * @return Position of the top-left corner of the viewport inside the screen
254 TerminalPosition
getViewportOffset();
257 * Drawn the 'frame', meaning anything that is outside the viewport (title, scrollbar, etc)
258 * @param graphics Graphics to use to text drawing operations
259 * @param realSize Size of the real terminal
260 * @param virtualSize Size of the virtual screen
261 * @param virtualScrollPosition If the virtual screen is larger than the real terminal, this is the current
262 * scroll offset the VirtualScreen is using
265 TextGraphics graphics
,
266 TerminalSize realSize
,
267 TerminalSize virtualSize
,
268 TerminalPosition virtualScrollPosition
);
271 private static class DefaultFrameRenderer
implements FrameRenderer
{
273 public TerminalSize
getViewportSize(TerminalSize realSize
, TerminalSize virtualSize
) {
274 if(realSize
.getColumns() > 1 && realSize
.getRows() > 2) {
275 return realSize
.withRelativeColumns(-1).withRelativeRows(-2);
283 public TerminalPosition
getViewportOffset() {
284 return TerminalPosition
.TOP_LEFT_CORNER
;
288 public void drawFrame(
289 TextGraphics graphics
,
290 TerminalSize realSize
,
291 TerminalSize virtualSize
,
292 TerminalPosition virtualScrollPosition
) {
294 if(realSize
.getColumns() == 1 || realSize
.getRows() <= 2) {
297 TerminalSize viewportSize
= getViewportSize(realSize
, virtualSize
);
299 graphics
.setForegroundColor(TextColor
.ANSI
.WHITE
);
300 graphics
.setBackgroundColor(TextColor
.ANSI
.BLACK
);
302 graphics
.putString(0, graphics
.getSize().getRows() - 1, "Terminal too small, use ALT+arrows to scroll");
304 int horizontalSize
= (int)(((double)(viewportSize
.getColumns()) / (double)virtualSize
.getColumns()) * (viewportSize
.getColumns()));
305 int scrollable
= viewportSize
.getColumns() - horizontalSize
- 1;
306 int horizontalPosition
= (int)((double)scrollable
* ((double)virtualScrollPosition
.getColumn() / (double)(virtualSize
.getColumns() - viewportSize
.getColumns())));
308 new TerminalPosition(horizontalPosition
, graphics
.getSize().getRows() - 2),
309 new TerminalPosition(horizontalPosition
+ horizontalSize
, graphics
.getSize().getRows() - 2),
310 Symbols
.BLOCK_MIDDLE
);
312 int verticalSize
= (int)(((double)(viewportSize
.getRows()) / (double)virtualSize
.getRows()) * (viewportSize
.getRows()));
313 scrollable
= viewportSize
.getRows() - verticalSize
- 1;
314 int verticalPosition
= (int)((double)scrollable
* ((double)virtualScrollPosition
.getRow() / (double)(virtualSize
.getRows() - viewportSize
.getRows())));
316 new TerminalPosition(graphics
.getSize().getColumns() - 1, verticalPosition
),
317 new TerminalPosition(graphics
.getSize().getColumns() - 1, verticalPosition
+ verticalSize
),
318 Symbols
.BLOCK_MIDDLE
);