JOGL v2.6.0-rc-20250712
JOGL, High-Performance Graphics Binding for Java™ (public API).
RangeSlider.java
Go to the documentation of this file.
1/**
2 * Copyright 2010-2024 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 */
28package com.jogamp.graph.ui.widgets;
29
30import java.util.ArrayList;
31
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;
53
54/**
55 * RangeSlider {@link Widget} either utilizing a simple positional round knob
56 * or a rectangular page-sized knob.
57 * @see #RangeSlider(int, Vec2f, float, Vec2f, float, float)
58 * @see #RangeSlider(int, Vec2f, Vec2f, float, float, float)
59 */
60public final class RangeSlider extends Widget {
61 /**
62 * {@link RangeSlider} slider value changed listener
63 */
64 public static interface ChangeListener {
65 /**
66 * Slide dragged by user (including clicked position)
67 * @param w the {@link RangeSlider} widget owning the slider
68 * @param old_val previous absolute value position of the slider
69 * @param val the absolute value position of the slider
70 * @param old_val_pct previous percentage value position of the slider
71 * @param val_pct the percentage value position of the slider
72 * @param pos object position relative to the slider's bar
73 * @param e NEWT original event or {@code null} if sourced from non-mouse, e.g. key-event
74 */
75 void dragged(RangeSlider w, float old_val, float val, float old_val_pct, float val_pct, Vec3f pos, MouseEvent e);
76 }
77 private static interface ChangedAction {
78 public void run(ChangeListener l);
79 }
80 /**
81 * {@link RangeSlider} slider value peek listener
82 */
83 public static interface PeekListener {
84 /**
85 * Slide position/value peeked by user (mouse over/hover)
86 * @param w the {@link RangeSlider} widget owning the slider
87 * @param val the absolute value peeked at the slider
88 * @param val_pct the percentage value position peeked at the slider
89 * @param pos object position relative to the slider's bar
90 * @param e NEWT original event
91 */
92 void peeked(RangeSlider w, float val, float val_pct, Vec3f pos, MouseEvent e);
93 }
94 private static interface PeekAction {
95 public void run(PeekListener l);
96 }
97
98 private static final boolean DEBUG = false;
99 private static final float pageKnobScale = 0.6f; // 0.6 * barWidth
100 private static final float pageBarLineScale = 0.25f; // 1/4 * ( barWidth - pageKnobWidth )
101 private static final float pageKnobSizePctMin = 5f/100f;
102 private final boolean horizontal;
103 /** Knob thickness orthogonal to sliding direction */
104 private float knobThickn;
105 /** Knob length in sliding direction */
106 private float knobLength;
107 private final Vec2f size;
108 private final Group barAndKnob, marks;
109 private final Rectangle bar;
110 private final GraphShape knob;
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);
120
121 /**
122 * Constructs a {@link RangeSlider}, i.e. its shapes and controls.
123 * <p>
124 * This slider comprises a background bar and a positional round knob,
125 * with {@link #getValue()} at center position.
126 * </p>
127 * <p>
128 * The spatial {@code size} gets automatically updated at {@link #validate(GL2ES2)}
129 * </p>
130 * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}.
131 * @param size spatial dimension of this slider box. A horizontal slider has width >= height.
132 * @param knobScale multiple of slider-bar height for {@link #getKnobThickness()}
133 * @param minMax minimum- and maximum-value of slider
134 * @param unitSize size of one unit (element) in sliding direction
135 * @param value current value of slider
136 */
137 public RangeSlider(final int renderModes, final Vec2f size, final float knobScale,
138 final Vec2f minMax, final float unitSize, final float value) {
139 this(renderModes, size, knobScale, minMax, unitSize, Float.NaN, value);
140 }
141 /**
142 * Constructs a {@link RangeSlider}, i.e. its shapes and controls.
143 * <p>
144 * This slider comprises a framing bar and a rectangular page-sized knob,
145 * with {@link #getValue()} at page-start position.
146 * </p>
147 * <p>
148 * The spatial {@code size} and {@code pageSize} gets automatically updated at {@link #validate(GL2ES2)}
149 * </p>
150 * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}.
151 * @param size spatial dimension of this slider box. A horizontal slider has width >= height.
152 * @param minMax minimum- and maximum-value of slider
153 * @param unitSize size of one unit (element) in sliding direction
154 * @param pageSize size of one virtual-page, triggers rendering mode from knob to rectangle
155 * @param value current value of slider
156 */
157 public RangeSlider(final int renderModes, final Vec2f size,
158 final Vec2f minMax, final float unitSize, final float pageSize, final float value) {
159 this(renderModes, size, 0, minMax, unitSize, pageSize, value);
160 }
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) {
163 final int renderModes = renderModes_ & ~(Region.COLORCHANNEL_RENDERING_BIT);
164 this.knobScale = knobScale;
165 this.unitSize = unitSize;
166 this.pageSize = pageSz;
167 this.horizontal = size.x() >= size.y();
168 barAndKnob = new Group();
169 barAndKnob.setInteractive(false);
170 marks = new Group();
171 marks.setInteractive(false);
172
173 this.size = new Vec2f(size);
174 if( DEBUG ) { System.err.println("RangeSlider.ctor0 "+getDescription()); }
175 setMinMaxImpl(minMax.x(), minMax.y()); // pre-set for setKnobSize()
176 setKnobSize(pageSize, false, false);
177 if( DEBUG ) { System.err.println("RangeSlider.ctor1 "+getDescription()); }
178 if( Float.isFinite(pageSize) ) {
179 final float barLineWidth;
180 if( horizontal ) {
181 barLineWidth = ( size.y() - knobThickn ) * pageBarLineScale;
182 knob = new Rectangle(renderModes, knobLength, knobThickn, 0);
183 } else {
184 barLineWidth = ( size.x() - knobThickn ) * pageBarLineScale;
185 knob = new Rectangle(renderModes, knobThickn, knobLength, 0);
186 }
187 bar = new Rectangle(renderModes, this.size.x(), this.size.y(), barLineWidth);
188 } else {
189 bar = new Rectangle(renderModes, this.size.x(), this.size.y(), 0);
190 knob = new BaseButton(renderModes , knobThickn*1.01f, knobThickn);
191 setBackgroundBarColor(0.60f, 0.60f, 0.60f, 0.5f);
192 }
193 if( DEBUG ) { System.err.println("RangeSlider.ctor3 "+getDescription()); }
194 setColor(0.80f, 0.80f, 0.80f, 0.7f);
195
196 setName("RangeSlider.container");
197 bar.setToggleable(false).setInteractive(true).setDragAndResizable(false).setName("RangeSlider.bar");
198 knob.setToggleable(false).setInteractive(true).setResizable(false).setName("RangeSlider.knob");
199 barAndKnob.addShape( bar );
200 barAndKnob.addShape( marks );
201 barAndKnob.addShape( knob );
202 addShape(barAndKnob);
203
204 reconfig(minMax, true, value, false, 0);
205
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; // offset to knob start
211 setValue(dest.x(), dest.y(), dy);
212 } else {
213 setValue(dest.x(), dest.y(), knobLength/2f); // centered
214 }
215 dispatchToListener( (final ChangeListener l) -> {
216 l.dragged(RangeSlider.this, old_val, val, old_val_pct, val_pct, dest, e);
217 });
218 });
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);
225 });
226 });
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);
232 });
233 });
234 bar.addActivationListener((final Shape s) -> {
236 });
237 final Shape.MouseGestureListener mouseListener = new Shape.MouseGestureAdapter() {
238 @Override
239 public void mouseWheelMoved(final MouseEvent e) {
240 final float old_val = val;
241 final float old_val_pct = val_pct;
242 float v = old_val;
243 if( !e.isControlDown() ) {
244 if( e.getRotation()[1] < 0f ) {
245 if( inverted ) {
246 v+=unitSize;
247 } else {
248 v-=unitSize;
249 }
250 } else {
251 if( inverted ) {
252 v-=unitSize;
253 } else {
254 v+=unitSize;
255 }
256 }
257 } else if( Float.isFinite(pageSize) ){
258 if( e.getRotation()[1] < 0f ) {
259 if( inverted ) {
260 v+=pageSize;
261 } else {
262 v-=pageSize;
263 }
264 } else {
265 if( inverted ) {
266 v-=pageSize;
267 } else {
268 v+=pageSize;
269 }
270 }
271 }
272 setValue( v );
273 dispatchToListener( (final ChangeListener l) -> {
274 l.dragged(RangeSlider.this, old_val, val, old_val_pct, val_pct, knob.getPosition().minus(bar.getPosition()), e);
275 });
276 }
277 };
278 final KeyListener keyListener = new KeyAdapter() {
279 @Override
280 public void keyReleased(final KeyEvent e) {
281 final float old_val = val;
282 final float old_val_pct = val_pct;
283 float v = old_val;
284 final short keySym = e.getKeySymbol();
285 boolean action = false;
286 if( horizontal ) {
287 if( keySym == KeyEvent.VK_RIGHT ) {
288 action = true;
289 if( inverted ) {
290 v-=unitSize;
291 } else {
292 v+=unitSize;
293 }
294 } else if( keySym == KeyEvent.VK_LEFT ) {
295 action = true;
296 if( inverted ) {
297 v+=unitSize;
298 } else {
299 v-=unitSize;
300 }
301 }
302 } else {
303 if( keySym == KeyEvent.VK_DOWN ) {
304 action = true;
305 if( inverted ) {
306 v+=unitSize;
307 } else {
308 v-=unitSize;
309 }
310 } else if( keySym == KeyEvent.VK_UP ) {
311 action = true;
312 if( inverted ) {
313 v-=unitSize;
314 } else {
315 v+=unitSize;
316 }
317 }
318 }
319 if( !action && Float.isFinite(pageSize) ) {
320 if( keySym == KeyEvent.VK_PAGE_DOWN ) {
321 action = true;
322 if( inverted ) {
323 v+=pageSize;
324 } else {
325 v-=pageSize;
326 }
327 } else if( keySym == KeyEvent.VK_PAGE_UP ) {
328 action = true;
329 if( inverted ) {
330 v-=pageSize;
331 } else {
332 v+=pageSize;
333 }
334 }
335 }
336 if( action ) {
337 setValue( v );
338 dispatchToListener( (final ChangeListener l) -> {
339 l.dragged(RangeSlider.this, old_val, val, old_val_pct, val_pct, knob.getPosition().minus(bar.getPosition()), null);
340 });
341 }
342 }
343 };
344 bar.addKeyListener(keyListener);
345 knob.addKeyListener(keyListener);
346 bar.addMouseListener(mouseListener);
347 knob.addMouseListener(mouseListener);
348
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();
353 @Override
354 public void run(final Shape s) {
355 if( bar.isActive() || knob.isActive() ) {
356 if( !oriColSet ) {
357 origCol.set( knob.getColor() );
358 oriColSet = true;
359 }
360 knob.setColor( tmp.mul(origCol, activeColMod) );
361 } else {
362 oriColSet = false;
363 knob.setColor( origCol );
364 }
365 }
366 };
367 bar.addActivationListener(onActivation);
368 knob.addActivationListener(onActivation);
369 }
370
371 @Override
372 public void receiveKeyEvents(final Shape source) {
373 source.addKeyListener(new Shape.ForwardKeyListener(barAndKnob));
374 source.addKeyListener(new Shape.ForwardKeyListener(knob));
375 }
376 @Override
377 public void receiveMouseEvents(final Shape source) {
378 source.addMouseListener(new Shape.ForwardMouseListener(barAndKnob) {
379 @Override
380 public void mouseClicked(final MouseEvent e) { /* nop */ }
381 });
382 }
383
384 @Override
385 protected void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer) {
386 super.clearImpl0(gl, renderer);
387 changeListeners.clear();
388 peekListeners.clear();
389 }
390 @Override
391 protected void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer) {
392 super.destroyImpl0(gl, renderer);
393 changeListeners.clear();
394 peekListeners.clear();
395 }
396
398 if(l == null) {
399 return this;
400 }
401 @SuppressWarnings("unchecked")
402 final ArrayList<ChangeListener> clonedListeners = (ArrayList<ChangeListener>) changeListeners.clone();
403 clonedListeners.add(l);
404 changeListeners = clonedListeners;
405 return this;
406 }
408 if (l == null) {
409 return this;
410 }
411 @SuppressWarnings("unchecked")
412 final ArrayList<ChangeListener> clonedListeners = (ArrayList<ChangeListener>) changeListeners.clone();
413 clonedListeners.remove(l);
414 changeListeners = clonedListeners;
415 return this;
416 }
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) );
421 }
422 }
423
425 if(l == null) {
426 return this;
427 }
428 @SuppressWarnings("unchecked")
429 final ArrayList<PeekListener> clonedListeners = (ArrayList<PeekListener>) peekListeners.clone();
430 clonedListeners.add(l);
431 peekListeners = clonedListeners;
432 return this;
433 }
435 if (l == null) {
436 return this;
437 }
438 @SuppressWarnings("unchecked")
439 final ArrayList<PeekListener> clonedListeners = (ArrayList<PeekListener>) peekListeners.clone();
440 clonedListeners.remove(l);
441 peekListeners = clonedListeners;
442 return this;
443 }
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) );
448 }
449 }
450
451 public Rectangle getBar() { return bar; }
452 public GraphShape getKnob() { return knob; }
453 public Group getMarks() { return marks; }
454 public RangeSlider clearMarks(final GL2ES2 gl, final RegionRenderer renderer) { marks.clear(gl, renderer); return this; }
455 public Shape addMark(final float value, final Vec4f color) {
456 final float sizex, sizey, itemLen, itemHeight;
457 if( horizontal ) {
458 sizey = size.y();
459 sizex = 2*sizey;
460 itemLen = sizex;
461 itemHeight = sizey;
462 } else {
463 sizex = size.x();
464 sizey = 2*sizex;
465 itemLen = sizey;
466 itemHeight = sizex;
467 }
468 final GraphShape mark = new Rectangle(knob.getRenderModes(), sizex, sizey, 0);
469 final Vec2f pos = getItemValuePos(new Vec2f(), value, itemLen, itemHeight);
470 mark.setInteractive(true).setToggleable(false).setDraggable(false).setResizable(false);
471 mark.setColor(color);
472 mark.moveTo(pos.x(), pos.y(), 0);
473 marks.addShape(mark);
474 return mark;
475 }
476
477 /** Returns spatial dimension of this slider */
478 public final Vec2f getSize() { return size; }
479 /** Returns spatial knob thickness orthogonal to sliding direction */
480 public final float getKnobThickness() { return knobThickn; }
481 /** Returns spatial knob length in sliding direction */
482 public final float getKnobLength() { return knobLength; }
483
484 /** Returns slider value range, see {@link #setMinMax(Vec2f, float)} */
485 public Vec2f getMinMax() { return minMax; }
486 /** Returns {@link #getMinMax()} range. */
487 public float getRange() { return minMax.y() - minMax.x(); }
488 private static float getRange(final Vec2f minMax) { return minMax.y() - minMax.x(); }
489 /** Returns current slider value */
490 public float getValue() { return val; }
491 /** Returns current slider {@link #getValue() value} in percentage of {@link #getRange()}, */
492 public float getValuePct() { return val_pct; }
493
494 /**
495 * Sets the page-size if a rectangular knob is being used, i.e. {@link #RangeSlider(int, Vec2f, Vec2f, float, float, float)},
496 * otherwise does nothing.
497 * @param pageSz the page-size, which will be clipped to {@link #getMinMax()}.
498 * @return this instance of chaining
499 * @see #getPageSize()
500 * @see #RangeSlider(int, Vec2f, Vec2f, float, float, float)
501 */
502 public RangeSlider setPageSize(final float pageSz) {
503 return setKnobSize(pageSz, true, true);
504 }
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);
508 if( Float.isFinite(range) && !FloatUtil.isZero(range) ) {
509 pageSize = Math.min(minMax.y(), Math.max(minMax.x(), pageSz));
510 }
511 final float pageSizePct = getPageSizePct(pageKnobSizePctMin);
512 final float width, height;
513 if( horizontal ) {
514 width = pageSizePct * this.size.x();
515 height = size.y() * pageKnobScale;
516 knobLength = width;
517 knobThickn = height;
518 if( !paddingSet ) {
519 setPaddding(new Padding(size.y()/2f, 0, size.y()/2f, 0));
520 paddingSet = true;
521 }
522 } else {
523 width = size.x() * pageKnobScale;
524 height = pageSizePct * this.size.y();
525 knobLength = height;
526 knobThickn = width;
527 if( !paddingSet ) {
528 setPaddding(new Padding(0, size.x()/2f, 0, size.x()/2f));
529 paddingSet = true;
530 }
531 }
532 if( adjKnob ) {
533 ((Rectangle)knob).setDimension(width, height, 0);
534 }
535 if( adjValue ) {
536 setValue( val );
537 }
538 } else if( Float.isFinite(pageSize) ) {
539 // nop w/ invalid pageSz but valid pageSize
540 } else {
541 if( horizontal ) {
542 knobThickn = size.y()*knobScale;
543 if( !paddingSet ) {
544 setPaddding(new Padding(knobThickn/2f, 0, knobThickn/2f, 0));
545 paddingSet = true;
546 }
547 } else {
548 knobThickn = size.x()*knobScale;
549 if( !paddingSet ) {
550 setPaddding(new Padding(0, knobThickn/2f, 0, knobThickn/2f));
551 paddingSet = true;
552 }
553 }
554 knobLength = knobThickn;
555 }
556 return this;
557 }
558 private boolean paddingSet = false;
559
560 private void setMinMaxImpl(final float min, final float max) {
561 this.minMax.set(Float.isFinite(min) ? min : 0, Float.isFinite(max) ? max : 0);
562 }
563 private RangeSlider reconfig(final Vec2f minMax,
564 final boolean modValue, final float value,
565 final boolean modKnobSz, final float pageSz)
566 {
567 if( null != minMax ) {
568 setMinMaxImpl(minMax.x(), minMax.y());
569 }
570 if( modKnobSz ) {
571 setKnobSize(pageSz, true, !modValue);
572 }
573 if( modValue ) {
574 setValue( value );
575 }
576 if( DEBUG ) { System.err.println("RangeSlider.cfg "+getDescription()); }
577 return this;
578 }
579
580 /**
581 * Returns the page-size if a rectangular knob is being used, i.e. {@link #RangeSlider(int, Vec2f, Vec2f, float, float, float)},
582 * otherwise returns {@link Float#NaN}.
583 * @see #setPageSize(float)
584 * @see #RangeSlider(int, Vec2f, Vec2f, float, float, float)
585 */
586 public float getPageSize() { return pageSize; }
587
588 /**
589 * Returns the page-size percentage if a rectangular knob is being used, i.e. {@link #RangeSlider(int, Vec2f, Vec2f, float, float, float)},
590 * otherwise returns {@link Float#NaN}.
591 * @param minPct minimum percentage to be returned, should be >= 0
592 * @see #setPageSize(float)
593 * @see #RangeSlider(int, Vec2f, Vec2f, float, float, float)
594 */
595 public float getPageSizePct(final float minPct) {
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;
599 } else {
600 return Float.NaN;
601 }
602 }
603
604 /** Sets the size of one unit (element) in sliding direction */
605 public RangeSlider setUnitSize(final float v) { unitSize = v; return this; }
606 /** Returns the size of one unit (element) in sliding direction */
607 public float getUnitSize() { return unitSize; }
608
609 /**
610 * Sets whether this slider uses an inverted value range,
611 * e.g. top 0% and bottom 100% for an vertical inverted slider
612 * instead of bottom 0% and top 100% for a vertical non-inverted slider.
613 */
614 public RangeSlider setInverted(final boolean v) { inverted = v; return setValue(val); }
615 /** See {@link #setInverted(boolean)}. */
616 public boolean isInverted() { return inverted; }
617
618 /**
619 * Sets slider value range and current value, also updates related pageSize parameter if used.
620 * @param minMax minimum- and maximum-value of slider
621 * @param value new value of slider, clipped against {@link #getMinMax()}
622 * @return this instance of chaining
623 */
624 public RangeSlider setMinMax(final Vec2f minMax, final float value) {
625 return reconfig(minMax, true, value, true, pageSize);
626 }
627
628 /**
629 * Sets slider value range, also updates related pageSize parameter if used.
630 * @param minMax minimum- and maximum-value of slider
631 * @return this instance of chaining
632 */
633 public RangeSlider setMinMax(final Vec2f minMax) {
634 return reconfig(minMax, false, 0, true, pageSize);
635 }
636
637 /**
638 * Calls {@link #setMinMax(Vec2f, float)} and {@link #setPageSize(float)}.
639 * @param minMax minimum- and maximum-value of slider
640 * @param value new value of slider, clipped against {@code minMax}
641 * @param pageSz the page-size, which will be clipped to {@code minMax}
642 * @return this instance of chaining
643 */
644 public RangeSlider setMinMaxPgSz(final Vec2f minMax, final float value, final float pageSz) {
645 return reconfig(minMax, true, value, true, pageSz);
646 }
647
648 /**
649 * Calls {@link #setMinMax(Vec2f, float)} and {@link #setPageSize(float)}.
650 * @param minMax minimum- and maximum-value of slider
651 * @param pageSz the page-size, which will be clipped to {@code minMax}
652 * @return this instance of chaining
653 */
654 public RangeSlider setMinMaxPgSz(final Vec2f minMax, final float pageSz) {
655 return reconfig(minMax, false, 0, true, pageSz);
656 }
657
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) ) );
660 }
661
662 // private float getKnobValuePct(final float pos_x, final float pos_y, final float adjustment) {
663 /**
664 * Sets slider value
665 * @param v new value of slider, clipped against {@link #getMinMax()}
666 * @return this instance of chaining
667 */
668 public RangeSlider setValue(final float v) {
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));
673 if( Float.isFinite(range) && !FloatUtil.isZero(range) ) {
674 val_pct = ( val - minMax.x() ) / range;
675 } else {
676 val_pct = 0f;
677 }
678 setKnob();
679 return this;
680 }
681
682 /**
683 * Returns generic item position reflects value on its center (round-knob) or page-size start and ranges from zero to max.
684 * @param posRes {@link Vec2f} result storage
685 * @param value value within {@link #getMinMax()}
686 * @param itemLen item length in sliding direction
687 * @param itemHeight item height orthogonal to sliding direction
688 */
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);
691 }
692 /**
693 * Returns generic item position reflects value on its center (round-knob) or page-size start and ranges from zero to max.
694 * @param posRes {@link Vec2f} result storage
695 * @param val_pct value percentage within [0..1]
696 * @param itemLen item length in sliding direction
697 * @param itemThickn item thickness orthogonal to sliding direction
698 */
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) ) {
703 if( inverted ) {
704 itemAdjust = itemLen; // top-edge
705 } else {
706 itemAdjust = 0; // bottom-edge
707 }
708 } else {
709 itemAdjust = itemLen * 0.5f; // centered
710 }
711 if( horizontal ) {
712 posRes.setX( Math.max(0, Math.min(size.x() - itemLen, v*size.x() - itemAdjust)) );
713 posRes.setY( -( itemThickn - size.y() ) * 0.5f );
714 } else {
715 posRes.setX( -( itemThickn - size.x() ) * 0.5f );
716 posRes.setY( Math.max(0, Math.min(size.y() - itemLen, v*size.y() - itemAdjust)) );
717 }
718 return posRes;
719 }
720 private float getKnobValuePct(final float pos_x, final float pos_y, final float adjustment) {
721 final float v;
722 if( horizontal ) {
723 v = ( pos_x + adjustment ) / size.x();
724 } else {
725 v = ( pos_y + adjustment ) / size.y();
726 }
727 return Math.max(0.0f, Math.min(1.0f, inverted ? 1f - v : v));
728 }
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 );
735 } else {
736 return 0f;
737 }
738 }
739
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);
743 }
744
745 /**
746 * Sets the slider knob color.
747 * <p>
748 * If this slider comprises a rectangular page-sized knob,
749 * its rectangular frame also shares the same color with alpha 1.0f.
750 * </p>
751 * <p>
752 * Base color w/o color channel, will be modulated w/ pressed- and toggle color
753 * </p>
754 * <p>
755 * Default RGBA value is 0.80f, 0.80f, 0.80f, 0.7f
756 * </p>
757 */
758 @Override
759 public final Shape setColor(final float r, final float g, final float b, final float a) {
760 super.setColor(r, g, b, a);
761 knob.setColor(r, g, b, a);
762 if( Float.isFinite(pageSize) ) {
763 bar.setColor(r, g, b, 1.0f);
764 }
765 return this;
766 }
767
768 /**
769 * Sets the slider knob color.
770 * <p>
771 * If this slider comprises a rectangular page-sized knob,
772 * its rectangular frame also shares the same color with alpha 1.0f.
773 * </p>
774 * <p>
775 * Base color w/o color channel, will be modulated w/ pressed- and toggle color
776 * </p>
777 * <p>
778 * Default RGBA value is 0.80f, 0.80f, 0.80f, 0.7f
779 * </p>
780 */
781 @Override
782 public Shape setColor(final Vec4f c) {
783 this.rgbaColor.set(c);
784 knob.setColor(c);
785 if( Float.isFinite(pageSize) ) {
786 bar.setColor(c.x(), c.y(), c.z(), 1.0f);
787 }
788 return this;
789 }
790
791 /**
792 * Sets the knob active modulation color
793 * <p>
794 * Default RGBA value is 0.1f, 0.1f, 0.1f, 1f
795 * </p>
796 */
798 if( !Float.isFinite(pageSize) ) {
799 activeColMod.set(c);
800 }
801 return this;
802 }
803
804 /**
805 * Sets the slider background bar color, if this slider comprises only a positional round knob.
806 * <p>
807 * Default RGBA value is 0.60f, 0.60f, 0.60f, 0.5f
808 * </p>
809 */
810 public Shape setBackgroundBarColor(final float r, final float g, final float b, final float a) {
811 if( !Float.isFinite(pageSize) ) {
812 bar.setColor(r, g, b, a);
813 }
814 return this;
815 }
816 /**
817 * Sets the slider background bar color, if this slider comprises only a positional round knob.
818 * <p>
819 * Default RGBA value is 0.60f, 0.60f, 0.60f, 0.5f
820 * </p>
821 */
823 if( !Float.isFinite(pageSize) ) {
824 bar.setColor(c);
825 }
826 return this;
827 }
828
829 /**
830 * {@inheritDoc}
831 * <p>
832 * Sets the slider bar and knob pressed color modulation.
833 * </p>
834 */
835 @Override
836 public final Shape setPressedColorMod(final float r, final float g, final float b, final float a) {
837 super.setPressedColorMod(r, g, b, a);
838 bar.setPressedColorMod(r, g, b, a);
839 knob.setPressedColorMod(r, g, b, a);
840 return this;
841 }
842
843 /** Return string description of current slider setting. */
844 public String getDescription() {
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;
850 if( horizontal ) {
851 return "H "+pre+detail+"/"+size.x()+post;
852 } else {
853 return "V "+pre+detail+"/"+size.y()+post;
854 }
855 } else {
856 if( horizontal ) {
857 return "H "+pre+post;
858 } else {
859 return "V "+pre+post;
860 }
861 }
862 }
863 @Override
864 public String getSubString() {
865 return super.getSubString()+", "+getDescription()+" @ "+val+", "+(100f*val_pct)+"%";
866 }
867 @Override
868 protected void validateImpl(final GL2ES2 gl, final GLProfile glp) {
869 if( isShapeDirty() ) {
870 super.validateImpl(gl, glp);
871 setKnobSize(pageSize, true, true);
872 if( DEBUG ) { System.err.println("RangeSlider.val "+getDescription()); }
873 }
874 }
875}
Abstract Outline shape representation define the method an OutlineShape(s) is bound and rendered.
Definition: Region.java:62
static final int COLORCHANNEL_RENDERING_BIT
Rendering-Mode bit for Region to optionally enable a color-channel per vertex.
Definition: Region.java:148
Graph based GLRegion Shape.
Definition: GraphShape.java:55
final int getRenderModes()
Returns validated Graph Region render modes, see create(..).
Definition: GraphShape.java:86
Group of Shapes, optionally utilizing a Group.Layout.
Definition: Group.java:61
Group()
Create a group of Shapes w/o Group.Layout.
Definition: Group.java:106
void addShape(final Shape s)
Adds a Shape.
Definition: Group.java:225
Forward KeyListener, to be attached to a key event source forwarded to the receiver set at constructo...
Definition: Shape.java:172
Forward MouseGestureListener, to be attached to a mouse event source forwarded to the receiver set at...
Definition: Shape.java:200
Generic Shape, potentially using a Graph via GraphShape or other means of representing content.
Definition: Shape.java:87
Shape setColor(final float r, final float g, final float b, final float a)
Set base color.
Definition: Shape.java:1389
Shape setName(final String name)
Set a symbolic name for this shape for identification.
Definition: Shape.java:339
final Shape setDraggable(final boolean draggable)
Set whether this shape is draggable, i.e.
Definition: Shape.java:1751
final Shape addKeyListener(final KeyListener l)
Definition: Shape.java:1841
final void onHover(final PointerListener l)
Set user callback to be notified when a pointer/mouse is moving over this shape.
Definition: Shape.java:481
Shape()
Create a generic UI Shape.
Definition: Shape.java:320
final Shape setInteractive(final boolean v)
Set whether this shape is interactive in general, i.e.
Definition: Shape.java:1711
final Shape addActivationListener(final Listener l)
Add user callback to be notified when shape is activated (pointer-over and/or click) or de-activated ...
Definition: Shape.java:511
final boolean isActive()
Returns true of this shape is active.
Definition: Shape.java:1633
final void onClicked(final PointerListener l)
Set user callback to be notified when shape is clicked.
Definition: Shape.java:503
final void clear(final GL2ES2 gl, final RegionRenderer renderer)
Clears all data and reset all states as if this instance was newly created.
Definition: Shape.java:425
final Shape setResizable(final boolean resizable)
Set whether this shape is resizable, i.e.
Definition: Shape.java:1769
final Shape moveTo(final float tx, final float ty, final float tz)
Move to scaled position.
Definition: Shape.java:543
final Vec3f getPosition()
Returns position Vec3f reference, i.e.
Definition: Shape.java:587
Shape setPressedColorMod(final float r, final float g, final float b, final float a)
Set pressed color, modulating getColor() if isPressed().
Definition: Shape.java:1423
final Shape setDragAndResizable(final boolean v)
Set whether this shape is draggable and resizable.
Definition: Shape.java:1801
final Vec4f getColor()
Returns base-color w/o color channel, will be modulated w/ getPressedColorMod(), getToggleOnColorMod(...
Definition: Shape.java:1366
final void dispatchActivationEvent(final Shape s)
Dispatch activation event event to this shape.
Definition: Shape.java:535
final Shape addMouseListener(final MouseGestureListener l)
Definition: Shape.java:1807
final void onMove(final MoveListener l)
Set user callback to be notified when shape is move(Vec3f)'ed.
Definition: Shape.java:485
final Shape setToggleable(final boolean toggleable)
Set this shape toggleable, default is off.
Definition: Shape.java:1573
GraphUI CSS property Padding, unscaled space belonging to the element and included in the element's s...
Definition: Padding.java:38
A GraphUI rectangle GraphShape.
Definition: Rectangle.java:47
RangeSlider Widget either utilizing a simple positional round knob or a rectangular page-sized knob.
Shape setBackgroundBarColor(final float r, final float g, final float b, final float a)
Sets the slider background bar color, if this slider comprises only a positional round knob.
Vec2f getMinMax()
Returns slider value range, see setMinMax(Vec2f, float).
Shape setActiveKnobColorMod(final Vec4f c)
Sets the knob active modulation color.
final float getKnobThickness()
Returns spatial knob thickness orthogonal to sliding direction.
final RangeSlider removeChangeListener(final ChangeListener l)
RangeSlider setMinMaxPgSz(final Vec2f minMax, final float pageSz)
Calls setMinMax(Vec2f, float) and setPageSize(float).
float getValue()
Returns current slider value.
final Vec2f getSize()
Returns spatial dimension of this slider.
String getDescription()
Return string description of current slider setting.
final RangeSlider addChangeListener(final ChangeListener l)
RangeSlider setMinMaxPgSz(final Vec2f minMax, final float value, final float pageSz)
Calls setMinMax(Vec2f, float) and setPageSize(float).
float getUnitSize()
Returns the size of one unit (element) in sliding direction.
void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer)
Custom clear(GL2ES2, RegionRenderer) task, called 1st.
void validateImpl(final GL2ES2 gl, final GLProfile glp)
RangeSlider setMinMax(final Vec2f minMax, final float value)
Sets slider value range and current value, also updates related pageSize parameter if used.
RangeSlider setInverted(final boolean v)
Sets whether this slider uses an inverted value range, e.g.
Shape addMark(final float value, final Vec4f color)
final Shape setColor(final float r, final float g, final float b, final float a)
Sets the slider knob color.
RangeSlider setValue(final float v)
Sets slider value.
final float getKnobLength()
Returns spatial knob length in sliding direction.
final Shape setPressedColorMod(final float r, final float g, final float b, final float a)
Set pressed color, modulating getColor() if isPressed().Default pressed color, modulation -factor w/o...
void receiveKeyEvents(final Shape source)
Forward KeyListener events to this Shape from source using a ForwardKeyListener.
void receiveMouseEvents(final Shape source)
Forward MouseGestureListener events to this Shape from source using a ForwardMouseListener.
Shape setColor(final Vec4f c)
Sets the slider knob color.
float getValuePct()
Returns current slider value in percentage of getRange(),.
float getRange()
Returns getMinMax() range.
float getPageSizePct(final float minPct)
Returns the page-size percentage if a rectangular knob is being used, i.e.
boolean isInverted()
See setInverted(boolean).
Shape setBackgroundBarColor(final Vec4f c)
Sets the slider background bar color, if this slider comprises only a positional round knob.
RangeSlider setMinMax(final Vec2f minMax)
Sets slider value range, also updates related pageSize parameter if used.
final RangeSlider removePeekListener(final PeekListener l)
final RangeSlider addPeekListener(final PeekListener l)
float getPageSize()
Returns the page-size if a rectangular knob is being used, i.e.
RangeSlider clearMarks(final GL2ES2 gl, final RegionRenderer renderer)
RangeSlider(final int renderModes, final Vec2f size, final Vec2f minMax, final float unitSize, final float pageSize, final float value)
Constructs a RangeSlider, i.e.
RangeSlider setPageSize(final float pageSz)
Sets the page-size if a rectangular knob is being used, i.e.
void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer)
Custom destroy(GL2ES2, RegionRenderer) task, called 1st.
RangeSlider setUnitSize(final float v)
Sets the size of one unit (element) in sliding direction.
RangeSlider(final int renderModes, final Vec2f size, final float knobScale, final Vec2f minMax, final float unitSize, final float value)
Constructs a RangeSlider, i.e.
A widget specifies specific UI semantics including individual controls.
Definition: Widget.java:44
Basic Float math utility functions.
Definition: FloatUtil.java:83
static boolean isZero(final float a, final float epsilon)
Returns true if value is zero, i.e.
2D Vector based upon two float components.
Definition: Vec2f.java:37
void set(final Vec2f o)
this = o, returns this.
Definition: Vec2f.java:73
void setY(final float y)
Definition: Vec2f.java:139
void setX(final float x)
Definition: Vec2f.java:138
3D Vector based upon three float components.
Definition: Vec3f.java:37
Vec3f minus(final Vec3f arg)
Returns this - arg; creates new vector.
Definition: Vec3f.java:255
4D Vector based upon four float components.
Definition: Vec4f.java:37
Vec4f set(final Vec4f o)
this = o, returns this.
Definition: Vec4f.java:67
Pointer event of type PointerType.
Definition: MouseEvent.java:74
Specifies the the OpenGL profile.
Definition: GLProfile.java:77
RangeSlider slider value changed listener
void dragged(RangeSlider w, float old_val, float val, float old_val_pct, float val_pct, Vec3f pos, MouseEvent e)
Slide dragged by user (including clicked position)
RangeSlider slider value peek listener
void peeked(RangeSlider w, float val, float val_pct, Vec3f pos, MouseEvent e)
Slide position/value peeked by user (mouse over/hover)