/** * Copyright 2010-2024 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; import java.util.ArrayList; import java.util.Comparator; import java.util.concurrent.atomic.AtomicInteger; 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.opengl.RegionRenderer; import com.jogamp.graph.ui.layout.Padding; import com.jogamp.math.FloatUtil; import com.jogamp.math.Matrix4f; import com.jogamp.math.Quaternion; import com.jogamp.math.Recti; import com.jogamp.math.Vec2f; import com.jogamp.math.Vec3f; import com.jogamp.math.Vec4f; import com.jogamp.math.geom.AABBox; import com.jogamp.math.util.PMVMatrix4f; import com.jogamp.newt.event.GestureHandler.GestureEvent; import com.jogamp.newt.event.GestureHandler.GestureListener; import com.jogamp.newt.event.KeyEvent; import com.jogamp.newt.event.KeyListener; import com.jogamp.newt.event.MouseAdapter; import com.jogamp.newt.event.NEWTEvent; import com.jogamp.newt.event.PinchToZoomGesture; import com.jogamp.newt.event.MouseEvent; import com.jogamp.newt.event.MouseListener; /** * Generic Shape, potentially using a Graph via {@link GraphShape} or other means of representing content. *

* A shape includes the following build-in user-interactions * - drag shape w/ 1-pointer click, see {@link #setDraggable(boolean)} * - resize shape w/ 1-pointer click and drag in 1/4th bottom-left and bottom-right corner, see {@link #setResizable(boolean)}. *

*

* A shape is expected to have its 0/0 origin in its bottom-left corner, otherwise the drag-zoom sticky-edge will not work as expected. *

*

* A shape's {@link #getBounds()} includes its optional {@link #getPadding()} and optional {@link #getBorderThickness()}. *

*

* GraphUI is GPU based and resolution independent. *

*

* GraphUI is intended to become an immediate- and retained-mode API. *

*

* Default colors (toggle-off is full color): * - non-toggle: 0.6 * color, static -> 0.6 * - pressed: 0.8 * color, static -> 0.5 * - toggle-off: 1.0 * color, static -> 0.6 * - toggle-on: 0.8 * color *

* @see Scene */ public abstract class Shape { /** * General {@link Shape} visitor */ public static interface Visitor1 { /** * Visitor method * @param s the {@link Shape} to process * @return true to signal operation complete and to stop traversal, otherwise false */ boolean visit(Shape s); } /** * General {@link Shape} visitor */ public static interface Visitor2 { /** * Visitor method * @param s the {@link Shape} to process * @param pmv the {@link PMVMatrix4f} setup from the {@link Scene} down to the {@link Shape} * @return true to signal operation complete and to stop traversal, otherwise false */ boolean visit(Shape s, final PMVMatrix4f pmv); } /** * {@link Shape} move listener */ public static interface MoveListener { /** * Move callback * @param s the moved shape * @param origin original position * @param dest new position */ void run(Shape s, Vec3f origin, Vec3f dest); } /** * {@link Shape} pointer listener, e.g. for {@link Shape#onClicked(PointerListener)} */ public static interface PointerListener { /** * Event callback * @param s the associated {@link Shape} for this event * @param pos relative object coordinates to the associated {@link Shape} * @param e original Newt {@link MouseEvent} */ void run(Shape s, final Vec3f pos, MouseEvent e); } /** * General {@link Shape} listener action */ public static interface Listener { void run(final Shape shape); } /** * {@link Shape} listener action returning a boolean value */ public static interface ListenerBool { boolean run(final Shape shape); } /** * Forward {@link KeyListener}, to be attached to a key event source forwarded to the receiver set at constructor. *

* This given receiver {@link Shape} must be {@link #setInteractive(boolean)} to have the events forwarded. *

* @see Shape#receiveKeyEvents(Shape) */ public static class ForwardKeyListener implements KeyListener { public final Shape receiver; /** * {@link ForwardKeyListener} Constructor * @param receiver the {@link KeyListener} receiver */ public ForwardKeyListener(final Shape receiver) { this.receiver = receiver; } private void dispatch(final KeyEvent e) { if( receiver.isInteractive() ) { receiver.dispatchKeyEvent(e); } } @Override public void keyPressed(final KeyEvent e) { dispatch(e); } @Override public void keyReleased(final KeyEvent e) { dispatch(e); } } /** * Forward {@link MouseGestureListener}, to be attached to a mouse event source forwarded to the receiver set at constructor. *

* This given receiver {@link Shape} must be {@link #setInteractive(boolean)} to have the events forwarded. *

* @see Shape#receiveMouseEvents(Shape) */ public static class ForwardMouseListener implements MouseGestureListener { public final Shape receiver; /** * {@link ForwardMouseListener} Constructor * @param receiver the {@link MouseGestureListener} receiver */ public ForwardMouseListener(final Shape receiver) { this.receiver = receiver; } private void dispatch(final MouseEvent e) { if( receiver.isInteractive() ) { receiver.dispatchMouseEvent(e); } } @Override public void mouseClicked(final MouseEvent e) { dispatch(e); } @Override public void mouseEntered(final MouseEvent e) { dispatch(e); } @Override public void mouseExited(final MouseEvent e) { dispatch(e); } @Override public void mousePressed(final MouseEvent e) { dispatch(e); } @Override public void mouseReleased(final MouseEvent e) { dispatch(e); } @Override public void mouseMoved(final MouseEvent e) { dispatch(e); } @Override public void mouseDragged(final MouseEvent e) { dispatch(e); } @Override public void mouseWheelMoved(final MouseEvent e) { dispatch(e); } @Override public void gestureDetected(final GestureEvent e) { if( receiver.isInteractive() ) { receiver.dispatchGestureEvent(e); } } }; protected static final boolean DEBUG_DRAW = false; private static final boolean DEBUG = false; private static final int DIRTY_SHAPE = 1 << 0 ; private static final int DIRTY_STATE = 1 << 1 ; private volatile Group parent = null; protected final AABBox box = new AABBox(); private final Vec3f position = new Vec3f(); private float zOffset = 0; private final Quaternion rotation = new Quaternion(); private Vec3f rotPivot = null; private final Vec3f scale = new Vec3f(1f, 1f, 1f); private final Matrix4f iMat = new Matrix4f(); private final Matrix4f tmpMat = new Matrix4f(); private volatile boolean iMatIdent = true; private volatile boolean iMatDirty = false; private final AtomicInteger dirty = new AtomicInteger(DIRTY_SHAPE | DIRTY_STATE); private final Object dirtySync = new Object(); /** Default base-color w/o color channel, will be modulated w/ pressed- and toggle color */ protected final Vec4f rgbaColor = new Vec4f(0.60f, 0.60f, 0.60f, 1.0f); /** Default pressed color-factor (darker and slightly transparent), modulates base-color. ~0.65 (due to alpha) */ protected final Vec4f pressedRGBAModulate = new Vec4f(0.70f, 0.70f, 0.70f, 0.8f); /** Default toggle color-factor (darker), modulates base-color. 0.60 * 0.83 ~= 0.50 */ protected final Vec4f toggleOnRGBAModulate = new Vec4f(0.83f, 0.83f, 0.83f, 1.0f); /** Default toggle color-factor (original), modulates base-color. 0.60 * 1.00 ~= 0.60 */ protected final Vec4f toggleOffRGBAModulate = new Vec4f(1.00f, 1.00f, 1.00f, 1.0f); /** Default active color-factor (dark), modulates base-color. 0.60 * 0.25 ~= 0.15 */ protected final Vec4f activeRGBAModulate = new Vec4f(0.25f, 0.25f, 0.25f, 1.0f); protected boolean activeRGBAModulateOn = false; private final Vec4f rgba_tmp = new Vec4f(0, 0, 0, 1); private final Vec4f cWhite = new Vec4f(1, 1, 1, 1); private int id = -1; private String name = "noname"; private static final int IO_VISIBLE = 1 << 0; private static final int IO_INTERACTIVE = 1 << 1; private static final int IO_ACTIVABLE = 1 << 2; private static final int IO_TOGGLEABLE = 1 << 3; private static final int IO_DRAGGABLE = 1 << 4; private static final int IO_RESIZABLE = 1 << 5; private static final int IO_RESIZE_FIXED_RATIO = 1 << 6; private static final int IO_ACTIVE = 1 << 7; private static final int IO_DISCARDED = 1 << 25; private static final int IO_DOWN = 1 << 26; private static final int IO_TOGGLE = 1 << 27; private static final int IO_DRAG_FIRST = 1 << 28; private static final int IO_IN_MOVE = 1 << 29; private static final int IO_IN_RESIZE_BR = 1 << 30; private static final int IO_IN_RESIZE_BL = 1 << 31; private volatile int ioState = IO_DRAGGABLE | IO_RESIZABLE | IO_INTERACTIVE | IO_ACTIVABLE | IO_VISIBLE; private final boolean isIO(final int mask) { return mask == ( ioState & mask ); } private final Shape setIO(final int mask, final boolean v) { if( v ) { ioState |= mask; } else { ioState &= ~mask; } return this; } private float borderThickness = 0f; private Padding padding = null; private final Vec4f borderColor = new Vec4f(0.0f, 0.0f, 0.0f, 1.0f); private ArrayList mouseListeners = new ArrayList(); private ArrayList keyListeners = new ArrayList(); private ListenerBool onInitListener = null; private PointerListener onHoverListener = null; private MoveListener onMoveListener = null; private Listener onToggleListener = null; private ArrayList activationListeners = new ArrayList(); private PointerListener onClickedListener = null; private final Vec2f objDraggedFirst = new Vec2f(); // b/c its relative to Shape and we stick to it private final int[] winDraggedLast = { 0, 0 }; // b/c its absolute window pos private static final float resize_sxy_min = 1f/200f; // 1/2% - TODO: Maybe customizable? private static final float resize_section = 1f/5f; // resize action in a corner private volatile Tooltip tooltip = null; /** * Create a generic UI {@link Shape} */ protected Shape() { } protected void setParent(final Group c) { parent = c; } /** * Returns the last parent container {@link Group} this shape has been added to or {@code null}. *

* Since a shape can be added to multiple container (DAG), * usability of this information depends on usage. *

*/ public Group getParent() { return parent; } /** Set a symbolic ID for this shape for identification. Default is -1 for noname. */ public final Shape setID(final int id) { this.id = id; return this; } /** Return the optional symbolic ID for this shape. */ public final int getID() { return this.id; } /** Set a symbolic name for this shape for identification. Default is `noname`. */ public Shape setName(final String name) { this.name = name; return this; } /** Return the optional symbolic name for this shape, defaults to `noname`. */ public final String getName() { return this.name; } /** Returns true if this shape denotes a {@link Group}, otherwise false. */ public boolean isGroup() { return false; } /** * Returns true if this shape is set {@link #setVisible(boolean) visible} by the user, otherwise false. Defaults to true. *

* Note that invisible shapes are not considered for picking/activation. *

* @see #isInteractive() */ public final boolean isVisible() { return isIO(IO_VISIBLE); } /** * Enable (default) or disable this shape's visibility. *

* Note that invisible shapes are not considered for picking/activation. *

*

* This visibility flag is toggled by the user only. *

*/ public final Shape setVisible(final boolean v) { return setIO(IO_VISIBLE, v); } /** * Sets the unscaled padding for this shape, which is included in unscaled {@link #getBounds()} and also includes the border. Default is zero. *

* Method issues {@link #markShapeDirty()}. *

* @param padding distance of shape to the border, i.e. padding * @return this shape for chaining * @see #getPadding() * @see #hasPadding() * @see #markShapeDirty() */ public final Shape setPaddding(final Padding padding) { this.padding = padding; markShapeDirty(); return this; } /** * Returns unscaled {@link Padding} of this shape, which is included in unscaled {@link #getBounds()} and also includes the border. Default is zero. * @see #setPaddding(Padding) * @see #hasPadding() */ public Padding getPadding() { return padding; } /** Returns true if {@link #setPaddding(Padding)} added a non {@link Padding#zeroSize()} spacing to this shape. */ public boolean hasPadding() { return null != padding && !padding.zeroSize(); } /** * Sets the thickness of the border, which is included in {@link #getBounds()} and is outside of {@link #getPadding()}. Default is zero for no border. *

* Method issues {@link #markShapeDirty()}. *

* @param thickness border thickness, zero for no border * @return this shape for chaining * @see #setBorderColor(Vec4f) * @see #markShapeDirty() */ public final Shape setBorder(final float thickness) { borderThickness = Math.max(0f, thickness); markShapeDirty(); return this; } /** Returns true if a border has been enabled via {@link #setBorder(float, Padding)}. */ public final boolean hasBorder() { return !FloatUtil.isZero(borderThickness); } /** Returns the border thickness, see {@link #setBorder(float, Padding)}. */ public final float getBorderThickness() { return borderThickness; } /** Perform given {@link Runnable} action synchronized */ public final void runSynced(final Runnable action) { synchronized ( dirtySync ) { action.run(); } } /** * Clears all data and reset all states as if this instance was newly created * @param gl current {@link GL2ES2} instance used to release GPU resources * @param renderer {@link RegionRenderer} used to release GPU resources */ public final void clear(final GL2ES2 gl, final RegionRenderer renderer) { synchronized ( dirtySync ) { stopToolTip(); clearImpl0(gl, renderer); resetState(); } } private final void resetState() { position.set(0f, 0f, 0f); rotation.setIdentity(); rotPivot = null; scale.set(1f, 1f, 1f); iMat.loadIdentity(); iMatIdent = true; iMatDirty = false; box.reset(); mouseListeners.clear(); keyListeners.clear(); onInitListener = null; onMoveListener = null; onToggleListener = null; activationListeners.clear(); onClickedListener = null; onHoverListener = null; markShapeDirty(); } /** * Destroys all data * @param gl current {@link GL2ES2} instance used to release GPU resources * @param renderer {@link RegionRenderer} used to release GPU resources */ public final void destroy(final GL2ES2 gl, final RegionRenderer renderer) { removeToolTip(); destroyImpl0(gl, renderer); resetState(); } /** * Set a user one-shot initializer callback. *

* {@link ListenerBool#run(Shape)} will be called * after each {@link #draw(GL2ES2, RegionRenderer)} * until it returns true, signaling user initialization is completed. *

* @param l callback, which shall return true signaling user initialization is done */ public final void onInit(final ListenerBool l) { onInitListener = l; } /** * Set user callback to be notified when a pointer/mouse is moving over this shape */ public final void onHover(final PointerListener l) { onHoverListener = l; } /** * Set user callback to be notified when shape is {@link #move(Vec3f)}'ed. */ public final void onMove(final MoveListener l) { onMoveListener = l; } /** * Set user callback to be notified when shape {@link #toggle()}'ed. *

* This is usually the case when clicked, see {@link #onClicked(PointerListener)}. *

*

* Use {@link #isToggleOn()} to retrieve the state. *

*/ public final void onToggle(final Listener l) { onToggleListener = l; } /** * Set user callback to be notified when shape is clicked. *

* Usually shape is {@link #toggle()}'ed when clicked, see {@link #onToggle(Listener)}. * However, in case shape is not {@link #isToggleable()} this is the last resort. *

*/ public final void onClicked(final PointerListener l) { onClickedListener = l; } /** * Add user callback to be notified when shape is activated (pointer-over and/or click) or de-activated (pointer left). *

* Use {@link #isActive()} to retrieve the state. *

*/ public final Shape addActivationListener(final Listener l) { if(l == null) { return this; } @SuppressWarnings("unchecked") final ArrayList clonedListeners = (ArrayList) activationListeners.clone(); clonedListeners.add(l); activationListeners = clonedListeners; return this; } public final Shape removeActivationListener(final Listener l) { if (l == null) { return this; } @SuppressWarnings("unchecked") final ArrayList clonedListeners = (ArrayList) activationListeners.clone(); clonedListeners.remove(l); activationListeners = clonedListeners; return this; } /** * Dispatch activation event event to this shape * @return true to signal operation complete and to stop traversal, otherwise false */ private final void dispatchActivationEvent(final Shape s) { final int sz = activationListeners.size(); for(int i = 0; i < sz; i++ ) { activationListeners.get(i).run(s); } } /** Move to scaled position. Position ends up in PMVMatrix4f unmodified. No {@link MoveListener} notification will occur. */ public final Shape moveTo(final float tx, final float ty, final float tz) { position.set(tx, ty, tz); iMatDirty = true; return this; } /** Move to scaled position. Position ends up in PMVMatrix4f unmodified. No {@link MoveListener} notification will occur. */ public final Shape moveTo(final Vec3f t) { position.set(t); iMatDirty = true; return this; } /** Move about scaled distance. Position ends up in PMVMatrix4f unmodified. No {@link MoveListener} notification will occur. */ public final Shape move(final float dtx, final float dty, final float dtz) { position.add(dtx, dty, dtz); iMatDirty = true; return this; } /** Move about scaled distance. Position ends up in PMVMatrix4f unmodified. No {@link MoveListener} notification will occur. */ public final Shape move(final Vec3f dt) { position.add(dt); iMatDirty = true; return this; } private final Shape moveNotify(final float dtx, final float dty, final float dtz) { forwardMove(position.copy(), position.add(dtx, dty, dtz)); return this; } private final void forwardMove(final Vec3f origin, final Vec3f dest) { if( !origin.isEqual(dest) ) { iMatDirty = true; if( null != onMoveListener ) { onMoveListener.run(this, origin, dest); } } } /** * Returns position {@link Vec3f} reference, i.e. scaled translation as set via {@link #moveTo(float, float, float) or {@link #move(float, float, float)}}. */ public final Vec3f getPosition() { iMatDirty = true; return position; } /** * Returns {@link Quaternion} for rotation. */ public final Quaternion getRotation() { iMatDirty = true; return rotation; } /** * Sets the rotation {@link Quaternion}. * @return this shape for chaining */ public final Shape setRotation(final Quaternion q) { rotation.set(q); iMatDirty = true; return this; } /** * Return unscaled rotation origin {@link Vec3f} reference, aka pivot. Null if not set via {@link #setRotationPivot(float, float, float)}. * @see #updateMat() */ public final Vec3f getRotationPivot() { return rotPivot; } /** * Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GL2ES2, GLProfile)}. * @return this shape for chaining */ public final Shape setRotationPivot(final float px, final float py, final float pz) { rotPivot = new Vec3f(px, py, pz); iMatDirty = true; return this; } /** * Set unscaled rotation origin, aka pivot. Usually the {@link #getBounds()} center and should be set while {@link #validateImpl(GL2ES2, GLProfile)}. * @param pivot rotation origin * @return this shape for chaining */ public final Shape setRotationPivot(final Vec3f pivot) { rotPivot = new Vec3f(pivot); iMatDirty = true; return this; } /** * Set scale factor to given scale. * @see #scale(Vec3f) * @see #getScale() */ public final Shape setScale(final Vec3f s) { scale.set(s); iMatDirty = true; return this; } /** * Set scale factor to given scale. * @see #scale(float, float, float) * @see #getScale() */ public final Shape setScale(final float sx, final float sy, final float sz) { scale.set(sx, sy, sz); iMatDirty = true; return this; } /** * Multiply current scale factor by given scale. * @see #setScale(Vec3f) * @see #getScale() */ public final Shape scale(final Vec3f s) { scale.mul(s); iMatDirty = true; return this; } /** * Multiply current scale factor by given scale. * @see #setScale(float, float, float) * @see #getScale() */ public final Shape scale(final float sx, final float sy, final float sz) { scale.mul(sx, sy, sz); iMatDirty = true; return this; } /** * Returns scale {@link Vec3f} reference. * @see #setScale(float, float, float) * @see #scale(float, float, float) * @see #updateMat() */ public final Vec3f getScale() { return scale; } /** * Marks the shape dirty, causing next {@link #draw(GL2ES2, RegionRenderer) draw()} * to recreate the Graph shape and reset the region. */ public final void markShapeDirty() { dirty.updateAndGet((final int pre) -> { return pre | DIRTY_SHAPE; } ); } /** * Marks the rendering state dirty, causing next {@link #draw(GL2ES2, RegionRenderer) draw()} * to notify the Graph region to reselect shader and repaint potentially used FBOs. */ public final void markStateDirty() { dirty.updateAndGet((final int pre) -> { return pre | DIRTY_STATE; } ); } /** Returns the shape's dirty state, see {@link #markShapeDirty()}. */ protected boolean isShapeDirty() { return 0 != ( dirty.get() & DIRTY_SHAPE ) ; } /** Returns the rendering dirty state, see {@link #markStateDirty()}. */ protected final boolean isStateDirty() { return 0 != ( dirty.get() & DIRTY_STATE ) ; } protected final String getDirtyString() { if( isShapeDirty() && isShapeDirty() ) { return "dirty[shape, state]"; } else if( isShapeDirty() ) { return "dirty[shape]"; } else if( isStateDirty() ) { return "dirty[state]"; } else { return "clean"; } } /** * Returns the unscaled bounding {@link AABBox} for this shape, borrowing internal instance. * * The returned {@link AABBox} will cover the unscaled shape * as well as its optional {@link #getPadding()} and optional {@link #getBorderThickness()}. * * The returned {@link AABBox} is only valid after an initial call to {@link #draw(GL2ES2, RegionRenderer) draw(..)} * or {@link #validate(GL2ES2)}. * * @see #getBounds(GLProfile) */ public final AABBox getBounds() { return box; } /** * Returns the scaled width of the bounding {@link AABBox} for this shape. * * The returned width will cover the scaled shape * as well as its optional scaled {@link #getPadding()} and optional scaled {@link #getBorderThickness()}. * * The returned width is only valid after an initial call to {@link #draw(GL2ES2, RegionRenderer) draw(..)} * or {@link #validate(GL2ES2)}. * * @see #getBounds() */ public final float getScaledWidth() { return box.getWidth() * getScale().x(); } /** * Returns the scaled height of the bounding {@link AABBox} for this shape. * * The returned height will cover the scaled shape * as well as its optional scaled {@link #getPadding()} and optional scaled {@link #getBorderThickness()}. * * The returned height is only valid after an initial call to {@link #draw(GL2ES2, RegionRenderer) draw(..)} * or {@link #validate(GL2ES2)}. * * @see #getBounds() */ public final float getScaledHeight() { return box.getHeight() * getScale().y(); } public final float getScaledDepth() { return box.getDepth() * getScale().z(); } /** * Returns the unscaled bounding {@link AABBox} for this shape. * * This variant differs from {@link #getBounds()} as it * returns a valid {@link AABBox} even before {@link #draw(GL2ES2, RegionRenderer) draw(..)} * and having an OpenGL instance available. * * @see #getBounds() */ public final AABBox getBounds(final GLProfile glp) { validate(glp); return box; } /** Experimental selection draw command used by {@link Scene}. */ public void drawToSelect(final GL2ES2 gl, final RegionRenderer renderer) { synchronized ( dirtySync ) { validate(gl); drawToSelectImpl0(gl, renderer); } } /** * Renders the shape. *

* {@link #applyMatToMv(PMVMatrix4f)} is expected to be completed beforehand. *

* @param gl the current GL object * @param renderer {@link RegionRenderer} which might be used for Graph Curve Rendering, also source of {@link RegionRenderer#getMatrix()} and {@link RegionRenderer#getViewport()}. */ public void draw(final GL2ES2 gl, final RegionRenderer renderer) { final boolean isPressed = isPressed(), isToggleOn = isToggleOn(); final Vec4f rgba; if( hasColorChannel() ) { if( isPressed ) { rgba = pressedRGBAModulate; } else if( isToggleable() ) { if( isToggleOn ) { rgba = toggleOnRGBAModulate; } else { rgba = toggleOffRGBAModulate; } } else if( activeRGBAModulateOn && isActive() ) { rgba = activeRGBAModulate; } else { rgba = cWhite; } } else { rgba = rgba_tmp; if( isPressed ) { rgba.mul(rgbaColor, pressedRGBAModulate); } else if( isToggleable() ) { if( isToggleOn ) { rgba.mul(rgbaColor, toggleOnRGBAModulate); } else { rgba.mul(rgbaColor, toggleOffRGBAModulate); } } else if( activeRGBAModulateOn && isActive() ) { rgba.mul(rgbaColor, activeRGBAModulate); } else { rgba.set(rgbaColor); } } synchronized ( dirtySync ) { validate(gl); drawImpl0(gl, renderer, rgba); } if( null != onInitListener ) { if( onInitListener.run(this) ) { onInitListener = null; } } } /** * Validates the shape's underlying {@link GLRegion}. *

* If the region is dirty, it gets {@link GLRegion#clear(GL2ES2) cleared} and is reused. *

* @param gl current {@link GL2ES2} object * @see #validate(GLProfile) */ public final Shape validate(final GL2ES2 gl) { synchronized ( dirtySync ) { if( isShapeDirty() ) { box.reset(); } validateImpl(gl, gl.getGLProfile()); dirty.set(0); } return this; } /** * Validates the shape's underlying {@link GLRegion} w/o a current {@link GL2ES2} object *

* If the region is dirty a new region is created * and the old one gets pushed to a dirty-list to get disposed when a GL context is available. *

* @see #validate(GL2ES2) */ public final Shape validate(final GLProfile glp) { synchronized ( dirtySync ) { if( isShapeDirty() ) { box.reset(); } validateImpl(null, glp); dirty.set(0); } return this; } /** * Validate the shape via {@link #validate(GL2ES2)} if {@code gl} is not null, * otherwise uses {@link #validate(GLProfile)}. * @see #validate(GL2ES2) * @see #validate(GLProfile) */ public final Shape validate(final GL2ES2 gl, final GLProfile glp) { if( null != gl ) { return validate(gl); } else { return validate(glp); } } /** * Applies the internal {@link Matrix4f} to the given {@link PMVMatrix4f#getMv() modelview matrix}, * i.e. {@code pmv.mulMv( getMat() )}. *

* Calls {@link #updateMat()} if dirty. *

* In case {@link #isMatIdentity()} is {@code true}, implementation is a no-operation. *

* @param pmv the matrix * @see #isMatIdentity() * @see #updateMat() * @see #getMat() * @see PMVMatrix4f#mulMv(Matrix4f) */ public final void applyMatToMv(final PMVMatrix4f pmv) { if( iMatDirty ) { updateMat(); } if( !iMatIdent ) { pmv.mulMv(iMat); } } /** * Returns the internal {@link Matrix4f} reference. *

* Calls {@link #updateMat()} if dirty. *

* @see #getMat(Matrix4f) * @see #applyMatToMv(PMVMatrix4f) * @see #updateMat() */ public final Matrix4f getMat() { if( iMatDirty ) { updateMat(); } return iMat; } /** * Returns a copy of the internal {@link Matrix4f} to {@code out}. *

* Calls {@link #updateMat()} if dirty. *

* @see #getMat() * @see #applyMatToMv(PMVMatrix4f) * @see #updateMat() */ public final Matrix4f getMat(final Matrix4f out) { if( iMatDirty ) { updateMat(); } out.load(iMat); return out; } /** * Returns true if {@link #getMat()} has not been mutated, i.e. contains identity. * @see #updateMat() */ public final boolean isMatIdentity() { return iMatIdent; } /** * Updates the internal {@link Matrix4f} with local position, rotation and scale. *
    *
  • Scale shape from its center position
  • *
  • Rotate shape around optional scaled pivot, see {@link #setRotationPivot(float[])}), otherwise rotate around its scaled center (default)
  • *
*

* Shape's origin should be bottom-left @ 0/0 to have build-in drag-zoom work properly. *

*

* Sets {@link #isMatIdentity()} to {@code true} if neither position, scale or rotate is performed, otherwise to {@code false}. *

*

* Called by {@link #applyMatToMv(PMVMatrix4f)}, {@link #getMat()} and {@link #getMat(Matrix4f)} if internal matrix is dirty. *

*

* After any mutating operations, .e.g {@link #move(float, float, float)} etc, the internal matrix is marked dirty. *

* @see #isMatIdentity() * @see #getMat() * @see #getPosition() * @see #getScale() * @see #getRotation() * @see #getRotationPivot() * @see #applyMatToMv(PMVMatrix4f) */ public final void updateMat() { final boolean hasPos = !position.isZero(); final boolean hasScale = !scale.isEqual(Vec3f.ONE); final boolean hasRotate = !rotation.isIdentity(); final boolean hasRotPivot = null != rotPivot; final Vec3f ctr = box.getCenter(); final boolean sameScaleRotatePivot = hasScale && hasRotate && ( !hasRotPivot || rotPivot.isEqual(ctr) ); if( sameScaleRotatePivot ) { iMatIdent = false; iMat.setToTranslation(position); // identity + translate, scaled // Scale shape from its center position and rotate around its center iMat.translate(ctr.x()*scale.x(), ctr.y()*scale.y(), ctr.z()*scale.z(), tmpMat); // add-back center, scaled iMat.rotate(rotation, tmpMat); iMat.scale(scale.x(), scale.y(), scale.z(), tmpMat); iMat.translate(-ctr.x(), -ctr.y(), -ctr.z(), tmpMat); // move to center } else if( hasRotate || hasScale ) { iMatIdent = false; iMat.setToTranslation(position); // identity + translate, scaled if( hasRotate ) { if( hasRotPivot ) { // Rotate shape around its scaled pivot iMat.translate(rotPivot.x()*scale.x(), rotPivot.y()*scale.y(), rotPivot.z()*scale.z(), tmpMat); // pivot back from rot-pivot, scaled iMat.rotate(rotation, tmpMat); iMat.translate(-rotPivot.x()*scale.x(), -rotPivot.y()*scale.y(), -rotPivot.z()*scale.z(), tmpMat); // pivot to rot-pivot, scaled } else { // Rotate shape around its scaled center iMat.translate(ctr.x()*scale.x(), ctr.y()*scale.y(), ctr.z()*scale.z(), tmpMat); // pivot back from center-pivot, scaled iMat.rotate(rotation, tmpMat); iMat.translate(-ctr.x()*scale.x(), -ctr.y()*scale.y(), -ctr.z()*scale.z(), tmpMat); // pivot to center-pivot, scaled } } if( hasScale ) { // Scale shape from its center position iMat.translate(ctr.x()*scale.x(), ctr.y()*scale.y(), ctr.z()*scale.z(), tmpMat); // add-back center, scaled iMat.scale(scale.x(), scale.y(), scale.z(), tmpMat); iMat.translate(-ctr.x(), -ctr.y(), -ctr.z(), tmpMat); // move to center } } else if( hasPos ) { iMatIdent = false; iMat.setToTranslation(position); // identity + translate, scaled } else { iMatIdent = true; iMat.loadIdentity(); } iMatDirty = false; } /** * {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) Setup} the given {@link PMVMatrix4f} * and apply this shape's {@link #applyMatToMv(PMVMatrix4f) transformation}. *

* @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} given {@link PMVMatrix4f} {@code pmv}. * @param viewport used viewport for {@link PMVMatrix4f#mapObjToWin(Vec3f, Recti, Vec3f)} * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @return the given {@link PMVMatrix4f} for chaining * @see Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) * @see #applyMatToMv(PMVMatrix4f) * @see #setPMVMatrix(Scene, PMVMatrix4f) */ public final PMVMatrix4f setPMVMatrix(final Scene.PMVMatrixSetup pmvMatrixSetup, final Recti viewport, final PMVMatrix4f pmv) { pmvMatrixSetup.set(pmv, viewport); applyMatToMv(pmv); return pmv; } /** * {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) Setup} the given {@link PMVMatrix4f} * and apply this shape's {@link #applyMatToMv(PMVMatrix4f) transformation}. *

* @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @return the given {@link PMVMatrix4f} for chaining * @see Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) * @see #applyMatToMv(PMVMatrix4f) * @see #setPMVMatrix(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, PMVMatrix4f) */ public final PMVMatrix4f setPMVMatrix(final Scene scene, final PMVMatrix4f pmv) { return setPMVMatrix(scene.getPMVMatrixSetup(), scene.getViewport(), pmv); } /** * Retrieve surface (view) port of this shape, i.e. lower x/y position and size. *

* The given {@link PMVMatrix4f} has to be setup properly for this object, * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. See {@link #setPMVMatrix(Scene, PMVMatrix4f)}. *

* @param pmv well formed {@link PMVMatrix4f}, e.g. could have been setup via {@link Shape#setPMVMatrix(Scene, PMVMatrix4f)}. * @param viewport the int[4] viewport * @param surfacePort Recti target surface port * @return given Recti {@code surfacePort} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} */ public final Recti getSurfacePort(final PMVMatrix4f pmv, final Recti viewport, final Recti surfacePort) { final Vec3f winCoordHigh = new Vec3f(); final Vec3f winCoordLow = new Vec3f(); final Vec3f high = box.getHigh(); final Vec3f low = box.getLow(); final Matrix4f matPMv = pmv.getPMv(); if( Matrix4f.mapObjToWin(high, matPMv, viewport, winCoordHigh) ) { if( Matrix4f.mapObjToWin(low, matPMv, viewport, winCoordLow) ) { surfacePort.setX( (int)Math.abs( winCoordLow.x() ) ); surfacePort.setY( (int)Math.abs( winCoordLow.y() ) ); surfacePort.setWidth( (int)Math.abs( winCoordHigh.x() - winCoordLow.x() ) ); surfacePort.setHeight( (int)Math.abs( winCoordHigh.y() - winCoordLow.y() ) ); return surfacePort; } } return null; } /** * Retrieve surface (view) size in pixels of this shape. *

* The given {@link PMVMatrix4f} has to be setup properly for this object, * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. See {@link #setPMVMatrix(Scene, PMVMatrix4f)}. *

* @param pmv well formed {@link PMVMatrix4f}, e.g. could have been setup via {@link Shape#setPMVMatrix(Scene, PMVMatrix4f)}. * @param viewport the int[4] viewport * @param surfaceSize int[2] target surface size * @return given int[2] {@code surfaceSize} in pixels for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, PMVMatrix4f, int[]) * @see #getSurfaceSize(Scene, PMVMatrix4f, int[]) */ public final int[/*2*/] getSurfaceSize(final PMVMatrix4f pmv, final Recti viewport, final int[/*2*/] surfaceSize) { // System.err.println("Shape::getSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); final Vec3f winCoordHigh = new Vec3f(); final Vec3f winCoordLow = new Vec3f(); final Vec3f high = box.getHigh(); final Vec3f low = box.getLow(); final Matrix4f matPMv = pmv.getPMv(); if( Matrix4f.mapObjToWin(high, matPMv, viewport, winCoordHigh) ) { if( Matrix4f.mapObjToWin(low, matPMv, viewport, winCoordLow) ) { surfaceSize[0] = (int)Math.abs(winCoordHigh.x() - winCoordLow.x()); surfaceSize[1] = (int)Math.abs(winCoordHigh.y() - winCoordLow.y()); return surfaceSize; } } return null; } /** * Retrieve surface (view) size in pixels of this shape. *

* The given {@link PMVMatrix4f} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} properly for this shape * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. *

* @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} given {@link PMVMatrix4f} {@code pmv}. * @param viewport used viewport for {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @param surfaceSize int[2] target surface size * @return given int[2] {@code surfaceSize} in pixels for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #getSurfaceSize(PMVMatrix4f, Recti, int[]) * @see #getSurfaceSize(Scene, PMVMatrix4f, int[]) */ public final int[/*2*/] getSurfaceSize(final Scene.PMVMatrixSetup pmvMatrixSetup, final Recti viewport, final PMVMatrix4f pmv, final int[/*2*/] surfaceSize) { return getSurfaceSize(setPMVMatrix(pmvMatrixSetup, viewport, pmv), viewport, surfaceSize); } /** * Retrieve surface (view) size in pixels of this shape. *

* The given {@link PMVMatrix4f} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} properly for this shape * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. *

* @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @param surfaceSize int[2] target surface size * @return given int[2] {@code surfaceSize} in pixels for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #getSurfaceSize(PMVMatrix4f, Recti, int[]) * @see #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, PMVMatrix4f, int[]) */ public final int[/*2*/] getSurfaceSize(final Scene scene, final PMVMatrix4f pmv, final int[/*2*/] surfaceSize) { return getSurfaceSize(scene.getPMVMatrixSetup(), scene.getViewport(), pmv, surfaceSize); } /** * Retrieve pixel per scaled shape-coordinate unit, i.e. [px]/[obj]. * @param shapeSizePx int[2] shape size in pixel as retrieved via e.g. {@link #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, PMVMatrix4f, int[])} * @param pixPerShape float[2] pixel scaled per shape-coordinate unit result storage * @return given float[2] {@code pixPerShape} * @see #getPixelPerShapeUnit(Scene, PMVMatrix4f, float[]) * @see #getSurfaceSize(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, PMVMatrix4f, int[]) * @see #getScaledWidth() * @see #getScaledHeight() */ public final float[] getPixelPerShapeUnit(final int[] shapeSizePx, final float[] pixPerShape) { pixPerShape[0] = shapeSizePx[0] / getScaledWidth(); pixPerShape[0] = shapeSizePx[1] / getScaledHeight(); return pixPerShape; } /** * Retrieve pixel per scaled shape-coordinate unit, i.e. [px]/[obj]. *

* The given {@link PMVMatrix4f} has to be setup properly for this object, * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. See {@link #setPMVMatrix(Scene, PMVMatrix4f)}. *

* @param pmv well formed {@link PMVMatrix4f}, e.g. could have been setup via {@link Shape#setPMVMatrix(Scene, PMVMatrix4f)}. * @param viewport the int[4] viewport * @param pixPerShape float[2] pixel per scaled shape-coordinate unit result storage * @return given float[2] {@code pixPerShape} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #getPixelPerShapeUnit(int[], float[]) * @see #getSurfaceSize(Scene, PMVMatrix4f, int[]) * @see #getScaledWidth() * @see #getScaledHeight() */ public final float[] getPixelPerShapeUnit(final PMVMatrix4f pmv, final Recti viewport, final float[] pixPerShape) { final int[] shapeSizePx = new int[2]; if( null != getSurfaceSize(pmv, viewport, shapeSizePx) ) { return getPixelPerShapeUnit(shapeSizePx, pixPerShape); } else { return null; } } /** * Retrieve pixel per scaled shape-coordinate unit, i.e. [px]/[obj]. *

* The given {@link PMVMatrix4f} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} properly for this shape * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. *

* @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @param pixPerShape float[2] pixel per scaled shape-coordinate unit result storage * @return given float[2] {@code pixPerShape} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #getPixelPerShapeUnit(int[], float[]) * @see #getSurfaceSize(Scene, PMVMatrix4f, int[]) * @see #getScaledWidth() * @see #getScaledHeight() */ public final float[] getPixelPerShapeUnit(final Scene scene, final PMVMatrix4f pmv, final float[] pixPerShape) { final int[] shapeSizePx = new int[2]; if( null != getSurfaceSize(scene, pmv, shapeSizePx) ) { return getPixelPerShapeUnit(shapeSizePx, pixPerShape); } else { return null; } } /** * Map given object coordinate relative to this shape to window coordinates. *

* The given {@link PMVMatrix4f} has to be setup properly for this object, * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. See {@link #setPMVMatrix(Scene, PMVMatrix4f)}. *

* @param pmv well formed {@link PMVMatrix4f}, e.g. could have been setup via {@link Shape#setPMVMatrix(Scene, PMVMatrix4f)}. * @param viewport the viewport * @param objPos object position relative to this shape's center * @param glWinPos int[2] target window position of objPos relative to this shape * @return given int[2] {@code glWinPos} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #shapeToWinCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, float[], PMVMatrix4f, int[]) * @see #shapeToWinCoord(Scene, float[], PMVMatrix4f, int[]) */ public final int[/*2*/] shapeToWinCoord(final PMVMatrix4f pmv, final Recti viewport, final Vec3f objPos, final int[/*2*/] glWinPos) { // System.err.println("Shape::objToWinCoordgetSurfaceSize.VP "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]); final Vec3f winCoord = new Vec3f(); if( pmv.mapObjToWin(objPos, viewport, winCoord) ) { glWinPos[0] = (int)(winCoord.x()); glWinPos[1] = (int)(winCoord.y()); return glWinPos; } return null; } /** * Map given object coordinate relative to this shape to window coordinates. *

* The given {@link PMVMatrix4f} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} properly for this shape * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. *

* @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} given {@link PMVMatrix4f} {@code pmv}. * @param viewport used viewport for {@link PMVMatrix4f#mapObjToWin(Vec3f, Recti, Vec3f)} * @param objPos object position relative to this shape's center * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @param glWinPos int[2] target window position of objPos relative to this shape * @return given int[2] {@code glWinPos} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #shapeToWinCoord(PMVMatrix4f, Recti, float[], int[]) * @see #shapeToWinCoord(Scene, float[], PMVMatrix4f, int[]) */ public final int[/*2*/] shapeToWinCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final Recti viewport, final Vec3f objPos, final PMVMatrix4f pmv, final int[/*2*/] glWinPos) { return this.shapeToWinCoord(setPMVMatrix(pmvMatrixSetup, viewport, pmv), viewport, objPos, glWinPos); } /** * Map given object coordinate relative to this shape to window coordinates. *

* The given {@link PMVMatrix4f} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} properly for this shape * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. *

* @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. * @param objPos object position relative to this shape's center * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @param glWinPos int[2] target window position of objPos relative to this shape * @return given int[2] {@code glWinPos} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} operation, otherwise {@code null} * @see #shapeToWinCoord(PMVMatrix4f, Recti, float[], int[]) * @see #shapeToWinCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, float[], PMVMatrix4f, int[]) */ public final int[/*2*/] shapeToWinCoord(final Scene scene, final Vec3f objPos, final PMVMatrix4f pmv, final int[/*2*/] glWinPos) { return this.shapeToWinCoord(scene.getPMVMatrixSetup(), scene.getViewport(), objPos, pmv, glWinPos); } /** * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. *

* The given {@link PMVMatrix4f} has to be setup properly for this object, * i.e. its {@link GLMatrixFunc#GL_PROJECTION} and {@link GLMatrixFunc#GL_MODELVIEW} for the surrounding scene * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. See {@link #setPMVMatrix(Scene, PMVMatrix4f)}. *

* @param pmv well formed {@link PMVMatrix4f}, e.g. could have been setup via {@link Shape#setPMVMatrix(Scene, PMVMatrix4f)}. * @param viewport the Rect4i viewport * @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 given {@code objPos} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} * and {@link Matrix4f#mapWinToObj(float, float, float, float, Matrix4f, Recti, Vec3f, Vec3f) gluUnProject(..)} * operation, otherwise {@code null} * @see #winToShapeCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, int, int, PMVMatrix4f, float[]) * @see #winToShapeCoord(Scene, int, int, PMVMatrix4f, float[]) */ public final Vec3f winToShapeCoord(final PMVMatrix4f pmv, final Recti viewport, final int glWinX, final int glWinY, final Vec3f objPos) { final Vec3f ctr = box.getCenter(); if( Matrix4f.mapObjToWin(ctr, pmv.getPMv(), viewport, objPos) ) { final float winZ = objPos.z(); if( Matrix4f.mapWinToObj(glWinX, glWinY, winZ, pmv.getPMvi(), viewport, objPos) ) { return objPos; } } return null; } /** * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. *

* The given {@link PMVMatrix4f} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} properly for this shape * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. *

* @param pmvMatrixSetup {@link Scene.PMVMatrixSetup} to {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} given {@link PMVMatrix4f} {@code pmv}. * @param viewport used viewport for {@link PMVMatrix4f#mapWinToObj(float, float, float, Recti, Vec3f)} * @param glWinX in GL window coordinates, origin bottom-left * @param glWinY in GL window coordinates, origin bottom-left * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @param objPos target object position of glWinX/glWinY relative to this shape * @return given {@code objPos} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} * and {@link Matrix4f#mapWinToObj(float, float, float, float, Matrix4f, Recti, Vec3f, Vec3f) gluUnProject(..)} * operation, otherwise {@code null} * @see #winToShapeCoord(PMVMatrix4f, Recti, int, int, float[]) * @see #winToShapeCoord(Scene, int, int, PMVMatrix4f, float[]) */ public final Vec3f winToShapeCoord(final Scene.PMVMatrixSetup pmvMatrixSetup, final Recti viewport, final int glWinX, final int glWinY, final PMVMatrix4f pmv, final Vec3f objPos) { return this.winToShapeCoord(setPMVMatrix(pmvMatrixSetup, viewport, pmv), viewport, glWinX, glWinY, objPos); } /** * Map given gl-window-coordinates to object coordinates relative to this shape and its z-coordinate. *

* The given {@link PMVMatrix4f} will be {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) setup} properly for this shape * including this shape's {@link #applyMatToMv(PMVMatrix4f)}. *

* @param scene {@link Scene} to retrieve {@link Scene.PMVMatrixSetup} and the viewport. * @param glWinX in GL window coordinates, origin bottom-left * @param glWinY in GL window coordinates, origin bottom-left * @param pmv a new {@link PMVMatrix4f} which will {@link Scene.PMVMatrixSetup#set(PMVMatrix4f, Recti) be setup}, * {@link #applyMatToMv(PMVMatrix4f) shape-transformed} and can be reused by the caller. * @param objPos target object position of glWinX/glWinY relative to this shape * @return given {@code objPos} for successful {@link Matrix4f#mapObjToWin(Vec3f, Matrix4f, Recti, Vec3f) gluProject(..)} * and {@link Matrix4f#mapWinToObj(float, float, float, float, Matrix4f, Recti, Vec3f, Vec3f) gluUnProject(..)} * operation, otherwise {@code null} * @see #winToShapeCoord(PMVMatrix4f, Recti, int, int, float[]) * @see #winToShapeCoord(com.jogamp.graph.ui.Scene.PMVMatrixSetup, Recti, int, int, PMVMatrix4f, float[]) */ public final Vec3f winToShapeCoord(final Scene scene, final int glWinX, final int glWinY, final PMVMatrix4f pmv, final Vec3f objPos) { return this.winToShapeCoord(scene.getPMVMatrixSetup(), scene.getViewport(), glWinX, glWinY, pmv, objPos); } /** * Returns base-color w/o color channel, will be modulated w/ {@link #getPressedColorMod()}, * {@link #getToggleOnColorMod()}, {@link #getToggleOffColorMod()} and {@link #getActiveColorMod()}. **/ public final Vec4f getColor() { return rgbaColor; } /** Returns modulation color when {@link #isPressed()}. */ public final Vec4f getPressedColorMod() { return pressedRGBAModulate; } /** Returns modulation color when {@link #isToggleOn()}. */ public final Vec4f getToggleOnColorMod() { return toggleOnRGBAModulate; } /** Returns modulation color when not {@link #isToggleOn()}. */ public final Vec4f getToggleOffColorMod() { return toggleOffRGBAModulate; } /** Returns modulation color when {@link #isActive()}. */ public final Vec4f getActiveColorMod() { return activeRGBAModulate; } /** * Set base color. *

* Base color w/o color channel, will be modulated w/ pressed- and toggle color *

*

* Default RGBA value is 0.60f, 0.60f, 0.60f, 1.0f *

*

* Method issues {@link #markShapeDirty()}. *

* @see #markShapeDirty() */ public Shape setColor(final float r, final float g, final float b, final float a) { this.rgbaColor.set(r, g, b, a); markShapeDirty(); return this; } /** * Set base color. *

* Default base-color w/o color channel, will be modulated w/ pressed- and toggle color *

*

* Default RGBA value is 0.60f, 0.60f, 0.60f, 1.0f *

*

* Method issues {@link #markShapeDirty()}. *

* @see #markShapeDirty() */ public Shape setColor(final Vec4f c) { this.rgbaColor.set(c); markShapeDirty(); return this; } /** * Set pressed color, modulating {@link #getColor()} if {@link #isPressed()}. *

* Default pressed color, modulation -factor w/o color channel, modulated base-color. ~0.65 (due to alpha) *

*

* Default RGBA value is 0.70f, 0.70f, 0.70f, 0.8f *

*/ public Shape setPressedColorMod(final float r, final float g, final float b, final float a) { this.pressedRGBAModulate.set(r, g, b, a); return this; } /** * Set toggle-on color, modulating {@link #getColor()} if {@link #isToggleOn()} and {@link #setToggleable(boolean)} *

* Default toggle-on color-factor w/o color channel, modulated base-color. 0.60 * 0.83 ~= 0.50 *

*

* Default RGBA value is 0.83f, 0.83f, 0.83f, 1.0f *

*/ public final Shape setToggleOnColorMod(final float r, final float g, final float b, final float a) { this.toggleOnRGBAModulate.set(r, g, b, a); return this; } /** * Set toggle-off color, modulating {@link #getColor()} if !{@link #isToggleOn()} and {@link #setToggleable(boolean)} *

* Default toggle-off color-factor w/o color channel, modulated base-color. 0.60 * 1.00 ~= 0.60 *

*

* Default RGBA value is 1.00f, 1.00f, 1.00f, 1.0f *

*/ public final Shape setToggleOffColorMod(final float r, final float g, final float b, final float a) { this.toggleOffRGBAModulate.set(r, g, b, a); return this; } /** * Enable active color, modulation {@link #getColor()} if {@link #isActive()} with passing {@code c != null}, * disable with passing {@code c == null}. *

* Default active color-factor w/o color channel, modulated base-color. 0.60 * 0.25 ~= 0.15 *

*

* Default is disabled. *

*/ public final Shape setActiveColorMod(final Vec4f c) { if( null == c ) { activeRGBAModulateOn = false; } else { activeRGBAModulateOn = true; this.activeRGBAModulate.set(c); } return this; } public final Vec4f getBorderColor() { return borderColor; } /** * Set border color. *

* Default RGBA value is 0.00f, 0.00f, 0.00f, 1.0f *

*

* Method issues {@link #markShapeDirty()}. *

* @see #setBorder(float) * @see #markShapeDirty() */ public final Shape setBorderColor(final float r, final float g, final float b, final float a) { this.borderColor.set(r, g, b, a); markShapeDirty(); return this; } /** * Set border color. *

* Default RGBA value is 0.00f, 0.00f, 0.00f, 1.0f *

*

* Method issues {@link #markShapeDirty()}. *

* @see #setBorder(float) * @see #markShapeDirty() */ public final Shape setBorderColor(final Vec4f c) { this.borderColor.set(c); markShapeDirty(); return this; } @Override public final String toString() { return getClass().getSimpleName()+"["+getSubString()+"]"; } public String getSubString() { final String iMatS; if( iMatDirty ) { iMatS = "mat-dirty, "; } else if( iMatIdent ) { iMatS = "mat-ident, "; } else { iMatS = ""; } final String pivotS; if( null != rotPivot ) { pivotS = "pivot["+rotPivot+"], "; } else { pivotS = ""; } final String scaleS; if( !scale.isEqual( Vec3f.ONE ) ) { scaleS = "scale["+scale+"], "; } else { scaleS = "scale 1, "; } final String rotateS; if( !rotation.isIdentity() ) { final Vec3f euler = rotation.toEuler(new Vec3f()); rotateS = "rot["+euler+"], "; } else { rotateS = ""; } final String discS = isDiscarded()?", DISCARDED":""; final String activeS = isActive()?", ACTIVE[adjZ "+getAdjustedZ()+"]":""; final String ps = hasPadding() ? padding.toString()+", " : ""; final String bs = hasBorder() ? "border[l "+getBorderThickness()+", c "+getBorderColor()+"], " : ""; final String idS = -1 != id ? ", id "+id : ""; final String nameS = "noname" != name ? ", '"+name+"'" : ""; return getDirtyString()+idS+nameS+", visible "+isIO(IO_VISIBLE)+discS+activeS+", toggle "+isIO(IO_TOGGLE)+ ", able[toggle "+isIO(IO_TOGGLEABLE)+", iactive "+isInteractive()+", resize "+isResizable()+", drag "+this.isDraggable()+ "], pos["+position+"], "+pivotS+scaleS+rotateS+iMatS+ ps+bs+"box"+box; } // // Input // public final Shape setPressed(final boolean b) { setIO(IO_DOWN, b); markStateDirty(); return this; } public final boolean isPressed() { return isIO(IO_DOWN); } /** * Set this shape toggleable, default is off. * @param toggleable * @see #isInteractive() */ public final Shape setToggleable(final boolean toggleable) { return setIO(IO_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)}. * @see #isInteractive() */ public boolean isToggleable() { return isIO(IO_TOGGLEABLE); } /** * Set this shape's toggle state, default is off. * @param v * @return */ public final Shape setToggle(final boolean v) { setIO(IO_TOGGLE, v); toggleNotify(v); if( null != onToggleListener ) { onToggleListener.run(this); } markStateDirty(); return this; } public final Shape toggle() { if( isToggleable() ) { setIO(IO_TOGGLE, !isToggleOn()); toggleNotify(isToggleOn()); if( null != onToggleListener ) { onToggleListener.run(this); } markStateDirty(); } return this; } protected void toggleNotify(final boolean on) {} /** Returns true this shape's toggle state. */ public final boolean isToggleOn() { return isIO(IO_TOGGLE); } protected final boolean setActive(final boolean v, final float zOffset) { if( isActivable() ) { setZOffset(zOffset); setIO(IO_ACTIVE, v); if( !v ) { releaseInteraction(); final Tooltip tt = tooltip; if( null != tt ) { tt.stop(false); } } if( DEBUG ) { System.err.println("XXX "+(v?" Active":"DeActive")+" "+this); } dispatchActivationEvent(this); return true; } else { return false; } } /** Returns true of this shape is active */ public final boolean isActive() { return isIO(IO_ACTIVE); } /* pp */ void setActiveTopLevel(final boolean v, final float zOffset) { setZOffset(zOffset); setIO(IO_ACTIVE, v); dispatchActivationEvent(this); } public final float getAdjustedZ() { return position.z() * getScale().z() + zOffset; } /* pp */ final void setZOffset(final float v) { zOffset = v; } /** * Set's a new {@link Tooltip} for this shape. *

* The {@link Shape} must be set {@link #setInteractive(boolean) interactive} * to receive the mouse-over signal, i.e. being picked. *

*/ public Tooltip setToolTip(final Tooltip newTooltip) { final Tooltip oldTT = this.tooltip; this.tooltip = null; if( null != oldTT ) { oldTT.stop(true); } newTooltip.setTool(this); this.tooltip = newTooltip; return newTooltip; } public void removeToolTip() { final Tooltip tt = tooltip; tooltip = null; if( null != tt ) { tt.stop(true); tt.setTool(null); } } private void stopToolTip() { final Tooltip tt = tooltip; if( null != tt ) { tt.stop(true); } } /* pp */ Tooltip startToolTip(final boolean lookupParents) { Tooltip tt = tooltip; if( null != tt ) { tt.start(); return tt; } else if( lookupParents ) { Shape p = getParent(); while( null != p ) { tt = p.startToolTip(false); if( null != tt ) { return tt; } else { p = p.getParent(); } } } return null; } public Tooltip getTooltip() { return tooltip; } /** * Set whether this shape is interactive in general, * i.e. any user interaction like * - {@link #isToggleable()} * - {@link #isDraggable()} * - {@link #isResizable()} * but excluding programmatic changes. * @param v new value for {@link #isInteractive()} * @see #isInteractive() * @see #isVisible() * @see #setDraggable(boolean) * @see #setResizable(boolean) * @see #setDragAndResizable(boolean) */ public final Shape setInteractive(final boolean v) { return setIO(IO_INTERACTIVE, v); } /** * Returns if this shape allows user interaction in general, see {@link #setInteractive(boolean)} * @see #setInteractive(boolean) * @see #isVisible() */ public final boolean isInteractive() { return isIO(IO_INTERACTIVE); } /** * Set whether this shape is allowed to be activated, i.e become {@link #isActive()}. *

* A non activable shape still allows a shape to be dragged or resized, * it just can't gain the main focus. *

*/ public final Shape setActivable(final boolean v) { return setIO(IO_ACTIVABLE, v); } /** Returns if this shape is allowed to be activated, i.e become {@link #isActive()}. */ public final boolean isActivable() { return isIO(IO_ACTIVABLE); } /** * Set whether this shape is discarded in last {@link #draw(GL2ES2, RegionRenderer)}, * i.e. culled via frustum or occlusion criteria. */ public final Shape setDiscarded(final boolean v) { return setIO(IO_DISCARDED, v); } /** Returns whether this shape is discarded in last {@link #draw(GL2ES2, RegionRenderer)}, i.e. culled via frustum or occlusion criteria.*/ public final boolean isDiscarded() { return isIO(IO_DISCARDED); } /** * Set whether this shape is draggable, * i.e. translated by 1-pointer-click and drag. *

* Default draggable is true. *

* @see #isDraggable() * @see #setInteractive(boolean) * @see #setResizable(boolean) * @see #setDragAndResizable(boolean) */ public final Shape setDraggable(final boolean draggable) { return setIO(IO_DRAGGABLE, draggable); } /** * Returns if this shape is draggable, a user interaction. * @see #setDraggable(boolean) */ public final boolean isDraggable() { return isIO(IO_DRAGGABLE); } /** * Set whether this shape is resizable, * i.e. zoomed by 1-pointer-click and drag in 1/4th bottom-left and bottom-right corner. *

* Default resizable is true. *

* @see #isResizable() * @see #setInteractive(boolean) * @see #setDraggable(boolean) * @see #setDragAndResizable(boolean) */ public final Shape setResizable(final boolean resizable) { return setIO(IO_RESIZABLE, resizable); } /** * Returns if this shape is resizable, a user interaction. * @see #setResizable(boolean) */ public final boolean isResizable() { return isIO(IO_RESIZABLE); } /** * Returns if aspect-ratio shall be kept at resize, if {@link #isResizable()}. * @see #setFixedARatioResize(boolean) */ public final boolean isFixedARatioResize() { return isIO(IO_RESIZE_FIXED_RATIO); } /** * Sets whether aspect-ratio shall be kept at resize, if {@link #isResizable()}. * @see #isResizable() * @see #isFixedARatioResize() */ public final Shape setFixedARatioResize(final boolean v) { return setIO(IO_RESIZE_FIXED_RATIO, v); } /** * Set whether this shape is draggable and resizable. *

* Default draggable and resizable is true. *

* @see #isDraggable() * @see #isResizable() * @see #setInteractive(boolean) * @see #setDraggable(boolean) * @see #setResizable(boolean) */ public final Shape setDragAndResizable(final boolean v) { setDraggable(v); setResizable(v); return this; } public final Shape addMouseListener(final MouseGestureListener l) { if(l == null) { return this; } @SuppressWarnings("unchecked") final ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); clonedListeners.add(l); mouseListeners = clonedListeners; return this; } public final Shape removeMouseListener(final MouseGestureListener l) { if (l == null) { return this; } @SuppressWarnings("unchecked") final ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); clonedListeners.remove(l); mouseListeners = clonedListeners; return this; } /** * Forward {@link MouseGestureListener} events to this {@link Shape} from {@code source} using a {@link ForwardMouseListener}. *

* This source {@link Shape} must be {@link #setInteractive(boolean)} to receive and forward the events. *

*

* This receiver {@link Shape} must be {@link #setInteractive(boolean)} to have the events forwarded. *

* @see #receiveKeyEvents(Shape) */ public void receiveMouseEvents(final Shape source) { source.addMouseListener(new Shape.ForwardMouseListener(this)); } public final Shape addKeyListener(final KeyListener l) { if(l == null) { return this; } @SuppressWarnings("unchecked") final ArrayList clonedListeners = (ArrayList) keyListeners.clone(); clonedListeners.add(l); keyListeners = clonedListeners; return this; } public final Shape removeKeyListener(final KeyListener l) { if (l == null) { return this; } @SuppressWarnings("unchecked") final ArrayList clonedListeners = (ArrayList) keyListeners.clone(); clonedListeners.remove(l); keyListeners = clonedListeners; return this; } /** * Forward {@link KeyListener} events to this {@link Shape} from {@code source} using a {@link ForwardKeyListener}. *

* This source {@link Shape} must be {@link #setInteractive(boolean)} to receive and forward the events. *

*

* This receiver {@link Shape} must be {@link #setInteractive(boolean)} to have the events forwarded. *

* @see #receiveMouseEvents(Shape) */ public void receiveKeyEvents(final Shape source) { source.addKeyListener(new Shape.ForwardKeyListener(this)); } /** * 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 info for propagated {@link NEWTEvent}s * containing reference of {@link #shape the intended shape} as well as * the {@link #objPos rotated relative position} to this shape. * The latter is normalized to bottom-left zero origin, allowing easier usage. */ public static class EventInfo { /** 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 Vec3f 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 Vec2f objDrag = new Vec2f(); /** 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. */ EventInfo(final int glWinX, final int glWinY, final Shape shape, final Vec3f objPos) { this.winPos = new int[] { glWinX, glWinY }; this.shape = shape; this.objPos = objPos; } @Override public String toString() { return "EventInfo[winPos ["+winPos[0]+", "+winPos[1]+"], objPos ["+objPos+"], "+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 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); boolean ires = false; final short eventType = e.getEventType(); if( 1 == e.getPointerCount() ) { switch( eventType ) { case MouseEvent.EVENT_MOUSE_MOVED: if( null != onHoverListener ) { onHoverListener.run(this, objPos, e); } ires = true; break; case MouseEvent.EVENT_MOUSE_PRESSED: if( resizableOrDraggable ) { setIO(IO_DRAG_FIRST, true); ires = true; } setPressed(true); break; case MouseEvent.EVENT_MOUSE_RELEASED: // Release interactions: last pointer has been lifted! releaseInteraction(); ires = true; break; case MouseEvent.EVENT_MOUSE_CLICKED: if( isToggleable() ) { toggle(); } if( null != onClickedListener ) { onClickedListener.run(this, objPos, e); } ires = true; break; } } 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 { 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 true; // end signal traversal at 1st drag } shapeEvent.objDrag.set( objPos.x() - objDraggedFirst.x(), objPos.y() - objDraggedFirst.y() ); shapeEvent.objDrag.mul(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( isIO(IO_IN_RESIZE_BR) ) { moveNotify( 0, sdy2, 0f); // bottom-right, sticky left- and top-edge } else { moveNotify( sdx, sdy2, 0f); // bottom-left, sticky right- and top-edge } 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); } moveNotify( sdx, sdy, 0f); return true; // end signal traversal with completed move } } } // resizableOrDraggable && EVENT_MOUSE_DRAGGED e.setAttachment(shapeEvent); return dispatchMouseEvent(e) || ires; } /** * Dispatch given NEWT mouse event to this shape * @param e original Newt {@link MouseEvent} * @return true to signal operation complete and to stop traversal, otherwise false */ /* pp */ final boolean dispatchMouseEvent(final MouseEvent e) { final short eventType = e.getEventType(); 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()); } } return e.isConsumed(); // end signal traversal if consumed } /** * @param e original Newt {@link GestureEvent} * @param glWinX x-position in OpenGL model space * @param glWinY y-position in OpenGL model space * @param pmv well formed PMVMatrix4f for this shape * @param viewport the viewport * @param objPos object position of mouse event relative to this shape */ /* pp */ final void dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final PMVMatrix4f pmv, final Recti viewport, final Vec3f objPos) { if( isInteractive() && isResizable() && e instanceof PinchToZoomGesture.ZoomEvent ) { final PinchToZoomGesture.ZoomEvent ze = (PinchToZoomGesture.ZoomEvent) e; final float pixels = ze.getDelta() * ze.getScale(); // final int winX2 = glWinX + Math.round(pixels); final Vec3f objPos2 = winToShapeCoord(pmv, viewport, winX2, glWinY, new Vec3f()); if( null == objPos2 ) { return; } final float dx = objPos2.x(); final float dy = objPos2.y(); final float sx = scale.x() + ( dx/box.getWidth() ); // bottom-right final float sy = scale.y() + ( dy/box.getHeight() ); if( DEBUG ) { System.err.printf("DragZoom: resize[br %b, bl %b], win %4d/%4d, obj %s, %s + %.3f/%.3f -> %.3f/%.3f%n", isIO(IO_IN_RESIZE_BR), isIO(IO_IN_RESIZE_BL), glWinX, glWinY, objPos, position, dx, dy, sx, sy); } if( resize_sxy_min <= sx && resize_sxy_min <= sy ) { // avoid scale flip if( DEBUG ) { System.err.printf("PinchZoom: pixels %f, win %4d/%4d, obj %s, %s + %.3f/%.3f -> %.3f/%.3f%n", pixels, glWinX, glWinY, objPos, position, dx, dy, sx, sy); } // moveNotify(dx, dy, 0f); setScale(sx, sy, scale.z()); } return; // FIXME: pass through event? Issue zoom event? } final Shape.EventInfo shapeEvent = new EventInfo(glWinX, glWinY, this, objPos); e.setAttachment(shapeEvent); dispatchGestureEvent(e); } /** * Dispatch given NEWT mouse event to this shape * @param e original Newt {@link MouseEvent} * @return true to signal operation complete and to stop traversal, otherwise false */ /* pp */ final boolean dispatchGestureEvent(final GestureEvent e) { for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { mouseListeners.get(i).gestureDetected(e); } return e.isConsumed(); // end signal traversal if consumed } /** * Dispatch given NEWT key event to this shape * @param e original Newt {@link KeyEvent} * @return true to signal operation complete and to stop traversal, otherwise false */ /* pp */ final boolean dispatchKeyEvent(final KeyEvent e) { /** * Checked at caller! if( !isInteractive() ) { return false; } */ final short eventType = e.getEventType(); for(int i = 0; !e.isConsumed() && i < keyListeners.size(); i++ ) { final KeyListener l = keyListeners.get(i); switch( eventType ) { case KeyEvent.EVENT_KEY_PRESSED: l.keyPressed(e); break; case KeyEvent.EVENT_KEY_RELEASED: l.keyReleased(e); break; default: throw new NativeWindowException("Unexpected key event type " + e.getEventType()); } } return e.isConsumed(); // end signal traversal if consumed } // // // protected abstract void validateImpl(final GL2ES2 gl, final GLProfile glp); /** * Actual draw implementation, called by {@link #draw(GL2ES2, RegionRenderer)} * @param gl * @param renderer * @param rgba */ protected abstract void drawImpl0(final GL2ES2 gl, final RegionRenderer renderer, final Vec4f rgba); /** * Actual draw implementation, called by {@link #drawToSelect(GL2ES2, RegionRenderer)} * @param gl * @param renderer */ protected abstract void drawToSelectImpl0(final GL2ES2 gl, final RegionRenderer renderer); /** Custom {@link #clear(GL2ES2, RegionRenderer)} task, called 1st. */ protected abstract void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer); /** Custom {@link #destroy(GL2ES2, RegionRenderer)} task, called 1st. */ protected abstract void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer); /** * Returns true if implementation uses an extra color channel or texture * which will be modulated with the passed rgba color {@link #drawImpl0(GL2ES2, RegionRenderer, float[])}. * * Otherwise the base color will be modulated and passed to {@link #drawImpl0(GL2ES2, RegionRenderer, float[])}. */ public abstract boolean hasColorChannel(); @SuppressWarnings("unused") private static int compareAsc0(final float a, final float b) { if( FloatUtil.isEqual2(a, b) ) { return 0; } else if( a < b ){ return -1; } else { return 1; } } private static int compareAsc1(final float a, final float b) { if (a < b) { return -1; // Neither is NaN, a is smaller } if (a > b) { return 1; // Neither is NaN, a is larger } return 0; } @SuppressWarnings("unused") private static int compareDesc0(final float a, final float b) { if( FloatUtil.isEqual2(a, b) ) { return 0; } else if( a < b ){ return 1; } else { return -1; } } private static int compareDesc1(final float a, final float b) { if (a < b) { return 1; // Neither is NaN, a is smaller } if (a > b) { return -1; // Neither is NaN, a is larger } return 0; } public static Comparator ZAscendingComparator = new Comparator() { @Override public int compare(final Shape s1, final Shape s2) { return compareAsc1( s1.getAdjustedZ(), s2.getAdjustedZ() ); } }; public static Comparator ZDescendingComparator = new Comparator() { @Override public int compare(final Shape s1, final Shape s2) { return compareDesc1( s2.getAdjustedZ(), s1.getAdjustedZ() ); } }; // // // }