javafx.scene.control.Slider Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.control;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.WritableValue;
import javafx.geometry.Orientation;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.util.StringConverter;
import com.sun.javafx.util.Utils;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableIntegerProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.converter.BooleanConverter;
import javafx.css.converter.EnumConverter;
import javafx.css.converter.SizeConverter;
import javafx.scene.control.skin.SliderSkin;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
/**
* The Slider Control is used to display a continuous or discrete range of
* valid numeric choices and allows the user to interact with the control. It is
* typically represented visually as having a "track" and a "knob" or "thumb"
* which is dragged within the track. The Slider can optionally show tick marks
* and labels indicating the different slider position values.
*
* The three fundamental variables of the slider are min
,
* max
, and value
. The value
should always
* be a number within the range defined by min
and
* max
. min
should always be less than or equal to
* max
(although a slider whose min
and
* max
are equal is a degenerate case that makes no sense).
* min
defaults to 0, whereas max
defaults to 100.
*
* This first example creates a slider whose range, or span, goes from 0 to 1,
* and whose value defaults to .5:
*
*
Slider slider = new Slider(0, 1, 0.5);
*
*
* This next example shows a slider with customized tick marks and tick mark
* labels, which also spans from 0 to 1:
*
*
Slider slider = new Slider(0, 1, 0.5);
* slider.setShowTickMarks(true);
* slider.setShowTickLabels(true);
* slider.setMajorTickUnit(0.25f);
* slider.setBlockIncrement(0.1f);
*
*
*
* @since JavaFX 2.0
*/
public class Slider extends Control {
/**
* Creates a default Slider instance.
*/
public Slider() {
initialize();
}
/**
* Constructs a Slider control with the specified slider min, max and current value values.
* @param min Slider minimum value
* @param max Slider maximum value
* @param value Slider current value
*/
public Slider(double min, double max, double value) {
setMax(max);
setMin(min);
setValue(value);
adjustValues();
initialize();
}
private void initialize() {
//Initialize the style class to be 'slider'.
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
setAccessibleRole(AccessibleRole.SLIDER);
pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, true);
}
/**
* The maximum value represented by this Slider. This must be a
* value greater than {@link #minProperty() min}.
*/
private DoubleProperty max;
public final void setMax(double value) {
maxProperty().set(value);
}
public final double getMax() {
return max == null ? 100 : max.get();
}
public final DoubleProperty maxProperty() {
if (max == null) {
max = new DoublePropertyBase(100) {
@Override protected void invalidated() {
if (get() < getMin()) {
setMin(get());
}
adjustValues();
notifyAccessibleAttributeChanged(AccessibleAttribute.MAX_VALUE);
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "max";
}
};
}
return max;
}
/**
* The minimum value represented by this Slider. This must be a
* value less than {@link #maxProperty() max}.
*/
private DoubleProperty min;
public final void setMin(double value) {
minProperty().set(value);
}
public final double getMin() {
return min == null ? 0 : min.get();
}
public final DoubleProperty minProperty() {
if (min == null) {
min = new DoublePropertyBase(0) {
@Override protected void invalidated() {
if (get() > getMax()) {
setMax(get());
}
adjustValues();
notifyAccessibleAttributeChanged(AccessibleAttribute.MIN_VALUE);
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "min";
}
};
}
return min;
}
/**
* The current value represented by this Slider. This value must
* always be between {@link #minProperty() min} and {@link #maxProperty() max},
* inclusive. If it is ever out of bounds either due to {@code min} or
* {@code max} changing or due to itself being changed, then it will
* be clamped to always remain valid.
*/
private DoubleProperty value;
public final void setValue(double value) {
if (!valueProperty().isBound()) valueProperty().set(value);
}
public final double getValue() {
return value == null ? 0 : value.get();
}
public final DoubleProperty valueProperty() {
if (value == null) {
value = new DoublePropertyBase(0) {
@Override protected void invalidated() {
adjustValues();
notifyAccessibleAttributeChanged(AccessibleAttribute.VALUE);
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "value";
}
};
}
return value;
}
/**
* When true, indicates the current value of this Slider is changing.
* It provides notification that the value is changing. Once the value is
* computed, it is reset back to false.
*/
private BooleanProperty valueChanging;
public final void setValueChanging(boolean value) {
valueChangingProperty().set(value);
}
public final boolean isValueChanging() {
return valueChanging == null ? false : valueChanging.get();
}
public final BooleanProperty valueChangingProperty() {
if (valueChanging == null) {
valueChanging = new SimpleBooleanProperty(this, "valueChanging", false);
}
return valueChanging;
}
// /**
// * The {@code span} is the distance, or quantity, between min and max value.
// * This will be strictly non-negative, since both {@code min} and
// * {@code max} are forced to maintain a proper relationship.
// */
// // public def span = bind max - min;
/**
* The orientation of the {@code Slider} can either be horizontal
* or vertical.
*/
private ObjectProperty orientation;
public final void setOrientation(Orientation value) {
orientationProperty().set(value);
}
public final Orientation getOrientation() {
return orientation == null ? Orientation.HORIZONTAL : orientation.get();
}
public final ObjectProperty orientationProperty() {
if (orientation == null) {
orientation = new StyleableObjectProperty(Orientation.HORIZONTAL) {
@Override protected void invalidated() {
final boolean vertical = (get() == Orientation.VERTICAL);
pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE, vertical);
pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, !vertical);
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.ORIENTATION;
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "orientation";
}
};
}
return orientation;
}
/**
* Indicates that the labels for tick marks should be shown. Typically a
* {@link Skin} implementation will only show labels if
* {@link #showTickMarksProperty() showTickMarks} is also true.
*/
private BooleanProperty showTickLabels;
public final void setShowTickLabels(boolean value) {
showTickLabelsProperty().set(value);
}
public final boolean isShowTickLabels() {
return showTickLabels == null ? false : showTickLabels.get();
}
public final BooleanProperty showTickLabelsProperty() {
if (showTickLabels == null) {
showTickLabels = new StyleableBooleanProperty(false) {
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.SHOW_TICK_LABELS;
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "showTickLabels";
}
};
}
return showTickLabels;
}
/**
* Specifies whether the {@link Skin} implementation should show tick marks.
*/
private BooleanProperty showTickMarks;
public final void setShowTickMarks(boolean value) {
showTickMarksProperty().set(value);
}
public final boolean isShowTickMarks() {
return showTickMarks == null ? false : showTickMarks.get();
}
public final BooleanProperty showTickMarksProperty() {
if (showTickMarks == null) {
showTickMarks = new StyleableBooleanProperty(false) {
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.SHOW_TICK_MARKS;
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "showTickMarks";
}
};
}
return showTickMarks;
}
/**
* The unit distance between major tick marks. For example, if
* the {@link #minProperty() min} is 0 and the {@link #maxProperty() max} is 100 and the
* {@link #majorTickUnitProperty() majorTickUnit} is 25, then there would be 5 tick marks: one at
* position 0, one at position 25, one at position 50, one at position
* 75, and a final one at position 100.
*
* This value should be positive and should be a value less than the
* span. Out of range values are essentially the same as disabling
* tick marks.
*/
private DoubleProperty majorTickUnit;
public final void setMajorTickUnit(double value) {
if (value <= 0) {
throw new IllegalArgumentException("MajorTickUnit cannot be less than or equal to 0.");
}
majorTickUnitProperty().set(value);
}
public final double getMajorTickUnit() {
return majorTickUnit == null ? 25 : majorTickUnit.get();
}
public final DoubleProperty majorTickUnitProperty() {
if (majorTickUnit == null) {
majorTickUnit = new StyleableDoubleProperty(25) {
@Override
public void invalidated() {
if (get() <= 0) {
throw new IllegalArgumentException("MajorTickUnit cannot be less than or equal to 0.");
}
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.MAJOR_TICK_UNIT;
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "majorTickUnit";
}
};
}
return majorTickUnit;
}
/**
* The number of minor ticks to place between any two major ticks. This
* number should be positive or zero. Out of range values will disable
* disable minor ticks, as will a value of zero.
*/
private IntegerProperty minorTickCount;
public final void setMinorTickCount(int value) {
minorTickCountProperty().set(value);
}
public final int getMinorTickCount() {
return minorTickCount == null ? 3 : minorTickCount.get();
}
public final IntegerProperty minorTickCountProperty() {
if (minorTickCount == null) {
minorTickCount = new StyleableIntegerProperty(3) {
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.MINOR_TICK_COUNT;
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "minorTickCount";
}
};
}
return minorTickCount;
}
/**
* Indicates whether the {@link #valueProperty() value} of the {@code Slider} should always
* be aligned with the tick marks. This is honored even if the tick marks
* are not shown.
*/
private BooleanProperty snapToTicks;
public final void setSnapToTicks(boolean value) {
snapToTicksProperty().set(value);
}
public final boolean isSnapToTicks() {
return snapToTicks == null ? false : snapToTicks.get();
}
public final BooleanProperty snapToTicksProperty() {
if (snapToTicks == null) {
snapToTicks = new StyleableBooleanProperty(false) {
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.SNAP_TO_TICKS;
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "snapToTicks";
}
};
}
return snapToTicks;
}
/**
* A function for formatting the label for a major tick. The number
* representing the major tick will be passed to the function. If this
* function is not specified, then a default function will be used by
* the {@link Skin} implementation.
*/
private ObjectProperty> labelFormatter;
public final void setLabelFormatter(StringConverter value) {
labelFormatterProperty().set(value);
}
public final StringConverter getLabelFormatter() {
return labelFormatter == null ? null : labelFormatter.get();
}
public final ObjectProperty> labelFormatterProperty() {
if (labelFormatter == null) {
labelFormatter = new SimpleObjectProperty<>(this, "labelFormatter");
}
return labelFormatter;
}
/**
* The amount by which to adjust the slider if the track of the slider is
* clicked. This is used when manipulating the slider position using keys. If
* {@link #snapToTicksProperty() snapToTicks} is true then the nearest tick mark to the adjusted
* value will be used.
*/
private DoubleProperty blockIncrement;
public final void setBlockIncrement(double value) {
blockIncrementProperty().set(value);
}
public final double getBlockIncrement() {
return blockIncrement == null ? 10 : blockIncrement.get();
}
public final DoubleProperty blockIncrementProperty() {
if (blockIncrement == null) {
blockIncrement = new StyleableDoubleProperty(10) {
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.BLOCK_INCREMENT;
}
@Override
public Object getBean() {
return Slider.this;
}
@Override
public String getName() {
return "blockIncrement";
}
};
}
return blockIncrement;
}
/**
* Adjusts {@link #valueProperty() value} to match newValue
. The
* value
is the actual amount between the
* {@link #minProperty() min} and {@link #maxProperty() max}. This function
* also takes into account {@link #snapToTicksProperty() snapToTicks}, which
* is the main difference between adjustValue and setValue. It also ensures
* that the value is some valid number between min and max.
*
* Note: This function is intended to be used by experts, primarily
* by those implementing new Skins or Behaviors. It is not common
* for developers or designers to access this function directly.
* @param newValue the new adjusted value
*/
public void adjustValue(double newValue) {
// figure out the "value" associated with the specified position
final double _min = getMin();
final double _max = getMax();
if (_max <= _min) return;
newValue = newValue < _min ? _min : newValue;
newValue = newValue > _max ? _max : newValue;
setValue(snapValueToTicks(newValue));
}
/**
* Increments the value by {@link #blockIncrementProperty() blockIncrement}, bounded by max. If the
* max is less than or equal to the min, then this method does nothing.
*/
public void increment() {
adjustValue(getValue() + getBlockIncrement());
}
/**
* Decrements the value by {@link #blockIncrementProperty() blockIncrement}, bounded by max. If the
* max is less than or equal to the min, then this method does nothing.
*/
public void decrement() {
adjustValue(getValue() - getBlockIncrement());
}
/**
* Ensures that min is always < max, that value is always
* somewhere between the two, and that if snapToTicks is set then the
* value will always be set to align with a tick mark.
*/
private void adjustValues() {
if ((getValue() < getMin() || getValue() > getMax()) /* && !isReadOnly(value)*/)
setValue(Utils.clamp(getMin(), getValue(), getMax()));
}
/**
* Utility function which, given the specified value, will position it
* either aligned with a tick, or simply clamp between min & max value,
* depending on whether snapToTicks is set.
*
* Note: This function is intended to be used by experts, primarily
* by those implementing new Skins or Behaviors. It is not common
* for developers or designers to access this function directly.
*/
private double snapValueToTicks(double val) {
double v = val;
if (isSnapToTicks()) {
double tickSpacing = 0;
// compute the nearest tick to this value
if (getMinorTickCount() != 0) {
tickSpacing = getMajorTickUnit() / (Math.max(getMinorTickCount(),0)+1);
} else {
tickSpacing = getMajorTickUnit();
}
int prevTick = (int)((v - getMin())/ tickSpacing);
double prevTickValue = (prevTick) * tickSpacing + getMin();
double nextTickValue = (prevTick + 1) * tickSpacing + getMin();
v = Utils.nearest(prevTickValue, v, nextTickValue);
}
return Utils.clamp(getMin(), v, getMax());
}
/** {@inheritDoc} */
@Override protected Skin> createDefaultSkin() {
return new SliderSkin(this);
}
/* *************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
private static final String DEFAULT_STYLE_CLASS = "slider";
private static class StyleableProperties {
private static final CssMetaData BLOCK_INCREMENT =
new CssMetaData<>("-fx-block-increment",
SizeConverter.getInstance(), 10.0) {
@Override
public boolean isSettable(Slider n) {
return n.blockIncrement == null || !n.blockIncrement.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Slider n) {
return (StyleableProperty)n.blockIncrementProperty();
}
};
private static final CssMetaData SHOW_TICK_LABELS =
new CssMetaData<>("-fx-show-tick-labels",
BooleanConverter.getInstance(), Boolean.FALSE) {
@Override
public boolean isSettable(Slider n) {
return n.showTickLabels == null || !n.showTickLabels.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Slider n) {
return (StyleableProperty)n.showTickLabelsProperty();
}
};
private static final CssMetaData SHOW_TICK_MARKS =
new CssMetaData<>("-fx-show-tick-marks",
BooleanConverter.getInstance(), Boolean.FALSE) {
@Override
public boolean isSettable(Slider n) {
return n.showTickMarks == null || !n.showTickMarks.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Slider n) {
return (StyleableProperty)n.showTickMarksProperty();
}
};
private static final CssMetaData SNAP_TO_TICKS =
new CssMetaData<>("-fx-snap-to-ticks",
BooleanConverter.getInstance(), Boolean.FALSE) {
@Override
public boolean isSettable(Slider n) {
return n.snapToTicks == null || !n.snapToTicks.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Slider n) {
return (StyleableProperty)n.snapToTicksProperty();
}
};
private static final CssMetaData MAJOR_TICK_UNIT =
new CssMetaData<>("-fx-major-tick-unit",
SizeConverter.getInstance(), 25.0) {
@Override
public boolean isSettable(Slider n) {
return n.majorTickUnit == null || !n.majorTickUnit.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Slider n) {
return (StyleableProperty)n.majorTickUnitProperty();
}
};
private static final CssMetaData MINOR_TICK_COUNT =
new CssMetaData<>("-fx-minor-tick-count",
SizeConverter.getInstance(), 3.0) {
@Override
public boolean isSettable(Slider n) {
return n.minorTickCount == null || !n.minorTickCount.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Slider n) {
return (StyleableProperty)n.minorTickCountProperty();
}
};
private static final CssMetaData ORIENTATION =
new CssMetaData<>("-fx-orientation",
new EnumConverter<>(Orientation.class),
Orientation.HORIZONTAL) {
@Override
public Orientation getInitialValue(Slider node) {
// A vertical Slider should remain vertical
return node.getOrientation();
}
@Override
public boolean isSettable(Slider n) {
return n.orientation == null || !n.orientation.isBound();
}
@Override
public StyleableProperty getStyleableProperty(Slider n) {
return (StyleableProperty)(WritableValue)n.orientationProperty();
}
};
private static final List> STYLEABLES;
static {
final List> styleables =
new ArrayList<>(Control.getClassCssMetaData());
styleables.add(BLOCK_INCREMENT);
styleables.add(SHOW_TICK_LABELS);
styleables.add(SHOW_TICK_MARKS);
styleables.add(SNAP_TO_TICKS);
styleables.add(MAJOR_TICK_UNIT);
styleables.add(MINOR_TICK_COUNT);
styleables.add(ORIENTATION);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
/**
* Gets the {@code CssMetaData} associated with this class, which may include the
* {@code CssMetaData} of its superclasses.
* @return the {@code CssMetaData}
* @since JavaFX 8.0
*/
public static List> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
/**
* {@inheritDoc}
* @since JavaFX 8.0
*/
@Override protected List> getControlCssMetaData() {
return getClassCssMetaData();
}
private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE =
PseudoClass.getPseudoClass("vertical");
private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE =
PseudoClass.getPseudoClass("horizontal");
/* *************************************************************************
* *
* Accessibility handling *
* *
**************************************************************************/
/** {@inheritDoc} */
@Override
public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
switch (attribute) {
case VALUE: return getValue();
case MAX_VALUE: return getMax();
case MIN_VALUE: return getMin();
case ORIENTATION: return getOrientation();
default: return super.queryAccessibleAttribute(attribute, parameters);
}
}
/** {@inheritDoc} */
@Override
public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
switch (action) {
case INCREMENT: increment(); break;
case DECREMENT: decrement(); break;
case SET_VALUE: {
Double value = (Double) parameters[0];
if (value != null) setValue(value);
break;
}
default: super.executeAccessibleAction(action, parameters);
}
}
}