diff options
author | Sven Gothel <[email protected]> | 2023-03-10 03:11:24 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-03-10 03:11:24 +0100 |
commit | 3131eaaf5272ca3f0011e334eb08c6ba68702a6c (patch) | |
tree | 73f2e61e9986d2b72cad12ddc2b6cd5193e53028 /src/graphui/classes | |
parent | 52e4bc6cd4ace3fdf7ccaf790566500670709a44 (diff) |
GraphUI: Promote API to JOGL via graphui.jar or within any jogl-all*.jar (WIP)
Root package is 'com.jogamp.graph.ui.gl', i.e. a sub-package of Graph denoting UI and OpenGL usage.
Implementation will stay small, hence relative files size costs are minimal.
Source and build is in parallel to nativewindow, jogl and newt
and has a dependency to all of them.
The NEWT dependencies are merely the input listener ..
Diffstat (limited to 'src/graphui/classes')
12 files changed, 2671 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); + } + } +} diff --git a/src/graphui/classes/jogamp/graph/ui/shapes/Label0.java b/src/graphui/classes/jogamp/graph/ui/shapes/Label0.java new file mode 100644 index 000000000..1a7520daa --- /dev/null +++ b/src/graphui/classes/jogamp/graph/ui/shapes/Label0.java @@ -0,0 +1,81 @@ +/** + * 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 jogamp.graph.ui.shapes; + +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.TextRegionUtil; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.opengl.math.geom.AABBox; + +public class Label0 { + protected Font font; + protected String text; + protected final float[] rgbaColor; + + public Label0(final Font font, final String text, final float[] rgbaColor) { + this.font = font; + this.text = text; + this.rgbaColor = rgbaColor; + } + + public final String getText() { return text; } + + public final float[] getColor() { return rgbaColor; } + + 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; + } + + public final void setText(final String text) { + this.text = text; + } + + public final Font getFont() { return font; } + + public final void setFont(final Font font) { + this.font = font; + } + + public final AABBox addShapeToRegion(final float scale, final Region region, final AffineTransform tLeft, + final AffineTransform tmp1, final AffineTransform tmp2, final AffineTransform tmp3) + { + tmp1.setTransform(tLeft); + tmp1.scale(scale, scale, tmp2); + return TextRegionUtil.addStringToRegion(region, font, tmp1, text, rgbaColor, tmp2, tmp3); + } + + @Override + public final String toString(){ + final int m = Math.min(text.length(), 8); + return "Label0 ['" + text.substring(0, m) + "']"; + } +} |