diff options
Diffstat (limited to 'src/graphui/classes/com')
11 files changed, 2590 insertions, 0 deletions
diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java new file mode 100644 index 000000000..a87dd9a23 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java @@ -0,0 +1,636 @@ +/** + * 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.gl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +import com.jogamp.opengl.FPSCounter; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLCapabilitiesImmutable; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLRunnable; +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.newt.event.GestureHandler; +import com.jogamp.newt.event.InputEvent; +import com.jogamp.newt.event.MouseEvent; +import com.jogamp.newt.event.MouseListener; +import com.jogamp.newt.event.PinchToZoomGesture; +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.geom.AABBox; +import com.jogamp.opengl.util.PMVMatrix; + +/** + * GraphUI Scene + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * GraphUI is intended to become an immediate- and retained-mode API. + * </p> + * @see Shape + */ +public class Scene implements GLEventListener{ + private final ArrayList<Shape> shapes = new ArrayList<Shape>(); + + private final float sceneDist, zNear, zFar; + + private RegionRenderer renderer; + + private final int[] sampleCount = new int[1]; + + /** Describing the bounding box in model-coordinates of the near-plane parallel at distance one. */ + private final AABBox nearPlane1Box = new AABBox(); + private final int[] viewport = new int[] { 0, 0, 0, 0 }; + private final float[] sceneScale = new float[3]; + private final float[] scenePlaneOrigin = new float[3]; + + + private volatile Shape activeShape = null; + + private SBCMouseListener sbcMouseListener = null; + private SBCGestureListener sbcGestureListener = null; + private PinchToZoomGesture pinchToZoomGesture = null; + + private GLAutoDrawable cDrawable = null; + + public Scene(final float sceneDist, final float zNear, final float zFar) { + this(null, sceneDist, zNear, zFar); + } + + public Scene(final RegionRenderer renderer, final float sceneDist, final float zNear, final float zFar) { + this.renderer = renderer; + this.sceneDist = sceneDist; + this.zFar = zFar; + this.zNear = zNear; + this.sampleCount[0] = 4; + } + + /** Returns the associated RegionRenderer */ + public RegionRenderer getRenderer() { return renderer; } + /** Sets the associated RegionRenderer, may set to null to avoid its destruction when {@link #dispose(GLAutoDrawable)} this instance. */ + public void setRenderer(final RegionRenderer renderer) { + this.renderer = renderer; + } + /** Returns the associated RegionRenderer's RenderState, may be null. */ + public RenderState getRenderState() { + if( null != renderer ) { + return renderer.getRenderState(); + } + return null; + } + public final Vertex.Factory<? extends Vertex> getVertexFactory() { + if( null != renderer ) { + return renderer.getRenderState().getVertexFactory(); + } + return null; + } + + public void attachInputListenerTo(final GLWindow window) { + if(null == sbcMouseListener) { + sbcMouseListener = new SBCMouseListener(); + window.addMouseListener(sbcMouseListener); + sbcGestureListener = new SBCGestureListener(); + window.addGestureListener(sbcGestureListener); + pinchToZoomGesture = new PinchToZoomGesture(window.getNativeSurface(), false); + window.addGestureHandler(pinchToZoomGesture); + } + } + + public void detachInputListenerFrom(final GLWindow window) { + if(null != sbcMouseListener) { + window.removeMouseListener(sbcMouseListener); + sbcMouseListener = null; + window.removeGestureListener(sbcGestureListener); + sbcGestureListener = null; + window.removeGestureHandler(pinchToZoomGesture); + pinchToZoomGesture = null; + } + } + + public ArrayList<Shape> getShapes() { + return shapes; + } + public void addShape(final Shape b) { + shapes.add(b); + } + public void removeShape(final Shape b) { + shapes.remove(b); + } + public final Shape getShapeByIdx(final int id) { + if( 0 > id ) { + return null; + } + return shapes.get(id); + } + public Shape getShapeByName(final int name) { + for(final Shape b : shapes) { + if(b.getName() == name ) { + return b; + } + } + return null; + } + + public int getSampleCount() { return sampleCount[0]; } + public int setSampleCount(final int v) { + sampleCount[0] = Math.min(8, Math.max(v, 1)); // clip + markAllShapesDirty(); + return sampleCount[0]; + } + + public void setAllShapesQuality(final int q) { + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).setQuality(q); + } + } + public void setAllShapesSharpness(final float sharpness) { + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).setSharpness(sharpness); + } + } + public void markAllShapesDirty() { + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).markShapeDirty(); + } + } + + public final float[] getSceneScale() { return sceneScale; } + public final float[] getScenePlaneOrigin() { return scenePlaneOrigin; } + + @Override + public void init(final GLAutoDrawable drawable) { + System.err.println("SceneUIController: init"); + cDrawable = drawable; + if( null != renderer ) { + renderer.init(drawable.getGL().getGL2ES2()); + } + } + + private static Comparator<Shape> shapeZAscComparator = new Comparator<Shape>() { + @Override + public int compare(final Shape s1, final Shape s2) { + final float s1Z = s1.getBounds().getMinZ()+s1.getTranslate()[2]; + final float s2Z = s2.getBounds().getMinZ()+s2.getTranslate()[2]; + if( FloatUtil.isEqual(s1Z, s2Z, FloatUtil.EPSILON) ) { + return 0; + } else if( s1Z < s2Z ){ + return -1; + } else { + return 1; + } + } }; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void display(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + + gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + + final Object[] shapesS = shapes.toArray(); + Arrays.sort(shapesS, (Comparator)shapeZAscComparator); + + renderer.enable(gl, true); + + //final int shapeCount = shapes.size(); + final int shapeCount = shapesS.length; + for(int i=0; i<shapeCount; i++) { + // final UIShape uiShape = shapes.get(i); + final Shape uiShape = (Shape)shapesS[i]; + // System.err.println("Id "+i+": "+uiShape); + if( uiShape.isEnabled() ) { + uiShape.validate(gl, renderer); + pmv.glPushMatrix(); + uiShape.setTransform(pmv); + uiShape.drawShape(gl, renderer, sampleCount); + pmv.glPopMatrix(); + } + } + + renderer.enable(gl, false); + } + + public void pickShape(final int glWinX, final int glWinY, final float[] objPos, final Shape[] shape, final Runnable runnable) { + if( null == cDrawable ) { + return; + } + cDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + shape[0] = pickShapeImpl(glWinX, glWinY, objPos); + if( null != shape[0] ) { + runnable.run(); + } + return true; + } } ); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Shape pickShapeImpl(final int glWinX, final int glWinY, final float[] objPos) { + final float winZ0 = 0f; + final float winZ1 = 0.3f; + /** + final FloatBuffer winZRB = Buffers.newDirectFloatBuffer(1); + gl.glReadPixels( x, y, 1, 1, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, winZRB); + winZ1 = winZRB.get(0); // dir + */ + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + + final Ray ray = new Ray(); + + final Object[] shapesS = shapes.toArray(); + Arrays.sort(shapesS, (Comparator)shapeZAscComparator); + + 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, viewport, 0, ray); + pmv.glPopMatrix(); + 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; + } + } + } + } + return null; + } + private final float[] dpyTmp1V3 = new float[3]; + private final float[] dpyTmp2V3 = new float[3]; + private final float[] dpyTmp3V3 = new float[3]; + + /** + * Calling {@link Shape#winToObjCoord(RegionRenderer, int, int, float[])}, retrieving its object position. + * @param activeShape + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos resulting object position + * @param runnable action + */ + public void windowToShapeCoords(final Shape activeShape, final int glWinX, final int glWinY, final float[] objPos, final Runnable runnable) { + if( null == cDrawable || null == activeShape ) { + return; + } + cDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + final boolean ok; + { + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glPushMatrix(); + activeShape.setTransform(pmv); + ok = activeShape.winToObjCoord(renderer, glWinX, glWinY, objPos); + pmv.glPopMatrix(); + } + if( ok ) { + runnable.run(); + } + return true; + } } ); + } + + /** + * Disposes all {@link #addShape(Shape) added} {@link Shape}s. + * <p> + * Implementation also issues {@link RegionRenderer#destroy(GL2ES2)} if set + * and {@link #detachInputListenerFrom(GLWindow)} in case the drawable is of type {@link GLWindow}. + * </p> + * <p> + * {@inheritDoc} + * </p> + */ + @Override + public void dispose(final GLAutoDrawable drawable) { + System.err.println("SceneUIController: dispose"); + if( drawable instanceof GLWindow ) { + final GLWindow glw = (GLWindow) drawable; + detachInputListenerFrom(glw); + } + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).destroy(gl, renderer); + } + shapes.clear(); + cDrawable = null; + if( null != renderer ) { + renderer.destroy(gl); + } + } + + public static void mapWin2ObjectCoords(final PMVMatrix pmv, final int[] view, + final float zNear, final float zFar, + final float orthoX, final float orthoY, final float orthoDist, + final float[] winZ, final float[] objPos) { + winZ[0] = FloatUtil.getOrthoWinZ(orthoDist, zNear, zFar); + pmv.gluUnProject(orthoX, orthoY, winZ[0], view, 0, objPos, 0); + } + + @Override + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { + viewport[0] = x; + viewport[1] = y; + viewport[2] = width; + viewport[3] = height; + + final PMVMatrix pmv = renderer.getMatrix(); + renderer.reshapePerspective(45.0f, width, height, zNear, zFar); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glLoadIdentity(); + + System.err.printf("Reshape: zNear %f, zFar %f%n", zNear, zFar); + System.err.printf("Reshape: Frustum: %s%n", pmv.glGetFrustum()); + { + final float orthoDist = 1f; + final float[] obj00Coord = new float[3]; + final float[] obj11Coord = new float[3]; + final float[] winZ = new float[1]; + + mapWin2ObjectCoords(pmv, viewport, zNear, zFar, 0f, 0f, orthoDist, winZ, obj00Coord); + System.err.printf("Reshape: mapped.00: [%f, %f, %f], winZ %f -> [%f, %f, %f]%n", 0f, 0f, orthoDist, winZ[0], obj00Coord[0], obj00Coord[1], obj00Coord[2]); + + mapWin2ObjectCoords(pmv, viewport, zNear, zFar, width, height, orthoDist, winZ, obj11Coord); + System.err.printf("Reshape: mapped.11: [%f, %f, %f], winZ %f -> [%f, %f, %f]%n", (float)width, (float)height, orthoDist, winZ[0], obj11Coord[0], obj11Coord[1], obj11Coord[2]); + + nearPlane1Box.setSize( obj00Coord[0], // lx + obj00Coord[1], // ly + obj00Coord[2], // lz + obj11Coord[0], // hx + obj11Coord[1], // hy + obj11Coord[2] );// hz + System.err.printf("Reshape: dist1Box: %s%n", nearPlane1Box); + } + scenePlaneOrigin[0] = nearPlane1Box.getMinX() * sceneDist; + scenePlaneOrigin[1] = nearPlane1Box.getMinY() * sceneDist; + scenePlaneOrigin[2] = nearPlane1Box.getMinZ() * sceneDist; + sceneScale[0] = ( nearPlane1Box.getWidth() * sceneDist ) / width; + sceneScale[1] = ( nearPlane1Box.getHeight() * sceneDist ) / height; + sceneScale[2] = 1f; + System.err.printf("Scene Origin [%f, %f, %f]%n", scenePlaneOrigin[0], scenePlaneOrigin[1], scenePlaneOrigin[2]); + System.err.printf("Scene Scale %f * [%f x %f] / [%d x %d] = [%f, %f, %f]%n", + sceneDist, nearPlane1Box.getWidth(), nearPlane1Box.getHeight(), + width, height, + sceneScale[0], sceneScale[1], sceneScale[2]); + + pmv.glTranslatef(scenePlaneOrigin[0], scenePlaneOrigin[1], scenePlaneOrigin[2]); + pmv.glScalef(sceneScale[0], sceneScale[1], sceneScale[2]); + } + + public final Shape getActiveShape() { + return activeShape; + } + + public void release() { + setActiveShape(null); + } + private void setActiveShape(final Shape shape) { + activeShape = shape; + } + + private final class SBCGestureListener implements GestureHandler.GestureListener { + @Override + public void gestureDetected(final GestureEvent gh) { + if( null != activeShape ) { + // 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 = viewport[3] - e.getY() - 1; + final float[] objPos = new float[3]; + final Shape shape = activeShape; + windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() { + @Override + public void run() { + shape.dispatchGestureEvent(gh, glWinX, glWinY, objPos); + } } ); + } + } + } + } + + /** + * Dispatch mouse event, either directly sending to activeShape or picking one + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + */ + final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY) { + if( null == activeShape ) { + dispatchMouseEventPickShape(e, glWinX, glWinY, true); + } else { + dispatchMouseEventForShape(activeShape, e, glWinX, glWinY); + } + } + /** + * Pick the shape using the event coordinates + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param setActive + */ + final void dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY, final boolean setActive) { + final float[] objPos = new float[3]; + final Shape[] shape = { null }; + pickShape(glWinX, glWinY, objPos, shape, new Runnable() { + @Override + public void run() { + if( setActive ) { + setActiveShape(shape[0]); + } + shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos); + } } ); + } + /** + * Dispatch event to shape + * @param shape target active shape of event + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + */ + final void dispatchMouseEventForShape(final Shape shape, final MouseEvent e, final int glWinX, final int glWinY) { + final float[] objPos = new float[3]; + windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() { + @Override + public void run() { + shape.dispatchMouseEvent(e, glWinX, glWinY, objPos); + } } ); + } + + private class SBCMouseListener implements MouseListener { + int lx=-1, ly=-1, lId=-1; + + void clear() { + lx = -1; ly = -1; lId = -1; + } + + @Override + public void mousePressed(final MouseEvent e) { + if( -1 == lId || e.getPointerId(0) == lId ) { + lx = e.getX(); + ly = e.getY(); + lId = e.getPointerId(0); + } + // flip to GL window coordinates, origin bottom-left + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + dispatchMouseEvent(e, glWinX, glWinY); + } + + @Override + public void mouseReleased(final MouseEvent e) { + // flip to GL window coordinates, origin bottom-left + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + dispatchMouseEvent(e, glWinX, glWinY); + if( 1 == e.getPointerCount() ) { + // Release active shape: last pointer has been lifted! + release(); + clear(); + } + } + + @Override + public void mouseClicked(final MouseEvent e) { + // flip to GL window coordinates + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + // activeId should have been released by mouseRelease() already! + dispatchMouseEventPickShape(e, glWinX, glWinY, false); + // Release active shape: last pointer has been lifted! + release(); + clear(); + } + + @Override + public void mouseDragged(final MouseEvent e) { + // drag activeShape, if no gesture-activity, only on 1st pointer + if( null != activeShape && !pinchToZoomGesture.isWithinGesture() && e.getPointerId(0) == lId ) { + lx = e.getX(); + ly = e.getY(); + + // dragged .. delegate to active shape! + // flip to GL window coordinates, origin bottom-left + final int glWinX = lx; + final int glWinY = viewport[3] - ly - 1; + dispatchMouseEventForShape(activeShape, e, glWinX, glWinY); + } + } + + @Override + public void mouseWheelMoved(final MouseEvent e) { + // flip to GL window coordinates + final int glWinX = lx; + final int glWinY = viewport[3] - ly - 1; + dispatchMouseEventPickShape(e, glWinX, glWinY, true); + } + + @Override + public void mouseMoved(final MouseEvent e) { + if( -1 == lId || e.getPointerId(0) == lId ) { + lx = e.getX(); + ly = e.getY(); + lId = e.getPointerId(0); + } + } + @Override + public void mouseEntered(final MouseEvent e) { } + @Override + public void mouseExited(final MouseEvent e) { + release(); + clear(); + } + } + + /** + * Return a formatted status string containing avg fps and avg frame duration. + * @param glad GLAutoDrawable instance for FPSCounter, its chosen GLCapabilities and its GL's swap-interval + * @param renderModes render modes for {@link Region#getRenderModeString(int)} + * @param quality the Graph-Curve quality setting + * @param dpi the monitor's DPI (vertical preferred) + * @return formatted status string + */ + public String getStatusText(final GLAutoDrawable glad, final int renderModes, final int quality, final float dpi) { + final FPSCounter fpsCounter = glad.getAnimator(); + final float lfps, tfps, td; + if( null != fpsCounter ) { + lfps = fpsCounter.getLastFPS(); + tfps = fpsCounter.getTotalFPS(); + td = (float)fpsCounter.getLastFPSPeriod() / (float)fpsCounter.getUpdateFPSFrames(); + } else { + lfps = 0f; + tfps = 0f; + td = 0f; + } + final String modeS = Region.getRenderModeString(renderModes); + final GLCapabilitiesImmutable caps = glad.getChosenGLCapabilities(); + return String.format("%03.1f/%03.1f fps, %.1f ms/f, v-sync %d, dpi %.1f, %s-samples %d, q %d, msaa %d, blend %b, alpha %d", + lfps, tfps, td, glad.getGL().getSwapInterval(), dpi, modeS, getSampleCount(), quality, + caps.getNumSamples(), + getRenderState().isHintMaskSet(RenderState.BITHINT_BLENDING_ENABLED), + caps.getAlphaBits()); + } + + /** + * Return a formatted status string containing avg fps and avg frame duration. + * @param fpsCounter the counter, must not be null + * @return formatted status string + */ + public static String getStatusText(final FPSCounter fpsCounter) { + final float lfps = fpsCounter.getLastFPS(); + final float tfps = fpsCounter.getTotalFPS(); + final float td = (float)fpsCounter.getLastFPSPeriod() / (float)fpsCounter.getUpdateFPSFrames(); + return String.format("%03.1f/%03.1f fps, %.1f ms/f", lfps, tfps, td); + } + +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java new file mode 100644 index 000000000..97439c24c --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/Shape.java @@ -0,0 +1,792 @@ +/** + * 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.gl; + +import java.util.ArrayList; + +import com.jogamp.nativewindow.NativeWindowException; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.newt.event.GestureHandler.GestureEvent; +import com.jogamp.newt.event.GestureHandler.GestureListener; +import com.jogamp.newt.event.MouseAdapter; +import com.jogamp.newt.event.NEWTEvent; +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.geom.AABBox; +import com.jogamp.opengl.util.PMVMatrix; + +/** + * GraphUI Shape + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * GraphUI is intended to become an immediate- and retained-mode API. + * </p> + * @see Scene + */ +public abstract class Shape { + public static final boolean DRAW_DEBUG_BOX = false; + private static final boolean DEBUG = false; + + protected static final int DIRTY_SHAPE = 1 << 0 ; + protected static final int DIRTY_STATE = 1 << 1 ; + + private final Factory<? extends Vertex> vertexFactory; + private final int renderModes; + protected final AABBox box; + + protected final AffineTransform tempT1 = new AffineTransform(); + protected final AffineTransform tempT2 = new AffineTransform(); + protected final AffineTransform tempT3 = new AffineTransform(); + protected final AffineTransform tempT4 = new AffineTransform(); + + protected final float[] translate = new float[] { 0f, 0f, 0f }; + protected final Quaternion rotation = new Quaternion(); + protected final float[] rotOrigin = new float[] { 0f, 0f, 0f }; + protected final float[] scale = new float[] { 1f, 1f, 1f }; + + protected GLRegion region = null; + protected int regionQuality = Region.MAX_QUALITY; + + protected int dirty = DIRTY_SHAPE | DIRTY_STATE; + protected float shapesSharpness = OutlineShape.DEFAULT_SHARPNESS; + + /** Default base-color w/o color channel, will be modulated w/ pressed- and toggle color */ + protected final float[] rgbaColor = {0.75f, 0.75f, 0.75f, 1.0f}; + /** Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 */ + protected final float[] pressedRGBAModulate = {1.2f, 1.2f, 1.2f, 0.7f}; + /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 1.13 ~ 0.85 */ + protected final float[] toggleOnRGBAModulate = {1.13f, 1.13f, 1.13f, 1.0f}; + /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 0.86 ~ 0.65 */ + protected final float[] toggleOffRGBAModulate = {0.86f, 0.86f, 0.86f, 1.0f}; + + private int name = -1; + + private boolean down = false; + private boolean toggle = false; + private boolean toggleable = false; + private boolean draggable = true; + private boolean enabled = true; + private ArrayList<MouseGestureListener> mouseListeners = new ArrayList<MouseGestureListener>(); + + public Shape(final Factory<? extends Vertex> factory, final int renderModes) { + this.vertexFactory = factory; + this.renderModes = renderModes; + this.box = new AABBox(); + } + + /** Set a symbolic name for this shape for identification. Default is -1 for noname. */ + public void setName(final int name) { this.name = name; } + /** Return the optional symbolic name for this shape. */ + public int getName() { return this.name; } + + public final Vertex.Factory<? extends Vertex> getVertexFactory() { return vertexFactory; } + + /** Returns true if this shape is enabled and hence visible, otherwise false. */ + public boolean isEnabled() { return enabled; } + /** Enable or disable this shape, i.e. its visibility. */ + public void setEnabled(final boolean v) { enabled = v; } + + /** + * Clears all data and reset all states as if this instance was newly created + * @param gl TODO + * @param renderer TODO\ + */ + public void clear(final GL2ES2 gl, final RegionRenderer renderer) { + clearImpl(gl, renderer); + translate[0] = 0f; + translate[1] = 0f; + translate[2] = 0f; + rotation.setIdentity(); + rotOrigin[0] = 0f; + rotOrigin[1] = 0f; + rotOrigin[2] = 0f; + scale[0] = 1f; + scale[1] = 1f; + scale[2] = 1f; + box.reset(); + markShapeDirty(); + } + + /** + * Destroys all data + * @param gl + * @param renderer + */ + public void destroy(final GL2ES2 gl, final RegionRenderer renderer) { + destroyImpl(gl, renderer); + translate[0] = 0f; + translate[1] = 0f; + translate[2] = 0f; + rotation.setIdentity(); + rotOrigin[0] = 0f; + rotOrigin[1] = 0f; + rotOrigin[2] = 0f; + scale[0] = 1f; + scale[1] = 1f; + scale[2] = 1f; + box.reset(); + markShapeDirty(); + } + + public void setTranslate(final float tx, final float ty, final float tz) { + translate[0] = tx; + translate[1] = ty; + translate[2] = tz; + // System.err.println("UIShape.setTranslate: "+tx+"/"+ty+"/"+tz+": "+toString()); + } + public void translate(final float tx, final float ty, final float tz) { + translate[0] += tx; + translate[1] += ty; + translate[2] += tz; + // System.err.println("UIShape.translate: "+tx+"/"+ty+"/"+tz+": "+toString()); + } + public final float[] getTranslate() { return translate; } + public final Quaternion getRotation() { return rotation; } + public final float[] getRotationOrigin() { return rotOrigin; } + public void setRotationOrigin(final float rx, final float ry, final float rz) { + rotOrigin[0] = rx; + rotOrigin[1] = ry; + rotOrigin[2] = rz; + } + public void setScale(final float sx, final float sy, final float sz) { + scale[0] = sx; + scale[1] = sy; + scale[2] = sz; + } + public void scale(final float sx, final float sy, final float sz) { + scale[0] *= sx; + scale[1] *= sy; + scale[2] *= sz; + } + public final float[] getScale() { return scale; } + + public final void markShapeDirty() { + dirty |= DIRTY_SHAPE; + } + public final boolean isShapeDirty() { + return 0 != ( dirty & DIRTY_SHAPE ) ; + } + public final void markStateDirty() { + dirty |= DIRTY_STATE; + } + public final boolean isStateDirty() { + return 0 != ( dirty & DIRTY_STATE ) ; + } + + public final AABBox getBounds() { return box; } + + public final int getRenderModes() { return renderModes; } + + public GLRegion getRegion(final GL2ES2 gl, final RegionRenderer renderer) { + validate(gl, renderer); + return region; + } + + /** + * Renders {@link OutlineShape} using local {@link GLRegion} which might be cached or updated. + * <p> + * No matrix operations (translate, scale, ..) are performed. + * </p> + * @param gl + * @param renderer + * @param sampleCount + */ + public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + final float r, g, b, a; + final boolean isPressed = isPressed(), isToggleOn = isToggleOn(); + final boolean modBaseColor = !Region.hasColorChannel( renderModes ) && !Region.hasColorTexture( renderModes ); + if( modBaseColor ) { + if( isPressed ) { + r = rgbaColor[0]*pressedRGBAModulate[0]; + g = rgbaColor[1]*pressedRGBAModulate[1]; + b = rgbaColor[2]*pressedRGBAModulate[2]; + a = rgbaColor[3]*pressedRGBAModulate[3]; + } else if( isToggleable() ) { + if( isToggleOn ) { + r = rgbaColor[0]*toggleOnRGBAModulate[0]; + g = rgbaColor[1]*toggleOnRGBAModulate[1]; + b = rgbaColor[2]*toggleOnRGBAModulate[2]; + a = rgbaColor[3]*toggleOnRGBAModulate[3]; + } else { + r = rgbaColor[0]*toggleOffRGBAModulate[0]; + g = rgbaColor[1]*toggleOffRGBAModulate[1]; + b = rgbaColor[2]*toggleOffRGBAModulate[2]; + a = rgbaColor[3]*toggleOffRGBAModulate[3]; + } + } else { + r = rgbaColor[0]; + g = rgbaColor[1]; + b = rgbaColor[2]; + a = rgbaColor[3]; + } + } else { + if( isPressed ) { + r = pressedRGBAModulate[0]; + g = pressedRGBAModulate[1]; + b = pressedRGBAModulate[2]; + a = pressedRGBAModulate[3]; + } else if( isToggleable() ) { + if( isToggleOn ) { + r = toggleOnRGBAModulate[0]; + g = toggleOnRGBAModulate[1]; + b = toggleOnRGBAModulate[2]; + a = toggleOnRGBAModulate[3]; + } else { + r = toggleOffRGBAModulate[0]; + g = toggleOffRGBAModulate[1]; + b = toggleOffRGBAModulate[2]; + a = toggleOffRGBAModulate[3]; + } + } else { + r = rgbaColor[0]; + g = rgbaColor[1]; + b = rgbaColor[2]; + a = rgbaColor[3]; + } + } + renderer.getRenderState().setColorStatic(r, g, b, a); + getRegion(gl, renderer).draw(gl, renderer, sampleCount); + } + + protected GLRegion createGLRegion(final GLProfile glp) { + return GLRegion.create(glp, renderModes, null); + } + + /** + * Validates the shape's underlying {@link GLRegion}. + * + * @param gl + * @param renderer + */ + public final void validate(final GL2ES2 gl, final RegionRenderer renderer) { + if( isShapeDirty() || null == region ) { + box.reset(); + if( null == region ) { + region = createGLRegion(gl.getGLProfile()); + } else { + region.clear(gl); + } + addShapeToRegion(gl, renderer); + if( DRAW_DEBUG_BOX ) { + region.clear(gl); + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + shape.setSharpness(shapesSharpness); + shape.setIsQuadraticNurbs(); + region.addOutlineShape(shape, null, rgbaColor); + } + region.setQuality(regionQuality); + dirty &= ~(DIRTY_SHAPE|DIRTY_STATE); + } else if( isStateDirty() ) { + region.markStateDirty(); + dirty &= ~DIRTY_STATE; + } + } + + /** + * Setup the pre-selected {@link GLMatrixFunc#GL_MODELVIEW} {@link PMVMatrix} for this object. + * @param pmv the matrix + */ + public void setTransform(final PMVMatrix pmv) { + final float[] uiTranslate = getTranslate(); + pmv.glTranslatef(uiTranslate[0], uiTranslate[1], uiTranslate[2]); + + final Quaternion quat = getRotation(); + final boolean rotate = !quat.isIdentity(); + final float[] uiScale = getScale(); + final boolean scale = !VectorUtil.isVec3Equal(uiScale, 0, VectorUtil.VEC3_ONE, 0, FloatUtil.EPSILON); + if( rotate || scale ) { + final float[] rotOrigin = getRotationOrigin(); + final boolean pivot = !VectorUtil.isVec3Zero(rotOrigin, 0, FloatUtil.EPSILON); + if( pivot ) { + pmv.glTranslatef(rotOrigin[0], rotOrigin[1], rotOrigin[2]); + } + if( scale ) { + pmv.glScalef(uiScale[0], uiScale[1], uiScale[2]); + } + if( rotate ) { + pmv.glRotate(quat); + } + if( pivot ) { + pmv.glTranslatef(-rotOrigin[0], -rotOrigin[1], -rotOrigin[2]); + } + } + } + + /** + * Retrieve window surface size of this shape + * <p> + * The {@link RegionRenderer#getMatrix()} has to be setup properly for this object, + * i.e. reshape for {@link GLMatrixFunc#GL_PROJECTION} and {@link #setTransform(PMVMatrix)} for {@link GLMatrixFunc#GL_MODELVIEW}. + * </p> + * @param renderer source of viewport and {@link PMVMatrix} + * @param surfaceSize target surface size + * @return true for successful gluProject(..) operation, otherwise false + */ + public boolean getSurfaceSize(final RegionRenderer renderer, final int[/*2*/] surfaceSize) { + boolean res = false; + final int[/*4*/] viewport = renderer.getViewport(new int[4]); + // System.err.println("UIShape::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 PMVMatrix pmv = renderer.getMatrix(); + + if( pmv.gluProject(high[0], high[1], high[2], viewport, 0, winCoordHigh, 0) ) { + // System.err.printf("UIShape::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) ) { + // System.err.printf("UIShape::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]); + // System.err.printf("UIShape::surfaceSize.S: shape %d: %f x %f -> %d x %d%n", getName(), winCoordHigh[0] - winCoordLow[0], winCoordHigh[1] - winCoordLow[1], surfaceSize[0], surfaceSize[1]); + res = true; + } + } + return res; + } + + /** + * Map given object coordinate relative to this shape to window coordinates + * <p> + * The {@link RegionRenderer#getMatrix()} has to be setup properly for this object, + * i.e. reshape for {@link GLMatrixFunc#GL_PROJECTION} and {@link #setTransform(PMVMatrix)} for {@link GLMatrixFunc#GL_MODELVIEW}. + * </p> + * @param renderer source of viewport and {@link PMVMatrix} + * @param objPos object position relative to this shape's center + * @param glWinPos target window position of objPos relative to this shape + * @return true for successful gluProject(..) operation, otherwise false + */ + public boolean objToWinCoord(final RegionRenderer renderer, final float[/*3*/] objPos, final int[/*2*/] glWinPos) { + boolean res = false; + final int[/*4*/] viewport = renderer.getViewport(new int[4]); + // System.err.println("UIShape::objToWinCoordgetSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); + final float[] winCoord = new float[3]; + final PMVMatrix pmv = renderer.getMatrix(); + + if( pmv.gluProject(objPos[0], objPos[1], objPos[2], viewport, 0, winCoord, 0) ) { + // System.err.printf("UIShape::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]); + // System.err.printf("UIShape::objToWinCoord.X: shape %d: %f / %f -> %d / %d%n", getName(), winCoord[0], winCoord[1], glWinPos[0], glWinPos[1]); + res = true; + } + return res; + } + + /** + * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. + * <p> + * The {@link RegionRenderer#getMatrix()} has to be setup properly for this object, + * i.e. reshape for {@link GLMatrixFunc#GL_PROJECTION} and {@link #setTransform(PMVMatrix)} for {@link GLMatrixFunc#GL_MODELVIEW}. + * </p> + * @param renderer source of viewport and {@link PMVMatrix} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos target object position of glWinX/glWinY relative to this shape + * @return @return true for successful gluProject(..) and gluUnProject(..) operations, otherwise false + */ + public boolean winToObjCoord(final RegionRenderer renderer, final int glWinX, final int glWinY, final float[/*3*/] objPos) { + boolean res = false; + final float[] ctr = getBounds().getCenter(); + final int[] viewport = renderer.getViewport(new int[4]); + final float[] tmp = new float[3]; + final PMVMatrix pmv = renderer.getMatrix(); + + if( pmv.gluProject(ctr[0], ctr[1], ctr[2], viewport, 0, tmp, 0) ) { + // System.err.printf("UIShape::winToObjCoord.0: shape %d: obj [%f, %f, %f] -> win [%f, %f, %f]%n", getName(), ctr[0], ctr[1], ctr[2], tmp[0], tmp[1], tmp[2]); + if( pmv.gluUnProject(glWinX, glWinY, tmp[2], viewport, 0, objPos, 0) ) { + // System.err.printf("UIShape::winToObjCoord.1: shape %d: win [%d, %d, %f] -> obj [%f, %f, %f]%n", getName(), glWinX, glWinY, tmp[2], objPos[0], objPos[1], objPos[2]); + res = true; + } + } + return res; + } + + public float[] getColor() { + return rgbaColor; + } + + public final int getQuality() { return regionQuality; } + public final void setQuality(final int q) { + this.regionQuality = q; + if( null != region ) { + region.setQuality(q); + } + } + public final void setSharpness(final float sharpness) { + this.shapesSharpness = sharpness; + markShapeDirty(); + } + public final float getSharpness() { + return shapesSharpness; + } + + /** + * Set base color. + * <p> + * Default base-color w/o color channel, will be modulated w/ pressed- and toggle color + * </p> + */ + public final void setColor(final float r, final float g, final float b, final float a) { + this.rgbaColor[0] = r; + this.rgbaColor[1] = g; + this.rgbaColor[2] = b; + this.rgbaColor[3] = a; + } + + /** + * Set pressed color. + * <p> + * Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 + * </p> + */ + public final void setPressedColorMod(final float r, final float g, final float b, final float a) { + this.pressedRGBAModulate[0] = r; + this.pressedRGBAModulate[1] = g; + this.pressedRGBAModulate[2] = b; + this.pressedRGBAModulate[3] = a; + } + + /** + * Set toggle-on color. + * <p> + * Default toggle-on color-factor w/o color channel, modulated base-color. 0.75 * 1.13 ~ 0.85 + * </p> + */ + public final void setToggleOnColorMod(final float r, final float g, final float b, final float a) { + this.toggleOnRGBAModulate[0] = r; + this.toggleOnRGBAModulate[1] = g; + this.toggleOnRGBAModulate[2] = b; + this.toggleOnRGBAModulate[3] = a; + } + + /** + * Set toggle-off color. + * <p> + * Default toggle-off color-factor w/o color channel, modulated base-color. 0.75 * 0.86 ~ 0.65 + * </p> + */ + public final void setToggleOffColorMod(final float r, final float g, final float b, final float a) { + this.toggleOffRGBAModulate[0] = r; + this.toggleOffRGBAModulate[1] = g; + this.toggleOffRGBAModulate[2] = b; + this.toggleOffRGBAModulate[3] = a; + } + + @Override + public final String toString() { + return getClass().getSimpleName()+"["+getSubString()+"]"; + } + + public String getSubString() { + return "enabled "+enabled+", toggle[able "+toggleable+", state "+toggle+"], "+translate[0]+" / "+translate[1]+", box "+box; + } + + // + // Input + // + + public void setPressed(final boolean b) { + this.down = b; + markStateDirty(); + } + public boolean isPressed() { + return this.down; + } + + public void setToggleable(final boolean toggleable) { + this.toggleable = toggleable; + } + /** + * Returns true if this shape is toggable, + * i.e. rendered w/ {@link #setToggleOnColorMod(float, float, float, float)} or {@link #setToggleOffColorMod(float, float, float, float)}. + */ + public boolean isToggleable() { + return toggleable; + } + public void setToggle(final boolean v) { + toggle = v; + markStateDirty(); + } + public void toggle() { + if( isToggleable() ) { + toggle = !toggle; + } + markStateDirty(); + } + public boolean isToggleOn() { return toggle; } + + /** + * Set whether this shape is draggable, + * i.e. translated by 1-pointer-click and drag. + * <p> + * Default is draggable on. + * </p> + */ + public void setDraggable(final boolean draggable) { + this.draggable = draggable; + } + public boolean isDraggable() { + return draggable; + } + + public final void addMouseListener(final MouseGestureListener l) { + if(l == null) { + return; + } + @SuppressWarnings("unchecked") + final ArrayList<MouseGestureListener> clonedListeners = (ArrayList<MouseGestureListener>) mouseListeners.clone(); + clonedListeners.add(l); + mouseListeners = clonedListeners; + } + + public final void removeMouseListener(final MouseGestureListener l) { + if (l == null) { + return; + } + @SuppressWarnings("unchecked") + final ArrayList<MouseGestureListener> clonedListeners = (ArrayList<MouseGestureListener>) mouseListeners.clone(); + clonedListeners.remove(l); + mouseListeners = clonedListeners; + } + + /** + * Combining {@link MouseListener} and {@link GestureListener} + */ + public static interface MouseGestureListener extends MouseListener, GestureListener { + } + + /** + * Convenient adapter combining dummy implementation for {@link MouseListener} and {@link GestureListener} + */ + public static abstract class MouseGestureAdapter extends MouseAdapter implements MouseGestureListener { + @Override + public void gestureDetected(final GestureEvent gh) { + } + } + + /** + * {@link Shape} event details for propagated {@link NEWTEvent}s + * containing reference of {@link #shape the intended shape} as well as + * the {@link #objPos rotated relative position}. + * The latter is normalized to lower-left zero origin, allowing easier usage. + */ + public static class UIShapeEvent { + /** 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; + /** 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 }; + /** The drag delta of GL window coordinates, origin bottom-left */ + public final int[] winDrag = { 0, 0 }; + + /** + * Ctor + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param shape associated shape + * @param objPos relative object coordinate of glWinX/glWinY to the associated shape. + */ + UIShapeEvent(final int glWinX, final int glWinY, final Shape shape, final float[] objPos) { + this.winPos = new int[] { glWinX, glWinY }; + this.shape = shape; + this.objPos = objPos; + } + + @Override + public String toString() { + return "EventDetails[winPos ["+winPos[0]+", "+winPos[1]+"], objPos ["+objPos[0]+", "+objPos[1]+", "+objPos[2]+"], "+shape+"]"; + } + } + + /** + * @param e original Newt {@link GestureEvent} + * @param glWinX x-position in OpenGL model space + * @param glWinY y-position in OpenGL model space + */ + public final void dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final float[] objPos) { + e.setAttachment(new UIShapeEvent(glWinX, glWinY, this, objPos)); + for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { + mouseListeners.get(i).gestureDetected(e); + } + } + + boolean dragFirst = false; + float[] objDraggedFirst = { 0f, 0f }; // b/c its relative to UIShape and we stick to it + int[] winDraggedLast = { 0, 0 }; // b/c its absolute window pos + + /** + * Dispatch given NEWT mouse event to this shape + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos object position of mouse event within this shape + */ + public final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos) { + final Shape.UIShapeEvent shapeEvent = new UIShapeEvent(glWinX, glWinY, this, objPos); + e.setAttachment(shapeEvent); + + final short eventType = e.getEventType(); + if( 1 == e.getPointerCount() ) { + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_CLICKED: + toggle(); + break; + case MouseEvent.EVENT_MOUSE_PRESSED: + setPressed(true); + break; + case MouseEvent.EVENT_MOUSE_RELEASED: + setPressed(false); + break; + } + } + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_PRESSED: + dragFirst = true; + break; + case MouseEvent.EVENT_MOUSE_RELEASED: + dragFirst = false; + break; + case MouseEvent.EVENT_MOUSE_DRAGGED: { + // 1 pointer drag + if(dragFirst) { + objDraggedFirst[0] = objPos[0]; + objDraggedFirst[1] = objPos[1]; + winDraggedLast[0] = glWinX; + winDraggedLast[1] = glWinY; + dragFirst=false; + return; + } + shapeEvent.objDrag[0] = objPos[0] - objDraggedFirst[0]; + shapeEvent.objDrag[1] = objPos[1] - objDraggedFirst[1]; + shapeEvent.winDrag[0] = glWinX - winDraggedLast[0]; + shapeEvent.winDrag[1] = glWinY - winDraggedLast[1]; + winDraggedLast[0] = glWinX; + winDraggedLast[1] = glWinY; + if( draggable && e.getPointerCount() == 1 ) { + translate(shapeEvent.objDrag[0], shapeEvent.objDrag[1], 0f); + } + if( DEBUG ) { + System.err.printf("Dragged: win %4d/%4d + %2d/%2d; obj %.3f/%.3f + %.3f/%.3f%n", + shapeEvent.winPos[0], shapeEvent.winPos[1], + shapeEvent.winDrag[0], shapeEvent.winDrag[1], + shapeEvent.objPos[0], shapeEvent.objPos[1], + shapeEvent.objDrag[0], shapeEvent.objDrag[1]); + } + } + break; + } + + for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { + final MouseGestureListener l = mouseListeners.get(i); + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_CLICKED: + l.mouseClicked(e); + break; + case MouseEvent.EVENT_MOUSE_ENTERED: + l.mouseEntered(e); + break; + case MouseEvent.EVENT_MOUSE_EXITED: + l.mouseExited(e); + break; + case MouseEvent.EVENT_MOUSE_PRESSED: + l.mousePressed(e); + break; + case MouseEvent.EVENT_MOUSE_RELEASED: + l.mouseReleased(e); + break; + case MouseEvent.EVENT_MOUSE_MOVED: + l.mouseMoved(e); + break; + case MouseEvent.EVENT_MOUSE_DRAGGED: + l.mouseDragged(e); + break; + case MouseEvent.EVENT_MOUSE_WHEEL_MOVED: + l.mouseWheelMoved(e); + break; + default: + throw new NativeWindowException("Unexpected mouse event type " + e.getEventType()); + } + } + } + + // + // + // + + protected abstract void clearImpl(GL2ES2 gl, RegionRenderer renderer); + protected abstract void destroyImpl(GL2ES2 gl, RegionRenderer renderer); + protected abstract void addShapeToRegion(GL2ES2 gl, RegionRenderer renderer); + + // + // + // + + protected OutlineShape createDebugOutline(final OutlineShape shape, final AABBox box) { + final float d = 0.025f; + final float tw = box.getWidth() + d*2f; + final float th = box.getHeight() + d*2f; + + final float minX = box.getMinX() - d; + final float minY = box.getMinY() - d; + final float z = 0; // box.getMinZ() + 0.025f; + + // CCW! + shape.moveTo(minX, minY, z); + shape.lineTo(minX+tw, minY, z); + shape.lineTo(minX+tw, minY + th, z); + shape.lineTo(minX, minY + th, z); + shape.closePath(); + + // shape.addVertex(minX, minY, z, true); + // shape.addVertex(minX+tw, minY, z, true); + // shape.addVertex(minX+tw, minY + th, z, true); + // shape.addVertex(minX, minY + th, z, true); + // shape.closeLastOutline(true); + + return shape; + } + +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Button.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Button.java new file mode 100644 index 000000000..981441f9a --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Button.java @@ -0,0 +1,201 @@ +/** + * 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.gl.shapes; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2ES2; + +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.opengl.math.geom.AABBox; + +import jogamp.graph.ui.shapes.Label0; + +/** + * A GraphUI text labeled {@link RoundButton} {@link Shape} + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + * </p> + */ +public class Button extends RoundButton { + /** {@value} */ + public static final float DEFAULT_SPACING_X = 0.12f; + /** {@value} */ + public static final float DEFAULT_SPACING_Y = 0.42f; + + private static final float DEFAULT_2PASS_LABEL_ZOFFSET = -0.005f; // -0.05f; + + private final Label0 label; + private float spacingX = DEFAULT_SPACING_X; + private float spacingY = DEFAULT_SPACING_Y; + + public Button(final Factory<? extends Vertex> factory, final int renderModes, + final Font labelFont, final String labelText, + final float width, final float height) { + super(factory, renderModes | Region.COLORCHANNEL_RENDERING_BIT, width, height); + this.label = new Label0(labelFont, labelText, new float[] { 1.33f, 1.33f, 1.33f, 1.0f }); // 0.75 * 1.33 = 1.0 + setColor(0.75f, 0.75f, 0.75f, 1.0f); + setPressedColorMod(0.9f, 0.9f, 0.9f, 0.7f); + setToggleOffColorMod(0.65f, 0.65f, 0.65f, 1.0f); + setToggleOnColorMod(0.85f, 0.85f, 0.85f, 1.0f); + } + + public Font getFont() { return label.getFont(); } + public String getLaben() { return label.getText(); } + + @Override + public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + if( false ) { + // Setup poly offset for z-fighting + gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); + gl.glPolygonOffset(0f, 1f); + super.drawShape(gl, renderer, sampleCount); + gl.glDisable(GL.GL_POLYGON_OFFSET_FILL); + } else { + super.drawShape(gl, renderer, sampleCount); + } + } + + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + if(corner == 0.0f) { + createSharpOutline(shape, DEFAULT_2PASS_LABEL_ZOFFSET); + } else { + createCurvedOutline(shape, DEFAULT_2PASS_LABEL_ZOFFSET); + } + shape.setIsQuadraticNurbs(); + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, null, rgbaColor); + box.resize(shape.getBounds()); + + // Precompute text-box size .. guessing pixelSize + final float lw = width * ( 1f - spacingX ) ; + final float lh = height * ( 1f - spacingY ) ; + final AABBox lbox0_em = label.getFont().getGlyphBounds(label.getText(), tempT1, tempT2); + final float lsx = lw / lbox0_em.getWidth(); + final float lsy = lh / lbox0_em.getHeight(); + 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]); + // Center text .. (share same center w/ button) + final float[] lctr = lbox1_s.getCenter(); + final float[] ctr = box.getCenter(); + final float[] ltx = new float[] { ctr[0] - lctr[0], ctr[1] - lctr[1], 0f }; + + if( DRAW_DEBUG_BOX ) { + System.err.println("RIButton: dim "+width+" x "+height+", spacing "+spacingX+", "+spacingY); + System.err.println("RIButton: net-text "+lw+" x "+lh); + System.err.println("RIButton: shape "+box); + System.err.println("RIButton: text_em "+lbox0_em+" em, "+label.getText()); + System.err.println("RIButton: lscale "+lsx+" x "+lsy+" -> "+lScale); + System.err.printf ("RIButton: text_s %s%n", lbox1_s); + System.err.printf ("RIButton: tleft %f / %f, %f / %f%n", ltx[0], ltx[1], ltx[0] * lScale, ltx[1] * lScale); + } + + final AABBox lbox2 = label.addShapeToRegion(lScale, region, tempT1.setToTranslation(ltx[0], ltx[1]), tempT2, tempT3, tempT4); + if( DRAW_DEBUG_BOX ) { + System.err.printf("RIButton.X: lbox2 %s%n", lbox2); + } + + setRotationOrigin( ctr[0], ctr[1], ctr[2]); + + if( DRAW_DEBUG_BOX ) { + System.err.println("XXX.UIShape.RIButton: Added Shape: "+shape+", "+box); + } + } + + public final float getSpacingX() { return spacingX; } + public final float getSpacingY() { return spacingY; } + + /** + * In percent of text label + * @param spacingX spacing in percent on X, default is {@link #DEFAULT_SPACING_X} + * @param spacingY spacing in percent on Y, default is {@link #DEFAULT_SPACING_Y} + */ + public final void setSpacing(final float spacingX, final float spacingY) { + if ( spacingX < 0.0f ) { + this.spacingX = 0.0f; + } else if ( spacingX > 1.0f ) { + this.spacingX = 1.0f; + } else { + this.spacingX = spacingX; + } + if ( spacingY < 0.0f ) { + this.spacingY = 0.0f; + } else if ( spacingY > 1.0f ) { + this.spacingY = 1.0f; + } else { + this.spacingY = spacingY; + } + markShapeDirty(); + } + + public final float[] getLabelColor() { + return label.getColor(); + } + + public final void setLabelColor(final float r, final float g, final float b) { + label.setColor(r, g, b, 1.0f); + markShapeDirty(); + } + + public final void setFont(final Font labelFont) { + if( !label.getFont().equals(labelFont) ) { + label.setFont(labelFont); + markShapeDirty(); + } + } + public final void setLabel(final String labelText) { + if( !label.getText().equals(labelText) ) { + label.setText(labelText); + markShapeDirty(); + } + } + public final void setLabel(final Font labelFont, final String labelText) { + if( !label.getText().equals(labelText) || !label.getFont().equals(labelFont) ) { + label.setFont(labelFont); + label.setText(labelText); + markShapeDirty(); + } + } + + @Override + public String getSubString() { + return super.getSubString()+", "+ label + ", " + "spacing: " + spacingX+"/"+spacingY; + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/CrossHair.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/CrossHair.java new file mode 100644 index 000000000..b93639e5d --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/CrossHair.java @@ -0,0 +1,111 @@ +/** + * 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.gl.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.graph.ui.gl.Shape; + +/** + * A GraphUI Crosshair {@link Shape} + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + */ +public class CrossHair extends Shape { + private float width, height, lineWidth; + + public CrossHair(final Factory<? extends Vertex> factory, final int renderModes, final float width, final float height, final float linewidth) { + super(factory, renderModes); + this.width = width; + this.height = height; + this.lineWidth = linewidth; + } + + public final float getWidth() { return width; } + public final float getHeight() { return height; } + public final float getLineWidth() { return lineWidth; } + + public void setDimension(final float width, final float height, final float lineWidth) { + this.width = width; + this.height = height; + this.lineWidth = lineWidth; + markShapeDirty(); + } + + @Override + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + + final float lwh = lineWidth/2f; + + final float tw = getWidth(); + final float th = getHeight(); + final float twh = tw/2f; + final float thh = th/2f; + + final float ctrX = 0f, ctrY = 0f; + final float ctrZ = 0f; + + // middle vertical (CCW!) + shape.moveTo(ctrX-lwh, ctrY-thh, ctrZ); + shape.lineTo(ctrX+lwh, ctrY-thh, ctrZ); + shape.lineTo(ctrX+lwh, ctrY+thh, ctrZ); + shape.lineTo(ctrX-lwh, ctrY+thh, ctrZ); + shape.closePath(); + + // middle horizontal (CCW!) + shape.moveTo(ctrX-twh, ctrY-lwh, ctrZ); + shape.lineTo(ctrX+twh, ctrY-lwh, ctrZ); + shape.lineTo(ctrX+twh, ctrY+lwh, ctrZ); + shape.lineTo(ctrX-twh, ctrY+lwh, ctrZ); + shape.closePath(); + + shape.setIsQuadraticNurbs(); + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, null, rgbaColor); + + box.resize(shape.getBounds()); + } + + @Override + public String getSubString() { + return super.getSubString()+", dim "+getWidth() + "x" + getHeight(); + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GLButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GLButton.java new file mode 100644 index 000000000..ace658a7b --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/GLButton.java @@ -0,0 +1,159 @@ +/** + * Copyright 2014-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.gl.shapes; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLCapabilitiesImmutable; +import com.jogamp.opengl.GLContext; +import com.jogamp.opengl.GLDrawable; +import com.jogamp.opengl.GLDrawableFactory; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLOffscreenAutoDrawable; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.opengl.FBObject; +import com.jogamp.opengl.util.texture.ImageSequence; +import com.jogamp.opengl.util.texture.Texture; + +/** + * A GraphUI {@link GLEventListener} based {@link TexSeqButton} {@link Shape}. + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * The {@link GLEventListener} is rendered via an {@link GLOffscreenAutoDrawable.FBO} into an {@link ImageSequence}. + * </p> + * <p> + * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + * </p> + */ +public class GLButton extends TexSeqButton { + private final GLEventListener glel; + private final boolean useAlpha; + private volatile int fboWidth = 200; + private volatile int fboHeight = 200; + private volatile GLOffscreenAutoDrawable.FBO fboGLAD = null; + private boolean animateGLEL = false; + + public GLButton(final Factory<? extends Vertex> factory, final int renderModes, + final float width, final float height, final int textureUnit, + final GLEventListener glel, final boolean useAlpha, final int fboWidth, final int fboHeight) { + super(factory, renderModes, width, height, new ImageSequence(textureUnit, true)); + this.glel = glel; + this.useAlpha = useAlpha; + + setColor(0.95f, 0.95f, 0.95f, 1.0f); + setPressedColorMod(1f, 1f, 1f, 0.9f); + setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); + setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); + + this.fboWidth = fboWidth; + this.fboHeight = fboHeight; + } + + public final void setAnimate(final boolean v) { animateGLEL = v; } + public final boolean getAnimate() { return animateGLEL; } + + public final void setFBOSize(final int fboWidth, final int fboHeight) { + this.fboWidth = fboWidth; + this.fboHeight = fboHeight; + } + + public final GLOffscreenAutoDrawable.FBO getFBOAutoDrawable() { return fboGLAD; } + + @Override + public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + final int[/*2*/] surfaceSize = new int[2]; + final boolean got_sz = getSurfaceSize(renderer, surfaceSize); + + if( null == fboGLAD ) { + final ImageSequence imgSeq = (ImageSequence)texSeq; + + final GLContext ctx = gl.getContext(); + final GLDrawable drawable = ctx.getGLDrawable(); + final GLCapabilitiesImmutable reqCaps = drawable.getRequestedGLCapabilities(); + final GLCapabilities caps = (GLCapabilities) reqCaps.cloneMutable(); + caps.setFBO(true); + caps.setDoubleBuffered(false); + if( !useAlpha ) { + caps.setAlphaBits(0); + } + final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile()); + + fboGLAD = (GLOffscreenAutoDrawable.FBO) factory.createOffscreenAutoDrawable( + drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice(), + caps, null, fboWidth, fboHeight); + fboWidth = 0; + fboHeight = 0; + fboGLAD.setSharedContext(ctx); + fboGLAD.setTextureUnit(imgSeq.getTextureUnit()); + fboGLAD.addGLEventListener(glel); + fboGLAD.display(); // 1st init! + + final FBObject.TextureAttachment texA01 = fboGLAD.getColorbuffer(GL.GL_FRONT).getTextureAttachment(); + final Texture tex = new Texture(texA01.getName(), imgSeq.getTextureTarget(), + fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), + false /* mustFlipVertically */); + imgSeq.addFrame(gl, tex); + markStateDirty(); + } else if( 0 != fboWidth*fboHeight ) { + fboGLAD.setSurfaceSize(fboWidth, fboHeight); + fboWidth = 0; + fboHeight = 0; + markStateDirty(); + } else if( got_sz && ( fboGLAD.getSurfaceWidth() != surfaceSize[0] || fboGLAD.getSurfaceHeight() != surfaceSize[1] ) ) { + // System.err.println("XXX FBO setSurfaceSize "+fboGLAD.getSurfaceWidth()+" x "+fboGLAD.getSurfaceHeight()+" -> "+surfaceSize[0]+" x "+surfaceSize[1]); + final ImageSequence imgSeq = (ImageSequence)texSeq; + + fboGLAD.setSurfaceSize(surfaceSize[0], surfaceSize[1]); + fboGLAD.display(); // re-init! + + imgSeq.destroy(gl); + final FBObject.TextureAttachment texA01 = fboGLAD.getColorbuffer(GL.GL_FRONT).getTextureAttachment(); + final Texture tex = new Texture(texA01.getName(), imgSeq.getTextureTarget(), + fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), fboGLAD.getSurfaceWidth(), fboGLAD.getSurfaceHeight(), + false /* mustFlipVertically */); + imgSeq.addFrame(gl, tex); + fboWidth = 0; + fboHeight = 0; + markStateDirty(); + } else if( animateGLEL ) { + fboGLAD.display(); + } + + super.drawShape(gl, renderer, sampleCount); + + if( animateGLEL ) { + markStateDirty(); // keep on going + } + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/ImageButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/ImageButton.java new file mode 100644 index 000000000..6ebc0d7ba --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/ImageButton.java @@ -0,0 +1,69 @@ +/** + * Copyright 2014-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.gl.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.opengl.util.texture.ImageSequence; + +/** + * A GraphUI {@link ImageSequence} based {@link TexSeqButton} {@link Shape}. + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + * </p> + */ +public class ImageButton extends TexSeqButton { + + public ImageButton(final Factory<? extends Vertex> factory, final int renderModes, + final float width, final float height, final ImageSequence texSeq) { + super(factory, renderModes, width, height, texSeq); + setColor(0.95f, 0.95f, 0.95f, 1.0f); + setPressedColorMod(1f, 1f, 1f, 0.9f); + setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); + setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); + } + + public final void setCurrentIdx(final int idx) { + ((ImageSequence)texSeq).setCurrentIdx(idx); + markStateDirty(); + } + + @Override + public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + super.drawShape(gl, renderer, sampleCount); + if( !((ImageSequence)texSeq).getManualStepping() ) { + markStateDirty(); // keep on going + } + }; +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Label.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Label.java new file mode 100644 index 000000000..4af782a37 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Label.java @@ -0,0 +1,137 @@ +/** + * 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.gl.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.graph.ui.gl.Shape; + +/** + * A GraphUI text label {@link Shape} + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + */ +public class Label extends Shape { + protected Font font; + protected float pixelSize; + protected String text; + + public Label(final Factory<? extends Vertex> factory, final int renderModes, final Font font, final float pixelSize, final String text) { + super(factory, renderModes); + this.font = font; + this.pixelSize = pixelSize; + this.text = text; + } + + public String getText() { + return text; + } + + /** + * Returns true if text has been updated, false if unchanged. + * @param text the text to be set. + */ + public boolean setText(final String text) { + if( !this.text.equals(text) ) { + this.text = text; + markShapeDirty(); + return true; + } else { + return false; + } + } + + public Font getFont() { + return font; + } + + /** + * Returns true if font has been updated, false if unchanged. + * @param font the font to be set. + */ + public boolean setFont(final Font font) { + if( !this.font.equals(font) ) { + this.font = font; + markShapeDirty(); + return true; + } else { + return false; + } + } + + public float getPixelSize() { + return pixelSize; + } + + public float getLineHeight() { + return pixelSize * font.getLineHeight(); + } + + public void setPixelSize(final float pixelSize) { + this.pixelSize = pixelSize; + markShapeDirty(); + } + + @Override + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + private final OutlineShape.Visitor shapeVisitor = new OutlineShape.Visitor() { + @Override + public void visit(final OutlineShape shape, final AffineTransform t) { + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, t, rgbaColor); + } + }; + + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + tempT1.setToScale(pixelSize, pixelSize); + final AABBox fbox = font.processString(shapeVisitor, tempT1, text, tempT2, tempT3); + final float[] ctr = box.getCenter(); + setRotationOrigin( ctr[0], ctr[1], ctr[2]); + box.resize(fbox); + } + + @Override + public String getSubString() { + final int m = Math.min(text.length(), 8); + return super.getSubString()+", psize " + pixelSize + ", '" + text.substring(0, m)+"'"; + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/MediaButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/MediaButton.java new file mode 100644 index 000000000..290abba6e --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/MediaButton.java @@ -0,0 +1,141 @@ +/** + * Copyright 2014-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.gl.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.common.util.InterruptSource; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.opengl.util.av.GLMediaPlayer; +import com.jogamp.opengl.util.av.GLMediaPlayer.GLMediaEventListener; +import com.jogamp.opengl.util.av.GLMediaPlayer.StreamException; +import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame; + +/** + * A GraphUI {@link GLMediaPlayer} based {@link TexSeqButton} {@link Shape}. + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + * </p> + */ +public class MediaButton extends TexSeqButton { + private boolean verbose = false; + + /** + * @param factory + * @param renderModes + * @param width + * @param height + * @param mPlayer + * @param mPlayerListener + */ + public MediaButton(final Factory<? extends Vertex> factory, final int renderModes, + final float width, final float height, + final GLMediaPlayer mPlayer) { + super(factory, renderModes, width, height, mPlayer); + setColor(0.8f, 0.8f, 0.8f, 1.0f); + setPressedColorMod(1.1f, 1.1f, 1.1f, 0.7f); + setToggleOffColorMod(0.8f, 0.8f, 0.8f, 1.0f); + setToggleOnColorMod(1.0f, 1.0f, 1.0f, 1.0f); + setEnabled(false); // data and shader n/a yet + } + + public void setVerbose(final boolean v) { verbose = v; } + + /** + * Add the default {@link GLMediaEventListener} to {@link #getGLMediaPlayer() this class's GLMediaPlayer}. + */ + public void addDefaultEventListener() { + getGLMediaPlayer().addEventListener(defGLMediaEventListener); + } + + public final GLMediaPlayer getGLMediaPlayer() { return (GLMediaPlayer)texSeq; } + + private final GLMediaEventListener defGLMediaEventListener = new GLMediaEventListener() { + @Override + public void newFrameAvailable(final GLMediaPlayer ts, final TextureFrame newFrame, final long when) { + // texButton.markStateDirty(); + } + + @Override + public void attributesChanged(final GLMediaPlayer mp, final int event_mask, final long when) { + final GLMediaPlayer mPlayer = (GLMediaPlayer)texSeq; + if( verbose ) { + System.err.println("MovieCube AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); + System.err.println("MovieCube State: "+mp); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { + MediaButton.this.setEnabled(true); // data and shader is available .. + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) ) { + // FIXME: mPlayer.resetGLState(); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { + new InterruptSource.Thread() { + @Override + public void run() { + // loop for-ever .. + mPlayer.seek(0); + mPlayer.play(); + } }.start(); + } else if( 0 != ( GLMediaEventListener.EVENT_CHANGE_ERR & event_mask ) ) { + final StreamException se = mPlayer.getStreamException(); + if( null != se ) { + se.printStackTrace(); + } + } + } }; + + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + ((GLMediaPlayer)texSeq).destroy(gl); + } + + @Override + public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { + final GLMediaPlayer mPlayer = (GLMediaPlayer)texSeq; + if( GLMediaPlayer.State.Initialized == mPlayer.getState() ) { + try { + mPlayer.initGL(gl); + mPlayer.setAudioVolume( 0f ); + mPlayer.play(); + markStateDirty(); + } catch (final Exception e) { + e.printStackTrace(); + } + } + super.drawShape(gl, renderer, sampleCount); + markStateDirty(); // keep on going + }; + +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Rectangle.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Rectangle.java new file mode 100644 index 000000000..6a93bc842 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/Rectangle.java @@ -0,0 +1,116 @@ +/** + * 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.gl.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.graph.ui.gl.Shape; + +/** + * A GraphUI Rectangle {@link Shape} + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + */ +public class Rectangle extends Shape { + private float width, height, lineWidth; + + public Rectangle(final Factory<? extends Vertex> factory, final int renderModes, final float width, final float height, final float linewidth) { + super(factory, renderModes); + this.width = width; + this.height = height; + this.lineWidth = linewidth; + } + + public final float getWidth() { return width; } + public final float getHeight() { return height; } + public final float getLineWidth() { return lineWidth; } + + public void setDimension(final float width, final float height, final float lineWidth) { + this.width = width; + this.height = height; + this.lineWidth = lineWidth; + markShapeDirty(); + } + + @Override + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + + final float lwh = lineWidth/2f; + + final float tw = getWidth(); + final float th = getHeight(); + + final float twh = tw/2f; + final float twh_o = twh+lwh; + final float twh_i = twh-lwh; + final float thh = th/2f; + final float thh_o = thh+lwh; + final float thh_i = thh-lwh; + + final float ctrX = 0f, ctrY = 0f; + final float ctrZ = 0f; + + // outer (CCW!) + shape.moveTo(ctrX-twh_o, ctrY-thh_o, ctrZ); + shape.lineTo(ctrX+twh_o, ctrY-thh_o, ctrZ); + shape.lineTo(ctrX+twh_o, ctrY+thh_o, ctrZ); + shape.lineTo(ctrX-twh_o, ctrY+thh_o, ctrZ); + shape.closePath(); + + // inner (CCW!) + shape.moveTo(ctrX-twh_i, ctrY-thh_i, ctrZ); + shape.lineTo(ctrX+twh_i, ctrY-thh_i, ctrZ); + shape.lineTo(ctrX+twh_i, ctrY+thh_i, ctrZ); + shape.lineTo(ctrX-twh_i, ctrY+thh_i, ctrZ); + shape.closePath(); + + shape.setIsQuadraticNurbs(); + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, null, rgbaColor); + + box.resize(shape.getBounds()); + } + + @Override + public String getSubString() { + return super.getSubString()+", dim "+getWidth() + "x" + getHeight(); + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/RoundButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/RoundButton.java new file mode 100644 index 000000000..56ab4146b --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/RoundButton.java @@ -0,0 +1,141 @@ +/** + * 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.gl.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.graph.ui.gl.Shape; + +/** + * An abstract GraphUI round Button {@link Shape} + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + * </p> + */ +public abstract class RoundButton extends Shape { + + /** {@value} */ + public static final float DEFAULT_CORNER = 1f; + protected float width; + protected float height; + protected float corner = DEFAULT_CORNER; + + protected RoundButton(final Factory<? extends Vertex> factory, final int renderModes, final float width, final float height) { + super(factory, renderModes); + this.width = width; + this.height = height; + } + + @Override + protected void clearImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + @Override + protected void destroyImpl(final GL2ES2 gl, final RegionRenderer renderer) { + } + + public final float getWidth() { return width; } + + public final float getHeight() { return height; } + + public final float getCorner() { return corner; } + + public void setSize(final float width, final float height) { + this.width = width; + this.height = height; + markShapeDirty(); + } + + protected void createSharpOutline(final OutlineShape shape, final float zOffset) { + final float tw = getWidth(); + final float th = getHeight(); + + final float minX = 0; + final float minY = 0; + final float minZ = zOffset; + + shape.addVertex(minX, minY, minZ, true); + shape.addVertex(minX+tw, minY, minZ, true); + shape.addVertex(minX+tw, minY + th, minZ, true); + shape.addVertex(minX, minY + th, minZ, true); + shape.closeLastOutline(true); + } + + protected void createCurvedOutline(final OutlineShape shape, final float zOffset) { + final float tw = getWidth(); + final float th = getHeight(); + final float dC = 0.5f*corner*Math.min(tw, th); + + final float minX = 0; + final float minY = 0; + final float minZ = zOffset; + + shape.addVertex(minX, minY + dC, minZ, true); + shape.addVertex(minX, minY, minZ, false); + + shape.addVertex(minX + dC, minY, minZ, true); + + shape.addVertex(minX + tw - dC, minY, minZ, true); + shape.addVertex(minX + tw, minY, minZ, false); + shape.addVertex(minX + tw, minY + dC, minZ, true); + shape.addVertex(minX + tw, minY + th- dC, minZ, true); + shape.addVertex(minX + tw, minY + th, minZ, false); + shape.addVertex(minX + tw - dC, minY + th, minZ, true); + shape.addVertex(minX + dC, minY + th, minZ, true); + shape.addVertex(minX, minY + th, minZ, false); + shape.addVertex(minX, minY + th - dC, minZ, true); + + shape.closeLastOutline(true); + } + + /** Set corner size, default is {@link #DEFAULT_CORNER} */ + public void setCorner(final float corner) { + if(corner > 1.0f){ + this.corner = 1.0f; + } + else if(corner < 0.01f){ + this.corner = 0.0f; + } + else{ + this.corner = corner; + } + markShapeDirty(); + } + + @Override + public String getSubString() { + return super.getSubString()+", dim "+getWidth() + "x" + getHeight() + ", corner " + corner; + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/TexSeqButton.java b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/TexSeqButton.java new file mode 100644 index 000000000..3ebde276f --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/shapes/TexSeqButton.java @@ -0,0 +1,87 @@ +/** + * Copyright 2014-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.gl.shapes; + +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLProfile; +import com.jogamp.graph.curve.OutlineShape; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.graph.geom.Vertex.Factory; +import com.jogamp.graph.ui.gl.Shape; +import com.jogamp.opengl.util.texture.TextureSequence; + +/** + * An abstract GraphUI {@link TextureSequence} {@link RoundButton} {@link Shape}. + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * This button is rendered with a round oval shape. + * To render it rectangular, {@link #setCorner(float)} to zero. + * </p> + */ +public abstract class TexSeqButton extends RoundButton { + protected final TextureSequence texSeq; + + public TexSeqButton(final Factory<? extends Vertex> factory, final int renderModes, + final float width, final float height, final TextureSequence texSeq) { + super(factory, renderModes | Region.COLORTEXTURE_RENDERING_BIT, width, height); + this.texSeq = texSeq; + } + + @Override + protected GLRegion createGLRegion(final GLProfile glp) { + return GLRegion.create(glp, getRenderModes(), texSeq); + } + + public final TextureSequence getTextureSequence() { return this.texSeq; } + + @Override + protected void addShapeToRegion(final GL2ES2 gl, final RegionRenderer renderer) { + final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); + if(corner == 0.0f) { + createSharpOutline(shape, 0f); + } else { + createCurvedOutline(shape, 0f); + } + shape.setIsQuadraticNurbs(); + shape.setSharpness(shapesSharpness); + region.addOutlineShape(shape, null, rgbaColor); + box.resize(shape.getBounds()); + + final float[] ctr = box.getCenter(); + setRotationOrigin( ctr[0], ctr[1], ctr[2]); + + if( DRAW_DEBUG_BOX ) { + System.err.println("XXX.UIShape.TextureSeqButton: Added Shape: "+shape+", "+box); + } + } +} |