/** * Copyright 2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. 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. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community OR * CONTRIBUTORS 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. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.graph.ui.layout; import java.util.List; import com.jogamp.graph.ui.GraphShape; import com.jogamp.graph.ui.Group; import com.jogamp.graph.ui.Shape; import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.opengl.util.PMVMatrix; /** * GraphUI Grid {@link Group.Layout}. */ public class GridLayout implements Group.Layout { /** Layout order for {@link Group#getShapes()}} after population. */ public static enum Order { /** COLUMN layout order of {@link Group#getShapes()}} is left to right and top to bottom. */ COLUMN, /** ROW layout order of {@link Group#getShapes()}} is top to bottom and left to right. */ ROW } private final Order order; private final int col_limit; private final int row_limit; private final float cellWidth, cellHeight; private final Padding padding; private int row_count, col_count; /** * Default layout order of {@link Group#getShapes()}} is {@link Order#COLUMN}. * @param column_limit [1..inf) * @param cellWidth * @param cellHeight */ public GridLayout(final int column_limit, final float cellWidth, final float cellHeight) { this(Math.max(1, column_limit), -1, cellWidth, cellHeight, new Padding()); } /** * Default layout order of {@link Group#getShapes()}} is {@link Order#COLUMN}. * @param column_limit [1..inf) * @param cellWidth * @param cellHeight * @param padding */ public GridLayout(final int column_limit, final float cellWidth, final float cellHeight, final Padding padding) { this(Math.max(1, column_limit), -1, cellWidth, cellHeight, padding); } /** * Default layout order of {@link Group#getShapes()}} is {@link Order#ROW}. * @param cellWidth * @param cellHeight * @param padding * @param row_limit [1..inf) */ public GridLayout(final float cellWidth, final float cellHeight, final Padding padding, final int row_limit) { this(-1, Math.max(1, row_limit), cellWidth, cellHeight, padding); } private GridLayout(final int column_limit, final int row_limit, final float cellWidth, final float cellHeight, final Padding padding) { this.order = 0 < column_limit ? Order.COLUMN : Order.ROW; this.col_limit = column_limit; this.row_limit = row_limit; this.cellWidth = cellWidth; this.cellHeight = cellHeight; this.padding = padding;; row_count = 0; col_count = 0; } public Order getOrder() { return order; } public int getColumnCount() { return col_count; } public int getRowCount() { return row_count; } @Override public void layout(final Group g, final AABBox box, final PMVMatrix pmv) { final List shapes = g.getShapes(); if( Order.COLUMN == order ) { row_count = (int) Math.ceil( (double)shapes.size() / (double)col_limit ); col_count = col_limit; } else { // Order.ROW_MAJOR == order row_count = row_limit; col_count = (int) Math.ceil( (double)shapes.size() / (double)row_limit ); } int col_i = 0, row_i = 0; final AABBox sbox = new AABBox(); for(final Shape s : shapes) { // measure size pmv.glPushMatrix(); s.setTransform(pmv); s.getBounds().transformMv(pmv, sbox); pmv.glPopMatrix(); // adjust size and position (centered) final float x = ( ( col_i ) * ( cellWidth + padding.width() ) ) + padding.left; final float y = ( ( row_count - row_i - 1 ) * ( cellHeight + padding.height() ) ) + padding.bottom; final float sx = cellWidth / sbox.getWidth(); final float sy = cellHeight/ sbox.getHeight(); final float sxy = sx < sy ? sx : sy; final float dxh = sbox.getWidth() * ( sx - sxy ) * 0.5f; final float dyh = sbox.getHeight() * ( sy - sxy ) * 0.5f; s.moveTo( x + dxh, y + dyh, 0f ); // center the scaled artifact s.move( sbox.getLow().mul(-1f*sxy) ); // remove the bottom-left delta s.scale( sxy, sxy, 1f); box.resize( x + cellWidth + padding.right, y + cellHeight + padding.top, 0); box.resize( x - padding.left, y - padding.bottom, 0); // System.err.println("["+row_i+"]["+col_i+"]: "+x+" / "+y+", sxy "+sxy+", d[xy]h "+dxh+" x "+dyh+", "+sbox); // position for next cell if( Order.COLUMN == order ) { if( col_i + 1 == col_limit ) { col_i = 0; row_i++; } else { col_i++; } } else { // Order.ROW_MAJOR == order if( row_i + 1 == row_limit ) { row_i = 0; col_i++; } else { row_i++; } } } } @Override public String toString() { return "Grid["+row_count+"x"+col_count+", "+order+", cell["+cellWidth+" x "+cellHeight+"], "+padding+"]"; } }