From 121260d2b7bc4f57a16ca53ed1b08082d7977bbe Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Mon, 18 Dec 2023 04:31:37 +0100 Subject: Bug 805: GraphUI Scene/Shape Pick-Active/Interaction: Pick shall complete traversion for most inner interactive shape; ... Pick shall complete traversion for most inner interactive shape - Shape::dispatchMouseEvent() is only invoked for interactive shapes, impl. simplified. - Remove 'Scene::dispatchMouseEvent(..)', use 'Scene::dispatchMouseEventPickShape(..)' for given use cases - Scene::dispatchMouseEventForShape(..) used for mouseDragged() only, i.e. using activeShape. +++ This allows a 'group widget' being used, allowing to click on inner shapes like a button. --- src/graphui/classes/com/jogamp/graph/ui/Scene.java | 82 ++++---- src/graphui/classes/com/jogamp/graph/ui/Shape.java | 216 +++++++++++---------- 2 files changed, 156 insertions(+), 142 deletions(-) diff --git a/src/graphui/classes/com/jogamp/graph/ui/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/Scene.java index 55c9ac680..b850fe346 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/Scene.java +++ b/src/graphui/classes/com/jogamp/graph/ui/Scene.java @@ -619,11 +619,10 @@ public final class Scene implements Container, GLEventListener { * @param glWinX window X coordinate, bottom-left origin * @param glWinY window Y coordinate, bottom-left origin * @param objPos storage for found object position in model-space of found {@link Shape} - * @param shape storage for found {@link Shape} or null * @param runnable the action to perform if {@link Shape} was found - * @return picked Shape if any or null as stored in {@code shape} + * @return last picked (inner) Shape if any or null */ - public Shape pickShape(final PMVMatrix4f pmv, final int glWinX, final int glWinY, final Vec3f objPos, final Shape[] shape, final Runnable runnable) { + public Shape pickShape(final PMVMatrix4f pmv, final int glWinX, final int glWinY, final Vec3f objPos, final Shape.Visitor1 visitor) { setupMatrix(pmv); final float winZ0 = 0f; @@ -635,25 +634,39 @@ public final class Scene implements Container, GLEventListener { */ final Recti viewport = getViewport(); final Ray ray = new Ray(); - shape[0] = null; - + final Shape[] shape = { null }; + final int[] shapeIdx = { -1 }; forSortedAll(Shape.ZDescendingComparator, pmv, (final Shape s, final PMVMatrix4f pmv2) -> { + shapeIdx[0]++; final boolean ok = s.isInteractive() && pmv.mapWinToRay(glWinX, glWinY, winZ0, winZ1, viewport, ray); if( ok ) { final AABBox sbox = s.getBounds(); if( sbox.intersectsRay(ray) ) { - // System.err.printf("Pick.0: shape %d, [%d, %d, %f/%f] -> %s%n", i, glWinX, glWinY, winZ0, winZ1, ray); + if( DEBUG ) { + System.err.printf("Pick.0: shape %d/%s/%s, [%d, %d, %f/%f] -> %s%n", shapeIdx[0], s.getClass().getSimpleName(), s.getName(), glWinX, glWinY, winZ0, winZ1, ray); + } if( null == sbox.getRayIntersection(objPos, ray, FloatUtil.EPSILON, true) ) { throw new InternalError("Ray "+ray+", box "+sbox); } - // System.err.printf("Pick.1: shape %d @ [%f, %f, %f], within %s%n", i, objPos[0], objPos[1], objPos[2], uiShape.getBounds()); - shape[0] = s; - runnable.run(); - return true; + if( visitor.visit(s) ) { + if( DEBUG ) { + System.err.printf("Pick.S: shape %d/%s/%s @ %s, %s%n", shapeIdx[0], s.getClass().getSimpleName(), s.getName(), objPos, s); + } + shape[0] = s; + } else if( DEBUG ) { + System.err.printf("Pick.1: shape %d/%s/%s @ %s, %s%n", shapeIdx[0], s.getClass().getSimpleName(), s.getName(), objPos, s); + } } } - return false; + return false; // continue traversing for most inner interactive shape }); + if( DEBUG ) { + if( null != shape[0] ) { + System.err.printf("Pick.X: shape %s/%s%n%n", shape[0].getClass().getSimpleName(), shape[0].getName()); + } else { + System.err.printf("Pick.X: shape null%n%n"); + } + } return shape[0]; } @@ -999,10 +1012,11 @@ public final class Scene implements Container, GLEventListener { } } private void setActiveShape(final Shape shape) { - if( activeShape != shape ) { - releaseActiveShape(); - if( null != shape ) { - shape.setActive(true, activeZOffsetScale * getZEpsilon(16)); + if( activeShape != shape && null != shape && + shape.setActive(true, activeZOffsetScale * getZEpsilon(16)) ) + { + if( null != activeShape ) { + activeShape.setActive(false, 0); } activeShape = shape; } @@ -1038,34 +1052,20 @@ public final class Scene implements Container, GLEventListener { } } - /** - * 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); - } else if( activeShape.isInteractive() ) { - 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 */ - final boolean dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY) { + private final boolean dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY) { final PMVMatrix4f pmv = new PMVMatrix4f(); final Vec3f objPos = new Vec3f(); - final Shape[] shape = { null }; - if( null != pickShape(pmv, glWinX, glWinY, objPos, shape, () -> { - shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos); - } ) ) - { - setActiveShape(shape[0]); + final Shape shape = pickShape(pmv, glWinX, glWinY, objPos, (final Shape s) -> { + return s.isInteractive() && ( s.dispatchMouseEvent(e, glWinX, glWinY, objPos) || true ); + }); + if( null != shape ) { + setActiveShape(shape); return true; } else { releaseActiveShape(); @@ -1079,7 +1079,7 @@ public final class Scene implements Container, GLEventListener { * @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) { + private final void dispatchMouseEventForShape(final Shape shape, final MouseEvent e, final int glWinX, final int glWinY) { final PMVMatrix4f pmv = new PMVMatrix4f(); final Vec3f objPos = new Vec3f(); winToShapeCoord(shape, glWinX, glWinY, pmv, objPos, () -> { shape.dispatchMouseEvent(e, glWinX, glWinY, objPos); }); @@ -1106,7 +1106,7 @@ public final class Scene implements Container, GLEventListener { // flip to GL window coordinates, origin bottom-left final int glWinX = e.getX(); final int glWinY = getHeight() - e.getY() - 1; - dispatchMouseEvent(e, glWinX, glWinY); + dispatchMouseEventPickShape(e, glWinX, glWinY); } @Override @@ -1114,7 +1114,7 @@ public final class Scene implements Container, GLEventListener { // flip to GL window coordinates, origin bottom-left final int glWinX = e.getX(); final int glWinY = getHeight() - e.getY() - 1; - dispatchMouseEvent(e, glWinX, glWinY); + dispatchMouseEventPickShape(e, glWinX, glWinY); if( !mouseOver ) { if( 1 == e.getPointerCount() ) { // Release active shape: last pointer has been lifted! @@ -1130,7 +1130,7 @@ public final class Scene implements Container, GLEventListener { final int glWinX = e.getX(); final int glWinY = getHeight() - e.getY() - 1; if( mouseOver ) { - dispatchMouseEvent(e, glWinX, glWinY); + dispatchMouseEventPickShape(e, glWinX, glWinY); } else { // activeId should have been released by mouseRelease() already! dispatchMouseEventPickShape(e, glWinX, glWinY); @@ -1143,7 +1143,7 @@ public final class Scene implements Container, GLEventListener { @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 ) { + if( null != activeShape && activeShape.isInteractive() && !pinchToZoomGesture.isWithinGesture() && e.getPointerId(0) == lId ) { lx = e.getX(); ly = e.getY(); @@ -1160,7 +1160,7 @@ public final class Scene implements Container, GLEventListener { // flip to GL window coordinates final int glWinX = lx; final int glWinY = getHeight() - ly - 1; - dispatchMouseEvent(e, glWinX, glWinY); + dispatchMouseEventPickShape(e, glWinX, glWinY); } @Override diff --git a/src/graphui/classes/com/jogamp/graph/ui/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/Shape.java index bfea0773f..125bb1a90 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/Shape.java +++ b/src/graphui/classes/com/jogamp/graph/ui/Shape.java @@ -1238,6 +1238,9 @@ public abstract class Shape { if( isActivable() ) { this.zOffset = zOffset; setIO(IO_ACTIVE, v); + if( !v ) { + releaseInteraction(); + } if( DEBUG ) { System.err.println("XXX "+(v?" Active":"DeActive")+" "+this); } @@ -1441,14 +1444,28 @@ public abstract class Shape { } } + private final void releaseInteraction() { + setPressed(false); + setIO(IO_IN_MOVE, false); + setIO(IO_IN_RESIZE_BR, false); + setIO(IO_IN_RESIZE_BL, false); + } + /** * 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 relative to this shape + * @return true to signal operation complete and to stop traversal, otherwise false */ - /* pp */ final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final Vec3f objPos) { + /* pp */ final boolean dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final Vec3f objPos) { + /** + * Checked at caller! + if( !isInteractive() ) { + return false; + } */ + final boolean resizableOrDraggable = isResizable() || isDraggable(); final Shape.EventInfo shapeEvent = new EventInfo(glWinX, glWinY, this, objPos); final short eventType = e.getEventType(); @@ -1461,123 +1478,119 @@ public abstract class Shape { } break; case MouseEvent.EVENT_MOUSE_PRESSED: - setIO(IO_DRAG_FIRST, true); + if( resizableOrDraggable ) { + setIO(IO_DRAG_FIRST, true); + } setPressed(true); break; case MouseEvent.EVENT_MOUSE_RELEASED: // Release active shape: last pointer has been lifted! - setPressed(false); - setIO(IO_IN_MOVE, false); - setIO(IO_IN_RESIZE_BR, false); - setIO(IO_IN_RESIZE_BL, false); + releaseInteraction(); break; } } - switch( eventType ) { - case MouseEvent.EVENT_MOUSE_DRAGGED: { - // adjust for rotation - final Vec3f euler = rotation.toEuler(new Vec3f()); - final boolean x_flip, y_flip; - { - final float x_rot = Math.abs(euler.x()); - final float y_rot = Math.abs(euler.y()); - x_flip = 1f*FloatUtil.HALF_PI <= y_rot && y_rot <= 3f*FloatUtil.HALF_PI; - y_flip = 1f*FloatUtil.HALF_PI <= x_rot && x_rot <= 3f*FloatUtil.HALF_PI; - } - // 1 pointer drag and potential drag-resize - if( isIO(IO_DRAG_FIRST) ) { - objDraggedFirst.set(objPos); - winDraggedLast[0] = glWinX; - winDraggedLast[1] = glWinY; - setIO(IO_DRAG_FIRST, false); - - final float ix = x_flip ? box.getWidth() - objPos.x() : objPos.x(); - final float iy = y_flip ? box.getHeight() - objPos.y() : objPos.y(); - final float minx_br = box.getMaxX() - box.getWidth() * resize_section; - final float miny_br = box.getMinY(); - final float maxx_br = box.getMaxX(); - final float maxy_br = box.getMinY() + box.getHeight() * resize_section; - if( minx_br <= ix && ix <= maxx_br && - miny_br <= iy && iy <= maxy_br ) { - if( isInteractive() && isResizable() ) { - setIO(IO_IN_RESIZE_BR, true); + if( resizableOrDraggable && MouseEvent.EVENT_MOUSE_DRAGGED == eventType ) { + // adjust for rotation + final Vec3f euler = rotation.toEuler(new Vec3f()); + final boolean x_flip, y_flip; + { + final float x_rot = Math.abs(euler.x()); + final float y_rot = Math.abs(euler.y()); + x_flip = 1f*FloatUtil.HALF_PI <= y_rot && y_rot <= 3f*FloatUtil.HALF_PI; + y_flip = 1f*FloatUtil.HALF_PI <= x_rot && x_rot <= 3f*FloatUtil.HALF_PI; + } + // 1 pointer drag and potential drag-resize + if( isIO(IO_DRAG_FIRST) ) { + objDraggedFirst.set(objPos); + winDraggedLast[0] = glWinX; + winDraggedLast[1] = glWinY; + setIO(IO_DRAG_FIRST, false); + + final float ix = x_flip ? box.getWidth() - objPos.x() : objPos.x(); + final float iy = y_flip ? box.getHeight() - objPos.y() : objPos.y(); + final float minx_br = box.getMaxX() - box.getWidth() * resize_section; + final float miny_br = box.getMinY(); + final float maxx_br = box.getMaxX(); + final float maxy_br = box.getMinY() + box.getHeight() * resize_section; + if( minx_br <= ix && ix <= maxx_br && + miny_br <= iy && iy <= maxy_br ) { + if( isResizable() ) { + setIO(IO_IN_RESIZE_BR, true); + } + } else { + final float minx_bl = box.getMinX(); + final float miny_bl = box.getMinY(); + final float maxx_bl = box.getMinX() + box.getWidth() * resize_section; + final float maxy_bl = box.getMinY() + box.getHeight() * resize_section; + if( minx_bl <= ix && ix <= maxx_bl && + miny_bl <= iy && iy <= maxy_bl ) { + if( isResizable() ) { + setIO(IO_IN_RESIZE_BL, true); } } else { - final float minx_bl = box.getMinX(); - final float miny_bl = box.getMinY(); - final float maxx_bl = box.getMinX() + box.getWidth() * resize_section; - final float maxy_bl = box.getMinY() + box.getHeight() * resize_section; - if( minx_bl <= ix && ix <= maxx_bl && - miny_bl <= iy && iy <= maxy_bl ) { - if( isInteractive() && isResizable() ) { - setIO(IO_IN_RESIZE_BL, true); - } - } else { - setIO(IO_IN_MOVE, isInteractive() && isDraggable()); - } + setIO(IO_IN_MOVE, isDraggable()); } - if( DEBUG ) { - System.err.printf("DragFirst: drag %b, resize[br %b, bl %b], obj[%s], flip[x %b, y %b]%n", - isIO(IO_IN_MOVE), isIO(IO_IN_RESIZE_BR), isIO(IO_IN_RESIZE_BL), objPos, x_flip, y_flip); - System.err.printf("DragFirst: %s%n", this); - } - return; } - shapeEvent.objDrag.set( objPos.x() - objDraggedFirst.x(), - objPos.y() - objDraggedFirst.y() ); - shapeEvent.objDrag.scale(x_flip ? -1f : 1f, y_flip ? -1f : 1f); - - shapeEvent.winDrag[0] = glWinX - winDraggedLast[0]; - shapeEvent.winDrag[1] = glWinY - winDraggedLast[1]; - winDraggedLast[0] = glWinX; - winDraggedLast[1] = glWinY; - if( 1 == e.getPointerCount() ) { - final float sdx = shapeEvent.objDrag.x() * scale.x(); // apply scale, since operation - final float sdy = shapeEvent.objDrag.y() * scale.y(); // is from a scaled-model-viewpoint - if( isIO(IO_IN_RESIZE_BR) || isIO(IO_IN_RESIZE_BL) ) { - final float bw = box.getWidth(); - final float bh = box.getHeight(); - final float sdy2, sx, sy; - if( isIO(IO_IN_RESIZE_BR) ) { - sx = scale.x() + sdx/bw; // bottom-right - } else { - sx = scale.x() - sdx/bw; // bottom-left + if( DEBUG ) { + System.err.printf("DragFirst: drag %b, resize[br %b, bl %b], obj[%s], flip[x %b, y %b]%n", + isIO(IO_IN_MOVE), isIO(IO_IN_RESIZE_BR), isIO(IO_IN_RESIZE_BL), objPos, x_flip, y_flip); + System.err.printf("DragFirst: %s%n", this); + } + return true; // end signal traversal at 1st drag + } + shapeEvent.objDrag.set( objPos.x() - objDraggedFirst.x(), + objPos.y() - objDraggedFirst.y() ); + shapeEvent.objDrag.scale(x_flip ? -1f : 1f, y_flip ? -1f : 1f); + + shapeEvent.winDrag[0] = glWinX - winDraggedLast[0]; + shapeEvent.winDrag[1] = glWinY - winDraggedLast[1]; + winDraggedLast[0] = glWinX; + winDraggedLast[1] = glWinY; + if( 1 == e.getPointerCount() ) { + final float sdx = shapeEvent.objDrag.x() * scale.x(); // apply scale, since operation + final float sdy = shapeEvent.objDrag.y() * scale.y(); // is from a scaled-model-viewpoint + if( isIO(IO_IN_RESIZE_BR) || isIO(IO_IN_RESIZE_BL) ) { + final float bw = box.getWidth(); + final float bh = box.getHeight(); + final float sdy2, sx, sy; + if( isIO(IO_IN_RESIZE_BR) ) { + sx = scale.x() + sdx/bw; // bottom-right + } else { + sx = scale.x() - sdx/bw; // bottom-left + } + if( isFixedARatioResize() ) { + sy = sx; + sdy2 = bh * ( scale.y() - sy ); + } else { + sdy2 = sdy; + sy = scale.y() - sdy2/bh; + } + if( resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip + if( DEBUG ) { + System.err.printf("DragZoom: resize[br %b, bl %b], win[%4d, %4d], , flip[x %b, y %b], obj[%s], dxy +[%s], sdxy +[%.4f, %.4f], sdxy2 +[%.4f, %.4f], scale [%s] -> [%.4f, %.4f]%n", + isIO(IO_IN_RESIZE_BR), isIO(IO_IN_RESIZE_BL), glWinX, glWinY, x_flip, y_flip, objPos, + shapeEvent.objDrag, sdx, sdy, sdx, sdy2, + scale, sx, sy); } - if( isFixedARatioResize() ) { - sy = sx; - sdy2 = bh * ( scale.y() - sy ); + if( isIO(IO_IN_RESIZE_BR) ) { + move( 0, sdy2, 0f); // bottom-right, sticky left- and top-edge } else { - sdy2 = sdy; - sy = scale.y() - sdy2/bh; + move( sdx, sdy2, 0f); // bottom-left, sticky right- and top-edge } - if( resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip - if( DEBUG ) { - System.err.printf("DragZoom: resize[br %b, bl %b], win[%4d, %4d], , flip[x %b, y %b], obj[%s], dxy +[%s], sdxy +[%.4f, %.4f], sdxy2 +[%.4f, %.4f], scale [%s] -> [%.4f, %.4f]%n", - isIO(IO_IN_RESIZE_BR), isIO(IO_IN_RESIZE_BL), glWinX, glWinY, x_flip, y_flip, objPos, - shapeEvent.objDrag, sdx, sdy, sdx, sdy2, - scale, sx, sy); - } - if( isIO(IO_IN_RESIZE_BR) ) { - move( 0, sdy2, 0f); // bottom-right, sticky left- and top-edge - } else { - move( sdx, sdy2, 0f); // bottom-left, sticky right- and top-edge - } - setScale(sx, sy, scale.z()); - } - return; // FIXME: pass through event? Issue zoom event? - } else if( isIO(IO_IN_MOVE) ) { - if( DEBUG ) { - System.err.printf("DragMove: win[%4d, %4d] +[%2d, %2d], , flip[x %b, y %b], obj[%s] +[%s], rot %s%n", - glWinX, glWinY, shapeEvent.winDrag[0], shapeEvent.winDrag[1], - x_flip, y_flip, objPos, shapeEvent.objDrag, euler); - } - move( sdx, sdy, 0f); - // FIXME: Pass through event? Issue move event? + setScale(sx, sy, scale.z()); + } + return true; // end signal traversal with completed drag + } else if( isIO(IO_IN_MOVE) ) { + if( DEBUG ) { + System.err.printf("DragMove: win[%4d, %4d] +[%2d, %2d], , flip[x %b, y %b], obj[%s] +[%s], rot %s%n", + glWinX, glWinY, shapeEvent.winDrag[0], shapeEvent.winDrag[1], + x_flip, y_flip, objPos, shapeEvent.objDrag, euler); } + move( sdx, sdy, 0f); + return true; // end signal traversal with completed move } } - break; - } + } // resizableOrDraggable && EVENT_MOUSE_DRAGGED e.setAttachment(shapeEvent); for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { @@ -1611,6 +1624,7 @@ public abstract class Shape { throw new NativeWindowException("Unexpected mouse event type " + e.getEventType()); } } + return e.isConsumed(); // end signal traversal if consumed } /** -- cgit v1.2.3