diff options
author | Sven Göthel <[email protected]> | 2024-01-19 00:30:04 +0100 |
---|---|---|
committer | Sven Göthel <[email protected]> | 2024-01-19 00:30:04 +0100 |
commit | 30369089cd02e9a0a4875c2a5f5958bcf497c701 (patch) | |
tree | 3482d60d2d4e43f4d7aba07f89518ca95c1ca11d /src/graphui | |
parent | f6ae0ff2e4bad67c929a53d705af02e7d92368bc (diff) |
GraphUI Tooltip*: Generalize Tooltip base (more versatile) and add TooltipShape supporting general Shapes to be added
Diffstat (limited to 'src/graphui')
5 files changed, 240 insertions, 41 deletions
diff --git a/src/graphui/classes/com/jogamp/graph/ui/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/Scene.java index 3541473ea..698aa2d2f 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/Scene.java +++ b/src/graphui/classes/com/jogamp/graph/ui/Scene.java @@ -127,7 +127,7 @@ public final class Scene implements Container, GLEventListener { private static final boolean DEBUG = false; private final List<Shape> shapes = new CopyOnWriteArrayList<Shape>(); - private final AtomicReference<Tooltip> toolTipStarted = new AtomicReference<Tooltip>(); + private final AtomicReference<Tooltip> toolTipActive = new AtomicReference<Tooltip>(); private final AtomicReference<Shape> toolTipHUD = new AtomicReference<Shape>(); private boolean doFrustumCulling = false; @@ -492,10 +492,14 @@ public final class Scene implements Container, GLEventListener { displayedOnce = true; syncDisplayedOnce.notifyAll(); } - final Tooltip tt = toolTipStarted.get(); + final Tooltip tt = toolTipActive.get(); if( null != tt && null == toolTipHUD.get() ) { final Shape[] hud = { null }; - if( tt.tick() && forOne(pmv, tt.getTool(), () -> { hud[0] = tt.createTip(Scene.this, pmv); }) ) { + if( tt.tick() && forOne(pmv, tt.getTool(), () -> { + final AABBox toolMvBounds = tt.getToolMvBounds(pmv); + hud[0] = tt.createTip(drawable, Scene.this, pmv, toolMvBounds); + }) ) + { setToolTip( hud[0] ); } } @@ -1190,7 +1194,7 @@ public final class Scene implements Container, GLEventListener { final Shape s = dispatchMouseEventPickShape(e, glWinX, glWinY); if( null != s ) { mouseOver = true; - toolTipStarted.set( s.startToolTip() ); + toolTipActive.set( s.startToolTip() ); } else { mouseOver = false; } @@ -1221,20 +1225,21 @@ public final class Scene implements Container, GLEventListener { } private void setToolTip(final Shape hud) { - toolTipStarted.set( null ); addShape( hud ); toolTipHUD.set( hud ); } private void clearToolTip() { - final Tooltip tt = toolTipStarted.getAndSet(null); + final Tooltip tt = toolTipActive.getAndSet(null); if( null != tt ) { tt.stop(); } final Shape s = toolTipHUD.getAndSet(null); if( null != s ) { invoke(false, (final GLAutoDrawable drawable) -> { - removeShape(drawable.getGL().getGL2ES2(), s); + if( s == removeShape(s) ) { + tt.destroyTip(drawable.getGL().getGL2ES2(), renderer, s); + } return true; }); } diff --git a/src/graphui/classes/com/jogamp/graph/ui/Shape.java b/src/graphui/classes/com/jogamp/graph/ui/Shape.java index b48163753..672aab9a7 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/Shape.java +++ b/src/graphui/classes/com/jogamp/graph/ui/Shape.java @@ -1418,14 +1418,20 @@ public abstract class Shape { return position.z() * getScale().z() + zOffset; } - /** Set's a new {@link Tooltip} for this shape. */ + /** + * Set's a new {@link Tooltip} for this shape. + * <p> + * The {@link Shape} must be set {@link #setInteractive(boolean) interactive} + * to receive the mouse-over signal, i.e. being picked. + * </p> + */ public Tooltip setToolTip(final Tooltip newTooltip) { final Tooltip oldTT = this.tooltip; this.tooltip = null; if( null != oldTT ) { oldTT.stop(); } - newTooltip.setToolOwner(this); + newTooltip.setTool(this); this.tooltip = newTooltip; return newTooltip; } @@ -1434,7 +1440,7 @@ public abstract class Shape { tooltip = null; if( null != tt ) { tt.stop(); - tt.setToolOwner(null); + tt.setTool(null); } } private void stopToolTip() { diff --git a/src/graphui/classes/com/jogamp/graph/ui/Tooltip.java b/src/graphui/classes/com/jogamp/graph/ui/Tooltip.java index b8e2467fe..7bed984f5 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/Tooltip.java +++ b/src/graphui/classes/com/jogamp/graph/ui/Tooltip.java @@ -28,7 +28,15 @@ package com.jogamp.graph.ui; import com.jogamp.common.os.Clock; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.math.Vec2f; +import com.jogamp.math.Vec4f; +import com.jogamp.math.geom.AABBox; import com.jogamp.math.util.PMVMatrix4f; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLAutoDrawable; /** A HUD tooltip for {@link Shape}, see {@link Shape#setToolTip(Tooltip)}. */ public abstract class Tooltip { @@ -41,13 +49,30 @@ public abstract class Tooltip { private volatile long delayT1; /** Shape 'tool' owning this tooltip. */ private Shape tool; + protected final int renderModes; + protected final Vec4f backColor = new Vec4f(1, 1, 0, 1); + protected final Vec4f frontColor = new Vec4f(0.1f, 0.1f, 0.1f, 1); - protected Tooltip(final long delayMS) { + /** + * + * @param backColor optional HUD tip background color + * @param frontColor optional HUD tip front color + * @param delayMS delay until HUD tip is visible after timer start (mouse moved) + * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. + */ + protected Tooltip(final Vec4f backColor, final Vec4f frontColor, final long delayMS, final int renderModes) { this.delayMS = delayMS; this.delayT1 = 0; this.tool = null; + this.renderModes = renderModes; + if( null != backColor ) { + this.backColor.set(backColor); + } + if( null != frontColor ) { + this.frontColor.set(frontColor); + } } - /* pp */ final void setToolOwner(final Shape owner) { tool = owner; } + /* pp */ final void setTool(final Shape tool) { this.tool = tool; } /** Returns {@link Shape} 'tool' owning this tooltip, set after {@link Shape#setToolTip(Tooltip)}. */ public final Shape getTool() { @@ -79,12 +104,60 @@ public abstract class Tooltip { return true; } + /** Little helper for {@link #createTip(GLAutoDrawable, Scene, PMVMatrix4f, AABBox)} returning the Mv {@link AABBox} of the tool within {@link Scene} Mv space. */ + public AABBox getToolMvBounds(final PMVMatrix4f pmv) { + return getTool().getBounds().transform(pmv.getMv(), new AABBox()); + } + /** Little helper for {@link #createTip(GLAutoDrawable, Scene, PMVMatrix4f, AABBox)} returning the Mv position of the tip within {@link Scene} Mv space. */ + public Vec2f getTipMvPosition(final Scene scene, final PMVMatrix4f pmv, final float tipWidth, final float tipHeight) { + return getTipMvPosition(scene, getToolMvBounds(pmv), tipWidth, tipHeight); + } + /** Little helper for {@link #createTip(GLAutoDrawable, Scene, PMVMatrix4f, AABBox)} returning the Mv position of the tip within {@link Scene} Mv space. */ + public Vec2f getTipMvPosition(final Scene scene, final AABBox toolMvBounds, final float tipWidth, final float tipHeight) { + final AABBox sceneAABox = scene.getBounds(); + final Vec2f pos = new Vec2f(); + if( toolMvBounds.getCenter().x() - tipWidth/2 >= sceneAABox.getLow().x() ) { + pos.setX( toolMvBounds.getCenter().x()-tipWidth/2 ); + } else { + pos.setX( sceneAABox.getLow().x() ); + } + if( toolMvBounds.getHigh().y() + tipHeight <= sceneAABox.getHigh().y() ) { + pos.setY( toolMvBounds.getHigh().y() ); + } else if( toolMvBounds.getHigh().y() >= tipHeight ) { + pos.setY( toolMvBounds.getHigh().y() - tipHeight ); + } else { + pos.setY( sceneAABox.getHigh().y() - tipHeight ); + } + return pos; + } + /** * Create a new HUD tip shape, usually called by {@link Scene} + * @param drawable current {@link GLAutoDrawable} * @param scene the {@link Scene} caller for which this HUD tip shape is created * @param pmv {@link PMVMatrix4f}, which shall be properly initialized, e.g. via {@link Scene#setupMatrix(PMVMatrix4f)} + * @param toolMvBounds TODO * @return newly created HUD tip shape + * @see #destroyTip(GL2ES2, RegionRenderer, Shape) */ - public abstract Shape createTip(final Scene scene, final PMVMatrix4f pmv); + public abstract Shape createTip(final GLAutoDrawable drawable, final Scene scene, final PMVMatrix4f pmv, AABBox toolMvBounds); + + /** + * Destroy a {@link #createTip(GLAutoDrawable, Scene, PMVMatrix4f, AABBox) created} HUD tip. + * <p> + * Called after {@link Scene#removeShape(Shape)}, allowing implementation to perform certain + * resource cleanup tasks. Even keeping the {@link Shape} tip alive is possible. + * </p> + * <p> + * This default implementation simply calls {@link Shape#destroy(GL2ES2, RegionRenderer)}. + * </p> + * @param gl + * @param renderer + * @param tip + * @see #createTip(GLAutoDrawable, Scene, PMVMatrix4f, AABBox) + */ + public void destroyTip(final GL2ES2 gl, final RegionRenderer renderer, final Shape tip) { + tip.destroy(gl, renderer); + } }
\ No newline at end of file diff --git a/src/graphui/classes/com/jogamp/graph/ui/TooltipShape.java b/src/graphui/classes/com/jogamp/graph/ui/TooltipShape.java new file mode 100644 index 000000000..ec3dbf45f --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/TooltipShape.java @@ -0,0 +1,125 @@ +/** + * Copyright 2023-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 com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.ui.layout.Alignment; +import com.jogamp.graph.ui.layout.BoxLayout; +import com.jogamp.graph.ui.layout.GridLayout; +import com.jogamp.graph.ui.shapes.Rectangle; +import com.jogamp.math.Vec2f; +import com.jogamp.math.Vec4f; +import com.jogamp.math.geom.AABBox; +import com.jogamp.math.util.PMVMatrix4f; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLAutoDrawable; + +/** A HUD {@link Shape} {@link Tooltip} for {@link Shape}, see {@link Shape#setToolTip(Tooltip)}. */ +public class TooltipShape extends Tooltip { + /** + * Optional HUD tip destroy callback for the user provided {@link Shape}, see {@link #Tooltip#destroyTip(GL2ES2, RegionRenderer, Shape)}. + * <p> + * In case no callback is being set via {@link TooltipShape#TooltipShape(Vec2f, long, Shape, DestroyCallback)} + * {@link #Tooltip#destroyTip(GL2ES2, RegionRenderer, Shape)} destroys the shape. + * Otherwise the callback gets invoked. + * </p> + * @param gl + * @param renderer + * @param tip + * @see TooltipShape#TooltipShape(Vec2f, long, Shape, DestroyCallback) + * @see TooltipShape#createTip(GLAutoDrawable, Scene, PMVMatrix4f, AABBox) + */ + public static interface DestroyCallback { + public void destroyTip(final GL2ES2 gl, final RegionRenderer renderer, final Shape tip); + } + + /** Text of this tooltip */ + private final Shape tip; + private final Vec2f scale; + private final float borderThickness; + private final DestroyCallback dtorCallback; + + /** + * Ctor of {@link TooltipShape}. + * <p> + * The {@link Shape} is destroyed via {@link #destroyTip(GL2ES2, RegionRenderer, Shape)}, + * since no {@link DestroyCallback} is being provided via {@link TooltipShape#TooltipShape(Vec2f, long, Shape, DestroyCallback)}. + * </p> + * @param scale HUD tip scale for the tip shape + * @param delayMS delay until HUD tip is visible after timer start (mouse moved) + * @param tip HUD tip shape + */ + public TooltipShape(final Vec2f scale, final long delayMS, final int renderModes, final Shape tip) { + this(null, null, 0, scale, delayMS, renderModes, tip, null); + } + + /** + * Ctor of {@link TooltipShape}. + * <p> + * The {@link Shape} is not destroyed via {@link #destroyTip(GL2ES2, RegionRenderer, Shape)}, + * if {@link DestroyCallback} {@code dtor} is not {@code null}, otherwise it is destroyed. + * </p> + * @param scale HUD tip scale for the tip shape + * @param delayMS delay until HUD tip is visible after timer start (mouse moved) + * @param tip HUD tip shape + * @param dtor + */ + public TooltipShape(final Vec4f backColor, final Vec4f borderColor, final float borderThickness, + final Vec2f scale, final long delayMS, final int renderModes, final Shape tip, final DestroyCallback dtor) { + super(backColor, borderColor, delayMS, renderModes); + this.tip = tip; + this.scale = scale; + this.borderThickness = borderThickness; + this.dtorCallback = dtor; + } + + @Override + public Shape createTip(final GLAutoDrawable drawable, final Scene scene, final PMVMatrix4f pmv, final AABBox toolMvBounds) { + final float zEps = scene.getZEpsilon(16); + + final float w = toolMvBounds.getWidth()*scale.x(); + final float h = toolMvBounds.getHeight()*scale.y(); + + final Group g = new Group(new BoxLayout(w, h, Alignment.FillCenter)); + g.addShape(new Rectangle(renderModes, 1*w/h, 1, 0).setColor(backColor).setBorder(borderThickness).setBorderColor(frontColor)); + g.addShape(tip.move(0, 0, zEps)); // above back + g.setInteractive(false); + + final Vec2f pos = getTipMvPosition(scene, toolMvBounds, w, h); + g.moveTo(pos.x(), pos.y(), 100*zEps); + return g; + } + @Override + public void destroyTip(final GL2ES2 gl, final RegionRenderer renderer, final Shape tip) { + if( null != dtorCallback ) { + dtorCallback.destroyTip(gl, renderer, tip); + } else { + super.destroyTip(gl, renderer, tip); + } + } +} diff --git a/src/graphui/classes/com/jogamp/graph/ui/TooltipText.java b/src/graphui/classes/com/jogamp/graph/ui/TooltipText.java index b421604c9..77386d224 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/TooltipText.java +++ b/src/graphui/classes/com/jogamp/graph/ui/TooltipText.java @@ -31,10 +31,12 @@ import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.font.Font; import com.jogamp.graph.ui.shapes.Button; +import com.jogamp.math.Vec2f; import com.jogamp.math.Vec4f; import com.jogamp.math.geom.AABBox; import com.jogamp.math.geom.plane.AffineTransform; import com.jogamp.math.util.PMVMatrix4f; +import com.jogamp.opengl.GLAutoDrawable; import com.jogamp.opengl.GLProfile; /** A HUD text {@link Tooltip} for {@link Shape}, see {@link Shape#setToolTip(Tooltip)}. */ @@ -44,9 +46,6 @@ public class TooltipText extends Tooltip { /** Font of this tooltip */ private final Font tipFont; private final float scaleY; - private final int renderModes; - private final Vec4f backColor = new Vec4f(); - private final Vec4f labelColor = new Vec4f(); /** * Ctor of {@link TooltipText}. @@ -61,13 +60,10 @@ public class TooltipText extends Tooltip { public TooltipText(final CharSequence tipText, final Font tipFont, final Vec4f backColor, final Vec4f labelColor, final float scaleY, final long delayMS, final int renderModes) { - super(delayMS); + super(backColor, labelColor, delayMS, renderModes); this.tipText = tipText; this.tipFont = tipFont; this.scaleY = scaleY; - this.renderModes = renderModes; - this.backColor.set(backColor); - this.labelColor.set(labelColor); } /** * Ctor of {@link TooltipText} using {@link Tooltip#DEFAULT_DELAY}, {@link Region#VBAA_RENDERING_BIT} @@ -78,43 +74,37 @@ public class TooltipText extends Tooltip { * @param tool the tool shape for this tip */ public TooltipText(final CharSequence tipText, final Font tipFont, final float scaleY) { - this(tipText, tipFont, new Vec4f(1, 1, 0, 0.80f), new Vec4f(0.1f, 0.1f, 0.1f, 1), scaleY, Tooltip.DEFAULT_DELAY, Region.VBAA_RENDERING_BIT); + this(tipText, tipFont, null, null, scaleY, Tooltip.DEFAULT_DELAY, Region.VBAA_RENDERING_BIT); } @Override - public Shape createTip(final Scene scene, final PMVMatrix4f pmv) { + public Shape createTip(final GLAutoDrawable gl, final Scene scene, final PMVMatrix4f pmv, final AABBox toolMvBounds) { final float zEps = scene.getZEpsilon(16); // Precompute text-box size .. guessing pixelSize + final AABBox sceneAABox = scene.getBounds(); final AffineTransform tempT1 = new AffineTransform(); final AffineTransform tempT2 = new AffineTransform(); final AABBox tipBox_em = tipFont.getGlyphBounds(tipText, tempT1, tempT2); - // final AABBox toolAABox = scene.getBounds(new PMVMatrix4f(), tool); - final AABBox toolAABox = getTool().getBounds().transform(pmv.getMv(), new AABBox()); - - final float h = toolAABox.getHeight() * scaleY; - final float w = tipBox_em.getWidth() / tipBox_em.getHeight() * h; - - final AABBox sceneAABox = scene.getBounds(); - final float xpos, ypos; - if( toolAABox.getCenter().x()-w/2 < sceneAABox.getLow().x() ) { - xpos = sceneAABox.getLow().x(); - } else { - xpos = toolAABox.getCenter().x()-w/2; - } - if( toolAABox.getHigh().y() > sceneAABox.getHigh().y() - h ) { - ypos = sceneAABox.getHigh().y() - h; - } else { - ypos = toolAABox.getHigh().y(); + float h = toolMvBounds.getHeight() * scaleY; + float w = tipBox_em.getWidth() * h / tipBox_em.getHeight(); + if( w > sceneAABox.getWidth() * 0.9f) { + w = sceneAABox.getWidth() * 0.9f; + h = tipBox_em.getHeight() * w / tipBox_em.getWidth(); + } else if( h > sceneAABox.getHeight() * 0.9f) { + h = sceneAABox.getHeight() * 0.9f; + w = tipBox_em.getWidth() * h / tipBox_em.getHeight(); } + final Vec2f pos = getTipMvPosition(scene, toolMvBounds, w, h); + final Button ntip = (Button) new Button(renderModes, tipFont, tipText, w, h, zEps) .setPerp() - .moveTo(xpos, ypos, 100*zEps) + .moveTo(pos.x(), pos.y(), 100*zEps) .setColor(backColor) // .setBorder(0.05f).setBorderColor(0, 0, 0, 1) .setInteractive(false); - ntip.setLabelColor(labelColor); + ntip.setLabelColor(frontColor); ntip.setSpacing(0.10f, 0.10f); return ntip; } |