/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.scenes.scene2d.ui; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.utils.Drawable; import com.badlogic.gdx.scenes.scene2d.utils.Layout; import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack; import com.badlogic.gdx.utils.GdxRuntimeException; /** * A container that contains two widgets and is divided either horizontally or vertically. The user may resize the * widgets. The child widgets are always sized to fill their half of the splitpane. * <p> * The preferred size of a splitpane is that of the child widgets and the size of the {@link SplitPaneStyle#handle}. The * widgets are sized depending on the splitpane's size and the {@link #setSplitAmount(float) split position}. * * @author mzechner * @author Nathan Sweet */ public class SplitPane extends WidgetGroup { SplitPaneStyle style; private Actor firstWidget, secondWidget; boolean vertical; float splitAmount = 0.5f, minAmount, maxAmount = 1; private float oldSplitAmount; private Rectangle firstWidgetBounds = new Rectangle(); private Rectangle secondWidgetBounds = new Rectangle(); Rectangle handleBounds = new Rectangle(); private Rectangle firstScissors = new Rectangle(); private Rectangle secondScissors = new Rectangle(); Vector2 lastPoint = new Vector2(); Vector2 handlePosition = new Vector2(); /** * @param firstWidget * May be null. * @param secondWidget * May be null. */ public SplitPane(Actor firstWidget, Actor secondWidget, boolean vertical, Skin skin) { this(firstWidget, secondWidget, vertical, skin, "default-" + (vertical ? "vertical" : "horizontal")); } /** * @param firstWidget * May be null. * @param secondWidget * May be null. */ public SplitPane(Actor firstWidget, Actor secondWidget, boolean vertical, Skin skin, String styleName) { this(firstWidget, secondWidget, vertical, skin.get(styleName, SplitPaneStyle.class)); } /** * @param firstWidget * May be null. * @param secondWidget * May be null. */ public SplitPane(Actor firstWidget, Actor secondWidget, boolean vertical, SplitPaneStyle style) { this.firstWidget = firstWidget; this.secondWidget = secondWidget; this.vertical = vertical; setStyle(style); setFirstWidget(firstWidget); setSecondWidget(secondWidget); setWidth(getPrefWidth()); setHeight(getPrefHeight()); initialize(); } private void initialize() { addListener(new InputListener() { int draggingPointer = -1; public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { if (draggingPointer != -1) return false; if (pointer == 0 && button != 0) return false; if (handleBounds.contains(x, y)) { draggingPointer = pointer; lastPoint.set(x, y); handlePosition.set(handleBounds.x, handleBounds.y); return true; } return false; } public void touchUp(InputEvent event, float x, float y, int pointer, int button) { if (pointer == draggingPointer) draggingPointer = -1; } public void touchDragged(InputEvent event, float x, float y, int pointer) { if (pointer != draggingPointer) return; Drawable handle = style.handle; if (!vertical) { float delta = x - lastPoint.x; float availWidth = getWidth() - handle.getMinWidth(); float dragX = handlePosition.x + delta; handlePosition.x = dragX; dragX = Math.max(0, dragX); dragX = Math.min(availWidth, dragX); splitAmount = dragX / availWidth; if (splitAmount < minAmount) splitAmount = minAmount; if (splitAmount > maxAmount) splitAmount = maxAmount; lastPoint.set(x, y); } else { float delta = y - lastPoint.y; float availHeight = getHeight() - handle.getMinHeight(); float dragY = handlePosition.y + delta; handlePosition.y = dragY; dragY = Math.max(0, dragY); dragY = Math.min(availHeight, dragY); splitAmount = 1 - (dragY / availHeight); if (splitAmount < minAmount) splitAmount = minAmount; if (splitAmount > maxAmount) splitAmount = maxAmount; lastPoint.set(x, y); } invalidate(); } }); } public void setStyle(SplitPaneStyle style) { this.style = style; invalidateHierarchy(); } /** * Returns the split pane's style. Modifying the returned style may not have an effect until * {@link #setStyle(SplitPaneStyle)} is called. */ public SplitPaneStyle getStyle() { return style; } @Override public void layout() { if (!vertical) calculateHorizBoundsAndPositions(); else calculateVertBoundsAndPositions(); Actor firstWidget = this.firstWidget; Rectangle firstWidgetBounds = this.firstWidgetBounds; if (firstWidget != null) { firstWidget.setX(firstWidgetBounds.x); firstWidget.setY(firstWidgetBounds.y); if (firstWidget.getWidth() != firstWidgetBounds.width || firstWidget.getHeight() != firstWidgetBounds.height) { firstWidget.setWidth(firstWidgetBounds.width); firstWidget.setHeight(firstWidgetBounds.height); if (firstWidget instanceof Layout) { Layout layout = (Layout) firstWidget; layout.invalidate(); layout.validate(); } } else { if (firstWidget instanceof Layout) ((Layout) firstWidget).validate(); } } Actor secondWidget = this.secondWidget; Rectangle secondWidgetBounds = this.secondWidgetBounds; if (secondWidget != null) { secondWidget.setX(secondWidgetBounds.x); secondWidget.setY(secondWidgetBounds.y); if (secondWidget.getWidth() != secondWidgetBounds.width || secondWidget.getHeight() != secondWidgetBounds.height) { secondWidget.setWidth(secondWidgetBounds.width); secondWidget.setHeight(secondWidgetBounds.height); if (secondWidget instanceof Layout) { Layout layout = (Layout) secondWidget; layout.invalidate(); layout.validate(); } } else { if (secondWidget instanceof Layout) ((Layout) secondWidget).validate(); } } } @Override public float getPrefWidth() { float width = firstWidget instanceof Layout ? ((Layout) firstWidget).getPrefWidth() : firstWidget.getWidth(); width += secondWidget instanceof Layout ? ((Layout) secondWidget).getPrefWidth() : secondWidget.getWidth(); if (!vertical) width += style.handle.getMinWidth(); return width; } @Override public float getPrefHeight() { float height = firstWidget instanceof Layout ? ((Layout) firstWidget).getPrefHeight() : firstWidget.getHeight(); height += secondWidget instanceof Layout ? ((Layout) secondWidget).getPrefHeight() : secondWidget.getHeight(); if (vertical) height += style.handle.getMinHeight(); return height; } public float getMinWidth() { return 0; } public float getMinHeight() { return 0; } public void setVertical(boolean vertical) { this.vertical = vertical; } private void calculateHorizBoundsAndPositions() { Drawable handle = style.handle; float height = getHeight(); float availWidth = getWidth() - handle.getMinWidth(); float leftAreaWidth = (int) (availWidth * splitAmount); float rightAreaWidth = availWidth - leftAreaWidth; float handleWidth = handle.getMinWidth(); firstWidgetBounds.set(0, 0, leftAreaWidth, height); secondWidgetBounds.set(leftAreaWidth + handleWidth, 0, rightAreaWidth, height); handleBounds.set(leftAreaWidth, 0, handleWidth, height); } private void calculateVertBoundsAndPositions() { Drawable handle = style.handle; float width = getWidth(); float height = getHeight(); float availHeight = height - handle.getMinHeight(); float topAreaHeight = (int) (availHeight * splitAmount); float bottomAreaHeight = availHeight - topAreaHeight; float handleHeight = handle.getMinHeight(); firstWidgetBounds.set(0, height - topAreaHeight, width, topAreaHeight); secondWidgetBounds.set(0, 0, width, bottomAreaHeight); handleBounds.set(0, bottomAreaHeight, width, handleHeight); } @Override public void draw(SpriteBatch batch, float parentAlpha) { validate(); Color color = getColor(); Drawable handle = style.handle; applyTransform(batch, computeTransform()); Matrix4 transform = batch.getTransformMatrix(); if (firstWidget != null) { ScissorStack.calculateScissors(getStage().getCamera(), transform, firstWidgetBounds, firstScissors); if (ScissorStack.pushScissors(firstScissors)) { if (firstWidget.isVisible()) firstWidget.draw(batch, parentAlpha * color.a); batch.flush(); ScissorStack.popScissors(); } } if (secondWidget != null) { ScissorStack.calculateScissors(getStage().getCamera(), transform, secondWidgetBounds, secondScissors); if (ScissorStack.pushScissors(secondScissors)) { if (secondWidget.isVisible()) secondWidget.draw(batch, parentAlpha * color.a); batch.flush(); ScissorStack.popScissors(); } } batch.setColor(color.r, color.g, color.b, color.a); handle.draw(batch, handleBounds.x, handleBounds.y, handleBounds.width, handleBounds.height); resetTransform(batch); } /** * @param split * The split amount between the min and max amount. */ public void setSplitAmount(float split) { this.splitAmount = Math.max(Math.min(maxAmount, split), minAmount); invalidate(); } public float getSplit() { return splitAmount; } public void setMinSplitAmount(float minAmount) { if (minAmount < 0) throw new GdxRuntimeException("minAmount has to be >= 0"); if (minAmount >= maxAmount) throw new GdxRuntimeException("minAmount has to be < maxAmount"); this.minAmount = minAmount; } public void setMaxSplitAmount(float maxAmount) { if (maxAmount > 1) throw new GdxRuntimeException("maxAmount has to be >= 0"); if (maxAmount <= minAmount) throw new GdxRuntimeException("maxAmount has to be > minAmount"); this.maxAmount = maxAmount; } /** * @param widget * May be null. */ public void setFirstWidget(Actor widget) { if (firstWidget != null) super.removeActor(firstWidget); firstWidget = widget; if (widget != null) super.addActor(widget); invalidate(); } /** * @param widget * May be null. */ public void setSecondWidget(Actor widget) { if (secondWidget != null) super.removeActor(secondWidget); secondWidget = widget; if (widget != null) super.addActor(widget); invalidate(); } public void addActor(Actor actor) { throw new UnsupportedOperationException("Use ScrollPane#setWidget."); } public void addActorAt(int index, Actor actor) { throw new UnsupportedOperationException("Use ScrollPane#setWidget."); } public void addActorBefore(Actor actorBefore, Actor actor) { throw new UnsupportedOperationException("Use ScrollPane#setWidget."); } public boolean removeActor(Actor actor) { throw new UnsupportedOperationException("Use ScrollPane#setWidget(null)."); } /** * The style for a splitpane, see {@link SplitPane}. * * @author mzechner * @author Nathan Sweet */ static public class SplitPaneStyle { public Drawable handle; public SplitPaneStyle() { } public SplitPaneStyle(Drawable handle) { this.handle = handle; } public SplitPaneStyle(SplitPaneStyle style) { this.handle = style.handle; } } }