diff options
author | Sven Gothel <[email protected]> | 2023-12-24 19:12:52 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-12-24 19:12:52 +0100 |
commit | c6e39c6e313a34688ca0164d7a34b6465e92396f (patch) | |
tree | ca74beed22defac6f26f7ef82f228ff83a339e61 /src/graphui/classes | |
parent | b5e6a852451f2a78a1783ca8fbd704005fbafd07 (diff) |
GraphUI RangeSlider Widget: Add rectangular page-sized knob mode using a 'page size' of covered view. Resolve color-setup.
Tested with FontView01
Diffstat (limited to 'src/graphui/classes')
-rw-r--r-- | src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java | 12 | ||||
-rw-r--r-- | src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java | 420 |
2 files changed, 357 insertions, 75 deletions
diff --git a/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java b/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java index 3fc48306a..799a9e8ad 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java +++ b/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java @@ -128,10 +128,14 @@ public class MediaPlayer extends Widget { mButton.setName("mp.mButton").setInteractive(false); mButton.setPerp().setPressedColorMod(1f, 1f, 1f, 0.85f); - final RangeSlider ctrlSlider = new RangeSlider(renderModes, aratio, ctrlSliderHeight, 4.0f, 0, 100, 0); + final RangeSlider ctrlSlider; { - final float dy = ( ctrlSlider.getKnobSize() - ctrlSliderHeight ) * 0.5f; - ctrlSlider.setPaddding(new Padding(0, 0, ctrlCellHeight-dy, 0)); + final float knobScale = 3f; + final float knobHeight = ctrlSliderHeight * knobScale; + ctrlSlider = new RangeSlider(renderModes, new Vec2f(aratio - knobHeight, ctrlSliderHeight), knobScale, new Vec2f(0, 100), 0); + final float dx = knobHeight / 2f; + final float dy = ( knobHeight - ctrlSliderHeight ) * 0.5f; + ctrlSlider.setPaddding(new Padding(0, dx, ctrlCellHeight-dy, dx)); } ctrlSlider.setName("mp.slider"); @@ -153,7 +157,7 @@ public class MediaPlayer extends Widget { // System.err.println("MediaButton State: "+mp); if( eventMask.isSet(GLMediaPlayer.EventMask.Bit.Init) ) { System.err.println(mp.toString()); - ctrlSlider.setMinMax(0, mp.getDuration(), 0); + ctrlSlider.setMinMax(new Vec2f(0, mp.getDuration()), 0); } else if( eventMask.isSet(GLMediaPlayer.EventMask.Bit.Play) ) { playButton.setToggle(true); } else if( eventMask.isSet(GLMediaPlayer.EventMask.Bit.Pause) ) { diff --git a/src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java b/src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java index 495d81149..f6498974e 100644 --- a/src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java +++ b/src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java @@ -32,20 +32,26 @@ import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.ui.Group; import com.jogamp.graph.ui.Shape; +import com.jogamp.graph.ui.layout.Padding; import com.jogamp.graph.ui.shapes.BaseButton; import com.jogamp.graph.ui.shapes.Button; import com.jogamp.graph.ui.shapes.Rectangle; import com.jogamp.math.Vec2f; import com.jogamp.math.Vec3f; import com.jogamp.math.Vec4f; +import com.jogamp.newt.event.KeyAdapter; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.event.KeyListener; import com.jogamp.newt.event.MouseEvent; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLProfile; import com.jogamp.opengl.util.texture.TextureSequence; /** - * RangeSlider {@link Widget} - * @see #RangeSlider(int, float, float, float, float, float, float) + * RangeSlider {@link Widget} either utilizing a simple positional round knob + * or a rectangular page-sized knob. + * @see #RangeSlider(int, Vec2f, float, Vec2f, float) + * @see #RangeSlider(int, Vec2f, Vec2f, float, float) */ public final class RangeSlider extends Widget { /** @@ -59,7 +65,7 @@ public final class RangeSlider extends Widget { /** Slider released down by user. */ void released(RangeSlider w, final MouseEvent e); /** - * Slide dragged by user + * Slide dragged by user (including clicked position) * @param w the {@link RangeSlider} widget owning the slider * @param old_val previous absolute value position of the slider * @param val the absolute value position of the slider @@ -69,60 +75,115 @@ public final class RangeSlider extends Widget { void dragged(RangeSlider w, float old_val, float val, float old_val_pct, float val_pct); } + private static final float pageKnobScale = 0.6f; // 0.6 * barWidth + private static final float pageBarLineScale = 0.25f; // 1/4 * ( barWidth - pageKnobWidth ) + private static final float pageKnobSizePctMin = 5f/100f; private final boolean horizontal; - private final float knobSz; - private final float width, height; + /** Width of knob orthogonal to sliding direction */ + private float knobDiameter; + /** Half length of knob in sliding direction */ + private float knobHalfLen; + private final Vec2f size; private final Group barAndKnob; private final Rectangle bar; - private final BaseButton knob; - private final Vec4f colBar = new Vec4f(0, 0, 1, 1); - private final Vec4f colKnob = new Vec4f(1, 0, 0, 1); + private final Shape knob; private SliderListener sliderListener = null; - private float min, max; - private float val, val_pct; + private final Vec2f minMax = new Vec2f(0, 100); + private float pageSize; + private float val=0, val_pct=0; + private boolean inverted=false; /** * Constructs a {@link RangeSlider}, i.e. its shapes and controls. + * <p> + * This slider comprises a background bar and a positional round knob. + * </p> * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. - * @param width width of this slider box. A horizontal slider has width >= height. - * @param height height of this slider box. A vertical slider has width < height. - * @param knobScale multiple of slider-bar height for {@link #getKnobSize()} - * @param min minimum value of slider - * @param max maximum value of slider + * @param size width and height of this slider box. A horizontal slider has width >= height. + * @param knobScale multiple of slider-bar height for {@link #getKnobHeight()} + * @param minMax minimum- and maximum-value of slider * @param value current value of slider */ - public RangeSlider(final int renderModes, final float width, final float height, final float knobScale, - final float min, final float max, final float value) { - this.horizontal = width >= height; - if( horizontal ) { - knobSz = height*knobScale; - this.width = width - knobSz; // half knobSz left and right - this.height = height; + public RangeSlider(final int renderModes, final Vec2f size, final float knobScale, + final Vec2f minMax, final float value) { + this(renderModes, size, knobScale, minMax, Float.NaN, value); + } + /** + * Constructs a {@link RangeSlider}, i.e. its shapes and controls. + * <p> + * This slider comprises a framing bar and a rectangular page-sized knob. + * </p> + * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}. + * @param size width and height of this slider box. A horizontal slider has width >= height. + * @param minMax minimum- and maximum-value of slider + * @param pageSize size of one virtual-page, triggers rendering mode from knob to rectangle + * @param value current value of slider + */ + public RangeSlider(final int renderModes, final Vec2f size, + final Vec2f minMax, final float pageSize, final float value) { + this(renderModes, size, 0, minMax, pageSize, value); + } + private RangeSlider(final int renderModes_, final Vec2f size, final float knobScale, + final Vec2f minMax, final float pageSz, final float value) { + // final int renderModes = ( renderModes_ & ~Region.AA_RENDERING_MASK ) | Region.COLORCHANNEL_RENDERING_BIT; + final int renderModes = renderModes_ & ~(Region.AA_RENDERING_MASK | Region.COLORCHANNEL_RENDERING_BIT); + this.pageSize = pageSz; + this.horizontal = size.x() >= size.y(); + barAndKnob = new Group(); + barAndKnob.setInteractive(true).setToggleable(false).setDraggable(false).setResizable(false); + this.size = new Vec2f(size); + if( Float.isNaN(pageSize) ) { + if( horizontal ) { + knobDiameter = size.y()*knobScale; + setPaddding(new Padding(knobHalfLen, 0, knobHalfLen, 0)); + } else { + knobDiameter = size.x()*knobScale; + setPaddding(new Padding(0, knobHalfLen, 0, knobHalfLen)); + } + knobHalfLen = knobDiameter * 0.5f; + bar = new Rectangle(renderModes, this.size.x(), this.size.y(), 0); + knob = new BaseButton(renderModes , knobDiameter*1.01f, knobDiameter); + setBackgroundBarColor(0.60f, 0.60f, 0.60f, 0.5f); } else { - knobSz = width*knobScale; - this.width = width; - this.height = height - knobSz; // half knobSz bottom and top + final float pageSizePct = Math.max(pageKnobSizePctMin, pageSize / getRange(minMax)); + final float barLineWidth; + final float knobWidth, knobHeight; + if( horizontal ) { + knobHeight = size.y() * pageKnobScale; + knobWidth = pageSizePct * this.size.x(); + barLineWidth = ( size.y() - knobHeight ) * pageBarLineScale; + knobHalfLen = knobWidth * 0.5f; + knobDiameter = knobHeight; + setPaddding(new Padding(size.y(), 0, size.y(), 0)); + } else { + knobWidth = size.x() * pageKnobScale; + knobHeight = pageSizePct * this.size.y(); + barLineWidth = ( size.x() - knobWidth ) * pageBarLineScale; + knobHalfLen = knobHeight * 0.5f; + knobDiameter = knobWidth; + setPaddding(new Padding(0, size.x(), 0, size.x())); + // System.err.println("ZZZ minMax "+minMax+", pageSize "+pageSize+" "+(pageSizePct*100f)+"% -> "+knobHeight+"/"+this.size.y()); + } + bar = new Rectangle(renderModes, this.size.x(), this.size.y(), barLineWidth); + knob = new Rectangle(renderModes, knobWidth, knobHeight, 0); } - barAndKnob = new Group(); - barAndKnob.setInteractive(true).setDraggable(false).setToggleable(false); - bar = new Rectangle(renderModes & ~Region.AA_RENDERING_MASK, this.width, this.height, 0); - bar.setToggleable(false); - bar.setColor(colBar); - knob = new BaseButton(renderModes & ~Region.AA_RENDERING_MASK, knobSz*1.01f, knobSz); - knob.setToggleable(false); - knob.setColor(colKnob); + setColor(0.30f, 0.30f, 0.30f, 1.0f); + + bar.setToggleable(false).setInteractive(false); + bar.setDraggable(false).setResizable(false); + + knob.setToggleable(false).setResizable(false); setName(getName()); barAndKnob.addShape( bar ); barAndKnob.addShape( knob ); addShape(barAndKnob); - setMinMax(min, max, value); + setMinMax(minMax, value); knob.onMove((final Shape s, final Vec3f origin, final Vec3f dest) -> { final float old_val = val; final float old_val_pct = val_pct; - setValuePct( getKnobValuePct( dest.x(), dest.y(), knobSz*0.5f ) ); - // System.err.println("KnobMove "+getName()+": "+origin+" -> "+dest+": "+old_val+" -> "+val+", "+(old_val_pct*100f)+"% -> "+(val_pct*100f)+"%"); + setValuePct( getKnobValuePct( dest.x(), dest.y(), knobHalfLen ) ); if( null != sliderListener ) { sliderListener.dragged(this, old_val, val, old_val_pct, val_pct); } @@ -130,19 +191,61 @@ public final class RangeSlider extends Widget { barAndKnob.addMouseListener(new Shape.MouseGestureAdapter() { @Override public void mouseClicked(final MouseEvent e) { + final float old_val = val; + final float old_val_pct = val_pct; final Shape.EventInfo shapeEvent = (Shape.EventInfo) e.getAttachment(); setValuePct( getKnobValuePct( shapeEvent.objPos.x(), shapeEvent.objPos.y(), 0 ) ); if( null != sliderListener ) { + sliderListener.dragged(RangeSlider.this, old_val, val, old_val_pct, val_pct); sliderListener.clicked(RangeSlider.this, e); } } + @Override + public void mouseWheelMoved(final MouseEvent e) { + final float old_val = val; + final float old_val_pct = val_pct; + float v = old_val; + if( !e.isControlDown() ) { + if( e.getRotation()[1] < 0f ) { + if( inverted ) { + v++; + } else { + v--; + } + } else { + if( inverted ) { + v--; + } else { + v++; + } + } + } else if( !Float.isNaN(pageSize) ){ + if( e.getRotation()[1] < 0f ) { + if( inverted ) { + v+=pageSize; + } else { + v-=pageSize; + } + } else { + if( inverted ) { + v-=pageSize; + } else { + v+=pageSize; + } + } + } + setValue( v ); + if( null != sliderListener ) { + sliderListener.dragged(RangeSlider.this, old_val, val, old_val_pct, val_pct); + } + } }); knob.addMouseListener(new Shape.MouseGestureAdapter() { @Override public void mouseClicked(final MouseEvent e) { - if( null != sliderListener ) { - sliderListener.clicked(RangeSlider.this, e); - } + // if( null != sliderListener ) { + // sliderListener.clicked(RangeSlider.this, e); + // } } @Override public void mousePressed(final MouseEvent e) { @@ -156,11 +259,75 @@ public final class RangeSlider extends Widget { sliderListener.released(RangeSlider.this, e); } } + }); + final KeyListener keyListener = new KeyAdapter() { @Override - public void mouseWheelMoved(final MouseEvent e) { - // Support ? + public void keyReleased(final KeyEvent e) { + final float old_val = val; + final float old_val_pct = val_pct; + float v = old_val; + final short keySym = e.getKeySymbol(); + boolean action = false; + if( horizontal ) { + if( keySym == KeyEvent.VK_RIGHT ) { + action = true; + if( inverted ) { + v--; + } else { + v++; + } + } else if( keySym == KeyEvent.VK_LEFT ) { + action = true; + if( inverted ) { + v++; + } else { + v--; + } + } + } else { + if( keySym == KeyEvent.VK_DOWN ) { + action = true; + if( inverted ) { + v++; + } else { + v--; + } + } else if( keySym == KeyEvent.VK_UP ) { + action = true; + if( inverted ) { + v--; + } else { + v++; + } + } + } + if( !action && !Float.isNaN(pageSize) ) { + if( keySym == KeyEvent.VK_PAGE_DOWN ) { + action = true; + if( inverted ) { + v+=pageSize; + } else { + v-=pageSize; + } + } else if( keySym == KeyEvent.VK_PAGE_UP ) { + action = true; + if( inverted ) { + v-=pageSize; + } else { + v+=pageSize; + } + } + } + if( action ) { + setValue( v ); + if( null != sliderListener ) { + sliderListener.dragged(RangeSlider.this, old_val, val, old_val_pct, val_pct); + } + } } - }); + }; + barAndKnob.addKeyListener(keyListener); + knob.addKeyListener(keyListener); } @Override @@ -180,44 +347,64 @@ public final class RangeSlider extends Widget { } public Rectangle getBar() { return bar; } - public BaseButton getKnob() { return knob; } + public Shape getKnob() { return knob; } - public final float getWidth() { return width; } - public final float getHeight() { return height; } - public final float getKnobSize() { return knobSz; } + public final Vec2f getSize() { return size; } + /** Height of knob orthogonal to sliding direction */ + public final float getKnobHeight() { return knobDiameter; } + /** Length of knob in sliding direction */ + public final float getKnobLength() { return knobHalfLen*2f; } - public float getMin() { return min; } - public float getMax() { return max; } - public float getRange() { return max - min; } + public Vec2f getMinMax() { return minMax; } + public float getRange() { return minMax.y() - minMax.x(); } + private float getRange(final Vec2f minMax) { return minMax.y() - minMax.x(); } public float getValue() { return val; } public float getValuePct() { return val_pct; } + public RangeSlider setPageSize(final float v) { + if( Float.isNaN(this.pageSize) || Float.isNaN(v) ) { + return this; + } + this.pageSize = v; + final float pageSizePct = Math.max(pageKnobSizePctMin, pageSize / getRange()); + final float knobWidth, knobHeight; + if( horizontal ) { + knobHeight = size.y() * pageKnobScale; + knobWidth = pageSizePct * this.size.x(); + knobHalfLen = knobWidth * 0.5f; + knobDiameter = knobHeight; + } else { + knobWidth = size.x() * pageKnobScale; + knobHeight = pageSizePct * this.size.y(); + knobHalfLen = knobHeight * 0.5f; + knobDiameter = knobWidth; + } + ((Rectangle)knob).setDimension(knobWidth, knobHeight, 0); + return this; + } + public float getPageSize() { return this.pageSize; } + + public RangeSlider setInverted(final boolean v) { inverted = v; return setValue(val); } + /** * Sets slider value range and current value - * @param min minimum value of slider - * @param max maximum value of slider + * @param minMax minimum- and maximum-value of slider * @param value current value of slider * @return this instance of chaining */ - public RangeSlider setMinMax(final float min, final float max, final float value) { - this.min = min; - this.max = max; - this.val = Math.max(min, Math.min(max, value)); - this.val_pct = ( value - min ) / getRange(); - setKnob(); - return this; + public RangeSlider setMinMax(final Vec2f minMax, final float value) { + this.minMax.set(minMax); + return setValue( value ); } public RangeSlider setValuePct(final float v) { - val_pct = v; - val = min + ( val_pct * getRange() ); - setKnob(); - return this; + final float pct = Math.max(0.0f, Math.min(1.0f, v)); + return setValue( minMax.x() + ( pct * getRange() ) ); } public RangeSlider setValue(final float v) { - val = v; - val_pct = ( val - min ) / getRange(); + val = Math.max(minMax.x(), Math.min(minMax.y(), v)); + val_pct = ( val - minMax.x() ) / getRange(); setKnob(); return this; } @@ -226,27 +413,118 @@ public final class RangeSlider extends Widget { * Knob position reflects value on its center and ranges from zero to max. */ private Vec2f getKnobPos(final Vec2f posRes, final float val_pct) { + final float v = inverted ? 1f - val_pct : val_pct; if( horizontal ) { - posRes.setX( val_pct*width - knobSz*0.5f ); - posRes.setY( -( knobSz - height ) * 0.5f ); + posRes.setX( Math.max(0, Math.min(size.x(), v*size.x() - knobHalfLen)) ); + posRes.setY( -( knobDiameter - size.y() ) * 0.5f ); } else { - posRes.setX( -( knobSz - width ) * 0.5f ); - posRes.setY( val_pct*height - knobSz*0.5f ); + posRes.setX( -( knobDiameter - size.x() ) * 0.5f ); + posRes.setY( Math.max(0, Math.min(size.y() - 2*knobHalfLen, v*size.y() - knobHalfLen)) ); } return posRes; } private float getKnobValuePct(final float pos_x, final float pos_y, final float adjustment) { final float v; if( horizontal ) { - v = ( pos_x + adjustment ) / width; + v = ( pos_x + adjustment ) / size.x(); } else { - v = ( pos_y + adjustment ) / height; + v = ( pos_y + adjustment ) / size.y(); } - return Math.max(0.0f, Math.min(1.0f, v)); + return Math.max(0.0f, Math.min(1.0f, inverted ? 1f - v : v)); } private void setKnob() { final Vec2f pos = getKnobPos(new Vec2f(), val_pct); knob.moveTo(pos.x(), pos.y(), Button.DEFAULT_LABEL_ZOFFSET); } + + /** + * Sets the slider knob color. + * <p> + * If this slider comprises a rectangular page-sized knob, + * its rectangular frame also shares the same color. + * </p> + * <p> + * Base color w/o color channel, will be modulated w/ pressed- and toggle color + * </p> + * <p> + * Default RGBA value is 0.30f, 0.30f, 0.30f, 1.0f + * </p> + */ + @Override + public final Shape setColor(final float r, final float g, final float b, final float a) { + super.setColor(r, g, b, a); + knob.setColor(r, g, b, a); + if( !Float.isNaN(pageSize) ) { + bar.setColor(r, g, b, a); + } + return this; + } + + /** + * Sets the slider knob color. + * <p> + * If this slider comprises a rectangular page-sized knob, + * its rectangular frame also shares the same color. + * </p> + * <p> + * Base color w/o color channel, will be modulated w/ pressed- and toggle color + * </p> + * <p> + * Default RGBA value is 0.30f, 0.30f, 0.30f, 1.0f + * </p> + */ + @Override + public Shape setColor(final Vec4f c) { + this.rgbaColor.set(c); + knob.setColor(c); + if( !Float.isNaN(pageSize) ) { + bar.setColor(c); + } + return this; + } + + /** + * Sets the slider background bar color, if this slider comprises only a positional round knob. + * <p> + * Default RGBA value is 0.60f, 0.60f, 0.60f, 0.5f + * </p> + */ + public Shape setBackgroundBarColor(final float r, final float g, final float b, final float a) { + if( Float.isNaN(pageSize) ) { + bar.setColor(r, g, b, a); + } + return this; + } + /** + * Sets the slider background bar color, if this slider comprises only a positional round knob. + * <p> + * Default RGBA value is 0.60f, 0.60f, 0.60f, 0.5f + * </p> + */ + public Shape setBackgroundBarColor(final Vec4f c) { + if( Float.isNaN(pageSize) ) { + bar.setColor(c); + } + return this; + } + + /** + * {@inheritDoc} + * <p> + * Sets the slider bar and knob pressed color modulation. + * </p> + */ + @Override + public final Shape setPressedColorMod(final float r, final float g, final float b, final float a) { + super.setPressedColorMod(r, g, b, a); + bar.setPressedColorMod(r, g, b, a); + knob.setPressedColorMod(r, g, b, a); + return this; + } + + @Override + public String getSubString() { + return super.getSubString()+", range["+minMax+"] @ "+val+", "+(100f*val_pct)+"%"; + } } |