From 121260d2b7bc4f57a16ca53ed1b08082d7977bbe Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
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(-)

(limited to 'src/graphui/classes')

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