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.Camera; 20 import com.badlogic.gdx.graphics.Color; 21 import com.badlogic.gdx.graphics.OrthographicCamera; 22 import com.badlogic.gdx.graphics.g2d.Batch; 23 import com.badlogic.gdx.graphics.g2d.BitmapFont; 24 import com.badlogic.gdx.math.Vector2; 25 import com.badlogic.gdx.scenes.scene2d.Actor; 26 import com.badlogic.gdx.scenes.scene2d.InputEvent; 27 import com.badlogic.gdx.scenes.scene2d.InputListener; 28 import com.badlogic.gdx.scenes.scene2d.Stage; 29 import com.badlogic.gdx.scenes.scene2d.Touchable; 30 import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle; 31 import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 32 import com.badlogic.gdx.utils.Align; 33 34 /** A table that can be dragged and act as a modal window. The top padding is used as the window's title height. 35 * <p> 36 * 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 37 * children to the window, it can be convenient to call {@link #pack()} to size the window to the size of the children. 38 * @author Nathan Sweet */ 39 public class Window extends Table { 40 static private final Vector2 tmpPosition = new Vector2(); 41 static private final Vector2 tmpSize = new Vector2(); 42 static private final int MOVE = 1 << 5; 43 44 private WindowStyle style; 45 boolean isMovable = true, isModal, isResizable; 46 int resizeBorder = 8; 47 boolean dragging; 48 boolean keepWithinStage = true; 49 Label titleLabel; 50 Table titleTable; 51 boolean drawTitleTable; 52 Window(String title, Skin skin)53 public Window (String title, Skin skin) { 54 this(title, skin.get(WindowStyle.class)); 55 setSkin(skin); 56 } 57 Window(String title, Skin skin, String styleName)58 public Window (String title, Skin skin, String styleName) { 59 this(title, skin.get(styleName, WindowStyle.class)); 60 setSkin(skin); 61 } 62 Window(String title, WindowStyle style)63 public Window (String title, WindowStyle style) { 64 if (title == null) throw new IllegalArgumentException("title cannot be null."); 65 setTouchable(Touchable.enabled); 66 setClip(true); 67 68 titleLabel = new Label(title, new LabelStyle(style.titleFont, style.titleFontColor)); 69 titleLabel.setEllipsis(true); 70 71 titleTable = new Table() { 72 public void draw (Batch batch, float parentAlpha) { 73 if (drawTitleTable) super.draw(batch, parentAlpha); 74 } 75 }; 76 titleTable.add(titleLabel).expandX().fillX().minWidth(0); 77 addActor(titleTable); 78 79 setStyle(style); 80 setWidth(150); 81 setHeight(150); 82 83 addCaptureListener(new InputListener() { 84 public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { 85 toFront(); 86 return false; 87 } 88 }); 89 addListener(new InputListener() { 90 int edge; 91 float startX, startY, lastX, lastY; 92 93 public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { 94 if (button == 0) { 95 int border = resizeBorder; 96 float width = getWidth(), height = getHeight(); 97 edge = 0; 98 if (isResizable && x >= 0 && x < width && y >= 0 && y < height) { 99 if (x < border) edge |= Align.left; 100 if (x > width - border) edge |= Align.right; 101 if (y < border) edge |= Align.bottom; 102 if (y > height - border) edge |= Align.top; 103 if (edge != 0) border += 25; 104 if (x < border) edge |= Align.left; 105 if (x > width - border) edge |= Align.right; 106 if (y < border) edge |= Align.bottom; 107 if (y > height - border) edge |= Align.top; 108 } 109 if (isMovable && edge == 0 && y <= height && y >= height - getPadTop() && x >= 0 && x <= width) edge = MOVE; 110 dragging = edge != 0; 111 startX = x; 112 startY = y; 113 lastX = x - width; 114 lastY = y - height; 115 } 116 return edge != 0 || isModal; 117 } 118 119 public void touchUp (InputEvent event, float x, float y, int pointer, int button) { 120 dragging = false; 121 } 122 123 public void touchDragged (InputEvent event, float x, float y, int pointer) { 124 if (!dragging) return; 125 float width = getWidth(), height = getHeight(); 126 float windowX = getX(), windowY = getY(); 127 128 float minWidth = getMinWidth(), maxWidth = getMaxWidth(); 129 float minHeight = getMinHeight(), maxHeight = getMaxHeight(); 130 Stage stage = getStage(); 131 boolean clampPosition = keepWithinStage && getParent() == stage.getRoot(); 132 133 if ((edge & MOVE) != 0) { 134 float amountX = x - startX, amountY = y - startY; 135 windowX += amountX; 136 windowY += amountY; 137 } 138 if ((edge & Align.left) != 0) { 139 float amountX = x - startX; 140 if (width - amountX < minWidth) amountX = -(minWidth - width); 141 if (clampPosition && windowX + amountX < 0) amountX = -windowX; 142 width -= amountX; 143 windowX += amountX; 144 } 145 if ((edge & Align.bottom) != 0) { 146 float amountY = y - startY; 147 if (height - amountY < minHeight) amountY = -(minHeight - height); 148 if (clampPosition && windowY + amountY < 0) amountY = -windowY; 149 height -= amountY; 150 windowY += amountY; 151 } 152 if ((edge & Align.right) != 0) { 153 float amountX = x - lastX - width; 154 if (width + amountX < minWidth) amountX = minWidth - width; 155 if (clampPosition && windowX + width + amountX > stage.getWidth()) amountX = stage.getWidth() - windowX - width; 156 width += amountX; 157 } 158 if ((edge & Align.top) != 0) { 159 float amountY = y - lastY - height; 160 if (height + amountY < minHeight) amountY = minHeight - height; 161 if (clampPosition && windowY + height + amountY > stage.getHeight()) 162 amountY = stage.getHeight() - windowY - height; 163 height += amountY; 164 } 165 setBounds(Math.round(windowX), Math.round(windowY), Math.round(width), Math.round(height)); 166 } 167 168 public boolean mouseMoved (InputEvent event, float x, float y) { 169 return isModal; 170 } 171 172 public boolean scrolled (InputEvent event, float x, float y, int amount) { 173 return isModal; 174 } 175 176 public boolean keyDown (InputEvent event, int keycode) { 177 return isModal; 178 } 179 180 public boolean keyUp (InputEvent event, int keycode) { 181 return isModal; 182 } 183 184 public boolean keyTyped (InputEvent event, char character) { 185 return isModal; 186 } 187 }); 188 } 189 setStyle(WindowStyle style)190 public void setStyle (WindowStyle style) { 191 if (style == null) throw new IllegalArgumentException("style cannot be null."); 192 this.style = style; 193 setBackground(style.background); 194 titleLabel.setStyle(new LabelStyle(style.titleFont, style.titleFontColor)); 195 invalidateHierarchy(); 196 } 197 198 /** Returns the window's style. Modifying the returned style may not have an effect until {@link #setStyle(WindowStyle)} is 199 * called. */ getStyle()200 public WindowStyle getStyle () { 201 return style; 202 } 203 keepWithinStage()204 void keepWithinStage () { 205 if (!keepWithinStage) return; 206 Stage stage = getStage(); 207 Camera camera = stage.getCamera(); 208 if (camera instanceof OrthographicCamera) { 209 OrthographicCamera orthographicCamera = (OrthographicCamera)camera; 210 float parentWidth = stage.getWidth(); 211 float parentHeight = stage.getHeight(); 212 if (getX(Align.right) - camera.position.x > parentWidth / 2 / orthographicCamera.zoom) 213 setPosition(camera.position.x + parentWidth / 2 / orthographicCamera.zoom, getY(Align.right), Align.right); 214 if (getX(Align.left) - camera.position.x < -parentWidth / 2 / orthographicCamera.zoom) 215 setPosition(camera.position.x - parentWidth / 2 / orthographicCamera.zoom, getY(Align.left), Align.left); 216 if (getY(Align.top) - camera.position.y > parentHeight / 2 / orthographicCamera.zoom) 217 setPosition(getX(Align.top), camera.position.y + parentHeight / 2 / orthographicCamera.zoom, Align.top); 218 if (getY(Align.bottom) - camera.position.y < -parentHeight / 2 / orthographicCamera.zoom) 219 setPosition(getX(Align.bottom), camera.position.y - parentHeight / 2 / orthographicCamera.zoom, Align.bottom); 220 } else if (getParent() == stage.getRoot()) { 221 float parentWidth = stage.getWidth(); 222 float parentHeight = stage.getHeight(); 223 if (getX() < 0) setX(0); 224 if (getRight() > parentWidth) setX(parentWidth - getWidth()); 225 if (getY() < 0) setY(0); 226 if (getTop() > parentHeight) setY(parentHeight - getHeight()); 227 } 228 } 229 draw(Batch batch, float parentAlpha)230 public void draw (Batch batch, float parentAlpha) { 231 Stage stage = getStage(); 232 if (stage.getKeyboardFocus() == null) stage.setKeyboardFocus(this); 233 234 keepWithinStage(); 235 236 if (style.stageBackground != null) { 237 stageToLocalCoordinates(tmpPosition.set(0, 0)); 238 stageToLocalCoordinates(tmpSize.set(stage.getWidth(), stage.getHeight())); 239 drawStageBackground(batch, parentAlpha, getX() + tmpPosition.x, getY() + tmpPosition.y, getX() + tmpSize.x, getY() 240 + tmpSize.y); 241 } 242 243 super.draw(batch, parentAlpha); 244 } 245 drawStageBackground(Batch batch, float parentAlpha, float x, float y, float width, float height)246 protected void drawStageBackground (Batch batch, float parentAlpha, float x, float y, float width, float height) { 247 Color color = getColor(); 248 batch.setColor(color.r, color.g, color.b, color.a * parentAlpha); 249 style.stageBackground.draw(batch, x, y, width, height); 250 } 251 drawBackground(Batch batch, float parentAlpha, float x, float y)252 protected void drawBackground (Batch batch, float parentAlpha, float x, float y) { 253 super.drawBackground(batch, parentAlpha, x, y); 254 255 // Manually draw the title table before clipping is done. 256 titleTable.getColor().a = getColor().a; 257 float padTop = getPadTop(), padLeft = getPadLeft(); 258 titleTable.setSize(getWidth() - padLeft - getPadRight(), padTop); 259 titleTable.setPosition(padLeft, getHeight() - padTop); 260 drawTitleTable = true; 261 titleTable.draw(batch, parentAlpha); 262 drawTitleTable = false; // Avoid drawing the title table again in drawChildren. 263 } 264 hit(float x, float y, boolean touchable)265 public Actor hit (float x, float y, boolean touchable) { 266 Actor hit = super.hit(x, y, touchable); 267 if (hit == null && isModal && (!touchable || getTouchable() == Touchable.enabled)) return this; 268 float height = getHeight(); 269 if (hit == null || hit == this) return hit; 270 if (y <= height && y >= height - getPadTop() && x >= 0 && x <= getWidth()) { 271 // Hit the title bar, don't use the hit child if it is in the Window's table. 272 Actor current = hit; 273 while (current.getParent() != this) 274 current = current.getParent(); 275 if (getCell(current) != null) return this; 276 } 277 return hit; 278 } 279 isMovable()280 public boolean isMovable () { 281 return isMovable; 282 } 283 setMovable(boolean isMovable)284 public void setMovable (boolean isMovable) { 285 this.isMovable = isMovable; 286 } 287 isModal()288 public boolean isModal () { 289 return isModal; 290 } 291 setModal(boolean isModal)292 public void setModal (boolean isModal) { 293 this.isModal = isModal; 294 } 295 setKeepWithinStage(boolean keepWithinStage)296 public void setKeepWithinStage (boolean keepWithinStage) { 297 this.keepWithinStage = keepWithinStage; 298 } 299 isResizable()300 public boolean isResizable () { 301 return isResizable; 302 } 303 setResizable(boolean isResizable)304 public void setResizable (boolean isResizable) { 305 this.isResizable = isResizable; 306 } 307 setResizeBorder(int resizeBorder)308 public void setResizeBorder (int resizeBorder) { 309 this.resizeBorder = resizeBorder; 310 } 311 isDragging()312 public boolean isDragging () { 313 return dragging; 314 } 315 getPrefWidth()316 public float getPrefWidth () { 317 return Math.max(super.getPrefWidth(), titleLabel.getPrefWidth() + getPadLeft() + getPadRight()); 318 } 319 getTitleTable()320 public Table getTitleTable () { 321 return titleTable; 322 } 323 getTitleLabel()324 public Label getTitleLabel () { 325 return titleLabel; 326 } 327 328 /** The style for a window, see {@link Window}. 329 * @author Nathan Sweet */ 330 static public class WindowStyle { 331 /** Optional. */ 332 public Drawable background; 333 public BitmapFont titleFont; 334 /** Optional. */ 335 public Color titleFontColor = new Color(1, 1, 1, 1); 336 /** Optional. */ 337 public Drawable stageBackground; 338 WindowStyle()339 public WindowStyle () { 340 } 341 WindowStyle(BitmapFont titleFont, Color titleFontColor, Drawable background)342 public WindowStyle (BitmapFont titleFont, Color titleFontColor, Drawable background) { 343 this.background = background; 344 this.titleFont = titleFont; 345 this.titleFontColor.set(titleFontColor); 346 } 347 WindowStyle(WindowStyle style)348 public WindowStyle (WindowStyle style) { 349 this.background = style.background; 350 this.titleFont = style.titleFont; 351 this.titleFontColor = new Color(style.titleFontColor); 352 } 353 } 354 } 355