/** * Copyright 2010 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.opengl.test.junit.graph.demos.ui; import java.util.ArrayList; import com.jogamp.nativewindow.NativeWindowException; import com.jogamp.opengl.GL2ES2; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.Vertex.Factory; import com.jogamp.newt.event.GestureHandler.GestureEvent; import com.jogamp.newt.event.GestureHandler.GestureListener; import com.jogamp.newt.event.MouseAdapter; import com.jogamp.newt.event.NEWTEvent; import com.jogamp.newt.event.MouseEvent; import com.jogamp.newt.event.MouseListener; import com.jogamp.opengl.math.Quaternion; import com.jogamp.opengl.math.geom.AABBox; public abstract class UIShape { public static final boolean DRAW_DEBUG_BOX = false; protected static final int DIRTY_SHAPE = 1 << 0 ; protected static final int DIRTY_STATE = 1 << 1 ; private final Factory<? extends Vertex> vertexFactory; private final int renderModes; protected final AABBox box; protected final float[] translate = new float[] { 0f, 0f, 0f }; protected final Quaternion rotation = new Quaternion(); protected final float[] rotOrigin = new float[] { 0f, 0f, 0f }; protected final float[] scale = new float[] { 1f, 1f, 1f }; protected GLRegion region = null; protected int regionQuality = Region.MAX_QUALITY; protected int dirty = DIRTY_SHAPE | DIRTY_STATE; protected float shapesSharpness = OutlineShape.DEFAULT_SHARPNESS; /** Default base-color w/o color channel, will be modulated w/ pressed- and toggle color */ protected final float[] rgbaColor = {0.75f, 0.75f, 0.75f, 1.0f}; /** Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 */ protected final float[] pressedRGBAModulate = {1.2f, 1.2f, 1.2f, 0.7f}; /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 1.13 ~ 0.85 */ protected final float[] toggleOnRGBAModulate = {1.13f, 1.13f, 1.13f, 1.0f}; /** Default toggle color-factor w/o color channel, modulated base-color. 0.75 * 0.86 ~ 0.65 */ protected final float[] toggleOffRGBAModulate = {0.86f, 0.86f, 0.86f, 1.0f}; private int name = -1; private boolean down = false; private boolean toggle = false; private boolean toggleable = false; private boolean enabled = true; private ArrayList<MouseGestureListener> mouseListeners = new ArrayList<MouseGestureListener>(); public UIShape(final Factory<? extends Vertex> factory, final int renderModes) { this.vertexFactory = factory; this.renderModes = renderModes; this.box = new AABBox(); } public void setName(final int name) { this.name = name; } public int getName() { return this.name; } public final Vertex.Factory<? extends Vertex> getVertexFactory() { return vertexFactory; } public boolean isEnabled() { return enabled; } public void setEnabled(final boolean v) { enabled = v; } /** * Clears all data and reset all states as if this instance was newly created * @param gl TODO * @param renderer TODO\ */ public void clear(final GL2ES2 gl, final RegionRenderer renderer) { clearImpl(gl, renderer); translate[0] = 0f; translate[1] = 0f; translate[2] = 0f; rotation.setIdentity(); rotOrigin[0] = 0f; rotOrigin[1] = 0f; rotOrigin[2] = 0f; scale[0] = 1f; scale[1] = 1f; scale[2] = 1f; box.reset(); markShapeDirty(); } /** * Destroys all data * @param gl * @param renderer */ public void destroy(final GL2ES2 gl, final RegionRenderer renderer) { destroyImpl(gl, renderer); translate[0] = 0f; translate[1] = 0f; translate[2] = 0f; rotation.setIdentity(); rotOrigin[0] = 0f; rotOrigin[1] = 0f; rotOrigin[2] = 0f; scale[0] = 1f; scale[1] = 1f; scale[2] = 1f; box.reset(); markShapeDirty(); } public void setTranslate(final float tx, final float ty, final float tz) { translate[0] = tx; translate[1] = ty; translate[2] = tz; // System.err.println("UIShape.setTranslate: "+tx+"/"+ty+"/"+tz+": "+toString()); } public void translate(final float tx, final float ty, final float tz) { translate[0] += tx; translate[1] += ty; translate[2] += tz; // System.err.println("UIShape.translate: "+tx+"/"+ty+"/"+tz+": "+toString()); } public final float[] getTranslate() { return translate; } public final Quaternion getRotation() { return rotation; } public final float[] getRotationOrigin() { return rotOrigin; } public void setRotationOrigin(final float rx, final float ry, final float rz) { rotOrigin[0] = rx; rotOrigin[1] = ry; rotOrigin[2] = rz; } public void setScale(final float sx, final float sy, final float sz) { scale[0] = sx; scale[1] = sy; scale[2] = sz; } public void scale(final float sx, final float sy, final float sz) { scale[0] *= sx; scale[1] *= sy; scale[2] *= sz; } public final float[] getScale() { return scale; } public final void markShapeDirty() { dirty |= DIRTY_SHAPE; } public final boolean isShapeDirty() { return 0 != ( dirty & DIRTY_SHAPE ) ; } public final void markStateDirty() { dirty |= DIRTY_STATE; } public final boolean isStateDirty() { return 0 != ( dirty & DIRTY_STATE ) ; } public final AABBox getBounds() { return box; } public final int getRenderModes() { return renderModes; } public GLRegion getRegion(final GL2ES2 gl, final RegionRenderer renderer) { validate(gl, renderer); return region; } /** * Renders {@link OutlineShape} using local {@link GLRegion} which might be cached or updated. * <p> * No matrix operations (translate, scale, ..) are performed. * </p> * @param gl * @param renderer * @param sampleCount */ public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) { final float r, g, b, a; final boolean isPressed = isPressed(), isToggleOn = isToggleOn(); final boolean modBaseColor = !Region.hasColorChannel( renderModes ) && !Region.hasColorTexture( renderModes ); if( modBaseColor ) { if( isPressed ) { r = rgbaColor[0]*pressedRGBAModulate[0]; g = rgbaColor[1]*pressedRGBAModulate[1]; b = rgbaColor[2]*pressedRGBAModulate[2]; a = rgbaColor[3]*pressedRGBAModulate[3]; } else if( isToggleable() ) { if( isToggleOn ) { r = rgbaColor[0]*toggleOnRGBAModulate[0]; g = rgbaColor[1]*toggleOnRGBAModulate[1]; b = rgbaColor[2]*toggleOnRGBAModulate[2]; a = rgbaColor[3]*toggleOnRGBAModulate[3]; } else { r = rgbaColor[0]*toggleOffRGBAModulate[0]; g = rgbaColor[1]*toggleOffRGBAModulate[1]; b = rgbaColor[2]*toggleOffRGBAModulate[2]; a = rgbaColor[3]*toggleOffRGBAModulate[3]; } } else { r = rgbaColor[0]; g = rgbaColor[1]; b = rgbaColor[2]; a = rgbaColor[3]; } } else { if( isPressed ) { r = pressedRGBAModulate[0]; g = pressedRGBAModulate[1]; b = pressedRGBAModulate[2]; a = pressedRGBAModulate[3]; } else if( isToggleable() ) { if( isToggleOn ) { r = toggleOnRGBAModulate[0]; g = toggleOnRGBAModulate[1]; b = toggleOnRGBAModulate[2]; a = toggleOnRGBAModulate[3]; } else { r = toggleOffRGBAModulate[0]; g = toggleOffRGBAModulate[1]; b = toggleOffRGBAModulate[2]; a = toggleOffRGBAModulate[3]; } } else { r = rgbaColor[0]; g = rgbaColor[1]; b = rgbaColor[2]; a = rgbaColor[3]; } } renderer.getRenderState().setColorStatic(r, g, b, a); getRegion(gl, renderer).draw(gl, renderer, sampleCount); } protected GLRegion createGLRegion() { return GLRegion.create(renderModes, null); } /** * Validates the shape's underlying {@link GLRegion}. * * @param gl * @param renderer */ public final void validate(final GL2ES2 gl, final RegionRenderer renderer) { if( isShapeDirty() || null == region ) { box.reset(); if( null == region ) { region = createGLRegion(); } else { region.clear(gl); } addShapeToRegion(gl, renderer); if( DRAW_DEBUG_BOX ) { region.clear(gl); final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory()); shape.setSharpness(shapesSharpness); shape.setIsQuadraticNurbs(); region.addOutlineShape(shape, null, rgbaColor); } region.setQuality(regionQuality); dirty &= ~(DIRTY_SHAPE|DIRTY_STATE); } else if( isStateDirty() ) { region.markStateDirty(); dirty &= ~DIRTY_STATE; } } public float[] getColor() { return rgbaColor; } public final int getQuality() { return regionQuality; } public final void setQuality(final int q) { this.regionQuality = q; if( null != region ) { region.setQuality(q); } } public final void setSharpness(final float sharpness) { this.shapesSharpness = sharpness; markShapeDirty(); } public final float getSharpness() { return shapesSharpness; } public final void setColor(final float r, final float g, final float b, final float a) { this.rgbaColor[0] = r; this.rgbaColor[1] = g; this.rgbaColor[2] = b; this.rgbaColor[3] = a; } public final void setPressedColorMod(final float r, final float g, final float b, final float a) { this.pressedRGBAModulate[0] = r; this.pressedRGBAModulate[1] = g; this.pressedRGBAModulate[2] = b; this.pressedRGBAModulate[3] = a; } public final void setToggleOnColorMod(final float r, final float g, final float b, final float a) { this.toggleOnRGBAModulate[0] = r; this.toggleOnRGBAModulate[1] = g; this.toggleOnRGBAModulate[2] = b; this.toggleOnRGBAModulate[3] = a; } public final void setToggleOffColorMod(final float r, final float g, final float b, final float a) { this.toggleOffRGBAModulate[0] = r; this.toggleOffRGBAModulate[1] = g; this.toggleOffRGBAModulate[2] = b; this.toggleOffRGBAModulate[3] = a; } @Override public final String toString() { return getClass().getSimpleName()+"["+getSubString()+"]"; } public String getSubString() { return "enabled "+enabled+", toggle[able "+toggleable+", state "+toggle+"], "+translate[0]+" / "+translate[1]+", box "+box; } // // Input // public void setPressed(final boolean b) { this.down = b; markStateDirty(); } public boolean isPressed() { return this.down; } public void setToggleable(final boolean toggleable) { this.toggleable = toggleable; } public boolean isToggleable() { return toggleable; } public void setToggle(final boolean v) { toggle = v; markStateDirty(); } public void toggle() { if( isToggleable() ) { toggle = !toggle; } markStateDirty(); } public boolean isToggleOn() { return toggle; } public final void addMouseListener(final MouseGestureListener l) { if(l == null) { return; } @SuppressWarnings("unchecked") final ArrayList<MouseGestureListener> clonedListeners = (ArrayList<MouseGestureListener>) mouseListeners.clone(); clonedListeners.add(l); mouseListeners = clonedListeners; } public final void removeMouseListener(final MouseGestureListener l) { if (l == null) { return; } @SuppressWarnings("unchecked") final ArrayList<MouseGestureListener> clonedListeners = (ArrayList<MouseGestureListener>) mouseListeners.clone(); clonedListeners.remove(l); mouseListeners = clonedListeners; } /** * Combining {@link MouseListener} and {@link GestureListener} */ public static interface MouseGestureListener extends MouseListener, GestureListener { } /** * Convenient adapter combining dummy implementation for {@link MouseListener} and {@link GestureListener} */ public static abstract class MouseGestureAdapter extends MouseAdapter implements MouseGestureListener { @Override public void gestureDetected(final GestureEvent gh) { } } /** * {@link UIShape} event details for propagated {@link NEWTEvent}s * containing reference of {@link #shape the intended shape} as well as * the {@link #objPos rotated relative position} and {@link #rotBounds bounding box}. * The latter fields are also normalized to lower-left zero origin, allowing easier usage. */ public static class PointerEventInfo { /** The intended {@link UIShape} instance for this event */ public final UIShape shape; /** The relative pointer position inside the intended {@link UIShape}. */ public final float[] objPos; /** window x-position in OpenGL model space */ public final int glWinX; /** window y-position in OpenGL model space */ public final int glWinY; PointerEventInfo(final int glWinX, final int glWinY, final UIShape shape, final float[] objPos) { this.glWinX = glWinX; this.glWinY = glWinY; this.shape = shape; this.objPos = objPos; } public String toString() { return "EventDetails[winPos ["+glWinX+", "+glWinY+"], objPos ["+objPos[0]+", "+objPos[1]+", "+objPos[2]+"], "+shape+"]"; } } /** * @param e original Newt {@link GestureEvent} * @param glWinX x-position in OpenGL model space * @param glWinY y-position in OpenGL model space */ public final void dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final float[] objPos) { e.setAttachment(new PointerEventInfo(glWinX, glWinY, this, objPos)); for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { mouseListeners.get(i).gestureDetected(e); } } /** * * @param e original Newt {@link MouseEvent} * @param glX x-position in OpenGL model space * @param glY y-position in OpenGL model space */ public final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos) { e.setAttachment(new PointerEventInfo(glWinX, glWinY, this, objPos)); final short eventType = e.getEventType(); if( 1 == e.getPointerCount() ) { switch( eventType ) { case MouseEvent.EVENT_MOUSE_CLICKED: toggle(); break; case MouseEvent.EVENT_MOUSE_PRESSED: setPressed(true); break; case MouseEvent.EVENT_MOUSE_RELEASED: setPressed(false); break; } } for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { final MouseGestureListener l = mouseListeners.get(i); switch( eventType ) { case MouseEvent.EVENT_MOUSE_CLICKED: l.mouseClicked(e); break; case MouseEvent.EVENT_MOUSE_ENTERED: l.mouseEntered(e); break; case MouseEvent.EVENT_MOUSE_EXITED: l.mouseExited(e); break; case MouseEvent.EVENT_MOUSE_PRESSED: l.mousePressed(e); break; case MouseEvent.EVENT_MOUSE_RELEASED: l.mouseReleased(e); break; case MouseEvent.EVENT_MOUSE_MOVED: l.mouseMoved(e); break; case MouseEvent.EVENT_MOUSE_DRAGGED: l.mouseDragged(e); break; case MouseEvent.EVENT_MOUSE_WHEEL_MOVED: l.mouseWheelMoved(e); break; default: throw new NativeWindowException("Unexpected mouse event type " + e.getEventType()); } } } // // // protected abstract void clearImpl(GL2ES2 gl, RegionRenderer renderer); protected abstract void destroyImpl(GL2ES2 gl, RegionRenderer renderer); protected abstract void addShapeToRegion(GL2ES2 gl, RegionRenderer renderer); // // // protected OutlineShape createDebugOutline(final OutlineShape shape, final AABBox box) { final float tw = box.getWidth(); final float th = box.getHeight(); final float minX = box.getMinX(); final float minY = box.getMinY(); final float z = box.getMinZ() + 0.025f; // CCW! shape.addVertex(minX, minY, z, true); shape.addVertex(minX+tw, minY, z, true); shape.addVertex(minX+tw, minY + th, z, true); shape.addVertex(minX, minY + th, z, true); shape.closeLastOutline(true); return shape; } }