001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.fx.control; 019 020import static java.lang.Boolean.FALSE; 021import static java.lang.Double.max; 022import static java.lang.Double.min; 023import static javafx.geometry.Orientation.HORIZONTAL; 024import static javafx.geometry.Orientation.VERTICAL; 025import static org.apiguardian.api.API.Status.STABLE; 026import static org.tquadrat.foundation.fx.FXUtils.clamp; 027import static org.tquadrat.foundation.fx.FXUtils.nearest; 028import static org.tquadrat.foundation.fx.control.RangeSlider.StyleableProperties.BLOCK_INCREMENT; 029import static org.tquadrat.foundation.fx.control.RangeSlider.StyleableProperties.ORIENTATION; 030import static org.tquadrat.foundation.fx.control.RangeSlider.StyleableProperties.SHOW_TICK_LABELS; 031import static org.tquadrat.foundation.fx.control.RangeSlider.StyleableProperties.SHOW_TICK_MARKS; 032import static org.tquadrat.foundation.fx.control.RangeSlider.StyleableProperties.SNAP_TO_TICKS; 033import static org.tquadrat.foundation.lang.Objects.requireValidDoubleArgument; 034 035import java.util.List; 036 037import org.apiguardian.api.API; 038import org.tquadrat.foundation.annotation.ClassVersion; 039import org.tquadrat.foundation.annotation.UtilityClass; 040import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError; 041import org.tquadrat.foundation.fx.control.skin.RangeSliderSkin; 042import org.tquadrat.foundation.fx.internal.FoundationFXControl; 043import javafx.beans.property.BooleanProperty; 044import javafx.beans.property.DoubleProperty; 045import javafx.beans.property.IntegerProperty; 046import javafx.beans.property.ObjectProperty; 047import javafx.beans.property.SimpleBooleanProperty; 048import javafx.beans.property.SimpleDoubleProperty; 049import javafx.beans.property.SimpleObjectProperty; 050import javafx.css.CssMetaData; 051import javafx.css.PseudoClass; 052import javafx.css.SimpleStyleableBooleanProperty; 053import javafx.css.SimpleStyleableDoubleProperty; 054import javafx.css.SimpleStyleableIntegerProperty; 055import javafx.css.SimpleStyleableObjectProperty; 056import javafx.css.StyleOrigin; 057import javafx.css.Styleable; 058import javafx.css.StyleableBooleanProperty; 059import javafx.css.StyleableDoubleProperty; 060import javafx.css.StyleableIntegerProperty; 061import javafx.css.StyleableObjectProperty; 062import javafx.css.StyleableProperty; 063import javafx.css.converter.BooleanConverter; 064import javafx.css.converter.EnumConverter; 065import javafx.css.converter.SizeConverter; 066import javafx.geometry.Orientation; 067import javafx.scene.control.Skin; 068import javafx.util.StringConverter; 069 070/** 071 * <p>{@summary The {@code RangeSlider} control is simply a JavaFX 072 * {@link javafx.scene.control.Slider} 073 * control with support for two 'thumbs', rather than one.} A thumb is the 074 * non-technical name for the draggable area inside the 075 * {@code Slider}/{@code RangeSlider} that allows for a value to be set.</p> 076 * <p>Because the RangeSlider has two thumbs, it also has a few additional 077 * rules and user interactions:</p> 078 * <ol> 079 * <li>The 'lower value' thumb can not move past the 'higher value' 080 * thumb.</li> 081 * <li>Whereas the 082 * {@link javafx.scene.control.Slider} 083 * control only has one 084 * {@link javafx.scene.control.Slider#valueProperty() value} 085 * property, the {@code RangeSlider} has a 086 * {@link #lowValueProperty() low value} 087 * and a 088 * {@link #highValueProperty() high value} 089 * property, not surprisingly represented by the 'low value' and 'high 090 * value' thumbs.</li> 091 * <li>The area between the low and high values represents the allowable 092 * range. For example, if the low value is 2 and the high value is 8, then 093 * the allowable range is between 2 and 8.</li> 094 * <li>The allowable range area is rendered differently. This area is able to 095 * be dragged with mouse/touch input to allow for the entire range to be 096 * modified. For example, following on from the previous example of the 097 * allowable range being between 2 and 8, if the user drags the range bar 098 * to the right, the low value will adjust to 3, and the high value 9, and 099 * so on until the user stops adjusting.</li> 100 * </ol> 101 * 102 * <h2>Code Samples</h2> 103 * <p>Instantiating a {@code RangeSlider} is simple. The first decision is to 104 * decide whether a horizontal or a vertical track is more appropriate. By 105 * default {@code RangeSlider} instances are horizontal, but this can be 106 * changed by setting the 107 * {@link #orientationProperty() orientation} 108 * property.</p> 109 * <p>Once the orientation is determined, the next most important decision is 110 * to determine what the 111 * {@link #minProperty() min}/{@link #maxProperty() max} 112 * and default 113 * {@link #lowValueProperty() low}/{@link #highValueProperty() high} 114 * values are. The {@code min}/{@code max} values represent the smallest and 115 * largest legal values for the thumbs to be set to, whereas the 116 * {@code low}/{@code high} values represent where the thumbs are currently, 117 * within the bounds of the {@code min}/{@code max} values. Because all four 118 * values are required in all circumstances, they are all required parameters 119 * to instantiate a {@code RangeSlider}: the 120 * {@linkplain #RangeSlider(double,double,double,double) constructor} 121 * takes four doubles, representing {@code min}, {@code max}, {@code lowValue} 122 * and {@code highValue} (in that order).</p> 123 * <p>For example, here is a simple horizontal {@code RangeSlider} that has a 124 * minimum value of 0, a maximum value of 100, a low value of 10 and a high 125 * value of 90:</p> 126 * <pre>{@code final RangeSlider hSlider = new RangeSlider( 0, 100, 10, 90 );}</pre> 127 * <p>To create a vertical slider, simply do the following:</p> 128 * <pre>{@code final RangeSlider vSlider = new RangeSlider( 0, 200, 30, 150 ); 129 * vSlider.setOrientation( Orientation.VERTICAL );}</pre> 130 * <p>This code creates a vertical {@code RangeSlider} with a {@code min} 131 * value of 0, a {@code max} value of 200, a {@code low} value of 30, and a 132 * {@code high} value of 150.</p> 133 * 134 * @see javafx.scene.control.Slider 135 * 136 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 137 * @inspired {@href https://controlsfx.github.io/ ControlsFX Project} 138 * @version $Id: RangeSlider.java 1121 2024-03-16 16:51:23Z tquadrat $ 139 * @since 0.4.6 140 * 141 * @UMLGraph.link 142 */ 143@SuppressWarnings( {"ClassWithTooManyFields", "ClassWithTooManyMethods"} ) 144@ClassVersion( sourceVersion = "$Id: RangeSlider.java 1121 2024-03-16 16:51:23Z tquadrat $" ) 145@API( status = STABLE, since = "0.4.6" ) 146public final class RangeSlider extends FoundationFXControl 147{ 148 /*---------------*\ 149 ====** Inner Classes **==================================================== 150 \*---------------*/ 151 /** 152 * The styleable properties for 153 * {@link RangeSlider}. 154 * 155 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 156 * @inspired {@href https://controlsfx.github.io/ ControlsFX Project} 157 * @version $Id: RangeSlider.java 1121 2024-03-16 16:51:23Z tquadrat $ 158 * @since 0.4.6 159 */ 160 @SuppressWarnings( {"ProtectedInnerClass", "InnerClassTooDeeplyNested", "AnonymousInnerClass"} ) 161 @UtilityClass 162 @ClassVersion( sourceVersion = "$Id: RangeSlider.java 1121 2024-03-16 16:51:23Z tquadrat $" ) 163 @API( status = STABLE, since = "0.4.6" ) 164 protected static final class StyleableProperties 165 { 166 /*------------------------*\ 167 ====** Static Initialisations **======================================= 168 \*------------------------*/ 169 /** 170 * The CSS attribute for the {@code BLOCK_INCREMENT}. 171 * 172 * @see #blockIncrementProperty() 173 */ 174 public static final CssMetaData<RangeSlider,Number> BLOCK_INCREMENT = new CssMetaData<>( "-fx-block-increment", SizeConverter.getInstance(), 10.0 ) 175 { 176 /** 177 * {@inheritDoc} 178 */ 179 @Override 180 public final StyleableProperty<Number> getStyleableProperty( final RangeSlider styleable ) { return styleable.m_BlockIncrementProperty; } 181 182 /** 183 * {@inheritDoc} 184 */ 185 @Override 186 public final boolean isSettable( final RangeSlider styleable ) { return !styleable.m_BlockIncrementProperty.isBound(); } 187 }; 188 189 /** 190 * The CSS attribute for the {@code MAJOR_TICK_UNIT}. 191 * 192 * @see #majorTickUnitProperty() 193 */ 194 public static final CssMetaData<RangeSlider,Number> MAJOR_TICK_UNIT = new CssMetaData<>( "-fx-major-tick-unit", SizeConverter.getInstance(), 25.0 ) 195 { 196 /** 197 * {@inheritDoc} 198 */ 199 @Override 200 public StyleableProperty<Number> getStyleableProperty( final RangeSlider styleable ) { return styleable.m_MajorTickUnitProperty; } 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override 206 public final boolean isSettable( final RangeSlider styleable ) { return !styleable.m_MajorTickUnitProperty.isBound(); 207 } 208 }; 209 210 /** 211 * The CSS attribute for the {@code MAJOR_TICK_UNIT}. 212 * 213 * @see #minorTickCountProperty() 214 */ 215 @SuppressWarnings( "OverlyComplexAnonymousInnerClass" ) 216 public static final CssMetaData<RangeSlider,Number> MINOR_TICK_COUNT = new CssMetaData<>( "-fx-minor-tick-count", SizeConverter.getInstance(), 3 ) 217 { 218 /** 219 * {@inheritDoc} 220 */ 221 @Override 222 public final StyleableProperty<Number> getStyleableProperty( final RangeSlider styleable ) { return styleable.m_MinorTickCountProperty; } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 public final boolean isSettable( final RangeSlider styleable ) 229 { 230 return styleable.m_MinorTickCountProperty == null || !styleable.m_MinorTickCountProperty.isBound(); 231 } 232 233 /** 234 * {@inheritDoc} 235 */ 236 @SuppressWarnings( "deprecation" ) 237 @Override 238 public final void set( final RangeSlider styleable, final Number value, final StyleOrigin origin ) 239 { 240 super.set( styleable, value.intValue(), origin ); 241 } // set() 242 }; 243 244 /** 245 * The CSS attribute for the {@code ORIENTATION}. 246 * 247 * @see #orientationProperty() 248 */ 249 public static final CssMetaData<RangeSlider,Orientation> ORIENTATION = new CssMetaData<>( "-fx-orientation", new EnumConverter<>( Orientation.class ), HORIZONTAL ) 250 { 251 /** 252 * {@inheritDoc} 253 */ 254 @Override 255 public final Orientation getInitialValue( final RangeSlider styleable ) 256 { 257 //---* A vertical RangeSlider should remain vertical *--------- 258 return styleable.getOrientation(); 259 } // getInitialValue() 260 261 /** 262 * {@inheritDoc} 263 */ 264 @Override 265 public final StyleableProperty<Orientation> getStyleableProperty( final RangeSlider styleable ) 266 { 267 return styleable.m_OrientationProperty; 268 } // getStyleableProperty() 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override 274 public final boolean isSettable( final RangeSlider styleable ) { return !styleable.m_OrientationProperty.isBound(); } 275 }; 276 277 /** 278 * The CSS attribute for {@code SHOW_TICK_LABELS}. 279 * 280 * @see #showTickLabelsProperty() 281 */ 282 public static final CssMetaData<RangeSlider,Boolean> SHOW_TICK_LABELS = new CssMetaData<>( "-fx-show-tick-labels", BooleanConverter.getInstance(), FALSE ) 283 { 284 /** 285 * {@inheritDoc} 286 */ 287 @Override 288 public final StyleableProperty<Boolean> getStyleableProperty( final RangeSlider styleable ) { return styleable.m_ShowTickLabelsProperty; } 289 290 /** 291 * {@inheritDoc} 292 */ 293 @Override 294 public final boolean isSettable( final RangeSlider styleable ) { return !styleable.m_ShowTickLabelsProperty.isBound(); } 295 }; 296 297 /** 298 * The CSS attribute for {@code SHOW_TICK_MARKS}. 299 * 300 * @see #showTickMarksProperty() 301 */ 302 public static final CssMetaData<RangeSlider,Boolean> SHOW_TICK_MARKS = new CssMetaData<>( "-fx-show-tick-marks", BooleanConverter.getInstance(), FALSE ) 303 { 304 /** 305 * {@inheritDoc} 306 */ 307 @Override 308 public final StyleableProperty<Boolean> getStyleableProperty( final RangeSlider styleable ) { return styleable.m_ShowTickMarksProperty; } 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override 314 public final boolean isSettable( final RangeSlider styleable ) { return !styleable.m_ShowTickMarksProperty.isBound(); } 315 }; 316 317 /** 318 * The CSS attribute for {@code SNAP_TO_TICKS}. 319 * 320 * @see #snapToTicksProperty() 321 */ 322 public static final CssMetaData<RangeSlider,Boolean> SNAP_TO_TICKS = new CssMetaData<>( "-fx-snap-to-ticks", BooleanConverter.getInstance(), FALSE ) 323 { 324 /** 325 * {@inheritDoc} 326 */ 327 @Override 328 public final StyleableProperty<Boolean> getStyleableProperty( final RangeSlider styleable ) { return styleable.m_SnapToTicksProperty; } 329 330 /** 331 * {@inheritDoc} 332 */ 333 @Override 334 public final boolean isSettable( final RangeSlider styleable ) { return !styleable.m_SnapToTicksProperty.isBound(); } 335 }; 336 337 /** 338 * The CSS attributes for 339 * {@link RangeSlider}. 340 */ 341 @SuppressWarnings( "StaticCollection" ) 342 public static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES = List.of( BLOCK_INCREMENT, MAJOR_TICK_UNIT, MINOR_TICK_COUNT, ORIENTATION, SHOW_TICK_LABELS, SHOW_TICK_MARKS, SNAP_TO_TICKS ); 343 344 /*--------------*\ 345 ====** Constructors **================================================= 346 \*--------------*/ 347 /** 348 * No instance allowed for this class! 349 */ 350 private StyleableProperties() { throw new PrivateConstructorForStaticClassCalledError( StyleableProperties.class ); } 351 } 352 // class StyleableProperties 353 354 /*-----------*\ 355 ====** Constants **======================================================== 356 \*-----------*/ 357 /** 358 * The default style class for {@code RangeSlider} instances: {@value}. 359 */ 360 public static final String DEFAULT_STYLE_CLASS = "range-slider"; //$NON-NLS-1$ 361 362 /*------------*\ 363 ====** Attributes **======================================================= 364 \*------------*/ 365 /** 366 * The property for the amount by which to adjust the slider if the track 367 * of the slider is clicked. 368 */ 369 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 370 private final StyleableDoubleProperty m_BlockIncrementProperty = new SimpleStyleableDoubleProperty( BLOCK_INCREMENT, this, "blockIncrement", 10.0 ); 371 372 /** 373 * <p>{@summary The property that indicates a change to the high value of 374 * this {@code RangeSlider}.}</p> 375 */ 376 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 377 private final BooleanProperty m_HighValueChangingProperty = new SimpleBooleanProperty(this, "highValueChanging", false); 378 379 /** 380 * <p>{@summary The high value property.} It represents the current 381 * position of the high value thumb, and is within the allowable range as 382 * specified by the 383 * {@link #minProperty() min} 384 * and 385 * {@link #maxProperty() max} 386 * properties. By default this value is 100.</p> 387 */ 388 @SuppressWarnings( "AnonymousInnerClass" ) 389 private final DoubleProperty m_HighValueProperty = new SimpleDoubleProperty( this, "highValue", 100.0D ) 390 { 391 /** 392 * {@inheritDoc} 393 */ 394 @Override 395 protected final void invalidated() { adjustHighValues(); } 396 }; 397 398 /** 399 * <p>{@summary The property that indicates a change to the low value of 400 * this {@code RangeSlider}.}</p> 401 */ 402 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 403 private final BooleanProperty m_LowValueChangingProperty = new SimpleBooleanProperty( this, "lowValueChanging", false ); 404 405 /** 406 * <p>{@summary The low value property.} It represents the current 407 * position of the low value thumb, and is within the allowable range as 408 * specified by the 409 * {@link #minProperty() min} 410 * and 411 * {@link #maxProperty() max} 412 * properties. By default this value is 0.</p> 413 */ 414 @SuppressWarnings( "AnonymousInnerClass" ) 415 private final DoubleProperty m_LowValueProperty = new SimpleDoubleProperty(this, "lowValue", 0.0D) 416 { 417 /** 418 * {@inheritDoc} 419 */ 420 @Override 421 protected final void invalidated() { adjustLowValues(); } 422 }; 423 424 /** 425 * The property for the unit distance between major tick marks. 426 */ 427 @SuppressWarnings( {"AnonymousInnerClass"} ) 428 private final StyleableDoubleProperty m_MajorTickUnitProperty = new SimpleStyleableDoubleProperty( StyleableProperties.MAJOR_TICK_UNIT, this, "majorTickUnit", 25.0 ) 429 { 430 /** 431 * {@inheritDoc} 432 */ 433 @Override 434 public final void invalidated() 435 { 436 if( get() <= 0 ) 437 { 438 throw new IllegalArgumentException( "MajorTickUnit cannot be less than or equal to 0." ); 439 } 440 } // invalidated() 441 }; 442 443 /** 444 * The property for the maximum value of this {@code RangeSlider}. 445 */ 446 @SuppressWarnings( "AnonymousInnerClass" ) 447 private final DoubleProperty m_MaxProperty = new SimpleDoubleProperty(this, "max", 100.0D ) 448 { 449 /** 450 * {@inheritDoc} 451 */ 452 @Override 453 protected final void invalidated() 454 { 455 if( get() < getMin() && !m_MinProperty.isBound() ) setMin( get() ); 456 adjustValues(); 457 } // invalidated() 458 }; 459 460 /** 461 * The property for the number of minor ticks to place between any two 462 * major ticks. 463 */ 464 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 465 private final StyleableIntegerProperty m_MinorTickCountProperty = new SimpleStyleableIntegerProperty( RangeSlider.StyleableProperties.MINOR_TICK_COUNT, this, "minorTickCount", 3 ); 466 467 /** 468 * The property for the maximum value of this {@code RangeSlider}. 469 */ 470 @SuppressWarnings( "AnonymousInnerClass" ) 471 private final DoubleProperty m_MinProperty = new SimpleDoubleProperty( this, "min", 0.0D ) 472 { 473 /** 474 * {@inheritDoc} 475 */ 476 @Override 477 protected final void invalidated() 478 { 479 if( get() > getMax() && !m_MaxProperty.isBound() ) setMax( get() ); 480 adjustValues(); 481 } // invalidated() 482 }; 483 484 /** 485 * The property that holds the orientation of this {@code RangeSlider}. 486 */ 487 @SuppressWarnings( {"AnonymousInnerClass"} ) 488 private final StyleableObjectProperty<Orientation> m_OrientationProperty = new SimpleStyleableObjectProperty<>( ORIENTATION, this, "orientation", HORIZONTAL ) 489 { 490 /** 491 * {@inheritDoc} 492 */ 493 @Override 494 protected final void invalidated() 495 { 496 final var vertical = (get() == VERTICAL); 497 pseudoClassStateChanged( VERTICAL_PSEUDOCLASS_STATE, vertical ); 498 pseudoClassStateChanged( HORIZONTAL_PSEUDOCLASS_STATE, !vertical ); 499 } // invalidated() 500 }; 501 502 /** 503 * The property for the flag that indicates whether the labels for the 504 * tick marks are shown or not. 505 */ 506 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 507 private final StyleableBooleanProperty m_ShowTickLabelsProperty = new SimpleStyleableBooleanProperty( SHOW_TICK_LABELS, this, "showTickLabels", false ); 508 509 /** 510 * The property that holds the flag that specifies whether the 511 * {@link Skin} 512 * implementation should show tick marks for this {@code RangeSlider}. 513 */ 514 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 515 private final StyleableBooleanProperty m_ShowTickMarksProperty = new SimpleStyleableBooleanProperty( SHOW_TICK_MARKS, this, "showTickMarks", false ); 516 517 /** 518 * The property for the flag that controls whether the thumbs will snap to 519 * the tick marks. 520 */ 521 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 522 private final StyleableBooleanProperty m_SnapToTicksProperty = new SimpleStyleableBooleanProperty( SNAP_TO_TICKS, this, "snapToTicks", false ); 523 524 /** 525 * The property for the tick label formatter. 526 */ 527 @SuppressWarnings( "ThisEscapedInObjectConstruction" ) 528 private final ObjectProperty<StringConverter<Number>> m_TickLabelFormatterProperty = new SimpleObjectProperty<>( this, "labelFormatter" ); 529 530 /*------------------------*\ 531 ====** Static Initialisations **=========================================== 532 \*------------------------*/ 533 /** 534 * The CSS pseudo class for the horizontal orientation of a 535 * {@code RangeSlider}. 536 */ 537 private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("horizontal"); 538 539 /** 540 * The CSS pseudo class for the vertical orientation of a 541 * {@code RangeSlider}. 542 */ 543 private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("vertical"); 544 545 /*--------------*\ 546 ====** Constructors **===================================================== 547 \*--------------*/ 548 /** 549 * Creates a new {@code RangeSlider} instance using default values 550 * of 0.0, 0.25, 0.75 and 1.0 for min/lowValue/highValue/max, respectively. 551 */ 552 @SuppressWarnings( "MagicNumber" ) 553 public RangeSlider() 554 { 555 this(0, 1.0, 0.25, 0.75); 556 } // RangeSlider() 557 558 /** 559 * Instantiates a default, horizontal {@code RangeSlider} instance with 560 * the specified min/max/low/high values. 561 * 562 * @param min The minimum allowable value that the {@code RangeSlider} 563 * will allow. 564 * @param max The maximum allowable value that the {@code RangeSlider} 565 * will allow. 566 * @param lowValue The initial value for the low value in the 567 * {@code RangeSlider}. 568 * @param highValue The initial value for the high value in the 569 * {@code RangeSlider}. 570 */ 571 public RangeSlider( final double min, final double max, final double lowValue, final double highValue ) 572 { 573 getStyleClass().setAll( DEFAULT_STYLE_CLASS ); 574 575 setMax( max ); 576 setMin( min ); 577 adjustValues(); 578 setLowValue( lowValue ); 579 setHighValue( highValue ); 580 } // RangeSlider() 581 582 /*---------*\ 583 ====** Methods **========================================================== 584 \*---------*/ 585 /** 586 * <p>{@summary Adjusts 587 * {@linkplain #highValueProperty() highValue} 588 * to match the given {@code newHigh}, or as closely as possible within 589 * the constraints imposed by the 590 * {@link #minProperty() min} 591 * and 592 * {@link #maxProperty() max} 593 * properties.} This method also takes into account the 594 * {@link #snapToTicksProperty() snapToTicks} flag, which is the main 595 * difference between {@code adjustHighValue()} and 596 * {@link #setHighValue(double) setHighValue()}.</p> 597 * 598 * @param newHigh The new value. 599 */ 600 public final void adjustHighValue( final double newHigh ) 601 { 602 final var d1 = getMin(); 603 final var d2 = getMax(); 604 if( d2 > d1 ) 605 { 606 final var value = min( max( newHigh, d1 ), d2 ); 607 setHighValue( snapValueToTicks( value ) ); 608 } 609 } // adjustHighValue() 610 611 /** 612 * Adjusts the high value. 613 * 614 * @see #adjustValues() 615 */ 616 private final void adjustHighValues() 617 { 618 if( (getHighValue() < getMin()) || (getHighValue() > getMax()) ) 619 { 620 setHighValue( clamp( getMin(), getHighValue(), getMax() ) ); 621 } 622 else if( (getHighValue() < getLowValue()) && ((getLowValue() >= getMin()) && (getLowValue() <= getMax())) ) 623 { 624 setHighValue( clamp(getLowValue(), getHighValue(), getMax() ) ); 625 } 626 } // adjustHighValues() 627 628 /** 629 * <p>{@summary Adjusts the 630 * {@linkplain #lowValueProperty() lowValue} 631 * to match the given {@code newLow}, or as closely as possible within 632 * the constraints imposed by the 633 * {@link #minProperty() min} 634 * and 635 * {@link #maxProperty() max} 636 * properties.} This method also takes into account the 637 * {@link #snapToTicksProperty() snapToTicks} 638 * flag, which is the main difference between {@code adjustLowValue()} and 639 * {@link #setLowValue(double) setLowValue()}.</p> 640 * 641 * @param newLow The new value. 642 */ 643 public final void adjustLowValue( final double newLow ) 644 { 645 final var d1 = getMin(); 646 final var d2 = getMax(); 647 if( d2 > d1 ) 648 { 649 final var value = min( max( newLow, d1 ), d2 ); 650 setLowValue( snapValueToTicks( value ) ); 651 } 652 } // adjustLowValue() 653 654 /** 655 * Adjusts the low value. 656 * 657 * @see #adjustValues() 658 */ 659 private final void adjustLowValues() 660 { 661 /* 662 * We first look if the LowValue is between the min and max. 663 */ 664 if( (getLowValue() < getMin()) || (getLowValue() > getMax()) ) 665 { 666 final var value = clamp( getMin(), getLowValue(), getMax() ); 667 setLowValue( value ); 668 /* 669 * If the LowValue seems right, we check if it's not superior to 670 * HighValue ONLY if the highValue itself is right. Because it may 671 * happen that the highValue has not yet been computed and is 672 * wrong, and therefore force the lowValue to change in a wrong way 673 * which may end up in an infinite loop. 674 */ 675 } 676 else if( (getLowValue() >= getHighValue()) && ((getHighValue() >= getMin()) && (getHighValue() <= getMax())) ) 677 { 678 final var value = clamp( getMin(), getLowValue(), getHighValue() ); 679 setLowValue( value ); 680 } 681 } // adjustLowValues() 682 683 /** 684 * Ensures that {@code min} is always < {@code max}, that the 685 * current {@code value} is always somewhere between the two, and that if 686 * {@code snapToTicks} is set then the {@code value} will always be set to 687 * align with a tick mark. 688 */ 689 private final void adjustValues() 690 { 691 adjustLowValues(); 692 adjustHighValues(); 693 } // adjustValues() 694 695 /** 696 * <p>{@summary Returns a reference to the property that holds the amount 697 * by which to adjust the slider if the track of the slider is 698 * clicked.} This is used when manipulating the slider position using 699 * keys. If 700 * {@link #snapToTicksProperty() snapToTicks} 701 * is {@code true} then the nearest tick mark to the adjusted value will 702 * be used.</p> 703 * 704 * @return The property reference. 705 */ 706 public final DoubleProperty blockIncrementProperty() { return m_BlockIncrementProperty; } 707 708 /** 709 * {@inheritDoc} 710 */ 711 @Override 712 protected final Skin<?> createDefaultSkin() 713 { 714 final var retValue = new RangeSliderSkin( this ); 715 716 //---* Done *---------------------------------------------------------- 717 return retValue; 718 } // createDefaultSkin() 719 720 /** 721 * Decrements the 722 * {@linkplain #highValueProperty() high value} 723 * by the 724 * {@linkplain #blockIncrementProperty() block increment} 725 * amount. 726 */ 727 public final void decrementHighValue() { adjustHighValue( getHighValue() - getBlockIncrement() ); } 728 729 /** 730 * Decrements the 731 * {@linkplain #lowValueProperty() low value} 732 * by the 733 * {@linkplain #blockIncrementProperty() block increment} 734 * amount. 735 */ 736 public final void decrementLowValue() { adjustLowValue( getLowValue() - getBlockIncrement() ); } 737 738 /** 739 * Returns the amount by which to adjust the slider if the track of the 740 * slider is clicked. 741 * 742 * @return The amount by which to adjust the slider if the track of the 743 * slider is clicked. 744 * 745 * @see #blockIncrementProperty() 746 */ 747 public final double getBlockIncrement() { return m_BlockIncrementProperty.get(); } 748 749 /** 750 * Gets the 751 * {@link CssMetaData} 752 * associated with this class, which may include the {@code CssMetaData} 753 * of its super classes. 754 * 755 * @return The {@code CssMetaData} for this class. 756 */ 757 @SuppressWarnings( "MethodOverridesStaticMethodOfSuperclass" ) 758 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { return StyleableProperties.STYLEABLES; } 759 760 /** 761 * {@inheritDoc} 762 */ 763 @Override 764 public final List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { return getClassCssMetaData(); } 765 766 /** 767 * Returns the current high value for the range slider. 768 * 769 * @return The high value. 770 */ 771 public final double getHighValue() { return m_HighValueProperty.get(); } 772 773 /** 774 * Returns the tick label formatter. 775 * 776 * @return The 777 * {@link StringConverter} 778 * that is used as the tick label formatter. 779 */ 780 public final StringConverter<Number> getLabelFormatter(){ return m_TickLabelFormatterProperty.get(); } 781 782 /** 783 * Returns the current low value for the range slider. 784 * 785 * @return The low value. 786 */ 787 public final double getLowValue() { return m_LowValueProperty.get(); } 788 789 /** 790 * Returns the unit distance between major tick marks. 791 * 792 * @return The unit distance between major tick marks. 793 * 794 * @see #majorTickUnitProperty() 795 */ 796 public final double getMajorTickUnit() { return m_MajorTickUnitProperty.get(); } 797 798 /** 799 * <p>{@summary Returns the maximum value for this 800 * {@code RangeSlider}.}</p> 801 * <p>100 is returned if the maximum value has never been set.</p> 802 * 803 * @return The maximum value for this range slider. 804 */ 805 public final double getMax() { return m_MaxProperty.get(); } 806 807 /** 808 * <p>{@summary Returns the minimum value for this 809 * {@code RangeSlider}.}</p> 810 * <p>0 is returned if the minimum has never been set.</p> 811 * 812 * @return The minimum value for this range slider. 813 */ 814 public final double getMin() { return m_MinProperty.get(); } 815 816 /** 817 * Returns the number of minor ticks to place between any two major ticks. 818 * 819 * @return The number of minor ticks to place between any two major ticks. 820 * 821 * @see #minorTickCountProperty() 822 */ 823 public final int getMinorTickCount() { return m_MinorTickCountProperty.get(); } 824 825 /** 826 * Returns the orientation of the {@code RangeSlider}. 827 * 828 * @return The orientation of the {@code RangeSlider}. 829 * {@link Orientation#HORIZONTAL} is returned by default. 830 */ 831 public final Orientation getOrientation() { return m_OrientationProperty.get(); } 832 833 /** 834 * {@inheritDoc} 835 */ 836 @Override 837 public final String getUserAgentStylesheet() 838 { 839 final var retValue = getUserAgentStylesheet( getClass(), "RangeSlider.css" ); 840 841 //---* Done *---------------------------------------------------------- 842 return retValue; 843 } // getUserAgentStylesheet() 844 845 /** 846 * <p>{@summary Returns a reference to the property that indicates a 847 * change to the high value of this {@code RangeSlider}.}</p> 848 * <p>When the property is set to {@code true}, it indicates that the 849 * current high value of this {@code RangeSlider} is changing. It provides 850 * notification that the high value is changing. Once the high value is 851 * computed, it is set back to {@code false}.</p> 852 * 853 * @return The property reference. 854 */ 855 public final BooleanProperty highValueChangingProperty() { return m_HighValueChangingProperty; } 856 857 /** 858 * <p>{@summary Returns a reference to the property that holds the high 859 * value.}</p> 860 * <p>The high value property represents the current position of the high 861 * value thumb, and is within the allowable range as specified by the 862 * {@link #minProperty() min} 863 * and 864 * {@link #maxProperty() max} 865 * properties. By default this value is 100.</p> 866 * 867 * @return The property reference. 868 */ 869 public final DoubleProperty highValueProperty() { return m_HighValueProperty; } 870 871 /** 872 * Increments the 873 * {@linkplain #highValueProperty() high value} 874 * by the 875 * {@linkplain #blockIncrementProperty() block increment} 876 * amount. 877 */ 878 public final void incrementHighValue() { adjustHighValue( getHighValue() + getBlockIncrement() ); } 879 880 /** 881 * Increments the 882 * {@linkplain #lowValueProperty() low value} 883 * value by the 884 * {@linkplain #blockIncrementProperty() block increment} 885 * amount. 886 */ 887 public final void incrementLowValue() { adjustLowValue( getLowValue() + getBlockIncrement() ); } 888 889 /** 890 * Returns whether the high value of this {@code RangeSlider} is currently 891 * changing. 892 * 893 * @return {@code true} if the high value is currently changing, otherwise 894 * {@code false}. 895 */ 896 public final boolean isHighValueChanging() { return m_HighValueChangingProperty.get(); } 897 898 /** 899 * Returns whether the low value of this {@code RangeSlider} is currently 900 * changing. 901 * 902 * @return {@code true} if the low value is currently changing, otherwise 903 * {@code false}. 904 */ 905 public final boolean isLowValueChanging() { return m_LowValueChangingProperty.get(); } 906 907 /** 908 * Returns the flag indicating whether labels of tick marks are being 909 * shown. 910 * 911 * @return {@code true} if the tick mark label are shown, otherwise 912 * {@code false}. 913 */ 914 public final boolean isShowTickLabels() { return m_ShowTickLabelsProperty.get(); } 915 916 /** 917 * Returns the flag indication whether the tick marks are shown. 918 * 919 * @return {@code true} if the tick marks are shown, otherwise 920 * {@code false}. 921 */ 922 public final boolean isShowTickMarks() { return m_ShowTickMarksProperty.get(); } 923 924 /** 925 * Returns the flag that controls whether the thumbs will snap to the tick 926 * marks. 927 * 928 * @return {@code true} if the thumbs will snap to the tick marks, 929 * otherwise {@code false}. 930 * 931 * @see #snapToTicksProperty() 932 */ 933 public final boolean isSnapToTicks() { return m_SnapToTicksProperty.get(); } 934 935 /** 936 * <p>{@summary Returns a reference to the property that holds the 937 * {@link StringConverter} 938 * that is used to format the tick mark labels.}</p> 939 * <p>If {@code null}, a default will be used.</p> 940 * 941 * @return The property reference. 942 */ 943 public final ObjectProperty<StringConverter<Number>> labelFormatterProperty() 944 { 945 return m_TickLabelFormatterProperty; 946 } // labelFormatterProperty() 947 948 /** 949 * <p>{@summary Returns a reference to the property that indicates a 950 * change to the low value of this {@code RangeSlider}.}</p> 951 * <p>When the property is set to {@code true}, it indicates that the 952 * current low value of this {@code RangeSlider} is changing. It provides 953 * notification that the low value is changing. Once the low value is 954 * computed, the property is set back to {@code false}.</p> 955 * 956 * @return The property reference. 957 */ 958 public final BooleanProperty lowValueChangingProperty() { return m_LowValueChangingProperty; } 959 960 /** 961 * <p>{@summary Returns a reference to the property that holds the low 962 * value.}</p> 963 * <p>The low value property represents the current position of the low 964 * value thumb, and is within the allowable range as specified by the 965 * {@link #minProperty() min} 966 * and 967 * {@link #maxProperty() max} 968 * properties. By default this value is 0.</p> 969 * 970 * @return The property reference. 971 */ 972 public final DoubleProperty lowValueProperty() { return m_LowValueProperty; } 973 974 /** 975 * <p>{@summary Returns a reference to the property that holds the unit 976 * distance between major tick marks.} For example, if the 977 * {@linkplain #minProperty() min value} 978 * is 0 and the 979 * {@linkplain #maxProperty() max value} 980 * is 100 and the {@code majorTickUnit} is set to 25, then there would be 981 * 5 tick marks: one at position 0, one at position 25, one at position 982 * 50, one at position 75, and a final one at position 100.</p> 983 * <p>This value should be positive and should be a value less than the 984 * span. Out of range values are essentially the same as disabling tick 985 * marks.</p> 986 * 987 * @return The property reference. 988 */ 989 public final DoubleProperty majorTickUnitProperty() { return m_MajorTickUnitProperty; } 990 991 /** 992 * <p>{@summary Returns a reference to the property that holds the maximum 993 * value for this {@code RangeSlider}.}</p> 994 * 995 * @return The property reference. 996 */ 997 public final DoubleProperty maxProperty() { return m_MaxProperty; } 998 999 /** 1000 * <p>{@summary Returns a reference to the property that holds the number 1001 * of minor ticks to place between any two major ticks.} This number 1002 * should be positive or zero. Out of range values will disable minor 1003 * ticks, as will a value of zero.</p> 1004 * 1005 * @return The property reference. 1006 */ 1007 public final IntegerProperty minorTickCountProperty() { return m_MinorTickCountProperty; } 1008 1009 /** 1010 * <p>{@summary Returns a reference to the property that holds the minimum 1011 * value for this {@code RangeSlider}.}</p> 1012 * 1013 * @return The property reference. 1014 */ 1015 public final DoubleProperty minProperty() { return m_MinProperty; } 1016 1017 /** 1018 * <p>{@summary Returns a reference to the property that holds the 1019 * orientation of this {@code RangeSlider}.}</p> 1020 * <p>The orientation can either be 1021 * {@linkplain Orientation#HORIZONTAL horizontal} 1022 * or 1023 * {@linkplain Orientation#VERTICAL vertical}.</p> 1024 * 1025 * @return The property reference. 1026 */ 1027 public final ObjectProperty<Orientation> orientationProperty() { return m_OrientationProperty; } 1028 1029 /** 1030 * Sets the amount by which to adjust the slider if the track of the 1031 * slider is clicked. 1032 * 1033 * @param value The adjustment value. 1034 * 1035 * @see #blockIncrementProperty() 1036 */ 1037 public final void setBlockIncrement( final double value) { m_BlockIncrementProperty.set( value ); } 1038 1039 /** 1040 * Call this when high value is changing. 1041 * 1042 * @param flag {@code true} if the high value is currently changing, 1043 * {@code false} otherwise. 1044 */ 1045 public final void setHighValueChanging( final boolean flag ) { m_HighValueChangingProperty.set( flag ); } 1046 1047 /** 1048 * Sets the high value for the range slider, which may or may not be 1049 * clamped to be within the allowable range as specified by the 1050 * {@link #minProperty() min} 1051 * and 1052 * {@link #maxProperty() max} 1053 * properties. 1054 * 1055 * @param high The value. 1056 */ 1057 public final void setHighValue( final double high ) 1058 { 1059 if( !m_HighValueProperty.isBound() ) m_HighValueProperty.set( high ); 1060 } // setHighValue() 1061 1062 /** 1063 * Sets the tick label formatter. 1064 * 1065 * @param formatter The 1066 * {@link StringConverter} 1067 * that is used to format the tick labels. 1068 */ 1069 public final void setLabelFormatter( final StringConverter<Number> formatter ) 1070 { 1071 m_TickLabelFormatterProperty.set( formatter ); 1072 } // setLabelFormatter() 1073 1074 /** 1075 * Sets the low value for the range slider, which may or may not be 1076 * clamped to be within the allowable range as specified by the 1077 * {@link #minProperty() min} 1078 * and 1079 * {@link #maxProperty() max} 1080 * properties. 1081 * 1082 * @param low The value. 1083 */ 1084 public final void setLowValue( final double low ) 1085 { 1086 if( !m_LowValueProperty.isBound() ) m_LowValueProperty.set( low ); 1087 } // setLowValue() 1088 1089 /** 1090 * Call this when the low value is changing. 1091 * 1092 * @param flag {@code true} if the low value is changing, {@code false} 1093 * otherwise. 1094 */ 1095 public final void setLowValueChanging( final boolean flag ) { m_LowValueChangingProperty.set( flag ); } 1096 1097 /** 1098 * Sets the unit distance between major tick marks. 1099 * 1100 * @param tickUnit The unit distance. 1101 * 1102 * @see #majorTickUnitProperty() 1103 */ 1104 public final void setMajorTickUnit( final double tickUnit ) 1105 { 1106 m_MajorTickUnitProperty.set( requireValidDoubleArgument( tickUnit, "tickUnit", value -> value > 0.0, $ -> "MajorTickUnit cannot be less than or equal to 0." ) ); 1107 } // setMajorTickUnit() 1108 1109 /** 1110 * Sets the maximum value for this {@code RangeSlider}. 1111 * 1112 * @param max The new value. 1113 */ 1114 public final void setMax( final double max ) { m_MaxProperty.set( max ); } 1115 1116 /** 1117 * Sets the minimum value for this {@code RangeSlider}. 1118 * 1119 * @param min The new value 1120 */ 1121 public final void setMin( final double min ) { m_MinProperty.set( min ); } 1122 1123 /** 1124 * Sets the number of minor ticks to place between any two major ticks. 1125 * 1126 * @param numberOfTicks The number of minor ticks; should be 0 or a 1127 * positive number, out of range values will disable the feature. 1128 * 1129 * @see #minorTickCountProperty() 1130 */ 1131 public final void setMinorTickCount( final int numberOfTicks ) { m_MinorTickCountProperty.set( numberOfTicks ); } 1132 1133 /** 1134 * Sets the orientation of the {@code RangeSlider}. 1135 * 1136 * @param orientation The orientation. 1137 */ 1138 public final void setOrientation( final Orientation orientation ) { m_OrientationProperty.set( orientation ); } 1139 1140 /** 1141 * Sets the flag that controls whether the thumbs will snap to the tick 1142 * marks. 1143 * 1144 * @param flag {@code true} if the thumbs snaps to the tick marks, 1145 * {@code false} if not. 1146 * 1147 * @see #snapToTicksProperty() 1148 */ 1149 public final void setSnapToTicks( final boolean flag ) { m_SnapToTicksProperty.set( flag ); } 1150 1151 /** 1152 * Sets whether labels of tick marks should be shown or not. 1153 * 1154 * @param flag {@code true} if the tick mark labels should be shown, 1155 * {@code false} if not. 1156 */ 1157 public final void setShowTickLabels( final boolean flag ) { m_ShowTickLabelsProperty.set( flag ); } 1158 1159 /** 1160 * Sets whether tick marks should be shown or not. 1161 * 1162 * @param flag {@code true} if the tick marks are shown, {@code false} 1163 * if not. 1164 */ 1165 public final void setShowTickMarks( final boolean flag ) { m_ShowTickMarksProperty.set( flag ); } 1166 1167 /** 1168 * <p>{@summary Returns a reference to the property that holds the flag 1169 * that indicates that the labels for tick marks should be shown.}</p> 1170 * <p>Typically, a 1171 * {@link Skin} 1172 * implementation will only show labels if 1173 * {@link #showTickMarksProperty() showTickMarks} 1174 * is also {@code true}.</p> 1175 * 1176 * @return The property reference. 1177 */ 1178 public final BooleanProperty showTickLabelsProperty() { return m_ShowTickLabelsProperty; } 1179 1180 /** 1181 * <p>{@summary Returns a reference to the property that holds the flag 1182 * that specifies whether the 1183 * {@link Skin} 1184 * implementation should show tick marks.}</p> 1185 * 1186 * @return The property reference. 1187 */ 1188 public final BooleanProperty showTickMarksProperty() { return m_ShowTickMarksProperty; } 1189 1190 /** 1191 * <p>{@summary Returns a reference to the property that holds the flag 1192 * that indicates whether the 1193 * {@linkplain #lowValueProperty() low value}/{@linkplain #highValueProperty() high value} 1194 * thumbs of the {@code RangeSlider} should always be aligned with the 1195 * tick marks.} This is honored even if the tick marks are not shown.</p> 1196 * 1197 * @return The property reference. 1198 */ 1199 public final BooleanProperty snapToTicksProperty() { return m_SnapToTicksProperty; } 1200 1201 /** 1202 * Aligns the given value with the nearest tick mark value. 1203 * 1204 * @param value The value. 1205 * @return The adjusted value. 1206 */ 1207 private final double snapValueToTicks( final double value) 1208 { 1209 var d1 = value; 1210 if( isSnapToTicks() ) 1211 { 1212 final double d2; 1213 if( getMinorTickCount() != 0 ) 1214 { 1215 d2 = getMajorTickUnit() / (double) (Integer.max( getMinorTickCount(), 0 ) + 1 ); 1216 } 1217 else 1218 { 1219 d2 = getMajorTickUnit(); 1220 } 1221 @SuppressWarnings( {"LocalVariableNamingConvention", "NumericCastThatLosesPrecision"} ) 1222 final var i = (int) ((d1 - getMin()) / d2); 1223 final var d3 = (double) i * d2 + getMin(); 1224 final var d4 = (double) (i + 1) * d2 + getMin(); 1225 d1 = nearest( d3, d1, d4 ); 1226 } 1227 final var retValue = clamp( getMin(), d1, getMax() ); 1228 1229 //---* Done *---------------------------------------------------------- 1230 return retValue; 1231 } // snapValueToTicks() 1232} 1233// class RangeSlider 1234 1235/* 1236 * End of file 1237 */