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