Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | /* |
2 | * This file is part of lanterna (http://code.google.com/p/lanterna/). | |
3 | * | |
4 | * lanterna is free software: you can redistribute it and/or modify | |
5 | * it under the terms of the GNU Lesser General Public License as published by | |
6 | * the Free Software Foundation, either version 3 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU Lesser General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU Lesser General Public License | |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | * | |
17 | * Copyright (C) 2010-2015 Martin | |
18 | */ | |
19 | package com.googlecode.lanterna.gui2; | |
20 | ||
21 | import com.googlecode.lanterna.TerminalPosition; | |
22 | import com.googlecode.lanterna.TerminalSize; | |
23 | ||
24 | import java.util.List; | |
25 | ||
26 | /** | |
27 | * Simple layout manager the puts all components on a single line, either horizontally or vertically. | |
28 | */ | |
29 | public class LinearLayout implements LayoutManager { | |
30 | /** | |
31 | * This enum type will decide the alignment of a component on the counter-axis, meaning the horizontal alignment on | |
32 | * vertical {@code LinearLayout}s and vertical alignment on horizontal {@code LinearLayout}s. | |
33 | */ | |
34 | public enum Alignment { | |
35 | /** | |
36 | * The component will be placed to the left (for vertical layouts) or top (for horizontal layouts) | |
37 | */ | |
38 | Beginning, | |
39 | /** | |
40 | * The component will be placed horizontally centered (for vertical layouts) or vertically centered (for | |
41 | * horizontal layouts) | |
42 | */ | |
43 | Center, | |
44 | /** | |
45 | * The component will be placed to the right (for vertical layouts) or bottom (for horizontal layouts) | |
46 | */ | |
47 | End, | |
48 | /** | |
49 | * The component will be forced to take up all the horizontal space (for vertical layouts) or vertical space | |
50 | * (for horizontal layouts) | |
51 | */ | |
52 | Fill, | |
53 | } | |
54 | ||
55 | private static class LinearLayoutData implements LayoutData { | |
56 | private final Alignment alignment; | |
57 | ||
58 | public LinearLayoutData(Alignment alignment) { | |
59 | this.alignment = alignment; | |
60 | } | |
61 | } | |
62 | ||
63 | /** | |
64 | * Creates a {@code LayoutData} for {@code LinearLayout} that assigns a component to a particular alignment on its | |
65 | * counter-axis, meaning the horizontal alignment on vertical {@code LinearLayout}s and vertical alignment on | |
66 | * horizontal {@code LinearLayout}s. | |
67 | * @param alignment Alignment to store in the {@code LayoutData} object | |
68 | * @return {@code LayoutData} object created for {@code LinearLayout}s with the specified alignment | |
69 | * @see Alignment | |
70 | */ | |
71 | public static LayoutData createLayoutData(Alignment alignment) { | |
72 | return new LinearLayoutData(alignment); | |
73 | } | |
74 | ||
75 | private final Direction direction; | |
76 | private int spacing; | |
77 | private boolean changed; | |
78 | ||
79 | /** | |
80 | * Default constructor, creates a vertical {@code LinearLayout} | |
81 | */ | |
82 | public LinearLayout() { | |
83 | this(Direction.VERTICAL); | |
84 | } | |
85 | ||
86 | /** | |
87 | * Standard constructor that creates a {@code LinearLayout} with a specified direction to position the components on | |
88 | * @param direction Direction for this {@code Direction} | |
89 | */ | |
90 | public LinearLayout(Direction direction) { | |
91 | this.direction = direction; | |
92 | this.spacing = direction == Direction.HORIZONTAL ? 1 : 0; | |
93 | this.changed = true; | |
94 | } | |
95 | ||
96 | /** | |
97 | * Sets the amount of empty space to put in between components. For horizontal layouts, this is number of columns | |
98 | * (by default 1) and for vertical layouts this is number of rows (by default 0). | |
99 | * @param spacing Spacing between components, either in number of columns or rows depending on the direction | |
100 | * @return Itself | |
101 | */ | |
102 | public LinearLayout setSpacing(int spacing) { | |
103 | this.spacing = spacing; | |
104 | this.changed = true; | |
105 | return this; | |
106 | } | |
107 | ||
108 | /** | |
109 | * Returns the amount of empty space to put in between components. For horizontal layouts, this is number of columns | |
110 | * (by default 1) and for vertical layouts this is number of rows (by default 0). | |
111 | * @return Spacing between components, either in number of columns or rows depending on the direction | |
112 | */ | |
113 | public int getSpacing() { | |
114 | return spacing; | |
115 | } | |
116 | ||
117 | @Override | |
118 | public TerminalSize getPreferredSize(List<Component> components) { | |
119 | if(direction == Direction.VERTICAL) { | |
120 | return getPreferredSizeVertically(components); | |
121 | } | |
122 | else { | |
123 | return getPreferredSizeHorizontally(components); | |
124 | } | |
125 | } | |
126 | ||
127 | private TerminalSize getPreferredSizeVertically(List<Component> components) { | |
128 | int maxWidth = 0; | |
129 | int height = 0; | |
130 | for(Component component: components) { | |
131 | TerminalSize preferredSize = component.getPreferredSize(); | |
132 | if(maxWidth < preferredSize.getColumns()) { | |
133 | maxWidth = preferredSize.getColumns(); | |
134 | } | |
135 | height += preferredSize.getRows(); | |
136 | } | |
137 | height += spacing * (components.size() - 1); | |
138 | return new TerminalSize(maxWidth, height); | |
139 | } | |
140 | ||
141 | private TerminalSize getPreferredSizeHorizontally(List<Component> components) { | |
142 | int maxHeight = 0; | |
143 | int width = 0; | |
144 | for(Component component: components) { | |
145 | TerminalSize preferredSize = component.getPreferredSize(); | |
146 | if(maxHeight < preferredSize.getRows()) { | |
147 | maxHeight = preferredSize.getRows(); | |
148 | } | |
149 | width += preferredSize.getColumns(); | |
150 | } | |
151 | width += spacing * (components.size() - 1); | |
152 | return new TerminalSize(width, maxHeight); | |
153 | } | |
154 | ||
155 | @Override | |
156 | public boolean hasChanged() { | |
157 | return changed; | |
158 | } | |
159 | ||
160 | @Override | |
161 | public void doLayout(TerminalSize area, List<Component> components) { | |
162 | if(direction == Direction.VERTICAL) { | |
163 | doVerticalLayout(area, components); | |
164 | } | |
165 | else { | |
166 | doHorizontalLayout(area, components); | |
167 | } | |
168 | this.changed = false; | |
169 | } | |
170 | ||
171 | private void doVerticalLayout(TerminalSize area, List<Component> components) { | |
172 | int remainingVerticalSpace = area.getRows(); | |
173 | int availableHorizontalSpace = area.getColumns(); | |
174 | for(Component component: components) { | |
175 | if(remainingVerticalSpace <= 0) { | |
176 | component.setPosition(TerminalPosition.TOP_LEFT_CORNER); | |
177 | component.setSize(TerminalSize.ZERO); | |
178 | } | |
179 | else { | |
180 | LinearLayoutData layoutData = (LinearLayoutData)component.getLayoutData(); | |
181 | Alignment alignment = Alignment.Beginning; | |
182 | if(layoutData != null) { | |
183 | alignment = layoutData.alignment; | |
184 | } | |
185 | ||
186 | TerminalSize preferredSize = component.getPreferredSize(); | |
187 | TerminalSize decidedSize = new TerminalSize( | |
188 | Math.min(availableHorizontalSpace, preferredSize.getColumns()), | |
189 | Math.min(remainingVerticalSpace, preferredSize.getRows())); | |
190 | if(alignment == Alignment.Fill) { | |
191 | decidedSize = decidedSize.withColumns(availableHorizontalSpace); | |
192 | alignment = Alignment.Beginning; | |
193 | } | |
194 | ||
195 | TerminalPosition position = component.getPosition(); | |
196 | position = position.withRow(area.getRows() - remainingVerticalSpace); | |
197 | switch(alignment) { | |
198 | case End: | |
199 | position = position.withColumn(availableHorizontalSpace - decidedSize.getColumns()); | |
200 | break; | |
201 | case Center: | |
202 | position = position.withColumn((availableHorizontalSpace - decidedSize.getColumns()) / 2); | |
203 | break; | |
204 | case Beginning: | |
205 | default: | |
206 | position = position.withColumn(0); | |
207 | break; | |
208 | } | |
209 | component.setPosition(position); | |
210 | component.setSize(component.getSize().with(decidedSize)); | |
211 | remainingVerticalSpace -= decidedSize.getRows() + spacing; | |
212 | } | |
213 | } | |
214 | } | |
215 | ||
216 | private void doHorizontalLayout(TerminalSize area, List<Component> components) { | |
217 | int remainingHorizontalSpace = area.getColumns(); | |
218 | int availableVerticalSpace = area.getRows(); | |
219 | for(Component component: components) { | |
220 | if(remainingHorizontalSpace <= 0) { | |
221 | component.setPosition(TerminalPosition.TOP_LEFT_CORNER); | |
222 | component.setSize(TerminalSize.ZERO); | |
223 | } | |
224 | else { | |
225 | LinearLayoutData layoutData = (LinearLayoutData)component.getLayoutData(); | |
226 | Alignment alignment = Alignment.Beginning; | |
227 | if(layoutData != null) { | |
228 | alignment = layoutData.alignment; | |
229 | } | |
230 | TerminalSize preferredSize = component.getPreferredSize(); | |
231 | TerminalSize decidedSize = new TerminalSize( | |
232 | Math.min(remainingHorizontalSpace, preferredSize.getColumns()), | |
233 | Math.min(availableVerticalSpace, preferredSize.getRows())); | |
234 | if(alignment == Alignment.Fill) { | |
235 | decidedSize = decidedSize.withRows(availableVerticalSpace); | |
236 | alignment = Alignment.Beginning; | |
237 | } | |
238 | ||
239 | TerminalPosition position = component.getPosition(); | |
240 | position = position.withColumn(area.getColumns() - remainingHorizontalSpace); | |
241 | switch(alignment) { | |
242 | case End: | |
243 | position = position.withRow(availableVerticalSpace - decidedSize.getRows()); | |
244 | break; | |
245 | case Center: | |
246 | position = position.withRow((availableVerticalSpace - decidedSize.getRows()) / 2); | |
247 | break; | |
248 | case Beginning: | |
249 | default: | |
250 | position = position.withRow(0); | |
251 | break; | |
252 | } | |
253 | component.setPosition(position); | |
254 | component.setSize(component.getSize().with(decidedSize)); | |
255 | remainingHorizontalSpace -= decidedSize.getColumns() + spacing; | |
256 | } | |
257 | } | |
258 | } | |
259 | } |