Browse Source

Merge pull request #45 from palexdev/staging

Version 11.11.1, merge from staging
Alessadro Parisi 4 years ago
parent
commit
c7bff884b0
46 changed files with 1289 additions and 548 deletions
  1. 130 2
      README.md
  2. 16 0
      ROADMAP.md
  3. 1 1
      build.gradle
  4. 2 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/FontResourcesDemoController.java
  5. 14 4
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ListViewsDemoController.java
  6. 3 3
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TextFieldsDemoController.java
  7. 21 23
      demo/src/main/resources/io/github/palexdev/materialfx/demo/CheckBoxesDemo.fxml
  8. 2 2
      demo/src/main/resources/io/github/palexdev/materialfx/demo/DatePickersDemo.fxml
  9. 84 73
      demo/src/main/resources/io/github/palexdev/materialfx/demo/ListViewsDemo.fxml
  10. 8 7
      demo/src/main/resources/io/github/palexdev/materialfx/demo/TextFieldsDemo.fxml
  11. 23 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/MFXColors.css
  12. 9 6
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/ProgressBarDemo.css
  13. 62 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TextFieldsDemo.css
  14. 1 1
      materialfx/gradle.properties
  15. 26 22
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java
  16. 46 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java
  17. 2 16
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java
  18. 56 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressBar.java
  19. 38 25
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java
  20. 3 2
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXFlowlessListCell.java
  21. 39 22
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXFlowlessCheckListCell.java
  22. 29 27
      materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java
  23. 25 0
      materialfx/src/main/java/io/github/palexdev/materialfx/selection/ListCheckModel.java
  24. 4 0
      materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/IListCheckModel.java
  25. 5 23
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java
  26. 22 2
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java
  27. 7 10
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java
  28. 39 19
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXLabelSkin.java
  29. 6 3
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPasswordFieldSkin.java
  30. 135 137
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXProgressBarSkin.java
  31. 89 75
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperSkin.java
  32. 25 28
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTextFieldSkin.java
  33. 110 1
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/ColorUtils.java
  34. 1 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXColors.css
  35. 50 2
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle1.css
  36. 2 2
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle2.css
  37. 48 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle3.css
  38. 10 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXFilterComboBox.css
  39. 6 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle1.css
  40. 10 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle2.css
  41. 5 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle3.css
  42. 8 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXPasswordField.css
  43. 11 10
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXProgressBar.css
  44. 8 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXStepper.css
  45. 48 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTextField.css
  46. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf

+ 130 - 2
README.md

@@ -35,6 +35,7 @@
 ## Table of Contents
 
 * [About the Project and History of JavaFX](#about-the-project-and-history-of-javafx)
+* [Some GIFs](#preview-gifs)
 * [Getting Started](#getting-started)
     * [Build](#build)
     * [Usage](#usage)
@@ -76,6 +77,133 @@ available [JFoenix](https://github.com/jfoenixadmin/JFoenix) library, which is a
 In recent months the project has evolved a lot, to the point that it is no longer a simple substitute.
 To date MaterialFX offers not only restyled controls, but also: new and unique controls such as the Stepper, controls completely redone from scratch such as ComboBoxes or TableViews (and many others), and many utilities for JavaFX and Java (NodeUtils, ColorUtils, StringUtils ...).
 
+<!-- PREVIEW GIFS -->
+
+## Preview GIFs
+
+#### Imgur Link: [Gallery](https://imgur.com/gallery/J0HUrSZ)
+
+<i>
+<details>
+<summary>Buttons</summary>
+<br>
+<a href="https://imgur.com/n3bKDN9"><img src="https://i.imgur.com/n3bKDN9.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>CheckBoxes</summary>
+<br>
+<a href="https://imgur.com/gbf69gq"><img src="https://i.imgur.com/gbf69gq.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>ComboBoxes</summary>
+<br>
+<a href="https://imgur.com/uS8rwgS"><img src="https://i.imgur.com/uS8rwgS.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>DatePickers</summary>
+<br>
+<a href="https://imgur.com/QVOBaXG"><img src="https://imgur.com/QVOBaXG.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>Dialogs</summary>
+<br>
+<a href="https://imgur.com/ilKuKF9"><img src="https://imgur.com/ilKuKF9.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>Labels</summary>
+<br>
+<a href="https://imgur.com/XWO40LR"><img src="https://imgur.com/XWO40LR.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>ListViews</summary>
+<br>
+<a href="https://imgur.com/WhYKgUI"><img src="https://imgur.com/WhYKgUI.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>Notifications</summary>
+<br>
+<a href="https://imgur.com/lT8zqzV"><img src="https://imgur.com/lT8zqzV.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>ProgressBars</summary>
+<br>
+<a href="https://imgur.com/8FEW5bt"><img src="https://imgur.com/8FEW5bt.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>ProgressSpinners</summary>
+<br>
+<a href="https://imgur.com/x0FiFXW"><img src="https://imgur.com/x0FiFXW.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>RadioButtons</summary>
+<br>
+<a href="https://imgur.com/i2fEmjB"><img src="https://imgur.com/i2fEmjB.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>ScrollPanes</summary>
+<br>
+<a href="https://imgur.com/bqRADeQ"><img src="https://imgur.com/bqRADeQ.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>Stepper</summary>
+<br>
+<a href="https://imgur.com/tNuAwF4"><img src="https://imgur.com/tNuAwF4.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>TableViews</summary>
+<br>
+<a href="https://imgur.com/GZYQtDT"><img src="https://imgur.com/GZYQtDT.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>TextFields</summary>
+<br>
+<a href="https://imgur.com/NYp8LJV"><img src="https://imgur.com/NYp8LJV.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>Toggles</summary>
+<br>
+<a href="https://imgur.com/HcXXi8q"><img src="https://imgur.com/HcXXi8q.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+
+<details>
+<summary>FontResources</summary>
+<br>
+<a href="https://imgur.com/PkCZM0v"><img src="https://imgur.com/PkCZM0v.gif" title="source: imgur.com" /></a>
+</details>
+<p></p>
+</i>
+
 <!-- GETTING STARTED -->
 
 ## Getting Started
@@ -105,7 +233,7 @@ repositories {
 }
 
 dependencies {
-implementation 'io.github.palexdev:materialfx:11.11.0'
+implementation 'io.github.palexdev:materialfx:11.11.1'
 }
 ```
 
@@ -115,7 +243,7 @@ implementation 'io.github.palexdev:materialfx:11.11.0'
 <dependency>
   <groupId>io.github.palexdev</groupId>
   <artifactId>materialfx</artifactId>
-  <version>11.11.0</version>
+  <version>11.11.1</version>
 </dependency>
 ```
 

+ 16 - 0
ROADMAP.md

@@ -0,0 +1,16 @@
+# TODOs and Future Plans
+
+- [ ] *MFXCard*
+- [ ] *MFXChipView(?)*
+- [ ] *MFXSlider*
+- [ ] *MFXHighlighter(?)*
+  <br></br>
+- [ ] *MFXColorPicker*
+- [ ] *MFXDateTimePicker(?)*
+- [ ] *MFXTimePicker*
+  <br></br>
+- [ ] *MFXCheckboxTableView*
+- [ ] *Scrollable MFXTableView*
+  <br></br>
+- [ ] *MFXToastNotification*
+- [ ] *Improve Notification System(?)*

+ 1 - 1
build.gradle

@@ -4,7 +4,7 @@ plugins {
 }
 
 group 'io.github.palexdev'
-version '11.11.0'
+version '11.11.1'
 
 repositories {
     mavenCentral()

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

@@ -77,6 +77,7 @@ public class FontResourcesDemoController implements Initializable {
         l1.setStyle("-fx-background-color: transparent");
         l1.setText("Description: " + fontResource.getDescription());
         l1.setMinWidth(300);
+        l1.setAlignment(Pos.CENTER_LEFT);
 
         MFXLabel l2 = new MFXLabel();
         l2.setLineColor(Color.TRANSPARENT);
@@ -84,6 +85,7 @@ public class FontResourcesDemoController implements Initializable {
         l2.setStyle("-fx-background-color: transparent");
         l2.setText("Code: " + Integer.toHexString(fontResource.getCode() | 0x10000).substring(1).toUpperCase());
         l2.setMinWidth(300);
+        l2.setAlignment(Pos.CENTER_LEFT);
 
         Separator s1 = new Separator(Orientation.VERTICAL);
         s1.setStyle("-fx-fill: white");

+ 14 - 4
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ListViewDemoController.java → demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/ListViewsDemoController.java

@@ -39,7 +39,7 @@ import java.util.List;
 import java.util.Random;
 import java.util.ResourceBundle;
 
-public class ListViewDemoController implements Initializable {
+public class ListViewsDemoController implements Initializable {
     private final Random random = new Random(System.currentTimeMillis());
 
     private enum State {
@@ -82,7 +82,7 @@ public class ListViewDemoController implements Initializable {
     private MFXFlowlessListView<String> cssViewNew;
 
     @FXML
-    private MFXButton switchButton;
+    private MFXButton swapButton;
 
     @FXML
     private MFXButton depthButton;
@@ -90,6 +90,12 @@ public class ListViewDemoController implements Initializable {
     @FXML
     private MFXButton colorsButton;
 
+    @FXML
+    private Label mulLabel;
+
+    @FXML
+    private MFXCheckbox allowSelection;
+
     private ObservableList<String> stringList;
     private ObservableList<Label> labelsList;
     private ObservableList<HBox> hBoxesList;
@@ -103,14 +109,18 @@ public class ListViewDemoController implements Initializable {
 
         state.addListener((observable, oldValue, newValue) -> {
             if (newValue == State.NEW) {
+                mulLabel.setVisible(true);
                 legacyBox.setVisible(false);
                 newBox.setVisible(true);
             } else {
+                mulLabel.setVisible(false);
                 legacyBox.setVisible(true);
                 newBox.setVisible(false);
             }
         });
 
+        checkList.getSelectionModel().allowsSelectionProperty().bind(allowSelection.selectedProperty());
+
         //  LEGACY //
         stringView.setItems(stringList);
         labelView.setItems(labelsList);
@@ -124,7 +134,7 @@ public class ListViewDemoController implements Initializable {
         checkList.setItems(stringList);
         cssViewNew.setItems(stringList);
 
-        switchButton.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> updateState());
+        swapButton.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> updateState());
         depthButton.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> updateDepth());
         colorsButton.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> updateColors());
 
@@ -235,7 +245,7 @@ public class ListViewDemoController implements Initializable {
 
     private void updateState() {
         State curr = state.get();
-        switchButton.setText(curr == State.LEGACY ?  "Switch to Legacy" : "Switch to New");
+        swapButton.setText(curr == State.LEGACY ?  "Switch to Legacy" : "Switch to New");
         state.set(curr == State.LEGACY ? State.NEW : State.LEGACY);
     }
 

+ 3 - 3
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/TextFieldsDemoController.java

@@ -80,15 +80,15 @@ public class TextFieldsDemoController implements Initializable {
         passwordValidated.setValidated(true);
         passwordValidated.getValidator().add(
                 BindingUtils.toProperty(passwordValidated.passwordProperty().length().greaterThanOrEqualTo(8)),
-                "Password must be at least 8 characters long"
+                "Must be at least 8 characters long"
         );
         passwordValidated.getValidator().add(BindingUtils.toProperty(
                 Bindings.createBooleanBinding(() -> passwordValidated.getPassword().matches(".*\\d.*"), passwordValidated.passwordProperty())),
-                "Password must contain at least one digit"
+                "Must contain at least one digit"
         );
         passwordValidated.getValidator().add(BindingUtils.toProperty(
                 Bindings.createBooleanBinding(() -> StringUtils.containsAny(passwordValidated.getPassword(), "", "?", "!", "@", "(", ")", "[", "]", "{", "}", "-", "_"), passwordValidated.passwordProperty())),
-                "Password must contain at least one special character among these: ?!@()[]{}-_"
+                "Must contain at least one among these: ?!@()[]{}-_"
         );
     }
 }

+ 21 - 23
demo/src/main/resources/io/github/palexdev/materialfx/demo/CheckBoxesDemo.fxml

@@ -8,7 +8,7 @@
 <?import javafx.scene.paint.Stop?>
 <StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
            prefWidth="600.0" stylesheets="@css/CheckBoxesDemo.css" xmlns="http://javafx.com/javafx/16">
-    <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Checkboxes color" StackPane.alignment="TOP_CENTER">
+    <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Checkboxes" StackPane.alignment="TOP_CENTER">
         <StackPane.margin>
             <Insets top="20.0" />
         </StackPane.margin>
@@ -48,32 +48,30 @@
     <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Checkboxes Mark Types" />
     <GridPane hgap="25.0" maxHeight="-Infinity" maxWidth="-Infinity" vgap="10.0">
         <columnConstraints>
-            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
-            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
-            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
-            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
+            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
+            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
+            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
+            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
         </columnConstraints>
         <rowConstraints>
-            <RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
-            <RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
-            <RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
+            <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
+            <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
+            <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
         </rowConstraints>
         <StackPane.margin>
-            <Insets top="180.0"/>
+            <Insets top="180.0" />
         </StackPane.margin>
-        <MFXCheckbox markType="mfx-caspian-mark" text="Caspian"/>
-        <MFXCheckbox markType="mfx-modena-mark" text="Modena" GridPane.columnIndex="1"/>
-        <MFXCheckbox markType="mfx-variant3-mark" text="Variant 3" GridPane.columnIndex="2"/>
-        <MFXCheckbox markType="mfx-variant4-mark" text="Variant 4" GridPane.columnIndex="3"/>
-        <MFXCheckbox markType="mfx-variant5-mark" text="Variant 5" GridPane.rowIndex="1"/>
-        <MFXCheckbox markType="mfx-variant6-mark" text="Variant 6" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
-        <MFXCheckbox markType="mfx-variant7-mark" text="Variant 7" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
-        <MFXCheckbox markSize="13.0" markType="mfx-variant8-mark" text="Variant 8" GridPane.columnIndex="3"
-                     GridPane.rowIndex="1"/>
-        <MFXCheckbox markType="mfx-variant9-mark" text="Variant 9" GridPane.rowIndex="2"/>
-        <MFXCheckbox markType="mfx-variant10-mark" text="Variant 10" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
-        <MFXCheckbox markType="mfx-variant11-mark" text="Variant 11" GridPane.columnIndex="2" GridPane.rowIndex="2"/>
-        <MFXCheckbox markSize="9.0" markType="mfx-variant12-mark" text="Variant 12" GridPane.columnIndex="3"
-                     GridPane.rowIndex="2"/>
+        <MFXCheckbox markType="mfx-caspian-mark" text="Caspian" />
+        <MFXCheckbox markType="mfx-modena-mark" text="Modena" GridPane.columnIndex="1" />
+        <MFXCheckbox markType="mfx-variant3-mark" text="Variant 3" GridPane.columnIndex="2" />
+        <MFXCheckbox markType="mfx-variant4-mark" text="Variant 4" GridPane.columnIndex="3" />
+        <MFXCheckbox markType="mfx-variant5-mark" text="Variant 5" GridPane.rowIndex="1" />
+        <MFXCheckbox markType="mfx-variant6-mark" text="Variant 6" GridPane.columnIndex="1" GridPane.rowIndex="1" />
+        <MFXCheckbox markType="mfx-variant7-mark" text="Variant 7" GridPane.columnIndex="2" GridPane.rowIndex="1" />
+        <MFXCheckbox markSize="13.0" markType="mfx-variant8-mark" text="Variant 8" GridPane.columnIndex="3" GridPane.rowIndex="1" />
+        <MFXCheckbox markType="mfx-variant9-mark" text="Variant 9" GridPane.rowIndex="2" />
+        <MFXCheckbox markType="mfx-variant10-mark" text="Variant 10" GridPane.columnIndex="1" GridPane.rowIndex="2" />
+        <MFXCheckbox markType="mfx-variant11-mark" text="Variant 11" GridPane.columnIndex="2" GridPane.rowIndex="2" />
+        <MFXCheckbox markSize="9.0" markType="mfx-variant12-mark" text="Variant 12" GridPane.columnIndex="3" GridPane.rowIndex="2" />
     </GridPane>
 </StackPane>

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

@@ -4,7 +4,7 @@
 <?import javafx.geometry.*?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.*?>
-<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/DatePickersDemo.css" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.DatePickersDemoController">
+<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/DatePickersDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.DatePickersDemoController">
    <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Date Pickers" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
          <Insets top="20.0" />
@@ -15,7 +15,7 @@
          <Insets right="300.0" top="70.0" />
       </StackPane.margin>
    </MFXDatePicker>
-   <MFXDatePicker closeOnDaySelected="false" colorText="true" StackPane.alignment="TOP_CENTER">
+   <MFXDatePicker closeOnDaySelected="false" closeOnEnter="true" colorText="true" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
          <Insets top="70.0" />
       </StackPane.margin>

+ 84 - 73
demo/src/main/resources/io/github/palexdev/materialfx/demo/ListViewsDemo.fxml

@@ -4,76 +4,87 @@
 <?import javafx.geometry.*?>
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
-<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="920.0" stylesheets="@css/ListViewsDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.ListViewDemoController">
-    <padding>
-        <Insets left="20.0" right="20.0" />
-    </padding>
-    <Label id="customLabel" alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="ListViews" StackPane.alignment="TOP_CENTER">
-        <StackPane.margin>
-            <Insets top="20.0" />
-        </StackPane.margin>
-    </Label>
-    <HBox fx:id="legacyBox" alignment="TOP_CENTER" maxHeight="-Infinity" prefHeight="250.0" prefWidth="680.0" spacing="20.0" visible="false">
-        <StackPane.margin>
-            <Insets top="40.0" />
-        </StackPane.margin>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="130.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Standard" />
-            <MFXListView fx:id="stringView" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="200.0" prefWidth="120.0" />
-        </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="160.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Labels" />
-            <MFXListView fx:id="labelView" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="200.0" prefWidth="150.0" />
-        </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="265.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="HBoxes" />
-            <MFXListView fx:id="hBoxView" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="200.0" prefWidth="265.0" />
-        </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="135.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="125.0" text="Customized and CSS" />
-            <MFXListView id="customView" fx:id="cssView" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="200.0" prefWidth="110.0" stylesheets="@css/ListViewsDemo.css" />
-        </VBox>
-    </HBox>
-    <HBox fx:id="newBox" alignment="TOP_CENTER" maxHeight="-Infinity" prefHeight="250.0" prefWidth="680.0" spacing="20.0">
-        <StackPane.margin>
-            <Insets top="40.0" />
-        </StackPane.margin>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="130.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Standard" />
-            <MFXFlowlessListView fx:id="stringViewNew" />
-        </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="160.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Labels" />
-            <MFXFlowlessListView fx:id="labelViewNew" />
-        </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="265.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="HBoxes" />
-            <MFXFlowlessListView fx:id="hBoxViewNew" />
-        </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="135.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="125.0" text="CheckListView" />
-            <MFXFlowlessCheckListView fx:id="checkList" />
-        </VBox>
-        <VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="135.0" spacing="10.0">
-            <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="125.0" text="Customized and CSS" />
-            <MFXFlowlessListView id="customViewNew" fx:id="cssViewNew" />
-        </VBox>
-    </HBox>
-    <VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="90.0" prefWidth="128.0" spacing="10.0" StackPane.alignment="BOTTOM_RIGHT">
-        <StackPane.margin>
-            <Insets bottom="5.0" />
-        </StackPane.margin>
-        <MFXButton fx:id="depthButton" prefWidth="70.0" text="3D" />
-        <MFXButton fx:id="colorsButton" text="Change bars color" />
-    </VBox>
-   <MFXButton fx:id="switchButton" buttonType="RAISED" prefWidth="120.0" text="Switch to Legacy" StackPane.alignment="TOP_CENTER">
-      <StackPane.margin>
-         <Insets top="75.0" />
-      </StackPane.margin>
-   </MFXButton>
-   <Label text="Multiple Selection On" StackPane.alignment="TOP_LEFT">
-      <StackPane.margin>
-         <Insets left="6.0" top="115.0" />
-      </StackPane.margin>
-   </Label>
-</StackPane>
+<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="920.0" stylesheets="@css/ListViewsDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.ListViewsDemoController">
+   <Label id="customLabel" alignment="CENTER" layoutX="327.0" layoutY="14.0" prefHeight="26.0" prefWidth="266.0" text="ListViews" AnchorPane.leftAnchor="327.0" AnchorPane.rightAnchor="327.0" AnchorPane.topAnchor="15.0" />
+   <HBox fx:id="legacyBox" alignment="CENTER" layoutX="5.0" layoutY="68.0" spacing="10.0" visible="false" AnchorPane.bottomAnchor="100.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="60.0">
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Standard">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXListView fx:id="stringView" depthLevel="LEVEL0" prefHeight="200.0" />
+      </VBox>
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Labels">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXListView fx:id="labelView" depthLevel="LEVEL0" prefHeight="200.0" />
+      </VBox>
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="HBoxes">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXListView fx:id="hBoxView" depthLevel="LEVEL0" prefHeight="200.0" prefWidth="250.0" />
+      </VBox>
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Customized and CSS">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXListView id="customView" fx:id="cssView" depthLevel="LEVEL0" prefHeight="200.0" />
+      </VBox>
+   </HBox>
+   <HBox fx:id="newBox" alignment="CENTER" layoutX="-5.0" layoutY="58.0" spacing="10.0" AnchorPane.bottomAnchor="100.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="60.0">
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Standard">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXFlowlessListView fx:id="stringViewNew" prefHeight="220.0" prefWidth="150.0" />
+         <Label fx:id="mulLabel" text="Multiple Selection On" />
+      </VBox>
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Labels">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXFlowlessListView fx:id="labelViewNew" prefHeight="220.0" prefWidth="150.0" />
+      </VBox>
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="HBoxes">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXFlowlessListView fx:id="hBoxViewNew" prefHeight="220.0" prefWidth="250.0" />
+      </VBox>
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="CheckListView">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXFlowlessCheckListView fx:id="checkList" prefHeight="220.0" prefWidth="150.0" />
+         <MFXCheckbox fx:id="allowSelection" markType="mfx-variant7-mark" text="Allow Selection" />
+      </VBox>
+      <VBox alignment="TOP_CENTER" spacing="10.0">
+         <Label id="label" alignment="CENTER" prefHeight="25.0" prefWidth="110.0" text="Customized and CSS">
+            <VBox.margin>
+               <Insets top="10.0" />
+            </VBox.margin>
+         </Label>
+         <MFXFlowlessListView id="customViewNew" fx:id="cssViewNew" prefHeight="220.0" prefWidth="150.0" />
+      </VBox>
+   </HBox>
+   <MFXButton fx:id="swapButton" buttonType="RAISED" depthLevel="LEVEL1" layoutX="385.0" layoutY="461.0" prefWidth="150.0" text="Swap" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="385.0" AnchorPane.rightAnchor="385.0" />
+   <MFXButton fx:id="depthButton" buttonType="RAISED" depthLevel="LEVEL1" layoutX="774.0" layoutY="440.0" text="3D On/Off" AnchorPane.bottomAnchor="50.0" />
+   <MFXButton fx:id="colorsButton" buttonType="RAISED" depthLevel="LEVEL1" layoutX="753.0" layoutY="475.0" text="Change Bar Colors" AnchorPane.bottomAnchor="10.0" />
+</AnchorPane>

+ 8 - 7
demo/src/main/resources/io/github/palexdev/materialfx/demo/TextFieldsDemo.fxml

@@ -4,7 +4,7 @@
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.*?>
-<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="450.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.TextFieldsDemoController">
+<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="450.0" prefWidth="650.0" stylesheets="@css/TextFieldsDemo.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.TextFieldsDemoController">
    <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="14.0" prefHeight="26.0" prefWidth="266.0" text="TextFields" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="20.0" />
    <HBox alignment="CENTER" layoutX="200.0" layoutY="46.0" prefWidth="200.0" spacing="60.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="60.0">
       <padding>
@@ -19,8 +19,8 @@
       <padding>
          <Insets bottom="20.0" top="20.0" />
       </padding>
-      <MFXTextField id="colors" alignment="CENTER" lineColor="#52db32" maxWidth="-Infinity" prefWidth="120.0" promptText="Colors" />
-      <MFXTextField fx:id="validated" lineColor="#52db32" maxWidth="-Infinity" prefWidth="120.0" text="Validation" />
+      <MFXTextField id="colors" alignment="CENTER" maxWidth="-Infinity" prefWidth="120.0" promptText="Colors" />
+      <MFXTextField fx:id="validated" lineColor="#a600ff" maxWidth="-Infinity" prefWidth="120.0" text="Validation" unfocusedLineColor="#00dfff" />
    </HBox>
    <HBox layoutX="15.0" layoutY="348.0" spacing="50.0" AnchorPane.bottomAnchor="15.0" AnchorPane.leftAnchor="15.0">
       <padding>
@@ -29,12 +29,13 @@
       <MFXCheckbox fx:id="checkbox" checkedColor="#00e240" markSize="9.0" markType="mfx-variant9-mark" text="CheckBox Validation" />
       <MFXDatePicker fx:id="picker" colorText="true" lineColor="#00c133b2" maxHeight="-Infinity" maxWidth="-Infinity" pickerColor="#00c133" prefHeight="30.0" prefWidth="120.0" />
    </HBox>
-   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="235.0" prefHeight="26.0" prefWidth="266.0" text="Customization" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="240.0" />
-   <HBox alignment="CENTER" layoutX="120.0" layoutY="266.0" prefWidth="200.0" spacing="60.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="275.0">
+   <Label id="customLabel" alignment="CENTER" layoutX="167.0" layoutY="235.0" prefHeight="26.0" prefWidth="266.0" text="PasswordFields" AnchorPane.leftAnchor="167.0" AnchorPane.rightAnchor="167.0" AnchorPane.topAnchor="260.0" />
+   <HBox alignment="CENTER" layoutX="120.0" layoutY="266.0" prefWidth="200.0" spacing="60.0" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="290.0">
       <padding>
          <Insets bottom="20.0" top="20.0" />
       </padding>
-      <MFXPasswordField lineColor="#52db32" promptText="Prompt Text" />
-      <MFXPasswordField fx:id="passwordValidated" lineColor="#52db32" promptText="Enter Password..." showPassword="true" />
+      <MFXPasswordField promptText="Prompt Text" />
+      <MFXPasswordField id="customPassword" promptText="Custom, Prompt Text" />
+      <MFXPasswordField fx:id="passwordValidated" promptText="Enter Password..." showPassword="true" />
    </HBox>
 </AnchorPane>

+ 23 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/MFXColors.css

@@ -0,0 +1,23 @@
+/*
+ *     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 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 General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+* {
+    -mfx-charcoal: #445055;
+    -mfx-red: #EF6E6B;
+    -mfx-purple: #673AB7;
+}

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

@@ -16,12 +16,15 @@
  *     along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#custom > .track {
-    -fx-background-color: derive(salmon, 60%);
+#custom .track {
+    -fx-fill: derive(salmon, 70%);
 }
 
-#custom > .bar,
-#custom:indeterminate > .bar {
-    -fx-background-color: salmon;
-    -fx-padding: 2.0;
+#custom .bar1,
+#custom:indeterminate .bar1 {
+    -fx-fill: linear-gradient(to bottom right, #6A6AF8 0%, #C850C0 30%, darkorange 100%);
+}
+
+#custom:indeterminate .bar2 {
+    -fx-fill: linear-gradient(to bottom right, #F4D03F 0%, #10d7b4 100%);
 }

+ 62 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/css/TextFieldsDemo.css

@@ -16,6 +16,8 @@
  *     along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+@import "MFXColors.css";
+
 .mfx-text-field {
     -fx-font-family: "Open Sans Regular";
 }
@@ -36,4 +38,64 @@
 #colors:focused {
     -fx-prompt-text-fill: darkorange;
     -fx-text-fill: darkorange;
+}
+
+#customPassword {
+    -fx-prompt-text-fill: -mfx-purple;
+    -fx-text-fill: -mfx-purple;
+    -mfx-unfocused-line-color: -mfx-purple;
+    -mfx-line-color: darkorange;
+}
+
+#customPassword:focused {
+    -fx-prompt-text-fill: darkorange;
+    -fx-text-fill: darkorange;
+}
+
+#customPassword .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: -mfx-purple;
+}
+
+#customPassword:focused .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: darkorange
+}
+
+#customPassword {
+    -fx-prompt-text-fill: -mfx-purple;
+    -fx-text-fill: -mfx-purple;
+    -mfx-unfocused-line-color: -mfx-purple;
+    -mfx-line-color: darkorange;
+}
+
+#customPassword:focused {
+    -fx-prompt-text-fill: darkorange;
+    -fx-text-fill: darkorange;
+}
+
+#customPassword .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: -mfx-purple;
+}
+
+#customPassword:focused .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: darkorange
+}
+
+#customPassword {
+    -fx-prompt-text-fill: -mfx-purple;
+    -fx-text-fill: -mfx-purple;
+    -mfx-unfocused-line-color: linear-gradient(to bottom right, #C01ADD 0%, #6A6AF8 100%);
+    -mfx-line-color: linear-gradient(to bottom right, #6A6AF8 0%, #C850C0 46%, darkorange 100%);
+}
+
+#customPassword:focused {
+    -fx-prompt-text-fill: darkorange;
+    -fx-text-fill: darkorange;
+}
+
+#customPassword .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: -mfx-purple;
+}
+
+#customPassword:focused .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: darkorange
 }

+ 1 - 1
materialfx/gradle.properties

@@ -1,6 +1,6 @@
 GROUP=io.github.palexdev
 POM_ARTIFACT_ID=materialfx
-VERSION_NAME=11.11.0
+VERSION_NAME=11.11.1
 
 POM_NAME=materialfx
 POM_DESCRIPTION=Material Desgin components for JavaFX

+ 26 - 22
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java

@@ -19,10 +19,12 @@
 package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.beans.MFXSnapshotWrapper;
 import io.github.palexdev.materialfx.controls.enums.DialogType;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.selection.ComboSelectionModelMock;
 import io.github.palexdev.materialfx.skins.MFXComboBoxSkin;
+import io.github.palexdev.materialfx.utils.ColorUtils;
 import io.github.palexdev.materialfx.validation.MFXDialogValidator;
 import io.github.palexdev.materialfx.validation.base.AbstractMFXValidator;
 import io.github.palexdev.materialfx.validation.base.Validated;
@@ -46,6 +48,11 @@ import static io.github.palexdev.materialfx.controls.enums.Styles.ComboBoxStyles
  * Extends {@code Control} and provides a new skin since it is built from scratch.
  * <p>
  * Side note: unlike JavaFX's one this is NOT editable.
+ * <p></p>
+ * <b>
+ * Warning: the selection via context menu won't work properly if the combo box data type is a Node.
+ * This is because {@link MFXSnapshotWrapper#getGraphic()} cannot take a screenshot of it since the popup is hidden.
+ * </b>
  *
  * @param <T> The type of the value that has been selected
  * @see ComboSelectionModelMock
@@ -70,7 +77,6 @@ public class MFXComboBox<T> extends Control implements Validated<MFXDialogValida
     private final ComboSelectionModelMock<T> mockSelection;
 
     private MFXDialogValidator validator;
-    private final ObjectProperty<Paint> invalidLineColor = new SimpleObjectProperty<>(Color.web("#EF6E6B"));
     protected static final PseudoClass INVALID_PSEUDO_CLASS = PseudoClass.getPseudoClass("invalid");
 
     private final ObjectProperty<MFXContextMenu> mfxContextMenu = new SimpleObjectProperty<>();
@@ -162,25 +168,6 @@ public class MFXComboBox<T> extends Control implements Validated<MFXDialogValida
         validator.setTitle(title);
     }
 
-    public Paint getInvalidLineColor() {
-        return invalidLineColor.get();
-    }
-
-    /**
-     * Specifies the color of the focused line when the validator state is invalid.
-     * <p></p>
-     * This workaround is needed because I discovered a rather surprising/shocking bug.
-     * If you set the line color in SceneBuilder (didn't test in Java code) and the validator state is invalid,
-     * the line won't change color as specified in the CSS file, damn you JavaFX :)
-     */
-    public ObjectProperty<Paint> invalidLineColorProperty() {
-        return invalidLineColor;
-    }
-
-    public void setInvalidLineColor(Paint invalidLineColor) {
-        this.invalidLineColor.set(invalidLineColor);
-    }
-
     //================================================================================
     // Methods
     //================================================================================
@@ -388,7 +375,12 @@ public class MFXComboBox<T> extends Control implements Validated<MFXDialogValida
             this,
             "lineColor",
             Color.rgb(82, 0, 237)
-    );
+    ) {
+        @Override
+        protected void invalidated() {
+            updateColors();
+        }
+    };
 
     /**
      * Specifies the unfocusedLine color.
@@ -398,7 +390,12 @@ public class MFXComboBox<T> extends Control implements Validated<MFXDialogValida
             this,
             "unfocusedLineColor",
             Color.rgb(159, 159, 159)
-    );
+    ) {
+        @Override
+        protected void invalidated() {
+            updateColors();
+        }
+    };
 
     /**
      * Specifies the lines' stroke width.
@@ -417,6 +414,13 @@ public class MFXComboBox<T> extends Control implements Validated<MFXDialogValida
             false
     );
 
+    private void updateColors() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("-mfx-line-color: ").append(ColorUtils.toCss(getLineColor())).append(";\n")
+                .append("-mfx-unfocused-line-color: ").append(ColorUtils.toCss(getUnfocusedLineColor())).append(";\n");
+        setStyle(sb.toString());
+    }
+
     public ComboBoxStyles getComboStyle() {
         return comboStyle.get();
     }

+ 46 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java

@@ -31,6 +31,8 @@ import javafx.geometry.*;
 import javafx.scene.control.DatePicker;
 import javafx.scene.control.Label;
 import javafx.scene.control.PopupControl;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.StackPane;
 import javafx.scene.layout.VBox;
@@ -172,6 +174,10 @@ public class MFXDatePicker extends VBox {
         });
 
         datePickerContent.currentDateProperty().addListener((observable, oldValue, newValue) -> {
+            if (newValue == null) {
+                return;
+            }
+
             datePicker.setValue(newValue);
             value.setText(newValue.format(datePickerContent.getDateFormatter()));
         });
@@ -181,6 +187,10 @@ public class MFXDatePicker extends VBox {
                 return;
             }
 
+            if (newValue == null || oldValue == null) {
+                return;
+            }
+
             if (oldValue.getYear() == newValue.getYear() ||
                     oldValue.getMonth() == newValue.getMonth()) {
                 if (oldValue.getDayOfMonth() != newValue.getDayOfMonth()) {
@@ -242,6 +252,12 @@ public class MFXDatePicker extends VBox {
                 calendar.setColor(getPickerColor());
             }
         });
+
+        popup.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
+            if (event.getCode() == KeyCode.ENTER && isCloseOnEnter()) {
+                popup.hide();
+            }
+        });
     }
 
     public MFXDatePickerContent getContent() {
@@ -305,6 +321,13 @@ public class MFXDatePicker extends VBox {
             true
     );
 
+    private final StyleableBooleanProperty closeOnEnter = new SimpleStyleableBooleanProperty(
+            StyleableProperties.CLOSE_ON_ENTER,
+            this,
+            "closeOnEnter",
+            false
+    );
+
     private final StyleableBooleanProperty animateCalendar = new SimpleStyleableBooleanProperty(
             StyleableProperties.ANIMATE_CALENDAR,
             this,
@@ -402,6 +425,21 @@ public class MFXDatePicker extends VBox {
         this.closeOnDaySelected.set(closeOnDaySelected);
     }
 
+    public boolean isCloseOnEnter() {
+        return closeOnEnter.get();
+    }
+
+    /**
+     * Specifies if the date picker popup should close on ENTER pressed.
+     */
+    public StyleableBooleanProperty closeOnEnterProperty() {
+        return closeOnEnter;
+    }
+
+    public void setCloseOnEnter(boolean closeOnEnter) {
+        this.closeOnEnter.set(closeOnEnter);
+    }
+
     public boolean isAnimateCalendar() {
         return animateCalendar.get();
     }
@@ -466,6 +504,14 @@ public class MFXDatePicker extends VBox {
                         true
                 );
 
+        private static final CssMetaData<MFXDatePicker, Boolean> CLOSE_ON_ENTER =
+                FACTORY.createBooleanCssMetaData(
+                        "-mfx-close-on-enter",
+                        MFXDatePicker::closeOnEnterProperty,
+                        false
+                );
+
+
         private static final CssMetaData<MFXDatePicker, Boolean> ANIMATE_CALENDAR =
                 FACTORY.createBooleanCssMetaData(
                         "-mfx-animate-calendar",

+ 2 - 16
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java

@@ -24,15 +24,10 @@ import io.github.palexdev.materialfx.skins.MFXPasswordFieldSkin;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.*;
-import javafx.css.PseudoClass;
 import javafx.scene.control.Skin;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.paint.Color;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
 /**
  * This is my implementation of a password field, a TextField which masks the given input text.
  * <p></p>
@@ -69,11 +64,7 @@ public class MFXPasswordField extends MFXTextField {
             if (newValue.trim().isEmpty()) {
                 return;
             }
-            if (newValue.length() > 1) {
-                super.set(newValue.substring(0, 1));
-            } else {
-                super.set(newValue);
-            }
+            super.set(newValue.length() > 1 ? newValue.substring(0, 1) : newValue);
         }
     };
     private final BooleanProperty allowCopy = new SimpleBooleanProperty(true);
@@ -109,17 +100,12 @@ public class MFXPasswordField extends MFXTextField {
                 () -> isShowPassword() ? "mfx-eye-slash" : "mfx-eye",
                 showPasswordProperty()
         ));
-        icon.colorProperty().bind(Bindings.createObjectBinding(
-                () -> {
-                    List<PseudoClass> pseudoClasses = new ArrayList<>(getPseudoClassStates());
-                    return pseudoClasses.stream().map(PseudoClass::getPseudoClassName).collect(Collectors.toList()).contains("invalid") ? getInvalidLineColor() : Color.web("#4D4D4D");
-                }, focusedProperty(), getPseudoClassStates()
-        ));
         MFXIconWrapper showPasswordIcon = new MFXIconWrapper(icon, 24).defaultRippleGeneratorBehavior();
         NodeUtils.makeRegionCircular(showPasswordIcon);
         showPasswordIcon.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
             setShowPassword(!isShowPassword());
             positionCaret(getText().length());
+            requestFocus();
             event.consume();
         });
 

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

@@ -20,9 +20,12 @@ package io.github.palexdev.materialfx.controls;
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.skins.MFXProgressBarSkin;
+import javafx.css.*;
 import javafx.scene.control.ProgressBar;
 import javafx.scene.control.Skin;
 
+import java.util.List;
+
 /**
  * This is the implementation of a progress bar following Google's material design guidelines.
  * <p>
@@ -32,6 +35,7 @@ public class MFXProgressBar extends ProgressBar {
     //================================================================================
     // Properties
     //================================================================================
+    private static final StyleablePropertyFactory<MFXProgressBar> FACTORY = new StyleablePropertyFactory<>(ProgressBar.getClassCssMetaData());
     private final String STYLE_CLASS = "mfx-progress-bar";
     private final String STYLESHEETS = MFXResourcesLoader.load("css/MFXProgressBar.css");
 
@@ -55,6 +59,53 @@ public class MFXProgressBar extends ProgressBar {
         setPrefWidth(200);
     }
 
+    //================================================================================
+    // Styleable Properties
+    //================================================================================
+    private final StyleableDoubleProperty animationSpeed = new SimpleStyleableDoubleProperty(
+            StyleableProperties.ANIMATION_SPEED,
+            this,
+            "animationSpeed",
+            1.0
+    );
+
+    public double getAnimationSpeed() {
+        return animationSpeed.get();
+    }
+
+    /**
+     * Specifies the indeterminate animation speed.
+     */
+    public StyleableDoubleProperty animationSpeedProperty() {
+        return animationSpeed;
+    }
+
+    public void setAnimationSpeed(double animationSpeed) {
+        this.animationSpeed.set(animationSpeed);
+    }
+
+    //================================================================================
+    // CssMetaData
+    //================================================================================
+    private static class StyleableProperties {
+        private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
+
+        private static final CssMetaData<MFXProgressBar, Number> ANIMATION_SPEED =
+                FACTORY.createSizeCssMetaData(
+                        "-mfx-animation-speed",
+                        MFXProgressBar::animationSpeedProperty,
+                        1.0
+                );
+
+        static {
+            cssMetaDataList = List.of(ANIMATION_SPEED);
+        }
+    }
+
+    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
+        return StyleableProperties.cssMetaDataList;
+    }
+
     //================================================================================
     // Override Methods
     //================================================================================
@@ -63,6 +114,11 @@ public class MFXProgressBar extends ProgressBar {
         return new MFXProgressBarSkin(this);
     }
 
+    @Override
+    protected List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
+        return getClassCssMetaData();
+    }
+
     @Override
     public String getUserAgentStylesheet() {
         return STYLESHEETS;

+ 38 - 25
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java

@@ -22,6 +22,7 @@ import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.controls.enums.DialogType;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.skins.MFXTextFieldSkin;
+import io.github.palexdev.materialfx.utils.ColorUtils;
 import io.github.palexdev.materialfx.validation.MFXDialogValidator;
 import io.github.palexdev.materialfx.validation.base.AbstractMFXValidator;
 import io.github.palexdev.materialfx.validation.base.Validated;
@@ -67,7 +68,6 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
     private final ObjectProperty<MFXContextMenu> mfxContextMenu = new SimpleObjectProperty<>();
 
     private MFXDialogValidator validator;
-    private final ObjectProperty<Paint> invalidLineColor = new SimpleObjectProperty<>(Color.web("#EF6E6B"));
     protected static final PseudoClass INVALID_PSEUDO_CLASS = PseudoClass.getPseudoClass("invalid");
 
     //================================================================================
@@ -154,25 +154,6 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
         validator.setTitle(title);
     }
 
-    public Paint getInvalidLineColor() {
-        return invalidLineColor.get();
-    }
-
-    /**
-     * Specifies the color of the focused line when the validator state is invalid.
-     * <p></p>
-     * This workaround is needed because I discovered a rather surprising/shocking bug.
-     * If you set the line color in SceneBuilder (didn't test in Java code) and the validator state is invalid,
-     * the line won't change color as specified in the CSS file, damn you JavaFX :)
-     */
-    public ObjectProperty<Paint> invalidLineColorProperty() {
-        return invalidLineColor;
-    }
-
-    public void setInvalidLineColor(Paint invalidLineColor) {
-        this.invalidLineColor.set(invalidLineColor);
-    }
-
     //================================================================================
     // Methods
     //================================================================================
@@ -239,14 +220,29 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
                 .setAccelerator("Ctrl + A")
                 .setAction(event -> selectAll());
 
+        MFXContextMenuItem redo = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-redo", 12))
+                .setText("Redo")
+                .setAccelerator("Ctrl + Y")
+                .setAction(event -> redo());
+
+        MFXContextMenuItem undo = new MFXContextMenuItem()
+                .setIcon(new MFXFontIcon("mfx-undo", 12))
+                .setText("Undo")
+                .setAccelerator("Ctrl + Z")
+                .setAction(event -> undo());
+
+
         setMFXContextMenu(
                 MFXContextMenu.Builder.build(this)
                         .addMenuItem(copy)
                         .addMenuItem(cut)
                         .addMenuItem(paste)
                         .addMenuItem(delete)
-                        .addSeparator()
                         .addMenuItem(selectAll)
+                        .addSeparator()
+                        .addMenuItem(redo)
+                        .addMenuItem(undo)
                         .install()
         );
     }
@@ -317,14 +313,24 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
             this,
             "lineColor",
             Color.rgb(50, 120, 220)
-    );
+    ) {
+        @Override
+        protected void invalidated() {
+            updateColors();
+        }
+    };
 
     private final StyleableObjectProperty<Paint> unfocusedLineColor = new SimpleStyleableObjectProperty<>(
             StyleableProperties.UNFOCUSED_LINE_COLOR,
             this,
             "unfocusedLineColor",
             Color.rgb(77, 77, 77)
-    );
+    ) {
+        @Override
+        protected void invalidated() {
+            updateColors();
+        }
+    };
 
     private final StyleableDoubleProperty lineStrokeWidth = new SimpleStyleableDoubleProperty(
             StyleableProperties.LINE_STROKE_WIDTH,
@@ -358,6 +364,13 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
         return textLimit.get();
     }
 
+    private void updateColors() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("-mfx-line-color: ").append(ColorUtils.toCss(getLineColor())).append(";\n")
+                .append("-mfx-unfocused-line-color: ").append(ColorUtils.toCss(getUnfocusedLineColor())).append(";\n");
+        setStyle(sb.toString());
+    }
+
     /**
      * Specifies the maximum text length.
      */
@@ -526,7 +539,7 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
 
     }
 
-    public static List<CssMetaData<? extends Styleable, ?>> getControlCssMetaDataList() {
+    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
         return StyleableProperties.cssMetaDataList;
     }
 
@@ -540,7 +553,7 @@ public class MFXTextField extends TextField implements Validated<MFXDialogValida
 
     @Override
     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
-        return MFXTextField.getControlCssMetaDataList();
+        return MFXTextField.getClassCssMetaData();
     }
 
     @Override

+ 3 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXFlowlessListCell.java

@@ -46,10 +46,10 @@ public abstract class AbstractMFXFlowlessListCell<T> extends HBox implements Cel
     protected final DoubleProperty fixedCellHeight = new SimpleDoubleProperty();
 
     private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
-    private final ReadOnlyBooleanWrapper empty = new ReadOnlyBooleanWrapper();
     protected final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
-    protected final PseudoClass EMPTY_PSEUDO_CLASS = PseudoClass.getPseudoClass("empty");
 
+    private final ReadOnlyBooleanWrapper empty = new ReadOnlyBooleanWrapper();
+    protected final PseudoClass EMPTY_PSEUDO_CLASS = PseudoClass.getPseudoClass("empty");
     protected final BooleanProperty showEmpty = new SimpleBooleanProperty(false);
 
     //================================================================================
@@ -109,6 +109,7 @@ public abstract class AbstractMFXFlowlessListCell<T> extends HBox implements Cel
                 }
             }
         });
+
         addEventFilter(MouseEvent.MOUSE_PRESSED, this::updateSelection);
         index.addListener(invalidated -> afterUpdateIndex());
         getSelectionModel().selectedItemsProperty().addListener((InvalidationListener) invalidated -> setSelected(getSelectionModel().containSelected(getIndex())));

+ 39 - 22
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXFlowlessCheckListCell.java

@@ -26,6 +26,8 @@ import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.effects.ripple.RipplePosition;
 import io.github.palexdev.materialfx.selection.ListCheckModel;
 import io.github.palexdev.materialfx.selection.base.IListCheckModel;
+import io.github.palexdev.materialfx.selection.base.IListSelectionModel;
+import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.beans.property.ReadOnlyBooleanProperty;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
 import javafx.css.PseudoClass;
@@ -53,8 +55,6 @@ public class MFXFlowlessCheckListCell<T> extends AbstractMFXFlowlessListCell<T>
     private final ReadOnlyBooleanWrapper checked = new ReadOnlyBooleanWrapper();
     protected static final PseudoClass CHECKED_PSEUDO_CLASS = PseudoClass.getPseudoClass("checked");
 
-    private boolean clearSelectionOnCheck = true;
-
     //================================================================================
     // Constructors
     //================================================================================
@@ -99,17 +99,20 @@ public class MFXFlowlessCheckListCell<T> extends AbstractMFXFlowlessListCell<T>
      * Sets the following additional behaviors:
      * <p>
      * - Binds the checked property to the selected property of the combo box.<p>
-     * - Clears the selection (if {@link #clearSelectionOnCheck} is true), updates the
-     * checked PseudoClass state and calls {@link #updateCheck()} when the checked property changes.
+     * - Adds a listener to {@link IListCheckModel#allowsSelectionProperty()} to properly handle selection.
      */
     @Override
     protected void setBehavior() {
         super.setBehavior();
-        checked.bind(checkbox.selectedProperty());
-        checked.addListener(invalidated -> {
-            if (clearSelectionOnCheck) {
+
+        getSelectionModel().allowsSelectionProperty().addListener((observable, oldValue, newValue) -> {
+            if (!newValue) {
                 getSelectionModel().clearSelection();
             }
+        });
+
+        checked.bind(checkbox.selectedProperty());
+        checked.addListener(invalidated -> {
             pseudoClassStateChanged(CHECKED_PSEUDO_CLASS, checked.get());
             updateCheck();
         });
@@ -135,6 +138,35 @@ public class MFXFlowlessCheckListCell<T> extends AbstractMFXFlowlessListCell<T>
         }
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * <p></p>
+     * Overridden to manage the selection properly according to {@link IListCheckModel#allowsSelectionProperty()}
+     */
+    @Override
+    protected void updateSelection(MouseEvent mouseEvent) {
+        if (!getSelectionModel().allowsSelection()) {
+            return;
+        }
+
+        if (NodeUtils.inHierarchy(mouseEvent.getPickResult().getIntersectedNode(), checkbox)) {
+            getSelectionModel().clearSelection();
+            return;
+        }
+
+        IListSelectionModel<T> selectionModel = getSelectionModel();
+        setSelected(!isSelected());
+
+        boolean selected = isSelected();
+        int index = getIndex();
+        if (!selected && selectionModel.containSelected(index)) {
+            selectionModel.clearSelectedItem(index);
+        } else if (selected && !selectionModel.containSelected(index)) {
+            selectionModel.select(index, getData(), mouseEvent);
+        }
+    }
+
     /**
      * Inherited doc:
      * <p>
@@ -167,21 +199,6 @@ public class MFXFlowlessCheckListCell<T> extends AbstractMFXFlowlessListCell<T>
         }
     }
 
-    public boolean isClearSelectionOnCheck() {
-        return clearSelectionOnCheck;
-    }
-
-    /**
-     * Sets the behavior of the cell when an item is checked.
-     * <p>
-     * Selection and check are handled separately, this means that you can check and select items
-     * at the same time. By setting this flag to true when an item is checked the selection
-     * will be cleared.
-     */
-    public void setClearSelectionOnCheck(boolean clearSelectionOnCheck) {
-        this.clearSelectionOnCheck = clearSelectionOnCheck;
-    }
-
     public boolean isChecked() {
         return checked.get();
     }

+ 29 - 27
materialfx/src/main/java/io/github/palexdev/materialfx/font/FontResources.java

@@ -19,7 +19,7 @@
 package io.github.palexdev.materialfx.font;
 
 /**
- * Enumerator class for MaterialFX font resources. (Count: 79)
+ * Enumerator class for MaterialFX font resources. (Count: 81)
  */
 public enum FontResources {
     ANGLE_DOWN("mfx-angle-down", '\uE900'),
@@ -75,32 +75,34 @@ public enum FontResources {
     MINUS_CIRCLE("mfx-minus-circle", '\uE932'),
     MODENA_MARK("mfx-modena-mark", '\uE933'),
     NEXT("mfx-next", '\uE934'),
-    RESTORE("mfx-restore", '\uE935'),
-    SEARCH("mfx-search", '\uE936'),
-    SEARCH_PLUS("mfx-search-plus", '\uE937'),
-    SELECT_ALL("mfx-select-all", '\uE938'),
-    SLIDERS("mfx-sliders", '\uE939'),
-    STEP_BACKWARD("mfx-step-backward", '\uE93A'),
-    STEP_FORWARD("mfx-step-forward", '\uE93B'),
-    SYNC("mfx-sync", '\uE93C'),
-    SYNC_LIGHT("mfx-sync-light", '\uE93D'),
-    USER("mfx-user", '\uE93E'),
-    USERS("mfx-users", '\uE93F'),
-    VARIANT10_MARK("mfx-variant10-mark", '\uE940'),
-    VARIANT11_MARK("mfx-variant11-mark", '\uE941'),
-    VARIANT12_MARK("mfx-variant12-mark", '\uE942'),
-    VARIANT3_MARK("mfx-variant3-mark", '\uE943'),
-    VARIANT4_MARK("mfx-variant4-mark", '\uE944'),
-    VARIANT5_MARK("mfx-variant5-mark", '\uE945'),
-    VARIANT6_MARK("mfx-variant6-mark", '\uE946'),
-    VARIANT7_MARK("mfx-variant7-mark", '\uE947'),
-    VARIANT8_MARK("mfx-variant8-mark", '\uE948'),
-    VARIANT9_MARK("mfx-variant9-mark", '\uE949'),
-    X("mfx-x", '\uE94A'),
-    X_ALT("mfx-x-alt", '\uE94B'),
-    X_CIRCLE("mfx-x-circle", '\uE94C'),
-    X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE94D'),
-    X_LIGHT("mfx-x-light", '\uE94E'),
+    REDO("mfx-redo", '\uE935'),
+    RESTORE("mfx-restore", '\uE936'),
+    SEARCH("mfx-search", '\uE937'),
+    SEARCH_PLUS("mfx-search-plus", '\uE938'),
+    SELECT_ALL("mfx-select-all", '\uE939'),
+    SLIDERS("mfx-sliders", '\uE93A'),
+    STEP_BACKWARD("mfx-step-backward", '\uE93B'),
+    STEP_FORWARD("mfx-step-forward", '\uE93C'),
+    SYNC("mfx-sync", '\uE93D'),
+    SYNC_LIGHT("mfx-sync-light", '\uE93E'),
+    UNDO("mfx-undo", '\uE93F'),
+    USER("mfx-user", '\uE940'),
+    USERS("mfx-users", '\uE941'),
+    VARIANT10_MARK("mfx-variant10-mark", '\uE942'),
+    VARIANT11_MARK("mfx-variant11-mark", '\uE943'),
+    VARIANT12_MARK("mfx-variant12-mark", '\uE944'),
+    VARIANT3_MARK("mfx-variant3-mark", '\uE945'),
+    VARIANT4_MARK("mfx-variant4-mark", '\uE946'),
+    VARIANT5_MARK("mfx-variant5-mark", '\uE947'),
+    VARIANT6_MARK("mfx-variant6-mark", '\uE948'),
+    VARIANT7_MARK("mfx-variant7-mark", '\uE949'),
+    VARIANT8_MARK("mfx-variant8-mark", '\uE94A'),
+    VARIANT9_MARK("mfx-variant9-mark", '\uE94B'),
+    X("mfx-x", '\uE94C'),
+    X_ALT("mfx-x-alt", '\uE94D'),
+    X_CIRCLE("mfx-x-circle", '\uE94E'),
+    X_CIRCLE_LIGHT("mfx-x-circle-light", '\uE94F'),
+    X_LIGHT("mfx-x-light", '\uE950'),
     ;
 
     public static FontResources findByDescription(String description) {

+ 25 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/selection/ListCheckModel.java

@@ -19,7 +19,9 @@
 package io.github.palexdev.materialfx.selection;
 
 import io.github.palexdev.materialfx.selection.base.IListCheckModel;
+import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.MapProperty;
+import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.SimpleMapProperty;
 
 import java.util.List;
@@ -35,6 +37,7 @@ public class ListCheckModel<T> extends ListSelectionModel<T> implements IListChe
     // Properties
     //================================================================================
     private final MapProperty<Integer, T> checkedItems = new SimpleMapProperty<>(getMap());
+    private final BooleanProperty allowsSelection = new SimpleBooleanProperty(false);
 
     //================================================================================
     // Override Methods
@@ -136,4 +139,26 @@ public class ListCheckModel<T> extends ListSelectionModel<T> implements IListChe
     public MapProperty<Integer, T> checkedItemsProperty() {
         return checkedItems;
     }
+
+    @Override
+    public boolean allowsSelection() {
+        return allowsSelection.get();
+    }
+
+    /**
+     * Specifies if the check list also should allow the selection of cells.
+     * <p></p>
+     * Note that even if this is true selection will be cleared when pressing on a checkbox.
+     * <p>
+     * So, to use both you should first check and then select.
+     */
+    @Override
+    public BooleanProperty allowsSelectionProperty() {
+        return allowsSelection;
+    }
+
+    @Override
+    public void setAllowsSelection(boolean allowsSelection) {
+        this.allowsSelection.set(allowsSelection);
+    }
 }

+ 4 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/selection/base/IListCheckModel.java

@@ -18,6 +18,7 @@
 
 package io.github.palexdev.materialfx.selection.base;
 
+import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.MapProperty;
 
 import java.util.List;
@@ -34,4 +35,7 @@ public interface IListCheckModel<T> extends IListSelectionModel<T> {
     T getCheckedItem(int index);
     List<T> getCheckedItems();
     MapProperty<Integer, T> checkedItemsProperty();
+    boolean allowsSelection();
+    BooleanProperty allowsSelectionProperty();
+    void setAllowsSelection(boolean allowsSelection);
 }

+ 5 - 23
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXComboBoxSkin.java

@@ -37,10 +37,8 @@ import javafx.animation.KeyValue;
 import javafx.animation.ScaleTransition;
 import javafx.animation.Timeline;
 import javafx.beans.InvalidationListener;
-import javafx.beans.binding.Bindings;
 import javafx.collections.FXCollections;
 import javafx.collections.MapChangeListener;
-import javafx.css.PseudoClass;
 import javafx.event.EventHandler;
 import javafx.geometry.*;
 import javafx.scene.control.Label;
@@ -53,10 +51,6 @@ import javafx.scene.paint.Color;
 import javafx.scene.shape.Line;
 import javafx.util.Duration;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
 /**
  * This is the implementation of the Skin associated with every {@link MFXComboBox}.
  */
@@ -88,30 +82,18 @@ public class MFXComboBoxSkin<T> extends SkinBase<MFXComboBox<T>> {
 
         unfocusedLine = new Line();
         unfocusedLine.getStyleClass().add("unfocused-line");
-        unfocusedLine.setManaged(false);
+        unfocusedLine.endXProperty().bind(comboBox.widthProperty().subtract(1));
         unfocusedLine.strokeWidthProperty().bind(comboBox.lineStrokeWidthProperty());
-        unfocusedLine.strokeProperty().bind(Bindings.createObjectBinding(
-                () -> {
-                    List<PseudoClass> pseudoClasses = new ArrayList<>(comboBox.getPseudoClassStates());
-                    return pseudoClasses.stream().map(PseudoClass::getPseudoClassName).collect(Collectors.toList()).contains("invalid") ? comboBox.getInvalidLineColor() : comboBox.getUnfocusedLineColor();
-                }, comboBox.focusedProperty(), comboBox.getPseudoClassStates(), comboBox.unfocusedLineColorProperty()
-        ));
+        unfocusedLine.setManaged(false);
         unfocusedLine.setSmooth(true);
-        unfocusedLine.endXProperty().bind(comboBox.widthProperty().subtract(1));
 
         focusedLine = new Line();
         focusedLine.getStyleClass().add("focused-line");
-        focusedLine.setManaged(false);
-        focusedLine.strokeWidthProperty().bind(comboBox.lineStrokeWidthProperty());
-        focusedLine.strokeProperty().bind(Bindings.createObjectBinding(
-                () -> {
-                    List<PseudoClass> pseudoClasses = new ArrayList<>(comboBox.getPseudoClassStates());
-                    return pseudoClasses.stream().map(PseudoClass::getPseudoClassName).collect(Collectors.toList()).contains("invalid") ? comboBox.getInvalidLineColor() : comboBox.getLineColor();
-                }, comboBox.focusedProperty(), comboBox.getPseudoClassStates(), comboBox.lineColorProperty()
-        ));
-        focusedLine.setSmooth(true);
         focusedLine.endXProperty().bind(comboBox.widthProperty().subtract(1));
+        focusedLine.strokeWidthProperty().bind(comboBox.lineStrokeWidthProperty());
+        focusedLine.setManaged(false);
         focusedLine.setScaleX(0.0);
+        focusedLine.setSmooth(true);
 
         MFXFontIcon warnIcon = new MFXFontIcon("mfx-exclamation-triangle", Color.RED);
         MFXIconWrapper warnWrapper = new MFXIconWrapper(warnIcon, 10);

+ 22 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerContent.java

@@ -434,12 +434,16 @@ public class MFXDatePickerContent extends VBox {
         lastSelectedDayCell.addListener((observable, oldValue, newValue) -> {
             LocalDate date = getCurrentDate().withDayOfMonth(Integer.parseInt(newValue.getText()));
             if (date.equals(getCurrentDate())) {
-                setCurrentDate(LocalDate.EPOCH);
+                setCurrentDate(null);
             }
             setCurrentDate(date);
         });
 
-        currentDate.addListener((observable, oldValue, newValue) -> selectedDate.setText(newValue.format(getDateFormatter())));
+        currentDate.addListener((observable, oldValue, newValue) -> {
+            if (newValue != null) {
+                selectedDate.setText(newValue.format(getDateFormatter()));
+            }
+        });
 
         dateFormatter.addListener((observable, oldValue, newValue) -> selectedDate.setText(getCurrentDate().format(newValue)));
     }
@@ -652,6 +656,9 @@ public class MFXDatePickerContent extends VBox {
                     validInput.set(true);
                     selectedDate.setText(date.format(getDateFormatter()));
                     setCurrentDate(LocalDate.parse(selectedDate.getText(), getDateFormatter()));
+
+                    selectYear();
+                    selectDay();
                 } catch (DateTimeParseException ex) {
                     ex.printStackTrace();
                     inputField.getValidator().add(validInput, "Invalid at index " + ex.getErrorIndex());
@@ -764,12 +771,25 @@ public class MFXDatePickerContent extends VBox {
             animateCalendar(forward);
         }
 
+        int oldYear = getYearMonth().getYear();
         if (forward) {
             setYearMonth(getYearMonth().plus(1, MONTHS));
         } else {
             setYearMonth(getYearMonth().minus(1, MONTHS));
         }
 
+        if (oldYear != getYearMonth().getYear()) {
+            yearsList.stream()
+                    .filter(year -> year.getText().equals(Integer.toString(oldYear)))
+                    .findFirst()
+                    .ifPresent(year -> {
+                        if (year.isSelectedDate()) {
+                            year.setSelectedDate(false);
+                        }
+                    });
+            selectYear();
+        }
+
         if (getLastSelectedDayCell() == null) {
             selectDay();
         }

+ 7 - 10
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXFilterComboBoxSkin.java

@@ -92,22 +92,21 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
 
         unfocusedLine = new Line();
         unfocusedLine.getStyleClass().add("unfocused-line");
-        unfocusedLine.setManaged(false);
+        unfocusedLine.endXProperty().bind(comboBox.widthProperty().subtract(1));
         unfocusedLine.strokeWidthProperty().bind(comboBox.lineStrokeWidthProperty());
-        unfocusedLine.strokeProperty().bind(comboBox.unfocusedLineColorProperty());
+        unfocusedLine.setManaged(false);
         unfocusedLine.setSmooth(true);
-        unfocusedLine.endXProperty().bind(comboBox.widthProperty().subtract(1));
 
         focusedLine = new Line();
         focusedLine.getStyleClass().add("focused-line");
-        focusedLine.setManaged(false);
-        focusedLine.strokeWidthProperty().bind(comboBox.lineStrokeWidthProperty());
-        focusedLine.strokeProperty().bind(comboBox.lineColorProperty());
-        focusedLine.setSmooth(true);
         focusedLine.endXProperty().bind(comboBox.widthProperty().subtract(1));
+        focusedLine.strokeWidthProperty().bind(comboBox.lineStrokeWidthProperty());
+        focusedLine.setManaged(false);
         focusedLine.setScaleX(0.0);
+        focusedLine.setSmooth(true);
 
-        MFXFontIcon warnIcon = new MFXFontIcon("mfx-exclamation-triangle", Color.RED);
+        MFXFontIcon warnIcon = new MFXFontIcon("mfx-exclamation-triangle", Color.web("#EF6E6B"));
+        warnIcon.setId("validationIcon");
         MFXIconWrapper warnWrapper = new MFXIconWrapper(warnIcon, 10);
 
         validate = new Label();
@@ -532,8 +531,6 @@ public class MFXFilterComboBoxSkin<T> extends SkinBase<MFXFilterComboBox<T>> {
         searchField.setPromptText("Search...");
         searchField.setId("search-field");
         searchField.getStylesheets().setAll(comboBox.getUserAgentStylesheet());
-        searchField.setUnfocusedLineColor(Color.TRANSPARENT);
-        searchField.setLineColor(Color.TRANSPARENT);
         searchField.setFocusTraversable(false);
 
         searchField.textProperty().addListener((observable, oldValue, newValue) -> {

+ 39 - 19
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXLabelSkin.java

@@ -25,6 +25,7 @@ import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
 import io.github.palexdev.materialfx.utils.LabelUtils;
 import javafx.animation.ScaleTransition;
 import javafx.beans.binding.Bindings;
+import javafx.beans.value.ChangeListener;
 import javafx.event.EventHandler;
 import javafx.geometry.Insets;
 import javafx.scene.Node;
@@ -35,7 +36,6 @@ import javafx.scene.input.KeyEvent;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Region;
-import javafx.scene.paint.Color;
 import javafx.scene.shape.Line;
 import javafx.util.Duration;
 
@@ -73,6 +73,8 @@ public class MFXLabelSkin extends SkinBase<MFXLabel> {
         }
     };
 
+    private ChangeListener<Boolean> editorFocusListener;
+
     //================================================================================
     // Constructors
     //================================================================================
@@ -204,6 +206,13 @@ public class MFXLabelSkin extends SkinBase<MFXLabel> {
             }
         });
 
+        label.editorFocusedProperty().addListener((observable, oldValue, newValue) -> {
+            if (label.isFocused()) {
+                return;
+            }
+            buildAndPlayAnimation(newValue);
+        });
+
         label.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
             if (label.isAnimateLines() && focusedLine.getScaleX() != 1.0) {
                 buildAndPlayAnimation(true);
@@ -254,33 +263,29 @@ public class MFXLabelSkin extends SkinBase<MFXLabel> {
         label.editorFocusedProperty().bind(textField.focusedProperty());
         textField.setId("editor-node");
         textField.setManaged(false);
-        textField.setUnfocusedLineColor(Color.TRANSPARENT);
-        textField.setLineColor(Color.TRANSPARENT);
+        textField.getStylesheets().setAll(label.getUserAgentStylesheet());
 
         textField.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
             if (event.getCode() == KeyCode.ENTER) {
-                label.setText(textField.getText());
-                container.getChildren().remove(textField);
-                textNode.setVisible(true);
-                textNode.setPrefWidth(Region.USE_COMPUTED_SIZE);
+                removeEditor(textField);
                 label.requestFocus();
             } else if (event.getCode() == KeyCode.ESCAPE) {
-                container.getChildren().remove(textField);
-                textNode.setVisible(true);
-                textNode.setPrefWidth(Region.USE_COMPUTED_SIZE);
+                removeEditor(textField);
                 label.requestFocus();
             }
         });
 
-        textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
-            if (!newValue) {
-                label.setText(textField.getText());
-                container.getChildren().remove(textField);
-                textNode.setVisible(true);
-                textNode.setPrefWidth(Region.USE_COMPUTED_SIZE);
-                label.requestFocus();
-            }
-        });
+        if (editorFocusListener == null) {
+            editorFocusListener = (observable, oldValue, newValue) -> {
+                if (!newValue) {
+                    label.setText(textField.getText());
+                    container.getChildren().remove(textField);
+                    textNode.setVisible(true);
+                    textNode.setPrefWidth(Region.USE_COMPUTED_SIZE);
+                }
+            };
+        }
+        textField.focusedProperty().addListener(editorFocusListener);
 
         container.getChildren().add(textField);
         computeEditorPosition(textField);
@@ -324,6 +329,21 @@ public class MFXLabelSkin extends SkinBase<MFXLabel> {
         return editor != null;
     }
 
+    /**
+     * Removes the editor and sets its text as the label's text.
+     */
+    private void removeEditor(MFXTextField textField) {
+        MFXLabel label = getSkinnable();
+
+        textNode.setVisible(false);
+        label.setText(textField.getText());
+
+        label.editorFocusedProperty().unbind();
+        textField.focusedProperty().removeListener(editorFocusListener);
+
+        container.getChildren().remove(textField);
+    }
+
     //================================================================================
     // Override Methods
     //================================================================================

+ 6 - 3
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXPasswordFieldSkin.java

@@ -94,9 +94,12 @@ public class MFXPasswordFieldSkin extends MFXTextFieldSkin {
         MFXPasswordField passwordField = (MFXPasswordField) getSkinnable();
 
         passwordField.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
-            if (event.getClickCount() >= 2 && event.getClickCount() % 2 == 0) {
-                passwordField.selectAll();
-                event.consume();
+            if (!passwordField.isShowPassword()) {
+                if (event.getClickCount() >= 2 && event.getClickCount() % 2 == 0) {
+                    passwordField.selectAll();
+                    passwordField.requestFocus();
+                    event.consume();
+                }
             }
         });
 

+ 135 - 137
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXProgressBarSkin.java

@@ -1,28 +1,15 @@
-/*
- *     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 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 General Public License for more details.
- *
- *     You should have received a copy of the GNU General Public License
- *     along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
- */
-
 package io.github.palexdev.materialfx.skins;
 
 import io.github.palexdev.materialfx.controls.MFXProgressBar;
 import javafx.animation.*;
+import javafx.scene.Group;
 import javafx.scene.control.SkinBase;
 import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
 import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.scene.shape.StrokeLineJoin;
+import javafx.scene.shape.StrokeType;
 import javafx.util.Duration;
 
 /**
@@ -32,13 +19,12 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
     //================================================================================
     // Properties
     //================================================================================
-    private final StackPane track;
-    private final StackPane bar1;
-    private final StackPane bar2;
+    private final StackPane container;
+    private final Rectangle track;
+    private final Rectangle bar1;
+    private final Rectangle bar2;
 
-    private boolean wasIndeterminate = false;
-    private double barWidth = 0;
-    private ParallelTransition indeterminateTransition;
+    private ParallelTransition indeterminateAnimation;
 
     //================================================================================
     // Constructors
@@ -46,146 +32,179 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
     public MFXProgressBarSkin(MFXProgressBar progressBar) {
         super(progressBar);
 
-        track = new StackPane();
-        track.getStyleClass().add("track");
+        track = buildRectangle("track");
+        track.heightProperty().bind(progressBar.heightProperty());
+        track.widthProperty().bind(progressBar.widthProperty());
+
+        bar1 = buildRectangle("bar1");
+        bar1.heightProperty().bind(progressBar.heightProperty());
+
+        bar2 = buildRectangle("bar2");
+        bar2.heightProperty().bind(progressBar.heightProperty());
+        bar2.visibleProperty().bind(progressBar.indeterminateProperty());
+
+        Rectangle clip = new Rectangle();
+        clip.heightProperty().bind(progressBar.heightProperty());
+        clip.widthProperty().bind(progressBar.widthProperty());
+        clip.arcHeightProperty().bind(track.arcHeightProperty());
+        clip.arcWidthProperty().bind(track.arcWidthProperty());
+
+        Group group = new Group(track, bar1, bar2);
+        group.setClip(clip);
+        group.setManaged(false);
 
-        bar1 = new StackPane();
-        bar1.getStyleClass().add("bar");
-        bar2 = new StackPane();
-        bar2.getStyleClass().add("bar");
+        container = new StackPane(group);
+        getChildren().setAll(container);
 
         setListeners();
-        getChildren().setAll(track, bar1, bar2);
     }
 
     //================================================================================
     // Methods
     //================================================================================
-    private Rectangle buildClip() {
+
+    /**
+     * Adds listeners for: progress, width, visible, parent,scene and animation speed properties.
+     */
+    private void setListeners() {
         MFXProgressBar progressBar = getSkinnable();
 
-        Rectangle clip = new Rectangle();
-        clip.widthProperty().bind(progressBar.widthProperty());
-        clip.heightProperty().bind(progressBar.heightProperty());
-        return clip;
+        progressBar.progressProperty().addListener((observable, oldValue, newValue) -> updateBars());
+        progressBar.widthProperty().addListener((observable, oldValue, newValue) -> {
+            resetBars();
+            updateBars();
+        });
+        progressBar.visibleProperty().addListener((observable, oldValue, newValue) -> {
+            resetBars();
+            updateBars();
+        });
+        progressBar.parentProperty().addListener((observable, oldValue, newValue) -> {
+            resetBars();
+            updateBars();
+        });
+        progressBar.sceneProperty().addListener((observable, oldValue, newValue) -> {
+            resetBars();
+            updateBars();
+        });
+        progressBar.animationSpeedProperty().addListener((observable, oldValue, newValue) -> {
+            resetBars();
+            updateBars();
+        });
     }
 
     /**
-     * Adds listeners for: width, visible, parent and scene properties.
+     * Responsible for updating the progress bar state.
+     * <p></p>
+     * If it is indeterminate calls {@link #playIndeterminateAnimation()}, otherwise calls
+     * {@link #resetBars()} and {@link #updateProgress()}.
      */
-    private void setListeners() {
+    protected void updateBars() {
         MFXProgressBar progressBar = getSkinnable();
 
-        progressBar.progressProperty().addListener(invalidated -> {
-            progressBar.requestLayout();
+        if (progressBar.isIndeterminate()) {
+            playIndeterminateAnimation();
+        } else {
+            resetBars();
             updateProgress();
-        });
-        progressBar.widthProperty().addListener((observable, oldValue, newValue) -> updateProgress());
-        progressBar.visibleProperty().addListener((observable, oldValue, newValue) -> updateAnimation());
-        progressBar.parentProperty().addListener((observable, oldValue, newValue) -> updateAnimation());
-        progressBar.sceneProperty().addListener((observable, oldValue, newValue) -> updateAnimation());
+        }
     }
 
     /**
-     * Resets the animation.
+     * Responsible for clearing the indeterminate animation (stop, clear children and set to null), and
+     * resetting the bars layout, scale and width properties.
      */
-    private void clearAnimation() {
-        if (indeterminateTransition != null) {
-            indeterminateTransition.stop();
-            indeterminateTransition.getChildren().clear();
-            indeterminateTransition = null;
+    protected void resetBars() {
+        if (indeterminateAnimation != null) {
+            indeterminateAnimation.stop();
+            indeterminateAnimation.getChildren().clear();
+            indeterminateAnimation = null;
         }
+
+        bar1.setLayoutX(0);
+        bar1.setScaleX(1.0);
+        bar1.setWidth(0);
+        bar2.setLayoutX(0);
+        bar2.setScaleX(1.0);
+        bar2.setWidth(0);
+    }
+
+    /**
+     * Responsible for calculating the bar width according to the current progress
+     * (so when the progress bar is not indeterminate).
+     */
+    protected void updateProgress() {
+        MFXProgressBar progressBar = getSkinnable();
+
+        double width = ((progressBar.getWidth()) * (progressBar.getProgress() * 100)) / 100;
+        bar1.setWidth(width);
     }
 
     /**
-     * Creates the animation for the indeterminate bar.
+     * If the indeterminate animation is already playing returns.
+     * <p></p>
+     * Responsible for building the indeterminate animation.
      */
-    private void createIndeterminateTimeline() {
+    protected void playIndeterminateAnimation() {
         MFXProgressBar progressBar = getSkinnable();
 
-        if (indeterminateTransition != null) {
-            clearAnimation();
+        if (indeterminateAnimation != null) {
+            return;
         }
 
         final double width = progressBar.getWidth() - (snappedLeftInset() + snappedRightInset());
-
-        KeyFrame kf0 = new KeyFrame(Duration.ZERO,
+        KeyFrame kf0 = new KeyFrame(Duration.ONE,
                 new KeyValue(bar1.scaleXProperty(), 0.7),
-                new KeyValue(bar1.translateXProperty(), -width),
-                new KeyValue(bar2.translateXProperty(), -width)
+                new KeyValue(bar1.layoutXProperty(), -width),
+                new KeyValue(bar1.widthProperty(), width / 2),
+                new KeyValue(bar2.layoutXProperty(), -width),
+                new KeyValue(bar2.widthProperty(), width / 2)
         );
         KeyFrame kf1 = new KeyFrame(Duration.millis(700),
                 new KeyValue(bar1.scaleXProperty(), 1.25, Interpolator.EASE_BOTH)
         );
         KeyFrame kf2 = new KeyFrame(Duration.millis(1300),
-                new KeyValue(bar1.translateXProperty(), width, Interpolator.LINEAR)
+                new KeyValue(bar1.layoutXProperty(), width, Interpolator.LINEAR)
         );
-        KeyFrame kf3 = new KeyFrame(Duration.millis(900),
+        KeyFrame kf3 = new KeyFrame(Duration.millis(1100),
                 new KeyValue(bar1.scaleXProperty(), 1.0, Interpolator.EASE_OUT)
         );
         KeyFrame kf4 = new KeyFrame(Duration.millis(1100),
-                new KeyValue(bar2.translateXProperty(), width * 2, Interpolator.LINEAR),
-                new KeyValue(bar2.scaleXProperty(), 2.25, Interpolator.EASE_BOTH)
+                new KeyValue(bar2.layoutXProperty(), width * 2, Interpolator.LINEAR),
+                new KeyValue(bar2.scaleXProperty(), 2, Interpolator.EASE_BOTH)
         );
 
         Timeline bar1Animation = new Timeline(kf0, kf1, kf2, kf3);
         Timeline bar2Animation = new Timeline(kf4);
         bar2Animation.setDelay(Duration.millis(1100));
 
-        indeterminateTransition = new ParallelTransition(bar1Animation, bar2Animation);
-        indeterminateTransition.setCycleCount(Timeline.INDEFINITE);
+        indeterminateAnimation = new ParallelTransition(bar1Animation, bar2Animation);
+        indeterminateAnimation.setCycleCount(Timeline.INDEFINITE);
+        indeterminateAnimation.setRate(progressBar.getAnimationSpeed());
+        indeterminateAnimation.play();
     }
 
     /**
-     * Pauses/Resumes the animation.
+     * Responsible for building the track and the bars for the progress bar.
      */
-    private void updateTimeline(boolean pause) {
+    protected Rectangle buildRectangle(String styleClass) {
         MFXProgressBar progressBar = getSkinnable();
 
-        if (progressBar.isIndeterminate()) {
-            if (indeterminateTransition == null) {
-                createIndeterminateTimeline();
-            }
-            if (pause) {
-                indeterminateTransition.pause();
-            } else {
-                indeterminateTransition.play();
-            }
-        }
-    }
-
-    private void updateAnimation() {
-        final boolean isTreeVisible = isTreeVisible();
-        if (indeterminateTransition != null) {
-            updateTimeline(!isTreeVisible);
-        } else if (isTreeVisible) {
-            createIndeterminateTimeline();
-        }
-    }
-
-    /**
-     * Updates the bar progress.
-     */
-    private void updateProgress() {
-        MFXProgressBar progressBar = getSkinnable();
-
-        final boolean isIndeterminate = progressBar.isIndeterminate();
-        if (!(isIndeterminate && wasIndeterminate)) {
-            barWidth = ((int) (progressBar.getWidth() - snappedLeftInset() - snappedRightInset()) * 2
-                    * Math.min(1, Math.max(0, progressBar.getProgress()))) / 2.0F;
-            progressBar.requestLayout();
-        }
-        wasIndeterminate = isIndeterminate;
-    }
-
-    private boolean isTreeVisible() {
-        MFXProgressBar progressBar = getSkinnable();
-        return progressBar.isVisible() && progressBar.getParent() != null && progressBar.getScene() != null;
+        Rectangle rectangle = new Rectangle();
+        rectangle.getStyleClass().setAll(styleClass);
+        rectangle.setStroke(Color.TRANSPARENT);
+        rectangle.setStrokeLineCap(StrokeLineCap.ROUND);
+        rectangle.setStrokeLineJoin(StrokeLineJoin.ROUND);
+        rectangle.setStrokeType(StrokeType.INSIDE);
+        rectangle.setStrokeWidth(0);
+/*        rectangle.arcHeightProperty().bind(progressBar.bordersRadiusProperty());
+        rectangle.arcWidthProperty().bind(progressBar.bordersRadiusProperty());*/
+        return rectangle;
     }
 
     //================================================================================
-    // Override Methods
+    // OverrideMethods
     //================================================================================
+
     @Override
     protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
         return Math.max(100, leftInset + bar1.prefWidth(getSkinnable().getWidth()) + rightInset);
@@ -193,12 +212,7 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
 
     @Override
     protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
-        return topInset + bar1.prefHeight(width) + bottomInset;
-    }
-
-    @Override
-    protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
-        return getSkinnable().prefWidth(height);
+        return Math.max(5, bar1.prefHeight(width)) + topInset + bottomInset;
     }
 
     @Override
@@ -207,33 +221,17 @@ public class MFXProgressBarSkin extends SkinBase<MFXProgressBar> {
     }
 
     @Override
-    public void dispose() {
-        super.dispose();
-
-        if (indeterminateTransition != null) {
-            clearAnimation();
-        }
+    protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+        return getSkinnable().prefWidth(height);
     }
 
     @Override
-    protected void layoutChildren(double x, double y, double w, double h) {
-        MFXProgressBar progressBar = getSkinnable();
-
-        track.resizeRelocate(x, y, w, h);
-        bar1.resizeRelocate(x, y, progressBar.isIndeterminate() ? w / 2 : barWidth, h);
-        bar2.resizeRelocate(x, y, progressBar.isIndeterminate() ? w / 2 : 0, h);
-
-        if (progressBar.isIndeterminate()) {
-            bar1.setTranslateX(-w);
-            bar2.setTranslateX(-w);
-            createIndeterminateTimeline();
-            indeterminateTransition.play();
-            progressBar.setClip(buildClip());
-        } else {
-            bar1.setTranslateX(0);
-            bar2.setTranslateX(0);
-            clearAnimation();
-            progressBar.setClip(null);
+    public void dispose() {
+        super.dispose();
+        if (indeterminateAnimation != null) {
+            indeterminateAnimation.stop();
+            indeterminateAnimation.getChildren().clear();
+            indeterminateAnimation = null;
         }
     }
 }

+ 89 - 75
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXStepperSkin.java

@@ -28,7 +28,7 @@ import io.github.palexdev.materialfx.controls.factories.RippleClipTypeFactory;
 import io.github.palexdev.materialfx.effects.ripple.RippleClipType;
 import javafx.animation.*;
 import javafx.beans.InvalidationListener;
-import javafx.beans.value.ChangeListener;
+import javafx.beans.binding.Bindings;
 import javafx.geometry.Bounds;
 import javafx.geometry.Pos;
 import javafx.scene.Group;
@@ -40,8 +40,11 @@ import javafx.scene.layout.BorderPane;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
 import javafx.scene.shape.Rectangle;
-import javafx.stage.Window;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.scene.shape.StrokeLineJoin;
+import javafx.scene.shape.StrokeType;
 import javafx.util.Duration;
 
 /**
@@ -50,72 +53,81 @@ import javafx.util.Duration;
  * It is basically a {@link BorderPane} with three sections: top, center, bottom.
  * <p>
  * At the top there is the {@link HBox} that contains the {@code MFXStepperToggles} and the progress bar
- * which is realized by using a group and two rectangles. One rectangle is for the background and the other is for the progress.
- * The first one is manually adjusted both for x property and width property.
+ * which is realized by using a group and two rectangles. One rectangle is for the background/track and the other is the progress/bar.
+ * The bar is manually adjusted according to the current selected toggle, its width is set using {@link MFXStepperToggle#getGraphicBounds()}
+ * (+10 to ensure that there's no white space between the bar and the toggle).
  * <p>
- * At the center there is a {@link StackPane} with a minimum size of {@code 400x400}, it is the content pane namely the node that
+ * At the center there is a {@link StackPane}, it is the content pane namely the node that
  * will contain the content specifies by each stepper toggle. The style class is set to "content-pane".
  * <p>
  * At the bottom there is the {@link HBox} that contains the previous and next buttons. The style class is set to "buttons-box".
  * <p></p>
  * The stepper skin is rather delicate because the progress bar is quite hard to manage since every layout change can
- * potentially break. The skin updates the layout by adding a listener to the {@link MFXStepper#needsLayoutProperty()}.
+ * potentially break it. The skin updates the layout by adding a listener to the {@link MFXStepper#needsLayoutProperty()}.
  * When it changes the progress must be computed again with {@link #computeProgress()}.
  * A workaround is also needed in case the progress bar is animated and the layout changes. Without the workaround the
  * progress bar layout is re-computed by using the animation so the reposition process is not instantaneous.
- * To fix this annoying UI issue a boolean flag (buttonWasPressed) is set to true only when buttons are pressed and then set to false when the animation finishes,
+ * To fix this annoying UI issue a boolean flag (buttonWasPressed) is set to true only when buttons are pressed and then set to false right after the layout update,
  * so every layout change is done without playing the animation.
  *
  * @see MFXStepperToggle
  */
 public class MFXStepperSkin extends SkinBase<MFXStepper> {
+    //================================================================================
+    // Properties
+    //================================================================================
     private final StackPane contentPane;
     private final HBox stepperBar;
     private final HBox buttonsBox;
     private final MFXButton nextButton;
     private final MFXButton previousButton;
-    private ChangeListener<Boolean> parentSizeListener;
-    private ChangeListener<Window> windowListener;
 
     // Progressbar
-    private final Group progressBar;
+    private final Group progressBarGroup;
     private final double height = 7;
-    private final Rectangle progressRect;
-    private final Rectangle backgroundRect;
-    private ParallelTransition progressAnimation;
+    private final Rectangle bar;
+    private final Rectangle track;
+    private Timeline progressAnimation;
     private boolean buttonWasPressed = false;
 
+    //================================================================================
+    // Constructors
+    //================================================================================
     public MFXStepperSkin(MFXStepper stepper) {
         super(stepper);
 
-        progressRect = new Rectangle(0, 0, 0, height);
-        progressRect.fillProperty().bind(stepper.progressColorProperty());
-        progressRect.strokeProperty().bind(stepper.progressBarBackgroundProperty());
-        progressRect.widthProperty().bind(stepper.widthProperty());
-        progressRect.arcWidthProperty().bind(stepper.progressBarBorderRadiusProperty());
-        progressRect.arcHeightProperty().bind(stepper.progressBarBorderRadiusProperty());
-        progressRect.getStyleClass().add("bar-progress");
-
-        backgroundRect = new Rectangle(0, height);
-        backgroundRect.fillProperty().bind(stepper.progressBarBackgroundProperty());
-        backgroundRect.arcWidthProperty().bind(stepper.progressBarBorderRadiusProperty());
-        backgroundRect.arcHeightProperty().bind(stepper.progressBarBorderRadiusProperty());
-        backgroundRect.getStyleClass().add("bar-background");
-
-        progressAnimation = new ParallelTransition();
-        progressAnimation.setInterpolator(MFXAnimationFactory.getInterpolatorV1());
-        progressAnimation.setOnFinished(event -> buttonWasPressed = false);
+        track = buildRectangle("track");
+        track.setHeight(height);
+        track.widthProperty().bind(stepper.widthProperty());
+
+        bar = buildRectangle("bar");
+        bar.setHeight(height);
+
+        Rectangle clip = new Rectangle();
+        clip.setHeight(height);
+        clip.widthProperty().bind(stepper.widthProperty());
+        clip.arcHeightProperty().bind(stepper.progressBarBorderRadiusProperty());
+        clip.arcWidthProperty().bind(stepper.progressBarBorderRadiusProperty());
 
-        progressBar = new Group(progressRect, backgroundRect);
-        progressBar.setManaged(false);
+        progressBarGroup = new Group(track, bar);
+        progressBarGroup.setManaged(false);
+        progressBarGroup.setClip(clip);
+
+        progressAnimation = new Timeline();
+        progressAnimation.setOnFinished(event -> buttonWasPressed = false);
 
-        stepperBar = new HBox(progressBar);
+        stepperBar = new HBox(progressBarGroup);
         stepperBar.spacingProperty().bind(stepper.spacingProperty());
         stepperBar.alignmentProperty().bind(stepper.alignmentProperty());
         stepperBar.getChildren().addAll(stepper.getStepperToggles());
         stepperBar.setMinHeight(100);
         stepperBar.setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);
 
+        progressBarGroup.layoutYProperty().bind(Bindings.createDoubleBinding(
+                () -> snapPositionY((stepperBar.getHeight() / 2.0) - (height / 2.0)),
+                stepperBar.heightProperty()
+        ));
+
         nextButton = new MFXButton("Next");
         nextButton.setManaged(false);
         nextButton.getRippleGenerator().setClipSupplier(() ->
@@ -143,16 +155,13 @@ public class MFXStepperSkin extends SkinBase<MFXStepper> {
         container.setBottom(buttonsBox);
         getChildren().add(container);
 
-        parentSizeListener = (observable, oldValue, newValue) -> {
-            if (!newValue) {
-                computeProgress();
-            }
-        };
-        windowListener = (observable, oldValue, newValue) -> computeProgress();
-
         setListeners();
     }
 
+    //================================================================================
+    // Methods
+    //================================================================================
+
     /**
      * Adds the following listeners and handlers/filters.
      * <p>
@@ -184,7 +193,7 @@ public class MFXStepperSkin extends SkinBase<MFXStepper> {
         stepper.getStepperToggles().addListener((InvalidationListener) invalidated -> {
             stepper.reset();
             stepperBar.getChildren().setAll(stepper.getStepperToggles());
-            stepperBar.getChildren().add(0, progressBar);
+            stepperBar.getChildren().add(0, progressBarGroup);
             stepper.next();
 
             PauseTransition pauseTransition = new PauseTransition(Duration.millis(250));
@@ -244,31 +253,23 @@ public class MFXStepperSkin extends SkinBase<MFXStepper> {
     }
 
     /**
-     * Responsible for computing the position and size of the rectangle used to show the progress.
-     * <p>
-     * Keep in mind that the rectangle which is moved is the background rectangle not the progress one.
-     * Think about it as the background rectangle covers the progress one and when some progress is made you want to
-     * uncover the progress one by moving the background one.
+     * Responsible for computing the width of the rectangle(bar) used to show the progress.
      * <p></p>
      * Three cases are evaluated:
-     * <p> - The stepper {@link MFXStepper#lastToggleProperty()} is true, so the background rectangle width will be 0.
-     * <p> - The current stepper toggle is not null, so the background rectangle width will be computed as follows.
+     * <p> - The stepper {@link MFXStepper#lastToggleProperty()} is true, so the bar's width is set to the stepper's width.
+     * <p> - The current stepper toggle is not null, so the bar's width is computed as follows:
      * The toggle's circle bounds are retrieved using {@link MFXStepperToggle#getGraphicBounds()}. The X is computed
-     * as the minX of those Bounds converted from local to parent using {@link Node#localToParent(Bounds)}. The width
-     * is computed as the stepper's width minus the previously calculated X.
-     * <p> - The current stepper toggle is null so the X is 0 and the width is equal to the stepper's width.
-     * <p></p>
-     * The computed values are used by {@link #updateProgressBar(double, double)}
+     * as the minX of those Bounds converted from local to parent using {@link Node#localToParent(Bounds)}.
+     * This value, +10 to ensure that there is not white space between the bar and the toggle, will be the bar's width.
+     * <p> - The current stepper toggle is null so the width is set to 0.
      * <p></p>
-     * It can be tricky to understand but with the given information it should be understandable, maybe draw it, it will
-     * be easier.
-     *
+     * The computed values are used by {@link #updateProgressBar(double)}
      */
     private void computeProgress() {
         MFXStepper stepper = getSkinnable();
 
         if (stepper.isLastToggle()) {
-            updateProgressBar(stepper.getWidth(), 0);
+            updateProgressBar(stepper.getWidth());
             return;
         }
 
@@ -277,37 +278,53 @@ public class MFXStepperSkin extends SkinBase<MFXStepper> {
             Bounds bounds = stepperToggle.getGraphicBounds();
             if (bounds != null) {
                 double minX = snapSizeX(stepperToggle.localToParent(bounds).getMinX());
-                double width = snapSizeX(stepper.getWidth() - minX);
-                updateProgressBar(minX, width);
+                updateProgressBar(minX + 10);
             }
         } else {
-            updateProgressBar(0, stepper.getWidth());
+            updateProgressBar(0);
         }
     }
 
     /**
-     * Sets the background rectangle x and width properties to the given values.
+     * Sets the bar's width property to the given value.
      * If the {@link MFXStepper#animatedProperty()} or the buttonWasPressed flag are false
-     * then the properties are updated immediately. Otherwise they are updated by two separate timelines
-     * played at the same time using a {@link ParallelTransition}.
+     * then the properties are updated immediately (without the animation). Otherwise they are updated by a timeline.
      */
-    private void updateProgressBar(double x, double width) {
+    private void updateProgressBar(double width) {
         MFXStepper stepper = getSkinnable();
         if (!stepper.isAnimated() || !buttonWasPressed) {
-            backgroundRect.setX(x);
-            backgroundRect.setWidth(width);
+            bar.setWidth(width);
             buttonWasPressed = false;
             return;
         }
 
-        KeyFrame keyFrame1 = new KeyFrame(Duration.millis(stepper.getAnimationDuration()), new KeyValue(backgroundRect.xProperty(), x));
-        KeyFrame keyFrame2 = new KeyFrame(Duration.millis(stepper.getAnimationDuration()), new KeyValue(backgroundRect.widthProperty(), width));
-        Timeline timeline1 = new Timeline(keyFrame1);
-        Timeline timeline2 = new Timeline(keyFrame2);
-        progressAnimation.getChildren().setAll(timeline1, timeline2);
+        KeyFrame kf = new KeyFrame(Duration.millis(stepper.getAnimationDuration()), new KeyValue(bar.widthProperty(), width, MFXAnimationFactory.getInterpolatorV2()));
+        progressAnimation.getKeyFrames().setAll(kf);
         progressAnimation.playFromStart();
     }
 
+    /**
+     * Responsible for building the track and the bar for the progress bar.
+     */
+    protected Rectangle buildRectangle(String styleClass) {
+        MFXStepper stepper = getSkinnable();
+
+        Rectangle rectangle = new Rectangle();
+        rectangle.getStyleClass().setAll(styleClass);
+        rectangle.setStroke(Color.TRANSPARENT);
+        rectangle.setStrokeLineCap(StrokeLineCap.ROUND);
+        rectangle.setStrokeLineJoin(StrokeLineJoin.ROUND);
+        rectangle.setStrokeType(StrokeType.INSIDE);
+        rectangle.setStrokeWidth(0);
+        rectangle.arcHeightProperty().bind(stepper.progressBarBorderRadiusProperty());
+        rectangle.arcWidthProperty().bind(stepper.progressBarBorderRadiusProperty());
+        return rectangle;
+    }
+
+    //================================================================================
+    // Override Methods
+    //================================================================================
+
     @Override
     protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
         return Math.max(super.computeMinWidth(height, topInset, leftInset, bottomInset, rightInset) + (getSkinnable().getExtraSpacing() * 2), 300);
@@ -335,16 +352,13 @@ public class MFXStepperSkin extends SkinBase<MFXStepper> {
             progressAnimation.stop();
         }
         progressAnimation = null;
-        parentSizeListener = null;
-        windowListener = null;
     }
 
     @Override
     protected void layoutChildren(double x, double y, double w, double h) {
         super.layoutChildren(x, y, w, h);
 
-        double barY = snapPositionY((stepperBar.getHeight() / 2.0) - (height / 2.0));
-        progressBar.resizeRelocate(0.0, barY, w, height);
+        progressBarGroup.resize(w, height);
 
         double bw = 125;
         double bh = 34;

+ 25 - 28
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTextFieldSkin.java

@@ -26,20 +26,17 @@ import io.github.palexdev.materialfx.utils.LabelUtils;
 import io.github.palexdev.materialfx.validation.MFXDialogValidator;
 import javafx.animation.ScaleTransition;
 import javafx.beans.binding.Bindings;
-import javafx.css.PseudoClass;
 import javafx.scene.Cursor;
 import javafx.scene.Node;
+import javafx.scene.control.IndexRange;
 import javafx.scene.control.Label;
 import javafx.scene.control.skin.TextFieldSkin;
+import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.Line;
 import javafx.util.Duration;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
 /**
  * This is the implementation of the {@code Skin} associated with every {@link MFXTextField}.
  * <p></p>
@@ -61,6 +58,7 @@ public class MFXTextFieldSkin extends TextFieldSkin {
     private final Line unfocusedLine;
     private final Line focusedLine;
     private final Label validate;
+    private IndexRange prev = null;
 
     //================================================================================
     // Constructors
@@ -70,15 +68,6 @@ public class MFXTextFieldSkin extends TextFieldSkin {
 
         unfocusedLine = new Line();
         unfocusedLine.getStyleClass().add("unfocused-line");
-        unfocusedLine.setManaged(false);
-        unfocusedLine.strokeWidthProperty().bind(textField.lineStrokeWidthProperty());
-        unfocusedLine.strokeLineCapProperty().bind(textField.lineStrokeCapProperty());
-        unfocusedLine.strokeProperty().bind(Bindings.createObjectBinding(
-                () -> {
-                    List<PseudoClass> pseudoClasses = new ArrayList<>(textField.getPseudoClassStates());
-                    return pseudoClasses.stream().map(PseudoClass::getPseudoClassName).collect(Collectors.toList()).contains("invalid") ? textField.getInvalidLineColor() : textField.getUnfocusedLineColor();
-                }, textField.focusedProperty(), textField.getPseudoClassStates(), textField.unfocusedLineColorProperty()
-        ));
         unfocusedLine.endXProperty().bind(Bindings.createDoubleBinding(() -> {
             Node icon = textField.getIcon();
             if (icon != null) {
@@ -87,21 +76,13 @@ public class MFXTextFieldSkin extends TextFieldSkin {
             }
             return textField.getWidth();
         }, textField.widthProperty(), textField.iconProperty()));
-        unfocusedLine.setSmooth(true);
+        unfocusedLine.strokeLineCapProperty().bind(textField.lineStrokeCapProperty());
+        unfocusedLine.strokeWidthProperty().bind(textField.lineStrokeWidthProperty());
         unfocusedLine.setManaged(false);
+        unfocusedLine.setSmooth(true);
 
         focusedLine = new Line();
         focusedLine.getStyleClass().add("focused-line");
-        focusedLine.setManaged(false);
-        focusedLine.strokeWidthProperty().bind(textField.lineStrokeWidthProperty());
-        focusedLine.strokeLineCapProperty().bind(textField.lineStrokeCapProperty());
-        focusedLine.strokeProperty().bind(Bindings.createObjectBinding(
-                () -> {
-                    List<PseudoClass> pseudoClasses = new ArrayList<>(textField.getPseudoClassStates());
-                    return pseudoClasses.stream().map(PseudoClass::getPseudoClassName).collect(Collectors.toList()).contains("invalid") ? textField.getInvalidLineColor() : textField.getLineColor();
-                }, textField.focusedProperty(), textField.getPseudoClassStates(), textField.lineColorProperty()
-        ));
-        focusedLine.setSmooth(true);
         focusedLine.endXProperty().bind(Bindings.createDoubleBinding(() -> {
             Node icon = textField.getIcon();
             if (icon != null) {
@@ -110,8 +91,11 @@ public class MFXTextFieldSkin extends TextFieldSkin {
             }
             return textField.getWidth();
         }, textField.widthProperty(), textField.iconProperty()));
-        focusedLine.setScaleX(0.0);
+        focusedLine.strokeLineCapProperty().bind(textField.lineStrokeCapProperty());
+        focusedLine.strokeWidthProperty().bind(textField.lineStrokeWidthProperty());
         focusedLine.setManaged(false);
+        focusedLine.setScaleX(0.0);
+        focusedLine.setSmooth(true);
 
         MFXFontIcon warnIcon = new MFXFontIcon("mfx-exclamation-triangle", Color.RED);
         MFXIconWrapper warnWrapper = new MFXIconWrapper(warnIcon, 10);
@@ -119,7 +103,6 @@ public class MFXTextFieldSkin extends TextFieldSkin {
         validate = new Label();
         validate.setGraphic(warnWrapper);
         validate.getStyleClass().add("validate-label");
-        validate.getStylesheets().setAll(textField.getUserAgentStylesheet());
         validate.textProperty().bind(textField.getValidator().validatorMessageProperty());
         validate.setGraphicTextGap(padding);
         validate.setVisible(false);
@@ -144,7 +127,7 @@ public class MFXTextFieldSkin extends TextFieldSkin {
     //================================================================================
 
     /**
-     * Adds listeners for: icon, icon insets, line, focus, disabled and validator properties.
+     * Adds listeners for: selection, icon, icon insets, line, focus, disabled and validator properties.
      * <p>
      * Validator: when the control is not focused, and of course if {@code isValidated} is true,
      * all the conditions in the validator are evaluated and if one is false the {@code validate} label is shown.
@@ -157,6 +140,20 @@ public class MFXTextFieldSkin extends TextFieldSkin {
         MFXTextField textField = (MFXTextField) getSkinnable();
         MFXDialogValidator validator = textField.getValidator();
 
+        textField.selectionProperty().addListener((observable, oldValue, newValue) ->{
+            if (prev == null) {
+                prev = newValue;
+            } else {
+                prev = oldValue;
+            }
+        });
+
+        textField.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
+            if (prev != null && event.getButton() == MouseButton.SECONDARY) {
+                textField.selectRange(prev.getStart(), prev.getEnd());
+            }
+        });
+
         textField.iconProperty().addListener((observable, oldValue, newValue) -> {
             if (newValue == null) {
                 getChildren().remove(oldValue);

+ 110 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/utils/ColorUtils.java

@@ -18,7 +18,7 @@
 
 package io.github.palexdev.materialfx.utils;
 
-import javafx.scene.paint.Color;
+import javafx.scene.paint.*;
 
 import java.util.Random;
 
@@ -31,6 +31,25 @@ public class ColorUtils {
     private ColorUtils() {
     }
 
+    /**
+     * Converts a JavaFX Paint object to the right CSS string.
+     * <p>
+     * Supports: {@link Color}, {@link LinearGradient}, {@link RadialGradient}.
+     */
+    public static String toCss(Paint paint) {
+        if (paint instanceof LinearGradient) {
+            LinearGradient gradient = (LinearGradient) paint;
+            return linearGradientToString(gradient);
+        }
+
+        if (paint instanceof RadialGradient) {
+            RadialGradient gradient = (RadialGradient) paint;
+            return radialGradientToString(gradient);
+        }
+
+        return rgb((Color) paint);
+    }
+
     /**
      * Converts a JavaFX's {@code Color} to CSS corresponding rgb function.
      *
@@ -62,4 +81,94 @@ public class ColorUtils {
     public static Color getRandomColor() {
         return Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256));
     }
+
+    /**
+     * Util method to convert {@link LinearGradient} to a CSS string.
+     * <p></p>
+     * This is partly a copy of {@link LinearGradient#toString()} but {@code Stops} are correctly converted
+     * for CSS.
+     *
+     * @param gradient the linear gradient to convert
+     * @see Stop
+     */
+    public static String linearGradientToString(LinearGradient gradient) {
+        final StringBuilder s = new StringBuilder("linear-gradient(from ")
+                .append(lengthToString(gradient.getStartX(), gradient.isProportional()))
+                .append(" ").append(lengthToString(gradient.getStartY(), gradient.isProportional()))
+                .append(" to ").append(lengthToString(gradient.getEndX(), gradient.isProportional()))
+                .append(" ").append(lengthToString(gradient.getEndY(), gradient.isProportional()))
+                .append(", ");
+
+        switch (gradient.getCycleMethod()) {
+            case REFLECT:
+                s.append("reflect").append(", ");
+                break;
+            case REPEAT:
+                s.append("repeat").append(", ");
+                break;
+        }
+
+        for (Stop stop : gradient.getStops()) {
+            s.append(stopToString(stop)).append(", ");
+        }
+
+        s.delete(s.length() - 2, s.length());
+        s.append(")");
+
+        return s.toString();
+    }
+
+    /**
+     * Util method to convert {@link RadialGradient} to a CSS string.
+     * <p></p>
+     * This is partly a copy of {@link RadialGradient#toString()} but {@code Stops} are correctly converted
+     * for CSS.
+     *
+     * @param gradient the radial gradient to convert
+     * @see Stop
+     */
+    public static String radialGradientToString(RadialGradient gradient) {
+        final StringBuilder s = new StringBuilder("radial-gradient(focus-angle ").append(gradient.getFocusAngle())
+                .append("deg, focus-distance ").append(gradient.getFocusDistance() * 100)
+                .append("% , center ").append(lengthToString(gradient.getCenterX(), gradient.isProportional()))
+                .append(" ").append(lengthToString(gradient.getCenterY(), gradient.isProportional()))
+                .append(", radius ").append(lengthToString(gradient.getRadius(), gradient.isProportional()))
+                .append(", ");
+
+        switch (gradient.getCycleMethod()) {
+            case REFLECT:
+                s.append("reflect").append(", ");
+                break;
+            case REPEAT:
+                s.append("repeat").append(", ");
+                break;
+        }
+
+        for (Stop stop : gradient.getStops()) {
+            s.append(stopToString(stop)).append(", ");
+        }
+
+        s.delete(s.length() - 2, s.length());
+        s.append(")");
+
+        return s.toString();
+
+    }
+
+    /**
+     * Properly converts a {@link Stop} to string. Partly copied from
+     * {@link Stop#toString()} but the color is converted using {@link #rgba(Color)}.
+     */
+    public static String stopToString(Stop stop) {
+        return rgba(stop.getColor()) + " " + stop.getOffset() * 100 + "%";
+    }
+
+    private static String lengthToString(double value, boolean proportional) {
+        if (proportional) {
+            return (value * 100) + "%";
+        } else {
+            return value + "px";
+        }
+    }
+
 }

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

@@ -19,4 +19,5 @@
 * {
     -mfx-charcoal: #445055;
     -mfx-red: #EF6E6B;
+    -mfx-purple: #673AB7;
 }

+ 50 - 2
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle1.css

@@ -19,11 +19,11 @@
 @import "Fonts.css";
 
 .mfx-combo-box {
-    -mfx-line-color: rgb(82, 0, 237);
-    -mfx-unfocused-line-color: rgb(159, 159, 159);
     -fx-background-color: rgb(232, 232, 232);
     -fx-background-radius: 3 3 0 0;
     -fx-border-width: 1.5;
+    -mfx-line-color: rgb(82, 0, 237);
+    -mfx-unfocused-line-color: rgb(159, 159, 159);
 }
 
 .mfx-combo-box .label {
@@ -100,4 +100,52 @@
     -fx-font-family: "Open Sans SemiBold";
     -fx-font-size: 11;
     -fx-text-fill: #EF6E6B;
+}
+
+.mfx-combo-box .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-combo-box .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-combo-box:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-combo-box .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-combo-box:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-combo-box .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-combo-box:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
 }

+ 2 - 2
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle2.css

@@ -19,12 +19,12 @@
 @import "Fonts.css";
 
 .mfx-combo-box {
-    -mfx-line-color: rgb(82, 0, 237);
-    -mfx-unfocused-line-color: rgb(159, 159, 159);
     -fx-background-color: white;
     -fx-background-radius: 2px;
     -fx-border-color: -mfx-unfocused-line-color;
     -fx-border-radius: 2px;
+    -mfx-line-color: rgb(82, 0, 237);
+    -mfx-unfocused-line-color: rgb(159, 159, 159);
 }
 
 .mfx-combo-box:focused,

+ 48 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXComboBoxStyle3.css

@@ -95,4 +95,52 @@
     -fx-font-family: "Open Sans SemiBold";
     -fx-font-size: 11;
     -fx-text-fill: #EF6E6B;
+}
+
+.mfx-combo-box .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-combo-box .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-combo-box:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-combo-box .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-combo-box:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-combo-box .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-combo-box:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-combo-box:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
 }

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

@@ -22,3 +22,13 @@
     -fx-prompt-text-fill: grey;
 }
 
+#search-field .focused-line,
+#search-field .unfocused-line {
+    -fx-stroke: transparent;
+}
+
+#validationIcon {
+    -mfx-color: #EF6E6B;
+}
+
+

+ 6 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle1.css

@@ -38,3 +38,9 @@
 .mfx-label:prompt {
     -mfx-text-fill: #A0A0A0;
 }
+
+
+#editor-node .focused-line,
+#editor-node .unfocused-line {
+    -fx-stroke: transparent;
+}

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

@@ -39,7 +39,17 @@
     -fx-border-color: -mfx-line-color;
 }
 
+.mfx-label:editor {
+    -fx-border-color: -mfx-line-color;
+}
+
 .mfx-label:prompt {
     -mfx-text-fill: #A0A0A0;
 }
 
+
+#editor-node .focused-line,
+#editor-node .unfocused-line {
+    -fx-stroke: transparent;
+}
+

+ 5 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXLabelStyle3.css

@@ -36,3 +36,8 @@
 .mfx-label:prompt {
     -mfx-text-fill: #A0A0A0;
 }
+
+#editor-node .focused-line,
+#editor-node .unfocused-line {
+    -fx-stroke: transparent;
+}

+ 8 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXPasswordField.css

@@ -26,6 +26,14 @@
     -fx-opacity: 1.0;
 }
 
+.mfx-password-field .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: #4D4D4D
+}
+
+.mfx-password-field:invalid .mfx-icon-wrapper .mfx-font-icon {
+    -mfx-color: #EF6E6B
+}
+
 .mfx-password-field .mfx-icon-wrapper .mfx-ripple-generator {
     -mfx-animation-speed: 2;
 }

+ 11 - 10
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXProgressBar.css

@@ -16,18 +16,19 @@
  *     along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-.mfx-progress-bar > .track {
-    -fx-background-color: #E0E0E0;
+.mfx-progress-bar .track {
+    -fx-fill: #E0E0E0;
 }
 
-.mfx-progress-bar > .bar,
-.mfx-progress-bar:indeterminate > .bar {
-    -fx-background-color: #0F9D58;
-    -fx-padding: 2.0;
+.mfx-progress-bar .bar1,
+.mfx-progress-bar:indeterminate .bar1,
+.mfx-progress-bar:indeterminate .bar2 {
+    -fx-fill: #0F9D58;
 }
 
-.mfx-progress-bar > .track,
-.mfx-progress-bar > .bar {
-    -fx-background-radius: 5;
-    -fx-background-insets: 0;
+.mfx-progress-bar .track,
+.mfx-progress-bar .bar1,
+.mfx-progress-bar .bar2 {
+    -fx-arc-height: 6;
+    -fx-arc-width: 6;
 }

+ 8 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXStepper.css

@@ -26,6 +26,14 @@
     -mfx-progress-color: -mfx-base-color;
 }
 
+.mfx-stepper .track {
+    -fx-fill: -mfx-bar-background;
+}
+
+.mfx-stepper .bar {
+    -fx-fill: -mfx-progress-color;
+}
+
 .mfx-stepper .buttons-box .mfx-button {
     -mfx-depth-level: level1;
     -fx-background-color: white;

+ 48 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/MFXTextField.css

@@ -38,4 +38,52 @@
 .mfx-text-field:invalid {
     -fx-text-fill: #EF6E6B;
     -fx-prompt-text-fill: #EF6E6B;
+}
+
+.mfx-text-field .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-text-field .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-text-field:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-text-field:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-text-field .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-text-field .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-text-field:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-text-field:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-text-field .unfocused-line {
+    -fx-stroke: -mfx-unfocused-line-color;
+}
+
+.mfx-text-field .focused-line {
+    -fx-stroke: -mfx-line-color;
+}
+
+.mfx-text-field:invalid .unfocused-line {
+    -fx-stroke: #EF6E6B;
+}
+
+.mfx-text-field:invalid .focused-line {
+    -fx-stroke: #EF6E6B;
 }

BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/MFXResources.ttf