All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.esotericsoftware.tablelayout.BaseTableLayout Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2011, Nathan Sweet 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the  nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL  BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/

package com.esotericsoftware.tablelayout;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

// BOZO - Support inserting cells/rows.

/** Base layout functionality.
 * @author Nathan Sweet */
abstract public class BaseTableLayout> {
	static public final int CENTER = 1 << 0;
	static public final int TOP = 1 << 1;
	static public final int BOTTOM = 1 << 2;
	static public final int LEFT = 1 << 3;
	static public final int RIGHT = 1 << 4;

	/** Scales the source to fit the target while keeping the same aspect ratio. This may cause the source to be smaller than the
	 * target in one dimension. */
	static public final int SCALE_FIT = 1 << 1;
	/** Scales the source to completely fill the target while keeping the same aspect ratio. This may cause the source to be larger
	 * than the target in one dimension. */
	static public final int SCALE_FILL = 1 << 2;
	/** Scales the source to completely fill the target. This may cause the source to not keep the same aspect ratio. */
	static public final int SCALE_STRETCH = 1 << 3;

	static public final String MIN = "min";
	static public final String PREF = "pref";
	static public final String MAX = "max";

	static public final int DEBUG_NONE = 0;
	static public final int DEBUG_ALL = 1 << 0;
	static public final int DEBUG_TABLE = 1 << 1;
	static public final int DEBUG_CELL = 1 << 2;
	static public final int DEBUG_WIDGET = 1 << 3;

	K toolkit;
	T table;
	HashMap nameToWidget = new HashMap(8);
	HashMap widgetToCell = new HashMap(8);
	private int columns, rows;

	private final ArrayList cells = new ArrayList(4);
	private final Cell cellDefaults = Cell.defaults(this);
	private final ArrayList columnDefaults = new ArrayList(2);
	private Cell rowDefaults;

	private int layoutX, layoutY;
	private int layoutWidth, layoutHeight;

	private boolean sizeInvalid = true;
	private int[] columnMinWidth, rowMinHeight;
	private int[] columnPrefWidth, rowPrefHeight;
	private int tableMinWidth, tableMinHeight;
	private int tablePrefWidth, tablePrefHeight;
	private int[] columnWidth, rowHeight;
	private float[] expandWidth, expandHeight;
	private int[] columnWeightedWidth, rowWeightedHeight;

	String width, height;
	String padTop, padLeft, padBottom, padRight;
	int align = CENTER;
	int debug = DEBUG_NONE;

	public BaseTableLayout (K toolkit) {
		this.toolkit = toolkit;
	}

	public void invalidate () {
		sizeInvalid = true;
	}

	abstract public void invalidateHierarchy ();

	/** The position within it's parent and size of the widget that will be laid out. Must be set before layout. */
	public void setLayoutSize (int tableLayoutX, int tableLayoutY, int tableLayoutWidth, int tableLayoutHeight) {
		this.layoutX = tableLayoutX;
		this.layoutY = tableLayoutY;
		this.layoutWidth = tableLayoutWidth;
		this.layoutHeight = tableLayoutHeight;
	}

	/** Sets the name of a widget so it may be referenced in {@link #parse(String)}. */
	public C register (String name, C widget) {
		if (name == null) throw new IllegalArgumentException("name cannot be null.");
		name = name.toLowerCase().trim();
		if (nameToWidget.containsKey(name)) throw new IllegalArgumentException("Name is already used: " + name);
		nameToWidget.put(name, widget);
		return widget;
	}

	/** Parses a table description and adds the widgets and cells to the table. */
	public void parse (String tableDescription) {
		TableLayoutParser.parse(this, tableDescription);
	}

	/** Adds a new cell to the table with the specified widget.
	 * @param widget May be null to add a cell without a widget. */
	public Cell add (C widget) { // BOZO - Add column description parsing.
		widget = toolkit.wrap((L)this, widget);

		Cell cell = new Cell(this);
		cell.widget = widget;

		widgetToCell.put(widget, cell);

		for (Entry entry : nameToWidget.entrySet()) {
			if (widget == entry.getValue()) {
				cell.name = entry.getKey();
				break;
			}
		}

		if (cells.size() > 0) {
			// Set cell x and y.
			Cell lastCell = cells.get(cells.size() - 1);
			if (!lastCell.endRow) {
				cell.column = lastCell.column + lastCell.colspan;
				cell.row = lastCell.row;
			} else
				cell.row = lastCell.row + 1;
			// Set the index of the cell above.
			if (cell.row > 0) {
				outer:
				for (int i = cells.size() - 1; i >= 0; i--) {
					Cell other = cells.get(i);
					for (int column = other.column, nn = column + other.colspan; column < nn; column++) {
						if (other.column == cell.column) {
							cell.cellAboveIndex = i;
							break outer;
						}
					}
				}
			}
		}
		cells.add(cell);

		if (cell.column < columnDefaults.size()) {
			Cell columnDefaults = this.columnDefaults.get(cell.column);
			cell.set(columnDefaults != null ? columnDefaults : cellDefaults);
		} else
			cell.set(cellDefaults);
		cell.merge(rowDefaults);

		toolkit.addChild(table, widget, null);

		return cell;
	}

	public Cell stack (C... widgets) { // BOZO - Add column description parsing.
		C stack = toolkit.newStack();
		for (int i = 0, n = widgets.length; i < n; i++)
			toolkit.addChild(stack, widgets[i], null);
		return add(stack);
	}

	/** Indicates that subsequent cells should be added to a new row and returns the cell values that will be used as the defaults
	 * for all cells in the new row. */
	public Cell row () {
		if (cells.size() > 0) endRow();
		rowDefaults = new Cell(this);
		return rowDefaults;
	}

	private void endRow () {
		int rowColumns = 0;
		for (int i = cells.size() - 1; i >= 0; i--) {
			Cell cell = cells.get(i);
			if (cell.endRow) break;
			rowColumns += cell.colspan;
		}
		columns = Math.max(columns, rowColumns);
		rows++;
		cells.get(cells.size() - 1).endRow = true;
		invalidate();
	}

	/** Gets the cell values that will be used as the defaults for all cells in the specified column. */
	public Cell columnDefaults (int column) {
		Cell cell = columnDefaults.size() > column ? columnDefaults.get(column) : null;
		if (cell == null) {
			cell = new Cell(this);
			cell.set(cellDefaults);
			if (column >= columnDefaults.size()) {
				for (int i = columnDefaults.size(); i < column; i++)
					columnDefaults.add(null);
				columnDefaults.add(cell);
			} else
				columnDefaults.set(column, cell);
		}
		return cell;
	}

	/** Removes all widgets and cells from the table (same as {@link #clear()}) and additionally resets all table properties and
	 * cell, column, and row defaults. */
	public void reset () {
		clear();
		padTop = null;
		padLeft = null;
		padBottom = null;
		padRight = null;
		align = CENTER;
		if (debug != DEBUG_NONE) toolkit.clearDebugRectangles((L)this);
		debug = DEBUG_NONE;
		cellDefaults.set(Cell.defaults(this));
		columnDefaults.clear();
		rowDefaults = null;
	}

	/** Removes all widgets and cells from the table. */
	public void clear () {
		for (int i = cells.size() - 1; i >= 0; i--)
			toolkit.removeChild(table, (C)cells.get(i).widget);
		cells.clear();
		nameToWidget.clear();
		widgetToCell.clear();
		rows = 0;
		columns = 0;
		invalidate();
	}

	/** Returns the widget with the specified name, anywhere in the table hierarchy. */
	public C getWidget (String name) {
		return nameToWidget.get(name.toLowerCase());
	}

	/** Returns all named widgets, anywhere in the table hierarchy. */
	public List getWidgets () {
		return new ArrayList(nameToWidget.values());
	}

	/** Returns all widgets with the specified name prefix, anywhere in the table hierarchy. */
	public List getWidgets (String namePrefix) {
		ArrayList widgets = new ArrayList();
		for (Entry entry : nameToWidget.entrySet())
			if (entry.getKey().startsWith(namePrefix)) widgets.add(entry.getValue());
		return widgets;
	}

	/** Returns the cell for the specified widget, anywhere in the table hierarchy. */
	public Cell getCell (C widget) {
		return widgetToCell.get(widget);
	}

	/** Returns the cell with the specified name, anywhere in the table hierarchy. */
	public Cell getCell (String name) {
		return getCell(getWidget(name));
	}

	/** Returns all cells, anywhere in the table hierarchy. */
	public List getAllCells () {
		return new ArrayList(widgetToCell.values());
	}

	/** Returns all cells with the specified name prefix, anywhere in the table hierarchy. */
	public List getAllCells (String namePrefix) {
		ArrayList cells = new ArrayList();
		for (Cell cell : widgetToCell.values())
			if (cell.name.startsWith(namePrefix)) cells.add(cell);
		return cells;
	}

	/** Returns the cells for this table. */
	public List getCells () {
		return cells;
	}

	/** Sets the widget in the cell with the specified name. */
	public void setWidget (String name, C widget) {
		getCell(name).setWidget(widget);
	}

	/** Sets that this table is nested under the specified parent. This allows the root table to look up widgets and cells in nested
	 * tables, for convenience. */
	public void setParent (BaseTableLayout parent) {
		// Shared per table hierarchy.
		nameToWidget = parent.nameToWidget;
		widgetToCell = parent.widgetToCell;
	}

	public void setToolkit (K toolkit) {
		this.toolkit = toolkit;
	}

	/** Returns the widget that will be laid out. */
	public T getTable () {
		return table;
	}

	/** Sets the widget that will be laid out. */
	public void setTable (T table) {
		this.table = table;
	}

	/** The x position within it's parent of the widget that will be laid out. Set by {@link #setLayoutSize(int, int, int, int)}
	 * before layout. */
	public int getLayoutX () {
		return layoutX;
	}

	/** The y position within it's parent of the widget that will be laid out. Set by {@link #setLayoutSize(int, int, int, int)}
	 * before layout. */
	public int getLayoutY () {
		return layoutY;
	}

	/** The width of the widget that will be laid out. Set by {@link #setLayoutSize(int, int, int, int)} before layout. */
	public int getLayoutWidth () {
		return layoutWidth;
	}

	/** The height of the widget that will be laid out. Set by {@link #setLayoutSize(int, int, int, int)} before layout. */
	public int getLayoutHeight () {
		return layoutHeight;
	}

	/** The minimum width of the table. */
	public int getMinWidth () {
		if (sizeInvalid) computeSize();
		return tableMinWidth;
	}

	/** The minimum size of the table. */
	public int getMinHeight () {
		if (sizeInvalid) computeSize();
		return tableMinHeight;
	}

	/** The preferred width of the table. */
	public int getPrefWidth () {
		if (sizeInvalid) computeSize();
		return tablePrefWidth;
	}

	/** The preferred height of the table. */
	public int getPrefHeight () {
		if (sizeInvalid) computeSize();
		return tablePrefHeight;
	}

	/** The cell values that will be used as the defaults for all cells. */
	public Cell defaults () {
		return cellDefaults;
	}

	public K getToolkit () {
		return toolkit;
	}

	/** The fixed size of the table. */
	public L size (String width, String height) {
		this.width = width;
		this.height = height;
		sizeInvalid = true;
		return (L)this;
	}

	/** The fixed width of the table, or null. */
	public L width (String width) {
		this.width = width;
		sizeInvalid = true;
		return (L)this;
	}

	/** The fixed height of the table, or null. */
	public L height (String height) {
		this.height = height;
		sizeInvalid = true;
		return (L)this;
	}

	/** The fixed size of the table. */
	public L size (int width, int height) {
		this.width = String.valueOf(width);
		this.height = String.valueOf(height);
		sizeInvalid = true;
		return (L)this;
	}

	/** The fixed width of the table. */
	public L width (int width) {
		this.width = String.valueOf(width);
		sizeInvalid = true;
		return (L)this;
	}

	/** The fixed height of the table. */
	public L height (int height) {
		this.height = String.valueOf(height);
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding around the table. */
	public L pad (String pad) {
		padTop = pad;
		padLeft = pad;
		padBottom = pad;
		padRight = pad;
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding around the table. */
	public L pad (String top, String left, String bottom, String right) {
		padTop = top;
		padLeft = left;
		padBottom = bottom;
		padRight = right;
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the top of the table. */
	public L padTop (String padTop) {
		this.padTop = padTop;
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the left of the table. */
	public L padLeft (String padLeft) {
		this.padLeft = padLeft;
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the bottom of the table. */
	public L padBottom (String padBottom) {
		this.padBottom = padBottom;
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the right of the table. */
	public L padRight (String padRight) {
		this.padRight = padRight;
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding around the table. */
	public L pad (int pad) {
		padTop = String.valueOf(pad);
		padLeft = String.valueOf(pad);
		padBottom = String.valueOf(pad);
		padRight = String.valueOf(pad);
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding around the table. */
	public L pad (int top, int left, int bottom, int right) {
		padTop = String.valueOf(top);
		padLeft = String.valueOf(left);
		padBottom = String.valueOf(bottom);
		padRight = String.valueOf(right);
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the top of the table. */
	public L padTop (int padTop) {
		this.padTop = String.valueOf(padTop);
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the left of the table. */
	public L padLeft (int padLeft) {
		this.padLeft = String.valueOf(padLeft);
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the bottom of the table. */
	public L padBottom (int padBottom) {
		this.padBottom = String.valueOf(padBottom);
		sizeInvalid = true;
		return (L)this;
	}

	/** Padding at the right of the table. */
	public L padRight (int padRight) {
		this.padRight = String.valueOf(padRight);
		sizeInvalid = true;
		return (L)this;
	}

	/** Alignment of the table within the widget being laid out. Set to {@link #CENTER}, {@link #TOP}, {@link #BOTTOM},
	 * {@link #LEFT}, {@link #RIGHT}, or any combination of those. */
	public L align (int align) {
		this.align = align;
		return (L)this;
	}

	/** Alignment of the table within the widget being laid out. Set to "center", "top", "bottom", "left", "right", or a string
	 * containing any combination of those. */
	public L align (String value) {
		align = 0;
		if (value.contains("center")) align |= CENTER;
		if (value.contains("left")) align |= LEFT;
		if (value.contains("right")) align |= RIGHT;
		if (value.contains("top")) align |= TOP;
		if (value.contains("bottom")) align |= BOTTOM;
		return (L)this;
	}

	/** Sets the alignment of the table within the widget being laid out to {@link #CENTER}. */
	public L center () {
		align |= CENTER;
		return (L)this;
	}

	/** Sets the alignment of the table within the widget being laid out to {@link #TOP}. */
	public L top () {
		align |= TOP;
		align &= ~BOTTOM;
		return (L)this;
	}

	/** Sets the alignment of the table within the widget being laid out to {@link #LEFT}. */
	public L left () {
		align |= LEFT;
		align &= ~RIGHT;
		return (L)this;
	}

	/** Sets the alignment of the table within the widget being laid out to {@link #BOTTOM}. */
	public L bottom () {
		align |= BOTTOM;
		align &= ~TOP;
		return (L)this;
	}

	/** Sets the alignment of the table within the widget being laid out to {@link #RIGHT}. */
	public L right () {
		align |= RIGHT;
		align &= ~LEFT;
		return (L)this;
	}

	/** Turns on all debug lines. */
	public L debug () {
		this.debug = DEBUG_ALL;
		invalidate();
		return (L)this;
	}

	/** Turns on debug lines. Set to {@value #DEBUG_ALL}, {@value #DEBUG_TABLE}, {@value #DEBUG_CELL}, {@value #DEBUG_WIDGET}, or
	 * any combination of those. Set to {@value #DEBUG_NONE} to disable. */
	public L debug (int debug) {
		this.debug = debug;
		if (debug == DEBUG_NONE)
			toolkit.clearDebugRectangles((L)this);
		else
			invalidate();
		return (L)this;
	}

	/** Turns on debug lines. Set to "all", "table", "cell", "widget", or a string containing any combination of those. Set to null
	 * to disable. */
	public L debug (String value) {
		debug = 0;
		if (value == null) return (L)this;
		if (value.equalsIgnoreCase("true")) debug |= DEBUG_ALL;
		if (value.contains("all")) debug |= DEBUG_ALL;
		if (value.contains("cell")) debug |= DEBUG_CELL;
		if (value.contains("table")) debug |= DEBUG_TABLE;
		if (value.contains("widget")) debug |= DEBUG_WIDGET;
		if (debug == DEBUG_NONE)
			toolkit.clearDebugRectangles((L)this);
		else
			invalidate();
		return (L)this;
	}

	public int getDebug () {
		return debug;
	}

	public String getWidth () {
		return width;
	}

	public String getHeight () {
		return height;
	}

	public String getPadTop () {
		return padTop;
	}

	public String getPadLeft () {
		return padLeft;
	}

	public String getPadBottom () {
		return padBottom;
	}

	public String getPadRight () {
		return padRight;
	}

	public int getAlign () {
		return align;
	}

	/** Returns the row index for the y coordinate, or -1 if there are no cells. */
	public int getRow (float y) {
		int row = 0;
		y += toolkit.height((L)this, padTop);
		int i = 0, n = cells.size();
		if (n == 0) return -1;
		// Skip first row.
		while (i < n && !cells.get(i).isEndRow())
			i++;
		while (i < n) {
			Cell c = cells.get(i++);
			if (c.getIgnore()) continue;
			if (c.widgetY + c.computedPadTop > y) break;
			if (c.endRow) row++;
		}
		return rows - row;
	}

	private int[] ensureSize (int[] array, int size) {
		if (array == null || array.length < size) return new int[size];
		for (int i = 0, n = array.length; i < n; i++)
			array[i] = 0;
		return array;
	}

	private float[] ensureSize (float[] array, int size) {
		if (array == null || array.length < size) return new float[size];
		for (int i = 0, n = array.length; i < n; i++)
			array[i] = 0;
		return array;
	}

	private void computeSize () {
		sizeInvalid = false;

		Toolkit toolkit = this.toolkit;
		ArrayList cells = this.cells;

		if (cells.size() > 0 && !cells.get(cells.size() - 1).endRow) endRow();

		columnMinWidth = ensureSize(columnMinWidth, columns);
		rowMinHeight = ensureSize(rowMinHeight, rows);
		columnPrefWidth = ensureSize(columnPrefWidth, columns);
		rowPrefHeight = ensureSize(rowPrefHeight, rows);
		columnWidth = ensureSize(columnWidth, columns);
		rowHeight = ensureSize(rowHeight, rows);
		expandWidth = ensureSize(expandWidth, columns);
		expandHeight = ensureSize(expandHeight, rows);

		int spaceRightLast = 0;
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;

			// Collect columns/rows that expand.
			if (c.expandY != 0 && expandHeight[c.row] == 0) expandHeight[c.row] = c.expandY;
			if (c.colspan == 1 && c.expandX != 0 && expandWidth[c.column] == 0) expandWidth[c.column] = c.expandX;

			// Compute combined padding/spacing for cells.
			// Spacing between widgets isn't additive, the larger is used. Also, no spacing around edges.
			c.computedPadLeft = c.column == 0 ? toolkit.width(this, c.padLeft) : toolkit.width(this, c.padLeft)
				+ Math.max(0, toolkit.width(this, c.spaceLeft) - spaceRightLast);
			c.computedPadTop = c.cellAboveIndex == -1 ? toolkit.height(this, c.padTop) : toolkit.height(this, c.padTop)
				+ Math.max(0, toolkit.height(this, c.spaceTop) - toolkit.height(this, cells.get(c.cellAboveIndex).spaceBottom));
			int spaceRight = toolkit.width(this, c.spaceRight);
			c.computedPadRight = c.column + c.colspan == columns ? toolkit.width(this, c.padRight) : toolkit.width(this, c.padRight)
				+ spaceRight;
			c.computedPadBottom = c.row == rows - 1 ? toolkit.height(this, c.padBottom) : toolkit.height(this, c.padBottom)
				+ toolkit.height(this, c.spaceBottom);
			spaceRightLast = spaceRight;

			// Determine minimum and preferred cell sizes.
			int prefWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.prefWidth);
			int prefHeight = toolkit.getWidgetHeight(this, (C)c.widget, c.prefHeight);
			int minWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.minWidth);
			int minHeight = toolkit.getWidgetHeight(this, (C)c.widget, c.minHeight);
			int maxWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.maxWidth);
			int maxHeight = toolkit.getWidgetHeight(this, (C)c.widget, c.maxHeight);
			if (prefWidth < minWidth) prefWidth = minWidth;
			if (prefHeight < minHeight) prefHeight = minHeight;
			if (maxWidth > 0 && prefWidth > maxWidth) prefWidth = maxWidth;
			if (maxHeight > 0 && prefHeight > maxHeight) prefHeight = maxHeight;

			if (c.colspan == 1) { // Spanned column min and pref width is added later.
				int hpadding = c.computedPadLeft + c.computedPadRight;
				columnPrefWidth[c.column] = Math.max(columnPrefWidth[c.column], prefWidth + hpadding);
				columnMinWidth[c.column] = Math.max(columnMinWidth[c.column], minWidth + hpadding);
			}
			int vpadding = c.computedPadTop + c.computedPadBottom;
			rowPrefHeight[c.row] = Math.max(rowPrefHeight[c.row], prefHeight + vpadding);
			rowMinHeight[c.row] = Math.max(rowMinHeight[c.row], minHeight + vpadding);
		}

		// Colspan with expand will expand all spanned columns if none of the spanned columns have expand.
		outer:
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;
			if (c.expandX == 0) continue;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++)
				if (expandWidth[column] != 0) continue outer;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++)
				expandWidth[column] = c.expandX;
		}

		// Distribute any additional min and pref width added by colspanned cells to the columns spanned.
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;
			if (c.colspan == 1) continue;

			int minWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.minWidth);
			int prefWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.prefWidth);
			int maxWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.maxWidth);
			if (prefWidth < minWidth) prefWidth = minWidth;
			if (maxWidth > 0 && prefWidth > maxWidth) prefWidth = maxWidth;

			int spannedMinWidth = 0, spannedPrefWidth = 0;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++) {
				spannedMinWidth += columnMinWidth[column];
				spannedPrefWidth += columnPrefWidth[column];
			}

			// Distribute extra space using expand, if any columns have expand.
			float totalExpandWidth = 0;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++)
				totalExpandWidth += expandWidth[column];

			int extraMinWidth = Math.max(0, minWidth - spannedMinWidth);
			int extraPrefWidth = Math.max(0, prefWidth - spannedPrefWidth);
			for (int column = c.column, nn = column + c.colspan; column < nn; column++) {
				float ratio = totalExpandWidth == 0 ? 1f / c.colspan : expandWidth[column] / totalExpandWidth;
				columnMinWidth[column] += extraMinWidth * ratio;
				columnPrefWidth[column] += extraPrefWidth * ratio;
			}
		}

		// Determine table min and pref size.
		tableMinWidth = 0;
		tableMinHeight = 0;
		tablePrefWidth = 0;
		tablePrefHeight = 0;
		for (int i = 0; i < columns; i++) {
			tableMinWidth += columnMinWidth[i];
			tablePrefWidth += columnPrefWidth[i];
		}
		for (int i = 0; i < rows; i++) {
			tableMinHeight += rowMinHeight[i];
			tablePrefHeight += Math.max(rowMinHeight[i], rowPrefHeight[i]);
		}
		int hpadding = toolkit.width(this, padLeft) + toolkit.width(this, padRight);
		int vpadding = toolkit.height(this, padTop) + toolkit.height(this, padBottom);
		int width = toolkit.width(this, this.width);
		int height = toolkit.height(this, this.height);
		tableMinWidth = Math.max(tableMinWidth + hpadding, width);
		tableMinHeight = Math.max(tableMinHeight + vpadding, height);
		tablePrefWidth = Math.max(tablePrefWidth + hpadding, tableMinWidth);
		tablePrefHeight = Math.max(tablePrefHeight + vpadding, tableMinHeight);
	}

	/** Positions and sizes children of the widget being laid out using the cell associated with each child. */
	public void layout () {
		Toolkit toolkit = this.toolkit;
		ArrayList cells = this.cells;

		if (sizeInvalid) computeSize();

		int hpadding = toolkit.width(this, padLeft) + toolkit.width(this, padRight);
		int vpadding = toolkit.height(this, padTop) + toolkit.height(this, padBottom);

		// totalMinWidth/totalMinHeight are needed because tableMinWidth/tableMinHeight could be based on this.width or this.height.
		int totalMinWidth = 0, totalMinHeight = 0;
		float totalExpandWidth = 0, totalExpandHeight = 0;
		for (int i = 0; i < columns; i++) {
			totalMinWidth += columnMinWidth[i];
			totalExpandWidth += expandWidth[i];
		}
		for (int i = 0; i < rows; i++) {
			totalMinHeight += rowMinHeight[i];
			totalExpandHeight += expandHeight[i];
		}

		// Size columns and rows between min and pref size using (preferred - min) size to weight distribution of extra space.
		int[] columnWeightedWidth;
		int totalGrowWidth = tablePrefWidth - totalMinWidth;
		if (totalGrowWidth == 0)
			columnWeightedWidth = columnMinWidth;
		else {
			int extraWidth = Math.min(totalGrowWidth, Math.max(0, layoutWidth - totalMinWidth));
			columnWeightedWidth = this.columnWeightedWidth = ensureSize(this.columnWeightedWidth, columns);
			for (int i = 0; i < columns; i++) {
				int growWidth = columnPrefWidth[i] - columnMinWidth[i];
				float growRatio = growWidth / (float)totalGrowWidth;
				columnWeightedWidth[i] = columnMinWidth[i] + (int)(extraWidth * growRatio);
			}
		}

		int[] rowWeightedHeight;
		int totalGrowHeight = tablePrefHeight - totalMinHeight;
		if (totalGrowHeight == 0)
			rowWeightedHeight = rowMinHeight;
		else {
			rowWeightedHeight = this.rowWeightedHeight = ensureSize(this.rowWeightedHeight, rows);
			int extraHeight = Math.min(totalGrowHeight, Math.max(0, layoutHeight - totalMinHeight));
			for (int i = 0; i < rows; i++) {
				int growHeight = rowPrefHeight[i] - rowMinHeight[i];
				float growRatio = growHeight / (float)totalGrowHeight;
				rowWeightedHeight[i] = rowMinHeight[i] + (int)(extraHeight * growRatio);
			}
		}

		// Determine widget and cell sizes (before uniform/expand/fill).
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;

			int spannedWeightedWidth = 0;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++)
				spannedWeightedWidth += columnWeightedWidth[column];
			int weightedHeight = rowWeightedHeight[c.row];

			int prefWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.prefWidth);
			int prefHeight = toolkit.getWidgetHeight(this, (C)c.widget, c.prefHeight);
			int minWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.minWidth);
			int minHeight = toolkit.getWidgetHeight(this, (C)c.widget, c.minHeight);
			int maxWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.maxWidth);
			int maxHeight = toolkit.getWidgetHeight(this, (C)c.widget, c.maxHeight);
			if (prefWidth < minWidth) prefWidth = minWidth;
			if (prefHeight < minHeight) prefHeight = minHeight;
			if (maxWidth > 0 && prefWidth > maxWidth) prefWidth = maxWidth;
			if (maxHeight > 0 && prefHeight > maxHeight) prefHeight = maxHeight;

			c.widgetWidth = Math.min(spannedWeightedWidth - c.computedPadLeft - c.computedPadRight, prefWidth);
			c.widgetHeight = Math.min(weightedHeight - c.computedPadTop - c.computedPadBottom, prefHeight);

			if (c.colspan == 1) columnWidth[c.column] = Math.max(columnWidth[c.column], spannedWeightedWidth);
			rowHeight[c.row] = Math.max(rowHeight[c.row], weightedHeight);
		}

		// Uniform cells are all the same width/height.
		int uniformMaxWidth = 0, uniformMaxHeight = 0;
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;
			if (c.uniformX != null)
				uniformMaxWidth = Math.max(uniformMaxWidth, columnWidth[c.column] - c.computedPadLeft - c.computedPadRight);
			if (c.uniformY != null)
				uniformMaxHeight = Math.max(uniformMaxHeight, rowHeight[c.row] - c.computedPadTop - c.computedPadBottom);
		}
		if (uniformMaxWidth > 0 || uniformMaxHeight > 0) {
			outer:
			for (int i = 0, n = cells.size(); i < n; i++) {
				Cell c = cells.get(i);
				if (c.ignore) continue;
				if (uniformMaxWidth > 0 && c.uniformX != null) {
					int tempPadding = c.computedPadLeft + c.computedPadRight;
					int diff = uniformMaxWidth - (columnWidth[c.column] - tempPadding);
					if (diff > 0) {
						columnWidth[c.column] = uniformMaxWidth + tempPadding;
						tableMinWidth += diff;
						tablePrefWidth += diff;
					}
				}
				if (uniformMaxHeight > 0 && c.uniformY != null) {
					int tempPadding = c.computedPadTop + c.computedPadBottom;
					int diff = uniformMaxHeight - (rowHeight[c.row] - tempPadding);
					if (diff > 0) {
						rowHeight[c.row] = uniformMaxHeight + tempPadding;
						tableMinHeight += diff;
						tablePrefHeight += diff;
					}
				}
				continue outer;
			}
		}

		// Distribute remaining space to any expanding columns/rows.
		if (totalExpandWidth > 0) {
			int extra = layoutWidth - hpadding;
			for (int i = 0; i < columns; i++)
				extra -= columnWidth[i];
			int used = 0, lastIndex = 0;
			for (int i = 0; i < columns; i++) {
				if (expandWidth[i] == 0) continue;
				int amount = (int)(extra * expandWidth[i] / totalExpandWidth);
				columnWidth[i] += amount;
				used += amount;
				lastIndex = i;
			}
			columnWidth[lastIndex] += extra - used;
		}
		if (totalExpandHeight > 0) {
			int extra = layoutHeight - vpadding;
			for (int i = 0; i < rows; i++)
				extra -= rowHeight[i];
			int used = 0, lastIndex = 0;
			for (int i = 0; i < rows; i++) {
				if (expandHeight[i] == 0) continue;
				int amount = (int)(extra * expandHeight[i] / totalExpandHeight);
				rowHeight[i] += amount;
				used += amount;
				lastIndex = i;
			}
			rowHeight[lastIndex] += extra - used;
		}

		// Distribute any additional width added by colspanned cells to the columns spanned.
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;
			if (c.colspan == 1) continue;

			int extraWidth = 0;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++)
				extraWidth += columnWeightedWidth[column] - columnWidth[column];
			extraWidth -= c.computedPadLeft + c.computedPadRight;

			extraWidth /= c.colspan;
			if (extraWidth > 0) {
				for (int column = c.column, nn = column + c.colspan; column < nn; column++)
					columnWidth[column] += extraWidth;
			}
		}

		// Determine table size.
		int tableWidth = 0, tableHeight = 0;
		for (int i = 0; i < columns; i++)
			tableWidth += columnWidth[i];
		int width = toolkit.width(this, this.width);
		tableWidth = Math.max(tableWidth + hpadding, width);

		for (int i = 0; i < rows; i++)
			tableHeight += rowHeight[i];
		int height = toolkit.height(this, this.height);
		tableHeight = Math.max(tableHeight + vpadding, height);

		// Position table within the container.
		int x = layoutX + toolkit.width(this, padLeft);
		if ((align & RIGHT) != 0)
			x += layoutWidth - tableWidth;
		else if ((align & LEFT) == 0) // Center
			x += (layoutWidth - tableWidth) / 2;

		int y = layoutY + toolkit.height(this, padTop);
		if ((align & BOTTOM) != 0)
			y += layoutHeight - tableHeight;
		else if ((align & TOP) == 0) // Center
			y += (layoutHeight - tableHeight) / 2;

		// Position widgets within cells.
		int currentX = x;
		int currentY = y;
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;

			int spannedCellWidth = 0;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++)
				spannedCellWidth += columnWidth[column];
			spannedCellWidth -= c.computedPadLeft + c.computedPadRight;

			currentX += c.computedPadLeft;

			if (c.fillX > 0) {
				c.widgetWidth = (int)(spannedCellWidth * c.fillX);
				int maxWidth = toolkit.getWidgetWidth(this, (C)c.widget, c.maxWidth);
				if (maxWidth > 0) c.widgetWidth = Math.min(c.widgetWidth, maxWidth);
			}
			if (c.fillY > 0) {
				c.widgetHeight = (int)(rowHeight[c.row] * c.fillY) - c.computedPadTop - c.computedPadBottom;
				int maxHeight = toolkit.getWidgetHeight(this, (C)c.widget, c.maxHeight);
				if (maxHeight > 0) c.widgetHeight = Math.min(c.widgetHeight, maxHeight);
			}

			if (c.scaling != SCALE_STRETCH) {
				float sourceWidth = toolkit.getWidgetWidth(this, (C)c.widget, PREF);
				float sourceHeight = toolkit.getWidgetHeight(this, (C)c.widget, PREF);
				switch (c.scaling) {
				case SCALE_FIT: {
					float scale = c.widgetHeight / (float)c.widgetWidth > sourceHeight / sourceWidth ? c.widgetWidth / sourceWidth
						: c.widgetHeight / sourceHeight;
					c.widgetWidth = (int)(sourceWidth * scale);
					c.widgetHeight = (int)(sourceHeight * scale);
					break;
				}
				case SCALE_FILL: {
					float scale = c.widgetHeight / (float)c.widgetWidth < sourceHeight / sourceWidth ? c.widgetWidth / sourceWidth
						: c.widgetHeight / sourceHeight;
					c.widgetWidth = (int)(sourceWidth * scale);
					c.widgetHeight = (int)(sourceHeight * scale);
					break;
				}
				}
			}

			if ((c.align & LEFT) != 0)
				c.widgetX = currentX;
			else if ((c.align & RIGHT) != 0)
				c.widgetX = currentX + spannedCellWidth - c.widgetWidth;
			else
				c.widgetX = currentX + (spannedCellWidth - c.widgetWidth) / 2;

			if ((c.align & TOP) != 0)
				c.widgetY = currentY + c.computedPadTop;
			else if ((c.align & BOTTOM) != 0)
				c.widgetY = currentY + rowHeight[c.row] - c.widgetHeight - c.computedPadBottom;
			else
				c.widgetY = currentY + (rowHeight[c.row] - c.widgetHeight + c.computedPadTop - c.computedPadBottom) / 2;

			if (c.endRow) {
				currentX = x;
				currentY += rowHeight[c.row];
			} else
				currentX += spannedCellWidth + c.computedPadRight;
		}

		// Draw debug widgets and bounds.
		if (debug == DEBUG_NONE) return;
		toolkit.clearDebugRectangles(this);
		currentX = x;
		currentY = y;
		if ((debug & DEBUG_TABLE) != 0 || (debug & DEBUG_ALL) != 0) {
			toolkit.addDebugRectangle(this, DEBUG_TABLE, layoutX, layoutY, layoutWidth, layoutHeight);
			toolkit.addDebugRectangle(this, DEBUG_TABLE, x, y, tableWidth - hpadding, tableHeight - vpadding);
		}
		for (int i = 0, n = cells.size(); i < n; i++) {
			Cell c = cells.get(i);
			if (c.ignore) continue;

			// Widget bounds.
			if ((debug & DEBUG_WIDGET) != 0 || (debug & DEBUG_ALL) != 0)
				toolkit.addDebugRectangle(this, DEBUG_WIDGET, c.widgetX, c.widgetY, c.widgetWidth, c.widgetHeight);

			// Cell bounds.
			int spannedCellWidth = 0;
			for (int column = c.column, nn = column + c.colspan; column < nn; column++)
				spannedCellWidth += columnWidth[column];
			spannedCellWidth -= c.computedPadLeft + c.computedPadRight;
			currentX += c.computedPadLeft;
			if ((debug & DEBUG_CELL) != 0 || (debug & DEBUG_ALL) != 0) {
				toolkit.addDebugRectangle(this, DEBUG_CELL, currentX, currentY + c.computedPadTop, spannedCellWidth, rowHeight[c.row]
					- c.computedPadTop - c.computedPadBottom);
			}

			if (c.endRow) {
				currentX = x;
				currentY += rowHeight[c.row];
			} else
				currentX += spannedCellWidth + c.computedPadRight;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy