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