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