+++ /dev/null
-package com.googlecode.lanterna.screen;
-
-import com.googlecode.lanterna.*;
-import com.googlecode.lanterna.graphics.TextGraphics;
-import com.googlecode.lanterna.input.KeyStroke;
-import com.googlecode.lanterna.input.KeyType;
-
-import java.io.IOException;
-
-/**
- * VirtualScreen wraps a normal screen and presents it as a screen that has a configurable minimum size; if the real
- * screen is smaller than this size, the presented screen will add scrolling to get around it. To anyone using this
- * class, it will appear and behave just as a normal screen. Scrolling is done by using CTRL + arrow keys.
- * <p>
- * 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
- * small the user makes the terminal. This should make programming GUIs easier.
- * @author Martin
- */
-public class VirtualScreen extends AbstractScreen {
- private final Screen realScreen;
- private final FrameRenderer frameRenderer;
- private TerminalSize minimumSize;
- private TerminalPosition viewportTopLeft;
- private TerminalSize viewportSize;
-
- /**
- * Creates a new VirtualScreen that wraps a supplied Screen. The screen passed in here should be the real screen
- * that is created on top of the real {@code Terminal}, it will have the correct size and content for what's
- * actually displayed to the user, but this class will present everything as one view with a fixed minimum size,
- * no matter what size the real terminal has.
- * <p>
- * The initial minimum size will be the current size of the screen.
- * @param screen Real screen that will be used when drawing the whole or partial virtual screen
- */
- public VirtualScreen(Screen screen) {
- super(screen.getTerminalSize());
- this.frameRenderer = new DefaultFrameRenderer();
- this.realScreen = screen;
- this.minimumSize = screen.getTerminalSize();
- this.viewportTopLeft = TerminalPosition.TOP_LEFT_CORNER;
- this.viewportSize = minimumSize;
- }
-
- /**
- * Sets the minimum size we want the virtual screen to have. If the user resizes the real terminal to something
- * smaller than this, the virtual screen will refuse to make it smaller and add scrollbars to the view.
- * @param minimumSize Minimum size we want the screen to have
- */
- public void setMinimumSize(TerminalSize minimumSize) {
- this.minimumSize = minimumSize;
- TerminalSize virtualSize = minimumSize.max(realScreen.getTerminalSize());
- if(!minimumSize.equals(virtualSize)) {
- addResizeRequest(virtualSize);
- super.doResizeIfNecessary();
- }
- calculateViewport(realScreen.getTerminalSize());
- }
-
- /**
- * Returns the minimum size this virtual screen can have. If the real terminal is made smaller than this, the
- * virtual screen will draw scrollbars and implement scrolling
- * @return Minimum size configured for this virtual screen
- */
- public TerminalSize getMinimumSize() {
- return minimumSize;
- }
-
- @Override
- public void startScreen() throws IOException {
- realScreen.startScreen();
- }
-
- @Override
- public void stopScreen() throws IOException {
- realScreen.stopScreen();
- }
-
- @Override
- public TextCharacter getFrontCharacter(TerminalPosition position) {
- return null;
- }
-
- @Override
- public void setCursorPosition(TerminalPosition position) {
- super.setCursorPosition(position);
- if(position == null) {
- realScreen.setCursorPosition(null);
- return;
- }
- position = position.withRelativeColumn(-viewportTopLeft.getColumn()).withRelativeRow(-viewportTopLeft.getRow());
- if(position.getColumn() >= 0 && position.getColumn() < viewportSize.getColumns() &&
- position.getRow() >= 0 && position.getRow() < viewportSize.getRows()) {
- realScreen.setCursorPosition(position);
- }
- else {
- realScreen.setCursorPosition(null);
- }
- }
-
- @Override
- public synchronized TerminalSize doResizeIfNecessary() {
- TerminalSize underlyingSize = realScreen.doResizeIfNecessary();
- if(underlyingSize == null) {
- return null;
- }
-
- TerminalSize newVirtualSize = calculateViewport(underlyingSize);
- if(!getTerminalSize().equals(newVirtualSize)) {
- addResizeRequest(newVirtualSize);
- return super.doResizeIfNecessary();
- }
- return newVirtualSize;
- }
-
- private TerminalSize calculateViewport(TerminalSize realTerminalSize) {
- TerminalSize newVirtualSize = minimumSize.max(realTerminalSize);
- if(newVirtualSize.equals(realTerminalSize)) {
- viewportSize = realTerminalSize;
- viewportTopLeft = TerminalPosition.TOP_LEFT_CORNER;
- }
- else {
- TerminalSize newViewportSize = frameRenderer.getViewportSize(realTerminalSize, newVirtualSize);
- if(newViewportSize.getRows() > viewportSize.getRows()) {
- viewportTopLeft = viewportTopLeft.withRow(Math.max(0, viewportTopLeft.getRow() - (newViewportSize.getRows() - viewportSize.getRows())));
- }
- if(newViewportSize.getColumns() > viewportSize.getColumns()) {
- viewportTopLeft = viewportTopLeft.withColumn(Math.max(0, viewportTopLeft.getColumn() - (newViewportSize.getColumns() - viewportSize.getColumns())));
- }
- viewportSize = newViewportSize;
- }
- return newVirtualSize;
- }
-
- @Override
- public void refresh(RefreshType refreshType) throws IOException {
- setCursorPosition(getCursorPosition()); //Make sure the cursor is at the correct position
- if(!viewportSize.equals(realScreen.getTerminalSize())) {
- frameRenderer.drawFrame(
- realScreen.newTextGraphics(),
- realScreen.getTerminalSize(),
- getTerminalSize(),
- viewportTopLeft);
- }
-
- //Copy the rows
- TerminalPosition viewportOffset = frameRenderer.getViewportOffset();
- if(realScreen instanceof AbstractScreen) {
- AbstractScreen asAbstractScreen = (AbstractScreen)realScreen;
- getBackBuffer().copyTo(
- asAbstractScreen.getBackBuffer(),
- viewportTopLeft.getRow(),
- viewportSize.getRows(),
- viewportTopLeft.getColumn(),
- viewportSize.getColumns(),
- viewportOffset.getRow(),
- viewportOffset.getColumn());
- }
- else {
- for(int y = 0; y < viewportSize.getRows(); y++) {
- for(int x = 0; x < viewportSize.getColumns(); x++) {
- realScreen.setCharacter(
- x + viewportOffset.getColumn(),
- y + viewportOffset.getRow(),
- getBackBuffer().getCharacterAt(
- x + viewportTopLeft.getColumn(),
- y + viewportTopLeft.getRow()));
- }
- }
- }
- realScreen.refresh(refreshType);
- }
-
- @Override
- public KeyStroke pollInput() throws IOException {
- return filter(realScreen.pollInput());
- }
-
- @Override
- public KeyStroke readInput() throws IOException {
- return filter(realScreen.readInput());
- }
-
- private KeyStroke filter(KeyStroke keyStroke) throws IOException {
- if(keyStroke == null) {
- return null;
- }
- else if(keyStroke.isAltDown() && keyStroke.getKeyType() == KeyType.ArrowLeft) {
- if(viewportTopLeft.getColumn() > 0) {
- viewportTopLeft = viewportTopLeft.withRelativeColumn(-1);
- refresh();
- return null;
- }
- }
- else if(keyStroke.isAltDown() && keyStroke.getKeyType() == KeyType.ArrowRight) {
- if(viewportTopLeft.getColumn() + viewportSize.getColumns() < getTerminalSize().getColumns()) {
- viewportTopLeft = viewportTopLeft.withRelativeColumn(1);
- refresh();
- return null;
- }
- }
- else if(keyStroke.isAltDown() && keyStroke.getKeyType() == KeyType.ArrowUp) {
- if(viewportTopLeft.getRow() > 0) {
- viewportTopLeft = viewportTopLeft.withRelativeRow(-1);
- realScreen.scrollLines(0,viewportSize.getRows()-1,-1);
- refresh();
- return null;
- }
- }
- else if(keyStroke.isAltDown() && keyStroke.getKeyType() == KeyType.ArrowDown) {
- if(viewportTopLeft.getRow() + viewportSize.getRows() < getTerminalSize().getRows()) {
- viewportTopLeft = viewportTopLeft.withRelativeRow(1);
- realScreen.scrollLines(0,viewportSize.getRows()-1,1);
- refresh();
- return null;
- }
- }
- return keyStroke;
- }
-
- @Override
- public void scrollLines(int firstLine, int lastLine, int distance) {
- // do base class stuff (scroll own back buffer)
- super.scrollLines(firstLine, lastLine, distance);
- // vertical range visible in realScreen:
- int vpFirst = viewportTopLeft.getRow(),
- vpRows = viewportSize.getRows();
- // adapt to realScreen range:
- firstLine = Math.max(0, firstLine - vpFirst);
- lastLine = Math.min(vpRows - 1, lastLine - vpFirst);
- // if resulting range non-empty: scroll that range in realScreen:
- if (firstLine <= lastLine) {
- realScreen.scrollLines(firstLine, lastLine, distance);
- }
- }
-
- /**
- * Interface for rendering the virtual screen's frame when the real terminal is too small for the virtual screen
- */
- public interface FrameRenderer {
- /**
- * Given the size of the real terminal and the current size of the virtual screen, how large should the viewport
- * where the screen content is drawn be?
- * @param realSize Size of the real terminal
- * @param virtualSize Size of the virtual screen
- * @return Size of the viewport, according to this FrameRenderer
- */
- TerminalSize getViewportSize(TerminalSize realSize, TerminalSize virtualSize);
-
- /**
- * Where in the virtual screen should the top-left position of the viewport be? To draw the viewport from the
- * top-left position of the screen, return 0x0 (or TerminalPosition.TOP_LEFT_CORNER) here.
- * @return Position of the top-left corner of the viewport inside the screen
- */
- TerminalPosition getViewportOffset();
-
- /**
- * Drawn the 'frame', meaning anything that is outside the viewport (title, scrollbar, etc)
- * @param graphics Graphics to use to text drawing operations
- * @param realSize Size of the real terminal
- * @param virtualSize Size of the virtual screen
- * @param virtualScrollPosition If the virtual screen is larger than the real terminal, this is the current
- * scroll offset the VirtualScreen is using
- */
- void drawFrame(
- TextGraphics graphics,
- TerminalSize realSize,
- TerminalSize virtualSize,
- TerminalPosition virtualScrollPosition);
- }
-
- private static class DefaultFrameRenderer implements FrameRenderer {
- @Override
- public TerminalSize getViewportSize(TerminalSize realSize, TerminalSize virtualSize) {
- if(realSize.getColumns() > 1 && realSize.getRows() > 2) {
- return realSize.withRelativeColumns(-1).withRelativeRows(-2);
- }
- else {
- return realSize;
- }
- }
-
- @Override
- public TerminalPosition getViewportOffset() {
- return TerminalPosition.TOP_LEFT_CORNER;
- }
-
- @Override
- public void drawFrame(
- TextGraphics graphics,
- TerminalSize realSize,
- TerminalSize virtualSize,
- TerminalPosition virtualScrollPosition) {
-
- if(realSize.getColumns() == 1 || realSize.getRows() <= 2) {
- return;
- }
- TerminalSize viewportSize = getViewportSize(realSize, virtualSize);
-
- graphics.setForegroundColor(TextColor.ANSI.WHITE);
- graphics.setBackgroundColor(TextColor.ANSI.BLACK);
- graphics.fill(' ');
- graphics.putString(0, graphics.getSize().getRows() - 1, "Terminal too small, use ALT+arrows to scroll");
-
- int horizontalSize = (int)(((double)(viewportSize.getColumns()) / (double)virtualSize.getColumns()) * (viewportSize.getColumns()));
- int scrollable = viewportSize.getColumns() - horizontalSize - 1;
- int horizontalPosition = (int)((double)scrollable * ((double)virtualScrollPosition.getColumn() / (double)(virtualSize.getColumns() - viewportSize.getColumns())));
- graphics.drawLine(
- new TerminalPosition(horizontalPosition, graphics.getSize().getRows() - 2),
- new TerminalPosition(horizontalPosition + horizontalSize, graphics.getSize().getRows() - 2),
- Symbols.BLOCK_MIDDLE);
-
- int verticalSize = (int)(((double)(viewportSize.getRows()) / (double)virtualSize.getRows()) * (viewportSize.getRows()));
- scrollable = viewportSize.getRows() - verticalSize - 1;
- int verticalPosition = (int)((double)scrollable * ((double)virtualScrollPosition.getRow() / (double)(virtualSize.getRows() - viewportSize.getRows())));
- graphics.drawLine(
- new TerminalPosition(graphics.getSize().getColumns() - 1, verticalPosition),
- new TerminalPosition(graphics.getSize().getColumns() - 1, verticalPosition + verticalSize),
- Symbols.BLOCK_MIDDLE);
- }
- }
-}