/** * 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.Group; import com.jogamp.graph.ui.Shape; import com.jogamp.math.FloatUtil; import com.jogamp.math.Vec2f; import com.jogamp.math.Vec3f; import com.jogamp.math.geom.AABBox; import com.jogamp.math.util.PMVMatrix4f; /** * GraphUI Stack {@link Group.Layout}. *

* A stack of {@link Shape}s *

*

*/ public class BoxLayout implements Group.Layout { private final Vec2f cellSize; private final Alignment alignment; /** Scaled {@link Margin} value is applied w/o additional scaling. */ private final Margin margin; /** Unscaled {@link Padding} value. */ private final Padding padding; private static final boolean TRACE_LAYOUT = false; /** */ public BoxLayout() { this(0f, 0f, Alignment.None, Margin.None, null); } /** * * @param padding unscaled {@link Padding} applied to each {@Shape} via {@link Shape#setPaddding(Padding)} and is scaled if {@link Alignment.Bit#Fill} */ public BoxLayout(final Padding padding) { this(0f, 0f, Alignment.None, Margin.None, padding); } /** * * @param cellWidth optional cell width, zero for none * @param cellHeight optional cell height, zero for none * @param alignment */ public BoxLayout(final float cellWidth, final float cellHeight, final Alignment alignment) { this(cellWidth, cellHeight, alignment, Margin.None, null); } /** * * @param cellWidth optional cell width, zero for none * @param cellHeight optional cell height, zero for none * @param margin scaled {@link Margin} is applied unscaled and ignored with only center {@link Alignment} w/o additional scaling */ public BoxLayout(final float cellWidth, final float cellHeight, final Margin margin) { this(cellWidth, cellHeight, Alignment.None, margin, null); } /** * * @param cellWidth optional cell width, zero for none * @param cellHeight optional cell height, zero for none * @param padding unscaled {@link Padding} applied to each {@Shape} via {@link Shape#setPaddding(Padding)} and is scaled if {@link Alignment.Bit#Fill} */ public BoxLayout(final float cellWidth, final float cellHeight, final Padding padding) { this(cellWidth, cellHeight, Alignment.None, Margin.None, padding); } /** * * @param cellWidth optional cell width, zero for none * @param cellHeight optional cell height, zero for none * @param margin scaled {@link Margin} is applied unscaled and ignored with only center {@link Alignment} w/o additional scaling * @param padding unscaled {@link Padding} applied to each {@Shape} via {@link Shape#setPaddding(Padding)} and is scaled if {@link Alignment.Bit#Fill} */ public BoxLayout(final float cellWidth, final float cellHeight, final Margin margin, final Padding padding) { this(cellWidth, cellHeight, Alignment.None, margin, padding); } /** * * @param cellWidth optional cell width, zero for none * @param cellHeight optional cell height, zero for none * @param margin scaled {@link Margin} is applied unscaled */ public BoxLayout(final float cellWidth, final float cellHeight, final Alignment alignment, final Margin margin) { this(cellWidth, cellHeight, alignment, margin, null); } /** * * @param cellWidth optional cell width, zero for none * @param cellHeight optional cell height, zero for none * @param alignment * @param padding unscaled {@link Padding} applied to each {@Shape} via {@link Shape#setPaddding(Padding)} and is scaled if {@link Alignment.Bit#Fill} */ public BoxLayout(final float cellWidth, final float cellHeight, final Alignment alignment, final Padding padding) { this(cellWidth, cellHeight, alignment, Margin.None, padding); } /** * * @param cellWidth optional cell width, zero for none * @param cellHeight optional cell height, zero for none * @param alignment * @param margin scaled {@link Margin} is applied unscaled and ignored with only center {@link Alignment} w/o additional scaling * @param padding unscaled {@link Padding} applied to each {@Shape} via {@link Shape#setPaddding(Padding)} and is scaled if {@link Alignment.Bit#Fill} */ public BoxLayout(final float cellWidth, final float cellHeight, final Alignment alignment, final Margin margin, final Padding padding) { this.cellSize = new Vec2f(Math.max(0f, cellWidth), Math.max(0f, cellHeight)); this.alignment = alignment; this.margin = margin; this.padding = padding; } // Vec2f totalSize /** Returns the preset cell size */ public Vec2f getCellSize() { return cellSize; } /** Returns given {@link Alignment}. */ public Alignment getAlignment() { return alignment; } /** Returns given scaled {@link Margin}. */ public Margin getMargin() { return margin; } /** Returns given unscaled {@link Padding}, may be {@code null} if not given via constructor. */ public Padding getPadding() { return padding; } @Override public void preValidate(final Shape s) { if( null != padding && !padding.zeroSize() ) { s.setPaddding(padding); } } @Override public void layout(final Group g, final AABBox box, final PMVMatrix4f pmv) { final boolean hasCellWidth = !FloatUtil.isZero(cellSize.x()); final boolean hasCellHeight = !FloatUtil.isZero(cellSize.y()); final boolean isCenteredHoriz = hasCellWidth && alignment.isSet(Alignment.Bit.CenterHoriz); final boolean isCenteredVert = hasCellHeight && alignment.isSet(Alignment.Bit.CenterVert); final boolean isScaled = alignment.isSet(Alignment.Bit.Fill) && ( hasCellWidth || hasCellHeight ); final List shapes = g.getShapes(); final AABBox sbox = new AABBox(); for(int i=0; i < shapes.size(); ++i) { final Shape s = shapes.get(i); // measure size pmv.pushMv(); s.applyMatToMv(pmv); s.getBounds().transform(pmv.getMv(), sbox); pmv.popMv(); final float x = 0, y = 0; if( TRACE_LAYOUT ) { System.err.println("bl("+i+").0: sbox "+sbox+", s "+s); } // IF isScaled: Uniform scale w/ lowest axis scale and center position on lower-scale axis final float shapeWidthU = sbox.getWidth(); final float shapeHeightU = sbox.getHeight(); if( FloatUtil.isZero(shapeHeightU) || FloatUtil.isZero(shapeHeightU) ) { continue; } final float sxy; float dxh = 0, dyh = 0; if( isScaled ) { // scaling to cell size final float cellWidth = hasCellWidth ? cellSize.x() - margin.width() : shapeWidthU; final float cellHeight = hasCellHeight ? cellSize.y() - margin.height() : shapeHeightU; final float sx = cellWidth / shapeWidthU; final float sy = cellHeight/ shapeHeightU; sxy = sx < sy ? sx : sy; if( isCenteredHoriz ) { dxh += shapeWidthU * ( sx - sxy ) * 0.5f; // horiz-center (adjustment for scale-axis w/o margin) } if( isCenteredVert ) { dyh += shapeHeightU * ( sy - sxy ) * 0.5f; // vert-center (adjustment for scale-axis w/o margin) } dyh += margin.bottom; // always consider unscaled margin when scaling dxh += margin.left; // ditto if( TRACE_LAYOUT ) { System.err.println("bl("+i+").s: "+sx+" x "+sy+" -> "+sxy+": +"+dxh+" / "+dyh+", U: s "+shapeWidthU+" x "+shapeHeightU+", sz "+cellWidth+" x "+cellHeight); } } else { sxy = 1; } final float shapeWidthS = sxy * shapeWidthU; final float shapeHeightS = sxy * shapeHeightU; final float cellWidthS = hasCellWidth ? cellSize.x() : shapeWidthS; final float cellHeightS = hasCellHeight ? cellSize.y() : shapeHeightS; if( !isScaled ) { // Center w/o scale and ignoring margin (not scaled) if( isCenteredHoriz ) { dxh += 0.5f * ( cellWidthS - shapeWidthS ); // horiz-center } else { dxh += margin.left; } if( isCenteredVert ) { dyh += 0.5f * ( cellHeightS - shapeHeightS ); // vert-center } else { dyh += margin.bottom; } } if( TRACE_LAYOUT ) { System.err.println("bl("+i+").m: "+x+" / "+y+" + "+dxh+" / "+dyh+", sxy "+sxy+", S: s "+shapeWidthS+" x "+shapeHeightS+", sz "+cellWidthS+" x "+cellHeightS); } // Position and scale shape { // New shape position s.moveTo( x + dxh, y + dyh, s.getPosition().z() ); // Remove the negative or positive delta on centered axis. // Only remove negative offset of non-centered axis (i.e. underline) final Vec3f diffBL = new Vec3f(s.getBounds().getLow()); diffBL.setZ(0); if( isCenteredHoriz || isCenteredVert ) { if( !isCenteredVert && diffBL.y() > 0 ) { diffBL.setY(0); // only adjust negative if !center-vert } else if( !isCenteredHoriz && diffBL.x() > 0 ) { diffBL.setX(0); // only adjust negative if !center-horiz } diffBL.mul(s.getScale()).scale(-1f); } else { diffBL.min(new Vec3f()).mul(s.getScale()).scale(-1f); } s.move( diffBL.scale(sxy) ); if( TRACE_LAYOUT ) { System.err.println("bl("+i+").bl: sbox0 "+s.getBounds()+", diffBL_ "+diffBL); } // resize bounds box.resize( x, y, sbox.getMinZ()); box.resize( x + cellWidthS, y + cellHeightS, sbox.getMaxZ()); } s.scale( sxy, sxy, 1f); if( TRACE_LAYOUT ) { System.err.println("bl("+i+").x: "+dxh+" / "+dyh+" -> "+s.getPosition()+", p3 "+shapeWidthS+" x "+shapeHeightS+", sz3 "+cellWidthS+" x "+cellHeightS+", box "+box.getWidth()+" x "+box.getHeight()); System.err.println("bl("+i+").x: "+s); System.err.println("bl("+i+").x: "+box); } } if( Float.isInfinite(box.getWidth()) || Float.isInfinite(box.getHeight()) ) { box.resize(0, 0, 0); } if( TRACE_LAYOUT ) { System.err.println("bl(X).x: "+box); } } @Override public String toString() { final String p_s = ( null == padding || padding.zeroSize() ) ? "" : ", "+padding.toString(); final String m_s = margin.zeroSize() ? "" : ", "+margin.toString(); return "Box[cell "+cellSize+", a "+alignment+m_s+p_s+"]"; } }