diff options
author | Sven Gothel <[email protected]> | 2023-04-05 10:06:26 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-04-05 10:06:26 +0200 |
commit | 1eb9d91bbf5d24a02c4d9e98501ff51eb7ecdcd0 (patch) | |
tree | dc1c7615f4b99d7b09c9b3c569f6e3d459dbb4e5 /src/graphui/classes | |
parent | 15e60161787224e85172685f74dc0ac195969b51 (diff) |
GraphUI: Adopting Vec*f API; Adding Group; Scene + Group are Container, traversing the PMVMatrix throughout childs (-> see TreeTool).
Utilizing the Vec*f (and Matrix4f) API w/ AABBox et al renders our code more clean & safe,
see commit 15e60161787224e85172685f74dc0ac195969b51.
A Group allows to contain multiple Shapes,
hence the PMVMatrix must be traversed accordingly using TreeTool
for all operations (draw, picking, win->obj coordinates, ..).
Hence Scene + Group are now implementing Container
and reuse code via TreeTool and a Shape.Visitor*.
This will allow further simplification of user code.
Diffstat (limited to 'src/graphui/classes')
10 files changed, 913 insertions, 214 deletions
diff --git a/src/graphui/classes/com/jogamp/graph/ui/Container.java b/src/graphui/classes/com/jogamp/graph/ui/Container.java new file mode 100644 index 000000000..e48476fcf --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/Container.java @@ -0,0 +1,97 @@ +/** + * 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; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +import com.jogamp.graph.ui.Shape.Visitor2; +import com.jogamp.graph.ui.Shape.Visitor1; +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.util.PMVMatrix; + +/** + * Container interface of UI {@link Shape}s + * @see Scene + * @see Shape + */ +public interface Container { + + List<Shape> getShapes(); + + void addShape(Shape s); + + /** Removes given shape, , w/o {@link Shape#destroy(com.jogamp.opengl.GL2ES2, com.jogamp.graph.curve.opengl.RegionRenderer) destroying} them. */ + void removeShape(Shape s); + + void addShapes(Collection<? extends Shape> shapes); + + /** Removes all given shapes, w/o {@link Shape#destroy(com.jogamp.opengl.GL2ES2, com.jogamp.graph.curve.opengl.RegionRenderer) destroying} them. */ + void removeShapes(Collection<? extends Shape> shapes); + + boolean contains(Shape s); + + AABBox getBounds(final PMVMatrix pmv, Shape shape); + + /** + * Traverses through the graph up until {@code shape} and apply {@code action} on it. + * @param pmv + * @param shape + * @param action + * @return true to signal operation complete, i.e. {@code shape} found, otherwise false + */ + boolean forOne(final PMVMatrix pmv, final Shape shape, final Runnable action); + + /** + * Traverses through the graph and apply {@link Visitor1#visit(Shape)} for each, stop if it returns true. + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor1#visit(Shape)} returned true, otherwise false + */ + boolean forAll(Visitor1 v); + + /** + * Traverses through the graph and apply {@link Visitor2#visit(Shape, PMVMatrix)} for each, stop if it returns true. + * @param pmv + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor2#visit(Shape, PMVMatrix)} returned true, otherwise false + */ + boolean forAll(final PMVMatrix pmv, Visitor2 v); + + /** + * Traverses through the graph and apply {@link Visitor#visit(Shape, PMVMatrix)} for each, stop if it returns true. + * + * Each {@link Container} level is sorted using {@code sortComp} + * @param sortComp + * @param pmv + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor2#visit(Shape, PMVMatrix)} returned true, otherwise false + */ + boolean forSortedAll(final Comparator<Shape> sortComp, final PMVMatrix pmv, final Visitor2 v); +}
\ No newline at end of file diff --git a/src/graphui/classes/com/jogamp/graph/ui/GraphShape.java b/src/graphui/classes/com/jogamp/graph/ui/GraphShape.java index 6efd7f5f4..8b142210a 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/GraphShape.java +++ b/src/graphui/classes/com/jogamp/graph/ui/GraphShape.java @@ -38,7 +38,6 @@ import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.Vertex.Factory; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.opengl.util.texture.TextureSequence; /** @@ -137,7 +136,7 @@ public abstract class GraphShape extends Shape { if( null != gl ) { clearDirtyRegions(gl); } - if( isShapeDirty() || null == region ) { + if( isShapeDirty() ) { if( null == region ) { region = createGLRegion(glp); } else if( null == gl ) { @@ -164,7 +163,7 @@ public abstract class GraphShape extends Shape { final float x2 = box.getMaxX(); final float y1 = box.getMinY(); final float y2 = box.getMaxY(); - final float z = box.getCenter()[2]; // 0; // box.getMinZ() + 0.025f; + final float z = box.getCenter().z(); // 0; // box.getMinZ() + 0.025f; { // Outer OutlineShape as Winding.CCW. shape.moveTo(x1, y1, z); diff --git a/src/graphui/classes/com/jogamp/graph/ui/Group.java b/src/graphui/classes/com/jogamp/graph/ui/Group.java new file mode 100644 index 000000000..8174b279c --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/Group.java @@ -0,0 +1,249 @@ +/** + * 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.util.PMVMatrix; + +import jogamp.graph.ui.TreeTool; + +/** + * Group of UI {@link Shape}s, optionally utilizing a {@link Group.Layout}. + * @see Scene + * @see Shape + * @see Group.Layout + */ +public class Group extends Shape implements Container { + /** Layout for the group, called @ {@link Group#validate(GL2ES2)} or {@link Group#validate(GLProfile)}. */ + public static interface Layout { + /** Performing the layout, called @ {@link Group#validate(GL2ES2)} or {@link Group#validate(GLProfile)}. */ + void layout(Group g); + } + + private final List<Shape> shapes = new ArrayList<Shape>(); + private Layout layouter; + + /** + * Create a Graph based {@link GLRegion} UI {@link Shape}. + */ + public Group() { + super(); + } + + /** + * Create a Graph based {@link GLRegion} UI {@link Shape} w/ given {@link Group.Layour}. + */ + public Group(final Layout l) { + super(); + this.layouter = l; + } + + /** Return current {@link Group.Layout}. */ + public Layout getLayour() { return layouter; } + + /** Set {@link Group.Layout}. */ + public void setLayout(final Layout l) { layouter = l; } + + @Override + public List<Shape> getShapes() { + return shapes; + } + @Override + public void addShape(final Shape s) { + shapes.add(s); + } + + /** Removes given shape, keeps it alive. */ + @Override + public void removeShape(final Shape s) { + shapes.remove(s); + } + + /** Removes all given shapes and destroys them. */ + public void removeShape(final GL2ES2 gl, final RegionRenderer renderer, final Shape s) { + s.setDebugBox(0f); + shapes.remove(s); + s.destroy(gl, renderer); + } + + @Override + public void addShapes(final Collection<? extends Shape> shapes) { + for(final Shape s : shapes) { + addShape(s); + } + } + /** Removes all given shapes, keeps them alive. */ + @Override + public void removeShapes(final Collection<? extends Shape> shapes) { + for(final Shape s : shapes) { + removeShape(s); + } + } + /** Removes all given shapes and destroys them. */ + public void removeShapes(final GL2ES2 gl, final RegionRenderer renderer, final Collection<? extends Shape> shapes) { + for(final Shape s : shapes) { + removeShape(gl, renderer, s); + } + } + + @Override + public boolean hasColorChannel() { + return false; // FIXME + } + + @Override + protected final void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer) { + for(final Shape s : shapes) { + // s.clearImpl0(gl, renderer);; + s.clear(gl, renderer);; + } + } + + @Override + protected final void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer) { + for(final Shape s : shapes) { + // s.destroyImpl0(gl, renderer); + s.destroy(gl, renderer);; + } + } + + private void layout() { + if( null != layouter ) { + layouter.layout(this); + } + } + + private final boolean doFrustumCulling = true; // FIXME + + @Override + protected final void drawImpl0(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount, final float[] rgba) { + final PMVMatrix pmv = renderer.getMatrix(); + final int shapeCount = shapes.size(); + for(int i=0; i<shapeCount; i++) { + final Shape shape = shapes.get(i); + if( shape.isEnabled() ) { + pmv.glPushMatrix(); + shape.setTransform(pmv); + + if( !doFrustumCulling || !pmv.glGetFrustum().isAABBoxOutside( shape.getBounds() ) ) { + if( null == rgba ) { + shape.drawToSelect(gl, renderer, sampleCount); + } else { + shape.draw(gl, renderer, sampleCount); + } + } + pmv.glPopMatrix(); + } + } + } + + @Override + protected void validateImpl(final GLProfile glp, final GL2ES2 gl) { + if( isShapeDirty() ) { + layout(); + final PMVMatrix pmv = new PMVMatrix(); + final AABBox tmpBox = new AABBox(); + final float[] vec3Tmp0 = new float[3]; + final float[] vec3Tmp1 = new float[3]; + for(final Shape s : shapes) { + // s.validateImpl(glp, gl); + if( null != gl ) { + s.validate(gl); + } else { + s.validate(glp); + } + pmv.glPushMatrix(); + s.setTransform(pmv); + s.getBounds().transformMv(tmpBox, pmv, vec3Tmp0, vec3Tmp0); + pmv.glPopMatrix(); + box.resize(tmpBox); + } + } + } + + @Override + public boolean contains(final Shape s) { + if( shapes.contains(s) ) { + return true; + } + for(final Shape shape : shapes) { + if( shape instanceof Container ) { + if( ((Container)shape).contains(s) ) { + return true; + } + } + } + return false; + } + + @Override + public AABBox getBounds(final PMVMatrix pmv, final Shape shape) { + pmv.reset(); + setTransform(pmv); + final AABBox res = new AABBox(); + if( null == shape ) { + return res; + } + final float[] vec3Tmp0 = new float[3]; + final float[] vec3Tmp1 = new float[3]; + forOne(pmv, shape, () -> { + shape.getBounds().transformMv(res, pmv, vec3Tmp0, vec3Tmp1); + }); + return res; + } + + @Override + public boolean forOne(final PMVMatrix pmv, final Shape shape, final Runnable action) { + return TreeTool.forOne(shapes, pmv, shape, action); + } + + @Override + public boolean forAll(final Visitor1 v) { + return TreeTool.forAll(shapes, v); + } + + @Override + public boolean forAll(final PMVMatrix pmv, final Visitor2 v) { + return TreeTool.forAll(shapes, pmv, v); + } + + @Override + public boolean forSortedAll(final Comparator<Shape> sortComp, final PMVMatrix pmv, final Visitor2 v) { + return TreeTool.forSortedAll(sortComp, shapes, pmv, v); + } +} + diff --git a/src/graphui/classes/com/jogamp/graph/ui/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/Scene.java index 831a9d334..ac3bbe146 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/Scene.java +++ b/src/graphui/classes/com/jogamp/graph/ui/Scene.java @@ -49,6 +49,8 @@ import com.jogamp.common.nio.Buffers; import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.ui.Shape.Visitor2; +import com.jogamp.graph.ui.Shape.Visitor1; import com.jogamp.newt.event.GestureHandler; import com.jogamp.newt.event.InputEvent; import com.jogamp.newt.event.MouseEvent; @@ -58,11 +60,14 @@ import com.jogamp.newt.event.GestureHandler.GestureEvent; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.Ray; +import com.jogamp.opengl.math.Vec3f; import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.opengl.util.GLPixelStorageModes; import com.jogamp.opengl.util.GLReadBufferUtil; import com.jogamp.opengl.util.PMVMatrix; +import jogamp.graph.ui.TreeTool; + /** * GraphUI Scene * <p> @@ -86,7 +91,7 @@ import com.jogamp.opengl.util.PMVMatrix; * </p> * @see Shape */ -public final class Scene implements GLEventListener { +public final class Scene implements Container, GLEventListener { /** Default scene distance on z-axis to projection is -1/5f. */ public static final float DEFAULT_SCENE_DIST = -1/5f; /** Default projection angle in degrees value is 45.0. */ @@ -201,14 +206,16 @@ public final class Scene implements GLEventListener { } } + @Override public List<Shape> getShapes() { return shapes; } + @Override public void addShape(final Shape s) { s.setDebugBox(dbgbox_thickness); shapes.add(s); } - /** Removes given shape, keeps it alive. */ + @Override public void removeShape(final Shape s) { s.setDebugBox(0f); shapes.remove(s); @@ -219,12 +226,13 @@ public final class Scene implements GLEventListener { shapes.remove(s); s.destroy(gl, renderer); } + @Override public void addShapes(final Collection<? extends Shape> shapes) { for(final Shape s : shapes) { addShape(s); } } - /** Removes all given shapes, keeps them alive. */ + @Override public void removeShapes(final Collection<? extends Shape> shapes) { for(final Shape s : shapes) { removeShape(s); @@ -236,6 +244,10 @@ public final class Scene implements GLEventListener { removeShape(gl, s); } } + @Override + public boolean contains(final Shape s) { + return false; + } public Shape getShapeByIdx(final int id) { if( 0 > id ) { return null; @@ -449,17 +461,9 @@ public final class Scene implements GLEventListener { * @param runnable the action to perform if {@link Shape} was found * @return picked Shape if any or null as stored in {@code shape} */ - public Shape pickShape(final PMVMatrix pmv, final int glWinX, final int glWinY, final float[] objPos, final Shape[] shape, final Runnable runnable) { + public Shape pickShape(final PMVMatrix pmv, final int glWinX, final int glWinY, final Vec3f objPos, final Shape[] shape, final Runnable runnable) { setupMatrix(pmv); - final Shape pick = pickShapeImpl(pmv, glWinX, glWinY, objPos); - shape[0] = pick; - if( null != pick ) { - runnable.run(); - } - return pick; - } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Shape pickShapeImpl(final PMVMatrix pmv, final int glWinX, final int glWinY, final float[] objPos) { + final float winZ0 = 0f; final float winZ1 = 0.3f; /** @@ -467,34 +471,28 @@ public final class Scene implements GLEventListener { gl.glReadPixels( x, y, 1, 1, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, winZRB); winZ1 = winZRB.get(0); // dir */ - + final int[] viewport = getViewport(); final Ray ray = new Ray(); - - final Object[] shapesS = shapes.toArray(); - Arrays.sort(shapesS, (Comparator)Shape.ZAscendingComparator); - - for(int i=shapesS.length-1; i>=0; i--) { - final Shape uiShape = (Shape)shapesS[i]; - - if( uiShape.isEnabled() ) { - pmv.glPushMatrix(); - uiShape.setTransform(pmv); - final boolean ok = pmv.gluUnProjectRay(glWinX, glWinY, winZ0, winZ1, getViewport(), 0, ray); - if( ok ) { - final AABBox sbox = uiShape.getBounds(); - if( sbox.intersectsRay(ray) ) { - // System.err.printf("Pick.0: shape %d, [%d, %d, %f/%f] -> %s%n", i, glWinX, glWinY, winZ0, winZ1, ray); - if( null == sbox.getRayIntersection(objPos, ray, FloatUtil.EPSILON, true, dpyTmp1V3, dpyTmp2V3, dpyTmp3V3) ) { - throw new InternalError("Ray "+ray+", box "+sbox); - } - // System.err.printf("Pick.1: shape %d @ [%f, %f, %f], within %s%n", i, objPos[0], objPos[1], objPos[2], uiShape.getBounds()); - return uiShape; + shape[0] = null; + + forSortedAll(Shape.ZAscendingComparator, pmv, (final Shape s, final PMVMatrix pmv2) -> { + final boolean ok = pmv.gluUnProjectRay(glWinX, glWinY, winZ0, winZ1, viewport, ray); + if( ok ) { + final AABBox sbox = s.getBounds(); + if( sbox.intersectsRay(ray) ) { + // System.err.printf("Pick.0: shape %d, [%d, %d, %f/%f] -> %s%n", i, glWinX, glWinY, winZ0, winZ1, ray); + if( null == sbox.getRayIntersection(objPos, ray, FloatUtil.EPSILON, true) ) { + throw new InternalError("Ray "+ray+", box "+sbox); } + // System.err.printf("Pick.1: shape %d @ [%f, %f, %f], within %s%n", i, objPos[0], objPos[1], objPos[2], uiShape.getBounds()); + shape[0] = s; + runnable.run(); + return true; } - pmv.glPopMatrix(); // we leave the stack open if picked above, allowing the modelview shape transform to be reused } - } - return null; + return false; + }); + return shape[0]; } private final float[] dpyTmp1V3 = new float[3]; private final float[] dpyTmp2V3 = new float[3]; @@ -514,7 +512,7 @@ public final class Scene implements GLEventListener { * @param shape storage for found {@link Shape} or null * @param runnable the action to perform if {@link Shape} was found */ - public void pickShapeGL(final int glWinX, final int glWinY, final float[] objPos, final Shape[] shape, final Runnable runnable) { + public void pickShapeGL(final int glWinX, final int glWinY, final Vec3f objPos, final Shape[] shape, final Runnable runnable) { if( null == cDrawable ) { return; } @@ -586,10 +584,79 @@ public final class Scene implements GLEventListener { * @param objPos resulting object position * @param runnable action */ - public void winToShapeCoord(final Shape shape, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[] objPos, final Runnable runnable) { - if( null != shape && null != shape.winToShapeCoord(pmvMatrixSetup, renderer.getViewport(), glWinX, glWinY, pmv, objPos) ) { - runnable.run(); + public void winToShapeCoord(final Shape shape, final int glWinX, final int glWinY, final PMVMatrix pmv, final Vec3f objPos, final Runnable runnable) { + if( null == shape ) { + return; } + final int[] viewport = getViewport(); + setupMatrix(pmv); + forOne(pmv, shape, () -> { + if( null != shape.winToShapeCoord(pmv, viewport, glWinX, glWinY, objPos) ) { + runnable.run(); + } + }); + } + + @Override + public AABBox getBounds(final PMVMatrix pmv, final Shape shape) { + final AABBox res = new AABBox(); + if( null == shape ) { + return res; + } + setupMatrix(pmv); + forOne(pmv, shape, () -> { + shape.getBounds().transformMv(res, pmv, new float[3], new float[3]); + }); + return res; + } + + /** + * Traverses through the graph up until {@code shape} and apply {@code action} on it. + * @param pmv + * @param shape + * @param action + * @return true to signal operation complete, i.e. {@code shape} found, otherwise false + */ + @Override + public boolean forOne(final PMVMatrix pmv, final Shape shape, final Runnable action) { + setupMatrix(pmv); + return TreeTool.forOne(shapes, pmv, shape, action); + } + + /** + * Traverses through the graph and apply {@link Visitor2#visit(Shape, PMVMatrix)} for each, stop if it returns true. + * @param pmv + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor2#visit(Shape, PMVMatrix)} returned true, otherwise false + */ + @Override + public boolean forAll(final PMVMatrix pmv, final Visitor2 v) { + setupMatrix(pmv); + return TreeTool.forAll(shapes, pmv, v); + } + + /** + * Traverses through the graph and apply {@link Visitor1#visit(Shape)} for each, stop if it returns true. + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor1#visit(Shape)} returned true, otherwise false + */ + @Override + public boolean forAll(final Visitor1 v) { + return TreeTool.forAll(shapes, v); + } + + /** + * Traverses through the graph and apply {@link Visitor#visit(Shape, PMVMatrix)} for each, stop if it returns true. + * + * Each {@link Container} level is sorted using {@code sortComp} + * @param sortComp + * @param pmv + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor2#visit(Shape, PMVMatrix)} returned true, otherwise false + */ + @Override + public boolean forSortedAll(final Comparator<Shape> sortComp, final PMVMatrix pmv, final Visitor2 v) { + return TreeTool.forSortedAll(sortComp, shapes, pmv, v); } /** @@ -788,16 +855,18 @@ public final class Scene implements GLEventListener { // gesture .. delegate to active shape! final InputEvent orig = gh.getTrigger(); if( orig instanceof MouseEvent ) { - final MouseEvent e = (MouseEvent) orig; - // flip to GL window coordinates - final int glWinX = e.getX(); - final int glWinY = getHeight() - e.getY() - 1; - final PMVMatrix pmv = new PMVMatrix(); - final float[] objPos = new float[3]; final Shape shape = activeShape; - winToShapeCoord(shape, glWinX, glWinY, pmv, objPos, () -> { - shape.dispatchGestureEvent(gh, glWinX, glWinY, pmv, renderer.getViewport(), objPos); - }); + if( shape.isInteractive() ) { + final MouseEvent e = (MouseEvent) orig; + // flip to GL window coordinates + final int glWinX = e.getX(); + final int glWinY = getHeight() - e.getY() - 1; + final PMVMatrix pmv = new PMVMatrix(); + final Vec3f objPos = new Vec3f(); + winToShapeCoord(shape, glWinX, glWinY, pmv, objPos, () -> { + shape.dispatchGestureEvent(gh, glWinX, glWinY, pmv, renderer.getViewport(), objPos); + }); + } } } } @@ -812,7 +881,7 @@ public final class Scene implements GLEventListener { final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY) { if( null == activeShape ) { dispatchMouseEventPickShape(e, glWinX, glWinY); - } else { + } else if( activeShape.isInteractive() ) { dispatchMouseEventForShape(activeShape, e, glWinX, glWinY); } } @@ -824,11 +893,13 @@ public final class Scene implements GLEventListener { */ final void dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY) { final PMVMatrix pmv = new PMVMatrix(); - final float[] objPos = new float[3]; + final Vec3f objPos = new Vec3f(); final Shape[] shape = { null }; if( null == pickShape(pmv, glWinX, glWinY, objPos, shape, () -> { setActiveShape(shape[0]); - shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos); + if( shape[0].isInteractive() ) { + shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos); + } } ) ) { releaseActiveShape(); @@ -843,7 +914,7 @@ public final class Scene implements GLEventListener { */ final void dispatchMouseEventForShape(final Shape shape, final MouseEvent e, final int glWinX, final int glWinY) { final PMVMatrix pmv = new PMVMatrix(); - final float[] objPos = new float[3]; + final Vec3f objPos = new Vec3f(); winToShapeCoord(shape, glWinX, glWinY, pmv, objPos, () -> { shape.dispatchMouseEvent(e, glWinX, glWinY, objPos); }); } @@ -1059,4 +1130,5 @@ public final class Scene implements GLEventListener { } }; private PMVMatrixSetup pmvMatrixSetup = defaultPMVMatrixSetup; + } diff --git a/src/graphui/classes/com/jogamp/graph/ui/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/Shape.java index dcac39504..658d7848d 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/Shape.java +++ b/src/graphui/classes/com/jogamp/graph/ui/Shape.java @@ -44,7 +44,8 @@ import com.jogamp.newt.event.MouseEvent; import com.jogamp.newt.event.MouseListener; import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.Quaternion; -import com.jogamp.opengl.math.VectorUtil; +import com.jogamp.opengl.math.Vec2f; +import com.jogamp.opengl.math.Vec3f; import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.opengl.util.PMVMatrix; @@ -67,6 +68,34 @@ import com.jogamp.opengl.util.PMVMatrix; * @see Scene */ public abstract class Shape { + /** + * General {@link Shape} visitor + */ + public static interface Visitor1 { + /** + * Visitor method + * @param s the {@link Shape} to process + * @return true to signal operation complete and to stop traversal, otherwise false + */ + boolean visit(Shape s); + } + + /** + * General {@link Shape} visitor + */ + public static interface Visitor2 { + /** + * Visitor method + * @param s the {@link Shape} to process + * @param pmv the {@link PMVMatrix} setup from the {@link Scene} down to the {@link Shape} + * @return true to signal operation complete and to stop traversal, otherwise false + */ + boolean visit(Shape s, final PMVMatrix pmv); + } + + /** + * General {@link Shape} listener action + */ public static interface Listener { void run(final Shape shape); } @@ -78,10 +107,10 @@ public abstract class Shape { protected final AABBox box; - private final float[] position = new float[] { 0f, 0f, 0f }; + private final Vec3f position = new Vec3f(); private final Quaternion rotation = new Quaternion(); - private final float[] rotPivot = new float[] { 0f, 0f, 0f }; - private final float[] scale = new float[] { 1f, 1f, 1f }; + private final Vec3f rotPivot = new Vec3f(); + private final Vec3f scale = new Vec3f(1f, 1f, 1f); private volatile int dirty = DIRTY_SHAPE | DIRTY_STATE; private final Object dirtySync = new Object(); @@ -102,6 +131,7 @@ public abstract class Shape { private boolean toggleable = false; private boolean draggable = true; private boolean resizable = true; + private boolean interactive = true; private boolean enabled = true; private float dbgbox_thickness = 0f; // fractional thickness of bounds, 0f for no debug box private ArrayList<MouseGestureListener> mouseListeners = new ArrayList<MouseGestureListener>(); @@ -142,16 +172,10 @@ public abstract class Shape { public final void clear(final GL2ES2 gl, final RegionRenderer renderer) { synchronized ( dirtySync ) { clearImpl0(gl, renderer); - position[0] = 0f; - position[1] = 0f; - position[2] = 0f; + position.set(0f, 0f, 0f); rotation.setIdentity(); - rotPivot[0] = 0f; - rotPivot[1] = 0f; - rotPivot[2] = 0f; - scale[0] = 1f; - scale[1] = 1f; - scale[2] = 1f; + rotPivot.set(0f, 0f, 0f); + scale.set(1f, 1f, 1f); box.reset(); markShapeDirty(); } @@ -164,16 +188,10 @@ public abstract class Shape { */ public final void destroy(final GL2ES2 gl, final RegionRenderer renderer) { destroyImpl0(gl, renderer); - position[0] = 0f; - position[1] = 0f; - position[2] = 0f; + position.set(0f, 0f, 0f); rotation.setIdentity(); - rotPivot[0] = 0f; - rotPivot[1] = 0f; - rotPivot[2] = 0f; - scale[0] = 1f; - scale[1] = 1f; - scale[2] = 1f; + rotPivot.set(0f, 0f, 0f); + scale.set(1f, 1f, 1f); box.reset(); markShapeDirty(); } @@ -182,45 +200,53 @@ public abstract class Shape { /** Move to scaled position. Position ends up in PMVMatrix unmodified. */ public final void moveTo(final float tx, final float ty, final float tz) { - position[0] = tx; - position[1] = ty; - position[2] = tz; + position.set(tx, ty, tz); + if( null != onMoveListener ) { + onMoveListener.run(this); + } + } + + /** Move to scaled position. Position ends up in PMVMatrix unmodified. */ + public final void moveTo(final Vec3f t) { + position.set(t); if( null != onMoveListener ) { onMoveListener.run(this); } - // System.err.println("Shape.setTranslate: "+tx+"/"+ty+"/"+tz+": "+toString()); } /** Move about scaled distance. Position ends up in PMVMatrix unmodified. */ public final void move(final float dtx, final float dty, final float dtz) { - position[0] += dtx; - position[1] += dty; - position[2] += dtz; + position.add(dtx, dty, dtz); if( null != onMoveListener ) { onMoveListener.run(this); } - // System.err.println("Shape.translate: "+tx+"/"+ty+"/"+tz+": "+toString()); } - /** Returns float[3] position, i.e. scaled translation as set via {@link #moveTo(float, float, float) or {@link #move(float, float, float)}}. */ - public final float[] getPosition() { return position; } + /** Move about scaled distance. Position ends up in PMVMatrix unmodified. */ + public final void move(final Vec3f dt) { + position.add(dt); + if( null != onMoveListener ) { + onMoveListener.run(this); + } + } + + /** Returns position, i.e. scaled translation as set via {@link #moveTo(float, float, float) or {@link #move(float, float, float)}}. */ + public final Vec3f getPosition() { return position; } /** Returns {@link Quaternion} for rotation. */ public final Quaternion getRotation() { return rotation; } - /** Return float[3] unscaled rotation origin, aka pivot. */ - public final float[] getRotationPivot() { return rotPivot; } + /** Return unscaled rotation origin, aka pivot. */ + public final Vec3f getRotationPivot() { return rotPivot; } /** Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GLProfile, GL2ES2)}. */ - public final void setRotationPivot(final float rx, final float ry, final float rz) { - rotPivot[0] = rx; - rotPivot[1] = ry; - rotPivot[2] = rz; + public final void setRotationPivot(final float px, final float py, final float pz) { + rotPivot.set(px, py, pz); } /** * Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GLProfile, GL2ES2)}. - * @param pivot float[3] rotation origin + * @param pivot rotation origin */ - public final void setRotationPivot(final float[/*3*/] pivot) { - System.arraycopy(pivot, 0, rotPivot, 0, 3); + public final void setRotationPivot(final Vec3f pivot) { + rotPivot.set(pivot); } /** @@ -229,9 +255,7 @@ public abstract class Shape { * @see #getScale() */ public final void setScale(final float sx, final float sy, final float sz) { - scale[0] = sx; - scale[1] = sy; - scale[2] = sz; + scale.set(sx, sy, sz); } /** * Multiply current scale factor by given scale. @@ -239,22 +263,14 @@ public abstract class Shape { * @see #getScale() */ public final void scale(final float sx, final float sy, final float sz) { - scale[0] *= sx; - scale[1] *= sy; - scale[2] *= sz; + scale.scale(sx, sy, sz); } /** - * Returns float[3] scale factors. + * Returns scale factors. * @see #setScale(float, float, float) * @see #scale(float, float, float) */ - public final float[] getScale() { return scale; } - /** Returns X-axis scale factor. */ - public final float getScaleX() { return scale[0]; } - /** Returns Y-axis scale factor. */ - public final float getScaleY() { return scale[1]; } - /** Returns Z-axis scale factor. */ - public final float getScaleZ() { return scale[2]; } + public final Vec3f getScale() { return scale; } /** * Marks the shape dirty, causing next {@link #draw(GL2ES2, RegionRenderer, int[]) draw()} @@ -304,7 +320,7 @@ public abstract class Shape { * @see #getBounds() */ public final float getScaledWidth() { - return box.getWidth() * getScaleX(); + return box.getWidth() * getScale().x(); } /** @@ -317,7 +333,7 @@ public abstract class Shape { * @see #getBounds() */ public final float getScaledHeight() { - return box.getHeight() * getScaleY(); + return box.getHeight() * getScale().y(); } /** @@ -450,39 +466,39 @@ public abstract class Shape { * @see #setScale(float, float, float) */ public void setTransform(final PMVMatrix pmv) { - final boolean hasScale = !VectorUtil.isVec3Equal(scale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON); + final boolean hasScale = !scale.isEqual(Vec3f.ONE); final boolean hasRotate = !rotation.isIdentity(); - final boolean hasRotPivot = !VectorUtil.isVec3Zero(rotPivot, 0, FloatUtil.EPSILON); - final float[] ctr = box.getCenter(); - final boolean sameScaleRotatePivot = hasScale && hasRotate && ( !hasRotPivot || VectorUtil.isVec3Equal(rotPivot, 0, ctr, 0, FloatUtil.EPSILON) ); + final boolean hasRotPivot = !rotPivot.isZero(); + final Vec3f ctr = box.getCenter(); + final boolean sameScaleRotatePivot = hasScale && hasRotate && ( !hasRotPivot || rotPivot.isEqual(ctr) ); - pmv.glTranslatef(position[0], position[1], position[2]); // translate, scaled + pmv.glTranslatef(position.x(), position.y(), position.z()); // translate, scaled if( sameScaleRotatePivot ) { // Scale shape from its center position and rotate around its center - pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // add-back center, scaled + pmv.glTranslatef(ctr.x()*scale.x(), ctr.y()*scale.y(), ctr.z()*scale.z()); // add-back center, scaled pmv.glRotate(rotation); - pmv.glScalef(scale[0], scale[1], scale[2]); - pmv.glTranslatef(-ctr[0], -ctr[1], -ctr[2]); // move to center + pmv.glScalef(scale.x(), scale.y(), scale.z()); + pmv.glTranslatef(-ctr.x(), -ctr.y(), -ctr.z()); // move to center } else if( hasRotate || hasScale ) { if( hasRotate ) { if( hasRotPivot ) { // Rotate shape around its scaled pivot - pmv.glTranslatef(rotPivot[0]*scale[0], rotPivot[1]*scale[1], rotPivot[2]*scale[2]); // pivot back from rot-pivot, scaled + pmv.glTranslatef(rotPivot.x()*scale.x(), rotPivot.y()*scale.y(), rotPivot.z()*scale.z()); // pivot back from rot-pivot, scaled pmv.glRotate(rotation); - pmv.glTranslatef(-rotPivot[0]*scale[0], -rotPivot[1]*scale[1], -rotPivot[2]*scale[2]); // pivot to rot-pivot, scaled + pmv.glTranslatef(-rotPivot.x()*scale.x(), -rotPivot.y()*scale.y(), -rotPivot.z()*scale.z()); // pivot to rot-pivot, scaled } else { // Rotate shape around its scaled center - pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // pivot back from center-pivot, scaled + pmv.glTranslatef(ctr.x()*scale.x(), ctr.y()*scale.y(), ctr.z()*scale.z()); // pivot back from center-pivot, scaled pmv.glRotate(rotation); - pmv.glTranslatef(-ctr[0]*scale[0], -ctr[1]*scale[1], -ctr[2]*scale[2]); // pivot to center-pivot, scaled + pmv.glTranslatef(-ctr.x()*scale.x(), -ctr.y()*scale.y(), -ctr.z()*scale.z()); // pivot to center-pivot, scaled } } if( hasScale ) { // Scale shape from its center position - pmv.glTranslatef(ctr[0]*scale[0], ctr[1]*scale[1], ctr[2]*scale[2]); // add-back center, scaled - pmv.glScalef(scale[0], scale[1], scale[2]); - pmv.glTranslatef(-ctr[0], -ctr[1], -ctr[2]); // move to center + pmv.glTranslatef(ctr.x()*scale.x(), ctr.y()*scale.y(), ctr.z()*scale.z()); // add-back center, scaled + pmv.glScalef(scale.x(), scale.y(), scale.z()); + pmv.glTranslatef(-ctr.x(), -ctr.y(), -ctr.z()); // move to center } } // TODO: Add alignment features. @@ -506,12 +522,12 @@ public abstract class Shape { // System.err.println("Shape::getSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); final float[] winCoordHigh = new float[3]; final float[] winCoordLow = new float[3]; - final float[] high = getBounds().getHigh(); - final float[] low = getBounds().getLow(); + final Vec3f high = box.getHigh(); + final Vec3f low = box.getLow(); - if( pmv.gluProject(high[0], high[1], high[2], viewport, 0, winCoordHigh, 0) ) { + if( pmv.gluProject(high.x(), high.y(), high.z(), viewport, 0, winCoordHigh, 0) ) { // System.err.printf("Shape::surfaceSize.H: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), high[0], high[1], high[2], winCoordHigh[0], winCoordHigh[1], winCoordHigh[2]); - if( pmv.gluProject(low[0], low[1], low[2], viewport, 0, winCoordLow, 0) ) { + if( pmv.gluProject(low.x(), low.y(), low.z(), viewport, 0, winCoordLow, 0) ) { // System.err.printf("Shape::surfaceSize.L: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), low[0], low[1], low[2], winCoordLow[0], winCoordLow[1], winCoordLow[2]); surfaceSize[0] = (int)(winCoordHigh[0] - winCoordLow[0]); surfaceSize[1] = (int)(winCoordHigh[1] - winCoordLow[1]); @@ -611,17 +627,17 @@ public abstract class Shape { * </p> * @param pmv well formed {@link PMVMatrix}, e.g. could have been setup via {@link Scene#setupMatrix(PMVMatrix) setupMatrix(..)} and {@link #setTransform(PMVMatrix)}. * @param viewport the int[4] viewport - * @param objPos float[3] object position relative to this shape's center + * @param objPos object position relative to this shape's center * @param glWinPos int[2] target window position of objPos relative to this shape * @return given int[2] {@code glWinPos} for successful gluProject(..) operation, otherwise {@code null} * @see #shapeToWinCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], float[], PMVMatrix, int[]) * @see #shapeToWinCoord(Scene, float[], PMVMatrix, int[]) */ - public int[/*2*/] shapeToWinCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final float[/*3*/] objPos, final int[/*2*/] glWinPos) { + public int[/*2*/] shapeToWinCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final Vec3f objPos, final int[/*2*/] glWinPos) { // System.err.println("Shape::objToWinCoordgetSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); final float[] winCoord = new float[3]; - if( pmv.gluProject(objPos[0], objPos[1], objPos[2], viewport, 0, winCoord, 0) ) { + if( pmv.gluProject(objPos.x(), objPos.y(), objPos.z(), viewport, 0, winCoord, 0) ) { // System.err.printf("Shape::objToWinCoord.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), objPos[0], objPos[1], objPos[2], winCoord[0], winCoord[1], winCoord[2]); glWinPos[0] = (int)(winCoord[0]); glWinPos[1] = (int)(winCoord[1]); @@ -639,7 +655,7 @@ public abstract class Shape { * </p> * @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) setup} given {@link PMVMatrix} {@code pmv}. * @param viewport used viewport for {@link PMVMatrix#gluProject(float, float, float, int[], int, float[], int)} - * @param objPos float[3] object position relative to this shape's center + * @param objPos object position relative to this shape's center * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup}, * {@link #setTransform(PMVMatrix) shape-transformed} and can be reused by the caller. * @param glWinPos int[2] target window position of objPos relative to this shape @@ -647,7 +663,7 @@ public abstract class Shape { * @see #shapeToWinCoord(PMVMatrix, int[], float[], int[]) * @see #shapeToWinCoord(Scene, float[], PMVMatrix, int[]) */ - public int[/*2*/] shapeToWinCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final int[/*4*/] viewport, final float[/*3*/] objPos, final PMVMatrix pmv, final int[/*2*/] glWinPos) { + public int[/*2*/] shapeToWinCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final int[/*4*/] viewport, final Vec3f objPos, final PMVMatrix pmv, final int[/*2*/] glWinPos) { pmvMatrixSetup.set(pmv, viewport[0], viewport[1], viewport[2], viewport[3]); setTransform(pmv); return this.shapeToWinCoord(pmv, viewport, objPos, glWinPos); @@ -660,7 +676,7 @@ public abstract class Shape { * including this shape's {@link #setTransform(PMVMatrix)}. * </p> * @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. - * @param objPos float[3] object position relative to this shape's center + * @param objPos object position relative to this shape's center * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup}, * {@link #setTransform(PMVMatrix) shape-transformed} and can be reused by the caller. * @param glWinPos int[2] target window position of objPos relative to this shape @@ -668,7 +684,7 @@ public abstract class Shape { * @see #shapeToWinCoord(PMVMatrix, int[], float[], int[]) * @see #shapeToWinCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], float[], PMVMatrix, int[]) */ - public int[/*2*/] shapeToWinCoord(final Scene scene, final float[/*3*/] objPos, final PMVMatrix pmv, final int[/*2*/] glWinPos) { + public int[/*2*/] shapeToWinCoord(final Scene scene, final Vec3f objPos, final PMVMatrix pmv, final int[/*2*/] glWinPos) { return this.shapeToWinCoord(scene.getPMVMatrixSetup(), scene.getViewport(), objPos, pmv, glWinPos); } @@ -683,19 +699,21 @@ public abstract class Shape { * @param viewport the int[4] viewport * @param glWinX in GL window coordinates, origin bottom-left * @param glWinY in GL window coordinates, origin bottom-left - * @param objPos float[3] target object position of glWinX/glWinY relative to this shape - * @return given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} + * @param objPos target object position of glWinX/glWinY relative to this shape + * @return given {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} * @see #winToShapeCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], int, int, PMVMatrix, float[]) * @see #winToShapeCoord(Scene, int, int, PMVMatrix, float[]) */ - public float[/*3*/] winToShapeCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final int glWinX, final int glWinY, final float[/*3*/] objPos) { - final float[] ctr = getBounds().getCenter(); + public Vec3f winToShapeCoord(final PMVMatrix pmv, final int[/*4*/] viewport, final int glWinX, final int glWinY, final Vec3f objPos) { + final Vec3f ctr = box.getCenter(); final float[] tmp = new float[3]; - if( pmv.gluProject(ctr[0], ctr[1], ctr[2], viewport, 0, tmp, 0) ) { + if( pmv.gluProject(ctr.x(), ctr.y(), ctr.z(), viewport, 0, tmp, 0) ) { // System.err.printf("Shape::winToObjCoord.0: shape %d: obj [%15.10ff, %15.10ff, %15.10ff] -> win [%d / %d -> %7.2ff, %7.2ff, %7.2ff, diff %7.2ff x %7.2ff]%n", getName(), ctr[0], ctr[1], ctr[2], glWinX, glWinY, tmp[0], tmp[1], tmp[2], glWinX-tmp[0], glWinY-tmp[1]); - if( pmv.gluUnProject(glWinX, glWinY, tmp[2], viewport, 0, objPos, 0) ) { + final float winZ = tmp[2]; + if( pmv.gluUnProject(glWinX, glWinY, winZ, viewport, 0, objPos.get(tmp), 0) ) { // System.err.printf("Shape::winToObjCoord.X: shape %d: win [%d, %d, %7.2ff] -> obj [%15.10ff, %15.10ff, %15.10ff]%n", getName(), glWinX, glWinY, tmp[2], objPos[0], objPos[1], objPos[2]); + objPos.set(tmp); return objPos; } } @@ -714,12 +732,12 @@ public abstract class Shape { * @param glWinY in GL window coordinates, origin bottom-left * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup}, * {@link #setTransform(PMVMatrix) shape-transformed} and can be reused by the caller. - * @param objPos float[3] target object position of glWinX/glWinY relative to this shape - * @return given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} + * @param objPos target object position of glWinX/glWinY relative to this shape + * @return given {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} * @see #winToShapeCoord(PMVMatrix, int[], int, int, float[]) * @see #winToShapeCoord(Scene, int, int, PMVMatrix, float[]) */ - public float[/*3*/] winToShapeCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final int[/*4*/] viewport, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[/*3*/] objPos) { + public Vec3f winToShapeCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final int[/*4*/] viewport, final int glWinX, final int glWinY, final PMVMatrix pmv, final Vec3f objPos) { pmvMatrixSetup.set(pmv, viewport[0], viewport[1], viewport[2], viewport[3]); setTransform(pmv); return this.winToShapeCoord(pmv, viewport, glWinX, glWinY, objPos); @@ -736,12 +754,12 @@ public abstract class Shape { * @param glWinY in GL window coordinates, origin bottom-left * @param pmv a new {@link PMVMatrix} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix, int, int, int, int) be setup}, * {@link #setTransform(PMVMatrix) shape-transformed} and can be reused by the caller. - * @param objPos float[3] target object position of glWinX/glWinY relative to this shape - * @return given float[3] {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} + * @param objPos target object position of glWinX/glWinY relative to this shape + * @return given {@code objPos} for successful gluProject(..) and gluUnProject(..) operation, otherwise {@code null} * @see #winToShapeCoord(PMVMatrix, int[], int, int, float[]) * @see #winToShapeCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, int[], int, int, PMVMatrix, float[]) */ - public float[/*3*/] winToShapeCoord(final Scene scene, final int glWinX, final int glWinY, final PMVMatrix pmv, final float[/*3*/] objPos) { + public Vec3f winToShapeCoord(final Scene scene, final int glWinX, final int glWinY, final PMVMatrix pmv, final Vec3f objPos) { return this.winToShapeCoord(scene.getPMVMatrixSetup(), scene.getViewport(), glWinX, glWinY, pmv, objPos); } @@ -808,14 +826,14 @@ public abstract class Shape { public String getSubString() { final String pivotS; - if( !VectorUtil.isVec3Zero(rotPivot, 0, FloatUtil.EPSILON) ) { - pivotS = "pivot["+rotPivot[0]+", "+rotPivot[1]+", "+rotPivot[2]+"], "; + if( !rotPivot.isZero() ) { + pivotS = "pivot["+rotPivot+"], "; } else { pivotS = ""; } final String scaleS; - if( !VectorUtil.isVec3Equal(scale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON) ) { - scaleS = "scale["+scale[0]+", "+scale[1]+", "+scale[2]+"], "; + if( !scale.isEqual( Vec3f.ONE ) ) { + scaleS = "scale["+scale+"], "; } else { scaleS = "scale 1, "; } @@ -825,7 +843,7 @@ public abstract class Shape { } else { rotateS = ""; } - return "enabled "+enabled+", toggle[able "+toggleable+", state "+toggle+"], pos["+position[0]+", "+position[1]+", "+position[2]+ + return "enabled "+enabled+", toggle[able "+toggleable+", state "+toggle+"], pos["+position+ "], "+pivotS+scaleS+rotateS+ "box "+box; } @@ -842,6 +860,11 @@ public abstract class Shape { return this.down; } + /** + * + * @param toggleable + * @see #isInteractive() + */ public void setToggleable(final boolean toggleable) { this.toggleable = toggleable; } @@ -849,6 +872,7 @@ public abstract class Shape { /** * Returns true if this shape is toggable, * i.e. rendered w/ {@link #setToggleOnColorMod(float, float, float, float)} or {@link #setToggleOffColorMod(float, float, float, float)}. + * @see #isInteractive() */ public boolean isToggleable() { return toggleable; @@ -866,15 +890,36 @@ public abstract class Shape { public boolean isToggleOn() { return toggle; } /** + * Set whether this shape is interactive, + * i.e. any user interaction like + * - {@link #isToggleable()} + * - {@link #isDraggable()} + * - {@link #isResizable()} + * but excluding programmatic changes. + * @param v new value for {@link #isInteractive()} + */ + public void setInteractive(final boolean v) { interactive = v; } + /** + * Returns if this shape allows user interaction, see {@link #setInteractive(boolean)} + * @see #setInteractive(boolean) + */ + public boolean isInteractive() { return interactive; } + + /** * Set whether this shape is draggable, * i.e. translated by 1-pointer-click and drag. * <p> * Default draggable is true. * </p> + * @see #isInteractive() */ public void setDraggable(final boolean draggable) { this.draggable = draggable; } + /** + * Returns if this shape is draggable, a user interaction. + * @see #isInteractive() + */ public boolean isDraggable() { return draggable; } @@ -885,10 +930,15 @@ public abstract class Shape { * <p> * Default resizable is true. * </p> + * @see #isInteractive() */ public void setResizable(final boolean resizable) { this.resizable = resizable; } + /** + * Returns if this shape is resiable, a user interaction. + * @see #isInteractive() + */ public boolean isResizable() { return resizable; } @@ -938,11 +988,11 @@ public abstract class Shape { /** The associated {@link Shape} for this event */ public final Shape shape; /** The relative object coordinate of glWinX/glWinY to the associated {@link Shape}. */ - public final float[] objPos; + public final Vec3f objPos; /** The GL window coordinates, origin bottom-left */ public final int[] winPos; /** The drag delta of the relative object coordinate of glWinX/glWinY to the associated {@link Shape}. */ - public final float[] objDrag = { 0f, 0f }; + public final Vec2f objDrag = new Vec2f(); /** The drag delta of GL window coordinates, origin bottom-left */ public final int[] winDrag = { 0, 0 }; @@ -953,7 +1003,7 @@ public abstract class Shape { * @param shape associated shape * @param objPos relative object coordinate of glWinX/glWinY to the associated shape. */ - EventInfo(final int glWinX, final int glWinY, final Shape shape, final float[] objPos) { + EventInfo(final int glWinX, final int glWinY, final Shape shape, final Vec3f objPos) { this.winPos = new int[] { glWinX, glWinY }; this.shape = shape; this.objPos = objPos; @@ -961,14 +1011,14 @@ public abstract class Shape { @Override public String toString() { - return "EventDetails[winPos ["+winPos[0]+", "+winPos[1]+"], objPos ["+objPos[0]+", "+objPos[1]+", "+objPos[2]+"], "+shape+"]"; + return "EventDetails[winPos ["+winPos[0]+", "+winPos[1]+"], objPos ["+objPos+"], "+shape+"]"; } } private boolean dragFirst = false; - private final float[] objDraggedFirst = { 0f, 0f }; // b/c its relative to Shape and we stick to it + private final Vec2f objDraggedFirst = new Vec2f(); // b/c its relative to Shape and we stick to it private final int[] winDraggedLast = { 0, 0 }; // b/c its absolute window pos - private boolean inDrag = false; + private boolean inMove = false; private int inResize = 0; // 1 br, 2 bl private static final float resize_sxy_min = 1f/200f; // 1/2% - TODO: Maybe customizable? private static final float resize_section = 1f/5f; // resize action in a corner @@ -980,7 +1030,7 @@ public abstract class Shape { * @param glWinY in GL window coordinates, origin bottom-left * @param objPos object position of mouse event relative to this shape */ - /* pp */ final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos) { + /* pp */ final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final Vec3f objPos) { final Shape.EventInfo shapeEvent = new EventInfo(glWinX, glWinY, this, objPos); final short eventType = e.getEventType(); @@ -996,7 +1046,7 @@ public abstract class Shape { case MouseEvent.EVENT_MOUSE_RELEASED: // Release active shape: last pointer has been lifted! setPressed(false); - inDrag = false; + inMove = false; inResize = 0; break; } @@ -1005,21 +1055,22 @@ public abstract class Shape { case MouseEvent.EVENT_MOUSE_DRAGGED: { // 1 pointer drag and potential drag-resize if(dragFirst) { - objDraggedFirst[0] = objPos[0]; - objDraggedFirst[1] = objPos[1]; + objDraggedFirst.set(objPos); winDraggedLast[0] = glWinX; winDraggedLast[1] = glWinY; dragFirst=false; - final float ix = objPos[0]; - final float iy = objPos[1]; + final float ix = objPos.x(); + final float iy = objPos.y(); final float minx_br = box.getMaxX() - box.getWidth() * resize_section; final float miny_br = box.getMinY(); final float maxx_br = box.getMaxX(); final float maxy_br = box.getMinY() + box.getHeight() * resize_section; if( minx_br <= ix && ix <= maxx_br && miny_br <= iy && iy <= maxy_br ) { - inResize = 1; // bottom-right + if( interactive && resizable ) { + inResize = 1; // bottom-right + } } else { final float minx_bl = box.getMinX(); final float miny_bl = box.getMinY(); @@ -1027,57 +1078,59 @@ public abstract class Shape { final float maxy_bl = box.getMinY() + box.getHeight() * resize_section; if( minx_bl <= ix && ix <= maxx_bl && miny_bl <= iy && iy <= maxy_bl ) { - inResize = 2; // bottom-left + if( interactive && resizable ) { + inResize = 2; // bottom-left + } } else { - inDrag = true; + inMove = interactive && draggable; } } if( DEBUG ) { - System.err.printf("DragFirst: drag %b, resize %d, obj[%.4f, %.4f, %.4f], drag +[%.4f, %.4f]%n", - inDrag, inResize, objPos[0], objPos[1], objPos[2], shapeEvent.objDrag[0], shapeEvent.objDrag[1]); + System.err.printf("DragFirst: drag %b, resize %d, obj[%s], drag +[%s]%n", + inMove, inResize, objPos, shapeEvent.objDrag); System.err.printf("DragFirst: %s%n", this); } return; } - shapeEvent.objDrag[0] = objPos[0] - objDraggedFirst[0]; - shapeEvent.objDrag[1] = objPos[1] - objDraggedFirst[1]; + shapeEvent.objDrag.set( objPos.x() - objDraggedFirst.x(), + objPos.y() - objDraggedFirst.y() ); shapeEvent.winDrag[0] = glWinX - winDraggedLast[0]; shapeEvent.winDrag[1] = glWinY - winDraggedLast[1]; winDraggedLast[0] = glWinX; winDraggedLast[1] = glWinY; if( 1 == e.getPointerCount() ) { - final float sdx = shapeEvent.objDrag[0] * scale[0]; // apply scale, since operation - final float sdy = shapeEvent.objDrag[1] * scale[1]; // is from a scaled-model-viewpoint - if( 0 != inResize && resizable ) { + final float sdx = shapeEvent.objDrag.x() * scale.x(); // apply scale, since operation + final float sdy = shapeEvent.objDrag.y() * scale.y(); // is from a scaled-model-viewpoint + if( 0 != inResize ) { final float bw = box.getWidth(); final float bh = box.getHeight(); final float sx; if( 1 == inResize ) { - sx = scale[0] + sdx/bw; // bottom-right + sx = scale.x() + sdx/bw; // bottom-right } else { - sx = scale[0] - sdx/bw; // bottom-left + sx = scale.x() - sdx/bw; // bottom-left } - final float sy = scale[1] - sdy/bh; + final float sy = scale.y() - sdy/bh; if( resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip if( DEBUG ) { - System.err.printf("DragZoom: resize %d, win[%4d, %4d], obj[%.4f, %.4f, %.4f], dxy +[%.4f, %.4f], sdxy +[%.4f, %.4f], scale [%.4f, %.4f] -> [%.4f, %.4f]%n", - inResize, glWinX, glWinY, objPos[0], objPos[1], objPos[2], - shapeEvent.objDrag[0], shapeEvent.objDrag[1], sdx, sdy, - scale[0], scale[1], sx, sy); + System.err.printf("DragZoom: resize %d, win[%4d, %4d], obj[%s], dxy +[%s], sdxy +[%.4f, %.4f], scale [%s] -> [%.4f, %.4f]%n", + inResize, glWinX, glWinY, objPos, + shapeEvent.objDrag, sdx, sdy, + scale, sx, sy); } if( 1 == inResize ) { move( 0, sdy, 0f); // bottom-right, sticky left- and top-edge } else { move( sdx, sdy, 0f); // bottom-left, sticky right- and top-edge } - setScale(sx, sy, scale[2]); + setScale(sx, sy, scale.z()); } return; // FIXME: pass through event? Issue zoom event? - } else if( inDrag && draggable ) { + } else if( inMove ) { if( DEBUG ) { - System.err.printf("DragMove: win[%4d, %4d] +[%2d, %2d], obj[%.4f, %.4f, %.4f] +[%.4f, %.4f]%n", + System.err.printf("DragMove: win[%4d, %4d] +[%2d, %2d], obj[%s] +[%s]%n", glWinX, glWinY, shapeEvent.winDrag[0], shapeEvent.winDrag[1], - objPos[0], objPos[1], objPos[2], shapeEvent.objDrag[0], shapeEvent.objDrag[1]); + objPos, shapeEvent.objDrag); } move( sdx, sdy, 0f); // FIXME: Pass through event? Issue move event? @@ -1129,32 +1182,30 @@ public abstract class Shape { * @param viewport the viewport * @param objPos object position of mouse event relative to this shape */ - /* pp */ final void dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final PMVMatrix pmv, final int[] viewport, final float[] objPos) { - if( resizable && e instanceof PinchToZoomGesture.ZoomEvent ) { + /* pp */ final void dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final PMVMatrix pmv, final int[] viewport, final Vec3f objPos) { + if( interactive && resizable && e instanceof PinchToZoomGesture.ZoomEvent ) { final PinchToZoomGesture.ZoomEvent ze = (PinchToZoomGesture.ZoomEvent) e; final float pixels = ze.getDelta() * ze.getScale(); // final int winX2 = glWinX + Math.round(pixels); - final float[] objPos2 = winToShapeCoord(pmv, viewport, winX2, glWinY, new float[3]); + final Vec3f objPos2 = winToShapeCoord(pmv, viewport, winX2, glWinY, new Vec3f()); if( null == objPos2 ) { return; } - final float dx = objPos2[0]; - final float dy = objPos2[1]; - final float sx = scale[0] + ( dx/box.getWidth() ); // bottom-right - final float sy = scale[1] + ( dy/box.getHeight() ); + final float dx = objPos2.x(); + final float dy = objPos2.y(); + final float sx = scale.x() + ( dx/box.getWidth() ); // bottom-right + final float sy = scale.y() + ( dy/box.getHeight() ); if( DEBUG ) { - System.err.printf("DragZoom: resize %b, obj %4d/%4d, %.3f/%.3f/%.3f %.3f/%.3f/%.3f + %.3f/%.3f -> %.3f/%.3f%n", - inResize, glWinX, glWinY, objPos[0], objPos[1], objPos[2], position[0], position[1], position[2], - dx, dy, sx, sy); + System.err.printf("DragZoom: resize %b, win %4d/%4d, obj %s, %s + %.3f/%.3f -> %.3f/%.3f%n", + inResize, glWinX, glWinY, objPos, position, dx, dy, sx, sy); } if( resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip if( DEBUG ) { - System.err.printf("PinchZoom: pixels %f, obj %4d/%4d, %.3f/%.3f/%.3f %.3f/%.3f/%.3f + %.3f/%.3f -> %.3f/%.3f%n", - pixels, glWinX, glWinY, objPos[0], objPos[1], objPos[2], position[0], position[1], position[2], - dx, dy, sx, sy); + System.err.printf("PinchZoom: pixels %f, win %4d/%4d, obj %s, %s + %.3f/%.3f -> %.3f/%.3f%n", + pixels, glWinX, glWinY, objPos, position, dx, dy, sx, sy); } // move(dx, dy, 0f); - setScale(sx, sy, scale[2]); + setScale(sx, sy, scale.z()); } return; // FIXME: pass through event? Issue zoom event? } @@ -1189,8 +1240,8 @@ public abstract class Shape { public static Comparator<Shape> ZAscendingComparator = new Comparator<Shape>() { @Override public int compare(final Shape s1, final Shape s2) { - final float s1Z = s1.getBounds().getMinZ()+s1.getPosition()[2]; - final float s2Z = s2.getBounds().getMinZ()+s2.getPosition()[2]; + final float s1Z = s1.getBounds().getMinZ()+s1.getPosition().z(); + final float s2Z = s2.getBounds().getMinZ()+s2.getPosition().z(); if( FloatUtil.isEqual(s1Z, s2Z, FloatUtil.EPSILON) ) { return 0; } else if( s1Z < s2Z ){ diff --git a/src/graphui/classes/com/jogamp/graph/ui/layout/GridLayout.java b/src/graphui/classes/com/jogamp/graph/ui/layout/GridLayout.java new file mode 100644 index 000000000..bd2fd2d76 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/layout/GridLayout.java @@ -0,0 +1,69 @@ +/** + * Copyright 2010-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 com.jogamp.graph.ui.Group; +import com.jogamp.graph.ui.Shape; +import com.jogamp.graph.ui.Group.Layout; + +public class GridLayout implements Group.Layout { + private final int columns; + private final float padX, padY; + + /** + * + * @param columns [1..inf) + * @param padX + * @param padY + */ + public GridLayout(final int columns, final float padX, final float padY) { + this.columns = Math.max(1, columns); + this.padX = padX; + this.padY = padY; + } + + @Override + public void layout(final Group g) { + int col = 0; + float x=0; + float y=0; + for(final Shape s : g.getShapes()) { + s.moveTo(x, y, 0); + if( col + 1 == columns ) { + col = 0; + // row++; + x = 0; + y -= padY; + } else { + col++; + x += padX; + } + } + } +} + diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java index ca726e30f..e8b0be863 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/Button.java @@ -35,6 +35,8 @@ import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.font.Font; import com.jogamp.graph.geom.plane.AffineTransform; import com.jogamp.graph.ui.GraphShape; +import com.jogamp.opengl.math.Vec2f; +import com.jogamp.opengl.math.Vec3f; import com.jogamp.opengl.math.geom.AABBox; import jogamp.graph.ui.shapes.Label0; @@ -111,11 +113,11 @@ public class Button extends RoundButton { final float lScale = lsx < lsy ? lsx : lsy; // Setting left-corner transform using text-box in font em-size [0..1] - final AABBox lbox1_s = new AABBox(lbox0_em).scale2(lScale, new float[3]); + final AABBox lbox1_s = new AABBox(lbox0_em).scale2(lScale); // Center text .. (share same center w/ button) - final float[] lctr = lbox1_s.getCenter(); - final float[] ctr = box.getCenter(); - final float[] ltxy = new float[] { ctr[0] - lctr[0], ctr[1] - lctr[1] }; + final Vec3f lctr = lbox1_s.getCenter(); + final Vec3f ctr = box.getCenter(); + final Vec2f ltxy = new Vec2f(ctr.x() - lctr.x(), ctr.y() - lctr.y() ); if( DEBUG_DRAW ) { System.err.println("Button: dim "+width+" x "+height+", spacing "+spacingX+", "+spacingY); @@ -124,7 +126,7 @@ public class Button extends RoundButton { System.err.println("Button: text_em "+lbox0_em+" em, "+label.getText()); System.err.println("Button: lscale "+lsx+" x "+lsy+" -> "+lScale); System.err.printf ("Button: text_s %s%n", lbox1_s); - System.err.printf ("Button: ltxy %f / %f, %f / %f%n", ltxy[0], ltxy[1], ltxy[0] * lScale, ltxy[1] * lScale); + System.err.printf ("Button: ltxy %s, %f / %f%n", ltxy, ltxy.x() * lScale, ltxy.y() * lScale); } final AABBox lbox2 = label.addShapeToRegion(lScale, region, ltxy, tempT1, tempT2, tempT3); diff --git a/src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java b/src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java index e7d89ade4..50b53e6f5 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java +++ b/src/graphui/classes/com/jogamp/graph/ui/shapes/Label.java @@ -166,7 +166,7 @@ public class Label extends GraphShape { /** Returns {@link Font#getLineHeight()} * {@link #getFontScale()} * {@link #getScaleY()}. */ public float getScaledLineHeight() { - return getScaleY() * fontScale * font.getLineHeight(); + return getScale().y() * fontScale * font.getLineHeight(); } /** diff --git a/src/graphui/classes/jogamp/graph/ui/TreeTool.java b/src/graphui/classes/jogamp/graph/ui/TreeTool.java new file mode 100644 index 000000000..ff893ab56 --- /dev/null +++ b/src/graphui/classes/jogamp/graph/ui/TreeTool.java @@ -0,0 +1,159 @@ +/** + * 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 jogamp.graph.ui; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import com.jogamp.graph.ui.Container; +import com.jogamp.graph.ui.Scene; +import com.jogamp.graph.ui.Shape; +import com.jogamp.graph.ui.Shape.Visitor1; +import com.jogamp.graph.ui.Shape.Visitor2; +import com.jogamp.opengl.util.PMVMatrix; + +/** Generic static {@link Shape} tree traversal tools, utilized by {@link Scene} and {@link Container} implementations. */ +public class TreeTool { + + /** + * Traverses through the graph up until {@code shape} and apply {@code action} on it. + * @param pmv + * @param shape + * @param action + * @return true to signal operation complete, i.e. {@code shape} found, otherwise false + */ + public static boolean forOne(final List<Shape> shapes, final PMVMatrix pmv, final Shape shape, final Runnable action) { + for(int i=shapes.size()-1; i>=0; i--) { + final Shape s = shapes.get(i); + if( s instanceof Container ) { + final Container c = (Container)s; + if( !c.contains(shape) ) { // fast-path: skip container + continue; + } + pmv.glPushMatrix(); + s.setTransform(pmv); + final boolean res = c.forOne(pmv, shape, action); + pmv.glPopMatrix(); + if( !res ) { throw new InternalError("Not found "+shape+" in "+c+", but contained"); } + return true; + } else { + if( s.equals(shape) ) { + pmv.glPushMatrix(); + s.setTransform(pmv); + action.run(); + pmv.glPopMatrix(); + return true; + } + } + } + return false; + } + + /** + * Traverses through the graph and apply {@link Visitor1#visit(Shape)} for each, stop if it returns true. + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor1#visit(Shape)} returned true, otherwise false + */ + public static boolean forAll(final List<Shape> shapes, final Visitor1 v) { + for(int i=shapes.size()-1; i>=0; i--) { + final Shape s = shapes.get(i); + boolean res; + if( s instanceof Container ) { + final Container c = (Container)s; + res = c.forAll(v); + } else { + res = v.visit(s); + } + if( res ) { + return true; + } + } + return false; + } + + /** + * Traverses through the graph and apply {@link Visitor2#visit(Shape, PMVMatrix)} for each, stop if it returns true. + * @param pmv + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor2#visit(Shape, PMVMatrix)} returned true, otherwise false + */ + public static boolean forAll(final List<Shape> shapes, final PMVMatrix pmv, final Visitor2 v) { + for(int i=shapes.size()-1; i>=0; i--) { + final Shape s = shapes.get(i); + pmv.glPushMatrix(); + s.setTransform(pmv); + boolean res; + if( s instanceof Container ) { + final Container c = (Container)s; + res = c.forAll(pmv, v); + } else { + res = v.visit(s, pmv); + } + pmv.glPopMatrix(); + if( res ) { + return true; + } + } + return false; + } + + /** + * Traverses through the graph and apply {@link Visitor#visit(Shape, PMVMatrix)} for each, stop if it returns true. + * + * Each {@link Container} level is sorted using {@code sortComp} + * @param sortComp + * @param pmv + * @param v + * @return true to signal operation complete and to stop traversal, i.e. {@link Visitor2#visit(Shape, PMVMatrix)} returned true, otherwise false + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static boolean forSortedAll(final Comparator<Shape> sortComp, final List<Shape> shapes, final PMVMatrix pmv, final Visitor2 v) { + final Object[] shapesS = shapes.toArray(); + Arrays.sort(shapesS, (Comparator)sortComp); + + for(int i=shapesS.length-1; i>=0; i--) { + final Shape s = (Shape)shapesS[i]; + pmv.glPushMatrix(); + s.setTransform(pmv); + boolean res; + if( s instanceof Container ) { + final Container c = (Container)s; + res = c.forAll(pmv, v); + } else { + res = v.visit(s, pmv); + } + pmv.glPopMatrix(); + if( res ) { + return true; + } + } + return false; + } + +} diff --git a/src/graphui/classes/jogamp/graph/ui/shapes/Label0.java b/src/graphui/classes/jogamp/graph/ui/shapes/Label0.java index fe5f7f8e5..a091a89ed 100644 --- a/src/graphui/classes/jogamp/graph/ui/shapes/Label0.java +++ b/src/graphui/classes/jogamp/graph/ui/shapes/Label0.java @@ -31,6 +31,7 @@ import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.TextRegionUtil; import com.jogamp.graph.font.Font; import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.opengl.math.Vec2f; import com.jogamp.opengl.math.geom.AABBox; public class Label0 { @@ -65,10 +66,10 @@ public class Label0 { this.font = font; } - public final AABBox addShapeToRegion(final float scale, final Region region, final float[] txy, + public final AABBox addShapeToRegion(final float scale, final Region region, final Vec2f txy, final AffineTransform tmp1, final AffineTransform tmp2, final AffineTransform tmp3) { - tmp1.setToTranslation(txy[0], txy[1]); + tmp1.setToTranslation(txy.x(), txy.y()); tmp1.scale(scale, scale, tmp2); return TextRegionUtil.addStringToRegion(region, font, tmp1, text, rgbaColor, tmp2, tmp3); } |