Skip to content

Instantly share code, notes, and snippets.

@xaguzman
Last active August 29, 2015 14:03
Show Gist options
  • Save xaguzman/634d3f2a95fdc3c26ebc to your computer and use it in GitHub Desktop.
Save xaguzman/634d3f2a95fdc3c26ebc to your computer and use it in GitHub Desktop.
Libgdx Scene2dUI Window with title bar
/*******************************************************************************
* 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.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds;
import com.badlogic.gdx.graphics.g2d.BitmapFontCache;
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.Stage;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.utils.Align;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
/** A table that can be dragged and act as a modal window. The top padding is used as the window's title height.
* <p>
* The preferred size of a window is the preferred size of the title text and the children as laid out by the table. After adding
* children to the window, it can be convenient to call {@link #pack()} to size the window to the size of the children.
* @author Nathan Sweet */
public class Window extends Table {
static private final Vector2 tmpPosition = new Vector2();
static private final Vector2 tmpSize = new Vector2();
static private final int MOVE = 1 << 5;
private WindowStyle style;
private String title;
private BitmapFontCache titleCache;
boolean isMovable = true, isModal, isResizable;
int resizeBorder = 8;
boolean dragging;
private int titleAlignment = Align.center;
boolean keepWithinStage = true;
Table buttonTable;
public Window (String title, Skin skin) {
this(title, skin.get(WindowStyle.class));
setSkin(skin);
}
public Window (String title, Skin skin, String styleName) {
this(title, skin.get(styleName, WindowStyle.class));
setSkin(skin);
}
public Window (String title, WindowStyle style) {
if (title == null) throw new IllegalArgumentException("title cannot be null.");
this.title = title;
setTouchable(Touchable.enabled);
setClip(true);
setStyle(style);
setWidth(150);
setHeight(150);
setTitle(title);
buttonTable = new Table();
addActor(buttonTable);
addCaptureListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
toFront();
return false;
}
});
addListener(new InputListener() {
int edge;
float startX, startY, lastX, lastY;
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
if (button == 0) {
int border = resizeBorder;
float width = getWidth(), height = getHeight();
edge = 0;
if (isResizable) {
if (x < border) edge |= Align.left;
if (x > width - border) edge |= Align.right;
if (y < border) edge |= Align.bottom;
if (y > height - border) edge |= Align.top;
if (edge != 0) border += 25;
if (x < border) edge |= Align.left;
if (x > width - border) edge |= Align.right;
if (y < border) edge |= Align.bottom;
if (y > height - border) edge |= Align.top;
}
if (isMovable && edge == 0 && y <= height && y >= height - getPadTop() && x >= 0 && x <= width) edge = MOVE;
dragging = edge != 0;
startX = x;
startY = y;
lastX = x;
lastY = y;
}
return edge != 0 || isModal;
}
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
dragging = false;
}
public void touchDragged (InputEvent event, float x, float y, int pointer) {
if (!dragging) return;
float width = getWidth(), height = getHeight();
float windowX = getX(), windowY = getY();
float minWidth = getMinWidth(), maxWidth = getMaxWidth();
float minHeight = getMinHeight(), maxHeight = getMaxHeight();
Stage stage = getStage();
boolean clampPosition = keepWithinStage && getParent() == stage.getRoot();
if ((edge & MOVE) != 0) {
float amountX = x - startX, amountY = y - startY;
windowX += amountX;
windowY += amountY;
}
if ((edge & Align.left) != 0) {
float amountX = x - startX;
if (width - amountX < minWidth) amountX = -(minWidth - width);
if (clampPosition && windowX + amountX < 0) amountX = -windowX;
width -= amountX;
windowX += amountX;
}
if ((edge & Align.bottom) != 0) {
float amountY = y - startY;
if (height - amountY < minHeight) amountY = -(minHeight - height);
if (clampPosition && windowY + amountY < 0) amountY = -windowY;
height -= amountY;
windowY += amountY;
}
if ((edge & Align.right) != 0) {
float amountX = x - lastX;
if (width + amountX < minWidth) amountX = minWidth - width;
if (clampPosition && windowX + width + amountX > stage.getWidth()) amountX = stage.getWidth() - windowX - width;
width += amountX;
}
if ((edge & Align.top) != 0) {
float amountY = y - lastY;
if (height + amountY < minHeight) amountY = minHeight - height;
if (clampPosition && windowY + height + amountY > stage.getHeight())
amountY = stage.getHeight() - windowY - height;
height += amountY;
}
lastX = x;
lastY = y;
setBounds(Math.round(windowX), Math.round(windowY), Math.round(width), Math.round(height));
}
public boolean mouseMoved (InputEvent event, float x, float y) {
return isModal;
}
public boolean scrolled (InputEvent event, float x, float y, int amount) {
return isModal;
}
public boolean keyDown (InputEvent event, int keycode) {
return isModal;
}
public boolean keyUp (InputEvent event, int keycode) {
return isModal;
}
public boolean keyTyped (InputEvent event, char character) {
return isModal;
}
});
}
public void setStyle (WindowStyle style) {
if (style == null) throw new IllegalArgumentException("style cannot be null.");
this.style = style;
setBackground(style.background);
titleCache = new BitmapFontCache(style.titleFont);
titleCache.setColor(style.titleFontColor);
if (title != null) setTitle(title);
invalidateHierarchy();
}
/** Returns the window's style. Modifying the returned style may not have an effect until {@link #setStyle(WindowStyle)} is
* called. */
public WindowStyle getStyle () {
return style;
}
void keepWithinStage () {
if (!keepWithinStage) return;
Stage stage = getStage();
if (getParent() == stage.getRoot()) {
float parentWidth = stage.getWidth();
float parentHeight = stage.getHeight();
if (getX() < 0) setX(0);
if (getRight() > parentWidth) setX(parentWidth - getWidth());
if (getY() < 0) setY(0);
if (getTop() > parentHeight) setY(parentHeight - getHeight());
}
}
public void draw (Batch batch, float parentAlpha) {
keepWithinStage();
if (style.stageBackground != null) {
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
Stage stage = getStage();
stageToLocalCoordinates(/* in/out */tmpPosition.set(0, 0));
stageToLocalCoordinates(/* in/out */tmpSize.set(stage.getWidth(), stage.getHeight()));
style.stageBackground
.draw(batch, getX() + tmpPosition.x, getY() + tmpPosition.y, getX() + tmpSize.x, getY() + tmpSize.y);
}
super.draw(batch, parentAlpha);
}
protected void drawBackground (Batch batch, float parentAlpha, float x, float y) {
float width = getWidth(), height = getHeight();
float padTop = getPadTop();
super.drawBackground(batch, parentAlpha, x, y);
if ( style.titleBarDrawable != null){
float barHeight = getHeight() * style.titleBarHeight;
style.titleBarDrawable.draw(batch, x, y + getHeight() - barHeight, width, barHeight);
}
// Draw button table.
buttonTable.getColor().a = getColor().a;
buttonTable.pack();
buttonTable.setPosition(width - buttonTable.getWidth(), Math.min(height - padTop, height - buttonTable.getHeight()));
buttonTable.draw(batch, parentAlpha);
// Draw the title without the batch transformed or clipping applied.
y += height;
TextBounds bounds = titleCache.getBounds();
if ((titleAlignment & Align.left) != 0)
x += getPadLeft();
else if ((titleAlignment & Align.right) != 0)
x += width - bounds.width - getPadRight();
else
x += (width - bounds.width) / 2;
if ((titleAlignment & Align.top) == 0) {
if ((titleAlignment & Align.bottom) != 0)
y -= padTop - bounds.height;
else
y -= (padTop - bounds.height) / 2;
}
titleCache.setColors(Color.tmp.set(getColor()).mul(style.titleFontColor));
titleCache.setPosition((int)x, (int)y);
titleCache.draw(batch, parentAlpha);
}
public Actor hit (float x, float y, boolean touchable) {
Actor hit = super.hit(x, y, touchable);
if (hit == null && isModal && (!touchable || getTouchable() == Touchable.enabled)) return this;
return hit;
}
public void setTitle (String title) {
this.title = title;
titleCache.setMultiLineText(title, 0, 0);
}
public String getTitle () {
return title;
}
/** @param titleAlignment {@link Align} */
public void setTitleAlignment (int titleAlignment) {
this.titleAlignment = titleAlignment;
}
public boolean isMovable () {
return isMovable;
}
public void setMovable (boolean isMovable) {
this.isMovable = isMovable;
}
public boolean isModal () {
return isModal;
}
public void setModal (boolean isModal) {
this.isModal = isModal;
}
public void setKeepWithinStage (boolean keepWithinStage) {
this.keepWithinStage = keepWithinStage;
}
public boolean isResizable () {
return isResizable;
}
public void setResizable (boolean isResizable) {
this.isResizable = isResizable;
}
public void setResizeBorder (int resizeBorder) {
this.resizeBorder = resizeBorder;
}
public boolean isDragging () {
return dragging;
}
public float getTitleWidth () {
return titleCache.getBounds().width;
}
public float getPrefWidth () {
return Math.max(super.getPrefWidth(), getTitleWidth() + getPadLeft() + getPadRight());
}
public Table getButtonTable () {
return buttonTable;
}
/** The style for a window, see {@link Window}.
* @author Nathan Sweet */
static public class WindowStyle {
/** Optional. */
public Drawable background;
public BitmapFont titleFont;
/** Optional. */
public Color titleFontColor = new Color(1, 1, 1, 1);
/** Optional. */
public Drawable stageBackground;
/** The drawable to use for the title. Optional */
public Drawable titleBarDrawable;
/** The height of the bar. It is given as a percent, relative to the window. By default, it is
* 0.1, which means 10% of the window is meant to be the title bar. Required if {@link #titleBarDrawable} is specified*/
public float titleBarHeight = 0.1f;
public WindowStyle () {
}
public WindowStyle (BitmapFont titleFont, Color titleFontColor, Drawable background, Drawable titleBarBG, float titleBarHeight) {
this.background = background;
this.titleFont = titleFont;
this.titleFontColor.set(titleFontColor);
this.titleBarDrawable = titleBarBG;
this.titleBarHeight = titleBarHeight;
}
public WindowStyle (WindowStyle style) {
this(style.titleFont, new Color(style.titleFontColor), style.background, style.titleBarDrawable, style.titleBarHeight);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment