/******************************************************************************* * 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.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.utils.Layout; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.SnapshotArray; /** A group that lays out its children top to bottom vertically, with optional wrapping. This can be easier than using * {@link Table} when actors need to be inserted into or removed from the middle of the group. * <p> * The preferred width is the largest preferred width of any child. The preferred height is the sum of the children's preferred * heights plus spacing. The preferred size is slightly different when {@link #wrap() wrap} is enabled. The min size is the * preferred size and the max size is 0. * <p> * Widgets are sized using their {@link Layout#getPrefWidth() preferred height}, so widgets which return 0 as their preferred * height will be given a height of 0. * @author Nathan Sweet */ public class VerticalGroup extends WidgetGroup { private float prefWidth, prefHeight, lastPrefWidth; private boolean sizeInvalid = true; private FloatArray columnSizes; // column height, column width, ... private int align = Align.top, columnAlign; private boolean reverse, round = true, wrap, expand; private float space, wrapSpace, fill, padTop, padLeft, padBottom, padRight; public VerticalGroup () { setTouchable(Touchable.childrenOnly); } public void invalidate () { super.invalidate(); sizeInvalid = true; } private void computeSize () { sizeInvalid = false; SnapshotArray<Actor> children = getChildren(); int n = children.size; prefWidth = 0; if (wrap) { prefHeight = 0; if (columnSizes == null) columnSizes = new FloatArray(); else columnSizes.clear(); FloatArray columnSizes = this.columnSizes; float space = this.space, wrapSpace = this.wrapSpace; float pad = padTop + padBottom, groupHeight = getHeight() - pad, x = 0, y = 0, columnWidth = 0; int i = 0, incr = 1; if (reverse) { i = n - 1; n = -1; incr = -1; } for (; i != n; i += incr) { Actor child = children.get(i); float width, height; if (child instanceof Layout) { Layout layout = (Layout)child; width = layout.getPrefWidth(); height = layout.getPrefHeight(); } else { width = child.getWidth(); height = child.getHeight(); } float incrY = height + (y > 0 ? space : 0); if (y + incrY > groupHeight && y > 0) { columnSizes.add(y); columnSizes.add(columnWidth); prefHeight = Math.max(prefHeight, y + pad); if (x > 0) x += wrapSpace; x += columnWidth; columnWidth = 0; y = 0; incrY = height; } y += incrY; columnWidth = Math.max(columnWidth, width); } columnSizes.add(y); columnSizes.add(columnWidth); prefHeight = Math.max(prefHeight, y + pad); if (x > 0) x += wrapSpace; prefWidth = Math.max(prefWidth, x + columnWidth); } else { prefHeight = padTop + padBottom + space * (n - 1); for (int i = 0; i < n; i++) { Actor child = children.get(i); if (child instanceof Layout) { Layout layout = (Layout)child; prefWidth = Math.max(prefWidth, layout.getPrefWidth()); prefHeight += layout.getPrefHeight(); } else { prefWidth = Math.max(prefWidth, child.getWidth()); prefHeight += child.getHeight(); } } } prefWidth += padLeft + padRight; if (round) { prefWidth = Math.round(prefWidth); prefHeight = Math.round(prefHeight); } } public void layout () { if (sizeInvalid) computeSize(); if (wrap) { layoutWrapped(); return; } boolean round = this.round; int align = this.align; float space = this.space, padLeft = this.padLeft, fill = this.fill; float columnWidth = (expand ? getWidth() : prefWidth) - padLeft - padRight, y = prefHeight - padTop + space; if ((align & Align.top) != 0) y += getHeight() - prefHeight; else if ((align & Align.bottom) == 0) // center y += (getHeight() - prefHeight) / 2; float startX; if ((align & Align.left) != 0) startX = padLeft; else if ((align & Align.right) != 0) startX = getWidth() - padRight - columnWidth; else startX = padLeft + (getWidth() - padLeft - padRight - columnWidth) / 2; align = columnAlign; SnapshotArray<Actor> children = getChildren(); int i = 0, n = children.size, incr = 1; if (reverse) { i = n - 1; n = -1; incr = -1; } for (int r = 0; i != n; i += incr) { Actor child = children.get(i); float width, height; Layout layout = null; if (child instanceof Layout) { layout = (Layout)child; width = layout.getPrefWidth(); height = layout.getPrefHeight(); } else { width = child.getWidth(); height = child.getHeight(); } if (fill > 0) width = columnWidth * fill; if (layout != null) { width = Math.max(width, layout.getMinWidth()); float maxWidth = layout.getMaxWidth(); if (maxWidth > 0 && width > maxWidth) width = maxWidth; } float x = startX; if ((align & Align.right) != 0) x += columnWidth - width; else if ((align & Align.left) == 0) // center x += (columnWidth - width) / 2; y -= height + space; if (round) child.setBounds(Math.round(x), Math.round(y), Math.round(width), Math.round(height)); else child.setBounds(x, y, width, height); if (layout != null) layout.validate(); } } private void layoutWrapped () { float prefWidth = getPrefWidth(); if (prefWidth != lastPrefWidth) { lastPrefWidth = prefWidth; invalidateHierarchy(); } int align = this.align; boolean round = this.round; float space = this.space, padLeft = this.padLeft, fill = this.fill, wrapSpace = this.wrapSpace; float maxHeight = prefHeight - padTop - padBottom; float columnX = padLeft, groupHeight = getHeight(); float yStart = prefHeight - padTop + space, y = 0, columnWidth = 0; if ((align & Align.right) != 0) columnX += getWidth() - prefWidth; else if ((align & Align.left) == 0) // center columnX += (getWidth() - prefWidth) / 2; if ((align & Align.top) != 0) yStart += groupHeight - prefHeight; else if ((align & Align.bottom) == 0) // center yStart += (groupHeight - prefHeight) / 2; groupHeight -= padTop; align = columnAlign; FloatArray columnSizes = this.columnSizes; SnapshotArray<Actor> children = getChildren(); int i = 0, n = children.size, incr = 1; if (reverse) { i = n - 1; n = -1; incr = -1; } for (int r = 0; i != n; i += incr) { Actor child = children.get(i); float width, height; Layout layout = null; if (child instanceof Layout) { layout = (Layout)child; width = layout.getPrefWidth(); height = layout.getPrefHeight(); } else { width = child.getWidth(); height = child.getHeight(); } if (y - height - space < padBottom || r == 0) { y = yStart; if ((align & Align.bottom) != 0) y -= maxHeight - columnSizes.get(r); else if ((align & Align.top) == 0) // center y -= (maxHeight - columnSizes.get(r)) / 2; if (r > 0) { columnX += wrapSpace; columnX += columnWidth; } columnWidth = columnSizes.get(r + 1); r += 2; } if (fill > 0) width = columnWidth * fill; if (layout != null) { width = Math.max(width, layout.getMinWidth()); float maxWidth = layout.getMaxWidth(); if (maxWidth > 0 && width > maxWidth) width = maxWidth; } float x = columnX; if ((align & Align.right) != 0) x += columnWidth - width; else if ((align & Align.left) == 0) // center x += (columnWidth - width) / 2; y -= height + space; if (round) child.setBounds(Math.round(x), Math.round(y), Math.round(width), Math.round(height)); else child.setBounds(x, y, width, height); if (layout != null) layout.validate(); } } public float getPrefWidth () { if (sizeInvalid) computeSize(); return prefWidth; } public float getPrefHeight () { if (wrap) return 0; if (sizeInvalid) computeSize(); return prefHeight; } /** If true (the default), positions and sizes are rounded to integers. */ public void setRound (boolean round) { this.round = round; } /** The children will be displayed last to first. */ public VerticalGroup reverse () { this.reverse = true; return this; } /** If true, the children will be displayed last to first. */ public VerticalGroup reverse (boolean reverse) { this.reverse = reverse; return this; } public boolean getReverse () { return reverse; } /** Sets the vertical space between children. */ public VerticalGroup space (float space) { this.space = space; return this; } public float getSpace () { return space; } /** Sets the horizontal space between columns when wrap is enabled. */ public VerticalGroup wrapSpace (float wrapSpace) { this.wrapSpace = wrapSpace; return this; } public float getWrapSpace () { return wrapSpace; } /** Sets the padTop, padLeft, padBottom, and padRight to the specified value. */ public VerticalGroup pad (float pad) { padTop = pad; padLeft = pad; padBottom = pad; padRight = pad; return this; } public VerticalGroup pad (float top, float left, float bottom, float right) { padTop = top; padLeft = left; padBottom = bottom; padRight = right; return this; } public VerticalGroup padTop (float padTop) { this.padTop = padTop; return this; } public VerticalGroup padLeft (float padLeft) { this.padLeft = padLeft; return this; } public VerticalGroup padBottom (float padBottom) { this.padBottom = padBottom; return this; } public VerticalGroup padRight (float padRight) { this.padRight = padRight; return this; } public float getPadTop () { return padTop; } public float getPadLeft () { return padLeft; } public float getPadBottom () { return padBottom; } public float getPadRight () { return padRight; } /** Sets the alignment of all widgets within the vertical group. Set to {@link Align#center}, {@link Align#top}, * {@link Align#bottom}, {@link Align#left}, {@link Align#right}, or any combination of those. */ public VerticalGroup align (int align) { this.align = align; return this; } /** Sets the alignment of all widgets within the vertical group to {@link Align#center}. This clears any other alignment. */ public VerticalGroup center () { align = Align.center; return this; } /** Sets {@link Align#top} and clears {@link Align#bottom} for the alignment of all widgets within the vertical group. */ public VerticalGroup top () { align |= Align.top; align &= ~Align.bottom; return this; } /** Adds {@link Align#left} and clears {@link Align#right} for the alignment of all widgets within the vertical group. */ public VerticalGroup left () { align |= Align.left; align &= ~Align.right; return this; } /** Sets {@link Align#bottom} and clears {@link Align#top} for the alignment of all widgets within the vertical group. */ public VerticalGroup bottom () { align |= Align.bottom; align &= ~Align.top; return this; } /** Adds {@link Align#right} and clears {@link Align#left} for the alignment of all widgets within the vertical group. */ public VerticalGroup right () { align |= Align.right; align &= ~Align.left; return this; } public int getAlign () { return align; } public VerticalGroup fill () { fill = 1f; return this; } /** @param fill 0 will use preferred height. */ public VerticalGroup fill (float fill) { this.fill = fill; return this; } public float getFill () { return fill; } public VerticalGroup expand () { expand = true; return this; } /** When true and wrap is false, the columns will take up the entire vertical group width. */ public VerticalGroup expand (boolean expand) { this.expand = expand; return this; } public boolean getExpand () { return expand; } /** Sets fill to 1 and expand to true. */ public VerticalGroup grow () { expand = true; fill = 1; return this; } /** If false, the widgets are arranged in a single column and the preferred height is the widget heights plus spacing. If true, * the widgets will wrap using the height of the vertical group. The preferred height of the group will be 0 as it is expected * that something external will set the height of the group. Default is false. * <p> * When wrap is enabled, the group's preferred width depends on the height of the group. In some cases the parent of the group * will need to layout twice: once to set the height of the group and a second time to adjust to the group's new preferred * width. */ public VerticalGroup wrap () { wrap = true; return this; } public VerticalGroup wrap (boolean wrap) { this.wrap = wrap; return this; } public boolean getWrap () { return wrap; } /** Sets the alignment of widgets within each column of the vertical group. Set to {@link Align#center}, {@link Align#left}, or * {@link Align#right}. */ public VerticalGroup columnAlign (int columnAlign) { this.columnAlign = columnAlign; return this; } /** Sets the alignment of widgets within each column to {@link Align#center}. This clears any other alignment. */ public VerticalGroup columnCenter () { columnAlign = Align.center; return this; } /** Adds {@link Align#left} and clears {@link Align#right} for the alignment of widgets within each column. */ public VerticalGroup columnLeft () { columnAlign |= Align.left; columnAlign &= ~Align.right; return this; } /** Adds {@link Align#right} and clears {@link Align#left} for the alignment of widgets within each column. */ public VerticalGroup columnRight () { columnAlign |= Align.right; columnAlign &= ~Align.left; return this; } protected void drawDebugBounds (ShapeRenderer shapes) { super.drawDebugBounds(shapes); if (!getDebug()) return; shapes.set(ShapeType.Line); shapes.setColor(getStage().getDebugColor()); shapes.rect(getX() + padLeft, getY() + padBottom, getOriginX(), getOriginY(), getWidth() - padLeft - padRight, getHeight() - padBottom - padTop, getScaleX(), getScaleY(), getRotation()); } }