Pārlūkot izejas kodu

:boom: Completed MFXSlider

:recycle: Renamed demo MFXResourcesLoader to MFXDemoResourcesLoader

MFXButton,
MFXButtonSkin:
:bug: Fixed button not accepting effects (Issue #66)

MFXComboBoxSkin:
:bug: Fixed listview not styling when using a custom css (Issue #57)

MFXSlider:
:recycle: Set decimal precision to 0 by default
:recycle: Fixed and improved thumb behavior and popup layout
:sparkles: Added new pseudo classes for more css customization

MFXSliderSkin:
:recycle: Popup layout/position is now managed by a separate internal class
:recycle: LayoutData, ignore slider decimal precision and compute all positions with decimal precision of 2 for more accuracy
:recycle: Clamp the zero position instead of adding 1

MFXTableViewSkin:
:recycle: Added css id for "rowsPerPageLabel" and "shownRowsLabel" (Enhancement for issue #64)

NodeUtils:
:recycle: waitForScene() and waitForSkin(), no need to use Callable<V>, use Runnable instead

.gitignore
TestDemo,
TestDemo.css:
Those are what I use to test new changes, removed from gitignore as I'm tired of losing them when I clone the repo on new machines. Please ignore the s**t I write there
palexdev 4 gadi atpakaļ
vecāks
revīzija
88020dc8fc
17 mainītis faili ar 423 papildinājumiem un 153 dzēšanām
  1. 0 2
      .gitignore
  2. 1 1
      demo/src/main/java/io/github/palexdev/materialfx/demo/Demo.java
  3. 14 4
      demo/src/main/java/io/github/palexdev/materialfx/demo/MFXDemoResourcesLoader.java
  4. 146 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/TestDemo.java
  5. 2 2
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DatePickersDemoController.java
  6. 20 20
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DemoController.java
  7. 2 2
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java
  8. 11 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TestDemo.css
  9. 7 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java
  10. 73 18
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java
  11. 2 14
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXButtonSkin.java
  12. 4 0
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java
  13. 116 68
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXSliderSkin.java
  14. 9 7
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java
  15. 6 7
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/NodeUtils.java
  16. 1 4
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/ScrollUtils.java
  17. 9 3
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXSlider.css

+ 0 - 2
.gitignore

@@ -10,7 +10,5 @@ out/
 !gradle-wrapper.jar
 
 # Others
-demo/src/main/java/io/github/palexdev/materialfx/demo/TestDemo.java
-demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TestDemo.css
 materialfx/src/test
 demo/scenicView.properties

+ 1 - 1
demo/src/main/java/io/github/palexdev/materialfx/demo/Demo.java

@@ -38,7 +38,7 @@ public class Demo extends Application {
     public void start(Stage primaryStage) throws IOException {
         CSSFX.start();
 
-        FXMLLoader fxmlLoader = new FXMLLoader(MFXResourcesLoader.load("Demo.fxml"));
+        FXMLLoader fxmlLoader = new FXMLLoader(MFXDemoResourcesLoader.loadURL("Demo.fxml"));
         fxmlLoader.setControllerFactory(controller -> new DemoController(primaryStage, getHostServices()));
         StackPane demoPane = fxmlLoader.load();
 

+ 14 - 4
demo/src/main/java/io/github/palexdev/materialfx/demo/MFXResourcesLoader.java → demo/src/main/java/io/github/palexdev/materialfx/demo/MFXDemoResourcesLoader.java

@@ -18,18 +18,28 @@
 
 package io.github.palexdev.materialfx.demo;
 
+import java.io.InputStream;
 import java.net.URL;
 
 /**
  * Utility class which manages the access to this project's assets.
  * Helps keeping the assets files structure organized.
  */
-public class MFXResourcesLoader {
+public class MFXDemoResourcesLoader {
 
-    private MFXResourcesLoader() {
+    private MFXDemoResourcesLoader() {
     }
 
-    public static URL load(String path) {
-        return MFXResourcesLoader.class.getResource(path);
+    public static URL loadURL(String path) {
+        return MFXDemoResourcesLoader.class.getResource(path);
     }
+
+    public static String load(String path) {
+        return loadURL(path).toString();
+    }
+
+    public static InputStream loadStream(String name) {
+        return MFXDemoResourcesLoader.class.getResourceAsStream(name);
+    }
+
 }

+ 146 - 0
demo/src/main/java/io/github/palexdev/materialfx/demo/TestDemo.java

@@ -0,0 +1,146 @@
+package io.github.palexdev.materialfx.demo;
+
+import io.github.palexdev.materialfx.beans.properties.ResettableDoubleProperty;
+import io.github.palexdev.materialfx.controls.MFXButton;
+import io.github.palexdev.materialfx.controls.MFXIconWrapper;
+import io.github.palexdev.materialfx.controls.MFXSlider;
+import io.github.palexdev.materialfx.controls.MFXTableView;
+import io.github.palexdev.materialfx.controls.cell.MFXTableColumn;
+import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
+import io.github.palexdev.materialfx.controls.enums.SliderEnum;
+import io.github.palexdev.materialfx.controls.enums.SliderEnum.SliderPopupSide;
+import io.github.palexdev.materialfx.demo.model.FilterablePerson;
+import io.github.palexdev.materialfx.font.FontResources;
+import io.github.palexdev.materialfx.font.MFXFontIcon;
+import io.github.palexdev.materialfx.utils.ColorUtils;
+import io.github.palexdev.materialfx.utils.NumberUtils;
+import javafx.application.Application;
+import javafx.beans.binding.Bindings;
+import javafx.geometry.Orientation;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+
+import java.util.Random;
+
+public class TestDemo extends Application {
+    private final Random random = new Random(System.currentTimeMillis());
+
+    @Override
+    public void start(Stage primaryStage) {
+        VBox box = new VBox(100);
+        box.setAlignment(Pos.CENTER);
+
+        MFXSlider slider = new MFXSlider();
+        slider.setSliderMode(SliderEnum.SliderMode.SNAP_TO_TICKS);
+        //slider.setOrientation(Orientation.VERTICAL);
+        //slider.setPopupSide(SliderPopupSide.OTHER_SIDE);
+
+        slider.setBidirectional(false);
+        slider.setPrefWidth(200);
+        slider.setValue(25);
+        slider.setMin(-50);
+        slider.setTickUnit(25);
+        slider.setUnitIncrement(1);
+        slider.setAlternativeUnitIncrement(5);
+        slider.setShowTicksAtEdges(true);
+        slider.setShowMajorTicks(true);
+        slider.setShowMinorTicks(true);
+
+        slider.setBidirectional(false);
+
+        HBox bbox = new HBox(20);
+        bbox.setAlignment(Pos.CENTER);
+
+        ResettableDoubleProperty property = new ResettableDoubleProperty(0.0, 10.5);
+        property.setFireChangeOnReset(true);
+
+        MFXButton b1 = new MFXButton("Change Thumb");
+        MFXButton b2 = new MFXButton("Change Popup");
+        MFXButton b3 = new MFXButton("Change Min");
+        MFXButton b4 = new MFXButton("Change Max");
+        MFXButton b5 = new MFXButton("Change Value");
+
+        b1.setOnAction(event -> slider.setThumbSupplier(() -> {
+            MFXButton button = new MFXButton("Thumb");
+            button.setPrefSize(40, 30);
+            return button;
+        }));
+        b2.setOnAction(event -> slider.setPopupSupplier(() -> {
+            Label label = new Label();
+            label.textProperty().bind(Bindings.createStringBinding(
+                    () -> NumberUtils.formatToString(slider.getValue(), slider.getDecimalPrecision()),
+                    slider.valueProperty()
+            ));
+            label.setStyle("-fx-border-color: orange");
+            return label;
+        }));
+        b3.setOnAction(event -> {
+            slider.setOrientation(slider.getOrientation() == Orientation.HORIZONTAL ? Orientation.VERTICAL : Orientation.HORIZONTAL);
+        });
+        b4.setOnAction(event -> {
+            slider.setPopupSide(slider.getPopupSide() == SliderPopupSide.DEFAULT ? SliderPopupSide.OTHER_SIDE : SliderPopupSide.DEFAULT);
+        });
+        b5.setOnAction(event -> {
+            property.reset();
+        });
+
+        property.addListener((observable, oldValue, newValue) -> System.out.println(newValue));
+
+        bbox.getChildren().addAll(b1, b2, b3, b4, b5);
+
+        box.getChildren().addAll(slider, bbox);
+        box.getStylesheets().add(MFXDemoResourcesLoader.load("css/TestDemo.css"));
+
+        Scene scene = new Scene(box, 800, 600);
+        primaryStage.setScene(scene);
+        primaryStage.show();
+
+        //ScenicView.show(scene);
+    }
+
+    public static void main(String[] args) {
+        launch(args);
+    }
+
+    private static boolean isInvalidCharacter(char c) {
+        if (c == 0x7F) return true;
+        if (c == 0xA) return true;
+        if (c == 0x9) return true;
+        return c < 0x20;
+    }
+
+    public MFXIconWrapper getRandomIcon(double size) {
+        FontResources[] resources = FontResources.values();
+        String desc = resources[random.nextInt(resources.length)].getDescription();
+        return new MFXIconWrapper(new MFXFontIcon(desc, size, ColorUtils.getRandomColor()), size * 1.5);
+    }
+
+    private void setupTable(MFXTableView<FilterablePerson> tableView) {
+        MFXTableColumn<FilterablePerson> firstName = new MFXTableColumn<>("FName");
+        MFXTableColumn<FilterablePerson> lastName = new MFXTableColumn<>("LName");
+        MFXTableColumn<FilterablePerson> address = new MFXTableColumn<>("Address");
+        MFXTableColumn<FilterablePerson> age = new MFXTableColumn<>("age");
+
+        firstName.setRowCellFunction(person -> new MFXTableRowCell(person.firstNameProperty()));
+        lastName.setRowCellFunction(person -> new MFXTableRowCell(person.lastNameProperty()));
+        address.setRowCellFunction(person -> new MFXTableRowCell(person.addressProperty()));
+        age.setRowCellFunction(person -> new MFXTableRowCell(person.ageProperty().asString()));
+
+        tableView.getTableColumns().addAll(firstName, lastName, address, age);
+        //tableView.setItems(people);
+    }
+
+    public String randStr() {
+        int leftLimit = 97; // letter 'a'
+        int rightLimit = 122; // letter 'z'
+        int targetStringLength = 30;
+        return random.ints(leftLimit, rightLimit + 1)
+                .limit(targetStringLength)
+                .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
+                .toString();
+    }
+}

+ 2 - 2
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DatePickersDemoController.java

@@ -19,7 +19,7 @@
 package io.github.palexdev.materialfx.demo.controllers;
 
 import io.github.palexdev.materialfx.controls.MFXDatePicker;
-import io.github.palexdev.materialfx.demo.MFXResourcesLoader;
+import io.github.palexdev.materialfx.demo.MFXDemoResourcesLoader;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
 import javafx.geometry.Insets;
@@ -39,7 +39,7 @@ public class DatePickersDemoController implements Initializable {
 
     @Override
     public void initialize(URL location, ResourceBundle resources) {
-        String css = MFXResourcesLoader.load("css/CustomDatePicker.css").toString();
+        String css = MFXDemoResourcesLoader.load("css/CustomDatePicker.css");
         customPicker.getContent().getStylesheets().add(css);
 
         MFXDatePicker initialized = new MFXDatePicker(LocalDate.now());

+ 20 - 20
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DemoController.java

@@ -21,7 +21,7 @@ package io.github.palexdev.materialfx.demo.controllers;
 import io.github.palexdev.materialfx.beans.MFXLoaderBean.Builder;
 import io.github.palexdev.materialfx.controls.*;
 import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
-import io.github.palexdev.materialfx.demo.MFXResourcesLoader;
+import io.github.palexdev.materialfx.demo.MFXDemoResourcesLoader;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.ScrollUtils;
@@ -137,24 +137,24 @@ public class DemoController implements Initializable {
 
         // VLoader
         vLoader.setContentPane(contentPane);
-        vLoader.addItem("BUTTONS", Builder.build(new MFXRectangleToggleNode("BUTTONS"), MFXResourcesLoader.load("ButtonsDemo.fxml")).setDefaultRoot(true));
-        vLoader.addItem("CHECKBOXES", Builder.build(new MFXRectangleToggleNode("CHECKBOXES"), MFXResourcesLoader.load("CheckBoxesDemo.fxml")));
-        vLoader.addItem("COMBOBOXES", Builder.build(new MFXRectangleToggleNode("COMBOBOXES"), MFXResourcesLoader.load("ComboBoxesDemo.fxml")));
-        vLoader.addItem("DATEPICKERS", Builder.build(new MFXRectangleToggleNode("DATEPICKERS"), MFXResourcesLoader.load("DatePickersDemo.fxml")));
-        vLoader.addItem("DIALOGS", Builder.build(new MFXRectangleToggleNode("DIALOGS"), MFXResourcesLoader.load("DialogsDemo.fxml")).setControllerFactory(controller -> new DialogsController(demoPane)));
-        vLoader.addItem("LABELS", Builder.build(new MFXRectangleToggleNode("LABELS"), MFXResourcesLoader.load("LabelsDemo.fxml")));
-        vLoader.addItem("LISTVIEWS", Builder.build(new MFXRectangleToggleNode("LISTVIEWS"), MFXResourcesLoader.load("ListViewsDemo.fxml")));
-        vLoader.addItem("NOTIFICATIONS", Builder.build(new MFXRectangleToggleNode("NOTIFICATIONS"), MFXResourcesLoader.load("NotificationsDemo.fxml")));
-        vLoader.addItem("PROGRESS_BARS", Builder.build(new MFXRectangleToggleNode("PROGRESS BARS"), MFXResourcesLoader.load("ProgressBarsDemo.fxml")));
-        vLoader.addItem("PROGRESS_SPINNERS", Builder.build(new MFXRectangleToggleNode("PROGRESS SPINNERS"), MFXResourcesLoader.load("ProgressSpinnersDemo.fxml")));
-        vLoader.addItem("RADIOBUTTONS", Builder.build(new MFXRectangleToggleNode("RADIOBUTTONS"), MFXResourcesLoader.load("RadioButtonsDemo.fxml")));
-        vLoader.addItem("SCROLLPANES", Builder.build(new MFXRectangleToggleNode("SCROLLPANES"), MFXResourcesLoader.load("ScrollPanesDemo.fxml")));
-        vLoader.addItem("STEPPER", Builder.build(new MFXRectangleToggleNode("STEPPER"), MFXResourcesLoader.load("StepperDemo.fxml")));
-        vLoader.addItem("TABLEVIEWS", Builder.build(new MFXRectangleToggleNode("TABLEVIEWS"), MFXResourcesLoader.load("TableViewsDemo.fxml")));
-        vLoader.addItem("TEXTFIELDS", Builder.build(new MFXRectangleToggleNode("TEXTFIELDS"), MFXResourcesLoader.load("TextFieldsDemo.fxml")));
-        vLoader.addItem("TOGGLES", Builder.build(new MFXRectangleToggleNode("TOGGLES"), MFXResourcesLoader.load("ToggleButtonsDemo.fxml")));
-        vLoader.addItem("TREEVIEWS", Builder.build(new MFXRectangleToggleNode("TREEVIEWS"), MFXResourcesLoader.load("TreeViewsDemo.fxml")));
-        vLoader.addItem("FONTRESOURCES", Builder.build(new MFXRectangleToggleNode("FONTRESOURCES"), MFXResourcesLoader.load("FontResourcesDemo.fxml")));
+        vLoader.addItem("BUTTONS", Builder.build(new MFXRectangleToggleNode("BUTTONS"), MFXDemoResourcesLoader.loadURL("ButtonsDemo.fxml")).setDefaultRoot(true));
+        vLoader.addItem("CHECKBOXES", Builder.build(new MFXRectangleToggleNode("CHECKBOXES"), MFXDemoResourcesLoader.loadURL("CheckBoxesDemo.fxml")));
+        vLoader.addItem("COMBOBOXES", Builder.build(new MFXRectangleToggleNode("COMBOBOXES"), MFXDemoResourcesLoader.loadURL("ComboBoxesDemo.fxml")));
+        vLoader.addItem("DATEPICKERS", Builder.build(new MFXRectangleToggleNode("DATEPICKERS"), MFXDemoResourcesLoader.loadURL("DatePickersDemo.fxml")));
+        vLoader.addItem("DIALOGS", Builder.build(new MFXRectangleToggleNode("DIALOGS"), MFXDemoResourcesLoader.loadURL("DialogsDemo.fxml")).setControllerFactory(controller -> new DialogsController(demoPane)));
+        vLoader.addItem("LABELS", Builder.build(new MFXRectangleToggleNode("LABELS"), MFXDemoResourcesLoader.loadURL("LabelsDemo.fxml")));
+        vLoader.addItem("LISTVIEWS", Builder.build(new MFXRectangleToggleNode("LISTVIEWS"), MFXDemoResourcesLoader.loadURL("ListViewsDemo.fxml")));
+        vLoader.addItem("NOTIFICATIONS", Builder.build(new MFXRectangleToggleNode("NOTIFICATIONS"), MFXDemoResourcesLoader.loadURL("NotificationsDemo.fxml")));
+        vLoader.addItem("PROGRESS_BARS", Builder.build(new MFXRectangleToggleNode("PROGRESS BARS"), MFXDemoResourcesLoader.loadURL("ProgressBarsDemo.fxml")));
+        vLoader.addItem("PROGRESS_SPINNERS", Builder.build(new MFXRectangleToggleNode("PROGRESS SPINNERS"), MFXDemoResourcesLoader.loadURL("ProgressSpinnersDemo.fxml")));
+        vLoader.addItem("RADIOBUTTONS", Builder.build(new MFXRectangleToggleNode("RADIOBUTTONS"), MFXDemoResourcesLoader.loadURL("RadioButtonsDemo.fxml")));
+        vLoader.addItem("SCROLLPANES", Builder.build(new MFXRectangleToggleNode("SCROLLPANES"), MFXDemoResourcesLoader.loadURL("ScrollPanesDemo.fxml")));
+        vLoader.addItem("STEPPER", Builder.build(new MFXRectangleToggleNode("STEPPER"), MFXDemoResourcesLoader.loadURL("StepperDemo.fxml")));
+        vLoader.addItem("TABLEVIEWS", Builder.build(new MFXRectangleToggleNode("TABLEVIEWS"), MFXDemoResourcesLoader.loadURL("TableViewsDemo.fxml")));
+        vLoader.addItem("TEXTFIELDS", Builder.build(new MFXRectangleToggleNode("TEXTFIELDS"), MFXDemoResourcesLoader.loadURL("TextFieldsDemo.fxml")));
+        vLoader.addItem("TOGGLES", Builder.build(new MFXRectangleToggleNode("TOGGLES"), MFXDemoResourcesLoader.loadURL("ToggleButtonsDemo.fxml")));
+        vLoader.addItem("TREEVIEWS", Builder.build(new MFXRectangleToggleNode("TREEVIEWS"), MFXDemoResourcesLoader.loadURL("TreeViewsDemo.fxml")));
+        vLoader.addItem("FONTRESOURCES", Builder.build(new MFXRectangleToggleNode("FONTRESOURCES"), MFXDemoResourcesLoader.loadURL("FontResourcesDemo.fxml")));
         vLoader.start();
 
         // Others
@@ -217,7 +217,7 @@ public class DemoController implements Initializable {
         MFXDialog infoDialog;
         MFXStageDialog stageDialog;
         try {
-            FXMLLoader loader = new FXMLLoader(MFXResourcesLoader.load("InfoDialog.fxml"));
+            FXMLLoader loader = new FXMLLoader(MFXDemoResourcesLoader.loadURL("InfoDialog.fxml"));
             loader.setControllerFactory(controller -> new InfoController(hostServices));
             infoDialog = loader.load();
         } catch (IOException e) {

+ 2 - 2
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/StepperDemoController.java

@@ -19,7 +19,7 @@
 package io.github.palexdev.materialfx.demo.controllers;
 
 import io.github.palexdev.materialfx.controls.*;
-import io.github.palexdev.materialfx.demo.MFXResourcesLoader;
+import io.github.palexdev.materialfx.demo.MFXDemoResourcesLoader;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.utils.BindingUtils;
 import javafx.beans.binding.Bindings;
@@ -144,7 +144,7 @@ public class StepperDemoController implements Initializable {
         b4.setMaxWidth(Region.USE_PREF_SIZE);
 
         VBox box = new VBox(10, b1, b2, b3, b4, checkbox);
-        box.getStylesheets().setAll(MFXResourcesLoader.load("css/StepperDemo.css").toString());
+        box.getStylesheets().setAll(MFXDemoResourcesLoader.load("css/StepperDemo.css"));
         box.setAlignment(Pos.CENTER);
         StackPane.setAlignment(box, Pos.CENTER);
 

+ 11 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TestDemo.css

@@ -0,0 +1,11 @@
+.mfx-slider:min .track {
+    -fx-fill: red;
+}
+
+.mfx-slider:max .bar {
+    -fx-fill: green;
+}
+
+.mfx-slider:val .track {
+    -fx-fill: gold;
+}

+ 7 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java

@@ -261,7 +261,13 @@ public class MFXButton extends Button {
             this,
             "buttonType",
             ButtonType.FLAT
-    );
+    ) {
+        @Override
+        public void set(ButtonType v) {
+            if (v == ButtonType.FLAT) setEffect(null);
+            super.set(v);
+        }
+    };
 
     public DepthLevel getDepthLevel() {
         return depthLevel.get();

+ 73 - 18
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java

@@ -12,7 +12,6 @@ import io.github.palexdev.materialfx.utils.NumberUtils;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.*;
 import javafx.css.*;
-import javafx.event.Event;
 import javafx.geometry.Orientation;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
@@ -74,7 +73,13 @@ public class MFXSlider extends Control {
         }
     };
     private final ObjectProperty<Supplier<Region>> popupSupplier = new SimpleObjectProperty<>();
-    private final IntegerProperty decimalPrecision = new SimpleIntegerProperty(2);
+    private final DoubleProperty popupPadding = new SimpleDoubleProperty(5.0);
+    private final IntegerProperty decimalPrecision = new SimpleIntegerProperty(0);
+
+    private final DoubleProperty cssVal = new SimpleDoubleProperty();
+    protected final PseudoClass MIN_PSEUDO_CLASS = PseudoClass.getPseudoClass("min");
+    protected final PseudoClass MAX_PSEUDO_CLASS = PseudoClass.getPseudoClass("max");
+    protected final PseudoClass VAL_PSEUDO_CLASS = PseudoClass.getPseudoClass("val");
 
     //================================================================================
     // Constructors
@@ -106,37 +111,57 @@ public class MFXSlider extends Control {
     private void initialize() {
         getStyleClass().add(STYLE_CLASS);
 
+        addListeners();
+
         defaultThumbSupplier();
         defaultPopupSupplier();
     }
 
+    private void addListeners() {
+        min.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
+        max.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
+        value.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
+        cssVal.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
+    }
+
+    private void handlePseudoClasses() {
+        pseudoClassStateChanged(MIN_PSEUDO_CLASS, false);
+        pseudoClassStateChanged(MAX_PSEUDO_CLASS, false);
+        pseudoClassStateChanged(VAL_PSEUDO_CLASS, false);
+
+        double val = getValue();
+        if (val == getMin()) {
+            pseudoClassStateChanged(MIN_PSEUDO_CLASS, true);
+        } else if (val == getMax()) {
+            pseudoClassStateChanged(MAX_PSEUDO_CLASS, true);
+        } else if (val == getCssVal()) {
+            pseudoClassStateChanged(VAL_PSEUDO_CLASS, true);
+        }
+    }
+
     protected void defaultThumbSupplier() {
         setThumbSupplier(() -> {
             MFXFontIcon thumb = new MFXFontIcon("mfx-circle", 12);
             MFXFontIcon thumbRadius = new MFXFontIcon("mfx-circle", 30);
             thumb.setMouseTransparent(true);
+            thumbRadius.setMouseTransparent(true);
             thumb.getStyleClass().setAll("thumb");
             thumbRadius.getStyleClass().setAll("thumb-radius");
 
-            thumbRadius.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
-                Node track = lookup(".track");
-                if (track != null) {
-                    Event.fireEvent(track, event);
-                }
-            });
-
             StackPane stackPane = new StackPane();
             stackPane.getStyleClass().add("thumb-container");
             stackPane.setMinSize(USE_PREF_SIZE, USE_PREF_SIZE);
             stackPane.setPrefSize(NodeUtils.getNodeWidth(thumb), NodeUtils.getNodeHeight(thumb));
             stackPane.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
-            stackPane.getChildren().setAll(thumbRadius, thumb);
+            stackPane.getChildren().setAll(thumb, thumbRadius);
 
             MFXCircleRippleGenerator rippleGenerator = new MFXCircleRippleGenerator(stackPane);
             rippleGenerator.setAnimateBackground(false);
-            rippleGenerator.setAnimationSpeed(1.5);
+            rippleGenerator.setAnimationSpeed(2);
+            rippleGenerator.setCheckBounds(false);
             rippleGenerator.setClipSupplier(() -> null);
             rippleGenerator.setMouseTransparent(true);
+            rippleGenerator.setRadiusMultiplier(2.5);
             rippleGenerator.setRippleRadius(6);
             rippleGenerator.setRipplePositionFunction(mouseEvent -> new RipplePosition(stackPane.getWidth() / 2, stackPane.getHeight() / 2));
             stackPane.addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple);
@@ -157,6 +182,11 @@ public class MFXSlider extends Control {
                     value
             ));
 
+            text.rotateProperty().bind(Bindings.createDoubleBinding(
+                    () -> getPopupSide() == SliderPopupSide.DEFAULT ? 0.0 : 180.0,
+                    popupSideProperty()
+            ));
+
             MFXFontIcon caret = new MFXFontIcon("mfx-caret-down", 22);
             caret.setId("popupCaret");
             caret.setBoundsType(TextBoundsType.VISUAL);
@@ -173,21 +203,22 @@ public class MFXSlider extends Control {
 
                     Orientation orientation = getOrientation();
                     double x = orientation == Orientation.HORIZONTAL ? (getWidth() / 2) - (caret.prefWidth(-1) / 2) : getHeight();
-                    double y = orientation == Orientation.HORIZONTAL ? getHeight() : -(caret.prefHeight(-1) / 2) + (getWidth() / 2);
+                    double y = orientation == Orientation.HORIZONTAL ? getHeight() : -(caret.prefHeight(-1) / 2) + (getHeight() / 2);
                     caret.relocate(snapPositionX(x), snapPositionY(y));
-                    caret.setRotate(orientation == Orientation.HORIZONTAL ? 0 : -90);
                 }
             };
             container.setAlignment(Pos.TOP_CENTER);
             container.setMinSize(45, 40);
             container.getStylesheets().add(STYLESHEET);
 
-            container.rotateProperty().bind(Bindings.createDoubleBinding(
+            caret.rotateProperty().bind(Bindings.createDoubleBinding(
                     () -> {
-                        Orientation orientation = getOrientation();
-                        return orientation == Orientation.HORIZONTAL ? 0.0 : 90.0;
-                    }, orientationProperty())
-            );
+                        container.requestLayout();
+                        return getOrientation() == Orientation.HORIZONTAL ? 0.0 : -90;
+                    },
+                    needsLayoutProperty(), rotateProperty(), popupSideProperty()
+            ));
+
             return container;
         });
     }
@@ -252,6 +283,18 @@ public class MFXSlider extends Control {
         this.popupSupplier.set(popupSupplier);
     }
 
+    public double getPopupPadding() {
+        return popupPadding.get();
+    }
+
+    public DoubleProperty popupPaddingProperty() {
+        return popupPadding;
+    }
+
+    public void setPopupPadding(double popupPadding) {
+        this.popupPadding.set(popupPadding);
+    }
+
     public int getDecimalPrecision() {
         return decimalPrecision.get();
     }
@@ -264,6 +307,18 @@ public class MFXSlider extends Control {
         this.decimalPrecision.set(decimalPrecision);
     }
 
+    public double getCssVal() {
+        return cssVal.get();
+    }
+
+    public DoubleProperty cssValProperty() {
+        return cssVal;
+    }
+
+    public void setCssVal(double cssVal) {
+        this.cssVal.set(cssVal);
+    }
+
     //================================================================================
     // Styleable Properties
     //================================================================================

+ 2 - 14
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXButtonSkin.java

@@ -19,8 +19,6 @@
 package io.github.palexdev.materialfx.skins;
 
 import io.github.palexdev.materialfx.controls.MFXButton;
-import io.github.palexdev.materialfx.controls.enums.ButtonType;
-import io.github.palexdev.materialfx.effects.DepthLevel;
 import io.github.palexdev.materialfx.effects.MFXDepthManager;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import javafx.scene.control.skin.ButtonSkin;
@@ -52,17 +50,8 @@ public class MFXButtonSkin extends ButtonSkin {
         MFXButton button = (MFXButton) getSkinnable();
         MFXCircleRippleGenerator rippleGenerator = button.getRippleGenerator();
 
-        button.depthLevelProperty().addListener((observable, oldValue, newValue) -> {
-            if (!newValue.equals(oldValue) && button.getButtonType().equals(ButtonType.RAISED)) {
-                button.setEffect(MFXDepthManager.shadowOf(newValue));
-            }
-        });
-
-        button.buttonTypeProperty().addListener((observable, oldValue, newValue) -> {
-            if (!newValue.equals(oldValue)) {
-                updateButtonType();
-            }
-        });
+        button.depthLevelProperty().addListener((observable, oldValue, newValue) -> updateButtonType());
+        button.buttonTypeProperty().addListener((observable, oldValue, newValue) -> updateButtonType());
 
         button.addEventFilter(MouseEvent.MOUSE_PRESSED, rippleGenerator::generateRipple);
     }
@@ -80,7 +69,6 @@ public class MFXButtonSkin extends ButtonSkin {
                 break;
             }
             case FLAT: {
-                button.setEffect(MFXDepthManager.shadowOf(DepthLevel.LEVEL0));
                 button.setPickOnBounds(true);
                 break;
             }

+ 4 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java

@@ -38,6 +38,7 @@ import javafx.animation.ScaleTransition;
 import javafx.animation.Timeline;
 import javafx.beans.InvalidationListener;
 import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
 import javafx.collections.MapChangeListener;
 import javafx.event.EventHandler;
 import javafx.geometry.*;
@@ -366,6 +367,9 @@ public class MFXComboBoxSkin<T> extends SkinBase<MFXComboBox<T>> {
                 listView.setItems(FXCollections.observableArrayList());
             }
         });
+
+        NodeUtils.waitForScene(comboBox, () -> listView.getStylesheets().setAll(comboBox.getStylesheets()), true);
+        comboBox.getStylesheets().addListener((ListChangeListener<? super String>) changed -> listView.getStylesheets().setAll(comboBox.getStylesheets()));
     }
 
     /**

+ 116 - 68
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXSliderSkin.java

@@ -3,6 +3,7 @@ package io.github.palexdev.materialfx.skins;
 import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.controls.MFXSlider;
 import io.github.palexdev.materialfx.controls.enums.SliderEnum.SliderMode;
+import io.github.palexdev.materialfx.controls.enums.SliderEnum.SliderPopupSide;
 import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.utils.AnimationUtils;
@@ -13,6 +14,7 @@ import javafx.animation.KeyFrame;
 import javafx.animation.KeyValue;
 import javafx.animation.PauseTransition;
 import javafx.beans.binding.Bindings;
+import javafx.beans.binding.DoubleBinding;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.event.EventHandler;
@@ -32,7 +34,6 @@ import javafx.scene.shape.Rectangle;
 import javafx.scene.shape.StrokeLineCap;
 import javafx.scene.shape.StrokeLineJoin;
 import javafx.scene.shape.StrokeType;
-import javafx.scene.transform.Rotate;
 import javafx.util.Duration;
 
 import java.util.List;
@@ -46,9 +47,10 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
     private final Group ticksGroup;
     private final NumberAxis ticksAxis;
     private Node thumb;
-    private Region valuePopup;
+    private Region popup;
 
     private final LayoutData layoutData = new LayoutData();
+    private final PopupManager popupManager = new PopupManager();
 
     private double preDragThumbPos;
     private Point2D dragStart;
@@ -80,9 +82,9 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
         bar.setMouseTransparent(true);
 
         thumb = slider.getThumbSupplier().get();
-        valuePopup = slider.getPopupSupplier().get();
-        valuePopup.setVisible(false);
-        valuePopup.setOpacity(0.0);
+        popup = slider.getPopupSupplier().get();
+        popup.setVisible(false);
+        popup.setOpacity(0.0);
 
         ticksAxis = new NumberAxis(slider.getMin(), slider.getMax(), slider.getTickUnit());
         ticksAxis.setMinorTickCount(slider.getMinorTicksCount());
@@ -102,7 +104,7 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
         ticksGroup.setManaged(false);
         ticksGroup.setMouseTransparent(true);
 
-        group = new Group(track, ticksGroup, bar, thumb, valuePopup);
+        group = new Group(track, ticksGroup, bar, thumb, popup);
         group.setManaged(false);
         group.getStylesheets().add(slider.getUserAgentStylesheet());
         getChildren().setAll(group);
@@ -209,8 +211,9 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
 
         /* SUPPLIERS HANDLING */
         slider.popupSupplierProperty().addListener((observable, oldValue, newValue) -> {
-            handleValuePopupChange();
+            handlePopupChange();
             slider.requestLayout();
+            popupManager.initPopup();
         });
         slider.thumbSupplierProperty().addListener((observable, oldValue, newValue) -> {
             handleThumbChange();
@@ -246,28 +249,7 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
         track.addEventHandler(MouseEvent.MOUSE_PRESSED, trackPressedHandler);
 
         /* POPUP HANDLING */
-        valuePopup.layoutXProperty().bind(Bindings.createDoubleBinding(
-                () -> {
-                    double hx = thumb.getLayoutX() - (valuePopup.getWidth() / 2) + layoutData.halfThumbWidth();
-                    if (slider.getOrientation() == Orientation.HORIZONTAL) {
-                       return hx;
-                    }
-                    Rotate rotate = new Rotate(-90, hx, 0);
-                    return rotate.getPivotX() + 3;
-                },
-                thumb.layoutXProperty(), valuePopup.widthProperty(), slider.thumbSupplierProperty(), slider.orientationProperty()
-        ));
-        valuePopup.layoutYProperty().bind(Bindings.createDoubleBinding(
-                () -> {
-                    double hy = -(valuePopup.getHeight() + (layoutData.halfThumbHeight() * 2));
-                    if (slider.getOrientation() == Orientation.HORIZONTAL) {
-                        return hy;
-                    }
-                    Rotate rotate = new Rotate(-90, 0, hy);
-                    return rotate.getPivotY();
-                },
-                slider.heightProperty(), valuePopup.heightProperty(), slider.thumbSupplierProperty(), slider.orientationProperty()
-        ));
+        popupManager.initPopup();
 
         /* NumberAxis LAYOUT HANDLING */
         ticksAxis.visibleProperty().bind(slider.showMinorTicksProperty());
@@ -332,32 +314,25 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
         slider.setValue(val);
     }
 
-    private void handleValuePopupChange() {
+    private void handlePopupChange() {
         MFXSlider slider = getSkinnable();
 
         int index = -1;
-        if (valuePopup != null) {
-            index = group.getChildren().indexOf(valuePopup);
-            valuePopup.layoutXProperty().unbind();
-            valuePopup.layoutYProperty().unbind();
-            group.getChildren().remove(valuePopup);
+        if (popup != null) {
+            index = group.getChildren().indexOf(popup);
+            popup.layoutXProperty().unbind();
+            popup.layoutYProperty().unbind();
+            group.getChildren().remove(popup);
         }
 
         Supplier<Region> popupSupplier = slider.getPopupSupplier();
-        valuePopup = popupSupplier != null ? popupSupplier.get() : null;
-
-        if (valuePopup != null) {
-            valuePopup.setVisible(false);
-            valuePopup.setOpacity(0.0);
-            valuePopup.layoutXProperty().bind(Bindings.createDoubleBinding(
-                    () -> thumb.getLayoutX() - (valuePopup.getWidth() / 2) + layoutData.halfThumbWidth(),
-                    thumb.layoutXProperty(), valuePopup.widthProperty()
-            ));
-            valuePopup.layoutYProperty().bind(Bindings.createDoubleBinding(
-                    () -> -(valuePopup.getHeight() + layoutData.halfThumbHeight()),
-                    slider.heightProperty(), valuePopup.heightProperty()
-            ));
-            group.getChildren().add(index >= 0 ? index : group.getChildren().size() - 1, valuePopup);
+        popup = popupSupplier != null ? popupSupplier.get() : null;
+
+        if (popup != null) {
+            popup.setVisible(false);
+            popup.setOpacity(0.0);
+            group.getChildren().add(index >= 0 ? index : group.getChildren().size() - 1, popup);
+            popupManager.initPopup();
         }
     }
 
@@ -379,39 +354,30 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
             thumb.addEventHandler(MouseEvent.MOUSE_PRESSED, thumbPressHandler);
             thumb.addEventHandler(MouseEvent.MOUSE_DRAGGED, thumbDragHandler);
             group.getChildren().add(index >= 0 ? index : group.getChildren().size() - 1, thumb);
-
-            valuePopup.layoutXProperty().bind(Bindings.createDoubleBinding(
-                    () -> thumb.getLayoutX() - (valuePopup.getWidth() / 2) + layoutData.halfThumbWidth(),
-                    thumb.layoutXProperty(), valuePopup.widthProperty(), slider.thumbSupplierProperty()
-            ));
-            valuePopup.layoutYProperty().bind(Bindings.createDoubleBinding(
-                    () -> -(valuePopup.getHeight() + layoutData.halfThumbHeight()),
-                    slider.heightProperty(), valuePopup.heightProperty(), slider.thumbSupplierProperty()
-            ));
         }
     }
 
     protected void showPopup() {
-        if (valuePopup == null) {
+        if (popup == null) {
             return;
         }
 
         releaseTimer.stop();
         AnimationUtils.SequentialBuilder.build()
-                .add(AnimationUtils.PauseBuilder.build().setDuration(Duration.ONE).setOnFinished(event -> valuePopup.setVisible(true)).getAnimation())
-                .add(new KeyFrame(Duration.millis(200), new KeyValue(valuePopup.opacityProperty(), 1.0, Interpolator.EASE_IN)))
+                .add(AnimationUtils.PauseBuilder.build().setDuration(Duration.ONE).setOnFinished(event -> popup.setVisible(true)).getAnimation())
+                .add(new KeyFrame(Duration.millis(200), new KeyValue(popup.opacityProperty(), 1.0, Interpolator.EASE_IN)))
                 .getAnimation()
                 .play();
     }
 
     protected void hidePopup() {
-        if (valuePopup == null) {
+        if (popup == null) {
             return;
         }
 
         AnimationUtils.SequentialBuilder.build()
-                .add(new KeyFrame(Duration.millis(200), new KeyValue(valuePopup.opacityProperty(), 0.0, Interpolator.EASE_OUT)))
-                .setOnFinished(event -> valuePopup.setVisible(false))
+                .add(new KeyFrame(Duration.millis(200), new KeyValue(popup.opacityProperty(), 0.0, Interpolator.EASE_OUT)))
+                .setOnFinished(event -> popup.setVisible(false))
                 .getAnimation()
                 .play();
     }
@@ -531,8 +497,9 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
                         val,
                         NumberRange.of(slider.getMin(), slider.getMax()),
                         NumberRange.of(0.0, slider.getWidth()),
-                        slider.getDecimalPrecision()
-                ) + 1;
+                        2
+                );
+                zeroPos = NumberUtils.clamp(zeroPos, 0, slider.getWidth());
             }
 
             thumbX = snapPositionX(
@@ -540,7 +507,7 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
                             slider.getValue(),
                             NumberRange.of(slider.getMin(), slider.getMax()),
                             NumberRange.of(0.0, slider.getWidth()),
-                            slider.getDecimalPrecision()
+                            2
                     ) - halfThumbWidth());
             thumbY = snapPositionY(-halfThumbHeight() + (slider.getHeight() / 2));
 
@@ -595,7 +562,9 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
 
                 if (!slider.isShowTicksAtEdges() &&
                         ((double) tickMark.getValue() == slider.getMax() || (double) tickMark.getValue() == slider.getMin())
-                ) { continue; }
+                ) {
+                    continue;
+                }
 
                 ticksGroup.getChildren().add(tickData.tick);
                 tickData.tick.relocate(tickData.x, ticksY);
@@ -622,6 +591,85 @@ public class MFXSliderSkin extends SkinBase<MFXSlider> {
         }
     }
 
+    protected class PopupManager {
+        private DoubleBinding xBinding;
+        private DoubleBinding yBinding;
+        private DoubleBinding rotate;
+
+        private void initPopup() {
+            MFXSlider slider = getSkinnable();
+            if (popup == null) {
+                return;
+            }
+
+            xBinding = Bindings.createDoubleBinding(
+                    this::computeXPos,
+                    thumb.layoutXProperty(), popup.widthProperty(), slider.thumbSupplierProperty(), slider.orientationProperty(), slider.popupSideProperty()
+            );
+            yBinding = Bindings.createDoubleBinding(
+                    this::computeYPos,
+                    thumb.layoutYProperty(), popup.heightProperty(), slider.thumbSupplierProperty(), slider.orientationProperty(), slider.popupSideProperty()
+            );
+            rotate = Bindings.createDoubleBinding(
+                    this::computeRotate,
+                    slider.orientationProperty(), slider.popupSideProperty()
+            );
+
+            popup.rotateProperty().bind(rotate);
+            popup.layoutXProperty().bind(xBinding);
+            popup.layoutYProperty().bind(yBinding);
+        }
+
+        private double computeRotate() {
+            MFXSlider slider = getSkinnable();
+
+            if (slider.getOrientation() == Orientation.HORIZONTAL && slider.getPopupSide() == SliderPopupSide.OTHER_SIDE) {
+                return 180;
+            }
+
+            if (slider.getOrientation() == Orientation.VERTICAL) {
+                return slider.getPopupSide() == SliderPopupSide.DEFAULT ? 90 : -90;
+            }
+
+            return 0;
+        }
+
+        private double computeXPos() {
+            MFXSlider slider = getSkinnable();
+
+            double x;
+            if (slider.getOrientation() == Orientation.HORIZONTAL) {
+                x = thumb.getLayoutX() - ((popup.getWidth() - layoutData.halfThumbWidth() * 2) / 2);
+                x = slider.getPopupSide() == SliderPopupSide.DEFAULT ? x : x - 1;
+            } else {
+                x = thumb.getLayoutX() - (popup.getHeight() / 2) + (layoutData.halfThumbWidth() / 2) + 1;
+            }
+
+            return snapPositionX(x);
+        }
+
+        private double computeYPos() {
+            MFXSlider slider = getSkinnable();
+
+            double y;
+            if (slider.getOrientation() == Orientation.HORIZONTAL) {
+                if (slider.getPopupSide() == SliderPopupSide.DEFAULT) {
+                    y = -(popup.getHeight() + layoutData.halfThumbHeight() + slider.getPopupPadding());
+                } else {
+                    y = slider.getHeight() + layoutData.halfThumbHeight() + slider.getPopupPadding();
+                }
+            } else {
+                if (slider.getPopupSide() == SliderPopupSide.DEFAULT) {
+                    y = -(popup.getWidth() + layoutData.halfThumbHeight() + (slider.getPopupPadding() / 1.5));
+                } else {
+                    y = (slider.getHeight() * 1.5) + layoutData.halfThumbHeight() + slider.getPopupPadding();
+                }
+            }
+
+            return snapPositionY(y);
+        }
+    }
+
     protected static class TickData {
         private Node tick;
         private double tickVal;

+ 9 - 7
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTableViewSkin.java

@@ -118,7 +118,7 @@ public class MFXTableViewSkin<T> extends SkinBase<MFXTableView<T>> {
     private final HBox pgcBox;
     private final Label rowsPerPageLabel;
     private final MFXComboBox<Integer> rowsPerPageCombo;
-    private final Label shownRows;
+    private final Label shownRowsLabel;
 
     private final ObjectProperty<IndexRange> shownRowsRange = new SimpleObjectProperty<>();
 
@@ -152,13 +152,15 @@ public class MFXTableViewSkin<T> extends SkinBase<MFXTableView<T>> {
         VBox.setVgrow(rowsBox, Priority.ALWAYS);
 
         rowsPerPageLabel = new Label("Rows Per Page");
+        rowsPerPageLabel.setId("rowsPerPageLabel");
 
         rowsPerPageCombo = new MFXComboBox<>();
         rowsPerPageCombo.setComboStyle(Styles.ComboBoxStyles.STYLE2);
         rowsPerPageCombo.setMaxPopupHeight(100);
 
-        shownRows = new Label("Shown Rows: ");
-        shownRows.textProperty().bind(Bindings.createStringBinding(
+        shownRowsLabel = new Label("Shown Rows: ");
+        shownRowsLabel.setId("shownRowsLabel");
+        shownRowsLabel.textProperty().bind(Bindings.createStringBinding(
                 () -> {
                     if (getShownRowsRange() != null) {
                         return new StringBuilder()
@@ -491,7 +493,7 @@ public class MFXTableViewSkin<T> extends SkinBase<MFXTableView<T>> {
 
         HBox pgcBox = new HBox(15,
                 filterIcon, clearFilterIcon, new Separator(Orientation.VERTICAL),
-                box1, new Separator(Orientation.VERTICAL), box2, new Separator(Orientation.VERTICAL), shownRows
+                box1, new Separator(Orientation.VERTICAL), box2, new Separator(Orientation.VERTICAL), shownRowsLabel
         );
         pgcBox.getStyleClass().setAll("pagination-controls-container");
         pgcBox.setAlignment(Pos.CENTER);
@@ -503,9 +505,9 @@ public class MFXTableViewSkin<T> extends SkinBase<MFXTableView<T>> {
         pgcBox.setPrefWidth(Region.USE_COMPUTED_SIZE);
         pgcBox.setMaxWidth(Double.MAX_VALUE);
 
-        shownRows.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
-        shownRows.setPadding(new Insets(5));
-        HBox.setHgrow(shownRows, Priority.ALWAYS);
+        shownRowsLabel.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
+        shownRowsLabel.setPadding(new Insets(5));
+        HBox.setHgrow(shownRowsLabel, Priority.ALWAYS);
 
         return pgcBox;
     }

+ 6 - 7
materialfx/src/main/java/io/github/palexdev/materialfx/utils/NodeUtils.java

@@ -42,7 +42,6 @@ import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
 
 /**
  * Utility class which provides convenience methods for working with Nodes
@@ -307,15 +306,15 @@ public class NodeUtils {
      * @param removeListener to specify if the listener added to the skin property
      *                       should be removed after it is not null anymore.
      */
-    public static <V> void waitForSkin(Control control, Callable<V> action, boolean removeListener) {
+    public static void waitForSkin(Control control, Runnable action, boolean removeListener) {
         if (control.getSkin() != null) {
-            ExecutionUtils.tryCallableAndPrint(action);
+            action.run();
         } else {
             control.skinProperty().addListener(new ChangeListener<>() {
                 @Override
                 public void changed(ObservableValue<? extends Skin<?>> observable, Skin<?> oldValue, Skin<?> newValue) {
                     if (newValue != null) {
-                        ExecutionUtils.tryCallableAndPrint(action);
+                        action.run();
                         if (removeListener) {
                             control.skinProperty().removeListener(this);
                         }
@@ -337,15 +336,15 @@ public class NodeUtils {
      * @param removeListener to specify if the listener added to the scene property
      *                       should be removed after it is not null anymore.
      */
-    public static <V> void waitForScene(Control control, Callable<V> action, boolean removeListener) {
+    public static void waitForScene(Control control, Runnable action, boolean removeListener) {
         if (control.getScene() != null) {
-            ExecutionUtils.tryCallableAndPrint(action);
+            action.run();
         } else {
             control.sceneProperty().addListener(new ChangeListener<>() {
                 @Override
                 public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
                     if (newValue != null) {
-                        ExecutionUtils.tryCallableAndPrint(action);
+                        action.run();
                         if (removeListener) {
                             control.sceneProperty().removeListener(this);
                         }

+ 1 - 4
materialfx/src/main/java/io/github/palexdev/materialfx/utils/ScrollUtils.java

@@ -19,7 +19,6 @@ import javafx.util.Duration;
 import org.reactfx.value.Var;
 
 import java.util.Set;
-import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -220,7 +219,7 @@ public class ScrollUtils {
      * Adds a fade in and out effect to the given scroll pane's scroll bars
      * with the given fadeSpeedMillis and hideAfterMillis values.
      *
-     * @see NodeUtils#waitForSkin(Control, Callable, boolean)
+     * @see NodeUtils#waitForSkin(Control, Runnable, boolean)
      */
     public static void animateScrollBars(ScrollPane scrollPane, double fadeSpeedMillis, double hideAfterMillis) {
         NodeUtils.waitForSkin(scrollPane, () -> {
@@ -244,7 +243,6 @@ public class ScrollUtils {
                     }
                 });
             });
-            return null;
         }, true);
     }
 
@@ -282,7 +280,6 @@ public class ScrollUtils {
         NodeUtils.waitForSkin(listView, () -> {
             VirtualFlow<?, ?> flow = (VirtualFlow<?, ?>) listView.lookup(".virtual-flow");
             smoothScroll(flow, speed, trackPadAdjustment);
-            return null;
         }, true);
     }
 

+ 9 - 3
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXSlider.css

@@ -39,12 +39,18 @@
     -mfx-color: -mfx-main-color-pressed;
 }
 
+.mfx-slider .thumb-container .mfx-ripple-generator {
+    -mfx-ripple-color: -mfx-main-color;
+}
+
 /* TICKS STYLING */
 .mfx-slider .tick-even,
-.mfx-slider .tick-odd,
-.mfx-slider .axis-minor-tick-mark {
+.mfx-slider .tick-odd {
     -mfx-color: derive(-mfx-main-color, 30%);
-    -fx-stroke: derive(-mfx-main-color, 130%);
+}
+
+.mfx-slider .axis-minor-tick-mark {
+    -fx-stroke: -mfx-main-color-hover;
 }
 
 /* POPUP */