1 /**
2  * Copyright 2010 JogAmp Community. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without modification, are
5  * permitted provided that the following conditions are met:
6  *
7  *    1. Redistributions of source code must retain the above copyright notice, this list of
8  *       conditions and the following disclaimer.
9  *
10  *    2. Redistributions in binary form must reproduce the above copyright notice, this list
11  *       of conditions and the following disclaimer in the documentation and/or other materials
12  *       provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  * The views and conclusions contained in the software and documentation are those of the
25  * authors and should not be interpreted as representing official policies, either expressed
26  * or implied, of JogAmp Community.
27  */
28 package com.jogamp.opengl.test.junit.graph.demos.ui;
29 
30 import java.util.ArrayList;
31 
32 import com.jogamp.nativewindow.NativeWindowException;
33 import com.jogamp.opengl.GL2ES2;
34 
35 import com.jogamp.graph.curve.OutlineShape;
36 import com.jogamp.graph.curve.Region;
37 import com.jogamp.graph.curve.opengl.GLRegion;
38 import com.jogamp.graph.curve.opengl.RegionRenderer;
39 import com.jogamp.graph.geom.Vertex;
40 import com.jogamp.graph.geom.Vertex.Factory;
41 import com.jogamp.newt.event.GestureHandler.GestureEvent;
42 import com.jogamp.newt.event.GestureHandler.GestureListener;
43 import com.jogamp.newt.event.MouseAdapter;
44 import com.jogamp.newt.event.NEWTEvent;
45 import com.jogamp.newt.event.MouseEvent;
46 import com.jogamp.newt.event.MouseListener;
47 import com.jogamp.opengl.math.Quaternion;
48 import com.jogamp.opengl.math.geom.AABBox;
49 
50 public abstract class UIShape {
51     public static final boolean DRAW_DEBUG_BOX = false;
52 
53     protected static final int DIRTY_SHAPE    = 1 << 0 ;
54     protected static final int DIRTY_STATE    = 1 << 1 ;
55 
56     private final Factory<? extends Vertex> vertexFactory;
57     private final int renderModes;
58     protected final AABBox box;
59 
60     protected final float[] translate = new float[] { 0f, 0f, 0f };
61     protected final Quaternion rotation = new Quaternion();
62     protected final float[] rotOrigin = new float[] { 0f, 0f, 0f };
63     protected final float[] scale = new float[] { 1f, 1f, 1f };
64 
65     protected GLRegion region = null;
66     protected int regionQuality = Region.MAX_QUALITY;
67 
68     protected int dirty = DIRTY_SHAPE | DIRTY_STATE;
69     protected float shapesSharpness = OutlineShape.DEFAULT_SHARPNESS;
70 
71     /** Default base-color w/o color channel, will be modulated w/ pressed- and toggle color */
72     protected final float[] rgbaColor         = {0.75f, 0.75f, 0.75f, 1.0f};
73     /** Default pressed color-factor w/o color channel, modulated base-color. 0.75 * 1.2 = 0.9 */
74     protected final float[] pressedRGBAModulate = {1.2f, 1.2f, 1.2f, 0.7f};
75     /** Default toggle color-factor w/o color channel, modulated base-color.  0.75 * 1.13 ~ 0.85 */
76     protected final float[] toggleOnRGBAModulate = {1.13f, 1.13f, 1.13f, 1.0f};
77     /** Default toggle color-factor w/o color channel, modulated base-color.  0.75 * 0.86 ~ 0.65 */
78     protected final float[] toggleOffRGBAModulate = {0.86f, 0.86f, 0.86f, 1.0f};
79 
80     private int name = -1;
81 
82     private boolean down = false;
83     private boolean toggle = false;
84     private boolean toggleable = false;
85     private boolean enabled = true;
86     private ArrayList<MouseGestureListener> mouseListeners = new ArrayList<MouseGestureListener>();
87 
UIShape(final Factory<? extends Vertex> factory, final int renderModes)88     public UIShape(final Factory<? extends Vertex> factory, final int renderModes) {
89         this.vertexFactory = factory;
90         this.renderModes = renderModes;
91         this.box = new AABBox();
92     }
93 
setName(final int name)94     public void setName(final int name) { this.name = name; }
getName()95     public int getName() { return this.name; }
96 
getVertexFactory()97     public final Vertex.Factory<? extends Vertex> getVertexFactory() { return vertexFactory; }
98 
isEnabled()99     public boolean isEnabled() { return enabled; }
setEnabled(final boolean v)100     public void setEnabled(final boolean v) { enabled = v; }
101 
102     /**
103      * Clears all data and reset all states as if this instance was newly created
104      * @param gl TODO
105      * @param renderer TODO\
106      */
clear(final GL2ES2 gl, final RegionRenderer renderer)107     public void clear(final GL2ES2 gl, final RegionRenderer renderer) {
108         clearImpl(gl, renderer);
109         translate[0] = 0f;
110         translate[1] = 0f;
111         translate[2] = 0f;
112         rotation.setIdentity();
113         rotOrigin[0] = 0f;
114         rotOrigin[1] = 0f;
115         rotOrigin[2] = 0f;
116         scale[0] = 1f;
117         scale[1] = 1f;
118         scale[2] = 1f;
119         box.reset();
120         markShapeDirty();
121     }
122 
123     /**
124      * Destroys all data
125      * @param gl
126      * @param renderer
127      */
destroy(final GL2ES2 gl, final RegionRenderer renderer)128     public void destroy(final GL2ES2 gl, final RegionRenderer renderer) {
129         destroyImpl(gl, renderer);
130         translate[0] = 0f;
131         translate[1] = 0f;
132         translate[2] = 0f;
133         rotation.setIdentity();
134         rotOrigin[0] = 0f;
135         rotOrigin[1] = 0f;
136         rotOrigin[2] = 0f;
137         scale[0] = 1f;
138         scale[1] = 1f;
139         scale[2] = 1f;
140         box.reset();
141         markShapeDirty();
142     }
143 
setTranslate(final float tx, final float ty, final float tz)144     public void setTranslate(final float tx, final float ty, final float tz) {
145         translate[0] = tx;
146         translate[1] = ty;
147         translate[2] = tz;
148         // System.err.println("UIShape.setTranslate: "+tx+"/"+ty+"/"+tz+": "+toString());
149     }
translate(final float tx, final float ty, final float tz)150     public void translate(final float tx, final float ty, final float tz) {
151         translate[0] += tx;
152         translate[1] += ty;
153         translate[2] += tz;
154         // System.err.println("UIShape.translate: "+tx+"/"+ty+"/"+tz+": "+toString());
155     }
getTranslate()156     public final float[] getTranslate() { return translate; }
getRotation()157     public final Quaternion getRotation() { return rotation; }
getRotationOrigin()158     public final float[] getRotationOrigin() { return rotOrigin; }
setRotationOrigin(final float rx, final float ry, final float rz)159     public void setRotationOrigin(final float rx, final float ry, final float rz) {
160         rotOrigin[0] = rx;
161         rotOrigin[1] = ry;
162         rotOrigin[2] = rz;
163     }
setScale(final float sx, final float sy, final float sz)164     public void setScale(final float sx, final float sy, final float sz) {
165         scale[0] = sx;
166         scale[1] = sy;
167         scale[2] = sz;
168     }
scale(final float sx, final float sy, final float sz)169     public void scale(final float sx, final float sy, final float sz) {
170         scale[0] *= sx;
171         scale[1] *= sy;
172         scale[2] *= sz;
173     }
getScale()174     public final float[] getScale() { return scale; }
175 
markShapeDirty()176     public final void markShapeDirty() {
177         dirty |= DIRTY_SHAPE;
178     }
isShapeDirty()179     public final boolean isShapeDirty() {
180         return 0 != ( dirty & DIRTY_SHAPE ) ;
181     }
markStateDirty()182     public final void markStateDirty() {
183         dirty |= DIRTY_STATE;
184     }
isStateDirty()185     public final boolean isStateDirty() {
186         return 0 != ( dirty & DIRTY_STATE ) ;
187     }
188 
getBounds()189     public final AABBox getBounds() { return box; }
190 
getRenderModes()191     public final int getRenderModes() { return renderModes; }
192 
getRegion(final GL2ES2 gl, final RegionRenderer renderer)193     public GLRegion getRegion(final GL2ES2 gl, final RegionRenderer renderer) {
194         validate(gl, renderer);
195         return region;
196     }
197 
198     /**
199      * Renders {@link OutlineShape} using local {@link GLRegion} which might be cached or updated.
200      * <p>
201      * No matrix operations (translate, scale, ..) are performed.
202      * </p>
203      * @param gl
204      * @param renderer
205      * @param sampleCount
206      */
drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount)207     public void drawShape(final GL2ES2 gl, final RegionRenderer renderer, final int[] sampleCount) {
208         final float r, g, b, a;
209         final boolean isPressed = isPressed(), isToggleOn = isToggleOn();
210         final boolean modBaseColor = !Region.hasColorChannel( renderModes ) && !Region.hasColorTexture( renderModes );
211         if( modBaseColor ) {
212             if( isPressed ) {
213                 r = rgbaColor[0]*pressedRGBAModulate[0];
214                 g = rgbaColor[1]*pressedRGBAModulate[1];
215                 b = rgbaColor[2]*pressedRGBAModulate[2];
216                 a = rgbaColor[3]*pressedRGBAModulate[3];
217             } else if( isToggleable() ) {
218                 if( isToggleOn ) {
219                     r = rgbaColor[0]*toggleOnRGBAModulate[0];
220                     g = rgbaColor[1]*toggleOnRGBAModulate[1];
221                     b = rgbaColor[2]*toggleOnRGBAModulate[2];
222                     a = rgbaColor[3]*toggleOnRGBAModulate[3];
223                 } else {
224                     r = rgbaColor[0]*toggleOffRGBAModulate[0];
225                     g = rgbaColor[1]*toggleOffRGBAModulate[1];
226                     b = rgbaColor[2]*toggleOffRGBAModulate[2];
227                     a = rgbaColor[3]*toggleOffRGBAModulate[3];
228                 }
229             } else {
230                 r = rgbaColor[0];
231                 g = rgbaColor[1];
232                 b = rgbaColor[2];
233                 a = rgbaColor[3];
234             }
235         } else {
236             if( isPressed ) {
237                 r = pressedRGBAModulate[0];
238                 g = pressedRGBAModulate[1];
239                 b = pressedRGBAModulate[2];
240                 a = pressedRGBAModulate[3];
241             } else if( isToggleable() ) {
242                 if( isToggleOn ) {
243                     r = toggleOnRGBAModulate[0];
244                     g = toggleOnRGBAModulate[1];
245                     b = toggleOnRGBAModulate[2];
246                     a = toggleOnRGBAModulate[3];
247                 } else {
248                     r = toggleOffRGBAModulate[0];
249                     g = toggleOffRGBAModulate[1];
250                     b = toggleOffRGBAModulate[2];
251                     a = toggleOffRGBAModulate[3];
252                 }
253             } else {
254                 r = rgbaColor[0];
255                 g = rgbaColor[1];
256                 b = rgbaColor[2];
257                 a = rgbaColor[3];
258             }
259         }
260         renderer.getRenderState().setColorStatic(r, g, b, a);
261         getRegion(gl, renderer).draw(gl, renderer, sampleCount);
262     }
263 
createGLRegion()264     protected GLRegion createGLRegion() {
265         return GLRegion.create(renderModes, null);
266     }
267 
268     /**
269      * Validates the shape's underlying {@link GLRegion}.
270      *
271      * @param gl
272      * @param renderer
273      */
validate(final GL2ES2 gl, final RegionRenderer renderer)274     public final void validate(final GL2ES2 gl, final RegionRenderer renderer) {
275         if( isShapeDirty() || null == region ) {
276             box.reset();
277             if( null == region ) {
278                 region = createGLRegion();
279             } else {
280                 region.clear(gl);
281             }
282             addShapeToRegion(gl, renderer);
283             if( DRAW_DEBUG_BOX ) {
284                 region.clear(gl);
285                 final OutlineShape shape = new OutlineShape(renderer.getRenderState().getVertexFactory());
286                 shape.setSharpness(shapesSharpness);
287                 shape.setIsQuadraticNurbs();
288                 region.addOutlineShape(shape, null, rgbaColor);
289             }
290             region.setQuality(regionQuality);
291             dirty &= ~(DIRTY_SHAPE|DIRTY_STATE);
292         } else if( isStateDirty() ) {
293             region.markStateDirty();
294             dirty &= ~DIRTY_STATE;
295         }
296     }
297 
getColor()298     public float[] getColor() {
299         return rgbaColor;
300     }
301 
getQuality()302     public final int getQuality() { return regionQuality; }
setQuality(final int q)303     public final void setQuality(final int q) {
304         this.regionQuality = q;
305         if( null != region ) {
306             region.setQuality(q);
307         }
308     }
setSharpness(final float sharpness)309     public final void setSharpness(final float sharpness) {
310         this.shapesSharpness = sharpness;
311         markShapeDirty();
312     }
getSharpness()313     public final float getSharpness() {
314         return shapesSharpness;
315     }
316 
setColor(final float r, final float g, final float b, final float a)317     public final void setColor(final float r, final float g, final float b, final float a) {
318         this.rgbaColor[0] = r;
319         this.rgbaColor[1] = g;
320         this.rgbaColor[2] = b;
321         this.rgbaColor[3] = a;
322     }
setPressedColorMod(final float r, final float g, final float b, final float a)323     public final void setPressedColorMod(final float r, final float g, final float b, final float a) {
324         this.pressedRGBAModulate[0] = r;
325         this.pressedRGBAModulate[1] = g;
326         this.pressedRGBAModulate[2] = b;
327         this.pressedRGBAModulate[3] = a;
328     }
setToggleOnColorMod(final float r, final float g, final float b, final float a)329     public final void setToggleOnColorMod(final float r, final float g, final float b, final float a) {
330         this.toggleOnRGBAModulate[0] = r;
331         this.toggleOnRGBAModulate[1] = g;
332         this.toggleOnRGBAModulate[2] = b;
333         this.toggleOnRGBAModulate[3] = a;
334     }
setToggleOffColorMod(final float r, final float g, final float b, final float a)335     public final void setToggleOffColorMod(final float r, final float g, final float b, final float a) {
336         this.toggleOffRGBAModulate[0] = r;
337         this.toggleOffRGBAModulate[1] = g;
338         this.toggleOffRGBAModulate[2] = b;
339         this.toggleOffRGBAModulate[3] = a;
340     }
341 
342     @Override
toString()343     public final String toString() {
344         return getClass().getSimpleName()+"["+getSubString()+"]";
345     }
346 
getSubString()347     public String getSubString() {
348         return "enabled "+enabled+", toggle[able "+toggleable+", state "+toggle+"], "+translate[0]+" / "+translate[1]+", box "+box;
349     }
350 
351     //
352     // Input
353     //
354 
setPressed(final boolean b)355     public void setPressed(final boolean b) {
356         this.down  = b;
357         markStateDirty();
358     }
isPressed()359     public boolean isPressed() {
360         return this.down;
361     }
362 
setToggleable(final boolean toggleable)363     public void setToggleable(final boolean toggleable) {
364         this.toggleable = toggleable;
365     }
isToggleable()366     public boolean isToggleable() {
367         return toggleable;
368     }
setToggle(final boolean v)369     public void setToggle(final boolean v) {
370         toggle = v;
371         markStateDirty();
372     }
toggle()373     public void toggle() {
374         if( isToggleable() ) {
375             toggle = !toggle;
376         }
377         markStateDirty();
378     }
isToggleOn()379     public boolean isToggleOn() { return toggle; }
380 
addMouseListener(final MouseGestureListener l)381     public final void addMouseListener(final MouseGestureListener l) {
382         if(l == null) {
383             return;
384         }
385         @SuppressWarnings("unchecked")
386         final ArrayList<MouseGestureListener> clonedListeners = (ArrayList<MouseGestureListener>) mouseListeners.clone();
387         clonedListeners.add(l);
388         mouseListeners = clonedListeners;
389     }
390 
removeMouseListener(final MouseGestureListener l)391     public final void removeMouseListener(final MouseGestureListener l) {
392         if (l == null) {
393             return;
394         }
395         @SuppressWarnings("unchecked")
396         final ArrayList<MouseGestureListener> clonedListeners = (ArrayList<MouseGestureListener>) mouseListeners.clone();
397         clonedListeners.remove(l);
398         mouseListeners = clonedListeners;
399     }
400 
401     /**
402      * Combining {@link MouseListener} and {@link GestureListener}
403      */
404     public static interface MouseGestureListener extends MouseListener, GestureListener {
405     }
406 
407     /**
408      * Convenient adapter combining dummy implementation for {@link MouseListener} and {@link GestureListener}
409      */
410     public static abstract class MouseGestureAdapter extends MouseAdapter implements MouseGestureListener {
411         @Override
gestureDetected(final GestureEvent gh)412         public void gestureDetected(final GestureEvent gh) {
413         }
414     }
415 
416     /**
417      * {@link UIShape} event details for propagated {@link NEWTEvent}s
418      * containing reference of {@link #shape the intended shape} as well as
419      * the {@link #objPos rotated relative position} and {@link #rotBounds bounding box}.
420      * The latter fields are also normalized to lower-left zero origin, allowing easier usage.
421      */
422     public static class PointerEventInfo {
423         /** The intended {@link UIShape} instance for this event */
424         public final UIShape shape;
425         /** The relative pointer position inside the intended {@link UIShape}. */
426         public final float[] objPos;
427         /** window x-position in OpenGL model space */
428         public final int glWinX;
429         /** window y-position in OpenGL model space */
430         public final int glWinY;
431 
PointerEventInfo(final int glWinX, final int glWinY, final UIShape shape, final float[] objPos)432         PointerEventInfo(final int glWinX, final int glWinY, final UIShape shape, final float[] objPos) {
433             this.glWinX = glWinX;
434             this.glWinY = glWinY;
435             this.shape = shape;
436             this.objPos = objPos;
437         }
438 
toString()439         public String toString() {
440             return "EventDetails[winPos ["+glWinX+", "+glWinY+"], objPos ["+objPos[0]+", "+objPos[1]+", "+objPos[2]+"], "+shape+"]";
441         }
442     }
443 
444     /**
445      * @param e original Newt {@link GestureEvent}
446      * @param glWinX x-position in OpenGL model space
447      * @param glWinY y-position in OpenGL model space
448      */
dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final float[] objPos)449     public final void dispatchGestureEvent(final GestureEvent e, final int glWinX, final int glWinY, final float[] objPos) {
450         e.setAttachment(new PointerEventInfo(glWinX, glWinY, this, objPos));
451         for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) {
452             mouseListeners.get(i).gestureDetected(e);
453         }
454     }
455 
456     /**
457      *
458      * @param e original Newt {@link MouseEvent}
459      * @param glX x-position in OpenGL model space
460      * @param glY y-position in OpenGL model space
461      */
dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos)462     public final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY, final float[] objPos) {
463         e.setAttachment(new PointerEventInfo(glWinX, glWinY, this, objPos));
464 
465         final short eventType = e.getEventType();
466         if( 1 == e.getPointerCount() ) {
467             switch( eventType ) {
468                 case MouseEvent.EVENT_MOUSE_CLICKED:
469                     toggle();
470                     break;
471                 case MouseEvent.EVENT_MOUSE_PRESSED:
472                     setPressed(true);
473                     break;
474                 case MouseEvent.EVENT_MOUSE_RELEASED:
475                     setPressed(false);
476                     break;
477             }
478         }
479 
480         for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) {
481             final MouseGestureListener l = mouseListeners.get(i);
482             switch( eventType ) {
483                 case MouseEvent.EVENT_MOUSE_CLICKED:
484                     l.mouseClicked(e);
485                     break;
486                 case MouseEvent.EVENT_MOUSE_ENTERED:
487                     l.mouseEntered(e);
488                     break;
489                 case MouseEvent.EVENT_MOUSE_EXITED:
490                     l.mouseExited(e);
491                     break;
492                 case MouseEvent.EVENT_MOUSE_PRESSED:
493                     l.mousePressed(e);
494                     break;
495                 case MouseEvent.EVENT_MOUSE_RELEASED:
496                     l.mouseReleased(e);
497                     break;
498                 case MouseEvent.EVENT_MOUSE_MOVED:
499                     l.mouseMoved(e);
500                     break;
501                 case MouseEvent.EVENT_MOUSE_DRAGGED:
502                     l.mouseDragged(e);
503                     break;
504                 case MouseEvent.EVENT_MOUSE_WHEEL_MOVED:
505                     l.mouseWheelMoved(e);
506                     break;
507                 default:
508                     throw new NativeWindowException("Unexpected mouse event type " + e.getEventType());
509             }
510         }
511     }
512 
513     //
514     //
515     //
516 
clearImpl(GL2ES2 gl, RegionRenderer renderer)517     protected abstract void clearImpl(GL2ES2 gl, RegionRenderer renderer);
destroyImpl(GL2ES2 gl, RegionRenderer renderer)518     protected abstract void destroyImpl(GL2ES2 gl, RegionRenderer renderer);
addShapeToRegion(GL2ES2 gl, RegionRenderer renderer)519     protected abstract void addShapeToRegion(GL2ES2 gl, RegionRenderer renderer);
520 
521     //
522     //
523     //
524 
createDebugOutline(final OutlineShape shape, final AABBox box)525     protected OutlineShape createDebugOutline(final OutlineShape shape, final AABBox box) {
526         final float tw = box.getWidth();
527         final float th = box.getHeight();
528 
529         final float minX = box.getMinX();
530         final float minY = box.getMinY();
531         final float z = box.getMinZ() + 0.025f;
532 
533         // CCW!
534         shape.addVertex(minX,    minY,      z, true);
535         shape.addVertex(minX+tw, minY,      z, true);
536         shape.addVertex(minX+tw, minY + th, z, true);
537         shape.addVertex(minX,    minY + th, z, true);
538         shape.closeLastOutline(true);
539 
540         return shape;
541     }
542 
543 }
544