• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.scenes.scene2d.ui;
18 
19 import com.badlogic.gdx.graphics.Color;
20 import com.badlogic.gdx.graphics.g2d.Batch;
21 import com.badlogic.gdx.math.Matrix4;
22 import com.badlogic.gdx.math.Rectangle;
23 import com.badlogic.gdx.math.Vector2;
24 import com.badlogic.gdx.scenes.scene2d.Actor;
25 import com.badlogic.gdx.scenes.scene2d.InputEvent;
26 import com.badlogic.gdx.scenes.scene2d.InputListener;
27 import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
28 import com.badlogic.gdx.scenes.scene2d.utils.Layout;
29 import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
30 import com.badlogic.gdx.utils.GdxRuntimeException;
31 
32 /** A container that contains two widgets and is divided either horizontally or vertically. The user may resize the widgets. The
33  * child widgets are always sized to fill their half of the splitpane.
34  * <p>
35  * The preferred size of a splitpane is that of the child widgets and the size of the {@link SplitPaneStyle#handle}. The widgets
36  * are sized depending on the splitpane's size and the {@link #setSplitAmount(float) split position}.
37  * @author mzechner
38  * @author Nathan Sweet */
39 public class SplitPane extends WidgetGroup {
40 	SplitPaneStyle style;
41 	private Actor firstWidget, secondWidget;
42 	boolean vertical;
43 	float splitAmount = 0.5f, minAmount, maxAmount = 1;
44 	private float oldSplitAmount;
45 
46 	private Rectangle firstWidgetBounds = new Rectangle();
47 	private Rectangle secondWidgetBounds = new Rectangle();
48 	Rectangle handleBounds = new Rectangle();
49 	private Rectangle firstScissors = new Rectangle();
50 	private Rectangle secondScissors = new Rectangle();
51 
52 	Vector2 lastPoint = new Vector2();
53 	Vector2 handlePosition = new Vector2();
54 
55 	/** @param firstWidget May be null.
56 	 * @param secondWidget May be null. */
SplitPane(Actor firstWidget, Actor secondWidget, boolean vertical, Skin skin)57 	public SplitPane (Actor firstWidget, Actor secondWidget, boolean vertical, Skin skin) {
58 		this(firstWidget, secondWidget, vertical, skin, "default-" + (vertical ? "vertical" : "horizontal"));
59 	}
60 
61 	/** @param firstWidget May be null.
62 	 * @param secondWidget May be null. */
SplitPane(Actor firstWidget, Actor secondWidget, boolean vertical, Skin skin, String styleName)63 	public SplitPane (Actor firstWidget, Actor secondWidget, boolean vertical, Skin skin, String styleName) {
64 		this(firstWidget, secondWidget, vertical, skin.get(styleName, SplitPaneStyle.class));
65 	}
66 
67 	/** @param firstWidget May be null.
68 	 * @param secondWidget May be null. */
SplitPane(Actor firstWidget, Actor secondWidget, boolean vertical, SplitPaneStyle style)69 	public SplitPane (Actor firstWidget, Actor secondWidget, boolean vertical, SplitPaneStyle style) {
70 		this.firstWidget = firstWidget;
71 		this.secondWidget = secondWidget;
72 		this.vertical = vertical;
73 		setStyle(style);
74 		setFirstWidget(firstWidget);
75 		setSecondWidget(secondWidget);
76 		setSize(getPrefWidth(), getPrefHeight());
77 		initialize();
78 	}
79 
initialize()80 	private void initialize () {
81 		addListener(new InputListener() {
82 			int draggingPointer = -1;
83 
84 			public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
85 				if (draggingPointer != -1) return false;
86 				if (pointer == 0 && button != 0) return false;
87 				if (handleBounds.contains(x, y)) {
88 					draggingPointer = pointer;
89 					lastPoint.set(x, y);
90 					handlePosition.set(handleBounds.x, handleBounds.y);
91 					return true;
92 				}
93 				return false;
94 			}
95 
96 			public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
97 				if (pointer == draggingPointer) draggingPointer = -1;
98 			}
99 
100 			public void touchDragged (InputEvent event, float x, float y, int pointer) {
101 				if (pointer != draggingPointer) return;
102 
103 				Drawable handle = style.handle;
104 				if (!vertical) {
105 					float delta = x - lastPoint.x;
106 					float availWidth = getWidth() - handle.getMinWidth();
107 					float dragX = handlePosition.x + delta;
108 					handlePosition.x = dragX;
109 					dragX = Math.max(0, dragX);
110 					dragX = Math.min(availWidth, dragX);
111 					splitAmount = dragX / availWidth;
112 					if (splitAmount < minAmount) splitAmount = minAmount;
113 					if (splitAmount > maxAmount) splitAmount = maxAmount;
114 					lastPoint.set(x, y);
115 				} else {
116 					float delta = y - lastPoint.y;
117 					float availHeight = getHeight() - handle.getMinHeight();
118 					float dragY = handlePosition.y + delta;
119 					handlePosition.y = dragY;
120 					dragY = Math.max(0, dragY);
121 					dragY = Math.min(availHeight, dragY);
122 					splitAmount = 1 - (dragY / availHeight);
123 					if (splitAmount < minAmount) splitAmount = minAmount;
124 					if (splitAmount > maxAmount) splitAmount = maxAmount;
125 					lastPoint.set(x, y);
126 				}
127 				invalidate();
128 			}
129 		});
130 	}
131 
setStyle(SplitPaneStyle style)132 	public void setStyle (SplitPaneStyle style) {
133 		this.style = style;
134 		invalidateHierarchy();
135 	}
136 
137 	/** Returns the split pane's style. Modifying the returned style may not have an effect until {@link #setStyle(SplitPaneStyle)}
138 	 * is called. */
getStyle()139 	public SplitPaneStyle getStyle () {
140 		return style;
141 	}
142 
143 	@Override
layout()144 	public void layout () {
145 		if (!vertical)
146 			calculateHorizBoundsAndPositions();
147 		else
148 			calculateVertBoundsAndPositions();
149 
150 		Actor firstWidget = this.firstWidget;
151 		if (firstWidget != null) {
152 			Rectangle firstWidgetBounds = this.firstWidgetBounds;
153 			firstWidget.setBounds(firstWidgetBounds.x, firstWidgetBounds.y, firstWidgetBounds.width, firstWidgetBounds.height);
154 			if (firstWidget instanceof Layout) ((Layout)firstWidget).validate();
155 		}
156 		Actor secondWidget = this.secondWidget;
157 		if (secondWidget != null) {
158 			Rectangle secondWidgetBounds = this.secondWidgetBounds;
159 			secondWidget.setBounds(secondWidgetBounds.x, secondWidgetBounds.y, secondWidgetBounds.width, secondWidgetBounds.height);
160 			if (secondWidget instanceof Layout) ((Layout)secondWidget).validate();
161 		}
162 	}
163 
164 	@Override
getPrefWidth()165 	public float getPrefWidth () {
166 		float first = firstWidget == null ? 0 : (firstWidget instanceof Layout ? ((Layout)firstWidget).getPrefWidth() : firstWidget
167 			.getWidth());
168 		float second = secondWidget == null ? 0 : (secondWidget instanceof Layout ? ((Layout)secondWidget).getPrefWidth()
169 			: secondWidget.getWidth());
170 		if (vertical) return Math.max(first, second);
171 		return first + style.handle.getMinWidth() + second;
172 	}
173 
174 	@Override
getPrefHeight()175 	public float getPrefHeight () {
176 		float first = firstWidget == null ? 0 : (firstWidget instanceof Layout ? ((Layout)firstWidget).getPrefHeight()
177 			: firstWidget.getHeight());
178 		float second = secondWidget == null ? 0 : (secondWidget instanceof Layout ? ((Layout)secondWidget).getPrefHeight()
179 			: secondWidget.getHeight());
180 		if (!vertical) return Math.max(first, second);
181 		return first + style.handle.getMinHeight() + second;
182 	}
183 
getMinWidth()184 	public float getMinWidth () {
185 		return 0;
186 	}
187 
getMinHeight()188 	public float getMinHeight () {
189 		return 0;
190 	}
191 
setVertical(boolean vertical)192 	public void setVertical (boolean vertical) {
193 		this.vertical = vertical;
194 	}
195 
calculateHorizBoundsAndPositions()196 	private void calculateHorizBoundsAndPositions () {
197 		Drawable handle = style.handle;
198 
199 		float height = getHeight();
200 
201 		float availWidth = getWidth() - handle.getMinWidth();
202 		float leftAreaWidth = (int)(availWidth * splitAmount);
203 		float rightAreaWidth = availWidth - leftAreaWidth;
204 		float handleWidth = handle.getMinWidth();
205 
206 		firstWidgetBounds.set(0, 0, leftAreaWidth, height);
207 		secondWidgetBounds.set(leftAreaWidth + handleWidth, 0, rightAreaWidth, height);
208 		handleBounds.set(leftAreaWidth, 0, handleWidth, height);
209 	}
210 
calculateVertBoundsAndPositions()211 	private void calculateVertBoundsAndPositions () {
212 		Drawable handle = style.handle;
213 
214 		float width = getWidth();
215 		float height = getHeight();
216 
217 		float availHeight = height - handle.getMinHeight();
218 		float topAreaHeight = (int)(availHeight * splitAmount);
219 		float bottomAreaHeight = availHeight - topAreaHeight;
220 		float handleHeight = handle.getMinHeight();
221 
222 		firstWidgetBounds.set(0, height - topAreaHeight, width, topAreaHeight);
223 		secondWidgetBounds.set(0, 0, width, bottomAreaHeight);
224 		handleBounds.set(0, bottomAreaHeight, width, handleHeight);
225 	}
226 
227 	@Override
draw(Batch batch, float parentAlpha)228 	public void draw (Batch batch, float parentAlpha) {
229 		validate();
230 
231 		Color color = getColor();
232 
233 		Drawable handle = style.handle;
234 		applyTransform(batch, computeTransform());
235 		Matrix4 transform = batch.getTransformMatrix();
236 		if (firstWidget != null) {
237 			batch.flush();
238 			getStage().calculateScissors(firstWidgetBounds, firstScissors);
239 			if (ScissorStack.pushScissors(firstScissors)) {
240 				if (firstWidget.isVisible()) firstWidget.draw(batch, parentAlpha * color.a);
241 				batch.flush();
242 				ScissorStack.popScissors();
243 			}
244 		}
245 		if (secondWidget != null) {
246 			batch.flush();
247 			getStage().calculateScissors(secondWidgetBounds, secondScissors);
248 			if (ScissorStack.pushScissors(secondScissors)) {
249 				if (secondWidget.isVisible()) secondWidget.draw(batch, parentAlpha * color.a);
250 				batch.flush();
251 				ScissorStack.popScissors();
252 			}
253 		}
254 		batch.setColor(color.r, color.g, color.b, parentAlpha * color.a);
255 		handle.draw(batch, handleBounds.x, handleBounds.y, handleBounds.width, handleBounds.height);
256 		resetTransform(batch);
257 	}
258 
259 	/** @param split The split amount between the min and max amount. */
setSplitAmount(float split)260 	public void setSplitAmount (float split) {
261 		this.splitAmount = Math.max(Math.min(maxAmount, split), minAmount);
262 		invalidate();
263 	}
264 
getSplit()265 	public float getSplit () {
266 		return splitAmount;
267 	}
268 
setMinSplitAmount(float minAmount)269 	public void setMinSplitAmount (float minAmount) {
270 		if (minAmount < 0) throw new GdxRuntimeException("minAmount has to be >= 0");
271 		if (minAmount >= maxAmount) throw new GdxRuntimeException("minAmount has to be < maxAmount");
272 		this.minAmount = minAmount;
273 	}
274 
setMaxSplitAmount(float maxAmount)275 	public void setMaxSplitAmount (float maxAmount) {
276 		if (maxAmount > 1) throw new GdxRuntimeException("maxAmount has to be <= 1");
277 		if (maxAmount <= minAmount) throw new GdxRuntimeException("maxAmount has to be > minAmount");
278 		this.maxAmount = maxAmount;
279 	}
280 
281 	/** @param widget May be null. */
setFirstWidget(Actor widget)282 	public void setFirstWidget (Actor widget) {
283 		if (firstWidget != null) super.removeActor(firstWidget);
284 		firstWidget = widget;
285 		if (widget != null) super.addActor(widget);
286 		invalidate();
287 	}
288 
289 	/** @param widget May be null. */
setSecondWidget(Actor widget)290 	public void setSecondWidget (Actor widget) {
291 		if (secondWidget != null) super.removeActor(secondWidget);
292 		secondWidget = widget;
293 		if (widget != null) super.addActor(widget);
294 		invalidate();
295 	}
296 
addActor(Actor actor)297 	public void addActor (Actor actor) {
298 		throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
299 	}
300 
addActorAt(int index, Actor actor)301 	public void addActorAt (int index, Actor actor) {
302 		throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
303 	}
304 
addActorBefore(Actor actorBefore, Actor actor)305 	public void addActorBefore (Actor actorBefore, Actor actor) {
306 		throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
307 	}
308 
removeActor(Actor actor)309 	public boolean removeActor (Actor actor) {
310 		throw new UnsupportedOperationException("Use ScrollPane#setWidget(null).");
311 	}
312 
313 	/** The style for a splitpane, see {@link SplitPane}.
314 	 * @author mzechner
315 	 * @author Nathan Sweet */
316 	static public class SplitPaneStyle {
317 		public Drawable handle;
318 
SplitPaneStyle()319 		public SplitPaneStyle () {
320 		}
321 
SplitPaneStyle(Drawable handle)322 		public SplitPaneStyle (Drawable handle) {
323 			this.handle = handle;
324 		}
325 
SplitPaneStyle(SplitPaneStyle style)326 		public SplitPaneStyle (SplitPaneStyle style) {
327 			this.handle = style.handle;
328 		}
329 	}
330 }
331