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.*; | |
25 | ||
26 | /** | |
27 | * BorderLayout imitates the BorderLayout class from AWT, allowing you to add a center component with optional | |
28 | * components around it in top, bottom, left and right locations. The edge components will be sized at their preferred | |
29 | * size and the center component will take up whatever remains. | |
30 | * @author martin | |
31 | */ | |
32 | public class BorderLayout implements LayoutManager { | |
33 | ||
34 | /** | |
35 | * This type is what you use as the layout data for components added to a panel using {@code BorderLayout} for its | |
36 | * layout manager. This values specified where inside the panel the component should be added. | |
37 | */ | |
38 | public enum Location implements LayoutData { | |
39 | /** | |
40 | * The component with this value as its layout data will occupy the center space, whatever is remaining after | |
41 | * the other components (if any) have allocated their space. | |
42 | */ | |
43 | CENTER, | |
44 | /** | |
45 | * The component with this value as its layout data will occupy the left side of the container, attempting to | |
46 | * allocate the preferred width of the component and at least the preferred height, but could be more depending | |
47 | * on the other components added. | |
48 | */ | |
49 | LEFT, | |
50 | /** | |
51 | * The component with this value as its layout data will occupy the right side of the container, attempting to | |
52 | * allocate the preferred width of the component and at least the preferred height, but could be more depending | |
53 | * on the other components added. | |
54 | */ | |
55 | RIGHT, | |
56 | /** | |
57 | * The component with this value as its layout data will occupy the top side of the container, attempting to | |
58 | * allocate the preferred height of the component and at least the preferred width, but could be more depending | |
59 | * on the other components added. | |
60 | */ | |
61 | TOP, | |
62 | /** | |
63 | * The component with this value as its layout data will occupy the bottom side of the container, attempting to | |
64 | * allocate the preferred height of the component and at least the preferred width, but could be more depending | |
65 | * on the other components added. | |
66 | */ | |
67 | BOTTOM, | |
68 | ; | |
69 | } | |
70 | ||
71 | //When components don't have a location, we'll assign an available location based on this order | |
72 | private static final List<Location> AUTO_ASSIGN_ORDER = Collections.unmodifiableList(Arrays.asList( | |
73 | Location.CENTER, | |
74 | Location.TOP, | |
75 | Location.BOTTOM, | |
76 | Location.LEFT, | |
77 | Location.RIGHT)); | |
78 | ||
79 | @Override | |
80 | public TerminalSize getPreferredSize(List<Component> components) { | |
81 | EnumMap<Location, Component> layout = makeLookupMap(components); | |
82 | int preferredHeight = | |
83 | (layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getRows() : 0) | |
84 | + | |
85 | Math.max( | |
86 | layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getRows() : 0, | |
87 | Math.max( | |
88 | layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getRows() : 0, | |
89 | layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getRows() : 0)) | |
90 | + | |
91 | (layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getRows() : 0); | |
92 | ||
93 | int preferredWidth = | |
94 | Math.max( | |
95 | (layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getColumns() : 0) + | |
96 | (layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getColumns() : 0) + | |
97 | (layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getColumns() : 0), | |
98 | Math.max( | |
99 | layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getColumns() : 0, | |
100 | layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getColumns() : 0)); | |
101 | return new TerminalSize(preferredWidth, preferredHeight); | |
102 | } | |
103 | ||
104 | @Override | |
105 | public void doLayout(TerminalSize area, List<Component> components) { | |
106 | EnumMap<Location, Component> layout = makeLookupMap(components); | |
107 | int availableHorizontalSpace = area.getColumns(); | |
108 | int availableVerticalSpace = area.getRows(); | |
109 | ||
110 | //We'll need this later on | |
111 | int topComponentHeight = 0; | |
112 | int leftComponentWidth = 0; | |
113 | ||
114 | //First allocate the top | |
115 | if(layout.containsKey(Location.TOP)) { | |
116 | Component topComponent = layout.get(Location.TOP); | |
117 | topComponentHeight = Math.min(topComponent.getPreferredSize().getRows(), availableVerticalSpace); | |
118 | topComponent.setPosition(TerminalPosition.TOP_LEFT_CORNER); | |
119 | topComponent.setSize(new TerminalSize(availableHorizontalSpace, topComponentHeight)); | |
120 | availableVerticalSpace -= topComponentHeight; | |
121 | } | |
122 | ||
123 | //Next allocate the bottom | |
124 | if(layout.containsKey(Location.BOTTOM)) { | |
125 | Component bottomComponent = layout.get(Location.BOTTOM); | |
126 | int bottomComponentHeight = Math.min(bottomComponent.getPreferredSize().getRows(), availableVerticalSpace); | |
127 | bottomComponent.setPosition(new TerminalPosition(0, area.getRows() - bottomComponentHeight)); | |
128 | bottomComponent.setSize(new TerminalSize(availableHorizontalSpace, bottomComponentHeight)); | |
129 | availableVerticalSpace -= bottomComponentHeight; | |
130 | } | |
131 | ||
132 | //Now divide the remaining space between LEFT, CENTER and RIGHT | |
133 | if(layout.containsKey(Location.LEFT)) { | |
134 | Component leftComponent = layout.get(Location.LEFT); | |
135 | leftComponentWidth = Math.min(leftComponent.getPreferredSize().getColumns(), availableHorizontalSpace); | |
136 | leftComponent.setPosition(new TerminalPosition(0, topComponentHeight)); | |
137 | leftComponent.setSize(new TerminalSize(leftComponentWidth, availableVerticalSpace)); | |
138 | availableHorizontalSpace -= leftComponentWidth; | |
139 | } | |
140 | if(layout.containsKey(Location.RIGHT)) { | |
141 | Component rightComponent = layout.get(Location.RIGHT); | |
142 | int rightComponentWidth = Math.min(rightComponent.getPreferredSize().getColumns(), availableHorizontalSpace); | |
143 | rightComponent.setPosition(new TerminalPosition(area.getColumns() - rightComponentWidth, topComponentHeight)); | |
144 | rightComponent.setSize(new TerminalSize(rightComponentWidth, availableVerticalSpace)); | |
145 | availableHorizontalSpace -= rightComponentWidth; | |
146 | } | |
147 | if(layout.containsKey(Location.CENTER)) { | |
148 | Component centerComponent = layout.get(Location.CENTER); | |
149 | centerComponent.setPosition(new TerminalPosition(leftComponentWidth, topComponentHeight)); | |
150 | centerComponent.setSize(new TerminalSize(availableHorizontalSpace, availableVerticalSpace)); | |
151 | } | |
152 | ||
153 | //Set the remaining components to 0x0 | |
154 | for(Component component: components) { | |
155 | if(!layout.values().contains(component)) { | |
156 | component.setPosition(TerminalPosition.TOP_LEFT_CORNER); | |
157 | component.setSize(TerminalSize.ZERO); | |
158 | } | |
159 | } | |
160 | } | |
161 | ||
162 | private EnumMap<Location, Component> makeLookupMap(List<Component> components) { | |
163 | EnumMap<Location, Component> map = new EnumMap<BorderLayout.Location, Component>(Location.class); | |
164 | List<Component> unassignedComponents = new ArrayList<Component>(); | |
165 | for(Component component: components) { | |
166 | if(component.getLayoutData() instanceof Location) { | |
167 | map.put((Location)component.getLayoutData(), component); | |
168 | } | |
169 | else { | |
170 | unassignedComponents.add(component); | |
171 | } | |
172 | } | |
173 | //Try to assign components to available locations | |
174 | for(Component component: unassignedComponents) { | |
175 | for(Location location: AUTO_ASSIGN_ORDER) { | |
176 | if(!map.containsKey(location)) { | |
177 | map.put(location, component); | |
178 | break; | |
179 | } | |
180 | } | |
181 | } | |
182 | return map; | |
183 | } | |
184 | ||
185 | @Override | |
186 | public boolean hasChanged() { | |
187 | //No internal state | |
188 | return false; | |
189 | } | |
190 | } |