TSplitPane initial
[fanfix.git] / src / jexer / TSplitPane.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer;
30
31 import jexer.bits.CellAttributes;
32 import jexer.bits.GraphicsChars;
33 import jexer.event.TMenuEvent;
34 import jexer.event.TMouseEvent;
35 import jexer.event.TResizeEvent;
36 import jexer.menu.TMenu;
37
38 /**
39 * TSplitPane contains two widgets with a draggable horizontal or vertical
40 * bar between them.
41 */
42 public class TSplitPane extends TWidget {
43
44 // ------------------------------------------------------------------------
45 // Variables --------------------------------------------------------------
46 // ------------------------------------------------------------------------
47
48 /**
49 * If true, split vertically. If false, split horizontally.
50 */
51 private boolean vertical = true;
52
53 /**
54 * The location of the split bar, either as a column number for vertical
55 * split or a row number for horizontal split.
56 */
57 private int split = 0;
58
59 /**
60 * The widget on the left side.
61 */
62 private TWidget left;
63
64 /**
65 * The widget on the right side.
66 */
67 private TWidget right;
68
69 /**
70 * The widget on the top side.
71 */
72 private TWidget top;
73
74 /**
75 * The widget on the bottom side.
76 */
77 private TWidget bottom;
78
79 /**
80 * If true, we are in the middle of a split move.
81 */
82 private boolean inSplitMove = false;
83
84 // ------------------------------------------------------------------------
85 // Constructors -----------------------------------------------------------
86 // ------------------------------------------------------------------------
87
88 /**
89 * Public constructor.
90 *
91 * @param parent parent widget
92 * @param x column relative to parent
93 * @param y row relative to parent
94 * @param width width of widget
95 * @param height height of widget
96 * @param vertical if true, split vertically
97 */
98 public TSplitPane(final TWidget parent, final int x, final int y,
99 final int width, final int height, final boolean vertical) {
100
101 super(parent, x, y, width, height);
102
103 this.vertical = vertical;
104 center();
105 }
106
107 // ------------------------------------------------------------------------
108 // Event handlers ---------------------------------------------------------
109 // ------------------------------------------------------------------------
110
111 /**
112 * Handle window/screen resize events.
113 *
114 * @param event resize event
115 */
116 @Override
117 public void onResize(final TResizeEvent event) {
118 if (event.getType() == TResizeEvent.Type.WIDGET) {
119 // Resize me
120 super.onResize(event);
121
122 if (vertical && (split >= getWidth() - 2)) {
123 center();
124 } else if (!vertical && (split >= getHeight() - 2)) {
125 center();
126 } else {
127 layoutChildren();
128 }
129 }
130 }
131
132 /**
133 * Handle mouse button presses.
134 *
135 * @param mouse mouse button event
136 */
137 @Override
138 public void onMouseDown(final TMouseEvent mouse) {
139
140 inSplitMove = false;
141
142 if (mouse.isMouse1()) {
143 if (vertical) {
144 inSplitMove = (mouse.getX() == split);
145 } else {
146 inSplitMove = (mouse.getY() == split);
147 }
148 if (inSplitMove) {
149 return;
150 }
151 }
152
153 // I didn't take it, pass it on to my children
154 super.onMouseDown(mouse);
155 }
156
157 /**
158 * Handle mouse button releases.
159 *
160 * @param mouse mouse button release event
161 */
162 @Override
163 public void onMouseUp(final TMouseEvent mouse) {
164
165 if (inSplitMove && mouse.isMouse1()) {
166 // Stop moving split
167 inSplitMove = false;
168 return;
169 }
170
171 // I didn't take it, pass it on to my children
172 super.onMouseUp(mouse);
173 }
174
175 /**
176 * Handle mouse movements.
177 *
178 * @param mouse mouse motion event
179 */
180 @Override
181 public void onMouseMotion(final TMouseEvent mouse) {
182
183 if (inSplitMove) {
184 if (vertical) {
185 split = mouse.getX();
186 split = Math.min(Math.max(1, split), getWidth() - 2);
187 } else {
188 split = mouse.getY();
189 split = Math.min(Math.max(1, split), getHeight() - 2);
190 }
191 layoutChildren();
192 return;
193 }
194
195 // I didn't take it, pass it on to my children
196 super.onMouseMotion(mouse);
197 }
198
199 // ------------------------------------------------------------------------
200 // TWidget ----------------------------------------------------------------
201 // ------------------------------------------------------------------------
202
203 /**
204 * Draw me on screen.
205 */
206 @Override
207 public void draw() {
208 CellAttributes attr = getTheme().getColor("tsplitpane");
209 if (vertical) {
210 vLineXY(split, 0, getHeight(), GraphicsChars.WINDOW_SIDE, attr);
211 // TODO: draw intersections of children
212 } else {
213 hLineXY(0, split, getWidth(), GraphicsChars.SINGLE_BAR, attr);
214 // TODO: draw intersections of children
215 }
216 }
217
218 // ------------------------------------------------------------------------
219 // TSplitPane -------------------------------------------------------------
220 // ------------------------------------------------------------------------
221
222 /**
223 * Get the widget on the left side.
224 *
225 * @return the widget on the left, or null if not set
226 */
227 public TWidget getLeft() {
228 return left;
229 }
230
231 /**
232 * Set the widget on the left side.
233 *
234 * @param left the widget to set, or null to remove
235 */
236 public void setLeft(final TWidget left) {
237 if (!vertical) {
238 throw new IllegalArgumentException("cannot set left on " +
239 "horizontal split pane");
240 }
241 if (left == null) {
242 remove(this.left);
243 this.left = null;
244 return;
245 }
246 this.left = left;
247 left.setParent(this, false);
248 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
249 getHeight()));
250 }
251
252 /**
253 * Get the widget on the right side.
254 *
255 * @return the widget on the right, or null if not set
256 */
257 public TWidget getRight() {
258 return right;
259 }
260
261 /**
262 * Set the widget on the right side.
263 *
264 * @param right the widget to set, or null to remove
265 */
266 public void setRight(final TWidget right) {
267 if (!vertical) {
268 throw new IllegalArgumentException("cannot set right on " +
269 "horizontal split pane");
270 }
271 if (right == null) {
272 remove(this.right);
273 this.right = null;
274 return;
275 }
276 this.right = right;
277 right.setParent(this, false);
278 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
279 getHeight()));
280 }
281
282 /**
283 * Get the widget on the top side.
284 *
285 * @return the widget on the top, or null if not set
286 */
287 public TWidget getTop() {
288 return top;
289 }
290
291 /**
292 * Set the widget on the top side.
293 *
294 * @param top the widget to set, or null to remove
295 */
296 public void setTop(final TWidget top) {
297 if (vertical) {
298 throw new IllegalArgumentException("cannot set top on vertical " +
299 "split pane");
300 }
301 if (top == null) {
302 remove(this.top);
303 this.top = null;
304 return;
305 }
306 this.top = top;
307 top.setParent(this, false);
308 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
309 getHeight()));
310 }
311
312 /**
313 * Get the widget on the bottom side.
314 *
315 * @return the widget on the bottom, or null if not set
316 */
317 public TWidget getBottom() {
318 return bottom;
319 }
320
321 /**
322 * Set the widget on the bottom side.
323 *
324 * @param bottom the widget to set, or null to remove
325 */
326 public void setBottom(final TWidget bottom) {
327 if (vertical) {
328 throw new IllegalArgumentException("cannot set bottom on " +
329 "vertical split pane");
330 }
331 if (bottom == null) {
332 remove(this.bottom);
333 this.bottom = null;
334 return;
335 }
336 this.bottom = bottom;
337 bottom.setParent(this, false);
338 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
339 getHeight()));
340 }
341
342 /**
343 * Layout the two child widgets.
344 */
345 private void layoutChildren() {
346 if (vertical) {
347 if (left != null) {
348 left.setDimensions(0, 0, split, getHeight());
349 left.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
350 left.getWidth(), left.getHeight()));
351 }
352 if (right != null) {
353 right.setDimensions(split + 1, 0, getWidth() - split - 1,
354 getHeight());
355 right.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
356 right.getWidth(), right.getHeight()));
357 }
358 } else {
359 if (top != null) {
360 top.setDimensions(0, 0, getWidth(), split);
361 top.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
362 top.getWidth(), top.getHeight()));
363 }
364 if (bottom != null) {
365 bottom.setDimensions(0, split + 1, getWidth(),
366 getHeight() - split - 1);
367 bottom.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
368 bottom.getWidth(), bottom.getHeight()));
369 }
370 }
371 }
372
373 /**
374 * Recenter the split to the middle of this split pane.
375 */
376 public void center() {
377 if (vertical) {
378 split = getWidth() / 2;
379 } else {
380 split = getHeight() / 2;
381 }
382 layoutChildren();
383 }
384
385 }