/* * This file is part of lanterna (http://code.google.com/p/lanterna/). * * lanterna is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) 2010-2017 Martin Berglund */ package com.googlecode.lanterna.gui2; import com.googlecode.lanterna.TerminalPosition; import com.googlecode.lanterna.TerminalSize; import java.util.List; /** * The default window manager implementation used by Lanterna. New windows will be generally added in a tiled manner, * starting in the top-left corner and moving down-right as new windows are added. By using the various window hints * that are available you have some control over how the window manager will place and size the windows. * * @author Martin */ public class DefaultWindowManager implements WindowManager { private final WindowDecorationRenderer windowDecorationRendererOverride; private TerminalSize lastKnownScreenSize; /** * Default constructor, will create a window manager that uses {@code DefaultWindowDecorationRenderer} for drawing * window decorations, unless the current theme has an override. Any size calculations done before the text GUI has * actually been started and displayed on the terminal will assume the terminal size is 80x24. */ public DefaultWindowManager() { this(null); } /** * Creates a new {@code DefaultWindowManager} using a {@code DefaultWindowDecorationRenderer} for drawing window * decorations, unless the current theme has an override. Any size calculations done before the text GUI has * actually been started and displayed on the terminal will use the size passed in with the * {@code initialScreenSize} parameter (if {@code null} then size will be assumed to be 80x24) * * @param initialScreenSize Size to assume the terminal has until the text GUI is started and can be notified of the * correct size */ public DefaultWindowManager(TerminalSize initialScreenSize) { this(null, initialScreenSize); } /** * Creates a new {@code DefaultWindowManager} using a specified {@code windowDecorationRendererOverride} for drawing window * decorations. Any size calculations done before the text GUI has actually been started and displayed on the * terminal will use the size passed in with the {@code initialScreenSize} parameter * * @param windowDecorationRenderer Window decoration renderer to use when drawing windows * @param initialScreenSize Size to assume the terminal has until the text GUI is started and can be notified of the * correct size */ public DefaultWindowManager(WindowDecorationRenderer windowDecorationRenderer, TerminalSize initialScreenSize) { this.windowDecorationRendererOverride = windowDecorationRenderer; if(initialScreenSize != null) { this.lastKnownScreenSize = initialScreenSize; } else { this.lastKnownScreenSize = new TerminalSize(80, 24); } } @Override public boolean isInvalid() { return false; } @Override public WindowDecorationRenderer getWindowDecorationRenderer(Window window) { if(window.getHints().contains(Window.Hint.NO_DECORATIONS)) { return new EmptyWindowDecorationRenderer(); } else if(windowDecorationRendererOverride != null) { return windowDecorationRendererOverride; } else if(window.getTheme() != null && window.getTheme().getWindowDecorationRenderer() != null) { return window.getTheme().getWindowDecorationRenderer(); } else { return new DefaultWindowDecorationRenderer(); } } @Override public void onAdded(WindowBasedTextGUI textGUI, Window window, List<Window> allWindows) { WindowDecorationRenderer decorationRenderer = getWindowDecorationRenderer(window); TerminalSize expectedDecoratedSize = decorationRenderer.getDecoratedSize(window, window.getPreferredSize()); window.setDecoratedSize(expectedDecoratedSize); //noinspection StatementWithEmptyBody if(window.getHints().contains(Window.Hint.FIXED_POSITION)) { //Don't place the window, assume the position is already set } else if(allWindows.isEmpty()) { window.setPosition(TerminalPosition.OFFSET_1x1); } else if(window.getHints().contains(Window.Hint.CENTERED)) { int left = (lastKnownScreenSize.getColumns() - expectedDecoratedSize.getColumns()) / 2; int top = (lastKnownScreenSize.getRows() - expectedDecoratedSize.getRows()) / 2; window.setPosition(new TerminalPosition(left, top)); } else { TerminalPosition nextPosition = allWindows.get(allWindows.size() - 1).getPosition().withRelative(2, 1); if(nextPosition.getColumn() + expectedDecoratedSize.getColumns() > lastKnownScreenSize.getColumns() || nextPosition.getRow() + expectedDecoratedSize.getRows() > lastKnownScreenSize.getRows()) { nextPosition = TerminalPosition.OFFSET_1x1; } window.setPosition(nextPosition); } // Finally, run through the usual calculations so the window manager's usual prepare method can have it's say prepareWindow(lastKnownScreenSize, window); } @Override public void onRemoved(WindowBasedTextGUI textGUI, Window window, List<Window> allWindows) { //NOP } @Override public void prepareWindows(WindowBasedTextGUI textGUI, List<Window> allWindows, TerminalSize screenSize) { this.lastKnownScreenSize = screenSize; for(Window window: allWindows) { prepareWindow(screenSize, window); } } /** * Called by {@link DefaultWindowManager} when iterating through all windows to decide their size and position. If * you override {@link DefaultWindowManager} to add your own logic to how windows are placed on the screen, you can * override this method and selectively choose which window to interfere with. Note that the two key properties that * are read by the GUI system after preparing all windows are the position and decorated size. Your custom * implementation should set these two fields directly on the window. You can infer the decorated size from the * content size by using the window decoration renderer that is attached to the window manager. * * @param screenSize Size of the terminal that is available to draw on * @param window Window to prepare decorated size and position for */ protected void prepareWindow(TerminalSize screenSize, Window window) { WindowDecorationRenderer decorationRenderer = getWindowDecorationRenderer(window); TerminalSize contentAreaSize; if(window.getHints().contains(Window.Hint.FIXED_SIZE)) { contentAreaSize = window.getSize(); } else { contentAreaSize = window.getPreferredSize(); } TerminalSize size = decorationRenderer.getDecoratedSize(window, contentAreaSize); TerminalPosition position = window.getPosition(); if(window.getHints().contains(Window.Hint.FULL_SCREEN)) { position = TerminalPosition.TOP_LEFT_CORNER; size = screenSize; } else if(window.getHints().contains(Window.Hint.EXPANDED)) { position = TerminalPosition.OFFSET_1x1; size = screenSize.withRelative( -Math.min(4, screenSize.getColumns()), -Math.min(3, screenSize.getRows())); if(!size.equals(window.getDecoratedSize())) { window.invalidate(); } } else if(window.getHints().contains(Window.Hint.FIT_TERMINAL_WINDOW) || window.getHints().contains(Window.Hint.CENTERED)) { //If the window is too big for the terminal, move it up towards 0x0 and if that's not enough then shrink //it instead while(position.getRow() > 0 && position.getRow() + size.getRows() > screenSize.getRows()) { position = position.withRelativeRow(-1); } while(position.getColumn() > 0 && position.getColumn() + size.getColumns() > screenSize.getColumns()) { position = position.withRelativeColumn(-1); } if(position.getRow() + size.getRows() > screenSize.getRows()) { size = size.withRows(screenSize.getRows() - position.getRow()); } if(position.getColumn() + size.getColumns() > screenSize.getColumns()) { size = size.withColumns(screenSize.getColumns() - position.getColumn()); } if(window.getHints().contains(Window.Hint.CENTERED)) { int left = (lastKnownScreenSize.getColumns() - size.getColumns()) / 2; int top = (lastKnownScreenSize.getRows() - size.getRows()) / 2; position = new TerminalPosition(left, top); } } window.setPosition(position); window.setDecoratedSize(size); } }