Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | package com.googlecode.lanterna.gui2; |
2 | ||
3 | import com.googlecode.lanterna.TerminalPosition; | |
4 | import com.googlecode.lanterna.TerminalSize; | |
5 | ||
6 | import java.util.List; | |
7 | ||
8 | /** | |
9 | * The default window manager implementation used by Lanterna. New windows will be generally added in a tiled manner, | |
10 | * starting in the top-left corner and moving down-right as new windows are added. By using the various window hints | |
11 | * that are available you have some control over how the window manager will place and size the windows. | |
12 | * | |
13 | * @author Martin | |
14 | */ | |
15 | public class DefaultWindowManager implements WindowManager { | |
16 | ||
17 | private final WindowDecorationRenderer windowDecorationRenderer; | |
18 | private TerminalSize lastKnownScreenSize; | |
19 | ||
20 | /** | |
21 | * Default constructor, will create a window manager that uses {@code DefaultWindowDecorationRenderer} for drawing | |
22 | * window decorations. Any size calculations done before the text GUI has actually been started and displayed on | |
23 | * the terminal will assume the terminal size is 80x24. | |
24 | */ | |
25 | public DefaultWindowManager() { | |
26 | this(new DefaultWindowDecorationRenderer()); | |
27 | } | |
28 | ||
29 | /** | |
30 | * Creates a new {@code DefaultWindowManager} with a specific window decoration renderer. Any size calculations done | |
31 | * before the text GUI has actually been started and displayed on the terminal will assume the terminal size is | |
32 | * 80x24. | |
33 | * | |
34 | * @param windowDecorationRenderer Window decoration renderer to use when drawing windows | |
35 | */ | |
36 | public DefaultWindowManager(WindowDecorationRenderer windowDecorationRenderer) { | |
37 | this(windowDecorationRenderer, null); | |
38 | } | |
39 | ||
40 | /** | |
41 | * Creates a new {@code DefaultWindowManager} using a {@code DefaultWindowDecorationRenderer} for drawing window | |
42 | * decorations. Any size calculations done before the text GUI has actually been started and displayed on the | |
43 | * terminal will use the size passed in with the {@code initialScreenSize} parameter | |
44 | * | |
45 | * @param initialScreenSize Size to assume the terminal has until the text GUI is started and can be notified of the | |
46 | * correct size | |
47 | */ | |
48 | public DefaultWindowManager(TerminalSize initialScreenSize) { | |
49 | this(new DefaultWindowDecorationRenderer(), initialScreenSize); | |
50 | } | |
51 | ||
52 | /** | |
53 | * Creates a new {@code DefaultWindowManager} using a specified {@code windowDecorationRenderer} for drawing window | |
54 | * decorations. Any size calculations done before the text GUI has actually been started and displayed on the | |
55 | * terminal will use the size passed in with the {@code initialScreenSize} parameter | |
56 | * | |
57 | * @param windowDecorationRenderer Window decoration renderer to use when drawing windows | |
58 | * @param initialScreenSize Size to assume the terminal has until the text GUI is started and can be notified of the | |
59 | * correct size | |
60 | */ | |
61 | public DefaultWindowManager(WindowDecorationRenderer windowDecorationRenderer, TerminalSize initialScreenSize) { | |
62 | this.windowDecorationRenderer = windowDecorationRenderer; | |
63 | if(initialScreenSize != null) { | |
64 | this.lastKnownScreenSize = initialScreenSize; | |
65 | } | |
66 | else { | |
67 | this.lastKnownScreenSize = new TerminalSize(80, 24); | |
68 | } | |
69 | } | |
70 | ||
71 | @Override | |
72 | public boolean isInvalid() { | |
73 | return false; | |
74 | } | |
75 | ||
76 | @Override | |
77 | public WindowDecorationRenderer getWindowDecorationRenderer(Window window) { | |
78 | if(window.getHints().contains(Window.Hint.NO_DECORATIONS)) { | |
79 | return new EmptyWindowDecorationRenderer(); | |
80 | } | |
81 | return windowDecorationRenderer; | |
82 | } | |
83 | ||
84 | @Override | |
85 | public void onAdded(WindowBasedTextGUI textGUI, Window window, List<Window> allWindows) { | |
86 | WindowDecorationRenderer decorationRenderer = getWindowDecorationRenderer(window); | |
87 | TerminalSize expectedDecoratedSize = decorationRenderer.getDecoratedSize(window, window.getPreferredSize()); | |
88 | window.setDecoratedSize(expectedDecoratedSize); | |
89 | ||
90 | if(window.getHints().contains(Window.Hint.FIXED_POSITION)) { | |
91 | //Don't place the window, assume the position is already set | |
92 | } | |
93 | else if(allWindows.isEmpty()) { | |
94 | window.setPosition(TerminalPosition.OFFSET_1x1); | |
95 | } | |
96 | else if(window.getHints().contains(Window.Hint.CENTERED)) { | |
97 | int left = (lastKnownScreenSize.getColumns() - expectedDecoratedSize.getColumns()) / 2; | |
98 | int top = (lastKnownScreenSize.getRows() - expectedDecoratedSize.getRows()) / 2; | |
99 | window.setPosition(new TerminalPosition(left, top)); | |
100 | } | |
101 | else { | |
102 | TerminalPosition nextPosition = allWindows.get(allWindows.size() - 1).getPosition().withRelative(2, 1); | |
103 | if(nextPosition.getColumn() + expectedDecoratedSize.getColumns() > lastKnownScreenSize.getColumns() || | |
104 | nextPosition.getRow() + expectedDecoratedSize.getRows() > lastKnownScreenSize.getRows()) { | |
105 | nextPosition = TerminalPosition.OFFSET_1x1; | |
106 | } | |
107 | window.setPosition(nextPosition); | |
108 | } | |
109 | ||
110 | // Finally, run through the usual calculations so the window manager's usual prepare method can have it's say | |
111 | prepareWindow(lastKnownScreenSize, window); | |
112 | } | |
113 | ||
114 | @Override | |
115 | public void onRemoved(WindowBasedTextGUI textGUI, Window window, List<Window> allWindows) { | |
116 | //NOP | |
117 | } | |
118 | ||
119 | @Override | |
120 | public void prepareWindows(WindowBasedTextGUI textGUI, List<Window> allWindows, TerminalSize screenSize) { | |
121 | this.lastKnownScreenSize = screenSize; | |
122 | for(Window window: allWindows) { | |
123 | prepareWindow(screenSize, window); | |
124 | } | |
125 | } | |
126 | ||
127 | /** | |
128 | * Called by {@link DefaultWindowManager} when iterating through all windows to decide their size and position. If | |
129 | * you override {@link DefaultWindowManager} to add your own logic to how windows are placed on the screen, you can | |
130 | * override this method and selectively choose which window to interfere with. Note that the two key properties that | |
131 | * are read by the GUI system after preparing all windows are the position and decorated size. Your custom | |
132 | * implementation should set these two fields directly on the window. You can infer the decorated size from the | |
133 | * content size by using the window decoration renderer that is attached to the window manager. | |
134 | * | |
135 | * @param screenSize Size of the terminal that is available to draw on | |
136 | * @param window Window to prepare decorated size and position for | |
137 | */ | |
138 | protected void prepareWindow(TerminalSize screenSize, Window window) { | |
139 | WindowDecorationRenderer decorationRenderer = getWindowDecorationRenderer(window); | |
140 | TerminalSize contentAreaSize; | |
141 | if(window.getHints().contains(Window.Hint.FIXED_SIZE)) { | |
142 | contentAreaSize = window.getSize(); | |
143 | } | |
144 | else { | |
145 | contentAreaSize = window.getPreferredSize(); | |
146 | } | |
147 | TerminalSize size = decorationRenderer.getDecoratedSize(window, contentAreaSize); | |
148 | TerminalPosition position = window.getPosition(); | |
149 | ||
150 | if(window.getHints().contains(Window.Hint.FULL_SCREEN)) { | |
151 | position = TerminalPosition.TOP_LEFT_CORNER; | |
152 | size = screenSize; | |
153 | } | |
154 | else if(window.getHints().contains(Window.Hint.EXPANDED)) { | |
155 | position = TerminalPosition.OFFSET_1x1; | |
156 | size = screenSize.withRelative( | |
157 | -Math.min(4, screenSize.getColumns()), | |
158 | -Math.min(3, screenSize.getRows())); | |
159 | if(!size.equals(window.getDecoratedSize())) { | |
160 | window.invalidate(); | |
161 | } | |
162 | } | |
163 | else if(window.getHints().contains(Window.Hint.FIT_TERMINAL_WINDOW) || | |
164 | window.getHints().contains(Window.Hint.CENTERED)) { | |
165 | //If the window is too big for the terminal, move it up towards 0x0 and if that's not enough then shrink | |
166 | //it instead | |
167 | while(position.getRow() > 0 && position.getRow() + size.getRows() > screenSize.getRows()) { | |
168 | position = position.withRelativeRow(-1); | |
169 | } | |
170 | while(position.getColumn() > 0 && position.getColumn() + size.getColumns() > screenSize.getColumns()) { | |
171 | position = position.withRelativeColumn(-1); | |
172 | } | |
173 | if(position.getRow() + size.getRows() > screenSize.getRows()) { | |
174 | size = size.withRows(screenSize.getRows() - position.getRow()); | |
175 | } | |
176 | if(position.getColumn() + size.getColumns() > screenSize.getColumns()) { | |
177 | size = size.withColumns(screenSize.getColumns() - position.getColumn()); | |
178 | } | |
179 | if(window.getHints().contains(Window.Hint.CENTERED)) { | |
180 | int left = (lastKnownScreenSize.getColumns() - size.getColumns()) / 2; | |
181 | int top = (lastKnownScreenSize.getRows() - size.getRows()) / 2; | |
182 | position = new TerminalPosition(left, top); | |
183 | } | |
184 | } | |
185 | ||
186 | window.setPosition(position); | |
187 | window.setDecoratedSize(size); | |
188 | } | |
189 | } |