/* * Copyright 2014 MovingBlocks * * 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 org.terasology.rendering.nui.layouts; import com.google.common.collect.Lists; import com.google.gson.annotations.SerializedName; import org.terasology.input.events.MouseButtonEvent; import org.terasology.input.events.MouseWheelEvent; import org.terasology.math.geom.Rect2i; import org.terasology.math.TeraMath; import org.terasology.math.geom.Vector2i; import org.terasology.rendering.nui.Canvas; import org.terasology.rendering.nui.CoreLayout; import org.terasology.rendering.nui.LayoutConfig; import org.terasology.rendering.nui.LayoutHint; import org.terasology.rendering.nui.UIWidget; import org.terasology.rendering.nui.events.NUIKeyEvent; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; /** */ public class MultiRowLayout extends CoreLayout<LayoutHint> { @LayoutConfig private int rows = 1; @LayoutConfig private int verticalSpacing; @LayoutConfig private int horizontalSpacing; @LayoutConfig private boolean autoSizeRows; private List<UIWidget> widgetList = Lists.newArrayList(); @LayoutConfig @SerializedName("row-heights") private float[] rowHeights = new float[]{1.0f}; public MultiRowLayout() { } public MultiRowLayout(String id) { super(id); } public void addWidget(UIWidget widget) { widgetList.add(widget); } @Override public void removeWidget(UIWidget widget) { widgetList.remove(widget); } public int getRows() { return rows; } public void setRows(int rows) { this.rows = rows; rowHeights = new float[rows]; float equalHeight = 1.0f / rows; for (int i = 0; i < rowHeights.length; ++i) { rowHeights[i] = equalHeight; } } public void setRowHeights(float ... heights) { if (heights.length > rows) { throw new IllegalArgumentException("More heights than rows"); } float total = 0; int rowIndex = 0; while (rowIndex < heights.length) { total += heights[rowIndex]; rowHeights[rowIndex] = heights[rowIndex]; rowIndex++; } if (total > 1.0f) { throw new IllegalArgumentException("Total height exceeds 1.0"); } if (rowIndex < rowHeights.length) { float remainingHeight = 1.0f - total; float heightPerRow = remainingHeight / (rowHeights.length - rowIndex); while (rowIndex < rowHeights.length) { rowHeights[rowIndex++] = heightPerRow; } } } @Override public void onDraw(Canvas canvas) { if (!widgetList.isEmpty()) { Vector2i availableSize = canvas.size(); int numColumns = TeraMath.ceilToInt((float) widgetList.size() / rows); if (numColumns > 0) { availableSize.x -= horizontalSpacing * (numColumns - 1); } if (rows > 0) { availableSize.y -= verticalSpacing * (rows - 1); } List<List<UIWidget>> columns = Lists.newArrayList(getColumnIterator()); List<ColumnInfo> columnInfos = Lists.newArrayList(); columnInfos.addAll(columns.stream().map(column -> calculateColumnSize(column, canvas, availableSize)).collect(Collectors.toList())); int[] minHeights = new int[rows]; int minColumnHeight = 0; int columnOffsetY = 0; if (autoSizeRows) { for (ColumnInfo column : columnInfos) { for (int row = 0; row < column.widgetSizes.size(); row++) { minHeights[row] = Math.max(minHeights[row], column.widgetSizes.get(row).getY()); } } for (int height : minHeights) { minColumnHeight += height; } minColumnHeight += (rows - 1) * verticalSpacing; columnOffsetY = (canvas.size().y - minColumnHeight) / 2; } else { minColumnHeight = canvas.size().y; for (int i = 0; i < rows; ++i) { minHeights[i] = TeraMath.floorToInt((minColumnHeight - (rows - 1) * verticalSpacing) * rowHeights[i]); } } int columnOffsetX = 0; int usedWidth = 0; for (ColumnInfo column : columnInfos) { usedWidth += column.width; } usedWidth += (columnInfos.size() - 1) * horizontalSpacing; columnOffsetX = (canvas.size().x - usedWidth) / 2; for (int columnIndex = 0; columnIndex < columns.size(); ++columnIndex) { List<UIWidget> column = columns.get(columnIndex); ColumnInfo columnInfo = columnInfos.get(columnIndex); int cellOffsetY = columnOffsetY; for (int i = 0; i < column.size(); ++i) { UIWidget widget = column.get(i); int columnWidth = columnInfo.width; if (widget != null) { Rect2i drawRegion = Rect2i.createFromMinAndSize(columnOffsetX, cellOffsetY, columnWidth, minHeights[i]); canvas.drawWidget(widget, drawRegion); } cellOffsetY += minHeights[i] + verticalSpacing; } columnOffsetX += columnInfo.width + horizontalSpacing; } } } private ColumnInfo calculateColumnSize(List<UIWidget> column, Canvas canvas, Vector2i areaHint) { int availableHeight = areaHint.y - verticalSpacing * (rows - 1); ColumnInfo columnInfo = new ColumnInfo(); for (int i = 0; i < rows && i < column.size(); ++i) { UIWidget widget = column.get(i); Vector2i cellSize = new Vector2i(areaHint.x, availableHeight); if (!autoSizeRows) { cellSize.y *= rowHeights[i]; } if (widget != null) { Vector2i contentSize = canvas.calculateRestrictedSize(widget, cellSize); columnInfo.widgetSizes.add(contentSize); columnInfo.width = Math.max(columnInfo.width, contentSize.x); } else { columnInfo.widgetSizes.add(new Vector2i(0, 0)); } } return columnInfo; } @Override public Vector2i getPreferredContentSize(Canvas canvas, Vector2i areaHint) { Vector2i availableSize = new Vector2i(areaHint); int numColumns = TeraMath.ceilToInt((float) widgetList.size() / rows); if (numColumns > 0) { availableSize.x -= horizontalSpacing * (numColumns - 1); } if (rows > 0) { availableSize.y -= verticalSpacing * (rows - 1); } Iterator<List<UIWidget>> columns = getColumnIterator(); Vector2i size = new Vector2i(); int[] rowSizes = new int[rows]; while (columns.hasNext()) { List<UIWidget> column = columns.next(); ColumnInfo columnInfo = calculateColumnSize(column, canvas, availableSize); size.x += columnInfo.width; if (columns.hasNext()) { size.x += horizontalSpacing; } for (int i = 0; i < columnInfo.widgetSizes.size(); ++i) { rowSizes[i] = Math.max(rowSizes[i], columnInfo.widgetSizes.get(i).getY()); } } for (int rowSize : rowSizes) { size.y += rowSize; } if (!autoSizeRows) { for (int i = 0; i < rows; ++i) { size.y = Math.max(size.y, TeraMath.floorToInt(rowSizes[i] / rowHeights[i])); } } size.y += verticalSpacing * (rows - 1); return size; } @Override public Vector2i getMaxContentSize(Canvas canvas) { Iterator<List<UIWidget>> columns = getColumnIterator(); Vector2i size = new Vector2i(); int[] rowSizes = new int[rows]; while (columns.hasNext()) { List<UIWidget> column = columns.next(); int columnWidth = 0; for (int i = 0; i < column.size(); ++i) { Vector2i maxSize = canvas.calculateMaximumSize(column.get(i)); rowSizes[i] = Math.max(rowSizes[i], maxSize.y); columnWidth = Math.max(columnWidth, maxSize.x); } size.x = TeraMath.addClampAtMax(size.x, columnWidth); if (columns.hasNext()) { size.x = TeraMath.addClampAtMax(size.x, horizontalSpacing); } } long height = 0; for (int rowSize : rowSizes) { height += rowSize; } if (!autoSizeRows) { for (int i = 0; i < rows; ++i) { height = Math.min(height, TeraMath.floorToInt(rowSizes[i] / rowHeights[i])); } } height += verticalSpacing * (rows - 1); size.y = (int) Math.min(Integer.MAX_VALUE, height); return size; } @Override public void update(float delta) { for (UIWidget widget : widgetList) { widget.update(delta); } } @Override public void onMouseButtonEvent(MouseButtonEvent event) { } @Override public void onMouseWheelEvent(MouseWheelEvent event) { } @Override public boolean onKeyEvent(NUIKeyEvent event) { return false; } @Override public Iterator<UIWidget> iterator() { return widgetList.iterator(); } @Override public void addWidget(UIWidget element, LayoutHint hint) { addWidget(element); } public int getHorizontalSpacing() { return horizontalSpacing; } public void setHorizontalSpacing(int horizontalSpacing) { this.horizontalSpacing = horizontalSpacing; } public int getVerticalSpacing() { return verticalSpacing; } public void setVerticalSpacing(int verticalSpacing) { this.verticalSpacing = verticalSpacing; } public boolean isAutoSizeRows() { return autoSizeRows; } public void setAutoSizeRows(boolean autoSizeRows) { this.autoSizeRows = autoSizeRows; } private Iterator<List<UIWidget>> getColumnIterator() { return new Iterator<List<UIWidget>>() { Iterator<UIWidget> contentIterator = iterator(); @Override public boolean hasNext() { return contentIterator.hasNext(); } @Override public List<UIWidget> next() { List<UIWidget> column = Lists.newArrayList(); for (int i = 0; i < rows; ++i) { if (contentIterator.hasNext()) { column.add(contentIterator.next()); } } return column; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } private static class ColumnInfo { private int width; private List<Vector2i> widgetSizes = Lists.newArrayList(); @Override public String toString() { return super.toString() + "{width:" + width + ", widgetSizes:" + widgetSizes + "}"; } } }