Browse Source

:recycle: Minor changes and improvements

Demo:
:recycle: Updated some controls' demo
:sparkles: Added a splash screen with logo, animations and even sound

MFXSlider:
:recycle: Instead of having three new pseudo classes that activate only when a the slider reaches certain values, you can now specify three separate lists of ranges. For example, in the first list you could add ranges from 0 to 25 and from 50 to 75, then in the second list from 26 to 49 and from 76 to 80, then in the third list from 81 to 100. When the value changes the slider checks in which range the value is, in CSS you can customize the slider according to those ranges with the following pseudo classes: ":range1", ":range2", ":range3".
You can also specify the same value for both bounds in the range, this will act as before this change, but still with much more flexibility.
For an example see the SlidersDemoController class.

MFXProgressBar,
MFXProgressSpinner:
:sparkles: Added the same css system described above to these controls

NumberRange:
:sparkles: Added a new method to create a new NumberRange from a single value, min and max bounds are the same.
:sparkles: Added four new static methods to check if a given value is contained in a given NumberRange
:sparkles: Added four new static methods to check if a given value is contained at least in one of the given NumberRanges (in a list of ranges)
palexdev 4 years ago
parent
commit
34ce0713dc
25 changed files with 544 additions and 115 deletions
  1. 1 1
      build.gradle
  2. 6 2
      demo/src/main/java/io/github/palexdev/materialfx/demo/TestDemo.java
  3. 54 2
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DemoController.java
  4. 5 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressBarsDemoController.java
  5. 5 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressSpinnersDemoController.java
  6. 40 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/SlidersDemoController.java
  7. 1 0
      demo/src/main/java/module-info.java
  8. 26 1
      demo/src/main/resources/io/github/palexdev/materialfx/demo/Demo.fxml
  9. 11 19
      demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressBarsDemo.fxml
  10. 2 2
      demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressSpinnersDemo.fxml
  11. 33 36
      demo/src/main/resources/io/github/palexdev/materialfx/demo/SlidersDemo.fxml
  12. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/logo.png
  13. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome1.wav
  14. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome2.wav
  15. 1 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Common.css
  16. 10 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Demo.css
  17. 26 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressBarDemo.css
  18. 37 7
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressSpinnersDemo.css
  19. 17 9
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/SlidersDemo.css
  20. 6 6
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TestDemo.css
  21. 69 0
      materialfx/src/main/java/io/github/palexdev/materialfx/beans/NumberRange.java
  22. 71 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressBar.java
  23. 71 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressSpinner.java
  24. 47 29
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java
  25. 5 0
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/NodeUtils.java

+ 1 - 1
build.gradle

@@ -16,7 +16,7 @@ subprojects {
 
     javafx {
         version = "16"
-        modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.swing', 'javafx.web' ]
+        modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.media', 'javafx.swing', 'javafx.web' ]
     }
 }
 

+ 6 - 2
demo/src/main/java/io/github/palexdev/materialfx/demo/TestDemo.java

@@ -18,6 +18,7 @@
 
 package io.github.palexdev.materialfx.demo;
 
+import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.beans.properties.ResettableDoubleProperty;
 import io.github.palexdev.materialfx.controls.MFXButton;
 import io.github.palexdev.materialfx.controls.MFXIconWrapper;
@@ -25,7 +26,6 @@ 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.SliderEnums;
 import io.github.palexdev.materialfx.controls.enums.SliderEnums.SliderPopupSide;
 import io.github.palexdev.materialfx.demo.model.FilterablePerson;
 import io.github.palexdev.materialfx.font.FontResources;
@@ -54,7 +54,7 @@ public class TestDemo extends Application {
         box.setAlignment(Pos.CENTER);
 
         MFXSlider slider = new MFXSlider();
-        slider.setSliderMode(SliderEnums.SliderMode.SNAP_TO_TICKS);
+        //slider.setSliderMode(SliderEnums.SliderMode.SNAP_TO_TICKS);
         slider.setBidirectional(false);
         slider.setPrefWidth(200);
         slider.setMin(10);
@@ -62,6 +62,10 @@ public class TestDemo extends Application {
         slider.setValue(-50);
         slider.setBidirectional(false);
 
+        slider.getRanges1().add(NumberRange.of(slider.getMin()));
+        slider.getRanges2().add(NumberRange.of(50.0));
+        slider.getRanges3().add(NumberRange.of(slider.getMax()));
+
         HBox bbox = new HBox(20);
         bbox.setAlignment(Pos.CENTER);
 

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

@@ -23,6 +23,7 @@ import io.github.palexdev.materialfx.controls.*;
 import io.github.palexdev.materialfx.demo.MFXDemoResourcesLoader;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.utils.AnimationUtils;
+import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.ScrollUtils;
 import javafx.animation.KeyFrame;
@@ -35,12 +36,17 @@ import javafx.fxml.Initializable;
 import javafx.geometry.Insets;
 import javafx.geometry.Pos;
 import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.image.ImageView;
 import javafx.scene.input.KeyCode;
 import javafx.scene.input.KeyEvent;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.media.Media;
+import javafx.scene.media.MediaPlayer;
 import javafx.scene.paint.Color;
 import javafx.stage.Modality;
 import javafx.stage.Stage;
@@ -54,10 +60,14 @@ public class DemoController implements Initializable {
     private final Stage primaryStage;
     private final HostServices hostServices;
 
+    private MFXButton opNavButton;
     private ParallelTransition openNav;
     private ParallelTransition closeNav;
     private boolean isNavShown = false;
 
+    private final MediaPlayer m1;
+    private final MediaPlayer m2;
+
     @FXML
     private StackPane demoPane;
 
@@ -73,12 +83,31 @@ public class DemoController implements Initializable {
     @FXML
     private MFXVLoader vLoader;
 
-    private MFXButton opNavButton;
-
     @FXML
     private StackPane contentPane;
 
+    @FXML
+    private VBox logoPane;
+
+    @FXML
+    private ImageView logo;
+
+    @FXML
+    private Label splashLabel1;
+
+    @FXML
+    private Label splashLabel2;
+
+    @FXML
+    private Label splashLabel3;
+
     public DemoController(Stage primaryStage, HostServices hostServices) {
+        m1 = new MediaPlayer(new Media(MFXDemoResourcesLoader.load("assets/welcome1.wav")));
+        m2 = new MediaPlayer(new Media(MFXDemoResourcesLoader.load("assets/welcome2.wav")));
+
+        m1.setVolume(0.3);
+        m2.setVolume(0.2);
+
         this.primaryStage = primaryStage;
         this.hostServices = hostServices;
     }
@@ -110,6 +139,8 @@ public class DemoController implements Initializable {
         infoButton.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> showInfo());
 
         opNavButton = new MFXButton("");
+        opNavButton.setOpacity(0.0);
+        opNavButton.setDisable(true);
         opNavButton.setId("navButton");
         opNavButton.setPrefSize(25, 25);
         opNavButton.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
@@ -148,6 +179,7 @@ public class DemoController implements Initializable {
         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("SLIDERS", Builder.build(new MFXRectangleToggleNode("SLIDERS"), MFXDemoResourcesLoader.loadURL("SlidersDemo.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")));
@@ -178,6 +210,26 @@ public class DemoController implements Initializable {
         initAnimations();
 
         demoPane.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> demoPane.requestFocus());
+
+        primaryStage.setOnShown(event -> presentation());
+    }
+
+    private void presentation() {
+        AnimationUtils.SequentialBuilder.build()
+                .add(KeyFrames.of(Duration.ONE, event -> m1.play()))
+                .add(AnimationUtils.TimelineBuilder.build().show(1000, logo).getAnimation())
+                .add(AnimationUtils.TimelineBuilder.build().show(450, splashLabel1).setDelay(200).getAnimation())
+                .add(AnimationUtils.TimelineBuilder.build().show(450, splashLabel2).setDelay(50).getAnimation())
+                .add(AnimationUtils.TimelineBuilder.build().show(450, splashLabel3).setDelay(50).getAnimation())
+                .setOnFinished(event -> AnimationUtils.SequentialBuilder.build()
+                        .add(KeyFrames.of(300, end -> m2.play()))
+                        .add(AnimationUtils.TimelineBuilder.build().hide(300, logoPane).setOnFinished(end -> logoPane.setVisible(false)).getAnimation())
+                        .add(AnimationUtils.ParallelBuilder.build().show(800, contentPane, opNavButton).setOnFinished(end -> opNavButton.setDisable(false)).getAnimation())
+                        .setDelay(750)
+                        .getAnimation().play())
+                .setDelay(750)
+                .getAnimation().play();
+
     }
 
     private void initAnimations() {

+ 5 - 0
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressBarsDemoController.java

@@ -18,6 +18,7 @@
 
 package io.github.palexdev.materialfx.demo.controllers;
 
+import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.controls.MFXProgressBar;
 import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
 import io.github.palexdev.materialfx.utils.AnimationUtils;
@@ -81,5 +82,9 @@ public class ProgressBarsDemoController implements Initializable {
         );
 
         a1.play();
+
+        determinate.getRanges1().add(NumberRange.of(0.0, 0.30));
+        determinate.getRanges2().add(NumberRange.of(0.31, 0.60));
+        determinate.getRanges3().add(NumberRange.of(0.61, 1.0));
     }
 }

+ 5 - 0
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ProgressSpinnersDemoController.java

@@ -18,6 +18,7 @@
 
 package io.github.palexdev.materialfx.demo.controllers;
 
+import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.controls.MFXProgressSpinner;
 import io.github.palexdev.materialfx.utils.AnimationUtils;
 import io.github.palexdev.materialfx.utils.AnimationUtils.KeyFrames;
@@ -60,5 +61,9 @@ public class ProgressSpinnersDemoController implements Initializable {
                 .setCycleCount(Timeline.INDEFINITE)
                 .getAnimation()
                 .play();
+
+        progress1.getRanges1().add(NumberRange.of(0.0, 0.30));
+        progress1.getRanges2().add(NumberRange.of(0.31, 0.60));
+        progress1.getRanges3().add(NumberRange.of(0.61, 1.0));
     }
 }

+ 40 - 0
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/SlidersDemoController.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 Parisi Alessandro
+ * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
+ *
+ * MaterialFX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MaterialFX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package io.github.palexdev.materialfx.demo.controllers;
+
+import io.github.palexdev.materialfx.beans.NumberRange;
+import io.github.palexdev.materialfx.controls.MFXSlider;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+public class SlidersDemoController implements Initializable {
+
+    @FXML
+    private MFXSlider customSlider;
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        customSlider.getRanges1().add(NumberRange.of(customSlider.getMin(), 33.0));
+        customSlider.getRanges2().add(NumberRange.of(34.0, 66.0));
+        customSlider.getRanges3().add(NumberRange.of(67.0, customSlider.getMax()));
+    }
+}

+ 1 - 0
demo/src/main/java/module-info.java

@@ -6,6 +6,7 @@ module MaterialFX.Demo {
     requires javafx.controls;
     requires javafx.fxml;
     requires javafx.graphics;
+    requires javafx.media;
 
     requires fr.brouillard.oss.cssfx;
     requires org.kordamp.ikonli.javafx;

+ 26 - 1
demo/src/main/resources/io/github/palexdev/materialfx/demo/Demo.fxml

@@ -21,9 +21,12 @@
 <?import io.github.palexdev.materialfx.controls.MFXScrollPane?>
 <?import io.github.palexdev.materialfx.controls.MFXVLoader?>
 <?import javafx.geometry.*?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.image.Image?>
+<?import javafx.scene.image.ImageView?>
 <?import javafx.scene.layout.*?>
 <StackPane id="demoPane" fx:id="demoPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="550.0" prefWidth="960.0" stylesheets="@css/Demo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.DemoController">
-   <StackPane id="contentPane" fx:id="contentPane" prefHeight="500.0" prefWidth="441.0">
+   <StackPane id="contentPane" fx:id="contentPane" opacity="0.0" prefHeight="500.0" prefWidth="441.0">
       <StackPane.margin>
          <Insets bottom="15.0" left="20.0" right="20.0" top="15.0" />
       </StackPane.margin>
@@ -51,4 +54,26 @@
          <Insets left="10.0" right="-10.0" top="5.0" />
       </padding>
    </HBox>
+   <VBox fx:id="logoPane" alignment="CENTER" prefHeight="200.0" prefWidth="100.0">
+      <ImageView fx:id="logo" cache="true" cacheHint="QUALITY" pickOnBounds="true" preserveRatio="true">
+         <Image url="@assets/logo.png"/>
+      </ImageView>
+      <HBox alignment="CENTER" prefHeight="100.0" spacing="50.0">
+         <Label id="splashLabel" fx:id="splashLabel1" text="Simple.">
+            <padding>
+               <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
+            </padding>
+         </Label>
+         <Label id="splashLabel" fx:id="splashLabel2" text="Efficient.">
+            <padding>
+               <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
+            </padding>
+         </Label>
+         <Label id="splashLabel" fx:id="splashLabel3" text="Material.">
+            <padding>
+               <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
+            </padding>
+         </Label>
+      </HBox>
+   </VBox>
 </StackPane>

+ 11 - 19
demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressBarsDemo.fxml

@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-
 <!--
   ~ Copyright (C) 2021 Parisi Alessandro
   ~ This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
@@ -23,31 +22,24 @@
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
-<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
-            prefWidth="600.0" stylesheets="@css/ProgressBarDemo.css" xmlns="http://javafx.com/javafx/16"
-            xmlns:fx="http://javafx.com/fxml/1"
-            fx:controller="io.github.palexdev.materialfx.demo.controllers.ProgressBarsDemoController">
-   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="14.0" prefHeight="26.0" prefWidth="266.0"
-          text="Progress Bars" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0"
-          AnchorPane.topAnchor="20.0"/>
-   <VBox alignment="TOP_CENTER" layoutX="250.0" layoutY="46.0" prefHeight="200.0" prefWidth="100.0" spacing="30.0"
-         AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0"
-         AnchorPane.topAnchor="80.0">
+<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/ProgressBarDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.ProgressBarsDemoController">
+   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="14.0" prefHeight="26.0" prefWidth="266.0" text="Progress Bars" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="20.0" />
+   <VBox alignment="TOP_CENTER" layoutX="250.0" layoutY="46.0" prefHeight="200.0" prefWidth="100.0" spacing="30.0" AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="80.0">
       <padding>
-         <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+         <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
       </padding>
       <HBox alignment="CENTER_LEFT" spacing="20.0">
-         <Label maxWidth="-Infinity" prefHeight="17.0" prefWidth="80.0" text="Indeterminate" HBox.hgrow="ALWAYS"/>
-         <MFXProgressBar prefWidth="300.0"/>
+         <Label maxWidth="-Infinity" prefHeight="17.0" prefWidth="80.0" text="Indeterminate" HBox.hgrow="ALWAYS" />
+         <MFXProgressBar prefWidth="300.0" />
       </HBox>
       <HBox alignment="CENTER_LEFT" spacing="20.0">
-         <Label maxWidth="-Infinity" prefHeight="17.0" prefWidth="80.0" text="Determinate" HBox.hgrow="ALWAYS"/>
-         <MFXProgressBar fx:id="determinate" prefWidth="300.0" progress="0.0"/>
-         <Label fx:id="progressLabel"/>
+         <Label maxWidth="-Infinity" prefHeight="17.0" prefWidth="80.0" text="Determinate" HBox.hgrow="ALWAYS" />
+         <MFXProgressBar id="determinate" fx:id="determinate" prefWidth="300.0" progress="0.0" />
+         <Label fx:id="progressLabel" />
       </HBox>
       <HBox alignment="CENTER_LEFT" spacing="20.0">
-         <Label maxWidth="-Infinity" prefHeight="17.0" prefWidth="80.0" text="Customized" HBox.hgrow="ALWAYS"/>
-         <MFXProgressBar id="custom" prefWidth="300.0"/>
+         <Label maxWidth="-Infinity" prefHeight="17.0" prefWidth="80.0" text="Customized" HBox.hgrow="ALWAYS" />
+         <MFXProgressBar id="custom" prefWidth="300.0" />
       </HBox>
    </VBox>
 </AnchorPane>

+ 2 - 2
demo/src/main/resources/io/github/palexdev/materialfx/demo/ProgressSpinnersDemo.fxml

@@ -22,7 +22,7 @@
 <?import javafx.geometry.*?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.*?>
-<StackPane prefHeight="400.0" prefWidth="600.0" stylesheets="@css/ProgressSpinnersDemo.css" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.ProgressSpinnersDemoController">
+<StackPane prefHeight="400.0" prefWidth="600.0" stylesheets="@css/ProgressSpinnersDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.ProgressSpinnersDemoController">
     <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Progress Spinners" StackPane.alignment="TOP_CENTER">
         <StackPane.margin>
           <Insets top="20.0" />
@@ -58,7 +58,7 @@
           <Insets top="100.0" />
         </StackPane.margin>
     </Label>
-    <MFXProgressSpinner fx:id="progress1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="50.0" progress="0.0" startingAngle="-40.0" styleClass="progress1">
+    <MFXProgressSpinner id="determinate" fx:id="progress1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="50.0" prefWidth="50.0" progress="0.0" startingAngle="-40.0">
         <StackPane.margin>
           <Insets right="350.0" top="225.0" />
         </StackPane.margin>

+ 33 - 36
demo/src/main/resources/io/github/palexdev/materialfx/demo/SlidersDemo.fxml

@@ -18,76 +18,73 @@
   ~ along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
   -->
 
-
 <?import io.github.palexdev.materialfx.controls.*?>
 <?import javafx.geometry.*?>
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
-<AnchorPane prefHeight="400.0" prefWidth="750.0" stylesheets="@css/SlidersDemo.css" xmlns="http://javafx.com/javafx/16"
->
-    <Label id="customLabel" alignment="CENTER" layoutX="177.0" layoutY="24.0" prefHeight="26.0" prefWidth="266.0"
-           text="Sliders" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="20.0"/>
-    <HBox layoutX="14.0" layoutY="60.0" prefHeight="207.0" prefWidth="100.0" AnchorPane.bottomAnchor="15.0"
-          AnchorPane.leftAnchor="15.0" AnchorPane.rightAnchor="15.0" AnchorPane.topAnchor="60.0">
+<AnchorPane prefHeight="400.0" prefWidth="750.0" stylesheets="@css/SlidersDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.SlidersDemoController">
+    <Label id="customLabel" alignment="CENTER" layoutX="177.0" layoutY="24.0" prefHeight="26.0" prefWidth="266.0" text="Sliders" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="20.0" />
+    <HBox alignment="CENTER" layoutX="14.0" layoutY="60.0" prefHeight="207.0" prefWidth="100.0" AnchorPane.bottomAnchor="15.0" AnchorPane.leftAnchor="15.0" AnchorPane.rightAnchor="15.0" AnchorPane.topAnchor="60.0">
         <VBox alignment="CENTER" prefHeight="325.0" prefWidth="250.0" spacing="50.0">
             <VBox alignment="CENTER" spacing="20.0">
                 <padding>
-                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
+                </padding>
+                <Label text="Default" />
+                <MFXSlider prefWidth="150.0" />
+            </VBox>
+            <VBox id="customSlider" alignment="CENTER" spacing="20.0">
+                <padding>
+                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                 </padding>
-                <Label text="Default"/>
-                <MFXSlider prefWidth="150.0"/>
+                <Label text="Custom 1" />
+                <MFXSlider id="customSlider" fx:id="customSlider" popupSide="OTHER_SIDE" prefWidth="150.0" />
             </VBox>
             <VBox id="customSlider" alignment="CENTER" spacing="20.0">
                 <padding>
                     <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
                 </padding>
-                <Label text="Customized"/>
-                <MFXSlider id="customSlider" cssVal="50.0" popupSide="OTHER_SIDE" prefWidth="150.0"/>
+                <Label text="Custom 2"/>
+                <MFXSlider id="customSlider2" popupSide="OTHER_SIDE" prefWidth="150.0"/>
             </VBox>
             <VBox alignment="CENTER" spacing="20.0">
                 <padding>
-                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                 </padding>
-                <Label text="Disabled"/>
-                <MFXSlider disable="true" prefWidth="150.0"/>
+                <Label text="Disabled" />
+                <MFXSlider disable="true" prefWidth="150.0" />
             </VBox>
         </VBox>
         <VBox alignment="CENTER" prefHeight="325.0" prefWidth="250.0" spacing="50.0">
             <VBox alignment="CENTER" spacing="20.0">
                 <padding>
-                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                 </padding>
-                <Label text="Bidirectional"/>
-                <MFXSlider min="-50.0" prefWidth="150.0"/>
+                <Label text="Bidirectional" />
+                <MFXSlider min="-50.0" prefWidth="150.0" />
             </VBox>
             <VBox alignment="CENTER" spacing="20.0">
                 <padding>
-                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                 </padding>
-                <Label text="Ticks and Snap To Ticks"/>
-                <MFXSlider minorTicksCount="4" prefWidth="150.0" showMajorTicks="true" showMinorTicks="true"
-                           sliderMode="SNAP_TO_TICKS" tickUnit="20.0"/>
+                <Label text="Ticks and Snap To Ticks" />
+                <MFXSlider minorTicksCount="4" prefWidth="150.0" showMajorTicks="true" showMinorTicks="true" sliderMode="SNAP_TO_TICKS" tickUnit="20.0" />
             </VBox>
             <VBox alignment="CENTER" spacing="20.0">
                 <padding>
-                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                 </padding>
-                <Label text="Decimal Precision of 2"/>
-                <MFXSlider decimalPrecision="2" prefWidth="150.0"/>
+                <Label text="Decimal Precision of 2" />
+                <MFXSlider decimalPrecision="2" prefWidth="150.0" />
             </VBox>
         </VBox>
-        <AnchorPane maxWidth="-Infinity" minWidth="-Infinity" prefHeight="325.0" prefWidth="250.0" HBox.hgrow="ALWAYS">
-            <MFXSlider layoutX="-33.0" layoutY="160.0" min="-100.0" orientation="VERTICAL" prefWidth="150.0"
-                       AnchorPane.leftAnchor="-50.0"/>
-            <MFXSlider disable="true" layoutX="128.0" layoutY="160.0" orientation="VERTICAL" prefWidth="150.0"
-                       AnchorPane.rightAnchor="-50.0"/>
-            <MFXSlider layoutX="35.0" layoutY="160.0" maxWidth="-Infinity" minWidth="-Infinity"
-                       orientation="VERTICAL" popupSide="OTHER_SIDE" prefWidth="150.0" AnchorPane.leftAnchor="50.0"
-                       AnchorPane.rightAnchor="50.0"/>
-            <Label alignment="CENTER" layoutX="114.0" layoutY="14.0" text="Vertical Sliders"
-                   AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="25.0">
+        <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="250.0" prefWidth="250.0">
+            <MFXSlider layoutX="-33.0" layoutY="160.0" min="-100.0" orientation="VERTICAL" prefWidth="150.0" AnchorPane.leftAnchor="-50.0" />
+            <MFXSlider disable="true" layoutX="128.0" layoutY="160.0" orientation="VERTICAL" prefWidth="150.0" AnchorPane.rightAnchor="-50.0" />
+            <MFXSlider layoutX="35.0" layoutY="160.0" maxWidth="-Infinity" minWidth="-Infinity" orientation="VERTICAL" popupSide="OTHER_SIDE" prefWidth="150.0" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0" />
+            <Label alignment="CENTER" layoutX="114.0" layoutY="14.0" text="Vertical Sliders" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="25.0">
                 <padding>
-                    <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
+                    <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
                 </padding>
             </Label>
         </AnchorPane>

BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/logo.png


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome1.wav


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/assets/welcome2.wav


+ 1 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Common.css

@@ -241,6 +241,7 @@
 }
 
 #customLabel .text {
+    -fx-font-smoothing-type: gray;
     -fx-font-family: "Open Sans Bold";
     -fx-font-size: 12.5;
     -fx-fill: white;

+ 10 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/Demo.css

@@ -128,4 +128,14 @@
     -mfx-track-color: #e6d7ff;
     -mfx-thumb-color: #b885ff;
     -mfx-thumb-hover-color: #9e47ff;
+}
+
+#splashLabel {
+    -fx-font-family: "Open Sans Bold";
+    -fx-font-size: 36;
+    -fx-text-fill: #6314A8;
+}
+
+#splashLabel .text {
+    -fx-font-smoothing-type: gray;
 }

+ 26 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressBarDemo.css

@@ -16,6 +16,8 @@
  * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+@import "MFXColors.css";
+
 #custom .track {
     -fx-fill: derive(salmon, 70%);
 }
@@ -27,4 +29,28 @@
 
 #custom:indeterminate .bar2 {
     -fx-fill: linear-gradient(to bottom right, #F4D03F 0%, #10d7b4 100%);
+}
+
+#determinate:range1 .bar1 {
+    -fx-fill: -mfx-red;
+}
+
+#determinate:range1 .track {
+    -fx-fill: derive(-mfx-red, 85%)
+}
+
+#determinate:range2 .bar1 {
+    -fx-fill: -mfx-blue;
+}
+
+#determinate:range2 .track {
+    -fx-fill: derive(-mfx-blue, 85%)
+}
+
+#determinate:range3 .bar1 {
+    -fx-fill: -mfx-green;
+}
+
+#determinate:range3 .track {
+    -fx-fill: derive(-mfx-green, 85%);
 }

+ 37 - 7
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressSpinnersDemo.css

@@ -16,13 +16,7 @@
  * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-.progress1 .text {
-    -fx-fill: green;
-}
-
-.progress1 .arc {
-    -fx-stroke: purple;
-}
+@import "MFXColors.css";
 
 .progress2 .text {
     visibility: false;
@@ -43,3 +37,39 @@
 #cssSpinner2 .track {
     -fx-stroke: #ffccc5;
 }
+
+#determinate:range1 .text {
+    -fx-fill: -mfx-red;
+}
+
+#determinate:range1 .arc{
+    -fx-stroke: -mfx-red;
+}
+
+#determinate:range1 .track{
+    -fx-stroke: derive(-mfx-red, 85%);
+}
+
+#determinate:range2 .text {
+    -fx-fill: -mfx-blue;
+}
+
+#determinate:range2 .arc{
+    -fx-stroke: -mfx-blue;
+}
+
+#determinate:range2 .track{
+    -fx-stroke: derive(-mfx-blue, 85%);
+}
+
+#determinate:range3 .text {
+    -fx-fill: -mfx-green;
+}
+
+#determinate:range3 .arc{
+    -fx-stroke: -mfx-green;
+}
+
+#determinate:range3 .track{
+    -fx-stroke: derive(-mfx-green, 85%);
+}

+ 17 - 9
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/SlidersDemo.css

@@ -1,31 +1,39 @@
 @import "MFXColors.css";
 
-#customSlider .track {
+#customSlider2 .track {
     -fx-fill: linear-gradient(to bottom right, #6A6AF8 0%, #C850C0 30%, darkorange 100%);
 }
 
-#customSlider .bar {
+#customSlider2 .bar {
     -fx-fill: linear-gradient(to bottom right, #F4D03F 0%, #10d7b4 100%);
 }
 
-#customSlider:min .thumb,
-#customSlider:min .track {
+#customSlider:range1 .thumb,
+#customSlider:range1 .track {
     -mfx-color: -mfx-red;
     -fx-fill: derive(-mfx-red, 85%)
 }
 
-#customSlider:val .thumb,
-#customSlider:val .track {
+#customSlider:range1 .bar {
+    -fx-fill: -mfx-red;
+}
+
+#customSlider:range2 .thumb,
+#customSlider:range2 .track {
     -mfx-color: -mfx-blue;
     -fx-fill: derive(-mfx-blue, 85%)
 }
 
-#customSlider:val .bar {
+#customSlider:range2 .bar {
     -fx-fill: -mfx-blue;
 }
 
-#customSlider:max .bar,
-#customSlider:max .thumb {
+#customSlider:range3 .bar,
+#customSlider:range3 .thumb {
     -mfx-color: -mfx-green;
     -fx-fill: -mfx-green;
+}
+
+#customSlider:range3 .track {
+    -fx-fill: derive(-mfx-green, 85%);
 }

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

@@ -16,14 +16,14 @@
  * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-.mfx-slider:min .track {
+.mfx-slider:range1 .track {
     -fx-fill: red;
 }
 
-.mfx-slider:max .bar {
-    -fx-fill: green;
+.mfx-slider:range2 .track {
+    -fx-fill: gold;
 }
 
-.mfx-slider:val .track {
-    -fx-fill: gold;
-}
+.mfx-slider:range3 .bar {
+    -fx-fill: green;
+}

+ 69 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/beans/NumberRange.java

@@ -18,6 +18,8 @@
 
 package io.github.palexdev.materialfx.beans;
 
+import java.util.List;
+
 /**
  * Simple bean to represent a range of values from min to max.
  *
@@ -52,7 +54,74 @@ public class NumberRange<T extends Number> {
     //================================================================================
     // Static Methods
     //================================================================================
+
+    /**
+     * Returns a new instance of NumberRange with the given min and max bounds.
+     */
     public static <T extends Number> NumberRange<T> of(T min, T max) {
         return new NumberRange<>(min, max);
     }
+
+    /**
+     * Returns a new instance of NumberRange with the given val as both min and max bounds.
+     */
+    public static <T extends Number> NumberRange<T> of(T val) {
+        return new NumberRange<>(val, val);
+    }
+
+    /**
+     * Checks if the given value is contained in the given range (bounds are included).
+     */
+    public static boolean inRangeOf(double val, NumberRange<Double> range) {
+        return Math.max(range.getMin(), val) == Math.min(val, range.getMax());
+    }
+
+    /**
+     * Checks if the given value is contained in the given range (bounds are included).
+     */
+    public static boolean inRangeOf(float val, NumberRange<Float> range) {
+        return Math.max(range.getMin(), val) == Math.min(val, range.getMax());
+    }
+
+    /**
+     * Checks if the given value is contained in the given range (bounds are included).
+     */
+    public static boolean inRangeOf(int val, NumberRange<Integer> range) {
+        return Math.max(range.getMin(), val) == Math.min(val, range.getMax());
+    }
+
+    /**
+     * Checks if the given value is contained in the given range (bounds are included).
+     */
+    public static boolean inRangeOf(long val, NumberRange<Long> range) {
+        return Math.max(range.getMin(), val) == Math.min(val, range.getMax());
+    }
+
+    /**
+     * Checks if the given value is contained in any of the given ranges (bounds are included).
+     */
+    public static boolean inRangeOf(double val, List<NumberRange<Double>> ranges) {
+        return ranges.stream().anyMatch(range -> inRangeOf(val, range));
+    }
+
+    /**
+     * Checks if the given value is contained in any of the given ranges (bounds are included).
+     */
+    public static boolean inRangeOf(float val, List<NumberRange<Float>> ranges) {
+        return ranges.stream().anyMatch(range -> inRangeOf(val, range));
+    }
+
+    /**
+     * Checks if the given value is contained in any of the given ranges (bounds are included).
+     */
+    public static boolean inRangeOf(int val, List<NumberRange<Integer>> ranges) {
+        return ranges.stream().anyMatch(range -> inRangeOf(val, range));
+    }
+
+    /**
+     * Checks if the given value is contained in any of the given ranges (bounds are included).
+     */
+    public static boolean inRangeOf(long val, List<NumberRange<Long>> ranges) {
+        return ranges.stream().anyMatch(range -> inRangeOf(val, range));
+    }
 }

+ 71 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressBar.java

@@ -19,7 +19,11 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.skins.MFXProgressBarSkin;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
 import javafx.css.*;
 import javafx.scene.control.ProgressBar;
 import javafx.scene.control.Skin;
@@ -28,10 +32,19 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
+
 /**
  * This is the implementation of a progress bar following Google's material design guidelines.
  * <p>
  * Extends {@code ProgressBar} and redefines the style class to "mfx-progress-bar" for usage in CSS.
+ * <p></p>
+ * MFXProgressBar introduces three new css pseudo classes:
+ * <p> - ":range1", activated when the bar value is contained in any of the ranges specified in here {@link #getRanges1()}
+ * <p> - ":range2", activated when the bar value is contained in any of the ranges specified in here {@link #getRanges2()}
+ * <p> - ":range3", activated when the bar value is contained in any of the ranges specified in here {@link #getRanges3()}
+ * <p>
+ * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  */
 public class MFXProgressBar extends ProgressBar {
     //================================================================================
@@ -41,6 +54,13 @@ public class MFXProgressBar extends ProgressBar {
     private final String STYLE_CLASS = "mfx-progress-bar";
     private final String STYLESHEETS = MFXResourcesLoader.load("css/MFXProgressBar.css");
 
+    private final ObservableList<NumberRange<Double>> ranges1 = FXCollections.observableArrayList();
+    private final ObservableList<NumberRange<Double>> ranges2 = FXCollections.observableArrayList();
+    private final ObservableList<NumberRange<Double>> ranges3 = FXCollections.observableArrayList();
+    protected final PseudoClass RANGE1_PSEUDO_CLASS = PseudoClass.getPseudoClass("range1");
+    protected final PseudoClass RANGE2_PSEUDO_CLASS = PseudoClass.getPseudoClass("range2");
+    protected final PseudoClass RANGE3_PSEUDO_CLASS = PseudoClass.getPseudoClass("range3");
+
     //================================================================================
     // Constructors
     //================================================================================
@@ -59,6 +79,57 @@ public class MFXProgressBar extends ProgressBar {
     private void initialize() {
         getStyleClass().add(STYLE_CLASS);
         setPrefWidth(200);
+
+        addListeners();
+    }
+
+    private void addListeners() {
+        ranges1.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        ranges2.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        ranges3.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        progressProperty().addListener((observable, oldValue, newValue) -> handlePseudoClasses());
+    }
+
+    /**
+     * Handles the ":range1", ":range2" and ":range3" css pseudo classes when these properties change:
+     * {@link #progressProperty()}, {@link #getRanges1()}, {@link #getRanges2()}, {@link #getRanges3()}.
+     */
+    private void handlePseudoClasses() {
+        double val = getProgress();
+        if (!isPseudoClassActive(this, RANGE1_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges1)) {
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, false);
+        } else if (!isPseudoClassActive(this, RANGE2_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges2)) {
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, false);
+        } else if (!isPseudoClassActive(this, RANGE3_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges3)) {
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, false);
+        }
+    }
+
+    /**
+     * Returns the first list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges1() {
+        return ranges1;
+    }
+
+    /**
+     * Returns the second list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges2() {
+        return ranges2;
+    }
+
+    /**
+     * Returns the third list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges3() {
+        return ranges3;
     }
 
     //================================================================================

+ 71 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressSpinner.java

@@ -19,7 +19,11 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.skins.MFXProgressSpinnerSkin;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
 import javafx.css.*;
 import javafx.scene.control.ProgressIndicator;
 import javafx.scene.control.Skin;
@@ -29,10 +33,19 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
+
 /**
  * Implementation of a spinning {@code ProgressIndicator}.
  * <p>
- * Extends {@link ProgressIndicator}
+ * Extends {@link ProgressIndicator}.
+ * <p></p>
+ * MFXProgressSpinner introduces three new css pseudo classes:
+ * <p> - ":range1", activated when the spinner value is contained in any of the ranges specified in here {@link #getRanges1()}
+ * <p> - ":range2", activated when the spinner value is contained in any of the ranges specified in here {@link #getRanges2()}
+ * <p> - ":range3", activated when the spinner value is contained in any of the ranges specified in here {@link #getRanges3()}
+ * <p>
+ * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  */
 public class MFXProgressSpinner extends ProgressIndicator {
     //================================================================================
@@ -42,6 +55,13 @@ public class MFXProgressSpinner extends ProgressIndicator {
     private final String STYLE_CLASS = "mfx-progress-spinner";
     private final String STYLESHEET = MFXResourcesLoader.load("css/MFXProgressSpinner.css");
 
+    private final ObservableList<NumberRange<Double>> ranges1 = FXCollections.observableArrayList();
+    private final ObservableList<NumberRange<Double>> ranges2 = FXCollections.observableArrayList();
+    private final ObservableList<NumberRange<Double>> ranges3 = FXCollections.observableArrayList();
+    protected final PseudoClass RANGE1_PSEUDO_CLASS = PseudoClass.getPseudoClass("range1");
+    protected final PseudoClass RANGE2_PSEUDO_CLASS = PseudoClass.getPseudoClass("range2");
+    protected final PseudoClass RANGE3_PSEUDO_CLASS = PseudoClass.getPseudoClass("range3");
+
     //================================================================================
     // Constructors
     //================================================================================
@@ -59,6 +79,56 @@ public class MFXProgressSpinner extends ProgressIndicator {
     //================================================================================
     private void initialize() {
         getStyleClass().add(STYLE_CLASS);
+        addListeners();
+    }
+
+    private void addListeners() {
+        ranges1.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        ranges2.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        ranges3.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        progressProperty().addListener((observable, oldValue, newValue) -> handlePseudoClasses());
+    }
+
+    /**
+     * Handles the ":range1", ":range2" and ":range3" css pseudo classes when these properties change:
+     * {@link #progressProperty()}, {@link #getRanges1()}, {@link #getRanges2()}, {@link #getRanges3()}.
+     */
+    private void handlePseudoClasses() {
+        double val = getProgress();
+        if (!isPseudoClassActive(this, RANGE1_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges1)) {
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, false);
+        } else if (!isPseudoClassActive(this, RANGE2_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges2)) {
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, false);
+        } else if (!isPseudoClassActive(this, RANGE3_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges3)) {
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, false);
+        }
+    }
+
+    /**
+     * Returns the first list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges1() {
+        return ranges1;
+    }
+
+    /**
+     * Returns the second list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges2() {
+        return ranges2;
+    }
+
+    /**
+     * Returns the third list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges3() {
+        return ranges3;
     }
 
     //================================================================================

+ 47 - 29
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java

@@ -19,6 +19,7 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.controls.enums.SliderEnums.SliderMode;
 import io.github.palexdev.materialfx.controls.enums.SliderEnums.SliderPopupSide;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
@@ -29,6 +30,9 @@ import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.NumberUtils;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.*;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
 import javafx.css.*;
 import javafx.geometry.Orientation;
 import javafx.geometry.Pos;
@@ -47,6 +51,8 @@ import javafx.scene.text.TextBoundsType;
 import java.util.List;
 import java.util.function.Supplier;
 
+import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
+
 /**
  * This is the implementation of a Slider following Google's material design guidelines.
  * <p></p>
@@ -86,9 +92,11 @@ import java.util.function.Supplier;
  * that if the minimum value is negative the bar will progress on the opposite side to zero.
  * <p></p>
  * MFXSlider introduces three new css pseudo classes:
- * <p> - ":min", activated when the slider reaches the minimum value specified by {@link #minProperty()}
- * <p> - ":max", activated when the slider reaches the maximum value specified by {@link #maxProperty()}
- * <p> - ":val", activated when the slider reaches the value specified by {@link #cssValProperty()}
+ * <p> - ":range1", activated when the slider value is contained in any of the ranges specified in here {@link #getRanges1()}
+ * <p> - ":range2", activated when the slider value is contained in any of the ranges specified in here {@link #getRanges2()}
+ * <p> - ":range3", activated when the slider value is contained in any of the ranges specified in here {@link #getRanges3()}
+ * <p>
+ * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  * <p></p>
  * <b>WARNING!</b>
  * <p>
@@ -148,10 +156,12 @@ public class MFXSlider extends Control {
     private final IntegerProperty decimalPrecision = new SimpleIntegerProperty(0);
     private final BooleanProperty enableKeyboard = new SimpleBooleanProperty(true);
 
-    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");
+    private final ObservableList<NumberRange<Double>> ranges1 = FXCollections.observableArrayList();
+    private final ObservableList<NumberRange<Double>> ranges2 = FXCollections.observableArrayList();
+    private final ObservableList<NumberRange<Double>> ranges3 = FXCollections.observableArrayList();
+    protected final PseudoClass RANGE1_PSEUDO_CLASS = PseudoClass.getPseudoClass("range1");
+    protected final PseudoClass RANGE2_PSEUDO_CLASS = PseudoClass.getPseudoClass("range2");
+    protected final PseudoClass RANGE3_PSEUDO_CLASS = PseudoClass.getPseudoClass("range3");
 
     //================================================================================
     // Constructors
@@ -190,28 +200,30 @@ public class MFXSlider extends Control {
     }
 
     private void addListeners() {
-        min.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
-        max.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
+        ranges1.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        ranges2.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
+        ranges3.addListener((ListChangeListener<? super NumberRange<Double>>) c -> handlePseudoClasses());
         value.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
-        cssVal.addListener((observable, oldValue, newValue) -> handlePseudoClasses());
     }
 
     /**
-     * Handles the ":min", ":max" and ":val" css pseudo classes when these properties change:
-     * {@link #minProperty()}, {@link #maxProperty()}, {@link #valueProperty()}, {@link #cssValProperty()}.
+     * Handles the ":range1", ":range2" and ":range3" css pseudo classes when these properties change:
+     * {@link #valueProperty()}, {@link #getRanges1()}, {@link #getRanges2()}, {@link #getRanges3()}.
      */
     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);
+        if (!isPseudoClassActive(this, RANGE1_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges1)) {
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, false);
+        } else if (!isPseudoClassActive(this, RANGE2_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges2)) {
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, false);
+        } else if (!isPseudoClassActive(this, RANGE3_PSEUDO_CLASS) && NumberRange.inRangeOf(val, ranges3)) {
+            pseudoClassStateChanged(RANGE3_PSEUDO_CLASS, true);
+            pseudoClassStateChanged(RANGE1_PSEUDO_CLASS, false);
+            pseudoClassStateChanged(RANGE2_PSEUDO_CLASS, false);
         }
     }
 
@@ -447,19 +459,25 @@ public class MFXSlider extends Control {
         this.enableKeyboard.set(enableKeyboard);
     }
 
-    public double getCssVal() {
-        return cssVal.get();
+    /**
+     * Returns the first list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges1() {
+        return ranges1;
     }
 
     /**
-     * Specifies the value at which the ":val" pseudo class should be activated.
+     * Returns the second list of ranges.
      */
-    public DoubleProperty cssValProperty() {
-        return cssVal;
+    public ObservableList<NumberRange<Double>> getRanges2() {
+        return ranges2;
     }
 
-    public void setCssVal(double cssVal) {
-        this.cssVal.set(cssVal);
+    /**
+     * Returns the third list of ranges.
+     */
+    public ObservableList<NumberRange<Double>> getRanges3() {
+        return ranges3;
     }
 
     //================================================================================

+ 5 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/utils/NodeUtils.java

@@ -20,6 +20,7 @@ package io.github.palexdev.materialfx.utils;
 
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
+import javafx.css.PseudoClass;
 import javafx.event.Event;
 import javafx.geometry.*;
 import javafx.scene.Group;
@@ -362,6 +363,10 @@ public class NodeUtils {
         }
     }
 
+    public static boolean isPseudoClassActive(Control control, PseudoClass pseudoClass) {
+        return control.getPseudoClassStates().contains(pseudoClass);
+    }
+
     //================================================================================
     // JavaFX private methods
     //================================================================================