Browse Source

Version 11.2.1
Demo completely redone
4 new controls: MFXDialog, MFXStageDialog, MFXHLoader and MFXVLoader
Completely redone MFXToggleNode
Added some animations
Added new effect: MFXScrimEffect, see MaterialDesign guidelines
Added new util class ToggleButtonsUtil
Added new class MFXResourcesManager for storing library resources

Minor changes:
Renamed MFXResources to MFXResourcesLoader
MFXButton, set better ripple radius and color on initialization
RippleGenerator, added flag for background animation, fix cast warning
RippleClipType, implemented circle
NodeUtils, added method to center node in AnchorPanes

Resources:
MFXButton, set focused color to white
MFXToggleNode, set default opacity and selected opacity for a better feedback on user interaction
Added new fonts

Signed-off-by: PAlex404 <alessandro.parisi406@gmail.com>

PAlex404 4 years ago
parent
commit
a94854da04
86 changed files with 3329 additions and 368 deletions
  1. 4 4
      README.md
  2. 4 20
      build.gradle
  3. 1 1
      demo/build.gradle
  4. 19 2
      demo/src/main/java/io/github/palexdev/materialfx/demo/Demo.java
  5. 3 3
      demo/src/main/java/io/github/palexdev/materialfx/demo/MFXResourcesLoader.java
  6. 67 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DemoController.java
  7. 199 0
      demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DialogsController.java
  8. 2 0
      demo/src/main/java/module-info.java
  9. 207 6
      demo/src/main/resources/io/github/palexdev/materialfx/demo/common.css
  10. 57 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/demo.css
  11. 14 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/demo.fxml
  12. 1 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/dialogs_demo.css
  13. 98 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/dialogs_demo.fxml
  14. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Bold.ttf
  15. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-BoldItalic.ttf
  16. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBold.ttf
  17. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf
  18. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Italic.ttf
  19. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Light.ttf
  20. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-LightItalic.ttf
  21. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Regular.ttf
  22. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBold.ttf
  23. BIN
      demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf
  24. 0 37
      demo/src/main/resources/io/github/palexdev/materialfx/demo/main_demo.css
  25. 0 20
      demo/src/main/resources/io/github/palexdev/materialfx/demo/main_demo.fxml
  26. 9 0
      demo/src/main/resources/io/github/palexdev/materialfx/demo/toggle_buttons_demo.css
  27. 22 31
      demo/src/main/resources/io/github/palexdev/materialfx/demo/toggle_buttons_demo.fxml
  28. 19 0
      materialfx/build.gradle
  29. 3 3
      materialfx/src/main/java/io/github/palexdev/materialfx/MFXResourcesLoader.java
  30. 63 0
      materialfx/src/main/java/io/github/palexdev/materialfx/MFXResourcesManager.java
  31. 67 0
      materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXLoadItem.java
  32. 16 7
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java
  33. 3 3
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java
  34. 130 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDialog.java
  35. 277 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXHLoader.java
  36. 211 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStageDialog.java
  37. 2 2
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXToggleButton.java
  38. 153 34
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXToggleNode.java
  39. 277 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXVLoader.java
  40. 353 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXDialog.java
  41. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/DialogType.java
  42. 0 26
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/MarkType.java
  43. 6 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/ToggleNodeShape.java
  44. 153 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXAnimationFactory.java
  45. 269 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXDialogFactory.java
  46. 78 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXStageDialogFactory.java
  47. 125 0
      materialfx/src/main/java/io/github/palexdev/materialfx/effects/MFXScrimEffect.java
  48. 4 1
      materialfx/src/main/java/io/github/palexdev/materialfx/effects/RippleClipType.java
  49. 31 12
      materialfx/src/main/java/io/github/palexdev/materialfx/effects/RippleGenerator.java
  50. 0 154
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXToggleNodeSkin.java
  51. 34 0
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/Loader.java
  52. 28 0
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/NodeUtils.java
  53. 34 0
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/ToggleButtonsUtil.java
  54. 5 1
      materialfx/src/main/java/module-info.java
  55. 216 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/fonts.css
  56. 2 1
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/mfx-button.css
  57. 47 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/mfx-dialog.css
  58. 8 0
      materialfx/src/main/resources/io/github/palexdev/materialfx/css/mfx-togglenode.css
  59. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Bold.ttf
  60. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Light.ttf
  61. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Medium.ttf
  62. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Regular.ttf
  63. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-SemiBold.ttf
  64. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-VariableFont_wght.ttf
  65. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Bold.ttf
  66. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-BoldItalic.ttf
  67. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBold.ttf
  68. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf
  69. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Italic.ttf
  70. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Light.ttf
  71. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-LightItalic.ttf
  72. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Regular.ttf
  73. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBold.ttf
  74. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf
  75. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Black.ttf
  76. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BlackItalic.ttf
  77. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Bold.ttf
  78. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BoldItalic.ttf
  79. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Italic.ttf
  80. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Light.ttf
  81. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-LightItalic.ttf
  82. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Medium.ttf
  83. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-MediumItalic.ttf
  84. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Regular.ttf
  85. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Thin.ttf
  86. BIN
      materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-ThinItalic.ttf

+ 4 - 4
README.md

@@ -54,7 +54,7 @@ With the release of JDK 11 in 2018, Oracle has made JavaFX part of the OpenJDK u
 Key features:
    - FXML and SceneBuilder, A designer can code in FXML or use JavaFX Scene Builder to interactively design the graphical user interface (GUI). Scene Builder generates FXML markup that can be ported to an IDE where a developer can add the business logic.
    - Built-in UI controls and CSS, JavaFX provides all the major UI controls required to develop a full-featured application. Components can be skinned with standard Web technologies such as CSS.
-   - Self-contained application deployment model. Self-contained application packages have all of the application resources and a private copy of the Java and JavaFX runtimes.
+   - Self-contained application deployment model. Self-contained application packages have all the application resources and a private copy of the Java and JavaFX runtimes.
      They are distributed as native installable packages and provide the same installation and launch experience as native applications for that operating system.
    JavaFX is a software platform for creating and delivering desktop applications, as well as rich Internet applications (RIAs) that can run across a wide variety of devices.
 
@@ -86,7 +86,7 @@ repositories {
 }
 
 dependencies {
-implementation 'io.github.palexdev:materialfx:11.1.0'
+implementation 'io.github.palexdev:materialfx:11.2.1'
 }
 ```
 ###### Maven
@@ -94,7 +94,7 @@ implementation 'io.github.palexdev:materialfx:11.1.0'
 <dependency>
   <groupId>io.github.palexdev</groupId>
   <artifactId>materialfx</artifactId>
-  <version>11.1.0</version>
+  <version>11.2.1</version>
 </dependency>
 ```
 
@@ -104,7 +104,7 @@ See the [open issues](https://github.com/palexdev/MaterialFX/issues) for a list
 
 <!-- CONTRIBUTING -->
 ## Contributing
-Contributions are what make the open source community such an amazing place to be learn, inspire, and create.
+Contributions are what make the open source community such an amazing place to learn, inspire, and create.
 Any contributions you make are **greatly appreciated**.
 
 1. Fork the Project

+ 4 - 20
build.gradle

@@ -4,38 +4,22 @@ plugins {
 }
 
 group 'io.github.palexdev'
-version '11.1.0'
+version '11.2.1'
 
 repositories {
     mavenCentral()
     jcenter()
 }
 
-/*subprojects {
+subprojects {
     apply plugin: 'org.openjfx.javafxplugin'
 
     javafx {
         version = "14.0.2.1"
-        modules = [ 'javafx.controls', 'javafx.fxml' ]
-    }
-}*/
-
-project(":demo") {
-    apply plugin: 'application'
-    apply plugin: 'org.openjfx.javafxplugin'
-
-    javafx {
-        version = '14.0.2.1'
-        modules = [ 'javafx.controls', 'javafx.fxml' ]
+        modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.web' ]
     }
 }
 
-project(":materialfx") {
-    apply plugin: 'org.openjfx.javafxplugin'
 
-    javafx {
-        version = '14.0.2.1'
-        modules = [ 'javafx.controls' ]
-    }
-}
+
 

+ 1 - 1
demo/build.gradle

@@ -17,7 +17,7 @@ dependencies {
 }
 application {
     mainClassName = 'MaterialFX.demo.main/io.github.palexdev.materialfx.demo.Demo'
-//    mainClassName = 'MaterialFX.demo.main/io.github.palexdev.materialfx.demo.TestDemo'
+    //mainClassName = 'MaterialFX.demo.main/io.github.palexdev.materialfx.demo.TestDemo'
 }
 
 jlink {

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

@@ -5,20 +5,37 @@ import javafx.application.Application;
 import javafx.fxml.FXMLLoader;
 import javafx.scene.Scene;
 import javafx.scene.layout.AnchorPane;
+import javafx.scene.paint.Color;
 import javafx.stage.Stage;
+import javafx.stage.StageStyle;
 
 import java.io.IOException;
 
 public class Demo extends Application {
+    private double xOffset;
+    private double yOffset;
 
     @Override
     public void start(Stage primaryStage) throws IOException {
         CSSFX.start();
 
-        AnchorPane demo = FXMLLoader.load(MFXResources.load("main_demo.fxml"));
+        FXMLLoader fxmlLoader = new FXMLLoader(MFXResourcesLoader.load("demo.fxml"));
+        AnchorPane demoPane = fxmlLoader.load();
+
+        demoPane.setOnMousePressed(event -> {
+            xOffset = primaryStage.getX() - event.getScreenX();
+            yOffset = primaryStage.getY() - event.getScreenY();
+        });
+        demoPane.setOnMouseDragged(event -> {
+            primaryStage.setX(event.getScreenX() + xOffset);
+            primaryStage.setY(event.getScreenY() + yOffset);
+        });
 
         primaryStage.setTitle("MaterialFX Demo - Features Preview");
-        primaryStage.setScene(new Scene(demo));
+        primaryStage.initStyle(StageStyle.TRANSPARENT);
+        Scene scene = new Scene(demoPane);
+        scene.setFill(Color.TRANSPARENT);
+        primaryStage.setScene(scene);
         primaryStage.show();
     }
 

+ 3 - 3
demo/src/main/java/io/github/palexdev/materialfx/demo/MFXResources.java → demo/src/main/java/io/github/palexdev/materialfx/demo/MFXResourcesLoader.java

@@ -6,12 +6,12 @@ import java.net.URL;
  * Utility class which manages the access to this project's assets.
  * Helps keeping the assets files structure organized.
  */
-public class MFXResources {
+public class MFXResourcesLoader {
 
-    private MFXResources() {
+    private MFXResourcesLoader() {
     }
 
     public static URL load(String path) {
-        return MFXResources.class.getResource(path);
+        return MFXResourcesLoader.class.getResource(path);
     }
 }

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

@@ -0,0 +1,67 @@
+package io.github.palexdev.materialfx.demo.controllers;
+
+import io.github.palexdev.materialfx.MFXResourcesManager.SVGResources;
+import io.github.palexdev.materialfx.controls.MFXButton;
+import io.github.palexdev.materialfx.controls.MFXHLoader;
+import io.github.palexdev.materialfx.controls.MFXToggleNode;
+import io.github.palexdev.materialfx.controls.MFXVLoader;
+import io.github.palexdev.materialfx.demo.MFXResourcesLoader;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.SVGPath;
+import javafx.stage.Stage;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+public class DemoController implements Initializable {
+    @FXML
+    private AnchorPane demoPane;
+
+    @FXML
+    private MFXHLoader hLoader;
+
+    @FXML
+    private MFXVLoader vLoader;
+
+    @FXML
+    private StackPane contentPane;
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        MFXButton closeButton = new MFXButton("");
+        SVGPath x = SVGResources.X.getSvgPath();
+        x.setScaleX(0.15);
+        x.setScaleY(0.15);
+        x.setFill(Color.WHITE);
+        closeButton.setPrefSize(20, 20);
+        closeButton.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+        closeButton.setGraphic(x);
+        closeButton.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> ((Stage) demoPane.getScene().getWindow()).close());
+        closeButton.setStyle("-fx-background-color: transparent");
+        demoPane.getChildren().add(closeButton);
+        AnchorPane.setTopAnchor(closeButton, 8.0);
+        AnchorPane.setRightAnchor(closeButton, 10.0);
+
+        hLoader.setContentPane(contentPane);
+        vLoader.setContentPane(contentPane);
+
+        hLoader.addItem(0, "BUTTONS", new MFXToggleNode("BUTTONS"), MFXResourcesLoader.load("buttons_demo.fxml"));
+        hLoader.addItem(1, "CHECKBOXES", new MFXToggleNode("CHECKBOXES"), MFXResourcesLoader.load("checkboxes_demo.fxml"));
+        hLoader.addItem(2, "TOGGLES", new MFXToggleNode("TOGGLES"), MFXResourcesLoader.load("toggle_buttons_demo.fxml"));
+        hLoader.addItem(3, "DIALOGS", new MFXToggleNode("DIALOGS"), MFXResourcesLoader.load("dialogs_demo.fxml"), controller -> new DialogsController(demoPane));
+        hLoader.setDefault("BUTTONS");
+
+        /*
+        vLoader.addItem(0, new MFXButton("Buttons", 80, 40), MFXResourcesLoader.load("buttons_demo.fxml"));
+        vLoader.addItem(1, new MFXButton("Checkboxes", 80, 40), MFXResourcesLoader.load("checkboxes_demo.fxml"));
+        vLoader.addItem(2, new MFXButton("Toggles", 80, 40), MFXResourcesLoader.load("toggle_buttons_demo.fxml"));
+        vLoader.addItem(3, new MFXButton("Dialogs", 80, 40), MFXResourcesLoader.load("dialogs_demo.fxml"), controller -> new DialogsController(demoPane));
+        */
+    }
+}

+ 199 - 0
demo/src/main/java/io/github/palexdev/materialfx/demo/controllers/DialogsController.java

@@ -0,0 +1,199 @@
+package io.github.palexdev.materialfx.demo.controllers;
+
+import io.github.palexdev.materialfx.controls.MFXButton;
+import io.github.palexdev.materialfx.controls.MFXStageDialog;
+import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog;
+import io.github.palexdev.materialfx.controls.enums.DialogType;
+import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
+import io.github.palexdev.materialfx.controls.factories.MFXDialogFactory;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.layout.Pane;
+import javafx.stage.Modality;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+public class DialogsController implements Initializable {
+    private final Pane pane;
+
+    @FXML
+    private MFXButton pError;
+
+    @FXML
+    private MFXButton pWarning;
+
+    @FXML
+    private MFXButton pInfo;
+
+    @FXML
+    private MFXButton pGeneric;
+
+    @FXML
+    private MFXButton pFade;
+
+    @FXML
+    private MFXButton pSlideLR;
+
+    @FXML
+    private MFXButton pSlideTB;
+
+    @FXML
+    private MFXButton pMix;
+
+    @FXML
+    private MFXButton sError;
+
+    @FXML
+    private MFXButton sWarning;
+
+    @FXML
+    private MFXButton sInfo;
+
+    @FXML
+    private MFXButton sGeneric;
+
+    @FXML
+    private MFXButton pDraggable;
+
+    @FXML
+    private MFXButton pOverlayClose;
+
+    @FXML
+    private MFXButton sModal;
+
+    private final AbstractMFXDialog dialog;
+    private final AbstractMFXDialog animateDialog;
+    private final String text =
+            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. " +
+                    "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, " +
+                    "when an unknown printer took a galley of type and scrambled it to make a type specimen book. " +
+                    "It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. " +
+                    "It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, " +
+                    "and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
+
+    public DialogsController(Pane pane) {
+        this.pane = pane;
+        this.dialog = MFXDialogFactory.buildDialog(DialogType.INFO, "MFXDialog - Generic Dialog", text);
+        this.animateDialog = MFXDialogFactory.buildDialog(DialogType.INFO, "", text);
+        this.animateDialog.setAnimateIn(true);
+        this.animateDialog.setAnimateOut(true);
+        this.pane.getChildren().addAll(dialog, animateDialog);
+    }
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+        pError.setOnAction(event -> {
+            resetDialog();
+            MFXDialogFactory.convertToSpecific(DialogType.ERROR, dialog);
+            dialog.setTitle("MFXDialog - Error Dialog");
+            dialog.show();
+        });
+
+        pWarning.setOnAction(event -> {
+            resetDialog();
+            MFXDialogFactory.convertToSpecific(DialogType.WARNING, dialog);
+            dialog.setTitle("MFXDialog - Warning Dialog");
+            dialog.show();
+        });
+
+        pInfo.setOnAction(event -> {
+            resetDialog();
+            MFXDialogFactory.convertToSpecific(DialogType.INFO, dialog);
+            dialog.setTitle("MFXDialog - Info Dialog");
+            dialog.show();
+        });
+
+        pGeneric.setOnAction(event -> {
+            AbstractMFXDialog genericDialog = MFXDialogFactory.buildGenericDialog("MFXDialog - Generic Dialog", text);
+            genericDialog.setCloseHandler(c -> {
+                genericDialog.close();
+                this.pane.getChildren().remove(genericDialog);
+            });
+            this.pane.getChildren().add(genericDialog);
+            genericDialog.show();
+        });
+
+        pFade.setOnAction(event -> {
+            resetDialog();
+            animateDialog.setTitle("MFXDialog - Fade Dialog");
+            animateDialog.setInAnimationType(MFXAnimationFactory.FADE_IN);
+            animateDialog.setOutAnimationType(MFXAnimationFactory.FADE_OUT);
+            animateDialog.show();
+        });
+
+        pSlideLR.setOnAction(event -> {
+            resetDialog();
+            animateDialog.setTitle("MFXDialog - Slide Left/Right Dialog");
+            animateDialog.setInAnimationType(MFXAnimationFactory.SLIDE_IN_LEFT);
+            animateDialog.setOutAnimationType(MFXAnimationFactory.SLIDE_OUT_RIGHT);
+            animateDialog.show();
+        });
+
+        pSlideTB.setOnAction(event -> {
+            resetDialog();
+            animateDialog.setTitle("MFXDialog - Slide Top/Bottom Dialog");
+            animateDialog.setInAnimationType(MFXAnimationFactory.SLIDE_IN_TOP);
+            animateDialog.setOutAnimationType(MFXAnimationFactory.SLIDE_OUT_BOTTOM);
+            animateDialog.show();
+        });
+
+        pMix.setOnAction(event -> {
+            resetDialog();
+            animateDialog.setTitle("MFXDialog - Mix Animation Dialog");
+            animateDialog.setInAnimationType(MFXAnimationFactory.SLIDE_IN_TOP);
+            animateDialog.setOutAnimationType(MFXAnimationFactory.SLIDE_OUT_RIGHT);
+            animateDialog.show();
+        });
+
+        pDraggable.setOnAction(event -> {
+            resetDialog();
+            MFXDialogFactory.convertToSpecific(DialogType.INFO, dialog);
+            dialog.setTitle("MFXDialog - Draggable Dialog");
+            dialog.setIsDraggable(true);
+            dialog.show();
+        });
+
+        pOverlayClose.setOnAction(event -> {
+            resetDialog();
+            MFXDialogFactory.convertToSpecific(DialogType.INFO, dialog);
+            dialog.setTitle("MFXDialog - Overlay Close Dialog");
+            dialog.setOverlayClose(true);
+            dialog.show();
+        });
+
+        sError.setOnAction(event -> {
+            MFXStageDialog dialog = new MFXStageDialog(DialogType.ERROR, "MFXStageDialog - Error Dialog", text);
+            dialog.show();
+        });
+
+        sWarning.setOnAction(event -> {
+            MFXStageDialog dialog = new MFXStageDialog(DialogType.WARNING, "MFXStageDialog - Warning Dialog", text);
+            dialog.show();
+        });
+
+        sInfo.setOnAction(event -> {
+            MFXStageDialog dialog = new MFXStageDialog(DialogType.INFO, "MFXStageDialog - Info Dialog", text);
+            dialog.show();
+        });
+
+        sGeneric.setOnAction(event -> {
+            MFXStageDialog dialog = new MFXStageDialog(DialogType.GENERIC, "MFXStageDialog - Generic Dialog", text);
+            dialog.show();
+        });
+
+        sModal.setOnAction(event -> {
+            MFXStageDialog dialog = new MFXStageDialog(DialogType.INFO, "MFXStageDialog - Modal Dialog", text);
+            dialog.setOwner(pane.getScene().getWindow());
+            dialog.setModality(Modality.APPLICATION_MODAL);
+            dialog.setScrimBackground(true);
+            dialog.setCenterInOwner(true);
+            dialog.show();
+        });
+    }
+
+    private void resetDialog() {
+        dialog.setOverlayClose(false);
+        dialog.setIsDraggable(false);
+    }
+}

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

@@ -7,7 +7,9 @@ module MaterialFX.demo.main {
     requires org.kordamp.ikonli.fontawesome5;
     requires javafx.fxml;
     requires javafx.graphics;
+    requires javafx.controls;
 
+    opens io.github.palexdev.materialfx.demo;
     opens io.github.palexdev.materialfx.demo.controllers;
     exports io.github.palexdev.materialfx.demo;
 }

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

@@ -1,16 +1,217 @@
 @font-face {
-    font-family: 'Comfortaa';
-    src: url('fonts/Comfortaa/Comfortaa-VariableFont_wght.ttf');
+    font-family: 'Comfortaa Bold';
+    font-weight: bold;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Comfortaa/Comfortaa-Bold.ttf') format('truetype');
 }
 
 @font-face {
-    font-family: "Comfortaa SemiBold";
-    src: url('fonts/Comfortaa/Comfortaa-SemiBold.ttf');
+    font-family: 'Comfortaa Light';
+    font-weight: 300px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Comfortaa/Comfortaa-Light.ttf') format('truetype');
 }
 
 @font-face {
-    font-family: Roboto;
-    src: url('fonts/Roboto/Roboto-Regular.ttf');
+    font-family: 'Comfortaa Medium';
+    font-weight: 500px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Comfortaa/Comfortaa-Medium.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Comfortaa Regular';
+    font-weight: normal;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Comfortaa/Comfortaa-Regular.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Comfortaa SemiBold';
+    font-weight: 600px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Comfortaa/Comfortaa-SemiBold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans Bold';
+    font-weight: bold;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-Bold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans BoldItalic';
+    font-weight: bold;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-BoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans ExtraBold';
+    font-weight: 800px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-ExtraBold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans ExtraBoldItalic';
+    font-weight: 800px;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans Light';
+    font-weight: 300px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-Light.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans LightItalic';
+    font-weight: 300px;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-LightItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans Italic';
+    font-weight: normal;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-Italic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans SemiBold';
+    font-weight: 600px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-SemiBold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans Regular';
+    font-weight: normal;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-Regular.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans SemiBoldItalic';
+    font-weight: 600px;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/OpenSans/OpenSans-SemiBoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Black';
+    font-weight: 900px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-Black.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto BlackItalic';
+    font-weight: 900px;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-BlackItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Bold';
+    font-weight: bold;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-Bold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto BoldItalic';
+    font-weight: bold;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-BoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Light';
+    font-weight: 300px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-Light.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Italic';
+    font-weight: normal;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-Italic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto LightItalic';
+    font-weight: 300px;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-LightItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Medium';
+    font-weight: 500px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-Medium.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto MediumItalic';
+    font-weight: 500px;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-MediumItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Regular';
+    font-weight: normal;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-Regular.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto ThinItalic';
+    font-weight: 100px;
+    font-style: italic;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-ThinItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto Thin';
+    font-weight: 100px;
+    font-style: normal;
+    font-display: swap;
+    src: url('fonts/Roboto/Roboto-Thin.ttf') format('truetype');
 }
 
 .label {

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

@@ -0,0 +1,57 @@
+@import "common.css";
+
+#demoPane {
+    -fx-background-color: #2b194f;
+    -fx-background-radius: 10;
+    -fx-border-radius: 10;
+}
+
+#contentPane {
+    -fx-background-color: #F4F5F6;
+    -fx-background-insets: 0 2 2 2;
+    -fx-background-radius: 10;
+    -fx-border-color: #005cd4;
+    -fx-border-radius: 10;
+    -fx-border-insets: 0 1 1 1;
+    -fx-border-width: 1.8;
+}
+
+#hLoader {
+    -fx-background-color: #005cd4;
+    -fx-background-radius: 10;
+    -fx-border-color: #565656;
+    -fx-border-width: 0.5;
+    -fx-border-radius: 10;
+}
+
+#hLoader .mfx-toggle-node {
+    -fx-background-color: #5c1ed9;
+    -fx-background-radius: 6;
+    -fx-background-insets: -1;
+    -fx-border-radius: 6;
+    -fx-border-insets: -1;
+    -fx-opacity: 0.6;
+    -mfx-shape: rectangle;
+    -mfx-size: 30px;
+}
+
+#hLoader .mfx-toggle-node .text {
+    -fx-font-family: 'Open Sans SemiBold';
+    -fx-font-size: 12.5;
+    -fx-fill: white;
+    -fx-opacity: 0.5;
+}
+
+#hLoader .mfx-toggle-node:selected {
+    -fx-background-color: linear-gradient(to right, #4a00e0 15%, #8e2de2 95%);;
+    -fx-opacity: 1;
+}
+
+#hLoader .mfx-toggle-node:selected .text {
+    -fx-opacity: 1.0;
+}
+
+#hLoader .mfx-toggle-node .ripple-generator {
+    -mfx-ripple-radius: 35px;
+    -mfx-ripple-color: rgba(198, 198, 198, 0.3)
+}

+ 14 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/demo.fxml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import io.github.palexdev.materialfx.controls.MFXHLoader?>
+<?import io.github.palexdev.materialfx.controls.MFXVLoader?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.layout.*?>
+<AnchorPane id="demoPane" fx:id="demoPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="445.0" prefWidth="780.0" stylesheets="@demo.css" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.DemoController">
+   <MFXHLoader id="hLoader" fx:id="hLoader" layoutX="45.0" layoutY="16.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="48.0" prefWidth="691.0" AnchorPane.leftAnchor="45.0" AnchorPane.rightAnchor="45.0" AnchorPane.topAnchor="15.0">
+      <padding>
+         <Insets bottom="10.0" />
+      </padding></MFXHLoader>
+   <StackPane id="contentPane" fx:id="contentPane" layoutX="14.0" layoutY="45.0" prefHeight="405.0" prefWidth="760.0" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="50.0" />
+   <MFXVLoader fx:id="vLoader" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="460.0" prefWidth="100.0" />
+</AnchorPane>

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

@@ -0,0 +1 @@
+@import url("common.css");

+ 98 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/dialogs_demo.fxml

@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import io.github.palexdev.materialfx.controls.*?>
+<?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="@toggle_buttons_demo.css" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.DialogsController">
+   <Label alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Pane Dialogs/Alerts" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets top="16.0" />
+      </StackPane.margin>
+   </Label>
+   <MFXButton fx:id="pError" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="ERROR" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets right="300.0" top="60.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pWarning" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="WARNING" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets right="100.0" top="60.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pInfo" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="INFO" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets left="100.0" top="60.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pGeneric" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="GENERIC" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets left="300.0" top="60.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pFade" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="FADE" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets right="300.0" top="100.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pSlideLR" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="SLIDE L/R" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets right="100.0" top="100.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pSlideTB" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="SLIDE T/B" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets left="100.0" top="100.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pMix" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="MIX" StackPane.alignment="TOP_CENTER">
+      <StackPane.margin>
+         <Insets left="300.0" top="100.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <Label alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Stage Dialogs/Alerts">
+      <StackPane.margin>
+         <Insets bottom="50.0" />
+      </StackPane.margin>
+   </Label>
+   <MFXButton fx:id="sError" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="ERROR">
+      <StackPane.margin>
+         <Insets right="300.0" top="25.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="sWarning" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="WARNING">
+      <StackPane.margin>
+         <Insets right="100.0" top="25.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="sInfo" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="INFO">
+      <StackPane.margin>
+         <Insets left="100.0" top="25.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="sGeneric" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="GENERIC">
+      <StackPane.margin>
+         <Insets left="300.0" top="25.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <Label alignment="CENTER" prefHeight="26.0" prefWidth="266.0" text="Options" textAlignment="CENTER">
+      <StackPane.margin>
+         <Insets top="120.0" />
+      </StackPane.margin>
+   </Label>
+   <MFXButton fx:id="pDraggable" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="Draggable (Pane)">
+      <StackPane.margin>
+         <Insets right="300.0" top="200.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="pOverlayClose" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="Overlay Close (Pane)">
+      <StackPane.margin>
+         <Insets top="200.0" />
+      </StackPane.margin>
+   </MFXButton>
+   <MFXButton fx:id="sModal" buttonType="RAISED" rippleColor="#cc74da" rippleRadius="20.0" text="Modal (Stage)">
+      <StackPane.margin>
+         <Insets left="300.0" top="200.0" />
+      </StackPane.margin>
+   </MFXButton>
+</StackPane>

BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Bold.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-BoldItalic.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBold.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Italic.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Light.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-LightItalic.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-Regular.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBold.ttf


BIN
demo/src/main/resources/io/github/palexdev/materialfx/demo/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf


+ 0 - 37
demo/src/main/resources/io/github/palexdev/materialfx/demo/main_demo.css

@@ -1,37 +0,0 @@
-.tab {
-    -fx-background-color: #fbfbfb;
-    -fx-border-width: 0 0 1 0;
-    -fx-border-color: #c2c2c2 #c2c2c2 #c2c2c2 #c2c2c2
-}
-
-.tab:selected {
-    -fx-background-radius: 0;
-    -fx-background-insets: 0;
-    -fx-background-color: #fbfbfb;
-    -fx-border-width: 0 0 5 0;
-    -fx-border-color: #c2c2c2 #c2c2c2 linear-gradient(to bottom right, #0093E9 0%, #7be4bc 100%) #c2c2c2
-}
-
-.tab-pane *.tab-header-background {
-    -fx-background-color: #fbfbfb, #fbfbfb, #fbfbfb;
-    -fx-border-width: 1 0 1 0;
-    -fx-border-color: #c2c2c2 #c2c2c2 #c2c2c2 #c2c2c2
-}
-
-.tab-pane {
-    -fx-background-color: WHITE;
-}
-
-/*.tab-pane .tab {
-    -fx-background-color: WHITE;
-    -fx-background-radius: 6;
-    -fx-border-color: gray;
-    -fx-border-radius: 6;
-    -fx-border-width: 2.5;
-    -fx-background-insets: 0 3 0 0;
-    -fx-border-insets: 0 3 0 0;
-}*/
-
-.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator {
-    -fx-border-color: transparent;
-}

+ 0 - 20
demo/src/main/resources/io/github/palexdev/materialfx/demo/main_demo.fxml

@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<?import javafx.scene.control.Tab?>
-<?import javafx.scene.control.TabPane?>
-<?import javafx.scene.layout.*?>
-<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="360.0" prefWidth="600.0" stylesheets="@main_demo.css" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
-    <TabPane prefHeight="430.0" prefWidth="600.0" tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
-        <Tab text="Buttons demo">
-            <StackPane prefHeight="258.0" prefWidth="600.0">
-                <fx:include maxWidth="-Infinity" source="buttons_demo.fxml" StackPane.alignment="CENTER" />
-            </StackPane>
-        </Tab>
-        <Tab text="Checkbox demo">
-            <fx:include source="checkboxes_demo.fxml" />
-        </Tab>
-        <Tab text="Toggle Buttons">
-            <fx:include source="toggle_buttons_demo.fxml"/>
-        </Tab>
-    </TabPane>
-</AnchorPane>

+ 9 - 0
demo/src/main/resources/io/github/palexdev/materialfx/demo/toggle_buttons_demo.css

@@ -2,4 +2,13 @@
 
 #customRippleRadius .container .ripple-generator {
     -mfx-ripple-radius: 15;
+}
+
+.mfx-toggle-node {
+    -mfx-shape: circle;
+}
+
+.mfx-toggle-node .ripple-generator {
+    -mfx-ripple-radius: 15;
+    -mfx-ripple-color: #b3b3b3;
 }

+ 22 - 31
demo/src/main/resources/io/github/palexdev/materialfx/demo/toggle_buttons_demo.fxml

@@ -5,83 +5,74 @@
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.*?>
 <?import org.kordamp.ikonli.javafx.FontIcon?>
-<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
-           prefWidth="600.0" stylesheets="@toggle_buttons_demo.css" xmlns="http://javafx.com/javafx/11.0.1"
-           xmlns:fx="http://javafx.com/fxml/1"
-           fx:controller="io.github.palexdev.materialfx.demo.controllers.TogglesController">
+<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" stylesheets="@toggle_buttons_demo.css" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="io.github.palexdev.materialfx.demo.controllers.TogglesController">
    <MFXToggleButton StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets right="320.0" top="60.0"/>
+         <Insets right="320.0" top="60.0" />
       </StackPane.margin>
    </MFXToggleButton>
    <MFXToggleButton toggleColor="#008f1b" toggleLineColor="#aaff00" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets top="60.0"/>
+         <Insets top="60.0" />
       </StackPane.margin>
    </MFXToggleButton>
-   <MFXToggleButton toggleColor="#b200ff" toggleLineColor="#ff00f6" unToggleColor="#797979" unToggleLineColor="#bfbfbf"
-                    StackPane.alignment="TOP_CENTER">
+   <MFXToggleButton toggleColor="#b200ff" toggleLineColor="#ff00f6" unToggleColor="#797979" unToggleLineColor="#bfbfbf" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets left="320.0" top="60.0"/>
+         <Insets left="320.0" top="60.0" />
       </StackPane.margin>
    </MFXToggleButton>
    <Label alignment="CENTER" maxWidth="266.0" prefHeight="26.0" text="Toggle Buttons" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets top="20.0"/>
+         <Insets top="20.0" />
       </StackPane.margin>
    </Label>
-   <MFXButton buttonType="RAISED" depthLevel="LEVEL1" onAction="#handleButtonClick" rippleColor="#0096ed"
-              rippleRadius="30.0" text="Change color">
+   <MFXButton buttonType="RAISED" depthLevel="LEVEL1" onAction="#handleButtonClick" rippleColor="#0096ed" rippleRadius="30.0" text="Change color">
       <StackPane.margin>
-         <Insets bottom="135.0" right="370.0"/>
+         <Insets bottom="135.0" right="370.0" />
       </StackPane.margin>
    </MFXButton>
-   <MFXToggleButton fx:id="toggleButton" automaticColorAdjustment="true" text="Automatic Colors" toggleColor="#006aff"
-                    StackPane.alignment="TOP_CENTER">
+   <MFXToggleButton fx:id="toggleButton" automaticColorAdjustment="true" text="Automatic Colors" toggleColor="#006aff" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets right="320.0" top="140.0"/>
+         <Insets right="320.0" top="140.0" />
       </StackPane.margin>
    </MFXToggleButton>
-   <MFXToggleButton id="customRippleRadius" automaticColorAdjustment="true" text="RippleRadiusCss" toggleColor="#0095c2"
-                    StackPane.alignment="TOP_CENTER">
+   <MFXToggleButton id="customRippleRadius" automaticColorAdjustment="true" text="RippleRadiusCss" toggleColor="#0095c2" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets top="140.0"/>
+         <Insets top="140.0" />
       </StackPane.margin>
    </MFXToggleButton>
-   <MFXToggleButton id="customRippleRadius" automaticColorAdjustment="true" disable="true" text="Disabled"
-                    toggleColor="#0095c2" StackPane.alignment="TOP_CENTER">
+   <MFXToggleButton id="customRippleRadius" automaticColorAdjustment="true" disable="true" text="Disabled" toggleColor="#0095c2" StackPane.alignment="TOP_CENTER">
       <StackPane.margin>
-         <Insets left="320.0" top="140.0"/>
+         <Insets left="320.0" top="140.0" />
       </StackPane.margin>
    </MFXToggleButton>
    <Label alignment="CENTER" maxWidth="266.0" prefHeight="26.0" text="Toggle Nodes">
       <StackPane.margin>
-         <Insets top="20.0"/>
+         <Insets top="20.0" />
       </StackPane.margin>
    </Label>
-   <MFXToggleNode>
+   <MFXToggleNode textFill="#006aff">
       <StackPane.margin>
-         <Insets right="250.0" top="150.0"/>
+         <Insets right="170.0" top="130.0" />
       </StackPane.margin>
       <graphic>
-         <FontIcon iconColor="#2d7dd7" iconLiteral="fas-home" iconSize="25"/>
+         <FontIcon iconColor="#5f9ff8" iconLiteral="fas-home" iconSize="30" />
       </graphic>
    </MFXToggleNode>
    <MFXToggleNode>
       <StackPane.margin>
-         <Insets top="150.0"/>
+         <Insets top="130.0" />
       </StackPane.margin>
       <graphic>
-         <FontIcon iconColor="#e51010" iconLiteral="fas-heart" iconSize="25"/>
+         <FontIcon iconColor="#e84747" iconLiteral="fas-heart" iconSize="30" />
       </graphic>
    </MFXToggleNode>
    <MFXToggleNode>
       <StackPane.margin>
-         <Insets left="250.0" top="150.0"/>
+         <Insets left="170.0" top="130.0" />
       </StackPane.margin>
       <graphic>
-         <FontIcon iconColor="#e28000" iconLiteral="fas-cogs" iconSize="25" selectionEnd="0" selectionFill="RED"
-                   selectionStart="0" text=""/>
+         <FontIcon iconColor="#e8a54d" iconLiteral="fas-key" iconSize="30" />
       </graphic>
    </MFXToggleNode>
 </StackPane>

+ 19 - 0
materialfx/build.gradle

@@ -39,8 +39,14 @@ task javadocJar(type: Jar, dependsOn: javadoc) {
     from javadoc.destinationDir
 }
 
+task sourcesJarBuild(type: Jar, dependsOn: classes) {
+    archiveClassifier.set('sources')
+    from sourceSets.main.allSource
+}
+
 artifacts {
     archives javadocJar
+    archives sourcesJarBuild
     archives jar
 }
 
@@ -54,3 +60,16 @@ jar {
         )
     }
 }
+
+task copyJar(type: Copy) {
+    from jar
+    into 'C:/Users/thund/AppData/Roaming/Scene Builder/Library'
+}
+
+task removeBnd(type: Delete) {
+    delete fileTree(project.buildDir) {
+        include '**/*.bnd'
+    }
+}
+
+build.dependsOn copyJar, removeBnd

+ 3 - 3
materialfx/src/main/java/io/github/palexdev/materialfx/MFXResources.java → materialfx/src/main/java/io/github/palexdev/materialfx/MFXResourcesLoader.java

@@ -6,11 +6,11 @@ import java.net.URL;
  * Utility class which manages the access to this project's assets.
  * Helps keeping the assets files structure organized.
  */
-public class MFXResources {
+public class MFXResourcesLoader {
 
-    private MFXResources() {}
+    private MFXResourcesLoader() {}
 
     public static URL load(String path) {
-        return MFXResources.class.getResource(path);
+        return MFXResourcesLoader.class.getResource(path);
     }
 }

+ 63 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/MFXResourcesManager.java

@@ -0,0 +1,63 @@
+package io.github.palexdev.materialfx;
+
+
+import javafx.scene.shape.SVGPath;
+
+public class MFXResourcesManager {
+
+    private MFXResourcesManager() {}
+
+    /**
+     * Convenience enum to keep and get SVG paths for different types of check marks.
+     */
+    public enum MarkType {
+        CASPIAN("M0,4H2L3,6L6,0H8L4,8H2Z"),
+        MODENA("M-0.25,6.083c0.843-0.758,4.583,4.833,5.75,4.833S14.5-1.5,15.917-0.917c1.292,0.532-8.75,17.083-10.5,17.083C3,16.167-1.083,6.833-0.25,6.083z"),
+        VARIANT3("M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"),
+        VARIANT4("M0 11c2.761.575 6.312 1.688 9 3.438 3.157-4.23 8.828-8.187 15-11.438-5.861 5.775-10.711 12.328-14 18.917-2.651-3.766-5.547-7.271-10-10.917z"),
+        VARIANT5("M20.303 4.846l.882.882-12.22 12.452-6.115-5.93.902-.902 5.303 5.028 11.248-11.53zm-.018-2.846l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285-3.715-3.715z"),
+        VARIANT6("M0 12.116l2.053-1.897c2.401 1.162 3.924 2.045 6.622 3.969 5.073-5.757 8.426-8.678 14.657-12.555l.668 1.536c-5.139 4.484-8.902 9.479-14.321 19.198-3.343-3.936-5.574-6.446-9.679-10.251z"),
+        VARIANT7("M0 11.522l1.578-1.626 7.734 4.619 13.335-12.526 1.353 1.354-14 18.646z"),
+        VARIANT8("M0 11.386l1.17-1.206c1.951.522 5.313 1.731 8.33 3.597 3.175-4.177 9.582-9.398 13.456-11.777l1.044 1.073-14 18.927-10-10.614z"),
+        VARIANT9("M24 6.278l-11.16 12.722-6.84-6 1.319-1.49 5.341 4.686 9.865-11.196 1.475 1.278zm-22.681 5.232l6.835 6.01-1.314 1.48-6.84-6 1.319-1.49zm9.278.218l5.921-6.728 1.482 1.285-5.921 6.756-1.482-1.313z");
+
+        private final String svhPath;
+
+        MarkType(String svhPath) {
+            this.svhPath = svhPath;
+        }
+
+        public String getSvhPath() {
+            return svhPath;
+        }
+    }
+
+    /**
+     * Convenience enum to keep and get various SVG paths.
+     */
+    public enum SVGResources {
+        CROSS("M 51.2 1.6 C 23.8 1.6 1.6 23.8 1.6 51.2 s 22.2 49.6 49.6 49.6 s 49.6 -22.2 49.6 -49.6 S 78.6 1.6 51.2 1.6 z m 0 92.8 c -23.74 0 -43.2 -19.22 -43.2 -43.2 c 0 -23.74 19.22 -43.2 43.2 -43.2 c 23.74 0 43.2 19.22 43.2 43.2 c 0 23.74 -19.22 43.2 -43.2 43.2 z m 18.96 -57.06 L 56.3 51.2 l 13.86 13.86 c 0.94 0.94 0.94 2.46 0 3.4 l -1.7 1.7 c -0.94 0.94 -2.46 0.94 -3.4 0 L 51.2 56.3 l -13.86 13.86 c -0.94 0.94 -2.46 0.94 -3.4 0 l -1.7 -1.7 c -0.94 -0.94 -0.94 -2.46 0 -3.4 l 13.86 -13.86 l -13.86 -13.86 c -0.94 -0.94 -0.94 -2.46 0 -3.4 l 1.7 -1.7 c 0.94 -0.94 2.46 -0.94 3.4 0 l 13.86 13.86 l 13.86 -13.86 c 0.94 -0.94 2.46 -0.94 3.4 0 l 1.7 1.7 c 0.92 0.94 0.92 2.46 0 3.4 z"),
+        EXCLAMATION("M 51.2 8 c 23.7242 0 43.2 19.215 43.2 43.2 c 0 23.8582 -19.322 43.2 -43.2 43.2 c -23.8488 0 -43.2 -19.3124 -43.2 -43.2 c 0 -23.8406 19.3204 -43.2 43.2 -43.2 m 0 -6.4 C 23.8086 1.6 1.6 23.8166 1.6 51.2 c 0 27.3994 22.2086 49.6 49.6 49.6 s 49.6 -22.2006 49.6 -49.6 C 100.8 23.8166 78.5914 1.6 51.2 1.6 z m -2.298 24 h 4.5958 c 1.3646 0 2.4548 1.1364 2.398 2.5 l -1.4 33.6 c -0.0536 1.2856 -1.1112 2.3 -2.398 2.3 h -1.7958 c -1.2866 0 -2.3444 -1.0146 -2.398 -2.3 l -1.4 -33.6 c -0.0566 -1.3636 1.0334 -2.5 2.398 -2.5 z M 51.2 68 c -3.0928 0 -5.6 2.5072 -5.6 5.6 s 2.5072 5.6 5.6 5.6 s 5.6 -2.5072 5.6 -5.6 s -2.5072 -5.6 -5.6 -5.6 z"),
+        EXCLAMATION_TRIANGLE("M 54.04 32 h 7.1 c 0.68 0 1.22 0.56 1.2 1.24 l -1.5 39.2 c -0.02 0.64 -0.56 1.16 -1.2 1.16 h -4.1 c -0.64 0 -1.18 -0.5 -1.2 -1.16 l -1.5 -39.2 c -0.02 -0.68 0.52 -1.24 1.2 -1.24 z M 57.6 77.6 c -3.1 0 -5.6 2.5 -5.6 5.6 s 2.5 5.6 5.6 5.6 s 5.6 -2.5 5.6 -5.6 s -2.5 -5.6 -5.6 -5.6 z m 56.3 10.4 L 65.92 4.8 c -3.68 -6.4 -12.94 -6.4 -16.64 0 L 1.3 88 c -3.68 6.38 0.92 14.4 8.32 14.4 H 105.6 c 7.36 0 12 -8 8.3 -14.4 z M 105.6 96 H 9.6 c -2.46 0 -4 -2.66 -2.78 -4.8 l 48 -83.2 c 1.22 -2.12 4.32 -2.14 5.54 0 l 48 83.2 c 1.24 2.12 -0.3 4.8 -2.76 4.8 z"),
+        INFO("M 51.2 8 c 23.7242 0 43.2 19.215 43.2 43.2 c 0 23.8582 -19.322 43.2 -43.2 43.2 c -23.8488 0 -43.2 -19.3124 -43.2 -43.2 c 0 -23.8406 19.3204 -43.2 43.2 -43.2 m 0 -6.4 C 23.8086 1.6 1.6 23.8166 1.6 51.2 c 0 27.3994 22.2086 49.6 49.6 49.6 s 49.6 -22.2006 49.6 -49.6 C 100.8 23.8166 78.5914 1.6 51.2 1.6 z m -7.2 68.8 h 2.4 V 46.4 h -2.4 c -1.3254 0 -2.4 -1.0746 -2.4 -2.4 v -1.6 c 0 -1.3254 1.0746 -2.4 2.4 -2.4 h 9.6 c 1.3254 0 2.4 1.0746 2.4 2.4 v 28 h 2.4 c 1.3254 0 2.4 1.0746 2.4 2.4 v 1.6 c 0 1.3254 -1.0746 2.4 -2.4 2.4 h -14.4 c -1.3254 0 -2.4 -1.0746 -2.4 -2.4 v -1.6 c 0 -1.3254 1.0746 -2.4 2.4 -2.4 z m 7.2 -48 c -3.5346 0 -6.4 2.8654 -6.4 6.4 s 2.8654 6.4 6.4 6.4 s 6.4 -2.8654 6.4 -6.4 s -2.8654 -6.4 -6.4 -6.4 z"),
+        X("M 48.544 51.2 l 20.014 -20.014 c 2.456 -2.456 2.456 -6.438 0 -8.896 l -4.448 -4.448 c -2.456 -2.456 -6.438 -2.456 -8.896 0 L 35.2 37.856 L 15.186 17.842 c -2.456 -2.456 -6.438 -2.456 -8.896 0 L 1.842 22.29 c -2.456 2.456 -2.456 6.438 0 8.896 L 21.856 51.2 L 1.842 71.214 c -2.456 2.456 -2.456 6.438 0 8.896 l 4.448 4.448 c 2.456 2.456 6.44 2.456 8.896 0 L 35.2 64.544 l 20.014 20.014 c 2.456 2.456 6.44 2.456 8.896 0 l 4.448 -4.448 c 2.456 -2.456 2.456 -6.438 0 -8.896 L 48.544 51.2 z");
+
+        private final String svgPath;
+
+        SVGResources(String svgPath) {
+            this.svgPath = svgPath;
+        }
+
+        public String getStringPath() {
+            return svgPath;
+        }
+
+        public SVGPath getSvgPath() {
+            SVGPath path = new SVGPath();
+            path.setContent(svgPath);
+            return path;
+        }
+    }
+}
+
+

+ 67 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/beans/MFXLoadItem.java

@@ -0,0 +1,67 @@
+package io.github.palexdev.materialfx.beans;
+
+import javafx.scene.Node;
+import javafx.scene.control.ToggleButton;
+import javafx.util.Callback;
+
+import java.net.URL;
+
+/**
+ * Support bean for {@code MFXHLoader} and {@code MFXVLoader}
+ * Basically a wrapper for a {@code Node} which is the root of an fxml file,
+ * the controller factory of the fxml file, the toggle button associated with the item
+ * which is responsible for the views switching, the {@code URL} of the fxml file,
+ * and an index which represents the toggle button position in the children list of the loader.
+ */
+public class MFXLoadItem {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private final int index;
+
+    private Node root;
+    private final Callback<Class<?>, Object> controllerFactory;
+    private final ToggleButton button;
+    private final URL fxmlURL;
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public MFXLoadItem(int index, ToggleButton button, URL fxmlURL) {
+        this(index, button, fxmlURL, null);
+    }
+
+    public MFXLoadItem(int index, ToggleButton button, URL fxmlURL, Callback<Class<?>, Object> controllerFactory) {
+        this.index = index;
+        this.button = button;
+        this.fxmlURL = fxmlURL;
+        this.controllerFactory = controllerFactory;
+    }
+
+    //================================================================================
+    // Methods
+    //================================================================================
+    public int getIndex() {
+        return index;
+    }
+
+    public Node getRoot() {
+        return root;
+    }
+
+    public Callback<Class<?>, Object> getControllerFactory() {
+        return controllerFactory;
+    }
+
+    public void setRoot(Node root) {
+        this.root = root;
+    }
+
+    public ToggleButton getButton() {
+        return button;
+    }
+
+    public URL getFxmlURL() {
+        return fxmlURL;
+    }
+}

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

@@ -1,6 +1,6 @@
 package io.github.palexdev.materialfx.controls;
 
-import io.github.palexdev.materialfx.MFXResources;
+import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.controls.enums.ButtonType;
 import io.github.palexdev.materialfx.effects.DepthLevel;
 import io.github.palexdev.materialfx.effects.RippleGenerator;
@@ -32,7 +32,7 @@ public class MFXButton extends Button {
     //================================================================================
     private static final StyleablePropertyFactory<MFXButton> FACTORY = new StyleablePropertyFactory<>(Button.getClassCssMetaData());
     private final String STYLE_CLASS = "mfx-button";
-    private final String STYLESHEET = MFXResources.load("css/mfx-button.css").toString();
+    private final String STYLESHEET = MFXResourcesLoader.load("css/mfx-button.css").toString();
     private final RippleGenerator rippleGenerator = new RippleGenerator(this);
 
     //================================================================================
@@ -40,29 +40,38 @@ public class MFXButton extends Button {
     //================================================================================
     public MFXButton() {
         setText("Button");
-        init();
+        initialize();
     }
 
     public MFXButton(String text) {
         super(text);
-        init();
+        initialize();
+    }
+
+    public MFXButton(String text, double prefWidth, double prefHeight) {
+        super(text);
+        setPrefSize(prefWidth, prefHeight);
+        initialize();
     }
 
     public MFXButton(String text, Node graphic) {
         super(text, graphic);
-        init();
+        initialize();
     }
 
     //================================================================================
     // Methods
     //================================================================================
-    private void init() {
+    private void initialize() {
         getStyleClass().add(STYLE_CLASS);
         setAlignment(Pos.CENTER);
         setBindings();
+
+        setRippleRadius(25);
+        setRippleColor(Color.rgb(190, 190, 190));
     }
 
-    //================================================================================
+//================================================================================
     // Ripple properties
     //================================================================================
 

+ 3 - 3
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java

@@ -1,7 +1,7 @@
 package io.github.palexdev.materialfx.controls;
 
-import io.github.palexdev.materialfx.MFXResources;
-import io.github.palexdev.materialfx.controls.enums.MarkType;
+import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.MFXResourcesManager.MarkType;
 import io.github.palexdev.materialfx.skins.MFXCheckboxSkin;
 import javafx.css.*;
 import javafx.scene.control.CheckBox;
@@ -23,7 +23,7 @@ public class MFXCheckbox extends CheckBox {
     //================================================================================
     private static final StyleablePropertyFactory<MFXCheckbox> FACTORY = new StyleablePropertyFactory<>(CheckBox.getClassCssMetaData());
     private final String STYLE_CLASS = "mfx-checkbox";
-    private final String STYLESHEET = MFXResources.load("css/mfx-checkbox.css").toString();
+    private final String STYLESHEET = MFXResourcesLoader.load("css/mfx-checkbox.css").toString();
 
     //================================================================================
     // Constructors

+ 130 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDialog.java

@@ -0,0 +1,130 @@
+package io.github.palexdev.materialfx.controls;
+
+import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog;
+import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
+import javafx.scene.Parent;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.Pane;
+import javafx.util.Duration;
+
+public class MFXDialog extends AbstractMFXDialog {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private final String STYLE_CLASS = "mfx-dialog";
+    private final String STYLESHEET = MFXResourcesLoader.load("css/mfx-dialog.css").toString();
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public MFXDialog() {
+        initialize();
+    }
+
+    //================================================================================
+    // Methods
+    //================================================================================
+    private void initialize() {
+        getStyleClass().add(STYLE_CLASS);
+        getStylesheets().setAll(STYLESHEET);
+
+        overlayClose.addListener(((observable, oldValue, newValue) -> {
+            if (newValue) {
+                addOverlayHandler();
+            } else {
+                removeOverlayHandler();
+            }
+        }));
+
+        isDraggable.addListener((observable, oldValue, newValue) -> {
+            if (newValue) {
+                makeDraggable();
+            } else {
+                clearDragHandlers();
+            }
+        });
+    }
+
+    //================================================================================
+    // Override Methods
+    //================================================================================
+
+    /**
+     * Tries to center the dialog before showing, currently works only for {@code AnchorPane}s
+     */
+    @Override
+    public void computeCenter() {
+        Parent parent = this.getParent();
+        if (!(parent instanceof Pane)) {
+            return;
+        }
+
+        Pane dialogParent = (Pane) parent;
+        if (dialogParent instanceof AnchorPane) {
+            double topBottom = (dialogParent.getHeight() - getPrefHeight()) / 2;
+            double leftRight = (dialogParent.getWidth() - getPrefWidth()) / 2;
+            setLayoutX(leftRight);
+            setLayoutY(topBottom);
+        }
+    }
+
+    /**
+     * Shows the dialog, computes the center and plays animations if requested
+     */
+    @Override
+    public void show() {
+        if (centerBeforeShow) {
+            computeCenter();
+        }
+
+        if (animateIn.get()) {
+            inAnimation.getChildren().setAll(inAnimationType.build(this, animationMillis.get()));
+            if (scrimBackground.get()) {
+                Timeline fadeInScrim = MFXAnimationFactory.FADE_IN.build(scrimEffect.getScrimNode(), animationMillis.get());
+                fadeInScrim.getKeyFrames().add(0,
+                        new KeyFrame(Duration.ZERO, event -> scrimEffect.modalScrim((Pane) getParent(), this, scrimOpacity.get()))
+                );
+                inAnimation.getChildren().add(fadeInScrim);
+            }
+            inAnimation.play();
+        } else {
+            if (scrimBackground.get()) {
+                scrimEffect.modalScrim((Pane) getParent(), this, scrimOpacity.get());
+            }
+        }
+        setVisible(true);
+    }
+
+    /**
+     * Closes the dialog, plays animations if requested
+     */
+    @Override
+    public void close() {
+        if (animateOut.get()) {
+            outAnimation.getChildren().setAll(outAnimationType.build(this, animationMillis.get()));
+            if (scrimBackground.get()) {
+                Timeline fadeOutScrim = MFXAnimationFactory.FADE_OUT.build(scrimEffect.getScrimNode(), animationMillis.get());
+                fadeOutScrim.setOnFinished(event -> scrimEffect.removeEffect((Pane) getParent()));
+                outAnimation.getChildren().add(fadeOutScrim);
+            }
+            outAnimation.setOnFinished(event -> {
+                setVisible(false);
+                setOpacity(1.0);
+            });
+            outAnimation.play();
+        } else {
+            if (scrimBackground.get()) {
+                scrimEffect.removeEffect((Pane) getParent());
+            }
+            setVisible(false);
+        }
+    }
+
+    @Override
+    public String getUserAgentStylesheet() {
+        return STYLESHEET;
+    }
+}

+ 277 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXHLoader.java

@@ -0,0 +1,277 @@
+package io.github.palexdev.materialfx.controls;
+
+import io.github.palexdev.materialfx.beans.MFXLoadItem;
+import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
+import io.github.palexdev.materialfx.utils.Loader;
+import io.github.palexdev.materialfx.utils.ToggleButtonsUtil;
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.MapChangeListener;
+import javafx.collections.ObservableMap;
+import javafx.concurrent.Task;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.util.Callback;
+
+import java.net.URL;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Convenience class for creating dashboards, no more hassle on managing multiple views.
+ * <p>
+ * This control extends {@code HBox} and has a {@code ThreadExecutorService} for loading fxml files in background
+ * leaving the UI responsive
+ * <p></p>
+ * Every time an fxml file is submitted with '{@code addItem}' a wrapper class (MFXItem) is created,
+ * then it's sent to the '{@code load}' method which creates a {@code Task} and submits it to the executor.
+ * <p>
+ * Everytime an MFXItem is loaded, a new ToggleButton is added to the {@code HBox}, the button already
+ * has a listener on the selectedProperty for switching the view and since nodes are cached the transition is faster
+ * than loading the fxml again.
+ * <p></p>
+ * Every toggle button is part of a ToggleGroup which is modified with
+ * the {@code ToggleButtonsUtil} class  to add 'always one selected' support
+ */
+public class MFXHLoader extends HBox {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private final String STYLE_CLASS = "mfx-hloader";
+    private Pane contentPane;
+    private final ToggleGroup toggleGroup;
+
+    private final BooleanProperty isAnimated = new SimpleBooleanProperty(false);
+    private final DoubleProperty animationMillis = new SimpleDoubleProperty(800);
+    private MFXAnimationFactory animationType = MFXAnimationFactory.FADE_IN;
+
+    private final ObservableMap<String, MFXLoadItem> bindMap;
+    private final ThreadPoolExecutor executor;
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public MFXHLoader() {
+        this(null);
+    }
+
+    public MFXHLoader(Pane contentPane) {
+        initialize();
+        this.contentPane = contentPane;
+        this.setPrefSize(Region.USE_COMPUTED_SIZE, 60);
+        this.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+        this.setSpacing(20);
+        this.setAlignment(Pos.CENTER);
+
+        this.toggleGroup = new ToggleGroup();
+
+        this.bindMap = FXCollections.observableHashMap();
+        this.executor = new ThreadPoolExecutor(
+                2,
+                4,
+                20,
+                TimeUnit.SECONDS,
+                new LinkedBlockingDeque<>(),
+                runnable -> {
+                    Thread t = Executors.defaultThreadFactory().newThread(runnable);
+                    t.setDaemon(true);
+                    return t;
+                }
+        );
+        this.executor.allowCoreThreadTimeOut(true);
+
+        this.bindMap.addListener((MapChangeListener<String, MFXLoadItem>) change -> {
+            if (change.wasAdded()) {
+                MFXLoadItem item = change.getValueAdded();
+                load(item);
+            }
+        });
+    }
+
+    //================================================================================
+    // Methods
+    //================================================================================
+    private void initialize() {
+        getStyleClass().add(STYLE_CLASS);
+    }
+
+    /**
+     * After calling 'addItem' a new task is created and submitted to the executor
+     * to load the given {@code MFXItem}.
+     *
+     * @param item The given item
+     */
+    private void load(MFXLoadItem item) {
+        Task<Void> task = new Task<>() {
+            @Override
+            protected Void call() {
+                try {
+                    FXMLLoader loader = new FXMLLoader(item.getFxmlURL());
+                    if (item.getControllerFactory() != null) {
+                        loader.setControllerFactory(item.getControllerFactory());
+                    }
+                    Node root = loader.load();
+                    item.setRoot(root);
+
+                    item.getButton().selectedProperty().addListener((observable, oldValue, newValue) -> {
+                        if (isAnimated.get()) {
+                            animationType.build(item.getRoot(), animationMillis.doubleValue()).play();
+                        }
+                        if (newValue) {
+                            try {
+                                contentPane.getChildren().set(0, item.getRoot());
+                            } catch (IndexOutOfBoundsException ex) {
+                                contentPane.getChildren().add(0, item.getRoot());
+                            }
+                        }
+                    });
+                    Platform.runLater(() -> MFXHLoader.this.getChildren().set(item.getIndex(), item.getButton()));
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                }
+                return null;
+            }
+        };
+        this.executor.submit(task);
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with a default key.
+     *
+     * @param index    The position of the button in the {@code HBox}
+     * @param button   The given button
+     * @param fxmlFile The given fxml file
+     */
+    public void addItem(int index, ToggleButton button, URL fxmlFile) {
+        Loader.checkFxmlFile(fxmlFile);
+        addItem(index, Loader.generateKey(fxmlFile), button, fxmlFile);
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with the given key.
+     *
+     * @param index    The position of the button in the {@code HBox}
+     * @param key      The given key
+     * @param button   The given button
+     * @param fxmlFile The given fxml file
+     */
+    public void addItem(int index, String key, ToggleButton button, URL fxmlFile) {
+        Loader.checkFxmlFile(fxmlFile);
+        this.getChildren().add(button);
+        button.setToggleGroup(toggleGroup);
+        ToggleButtonsUtil.addAlwaysOneSelectedSupport(toggleGroup);
+        this.bindMap.putIfAbsent(key, new MFXLoadItem(index, button, fxmlFile));
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with a default key.
+     *
+     * @param index             The position of the button in the {@code HBox}
+     * @param button            The given button
+     * @param fxmlFile          The given fxml file
+     * @param controllerFactory The given controller factory
+     */
+    public void addItem(int index, ToggleButton button, URL fxmlFile, Callback<Class<?>, Object> controllerFactory) {
+        Loader.checkFxmlFile(fxmlFile);
+        addItem(index, Loader.generateKey(fxmlFile), button, fxmlFile, controllerFactory);
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with the given key.
+     *
+     * @param index             The position of the button in the {@code HBox}
+     * @param key               The given key
+     * @param button            The given button
+     * @param fxmlFile          The given fxml file
+     * @param controllerFactory The given controller factory
+     */
+    public void addItem(int index, String key, ToggleButton button, URL fxmlFile, Callback<Class<?>, Object> controllerFactory) {
+        Loader.checkFxmlFile(fxmlFile);
+        this.getChildren().add(button);
+        button.setToggleGroup(toggleGroup);
+        ToggleButtonsUtil.addAlwaysOneSelectedSupport(toggleGroup);
+        this.bindMap.putIfAbsent(key, new MFXLoadItem(index, button, fxmlFile, controllerFactory));
+    }
+
+    /**
+     * Sets the pane in which switching views.
+     * This method MUST be called before loading any item.
+     */
+    public void setContentPane(Pane contentPane) {
+        this.contentPane = contentPane;
+    }
+
+    /**
+     * Sets the default view to set on show.
+     * This method should be called after adding any item.
+     *
+     * @param key The key of the wanted item
+     */
+    public void setDefault(String key) {
+        MFXLoadItem item = getLoadItem(key);
+        Task<Void> nullCheckTask = new Task<>() {
+            @Override
+            protected Void call() {
+                if (item.getRoot() == null) {
+                    this.runAndReset();
+                } else {
+                    item.getButton().setSelected(true);
+                }
+                return null;
+            }
+        };
+        this.executor.submit(nullCheckTask);
+    }
+
+    public MFXLoadItem getLoadItem(String key) {
+        return this.bindMap.get(key);
+    }
+
+    public boolean isIsAnimated() {
+        return isAnimated.get();
+    }
+
+    public BooleanProperty isAnimatedProperty() {
+        return isAnimated;
+    }
+
+    public void setIsAnimated(boolean isAnimated) {
+        this.isAnimated.set(isAnimated);
+    }
+
+    public double getAnimationMillis() {
+        return animationMillis.get();
+    }
+
+    public DoubleProperty animationMillisProperty() {
+        return animationMillis;
+    }
+
+    public void setAnimationMillis(double animationMillis) {
+        this.animationMillis.set(animationMillis);
+    }
+
+    public MFXAnimationFactory getAnimationType() {
+        return animationType;
+    }
+
+    public void setAnimationType(MFXAnimationFactory animationType) {
+        this.animationType = animationType;
+    }
+}

+ 211 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStageDialog.java

@@ -0,0 +1,211 @@
+package io.github.palexdev.materialfx.controls;
+
+import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog;
+import io.github.palexdev.materialfx.controls.enums.DialogType;
+import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
+import io.github.palexdev.materialfx.controls.factories.MFXStageDialogFactory;
+import io.github.palexdev.materialfx.effects.MFXScrimEffect;
+import javafx.animation.KeyFrame;
+import javafx.animation.ParallelTransition;
+import javafx.animation.Timeline;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.event.EventHandler;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+import javafx.stage.WindowEvent;
+import javafx.util.Duration;
+
+/**
+ * Wrapper class for creating MFXDialogs that use a new {@code Stage} to show instead of using a container.
+ */
+public class MFXStageDialog {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private final Stage dialogStage;
+    private final BooleanProperty centerInOwner = new SimpleBooleanProperty(false);
+
+    private final MFXScrimEffect scrimEffect = new MFXScrimEffect();
+    private double scrimOpacity = 0.15;
+    private boolean scrimBackground = false;
+
+    private boolean animate = true;
+    private double animationMillis = 400;
+    private final ParallelTransition inAnimation = new ParallelTransition();
+    private final ParallelTransition outAnimation = new ParallelTransition();
+
+    private double xOffset;
+    private double yOffset;
+
+    private final EventHandler<WindowEvent> centerHandler = new EventHandler<>() {
+        @Override
+        public void handle(WindowEvent event) {
+            double centerXPosition = dialogStage.getOwner().getX() + dialogStage.getOwner().getWidth()/2d;
+            double centerYPosition = dialogStage.getOwner().getY() + dialogStage.getOwner().getHeight()/2d;
+            dialogStage.setX(centerXPosition - dialogStage.getWidth()/2d);
+            dialogStage.setY(centerYPosition - dialogStage.getHeight()/2d);
+        }
+    };
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public MFXStageDialog(AbstractMFXDialog dialog) {
+        this.dialogStage = MFXStageDialogFactory.buildDialog(dialog);
+        initialize();
+    }
+
+    public MFXStageDialog(DialogType type, String title, String content) {
+        this.dialogStage = MFXStageDialogFactory.buildDialog(type, title, content);
+        initialize();
+    }
+
+    private void initialize() {
+        this.dialogStage.getScene().getRoot().setOnMousePressed(event -> {
+            xOffset = dialogStage.getX() - event.getScreenX();
+            yOffset = dialogStage.getY() - event.getScreenY();
+        });
+        this.dialogStage.getScene().getRoot().setOnMouseDragged(event -> {
+            dialogStage.setX(event.getScreenX() + xOffset);
+            dialogStage.setY(event.getScreenY() + yOffset);
+        });
+        this.centerInOwner.addListener((observable, oldValue, newValue) -> {
+            if (newValue) {
+                dialogStage.addEventHandler(WindowEvent.WINDOW_SHOWN, centerHandler);
+            } else {
+                dialogStage.removeEventHandler(WindowEvent.WINDOW_SHOWN, centerHandler);
+            }
+        });
+
+        getDialog().setCloseHandler(event -> close());
+    }
+
+    //================================================================================
+    // Methods
+    //================================================================================
+
+    /**
+     * Shows the dialog by showing the stage, center the stage in its owner and plays animations if requested
+     */
+    public void show() {
+        if (animate) {
+            resetAnimation();
+        }
+
+        if (scrimBackground) {
+            if (dialogStage.getOwner() == null || dialogStage.getModality().equals(Modality.NONE)) {
+                throw new IllegalStateException("Scrim background is set to true but the dialog stage owner is null or modality is not set!!");
+            }
+            if (animate) {
+                Timeline fadeInScrim = MFXAnimationFactory.FADE_IN.build(scrimEffect.getScrimNode(), animationMillis);
+                fadeInScrim.getKeyFrames().add(
+                        new KeyFrame(Duration.ONE, event -> scrimEffect.scrimWindow(dialogStage.getOwner(), scrimOpacity))
+                );
+                inAnimation.getChildren().add(fadeInScrim);
+            } else {
+                scrimEffect.scrimWindow(dialogStage.getOwner(), scrimOpacity);
+            }
+        }
+
+        if (animate) {
+            inAnimation.play();
+        }
+        if (centerInOwner.get()) {
+            if (dialogStage.getOwner() == null) {
+                throw new NullPointerException("Center in owner is set to true but dialog stage owner is null!!");
+            }
+        }
+        this.dialogStage.show();
+    }
+
+    /**
+     * Closes the dialog by closing the stage, plays animations if requested.
+     */
+    public void close() {
+        if (animate) {
+            resetAnimation();
+        }
+
+        if (scrimBackground) {
+            if (animate) {
+                Timeline fadeOutScrim = MFXAnimationFactory.FADE_OUT.build(scrimEffect.getScrimNode(), animationMillis);
+                fadeOutScrim.setOnFinished(event -> scrimEffect.removeEffect(dialogStage.getOwner()));
+                outAnimation.getChildren().add(fadeOutScrim);
+            } else {
+                scrimEffect.removeEffect(dialogStage.getOwner());
+            }
+        }
+
+        if (animate) {
+            outAnimation.setOnFinished(event -> this.dialogStage.close());
+            outAnimation.play();
+        } else {
+            this.dialogStage.close();
+        }
+    }
+
+    /**
+     * Sets the stage's owner
+     * @see Stage
+     */
+    public void setOwner(Window owner) {
+        this.dialogStage.initOwner(owner);
+    }
+
+    /**
+     * Sets the stage modality
+     * @see Stage
+     */
+    public void setModality(Modality modality) {
+        this.dialogStage.initModality(modality);
+    }
+
+    /**
+     * Returns the AbstractMFXDialog associated with this MFXStageDialog
+     * You can only change title and content as other properties are ignored
+     */
+    public AbstractMFXDialog getDialog() {
+        return (AbstractMFXDialog) this.dialogStage.getScene().getRoot();
+    }
+
+    /**
+     * Resets all parallel animations to one single animation, respectively FADE_IN and FADE_OUT
+     */
+    private void resetAnimation() {
+        this.inAnimation.getChildren().clear();
+        this.inAnimation.getChildren().add(MFXAnimationFactory.FADE_IN.build(dialogStage.getScene().getRoot(), animationMillis));
+
+        this.outAnimation.getChildren().clear();
+        outAnimation.getChildren().add(MFXAnimationFactory.FADE_OUT.build(dialogStage.getScene().getRoot(), animationMillis));
+    }
+
+    public boolean isCenterInOwner() {
+        return centerInOwner.get();
+    }
+
+    public BooleanProperty centerInOwnerProperty() {
+        return centerInOwner;
+    }
+
+    public void setCenterInOwner(boolean centerInOwner) {
+        this.centerInOwner.set(centerInOwner);
+    }
+
+    public void setScrimOpacity(double scrimOpacity) {
+        this.scrimOpacity = scrimOpacity;
+    }
+
+    public void setScrimBackground(boolean scrimBackground) {
+        this.scrimBackground = scrimBackground;
+    }
+
+    public void setAnimate(boolean animate) {
+        this.animate = animate;
+    }
+
+    public void setAnimationMillis(double animationMillis) {
+        this.animationMillis = animationMillis;
+    }
+}

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

@@ -1,6 +1,6 @@
 package io.github.palexdev.materialfx.controls;
 
-import io.github.palexdev.materialfx.MFXResources;
+import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.skins.MFXToggleButtonSkin;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.SimpleBooleanProperty;
@@ -25,7 +25,7 @@ public class MFXToggleButton extends ToggleButton {
     //================================================================================
     private static final StyleablePropertyFactory<MFXToggleButton> FACTORY = new StyleablePropertyFactory<>(ToggleButton.getClassCssMetaData());
     private final String STYLE_CLASS = "mfx-toggle-button";
-    private final String STYLESHEET = MFXResources.load("css/mfx-togglebutton.css").toString();
+    private final String STYLESHEET = MFXResourcesLoader.load("css/mfx-togglebutton.css").toString();
 
     //================================================================================
     // Constructors

+ 153 - 34
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXToggleNode.java

@@ -1,69 +1,159 @@
 package io.github.palexdev.materialfx.controls;
 
-import io.github.palexdev.materialfx.MFXResources;
-import io.github.palexdev.materialfx.skins.MFXToggleNodeSkin;
-import javafx.beans.property.DoubleProperty;
-import javafx.beans.property.SimpleDoubleProperty;
+import io.github.palexdev.materialfx.MFXResourcesLoader;
+import io.github.palexdev.materialfx.controls.enums.ToggleNodeShape;
+import io.github.palexdev.materialfx.effects.RippleGenerator;
 import javafx.css.*;
+import javafx.geometry.Insets;
 import javafx.scene.Node;
 import javafx.scene.control.Skin;
 import javafx.scene.control.ToggleButton;
+import javafx.scene.control.skin.ToggleButtonSkin;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.layout.Region;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
+import javafx.scene.shape.Circle;
+import javafx.util.Duration;
 
 import java.util.List;
 
-/**
- * This control allows to create a toggle button with any {@code Node} set as graphic.
- * <p>
- * For example you can see in the demo this is used with Ikonli icons.
- * <p>
- * Extends {@code ToggleButton}, redefines the style class to "mfx-toggle-node" for usage in CSS and
- * includes a {@code RippleGenerator}(in the Skin) to generate ripple effect when toggled/untoggled.
- */
 public class MFXToggleNode extends ToggleButton {
     //================================================================================
     // Properties
     //================================================================================
     private static final StyleablePropertyFactory<MFXToggleNode> FACTORY = new StyleablePropertyFactory<>(ToggleButton.getClassCssMetaData());
-    private final String STYLE_CLASS = "mfx-toggle-node";
-    private final String STYLESHEET = MFXResources.load("css/mfx-togglenode.css").toString();
+    private final String STYLECLASS = "mfx-toggle-node";
+    private final String STYLESHEET = MFXResourcesLoader.load("css/mfx-togglenode.css").toString();
+    private final RippleGenerator rippleGenerator = new RippleGenerator(this);
 
     //================================================================================
     // Constructors
     //================================================================================
     public MFXToggleNode() {
+        setText("");
+        initialize();
+    }
+
+    public MFXToggleNode(String text) {
+        super(text);
         initialize();
     }
 
     public MFXToggleNode(Node graphic) {
         super("", graphic);
+        initialize();
+    }
+
+    public MFXToggleNode(String text, Node graphic) {
+        super("", graphic);
+        initialize();
     }
 
     //================================================================================
     // Methods
     //================================================================================
     private void initialize() {
-        getStyleClass().add(STYLE_CLASS);
+        getStyleClass().add(STYLECLASS);
+        setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+        setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+
+        rippleGenerator.setAnimateBackground(false);
+        rippleGenerator.setRippleColor(Color.GRAY);
+        rippleGenerator.setInDuration(Duration.millis(350));
+
+        prefWidthProperty().bind(size);
+        prefHeightProperty().bind(size);
+        clip();
+
+        addListeners();
+        setSize(40);
+    }
+
+    /**
+     * Adds listener for toggleShapeProperty, sizeProperty and selectedProperty
+     */
+    private void addListeners() {
+        toggleShapeProperty().addListener((observable, oldValue, newValue) -> {
+            if (newValue.equals(ToggleNodeShape.CIRCLE)) {
+                prefWidthProperty().bind(size);
+                prefHeightProperty().bind(size);
+                clip();
+            } else {
+                setClip(null);
+                prefWidthProperty().bind(size.multiply(3.5));
+                prefHeightProperty().bind(size);
+            }
+        });
+
+        sizeProperty().addListener((sObservable, sOldValue, sNewValue) -> {
+            if (sNewValue.doubleValue() != sOldValue.doubleValue()) {
+                setSize(sNewValue.doubleValue());
+            }
+        });
+
+        selectedProperty().addListener((observable, oldValue, newValue) -> {
+            if (newValue) {
+                setBackground(new Background(new BackgroundFill(selectedColor.get(), CornerRadii.EMPTY, Insets.EMPTY)));
+            } else {
+                setBackground(new Background(new BackgroundFill(unSelectedColor.get(), CornerRadii.EMPTY, Insets.EMPTY)));
+            }
+        });
+    }
+
+    /**
+     * Resets the clip
+     */
+    private void clip() {
+        setClip(null);
+        Circle circle = new Circle(getSize() * 0.5);
+        circle.centerXProperty().bind(widthProperty().divide(2.0));
+        circle.centerYProperty().bind(heightProperty().divide(2.0));
+        setClip(circle);
     }
 
     //================================================================================
     // Styleable properties
     //================================================================================
 
+    /**
+     * Specifies the size (both width and height) of the control.
+     */
+    private final StyleableDoubleProperty size = new SimpleStyleableDoubleProperty(
+            StyleableProperties.SIZE,
+            this,
+            "size",
+            40.0
+    );
+
+    /**
+     * Specifies the shape of the control
+     */
+    private final StyleableObjectProperty<ToggleNodeShape> toggleShape = new SimpleStyleableObjectProperty<>(
+            StyleableProperties.SHAPE,
+            this,
+            "toggleShape",
+            ToggleNodeShape.CIRCLE
+    );
+
     /**
      * Specifies the background color when selected.
+     *
      * @see Color
      */
     private final StyleableObjectProperty<Paint> selectedColor = new SimpleStyleableObjectProperty<>(
             StyleableProperties.SELECTED_COLOR,
             this,
             "selectedColor",
-            Color.rgb(0, 0, 0, 0.2)
+            Color.rgb(190, 190, 190, 0.5)
     );
 
     /**
      * Specifies the background color when unselected.
+     *
      * @see Color
      */
     private final StyleableObjectProperty<Paint> unSelectedColor = new SimpleStyleableObjectProperty<>(
@@ -73,10 +163,29 @@ public class MFXToggleNode extends ToggleButton {
             Color.TRANSPARENT
     );
 
-    /**
-     * Specifies the border width of the control when selected.
-     */
-    private final DoubleProperty strokeWidth = new SimpleDoubleProperty(3.0);
+    public double getSize() {
+        return size.get();
+    }
+
+    public StyleableDoubleProperty sizeProperty() {
+        return size;
+    }
+
+    public void setSize(double size) {
+        this.size.set(size);
+    }
+
+    public ToggleNodeShape getToggleShape() {
+        return toggleShape.get();
+    }
+
+    public StyleableObjectProperty<ToggleNodeShape> toggleShapeProperty() {
+        return toggleShape;
+    }
+
+    public void setToggleShape(ToggleNodeShape toggleShape) {
+        this.toggleShape.set(toggleShape);
+    }
 
     public Paint getSelectedColor() {
         return selectedColor.get();
@@ -102,24 +211,27 @@ public class MFXToggleNode extends ToggleButton {
         this.unSelectedColor.set(unSelectedColor);
     }
 
-    public double getStrokeWidth() {
-        return strokeWidth.get();
-    }
-
-    public DoubleProperty strokeWidthProperty() {
-        return strokeWidth;
-    }
-
-    public void setStrokeWidth(double strokeWidth) {
-        this.strokeWidth.set(strokeWidth);
-    }
-
     //================================================================================
     // CssMetaData
     //================================================================================
     private static class StyleableProperties {
         private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;
 
+        private static final CssMetaData<MFXToggleNode, Number> SIZE =
+        FACTORY.createSizeCssMetaData(
+                "-mfx-size",
+                MFXToggleNode::sizeProperty,
+                40
+        );
+
+        private static final CssMetaData<MFXToggleNode, ToggleNodeShape> SHAPE =
+                FACTORY.createEnumCssMetaData(
+                        ToggleNodeShape.class,
+                        "-mfx-shape",
+                        MFXToggleNode::toggleShapeProperty,
+                        ToggleNodeShape.CIRCLE
+                );
+
         private static final CssMetaData<MFXToggleNode, Paint> SELECTED_COLOR =
                 FACTORY.createPaintCssMetaData(
                         "-mfx-selected-color",
@@ -135,7 +247,7 @@ public class MFXToggleNode extends ToggleButton {
                 );
 
         static {
-            cssMetaDataList = List.of(SELECTED_COLOR, UNSELECTED_COLOR);
+            cssMetaDataList = List.of(SIZE, SHAPE, SELECTED_COLOR, UNSELECTED_COLOR);
         }
     }
 
@@ -149,7 +261,14 @@ public class MFXToggleNode extends ToggleButton {
 
     @Override
     protected Skin<?> createDefaultSkin() {
-        return new MFXToggleNodeSkin(this);
+        ToggleButtonSkin skin = new ToggleButtonSkin(this);
+        this.getChildren().add(0, rippleGenerator);
+        this.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
+            rippleGenerator.setGeneratorCenterX(event.getX());
+            rippleGenerator.setGeneratorCenterY(event.getY());
+            rippleGenerator.createRipple();
+        });
+        return skin;
     }
 
     @Override

+ 277 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXVLoader.java

@@ -0,0 +1,277 @@
+package io.github.palexdev.materialfx.controls;
+
+import io.github.palexdev.materialfx.beans.MFXLoadItem;
+import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
+import io.github.palexdev.materialfx.utils.Loader;
+import io.github.palexdev.materialfx.utils.ToggleButtonsUtil;
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.MapChangeListener;
+import javafx.collections.ObservableMap;
+import javafx.concurrent.Task;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.util.Callback;
+
+import java.net.URL;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Convenience class for creating dashboards, no more hassle on managing multiple views.
+ * <p>
+ * This control extends {@code VBox} and has a {@code ThreadExecutorService} for loading fxml files in background
+ * leaving the UI responsive
+ * <p></p>
+ * Every time an fxml file is submitted with '{@code addItem}' a wrapper class (MFXItem) is created,
+ * then it's sent to the '{@code load}' method which creates a {@code Task} and submits it to the executor.
+ * <p>
+ * Everytime an MFXItem is loaded, a new ToggleButton is added to the {@code VBox}, the button already
+ * has a listener on the selectedProperty for switching the view and since nodes are cached the transition is faster
+ * than loading the fxml again.
+ * <p></p>
+ * Every toggle button is part of a ToggleGroup which is modified with
+ * the {@code ToggleButtonsUtil} class  to add 'always one selected' support
+ */
+public class MFXVLoader extends VBox {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private final String STYLE_CLASS = "mfx-vloader";
+    private Pane contentPane;
+    private final ToggleGroup toggleGroup;
+
+    private final BooleanProperty isAnimated = new SimpleBooleanProperty(false);
+    private final DoubleProperty animationMillis = new SimpleDoubleProperty(800);
+    private MFXAnimationFactory animationType = MFXAnimationFactory.FADE_IN;
+
+    private final ObservableMap<String, MFXLoadItem> bindMap;
+    private final ThreadPoolExecutor executor;
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public MFXVLoader() {
+        this(null);
+    }
+
+    public MFXVLoader(Pane contentPane) {
+        initialize();
+        this.contentPane = contentPane;
+        this.setPrefSize(Region.USE_COMPUTED_SIZE, 60);
+        this.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+        this.setSpacing(10);
+        this.setAlignment(Pos.CENTER);
+
+        this.toggleGroup = new ToggleGroup();
+
+        this.bindMap = FXCollections.observableHashMap();
+        this.executor = new ThreadPoolExecutor(
+                2,
+                4,
+                20,
+                TimeUnit.SECONDS,
+                new LinkedBlockingDeque<>(),
+                runnable -> {
+                    Thread t = Executors.defaultThreadFactory().newThread(runnable);
+                    t.setDaemon(true);
+                    return t;
+                }
+        );
+        this.executor.allowCoreThreadTimeOut(true);
+
+        this.bindMap.addListener((MapChangeListener<String, MFXLoadItem>) change -> {
+            if (change.wasAdded()) {
+                MFXLoadItem item = change.getValueAdded();
+                load(item);
+            }
+        });
+    }
+
+    //================================================================================
+    // Methods
+    //================================================================================
+    private void initialize() {
+        getStyleClass().add(STYLE_CLASS);
+    }
+
+    /**
+     * After calling 'addItem' a new task is created and submitted to the executor
+     * to load the given {@code MFXItem}.
+     *
+     * @param item The given item
+     */
+    private void load(MFXLoadItem item) {
+        Task<Void> task = new Task<>() {
+            @Override
+            protected Void call() {
+                try {
+                    FXMLLoader loader = new FXMLLoader(item.getFxmlURL());
+                    if (item.getControllerFactory() != null) {
+                        loader.setControllerFactory(item.getControllerFactory());
+                    }
+                    Node root = loader.load();
+                    item.setRoot(root);
+
+                    item.getButton().selectedProperty().addListener((observable, oldValue, newValue) -> {
+                        if (isAnimated.get()) {
+                            animationType.build(item.getRoot(), animationMillis.doubleValue()).play();
+                        }
+                        if (newValue) {
+                            try {
+                                contentPane.getChildren().set(0, item.getRoot());
+                            } catch (IndexOutOfBoundsException ex) {
+                                contentPane.getChildren().add(0, item.getRoot());
+                            }
+                        }
+                    });
+                    Platform.runLater(() -> MFXVLoader.this.getChildren().set(item.getIndex(), item.getButton()));
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                }
+                return null;
+            }
+        };
+        this.executor.submit(task);
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with a default key.
+     *
+     * @param index    The position of the button in the {@code VBox}
+     * @param button   The given button
+     * @param fxmlFile The given fxml file
+     */
+    public void addItem(int index, ToggleButton button, URL fxmlFile) {
+        Loader.checkFxmlFile(fxmlFile);
+        addItem(index, Loader.generateKey(fxmlFile), button, fxmlFile);
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with the given key.
+     *
+     * @param index    The position of the button in the {@code VBox}
+     * @param key      The given key
+     * @param button   The given button
+     * @param fxmlFile The given fxml file
+     */
+    public void addItem(int index, String key, ToggleButton button, URL fxmlFile) {
+        Loader.checkFxmlFile(fxmlFile);
+        this.getChildren().add(button);
+        button.setToggleGroup(toggleGroup);
+        ToggleButtonsUtil.addAlwaysOneSelectedSupport(toggleGroup);
+        this.bindMap.putIfAbsent(key, new MFXLoadItem(index, button, fxmlFile));
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with a default key.
+     *
+     * @param index             The position of the button in the {@code VBox}
+     * @param button            The given button
+     * @param fxmlFile          The given fxml file
+     * @param controllerFactory The given controller factory
+     */
+    public void addItem(int index, ToggleButton button, URL fxmlFile, Callback<Class<?>, Object> controllerFactory) {
+        Loader.checkFxmlFile(fxmlFile);
+        addItem(index, Loader.generateKey(fxmlFile), button, fxmlFile, controllerFactory);
+    }
+
+    /**
+     * Checks if the given fxml file is valid,
+     * then adds a new {@code MFXItem} to the map with the given key.
+     *
+     * @param index             The position of the button in the {@code VBox}
+     * @param key               The given key
+     * @param button            The given button
+     * @param fxmlFile          The given fxml file
+     * @param controllerFactory The given controller factory
+     */
+    public void addItem(int index, String key, ToggleButton button, URL fxmlFile, Callback<Class<?>, Object> controllerFactory) {
+        Loader.checkFxmlFile(fxmlFile);
+        this.getChildren().add(button);
+        button.setToggleGroup(toggleGroup);
+        ToggleButtonsUtil.addAlwaysOneSelectedSupport(toggleGroup);
+        this.bindMap.putIfAbsent(key, new MFXLoadItem(index, button, fxmlFile, controllerFactory));
+    }
+
+    /**
+     * Sets the pane in which switching views.
+     * This method MUST be called before loading any item.
+     */
+    public void setContentPane(Pane contentPane) {
+        this.contentPane = contentPane;
+    }
+
+    /**
+     * Sets the default view to set on show.
+     * This method should be called after adding any item.
+     *
+     * @param key The key of the wanted item
+     */
+    public void setDefault(String key) {
+        MFXLoadItem item = getLoadItem(key);
+        Task<Void> nullCheckTask = new Task<>() {
+            @Override
+            protected Void call() {
+                if (item.getRoot() == null) {
+                    this.runAndReset();
+                } else {
+                    item.getButton().setSelected(true);
+                }
+                return null;
+            }
+        };
+        this.executor.submit(nullCheckTask);
+    }
+
+    public MFXLoadItem getLoadItem(String key) {
+        return this.bindMap.get(key);
+    }
+
+    public boolean isIsAnimated() {
+        return isAnimated.get();
+    }
+
+    public BooleanProperty isAnimatedProperty() {
+        return isAnimated;
+    }
+
+    public void setIsAnimated(boolean isAnimated) {
+        this.isAnimated.set(isAnimated);
+    }
+
+    public double getAnimationMillis() {
+        return animationMillis.get();
+    }
+
+    public DoubleProperty animationMillisProperty() {
+        return animationMillis;
+    }
+
+    public void setAnimationMillis(double animationMillis) {
+        this.animationMillis.set(animationMillis);
+    }
+
+    public MFXAnimationFactory getAnimationType() {
+        return animationType;
+    }
+
+    public void setAnimationType(MFXAnimationFactory animationType) {
+        this.animationType = animationType;
+    }
+}

+ 353 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXDialog.java

@@ -0,0 +1,353 @@
+package io.github.palexdev.materialfx.controls.base;
+
+import io.github.palexdev.materialfx.controls.MFXButton;
+import io.github.palexdev.materialfx.controls.enums.DialogType;
+import io.github.palexdev.materialfx.controls.factories.MFXAnimationFactory;
+import io.github.palexdev.materialfx.effects.MFXScrimEffect;
+import io.github.palexdev.materialfx.utils.NodeUtils;
+import javafx.animation.ParallelTransition;
+import javafx.beans.property.*;
+import javafx.event.EventHandler;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+
+public abstract class AbstractMFXDialog extends BorderPane {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private DialogType type;
+
+    protected final StringProperty title = new SimpleStringProperty("");
+    protected final StringProperty content = new SimpleStringProperty("");
+    protected MFXButton closeButton;
+    protected boolean centerBeforeShow = true;
+
+    protected final MFXScrimEffect scrimEffect = new MFXScrimEffect();
+
+    /**
+     * Specifies the opacity/strength of the scrim effect.
+     */
+    protected final DoubleProperty scrimOpacity = new SimpleDoubleProperty(0.15);
+
+    /**
+     * Specifies whether to apply scrim effect to dialog's parent when shown or not.
+     */
+    protected final BooleanProperty scrimBackground = new SimpleBooleanProperty(true);
+
+    /**
+     * Specifies whether the dialog should close when clicking outside of it or not.
+     */
+    protected final BooleanProperty overlayClose = new SimpleBooleanProperty(false);
+
+    /**
+     * Specifies whether the dialog is draggable or not.
+     */
+    protected final BooleanProperty isDraggable = new SimpleBooleanProperty(false);
+
+    private double mouseAnchorX;
+    private double mouseAnchorY;
+
+    protected MFXAnimationFactory inAnimationType = MFXAnimationFactory.FADE_IN;
+    protected MFXAnimationFactory outAnimationType = MFXAnimationFactory.FADE_OUT;
+    protected final ParallelTransition inAnimation = new ParallelTransition();
+    protected final ParallelTransition outAnimation = new ParallelTransition();
+
+    /**
+     * Specifies whether to play the inAnimation when {@code show()} is called or not.
+     */
+    protected final BooleanProperty animateIn = new SimpleBooleanProperty(false);
+
+    /**
+     * Specifies whether to play the outAnimation when {@code close()} is called or not.
+     */
+    protected final BooleanProperty animateOut = new SimpleBooleanProperty(false);
+
+    /**
+     * Specifies the duration of in and out animations.
+     */
+    protected final DoubleProperty animationMillis = new SimpleDoubleProperty(300);
+
+    //================================================================================
+    // Event Handlers
+    //================================================================================
+    protected EventHandler<MouseEvent> closeHandler = event -> close();
+
+    private final EventHandler<MouseEvent> overlayCloseHandler = mouseEvent -> {
+        if (!NodeUtils.inHierarchy(mouseEvent.getPickResult().getIntersectedNode(), AbstractMFXDialog.this)) {
+            close();
+        }
+    };
+
+    private final EventHandler<MouseEvent> pressHandler = new EventHandler<>() {
+        @Override
+        public void handle(MouseEvent mouseEvent) {
+            mouseAnchorX = mouseEvent.getX();
+            mouseAnchorY = mouseEvent.getY();
+        }
+    };
+
+    private final EventHandler<MouseEvent> dragHandler = new EventHandler<>() {
+        @Override
+        public void handle(MouseEvent mouseEvent) {
+            Pane dialogParent = (Pane) getParent();
+
+            double maxX = dialogParent.getWidth() - getWidth();
+            double maxY = dialogParent.getHeight() - getHeight();
+            double computedX = mouseEvent.getX() + getLayoutX() - mouseAnchorX;
+            double computedY = mouseEvent.getY() + getLayoutY() - mouseAnchorY;
+
+            setManaged(false);
+            // Check X bounds
+            if (getLayoutX() >= 0 && getLayoutX() <= maxX) {
+                setLayoutX(computedX);
+            } else if (computedX > 0 && computedX < maxX) {
+                setLayoutX(computedX);
+            }
+
+            // Check Y bounds
+            if (getLayoutY() >= 0 && getLayoutY() <= maxY) {
+                setLayoutY(computedY);
+            } else if (computedY > 0 && computedY < maxY) {
+                setLayoutY(computedY);
+            }
+
+            /*
+             * Since this handler is not called for every pixel translate properties may end
+             * a little out of bounds (> maxX || < -maxX ; > maxY || < -maxY) if this happens
+             * translateX/Y are set to the max possible value
+             */
+            if (getLayoutX() > maxX) {
+                setLayoutX(maxX);
+            } else if (getLayoutX() < 0) {
+                setLayoutX(0);
+            }
+            if (getLayoutY() > maxY) {
+                setLayoutY(maxY);
+            } else if (getLayoutY() < 0) {
+                setLayoutY(0);
+            }
+
+            mouseEvent.consume();
+        }
+    };
+
+    //================================================================================
+    // Constructors
+    //================================================================================
+    public AbstractMFXDialog() {
+        setPrefSize(400, 300);
+        setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+
+        this.closeButton = new MFXButton("");
+        this.closeButton.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
+        setVisible(false);
+    }
+
+    //================================================================================
+    // Abstract Methods
+    //================================================================================
+    public abstract void show();
+    public abstract void close();
+    public abstract void computeCenter();
+
+    //================================================================================
+    // Methods
+    //================================================================================
+    public DialogType getType() {
+        return type;
+    }
+
+    public void setType(DialogType type) {
+        this.type = type;
+    }
+
+    public String getTitle() {
+        return title.get();
+    }
+
+    public StringProperty titleProperty() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title.set(title);
+    }
+
+    public String getContent() {
+        return content.get();
+    }
+
+    public StringProperty contentProperty() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content.set(content);
+    }
+
+    public void setCenterBeforeShow(boolean centerBeforeShow) {
+        this.centerBeforeShow = centerBeforeShow;
+    }
+
+    public MFXScrimEffect getScrimEffect() {
+        return scrimEffect;
+    }
+
+    public MFXButton getCloseButton() {
+        return closeButton;
+    }
+
+    /**
+     * Replaces the dialog's default close button with a new one and adds the close handler to it.
+     * @param closeButton The new close button
+     */
+    public void setCloseButton(MFXButton closeButton) {
+        this.closeButton = closeButton;
+        this.closeButton.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
+    }
+
+    /**
+     * Replaces the dialog's default close handler with a new one,
+     * removes the old one from the button, replaces the handler and then
+     * re-adds the handler to the button.
+     * @param newHandler The new close handler
+     */
+    public void setCloseHandler(EventHandler<MouseEvent> newHandler) {
+        this.closeButton.removeEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
+        this.closeHandler = newHandler;
+        this.closeButton.addEventHandler(MouseEvent.MOUSE_PRESSED, closeHandler);
+    }
+
+    public double getScrimOpacity() {
+        return scrimOpacity.get();
+    }
+
+    public DoubleProperty scrimOpacityProperty() {
+        return scrimOpacity;
+    }
+
+    public void setScrimOpacity(double scrimOpacity) {
+        this.scrimOpacity.set(scrimOpacity);
+    }
+
+    public boolean isScrimBackground() {
+        return scrimBackground.get();
+    }
+
+    public BooleanProperty scrimBackgroundProperty() {
+        return scrimBackground;
+    }
+
+    public void setScrimBackground(boolean scrimBackground) {
+        this.scrimBackground.set(scrimBackground);
+    }
+
+    public boolean isOverlayClose() {
+        return overlayClose.get();
+    }
+
+    public BooleanProperty overlayCloseProperty() {
+        return overlayClose;
+    }
+
+    public void setOverlayClose(boolean overlayClose) {
+        this.overlayClose.set(overlayClose);
+    }
+
+    public boolean isIsDraggable() {
+        return isDraggable.get();
+    }
+
+    public BooleanProperty isDraggableProperty() {
+        return isDraggable;
+    }
+
+    public void setIsDraggable(boolean isDraggable) {
+        this.isDraggable.set(isDraggable);
+    }
+
+    public void setInAnimationType(MFXAnimationFactory inAnimationType) {
+        this.inAnimationType = inAnimationType;
+    }
+
+    public void setOutAnimationType(MFXAnimationFactory outAnimationType) {
+        this.outAnimationType = outAnimationType;
+    }
+
+    public ParallelTransition getInAnimation() {
+        return inAnimation;
+    }
+
+    public ParallelTransition getOutAnimation() {
+        return outAnimation;
+    }
+
+    public boolean isAnimateIn() {
+        return animateIn.get();
+    }
+
+    public BooleanProperty animateInProperty() {
+        return animateIn;
+    }
+
+    public void setAnimateIn(boolean animateIn) {
+        this.animateIn.set(animateIn);
+    }
+
+    public boolean isAnimateOut() {
+        return animateOut.get();
+    }
+
+    public BooleanProperty animateOutProperty() {
+        return animateOut;
+    }
+
+    public void setAnimateOut(boolean animateOut) {
+        this.animateOut.set(animateOut);
+    }
+
+    public double getAnimationMillis() {
+        return animationMillis.get();
+    }
+
+    public DoubleProperty animationMillisProperty() {
+        return animationMillis;
+    }
+
+    public void setAnimationMillis(double animationMillis) {
+        this.animationMillis.set(animationMillis);
+    }
+
+
+    /**
+     * When the {@code overlayClose} property is set to true, adds the {@code EventHandler} for the close.
+     */
+    protected void addOverlayHandler() {
+        getParent().addEventHandler(MouseEvent.MOUSE_PRESSED, overlayCloseHandler);
+    }
+
+    /**
+     * When the {@code overlayClose} property is set to false, removes the {@code EventHandler} for the close.
+     */
+    protected void removeOverlayHandler() {
+        getParent().removeEventHandler(MouseEvent.MOUSE_PRESSED, overlayCloseHandler);
+    }
+
+    /**
+     * When the {@code isDraggable} property is set to true, adds the {@code EventHandler}s for the drag.
+     */
+    protected void makeDraggable() {
+        addEventFilter(MouseEvent.MOUSE_PRESSED, pressHandler);
+        addEventFilter(MouseEvent.MOUSE_DRAGGED, dragHandler);
+    }
+
+    /**
+     * When the {@code isDraggable} property is set to false, removes the {@code EventHandler}s for the drag.
+     */
+    protected void clearDragHandlers() {
+        removeEventFilter(MouseEvent.MOUSE_PRESSED, pressHandler);
+        removeEventFilter(MouseEvent.MOUSE_DRAGGED, dragHandler);
+    }
+}

+ 8 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/DialogType.java

@@ -0,0 +1,8 @@
+package io.github.palexdev.materialfx.controls.enums;
+
+public enum DialogType {
+    ERROR,
+    WARNING,
+    INFO,
+    GENERIC
+}

+ 0 - 26
materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/MarkType.java

@@ -1,26 +0,0 @@
-package io.github.palexdev.materialfx.controls.enums;
-
-/**
- * Convenience enum to keep and get SVG paths for different types of check marks
- */
-public enum MarkType {
-    CASPIAN("M0,4H2L3,6L6,0H8L4,8H2Z"),
-    MODENA("M-0.25,6.083c0.843-0.758,4.583,4.833,5.75,4.833S14.5-1.5,15.917-0.917c1.292,0.532-8.75,17.083-10.5,17.083C3,16.167-1.083,6.833-0.25,6.083z"),
-    VARIANT3("M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"),
-    VARIANT4("M0 11c2.761.575 6.312 1.688 9 3.438 3.157-4.23 8.828-8.187 15-11.438-5.861 5.775-10.711 12.328-14 18.917-2.651-3.766-5.547-7.271-10-10.917z"),
-    VARIANT5("M20.303 4.846l.882.882-12.22 12.452-6.115-5.93.902-.902 5.303 5.028 11.248-11.53zm-.018-2.846l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285-3.715-3.715z"),
-    VARIANT6("M0 12.116l2.053-1.897c2.401 1.162 3.924 2.045 6.622 3.969 5.073-5.757 8.426-8.678 14.657-12.555l.668 1.536c-5.139 4.484-8.902 9.479-14.321 19.198-3.343-3.936-5.574-6.446-9.679-10.251z"),
-    VARIANT7("M0 11.522l1.578-1.626 7.734 4.619 13.335-12.526 1.353 1.354-14 18.646z"),
-    VARIANT8("M0 11.386l1.17-1.206c1.951.522 5.313 1.731 8.33 3.597 3.175-4.177 9.582-9.398 13.456-11.777l1.044 1.073-14 18.927-10-10.614z"),
-    VARIANT9("M24 6.278l-11.16 12.722-6.84-6 1.319-1.49 5.341 4.686 9.865-11.196 1.475 1.278zm-22.681 5.232l6.835 6.01-1.314 1.48-6.84-6 1.319-1.49zm9.278.218l5.921-6.728 1.482 1.285-5.921 6.756-1.482-1.313z");
-
-    private final String svhPath;
-
-    MarkType(String svhPath) {
-        this.svhPath = svhPath;
-    }
-
-    public String getSvhPath() {
-        return svhPath;
-    }
-}

+ 6 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/enums/ToggleNodeShape.java

@@ -0,0 +1,6 @@
+package io.github.palexdev.materialfx.controls.enums;
+
+public enum ToggleNodeShape {
+    CIRCLE,
+    RECTANGLE
+}

+ 153 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXAnimationFactory.java

@@ -0,0 +1,153 @@
+package io.github.palexdev.materialfx.controls.factories;
+
+import javafx.animation.Interpolator;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.scene.Node;
+import javafx.util.Duration;
+
+/**
+ * Convenience factory for various animations applied to {@code Node}s
+ *
+ * @see Timeline
+ */
+public enum MFXAnimationFactory {
+    FADE_IN {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.opacityProperty(), 0, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.opacityProperty(), 1.0, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    FADE_OUT {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.opacityProperty(), 1.0, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.opacityProperty(), 0, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_IN_BOTTOM {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateYProperty(), -node.getBoundsInParent().getHeight() * 2, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateYProperty(), 0, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_OUT_BOTTOM {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateYProperty(), 0, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateYProperty(), node.getBoundsInParent().getHeight() * 2, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_IN_LEFT {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateXProperty(), -node.getBoundsInParent().getWidth() * 2, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateXProperty(), 0, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_OUT_LEFT {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateXProperty(), 0, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateXProperty(), -node.getBoundsInParent().getWidth() * 2, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_IN_RIGHT {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateXProperty(), node.getBoundsInParent().getWidth() * 2, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateXProperty(), 0, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_OUT_RIGHT {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateXProperty(), 0, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateXProperty(), node.getBoundsInParent().getWidth() * 2, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_IN_TOP {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateYProperty(), node.getBoundsInParent().getHeight() * 2, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateYProperty(), 0, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    },
+    SLIDE_OUT_TOP {
+        @Override
+        public Timeline build(Node node, double durationMillis) {
+            MFXAnimationFactory.resetNode(node);
+            KeyValue keyValue1 = new KeyValue(node.translateYProperty(), 0, interpolator);
+            KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
+
+            KeyValue keyValue2 = new KeyValue(node.translateYProperty(), -node.getBoundsInParent().getHeight() * 2, interpolator);
+            KeyFrame keyFrame2 = new KeyFrame(Duration.millis(durationMillis), keyValue2);
+
+            return new Timeline(keyFrame1, keyFrame2);
+        }
+    };
+
+    private static final Interpolator interpolator = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
+    private static void resetNode(Node node) {
+        node.setTranslateX(0);
+        node.setTranslateY(0);
+    }
+    public abstract Timeline build(Node node, double durationMillis);
+}

+ 269 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXDialogFactory.java

@@ -0,0 +1,269 @@
+package io.github.palexdev.materialfx.controls.factories;
+
+import io.github.palexdev.materialfx.MFXResourcesManager.SVGResources;
+import io.github.palexdev.materialfx.controls.MFXButton;
+import io.github.palexdev.materialfx.controls.MFXDialog;
+import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog;
+import io.github.palexdev.materialfx.controls.enums.ButtonType;
+import io.github.palexdev.materialfx.controls.enums.DialogType;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.SVGPath;
+import javafx.util.Duration;
+
+/**
+ * Factory class to build specific {@code MFXDialog}s and generic {@code MFXDialog}s.
+ * <p></p>
+ * Also provides convenience methods to convert a specific dialog to another one
+ * and from generic to specific (the reverse is not true).
+ * These methods are important because rather than building new dialogs it's
+ * way better performance-wise to create a single dialog and add it to the desired pane,
+ * then convert it to another specific one or generic and reset its title and content if needed.
+ * <p></p>
+ * This is an advise you don't have to follow, however, keep in mind that creating a new dialog
+ * every time you need one and add it to the pane could cause a little bit of lag.
+ * <p></p>
+ * For a proper use see the demo code in {@code DialogsController}
+ */
+public class MFXDialogFactory {
+
+    private MFXDialogFactory() {
+    }
+
+    /**
+     * Sets the header node of the given dialog to the given type.
+     *
+     * @param type The desired type
+     * @param dialog The dialog reference
+     */
+    public static void setHeaderNode(DialogType type, AbstractMFXDialog dialog) {
+        String color;
+        SVGPath icon;
+
+        switch (type) {
+            case ERROR:
+                icon = SVGResources.CROSS.getSvgPath();
+                color = "#ff9e9e";
+                break;
+            case WARNING:
+                icon = SVGResources.EXCLAMATION_TRIANGLE.getSvgPath();
+                color = "#ffa57f";
+                break;
+            case INFO:
+                icon = SVGResources.INFO.getSvgPath();
+                color = "#61caff";
+                break;
+            default:
+                return;
+        }
+
+        icon.setFill(Color.WHITE);
+        dialog.setTop(buildHeader(dialog, color, icon));
+        dialog.setType(type);
+    }
+
+    /**
+     * Sets the content node of the given dialog with a new one.
+     * @param dialog The dialog reference
+     * @param title The dialog's title
+     * @param content The dialog's content
+     */
+    public static void setContentNode(AbstractMFXDialog dialog, String title, String content)  {
+        dialog.setCenter(buildContent(dialog, title, content));
+    }
+
+    /**
+     * Builds an MFXDialog
+     * @param type The dialog's type
+     * @param title The dialog's title
+     * @param content The dialog's content
+     * @return A new MFXDialog
+     */
+    public static MFXDialog buildDialog(DialogType type, String title, String content) {
+        MFXDialog dialog = new MFXDialog();
+        setHeaderNode(type, dialog);
+        setContentNode(dialog, title, content);
+        dialog.setType(type);
+        return dialog;
+    }
+
+    /**
+     * Builds a generic MFXDialog
+     * @param title The dialog's title
+     * @param content The dialog's content
+     * @return A new generic MFXDialog
+     */
+    public static MFXDialog buildGenericDialog(String title, String content) {
+        MFXDialog dialog = new MFXDialog();
+        dialog.setPrefSize(480, 120);
+
+        StackPane headerNode = buildGenericHeader(dialog, title);
+        StackPane contentNode = buildGenericContent(dialog, content);
+        HBox buttonsBox = buildButtonsBox(dialog);
+
+        dialog.setTop(headerNode);
+        dialog.setCenter(contentNode);
+        dialog.setBottom(buttonsBox);
+        dialog.setType(DialogType.GENERIC);
+
+        return dialog;
+    }
+
+    /**
+     * Converts a given dialog to the desired type.
+     * @param type The desired type
+     * @param dialog The dialog reference
+     */
+    public static void convertToSpecific(DialogType type, AbstractMFXDialog dialog) {
+        if (dialog.getType().equals(DialogType.GENERIC)) {
+            dialog.setPrefSize(400, 300);
+        }
+        setContentNode(dialog, dialog.getTitle(), dialog.getContent());
+        setHeaderNode(type, dialog);
+        dialog.setBottom(null);
+        dialog.setType(type);
+    }
+
+    /**
+     * Common code for building specific dialog's header node.
+     * @param dialog The dialog reference
+     * @param color The header color
+     * @param icon The header icon
+     * @return A new header node
+     */
+    private static StackPane buildHeader(AbstractMFXDialog dialog, String color, SVGPath icon) {
+        StackPane headerNode = new StackPane();
+        headerNode.setPrefSize(dialog.getPrefWidth(), dialog.getPrefHeight() * 0.45);
+        headerNode.getStyleClass().add("header-node");
+        headerNode.setStyle("-fx-background-color: " + color + ";\n");
+
+        SVGPath closeSvg = SVGResources.X.getSvgPath();
+        closeSvg.setScaleX(0.2);
+        closeSvg.setScaleY(0.2);
+        closeSvg.setFill(Color.WHITE);
+
+        if (dialog.getType() != null && dialog.getType().equals(DialogType.GENERIC)) {
+            dialog.setCloseButton(new MFXButton(""));
+        }
+
+        MFXButton closeButton = dialog.getCloseButton();
+        closeButton.setPrefSize(20, 20);
+        closeButton.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+        closeButton.setGraphic(closeSvg);
+        closeButton.setRippleRadius(15);
+        closeButton.setRippleColor(Color.rgb(230, 230, 230, 0.7));
+        closeButton.setRippleInDuration(Duration.millis(500));
+        closeButton.setButtonType(ButtonType.FLAT);
+
+        StackPane.setAlignment(closeButton, Pos.TOP_RIGHT);
+        StackPane.setMargin(closeButton, new Insets(7, 7, 0, 0));
+        headerNode.getChildren().addAll(icon, closeButton);
+
+        return headerNode;
+    }
+
+    /**
+     * Common code for building specific dialog's content node.
+     * @param dialog The dialog reference
+     * @param title The dialog's title
+     * @param content The dialog's content
+     * @return A new header node
+     */
+    private static StackPane buildContent(AbstractMFXDialog dialog, String title, String content) {
+        StackPane contentNode = new StackPane();
+
+        Label titleLabel = new Label();
+        titleLabel.getStyleClass().setAll("title-label");
+        titleLabel.textProperty().bind(dialog.titleProperty());
+        dialog.setTitle(title);
+        StackPane.setAlignment(titleLabel, Pos.TOP_CENTER);
+        StackPane.setMargin(titleLabel, new Insets(15, 0, 0, 0));
+
+        Label contentLabel = new Label();
+        contentLabel.getStyleClass().setAll("content-label");
+        contentLabel.setMinHeight(Region.USE_PREF_SIZE);
+        contentLabel.setPrefWidth(dialog.getPrefWidth() * 0.9);
+        contentLabel.setWrapText(true);
+        contentLabel.textProperty().bind(dialog.contentProperty());
+        dialog.setContent(content);
+        StackPane.setAlignment(contentLabel, Pos.TOP_CENTER);
+        StackPane.setMargin(contentLabel, new Insets(40, 20, 20, 20));
+
+        contentNode.getChildren().addAll(titleLabel, contentLabel);
+        return contentNode;
+    }
+
+    /**
+     * Builds an header node for generic dialogs.
+     * @param dialog The dialog reference
+     * @param title The dialog's title.
+     * @return A new generic header node
+     */
+    private static StackPane buildGenericHeader(AbstractMFXDialog dialog, String title) {
+        StackPane headerNode = new StackPane();
+        headerNode.getStyleClass().add("header-node");
+        Label titleLabel = new Label();
+        titleLabel.getStyleClass().setAll("title-label");
+        titleLabel.textProperty().bind(dialog.titleProperty());
+        dialog.setTitle(title);
+        headerNode.getChildren().add(titleLabel);
+        StackPane.setAlignment(titleLabel, Pos.TOP_CENTER);
+        StackPane.setMargin(titleLabel, new Insets(15, 0, 0, 0));
+
+        return headerNode;
+    }
+
+    /**
+     * Builds a content node for generic dialogs.
+     * @param dialog The dialog reference
+     * @param content The dialog's content
+     * @return A new generic content node
+     */
+    private static StackPane buildGenericContent(AbstractMFXDialog dialog, String content) {
+        StackPane contentNode = new StackPane();
+        contentNode.getStyleClass().add("content-node");
+        Label contentLabel = new Label();
+        contentLabel.getStyleClass().setAll("content-label");
+        contentLabel.setMinHeight(Region.USE_PREF_SIZE);
+        contentLabel.setPrefWidth(dialog.getPrefWidth() * 0.9);
+        contentLabel.setWrapText(true);
+        contentLabel.textProperty().bind(dialog.contentProperty());
+        dialog.setContent(content);
+        contentNode.getChildren().add(contentLabel);
+        StackPane.setAlignment(contentLabel, Pos.TOP_CENTER);
+        StackPane.setMargin(contentLabel, new Insets(7, 0, 0, 0));
+
+        return contentNode;
+    }
+
+    /**
+     * Builds a button box for generic dialogs.
+     * @param dialog The dialog instance
+     * @return A new button box
+     */
+    private static HBox buildButtonsBox(AbstractMFXDialog dialog) {
+        HBox buttonsBox = new HBox();
+        buttonsBox.setSpacing(20);
+        buttonsBox.setAlignment(Pos.CENTER_RIGHT);
+        buttonsBox.setPadding(new Insets(0, 15, 10, 0));
+        buttonsBox.getStyleClass().add("buttons-box");
+
+        MFXButton closeButton = dialog.getCloseButton();
+        closeButton.setText("OK");
+        closeButton.setPrefSize(55, 20);
+        closeButton.setTextFill(Color.rgb(120, 66, 245));
+        closeButton.setRippleRadius(30);
+        closeButton.setRippleInDuration(Duration.millis(500));
+        closeButton.setRippleColor(Color.rgb(120, 66, 245, 0.3));
+
+        HBox.setMargin(closeButton, new Insets(5, 10, 0, 0));
+        buttonsBox.getChildren().add(closeButton);
+
+        return buttonsBox;
+    }
+}

+ 78 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/factories/MFXStageDialogFactory.java

@@ -0,0 +1,78 @@
+package io.github.palexdev.materialfx.controls.factories;
+
+import io.github.palexdev.materialfx.controls.base.AbstractMFXDialog;
+import io.github.palexdev.materialfx.controls.enums.DialogType;
+import javafx.scene.Scene;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+
+/**
+ * Factory class to build {@code MFXStageDialog}s
+ */
+public class MFXStageDialogFactory {
+
+    //================================================================================
+    // Methods
+    //================================================================================
+
+    /**
+     * Builds a MFXStageDialog from type, title and content
+     * @param type The dialog type
+     * @param title The dialog's title
+     * @param content The dialog's content
+     * @return The MFXStageDialog's stage
+     * @see DialogType
+     */
+    public static Stage buildDialog(DialogType type, String title, String content) {
+        Stage dialogStage = new Stage();
+        dialogStage.initStyle(StageStyle.TRANSPARENT);
+
+        AbstractMFXDialog dialog;
+        if (!type.equals(DialogType.GENERIC)) {
+            dialog = MFXDialogFactory.buildDialog(type, title, content);
+        } else {
+            dialog = MFXDialogFactory.buildGenericDialog(title, content);
+        }
+        dialog.setVisible(true);
+
+        Scene scene = buildScene(dialog);
+        dialogStage.setTitle(title);
+        dialogStage.setScene(scene);
+
+        return dialogStage;
+    }
+
+    /**
+     * Builds a MFXStageDialog from an AbstractMFXDialog or subclasses
+     * @param dialog The dialog
+     * @return The MFXStageDialog's stage
+     */
+    public static Stage buildDialog(AbstractMFXDialog dialog) {
+        Stage dialogStage = new Stage();
+        dialogStage.initStyle(StageStyle.TRANSPARENT);
+
+        dialog.setCloseHandler(event -> dialogStage.close());
+        dialog.setVisible(true);
+
+        Scene scene = buildScene(dialog);
+        dialogStage.setTitle(dialog.getTitle());
+        dialogStage.setScene(scene);
+
+        return dialogStage;
+    }
+
+    /**
+     * Creates a TRANSPARENT {@code Scene} however it doesn't seem to work
+     * so the dialog is clipped with a {@code Rectangle} to keep round corners
+     * @param pane The dialog
+     * @return The MFXStageDialog scene
+     */
+    private static Scene buildScene(Pane pane) {
+        Scene scene = new Scene(pane);
+        scene.setFill(Color.TRANSPARENT);
+        return scene;
+    }
+    
+}

+ 125 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/effects/MFXScrimEffect.java

@@ -0,0 +1,125 @@
+package io.github.palexdev.materialfx.effects;
+
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.effect.BlendMode;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Window;
+
+/**
+ * From Google's material design guidelines:
+ * <p>
+ * Scrims are temporary treatments that can be applied to Material surfaces for the purpose of making content on a surface less prominent.
+ * They help direct user attention to other parts of the screen, away from the surface receiving a scrim.
+ */
+public class MFXScrimEffect {
+    //================================================================================
+    // Properties
+    //================================================================================
+    private final Rectangle scrim;
+
+    //================================================================================
+    // Constructor
+    //================================================================================
+    public MFXScrimEffect() {
+        scrim = new Rectangle();
+    }
+
+    //================================================================================
+    // Methods
+    //================================================================================
+
+    /**
+     * Adds a scrim effect to the specified pane with specified opacity.
+     *
+     * @param pane The pane to which add the effect
+     * @param opacity The effect opacity/strength
+     */
+    public void scrim(Pane pane, double opacity) {
+        scrim.setWidth(pane.getWidth());
+        scrim.setHeight(pane.getHeight());
+        scrim.setFill(Color.rgb(0, 0, 0, opacity));
+        scrim.setBlendMode(BlendMode.SRC_ATOP);
+
+        pane.getChildren().add(0, scrim);
+    }
+
+    /**
+     * Adds a scrim effect to the specified pane with specified opacity.
+     * It also simulates the modal behavior of {@code Stage}s, leaving only the specified
+     * {@code Node} interactable.
+     *
+     * @param parent The pane to which add the effect
+     * @param child The node to leave interactable
+     * @param opacity The effect opacity/strength
+     */
+    public void modalScrim(Pane parent, Node child, double opacity) {
+        scrim.setWidth(parent.getWidth());
+        scrim.setHeight(parent.getHeight());
+        scrim.setFill(Color.rgb(0, 0, 0, opacity));
+        scrim.setBlendMode(BlendMode.SRC_ATOP);
+
+        /*
+         * Workaround, especially for SceneBuilder
+         * This method adds the scrim effect to the given pane's children list
+         * before the given node to leave interactable so if that node is let's say in position 2
+         * and there are others controls after index 2 they will be interactable.
+         * To fix that and avoid some hassle for developers this piece of code
+         * finds the node to leave interactable and if it is not in the last position of the list
+         * removes and re-adds it, then adds the scrim effect in the second-last position which of course is
+         * (list.size() - 1)
+         */
+        ObservableList<Node> children = parent.getChildren();
+        children.stream()
+                .filter(node -> node.equals(child))
+                .findFirst()
+                .ifPresent(node -> {
+                    if (children.indexOf(node) != children.size() - 1) {
+                        parent.getChildren().remove(node);
+                        parent.getChildren().add(node);
+                    }
+                });
+
+        parent.getChildren().add(children.size() - 1 , scrim);
+    }
+
+    /**
+     * Adds a scrim effect to the specified {@code Window}'s root pane with the specified opacity.
+     *
+     * @param window The desired window
+     * @param opacity The desired opacity
+     */
+    public void scrimWindow(Window window, double opacity) {
+        Pane root = (Pane) window.getScene().getRoot();
+
+        scrim.setWidth(root.getWidth());
+        scrim.setHeight(root.getHeight());
+        scrim.setFill(Color.rgb(0, 0, 0, opacity));
+        scrim.setBlendMode(BlendMode.SRC_ATOP);
+
+        root.getChildren().add(scrim);
+    }
+
+    /**
+     * Removes the scrim effect from the specified pane.
+     * @param pane The pane to which remove the effect.
+     */
+    public void removeEffect(Pane pane) {
+        pane.getChildren().remove(scrim);
+    }
+
+    /**
+     * Removes the scrim effect from the specified window.
+     * @param window The window to which remove the effect.
+     */
+    public void removeEffect(Window window) {
+        Pane root = (Pane) window.getScene().getRoot();
+        removeEffect(root);
+    }
+
+    public Node getScrimNode() {
+        return scrim;
+    }
+}

+ 4 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/effects/RippleClipType.java

@@ -10,7 +10,10 @@ public enum RippleClipType {
         @Override
         public Shape buildClip(Region region) {
             double radius = Math.sqrt(Math.pow(region.getWidth(), 2) + Math.pow(region.getHeight(), 2)) / 2;
-            return new Circle(radius);
+            Circle circle = new Circle(radius);
+            circle.setTranslateX(region.getWidth() / 2);
+            circle.setTranslateY(region.getHeight() / 2);
+            return circle;
         }
     },
     RECTANGLE {

+ 31 - 12
materialfx/src/main/java/io/github/palexdev/materialfx/effects/RippleGenerator.java

@@ -28,12 +28,11 @@ public class RippleGenerator extends Group {
 
     private RippleClipType rippleClipType = RippleClipType.RECTANGLE;
     private DepthLevel level = null;
-    private boolean animateBackground = true;
     private final Interpolator rippleInterpolator = Interpolator.SPLINE(0.0825, 0.3025, 0.0875, 0.9975);
     //private final Interpolator rippleInterpolator = Interpolator.SPLINE(0.1, 0.50, 0.3, 0.85);
-    private final ObjectProperty<Color> rippleColor = new StyleableObjectProperty<>(Color.ROYALBLUE)
+    private final StyleableObjectProperty<Color> rippleColor = new StyleableObjectProperty<>(Color.ROYALBLUE)
     {
-        @Override public CssMetaData getCssMetaData() { return StyleableProperties.RIPPLE_COLOR; }
+        @Override public CssMetaData<RippleGenerator, Color> getCssMetaData() { return StyleableProperties.RIPPLE_COLOR; }
         @Override public Object getBean() { return this; }
         @Override public String getName() { return "rippleColor"; }
     };
@@ -43,6 +42,12 @@ public class RippleGenerator extends Group {
             "rippleRadius",
             10.0
     );
+    private final StyleableBooleanProperty animateBackground = new SimpleStyleableBooleanProperty(
+            StyleableProperties.ANIMATE_BACKGROUND,
+            this,
+            "animateBackground",
+            true
+    );
     private final ObjectProperty<Duration> inDuration = new SimpleObjectProperty<>(Duration.millis(700));
     private final ObjectProperty<Duration> outDuration = new SimpleObjectProperty<>(inDuration.get().divide(2));
 
@@ -95,7 +100,7 @@ public class RippleGenerator extends Group {
         final Ripple ripple = new Ripple(generatorCenterX, generatorCenterY);
         getChildren().add(ripple);
 
-        if (animateBackground) {
+        if (animateBackground.get()) {
             Rectangle fillRect = new Rectangle(region.getWidth(), region.getHeight());
             fillRect.setFill(rippleColor.get());
             fillRect.setOpacity(0);
@@ -111,7 +116,6 @@ public class RippleGenerator extends Group {
 
         ripple.parallelTransition.setOnFinished(event -> getChildren().remove(ripple));
         ripple.parallelTransition.play();
-
     }
 
     public void setGeneratorCenterX(double generatorCenterX) {
@@ -122,10 +126,6 @@ public class RippleGenerator extends Group {
         this.generatorCenterY = generatorCenterY;
     }
 
-    public void setAnimateBackground(boolean animateBackground) {
-        this.animateBackground = animateBackground;
-    }
-
     public void setRippleClipType(RippleClipType rippleClipType) {
         this.rippleClipType = rippleClipType;
     }
@@ -134,7 +134,7 @@ public class RippleGenerator extends Group {
         return rippleColor.get();
     }
 
-    public final ObjectProperty<Color> rippleColorProperty() {
+    public final StyleableObjectProperty<Color> rippleColorProperty() {
         return rippleColor;
     }
 
@@ -154,6 +154,18 @@ public class RippleGenerator extends Group {
         this.rippleRadius.set(rippleRadius);
     }
 
+    public boolean isAnimateBackground() {
+        return animateBackground.get();
+    }
+
+    public StyleableBooleanProperty animateBackgroundProperty() {
+        return animateBackground;
+    }
+
+    public void setAnimateBackground(boolean animateBackground) {
+        this.animateBackground.set(animateBackground);
+    }
+
     public Duration getInDuration() {
         return inDuration.get();
     }
@@ -262,7 +274,7 @@ public class RippleGenerator extends Group {
         private static final CssMetaData<RippleGenerator, Color> RIPPLE_COLOR =
                 FACTORY.createColorCssMetaData(
                         "-mfx-ripple-color",
-                        rippleGenerator -> (StyleableProperty<Color>) rippleGenerator.rippleColorProperty()
+                        RippleGenerator::rippleColorProperty
                 );
 
         private static final CssMetaData<RippleGenerator, Number> RIPPLE_RADIUS =
@@ -272,8 +284,15 @@ public class RippleGenerator extends Group {
                         10.0
                 );
 
+        private static final CssMetaData<RippleGenerator, Boolean> ANIMATE_BACKGROUND =
+                FACTORY.createBooleanCssMetaData(
+                        "-mfx-animate-background",
+                        RippleGenerator::animateBackgroundProperty,
+                        true
+                );
+
         static {
-            cssMetaDataList = List.of(RIPPLE_COLOR, RIPPLE_RADIUS);
+            cssMetaDataList = List.of(RIPPLE_COLOR, RIPPLE_RADIUS, ANIMATE_BACKGROUND);
         }
 
     }

+ 0 - 154
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXToggleNodeSkin.java

@@ -1,154 +0,0 @@
-package io.github.palexdev.materialfx.skins;
-
-import io.github.palexdev.materialfx.controls.MFXToggleNode;
-import io.github.palexdev.materialfx.effects.RippleClipType;
-import io.github.palexdev.materialfx.effects.RippleGenerator;
-import javafx.animation.Interpolator;
-import javafx.animation.KeyFrame;
-import javafx.animation.KeyValue;
-import javafx.animation.Timeline;
-import javafx.scene.control.skin.ToggleButtonSkin;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.layout.StackPane;
-import javafx.scene.paint.Color;
-import javafx.scene.shape.Circle;
-import javafx.util.Duration;
-
-import java.util.Optional;
-
-/**
- *  This is the implementation of the {@code Skin} associated with every {@code MFXToggleNode}.
- */
-public class MFXToggleNodeSkin extends ToggleButtonSkin {
-    //================================================================================
-    // Properties
-    //================================================================================
-    private final StackPane container;
-    private final RippleGenerator rippleGenerator;
-    private final Circle circle;
-
-    //================================================================================
-    // Constructors
-    //================================================================================
-    public MFXToggleNodeSkin(MFXToggleNode control) {
-        super(control);
-
-        container = new StackPane();
-        Optional.ofNullable(control.getGraphic()).ifPresent(node -> container.getChildren().add(node));
-
-        circle = new Circle();
-        circle.setOpacity(0.0);
-        circle.setFill(control.getUnSelectedColor());
-        circle.setStrokeWidth(control.getStrokeWidth());
-
-        rippleGenerator = new RippleGenerator(control, RippleClipType.NOCLIP);
-        rippleGenerator.setAnimateBackground(false);
-        rippleGenerator.setRippleColor(Color.GRAY);
-        rippleGenerator.setInDuration(Duration.millis(250));
-
-        updateChildren();
-        setListeners(control);
-    }
-
-    //================================================================================
-    // Methods
-    //================================================================================
-
-    /**
-     * Adds listeners for: mouse pressed, selected and skin(workaround) properties.
-     * @param control The MFXToggleButton associated to this skin
-     */
-    private void setListeners(MFXToggleNode control) {
-        control.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
-            rippleGenerator.setGeneratorCenterX(event.getX());
-            rippleGenerator.setGeneratorCenterY(event.getY());
-            rippleGenerator.createRipple();
-        });
-
-        control.selectedProperty().addListener((observable, oldValue, newValue) -> buildAndPlayAnimation(newValue));
-
-        /*
-         * Workaround
-         * When the control is created the Skin is still null, so if the ToggleNode is set
-         * to be selected the animation won't be played. To fix this add a listener to the
-         * control's skinProperty, when the skin is not null and the ToggleNode isSelected,
-         * play the animation.
-         */
-        control.skinProperty().addListener((observable, oldValue, newValue) -> {
-            if (newValue != null && control.isSelected()) {
-                buildAndPlayAnimation(true);
-            }
-        });
-    }
-
-    /**
-     * Re-builds and plays the background animation every time the control is selected/unselected.
-     * @param isSelected The control's state
-     */
-    private void buildAndPlayAnimation(boolean isSelected) {
-        MFXToggleNode control = (MFXToggleNode) getSkinnable();
-
-        final KeyValue keyValue1;
-        final KeyValue keyValue2;
-        final KeyValue keyValue3;
-        if (isSelected) {
-            keyValue1 = new KeyValue(circle.opacityProperty(), 0.3, Interpolator.EASE_IN);
-            keyValue2 = new KeyValue(circle.fillProperty(), control.getSelectedColor(), Interpolator.EASE_IN);
-            keyValue3 = new KeyValue(circle.strokeProperty(), ((Color) control.getSelectedColor()).darker(), Interpolator.EASE_IN);
-        } else {
-            keyValue1 = new KeyValue(circle.opacityProperty(), 0.0, Interpolator.EASE_OUT);
-            keyValue2 = new KeyValue(circle.fillProperty(), control.getUnSelectedColor(), Interpolator.EASE_OUT);
-            keyValue3 = new KeyValue(circle.strokeProperty(), Color.TRANSPARENT, Interpolator.EASE_OUT);
-        }
-
-        Timeline timeline = new Timeline(
-                new KeyFrame(Duration.millis(100), keyValue1, keyValue2, keyValue3)
-        );
-        timeline.play();
-    }
-
-    /**
-     * Computes the radius to be used for the circle. It's either half the width or height
-     * of the control depending on which of the two is the smaller.
-     * @return The circle radius
-     */
-    private double computeRadius() {
-        return Math.min((getSkinnable().getWidth() / 2), (getSkinnable().getHeight() / 2));
-    }
-
-    //================================================================================
-    // Override Methods
-    //================================================================================
-
-    /**
-     * Adds the circle and the ripple generator to the control as soon as they are not null.
-     */
-    @Override
-    protected void updateChildren() {
-        super.updateChildren();
-        if (circle != null && rippleGenerator != null) {
-            getChildren().addAll(circle, rippleGenerator);
-        }
-    }
-
-    /**
-     * Each time the control sizes change, recalculates the circle center coordinates and radius,
-     * the clip center coordinates and radius, the ripple generator radius.
-     */
-    @Override
-    protected void layoutChildren(double x, double y, double w, double h) {
-        super.layoutChildren(x, y, w, h);
-
-        circle.setCenterX(getSkinnable().getWidth() / 2);
-        circle.setCenterY(getSkinnable().getHeight() / 2);
-        circle.setRadius(computeRadius() * 1.3);
-
-        rippleGenerator.setRippleRadius(computeRadius() * 1.2);
-
-        Circle clip = new Circle();
-        clip.setCenterX(getSkinnable().getWidth() / 2);
-        clip.setCenterY(getSkinnable().getHeight() / 2);
-        clip.setRadius(computeRadius() * 1.4);
-        getSkinnable().setClip(clip);
-    }
-}

+ 34 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/utils/Loader.java

@@ -0,0 +1,34 @@
+package io.github.palexdev.materialfx.utils;
+
+import java.net.URL;
+
+/**
+ * Convenience class to avoid duplicated code in {@code MFXHLoader} and {@code MFXVLoader} classes
+ */
+public class Loader {
+
+    private Loader() {
+    }
+
+    /**
+     * Check if the given URL is an fxml file.
+     */
+    public static void checkFxmlFile(URL fxmlFile) {
+        if (!fxmlFile.toString().endsWith(".fxml")) {
+            throw new IllegalArgumentException("The URL is invalid, doesn't end with '.fxml'!!");
+        }
+    }
+
+    /**
+     * If no key is specified when calling 'addItem' then a default key is generated,
+     * corresponds to the fxml file name without the extension.
+     * @param fxmlFile The given fxml file
+     * @return The generated key
+     */
+    public static String generateKey(URL fxmlFile) {
+        String url = fxmlFile.toString();
+        int lastSlash = url.lastIndexOf("/");
+        int lastDot = url.lastIndexOf(".");
+        return url.substring(lastSlash + 1, lastDot);
+    }
+}

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

@@ -3,6 +3,8 @@ package io.github.palexdev.materialfx.utils;
 import javafx.geometry.HPos;
 import javafx.geometry.Insets;
 import javafx.geometry.VPos;
+import javafx.scene.Node;
+import javafx.scene.layout.AnchorPane;
 import javafx.scene.layout.Background;
 import javafx.scene.layout.BackgroundFill;
 import javafx.scene.layout.Region;
@@ -58,6 +60,32 @@ public class NodeUtils {
         region.setBackground(new Background(fills.toArray(BackgroundFill[]::new)));
     }
 
+    /**
+     * Centers the specified node in an {@code AnchorPane}.
+     */
+    public static void centerNodeInAnchorPane(Node node, double topBottom, double leftRight) {
+        AnchorPane.setTopAnchor(node, topBottom);
+        AnchorPane.setBottomAnchor(node, topBottom);
+        AnchorPane.setLeftAnchor(node, leftRight);
+        AnchorPane.setRightAnchor(node, leftRight);
+    }
+
+    /**
+     * Checks if the specified element is in the hierarchy of the specified node.
+     */
+    public static boolean inHierarchy(Node node, Node element) {
+        if (element == null) {
+            return true;
+        }
+        while (node != null) {
+            if (node == element) {
+                return true;
+            }
+            node = node.getParent();
+        }
+        return false;
+    }
+
     /* The next two methods are copied from com.sun.javafx.scene.control.skin.Utils class
      * It's a private module, so to avoid adding exports and opens I copied them
      */

+ 34 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/utils/ToggleButtonsUtil.java

@@ -0,0 +1,34 @@
+package io.github.palexdev.materialfx.utils;
+
+import javafx.collections.ListChangeListener;
+import javafx.event.EventHandler;
+import javafx.scene.control.Toggle;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.input.MouseEvent;
+
+public class ToggleButtonsUtil {
+
+    private static final EventHandler<MouseEvent> consumeMouseEventFilter = (MouseEvent mouseEvent) -> {
+        if (((Toggle) mouseEvent.getSource()).isSelected()) {
+            mouseEvent.consume();
+        }
+    };
+
+    private static void addConsumeMouseEventFilter(Toggle toggle) {
+        ((ToggleButton) toggle).addEventFilter(MouseEvent.MOUSE_PRESSED, consumeMouseEventFilter);
+        ((ToggleButton) toggle).addEventFilter(MouseEvent.MOUSE_RELEASED, consumeMouseEventFilter);
+        ((ToggleButton) toggle).addEventFilter(MouseEvent.MOUSE_CLICKED, consumeMouseEventFilter);
+    }
+
+    public static void addAlwaysOneSelectedSupport(final ToggleGroup toggleGroup) {
+        toggleGroup.getToggles().addListener((ListChangeListener.Change<? extends Toggle> c) -> {
+            while (c.next()) {
+                for (final Toggle addedToggle : c.getAddedSubList()) {
+                    addConsumeMouseEventFilter(addedToggle);
+                }
+            }
+        });
+        toggleGroup.getToggles().forEach(ToggleButtonsUtil::addConsumeMouseEventFilter);
+    }
+}

+ 5 - 1
materialfx/src/main/java/module-info.java

@@ -1,12 +1,16 @@
 module MaterialFX.materialfx.main {
     requires javafx.controls;
+    requires javafx.fxml;
     requires javafx.graphics;
     requires java.desktop;
 
+    exports io.github.palexdev.materialfx;
+    exports io.github.palexdev.materialfx.beans;
     exports io.github.palexdev.materialfx.controls;
+    exports io.github.palexdev.materialfx.controls.base;
     exports io.github.palexdev.materialfx.controls.enums;
+    exports io.github.palexdev.materialfx.controls.factories;
     exports io.github.palexdev.materialfx.effects;
     exports io.github.palexdev.materialfx.skins;
     exports io.github.palexdev.materialfx.utils;
-    exports io.github.palexdev.materialfx;
 }

+ 216 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/fonts.css

@@ -0,0 +1,216 @@
+@font-face {
+    font-family: 'Comfortaa';
+    font-weight: bold;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Comfortaa/Comfortaa-Bold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Comfortaa';
+    font-weight: 300px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Comfortaa/Comfortaa-Light.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Comfortaa';
+    font-weight: 500px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Comfortaa/Comfortaa-Medium.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Comfortaa';
+    font-weight: normal;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Comfortaa/Comfortaa-Regular.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Comfortaa';
+    font-weight: 600px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Comfortaa/Comfortaa-SemiBold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: bold;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-Bold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: bold;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-BoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: 800px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-ExtraBold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: 800px;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: 300px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-Light.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: 300px;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-LightItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: normal;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-Italic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: 600px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-SemiBold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: normal;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-Regular.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Open Sans';
+    font-weight: 600px;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/OpenSans/OpenSans-SemiBoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 900px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-Black.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 900px;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-BlackItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: bold;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-Bold.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: bold;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-BoldItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 300px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-Light.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: normal;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-Italic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 300px;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-LightItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 500px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-Medium.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 500px;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-MediumItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: normal;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-Regular.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 100px;
+    font-style: italic;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-ThinItalic.ttf') format('truetype');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 100px;
+    font-style: normal;
+    font-display: swap;
+    src: url('../fonts/Roboto/Roboto-Thin.ttf') format('truetype');
+}
+

+ 2 - 1
materialfx/src/main/resources/io/github/palexdev/materialfx/css/mfx-button.css

@@ -7,6 +7,7 @@
 }
 
 .mfx-button,
-.mfx-button:armed {
+.mfx-button:armed,
+.mfx-button:focused {
     -fx-background-color: white;
 }

+ 47 - 0
materialfx/src/main/resources/io/github/palexdev/materialfx/css/mfx-dialog.css

@@ -0,0 +1,47 @@
+@import "fonts.css";
+
+/*================================================================================*/
+/* Common Dialog
+/*================================================================================*/
+.mfx-dialog {
+    -fx-background-color: white;
+    -fx-background-radius: 10;
+    -fx-border-color: #7a7a7a;
+    -fx-border-width: 0.2;
+    -fx-border-radius: 10;
+}
+
+.mfx-dialog .title-label {
+    -fx-font-family: "Open Sans";
+    -fx-font-size: 14;
+    -fx-font-weight: bold;
+}
+
+.mfx-dialog .content-label {
+    -fx-font-family: "Open Sans";
+    -fx-font-size: 11.5;
+}
+
+.mfx-dialog .buttons-box .mfx-button {
+    -fx-background-color: white;
+    -fx-font-weight: bold;
+}
+
+.mfx-dialog .buttons-box .mfx-button:hover {
+    -fx-background-color: #f4f1ff
+}
+
+/*================================================================================*/
+/* Others
+/*================================================================================*/
+.mfx-dialog .header-node {
+    -fx-background-radius: 10 10 0 0;
+}
+
+.mfx-dialog .header-node .mfx-button {
+    -fx-background-color: transparent;
+}
+
+.mfx-dialog .header-node .mfx-button .ripple-generator {
+    -mfx-animate-background: false;
+}

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

@@ -7,4 +7,12 @@
     -fx-background-color: TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT;
     -fx-background-radius: 3px;
     -fx-background-insets: 0px;
+}
+
+.mfx-toggle-node {
+    -fx-opacity: 0.5;
+}
+
+.mfx-toggle-node:selected {
+    -fx-opacity: 1.0;
 }

BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Bold.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Light.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Medium.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-Regular.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-SemiBold.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Comfortaa/Comfortaa-VariableFont_wght.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Bold.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-BoldItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBold.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Italic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Light.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-LightItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-Regular.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBold.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Black.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BlackItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Bold.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-BoldItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Italic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Light.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-LightItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Medium.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-MediumItalic.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Regular.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-Thin.ttf


BIN
materialfx/src/main/resources/io/github/palexdev/materialfx/fonts/Roboto/Roboto-ThinItalic.ttf