javajavafxcontrolsfx

ControlsFX RangeSlider is laggy when orientation is vertical


When setOrientation is set to Orientation.HORIZONTAL the movement of the slider is very smooth, but when setOrientation is set to Orientation.VERTICAL the movement is laggy.

Below is minimal reproduction:

package org.example;

import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.RangeSlider;

public class App extends Application {

    @Override
    public void start(Stage stage) {

        RangeSlider rangeSliderVertical = new RangeSlider();
        rangeSliderVertical.setOrientation(Orientation.VERTICAL);
        rangeSliderVertical.setMinHeight(500);
        rangeSliderVertical.setMax(100);
        rangeSliderVertical.setMin(0);
        rangeSliderVertical.setHighValue(100);
        rangeSliderVertical.setLowValue(0);

        RangeSlider rangeSliderHorizontal = new RangeSlider();
        rangeSliderHorizontal.setMinWidth(500);
        rangeSliderHorizontal.setMax(100);
        rangeSliderHorizontal.setMin(0);
        rangeSliderHorizontal.setHighValue(100);
        rangeSliderHorizontal.setLowValue(0);
        rangeSliderHorizontal.setOrientation(Orientation.HORIZONTAL);

        FlowPane vBoxWithSliders = new FlowPane();
        VBox.setVgrow(vBoxWithSliders, Priority.ALWAYS);
        HBox.setHgrow(vBoxWithSliders, Priority.ALWAYS);

        vBoxWithSliders.setAlignment(Pos.CENTER);
        vBoxWithSliders.getChildren().addAll(rangeSliderVertical, rangeSliderHorizontal);

        var scene = new Scene(vBoxWithSliders, 600, 600);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}

Any hints on how to achieve smooth movement when RangeSlider is vertical?

enter image description here


Solution

  • This is a bug in the RangeSliderSkin. The existing code includes the following listeners on the lowValueProperty() and highValueProperty():

        registerChangeListener(rangeSlider.lowValueProperty(), e -> {
            positionLowThumb();
            rangeBar.resizeRelocate(rangeStart, rangeBar.getLayoutY(), 
            rangeEnd - rangeStart, rangeBar.getHeight());
        });
        registerChangeListener(rangeSlider.highValueProperty(), e -> {
            positionHighThumb();
            rangeBar.resize(rangeEnd-rangeStart, rangeBar.getHeight());
        });
    

    See source code at https://github.com/controlsfx/controlsfx/blob/master/controlsfx/src/main/java/impl/org/controlsfx/skin/RangeSliderSkin.java

    The calls to rangeBar.resizeRelocate() and rangeBar.resize() are making the assumption that the range slider has a horizontal orientation. (You can see this as only the layoutX and/or width of the bar can change. Both layoutY and height are set to their current values.) The incorrect layout of the range bar is rectified before the range bar itself is redrawn in the layoutChildren() method. However, I think under some circumstances additional drag events can be processed before layoutChildren() is invoked, causing incorrect calculations on the positions of the thumbs.

    I have a fork of the ControlsFX repository at https://github.com/james-d/controlsfx/tree/master which fixes this with

        registerChangeListener(rangeSlider.lowValueProperty(), e -> {
            positionLowThumb();
            if (isHorizontal()) {
                rangeBar.resizeRelocate(rangeStart, rangeBar.getLayoutY(),
                        rangeEnd - rangeStart, rangeBar.getHeight());
            } else {
                rangeBar.resize(rangeBar.getWidth(), rangeEnd - rangeStart);
            }
        });
        registerChangeListener(rangeSlider.highValueProperty(), e -> {
            positionHighThumb();
            if (isHorizontal()) {
                rangeBar.resize(rangeEnd - rangeStart, rangeBar.getHeight());
            } else {
                rangeBar.resizeRelocate(rangeBar.getLayoutX(), rangeStart,
                        rangeBar.getWidth(), rangeEnd - rangeStart);
            }
        });
    

    and have submitted this as Issue 1521, as well as a pull request.

    Feel free to clone and build my fork as a temporary measure until (and unless) the pull request is accepted.