28package com.jogamp.graph.ui.widgets;
30import java.util.ArrayList;
32import com.jogamp.graph.curve.Region;
33import com.jogamp.graph.curve.opengl.GLRegion;
34import com.jogamp.graph.curve.opengl.RegionRenderer;
35import com.jogamp.graph.ui.GraphShape;
36import com.jogamp.graph.ui.Group;
37import com.jogamp.graph.ui.Shape;
38import com.jogamp.graph.ui.layout.Padding;
39import com.jogamp.graph.ui.shapes.BaseButton;
40import com.jogamp.graph.ui.shapes.Button;
41import com.jogamp.graph.ui.shapes.Rectangle;
42import com.jogamp.math.FloatUtil;
43import com.jogamp.math.Vec2f;
44import com.jogamp.math.Vec3f;
45import com.jogamp.math.Vec4f;
46import com.jogamp.newt.event.KeyAdapter;
47import com.jogamp.newt.event.KeyEvent;
48import com.jogamp.newt.event.KeyListener;
49import com.jogamp.newt.event.MouseEvent;
50import com.jogamp.opengl.GL2ES2;
51import com.jogamp.opengl.GLProfile;
52import com.jogamp.opengl.util.texture.TextureSequence;
77 private static interface ChangedAction {
94 private static interface PeekAction {
98 private static final boolean DEBUG =
false;
99 private static final float pageKnobScale = 0.6f;
100 private static final float pageBarLineScale = 0.25f;
101 private static final float pageKnobSizePctMin = 5f/100f;
102 private final boolean horizontal;
104 private float knobThickn;
106 private float knobLength;
107 private final Vec2f size;
108 private final Group barAndKnob, marks;
111 private ArrayList<ChangeListener> changeListeners =
new ArrayList<ChangeListener>();
112 private ArrayList<PeekListener> peekListeners =
new ArrayList<PeekListener>();
113 private final Vec2f minMax =
new Vec2f(0, 100);
114 private final float knobScale;
115 private float pageSize;
116 private float val=0, val_pct=0;
117 private boolean inverted=
false;
118 private float unitSize = 1;
119 private final Vec4f activeColMod =
new Vec4f(0.1f, 0.1f, 0.1f, 1f);
138 final Vec2f minMax,
final float unitSize,
final float value) {
139 this(renderModes, size, knobScale, minMax, unitSize, Float.NaN, value);
158 final Vec2f minMax,
final float unitSize,
final float pageSize,
final float value) {
159 this(renderModes, size, 0, minMax, unitSize, pageSize, value);
161 private RangeSlider(
final int renderModes_,
final Vec2f size,
final float knobScale,
162 final Vec2f minMax,
final float unitSize,
final float pageSz,
final float value) {
164 this.knobScale = knobScale;
165 this.unitSize = unitSize;
166 this.pageSize = pageSz;
167 this.horizontal = size.x() >= size.y();
168 barAndKnob =
new Group();
173 this.size =
new Vec2f(size);
174 if( DEBUG ) { System.err.println(
"RangeSlider.ctor0 "+
getDescription()); }
175 setMinMaxImpl(minMax.x(), minMax.y());
176 setKnobSize(pageSize,
false,
false);
177 if( DEBUG ) { System.err.println(
"RangeSlider.ctor1 "+
getDescription()); }
178 if( Float.isFinite(pageSize) ) {
179 final float barLineWidth;
181 barLineWidth = ( size.y() - knobThickn ) * pageBarLineScale;
182 knob =
new Rectangle(renderModes, knobLength, knobThickn, 0);
184 barLineWidth = ( size.x() - knobThickn ) * pageBarLineScale;
185 knob =
new Rectangle(renderModes, knobThickn, knobLength, 0);
187 bar =
new Rectangle(renderModes, this.size.x(),
this.size.y(), barLineWidth);
189 bar =
new Rectangle(renderModes, this.size.x(),
this.size.y(), 0);
190 knob =
new BaseButton(renderModes , knobThickn*1.01f, knobThickn);
193 if( DEBUG ) { System.err.println(
"RangeSlider.ctor3 "+
getDescription()); }
194 setColor(0.80f, 0.80f, 0.80f, 0.7f);
196 setName(
"RangeSlider.container");
204 reconfig(minMax,
true, value,
false, 0);
206 knob.
onMove((
final Shape s,
final Vec3f origin,
final Vec3f dest,
final MouseEvent e) -> {
207 final float old_val = val;
208 final float old_val_pct = val_pct;
209 if( Float.isFinite(pageSize) ) {
210 final float dy = inverted ? +knobLength: 0;
211 setValue(dest.x(), dest.y(), dy);
213 setValue(dest.x(), dest.y(), knobLength/2f);
215 dispatchToListener( (
final ChangeListener l) -> {
216 l.dragged(
RangeSlider.this, old_val, val, old_val_pct, val_pct, dest, e);
219 bar.
onClicked((
final Shape s,
final Vec3f pos,
final MouseEvent e) -> {
220 final float old_val = val;
221 final float old_val_pct = val_pct;
222 setValue(pos.x(), pos.y(), 0);
223 dispatchToListener( (
final ChangeListener l) -> {
224 l.dragged(
RangeSlider.this, old_val, val, old_val_pct, val_pct, pos, e);
227 bar.
onHover((
final Shape s,
final Vec3f pos,
final MouseEvent e) -> {
228 final float pval_pct = getKnobValuePct( pos.x(), pos.y(), 0 );
229 final float pval = valuePctToValue( pval_pct );
230 dispatchToListener( (
final PeekListener l) -> {
231 l.peeked(
this, pval, pval_pct, pos, e);
237 final Shape.MouseGestureListener mouseListener =
new Shape.MouseGestureAdapter() {
239 public void mouseWheelMoved(
final MouseEvent e) {
240 final float old_val = val;
241 final float old_val_pct = val_pct;
243 if( !e.isControlDown() ) {
244 if( e.getRotation()[1] < 0f ) {
257 }
else if( Float.isFinite(pageSize) ){
258 if( e.getRotation()[1] < 0f ) {
273 dispatchToListener( (
final ChangeListener l) -> {
278 final KeyListener keyListener =
new KeyAdapter() {
280 public void keyReleased(
final KeyEvent e) {
281 final float old_val = val;
282 final float old_val_pct = val_pct;
284 final short keySym = e.getKeySymbol();
285 boolean action =
false;
287 if( keySym == KeyEvent.VK_RIGHT ) {
294 }
else if( keySym == KeyEvent.VK_LEFT ) {
303 if( keySym == KeyEvent.VK_DOWN ) {
310 }
else if( keySym == KeyEvent.VK_UP ) {
319 if( !action && Float.isFinite(pageSize) ) {
320 if( keySym == KeyEvent.VK_PAGE_DOWN ) {
327 }
else if( keySym == KeyEvent.VK_PAGE_UP ) {
338 dispatchToListener( (
final ChangeListener l) -> {
349 final Shape.Listener onActivation =
new Shape.Listener() {
350 private final Vec4f origCol =
new Vec4f();
351 private boolean oriColSet =
false;
352 private final Vec4f tmp =
new Vec4f();
354 public void run(
final Shape s) {
360 knob.
setColor( tmp.mul(origCol, activeColMod) );
380 public void mouseClicked(final MouseEvent e) { }
386 super.clearImpl0(gl, renderer);
387 changeListeners.clear();
388 peekListeners.clear();
392 super.destroyImpl0(gl, renderer);
393 changeListeners.clear();
394 peekListeners.clear();
401 @SuppressWarnings(
"unchecked")
402 final ArrayList<ChangeListener> clonedListeners = (ArrayList<ChangeListener>) changeListeners.clone();
403 clonedListeners.add(l);
404 changeListeners = clonedListeners;
411 @SuppressWarnings(
"unchecked")
412 final ArrayList<ChangeListener> clonedListeners = (ArrayList<ChangeListener>) changeListeners.clone();
413 clonedListeners.remove(l);
414 changeListeners = clonedListeners;
417 private final void dispatchToListener(
final ChangedAction action) {
418 final int sz = changeListeners.size();
419 for(
int i = 0; i < sz; i++ ) {
420 action.run( changeListeners.get(i) );
428 @SuppressWarnings(
"unchecked")
429 final ArrayList<PeekListener> clonedListeners = (ArrayList<PeekListener>) peekListeners.clone();
430 clonedListeners.add(l);
431 peekListeners = clonedListeners;
438 @SuppressWarnings(
"unchecked")
439 final ArrayList<PeekListener> clonedListeners = (ArrayList<PeekListener>) peekListeners.clone();
440 clonedListeners.remove(l);
441 peekListeners = clonedListeners;
444 private final void dispatchToListener(
final PeekAction action) {
445 final int sz = peekListeners.size();
446 for(
int i = 0; i < sz; i++ ) {
447 action.run( peekListeners.get(i) );
456 final float sizex, sizey, itemLen, itemHeight;
469 final Vec2f pos = getItemValuePos(
new Vec2f(), value, itemLen, itemHeight);
472 mark.
moveTo(pos.x(), pos.y(), 0);
487 public float getRange() {
return minMax.y() - minMax.x(); }
488 private static float getRange(
final Vec2f minMax) {
return minMax.y() - minMax.x(); }
503 return setKnobSize(pageSz,
true,
true);
505 private RangeSlider setKnobSize(
final float pageSz,
final boolean adjKnob,
final boolean adjValue) {
506 if( Float.isFinite(pageSize) && Float.isFinite(pageSz) ) {
507 final float range = getRange(minMax);
509 pageSize = Math.min(minMax.y(), Math.max(minMax.x(), pageSz));
511 final float pageSizePct = getPageSizePct(pageKnobSizePctMin);
512 final float width, height;
514 width = pageSizePct * this.size.x();
515 height = size.y() * pageKnobScale;
519 setPaddding(
new Padding(size.y()/2f, 0, size.y()/2f, 0));
523 width = size.x() * pageKnobScale;
524 height = pageSizePct * this.size.y();
528 setPaddding(
new Padding(0, size.x()/2f, 0, size.x()/2f));
533 ((Rectangle)knob).setDimension(width, height, 0);
538 }
else if( Float.isFinite(pageSize) ) {
542 knobThickn = size.y()*knobScale;
544 setPaddding(
new Padding(knobThickn/2f, 0, knobThickn/2f, 0));
548 knobThickn = size.x()*knobScale;
550 setPaddding(
new Padding(0, knobThickn/2f, 0, knobThickn/2f));
554 knobLength = knobThickn;
558 private boolean paddingSet =
false;
560 private void setMinMaxImpl(
final float min,
final float max) {
561 this.minMax.
set(Float.isFinite(min) ? min : 0, Float.isFinite(max) ? max : 0);
563 private RangeSlider reconfig(
final Vec2f minMax,
564 final boolean modValue,
final float value,
565 final boolean modKnobSz,
final float pageSz)
567 if(
null != minMax ) {
568 setMinMaxImpl(minMax.x(), minMax.y());
571 setKnobSize(pageSz,
true, !modValue);
576 if( DEBUG ) { System.err.println(
"RangeSlider.cfg "+getDescription()); }
596 if( Float.isFinite(pageSize) ) {
597 final float range = getRange(minMax);
598 return Float.isFinite(range) && !
FloatUtil.
isZero(range) ? Math.max(minPct, pageSize / range) : minPct;
625 return reconfig(minMax,
true, value,
true, pageSize);
634 return reconfig(minMax,
false, 0,
true, pageSize);
645 return reconfig(minMax,
true, value,
true, pageSz);
655 return reconfig(minMax,
false, 0,
true, pageSz);
658 private RangeSlider setValue(
final float pos_x,
final float pos_y,
final float adjustment) {
659 return setValue( valuePctToValue( getKnobValuePct(pos_x, pos_y, adjustment) ) );
669 final float v1 = Float.isFinite(v) ? v : 0f;
670 final float pgsz = Float.isFinite(pageSize) ? pageSize : 0f;
671 final float range = getRange();
672 val = Math.max(minMax.x(), Math.min(minMax.y() - pgsz, v1));
674 val_pct = ( val - minMax.x() ) / range;
689 private Vec2f getItemValuePos(
final Vec2f posRes,
final float value,
final float itemLen,
final float itemHeight) {
690 return getItemPctPos(posRes, ( value - minMax.x() ) / getRange(), itemLen, itemHeight);
699 private Vec2f getItemPctPos(
final Vec2f posRes,
final float val_pct,
final float itemLen,
final float itemThickn) {
700 final float v = inverted ? 1f - val_pct : val_pct;
701 final float itemAdjust;
702 if( Float.isFinite(pageSize) ) {
704 itemAdjust = itemLen;
709 itemAdjust = itemLen * 0.5f;
712 posRes.
setX( Math.max(0, Math.min(size.x() - itemLen, v*size.x() - itemAdjust)) );
713 posRes.
setY( -( itemThickn - size.y() ) * 0.5f );
715 posRes.
setX( -( itemThickn - size.x() ) * 0.5f );
716 posRes.
setY( Math.max(0, Math.min(size.y() - itemLen, v*size.y() - itemAdjust)) );
720 private float getKnobValuePct(
final float pos_x,
final float pos_y,
final float adjustment) {
723 v = ( pos_x + adjustment ) / size.x();
725 v = ( pos_y + adjustment ) / size.y();
727 return Math.max(0.0f, Math.min(1.0f, inverted ? 1f - v : v));
729 private float valuePctToValue(
final float v) {
730 final float range = getRange();
731 if( Float.isFinite(v) && Float.isFinite(range) && !FloatUtil.isZero(range) ) {
732 final float pgsz_pct = Float.isFinite(pageSize) ? pageSize / range : 0f;
733 final float pct = Math.max(0f, Math.min(1f - pgsz_pct, v));
734 return minMax.x() + ( pct * range );
740 private void setKnob() {
741 final Vec2f pos = getItemPctPos(
new Vec2f(), val_pct, knobLength, knobThickn);
742 knob.moveTo(pos.x(), pos.y(), Button.DEFAULT_LABEL_ZOFFSET);
759 public final Shape setColor(
final float r,
final float g,
final float b,
final float a) {
762 if( Float.isFinite(pageSize) ) {
783 this.rgbaColor.set(c);
785 if( Float.isFinite(pageSize) ) {
786 bar.
setColor(c.x(), c.y(), c.z(), 1.0f);
798 if( !Float.isFinite(pageSize) ) {
811 if( !Float.isFinite(pageSize) ) {
823 if( !Float.isFinite(pageSize) ) {
845 final String pre =
"value "+val+
" "+(100f*val_pct)+
"%, range "+minMax;
846 final String post =
", ssize "+size+
", knob[l "+knobLength+
", t "+knobThickn+
"]";
847 if( Float.isFinite(pageSize) ) {
848 final float pageSizePct = getPageSizePct(pageKnobSizePctMin);
849 final String detail =
", pageSize "+pageSize+
" "+(pageSizePct*100f)+
"% -> "+knobLength;
851 return "H "+pre+detail+
"/"+size.x()+post;
853 return "V "+pre+detail+
"/"+size.y()+post;
857 return "H "+pre+post;
859 return "V "+pre+post;
865 return super.getSubString()+
", "+getDescription()+
" @ "+val+
", "+(100f*val_pct)+
"%";
869 if( isShapeDirty() ) {
870 super.validateImpl(gl, glp);
871 setKnobSize(pageSize,
true,
true);
872 if( DEBUG ) { System.err.println(
"RangeSlider.val "+getDescription()); }
Abstract Outline shape representation define the method an OutlineShape(s) is bound and rendered.
static final int COLORCHANNEL_RENDERING_BIT
Rendering-Mode bit for Region to optionally enable a color-channel per vertex.
Graph based GLRegion Shape.
final int getRenderModes()
Returns validated Graph Region render modes, see create(..).
Group of Shapes, optionally utilizing a Group.Layout.
Group()
Create a group of Shapes w/o Group.Layout.
void addShape(final Shape s)
Adds a Shape.
Forward KeyListener, to be attached to a key event source forwarded to the receiver set at constructo...
Forward MouseGestureListener, to be attached to a mouse event source forwarded to the receiver set at...
Generic Shape, potentially using a Graph via GraphShape or other means of representing content.
Shape setColor(final float r, final float g, final float b, final float a)
Set base color.
Shape setName(final String name)
Set a symbolic name for this shape for identification.
final Shape setDraggable(final boolean draggable)
Set whether this shape is draggable, i.e.
final Shape addKeyListener(final KeyListener l)
final void onHover(final PointerListener l)
Set user callback to be notified when a pointer/mouse is moving over this shape.
Shape()
Create a generic UI Shape.
final Shape setInteractive(final boolean v)
Set whether this shape is interactive in general, i.e.
final Shape addActivationListener(final Listener l)
Add user callback to be notified when shape is activated (pointer-over and/or click) or de-activated ...
final boolean isActive()
Returns true of this shape is active.
final void onClicked(final PointerListener l)
Set user callback to be notified when shape is clicked.
final void clear(final GL2ES2 gl, final RegionRenderer renderer)
Clears all data and reset all states as if this instance was newly created.
final Shape setResizable(final boolean resizable)
Set whether this shape is resizable, i.e.
final Shape moveTo(final float tx, final float ty, final float tz)
Move to scaled position.
final Vec3f getPosition()
Returns position Vec3f reference, i.e.
Shape setPressedColorMod(final float r, final float g, final float b, final float a)
Set pressed color, modulating getColor() if isPressed().
final Shape setDragAndResizable(final boolean v)
Set whether this shape is draggable and resizable.
final Vec4f getColor()
Returns base-color w/o color channel, will be modulated w/ getPressedColorMod(), getToggleOnColorMod(...
final void dispatchActivationEvent(final Shape s)
Dispatch activation event event to this shape.
final Shape addMouseListener(final MouseGestureListener l)
final void onMove(final MoveListener l)
Set user callback to be notified when shape is move(Vec3f)'ed.
final Shape setToggleable(final boolean toggleable)
Set this shape toggleable, default is off.
GraphUI CSS property Padding, unscaled space belonging to the element and included in the element's s...
A GraphUI rectangle GraphShape.
Basic Float math utility functions.
static boolean isZero(final float a, final float epsilon)
Returns true if value is zero, i.e.
2D Vector based upon two float components.
void set(final Vec2f o)
this = o, returns this.
3D Vector based upon three float components.
Vec3f minus(final Vec3f arg)
Returns this - arg; creates new vector.
4D Vector based upon four float components.
Vec4f set(final Vec4f o)
this = o, returns this.
Pointer event of type PointerType.
Specifies the the OpenGL profile.