Quellcode durchsuchen

:bookmark: Version 11.15.0

Beans Package
:recycle: SizeBean: override toString() method

Controls Package
:boom: Implement an API for controls that can be styled (almost all MFX controls). This API also allows SceneBuilder support for the new styling system introduced by version 11.14.0

Utils Package
:sparkles: Improved When, OnChanged and OnInvalidated constructs by importing them from the latest version of MFXCore
:sparkles: Added utility to detect SceneBuilder at runtime

Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
palexdev vor 2 Jahren
Ursprung
Commit
b590762e48
54 geänderte Dateien mit 1212 neuen und 96 gelöschten Zeilen
  1. 12 0
      CHANGELOG.md
  2. 17 10
      README.md
  3. 1 1
      gradle.properties
  4. 1 1
      materialfx/gradle.properties
  5. 12 0
      materialfx/src/main/java/io/github/palexdev/materialfx/beans/SizeBean.java
  6. 25 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXButton.java
  7. 6 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckListView.java
  8. 7 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckTreeItem.java
  9. 24 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckbox.java
  10. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCircleToggleNode.java
  11. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXComboBox.java
  12. 16 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXContextMenuItem.java
  13. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXDatePicker.java
  14. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXFilterComboBox.java
  15. 21 3
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXFilterPane.java
  16. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXListView.java
  17. 16 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXMagnifierPane.java
  18. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotificationCenter.java
  19. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPagination.java
  20. 7 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java
  21. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressBar.java
  22. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressSpinner.java
  23. 26 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRadioButton.java
  24. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRectangleToggleNode.java
  25. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXScrollPane.java
  26. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java
  27. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSpinner.java
  28. 16 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepper.java
  29. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepperToggle.java
  30. 17 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableView.java
  31. 16 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java
  32. 26 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXToggleButton.java
  33. 7 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTreeItem.java
  34. 7 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTreeView.java
  35. 8 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXListView.java
  36. 20 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXToggleNode.java
  37. 11 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeCell.java
  38. 11 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeItem.java
  39. 137 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/Themable.java
  40. 5 2
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckListCell.java
  41. 11 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java
  42. 16 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXComboBoxCell.java
  43. 16 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXDateCell.java
  44. 10 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXListCell.java
  45. 8 0
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXSimpleTreeCell.java
  46. 14 1
      materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/base/AbstractMFXListCell.java
  47. 7 1
      materialfx/src/main/java/io/github/palexdev/materialfx/css/themes/Stylesheets.java
  48. 42 3
      materialfx/src/main/java/io/github/palexdev/materialfx/css/themes/Theme.java
  49. 13 1
      materialfx/src/main/java/io/github/palexdev/materialfx/css/themes/Themes.java
  50. 2 2
      materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXDatePickerSkin.java
  51. 98 0
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/SceneBuilderIntegration.java
  52. 117 13
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/observables/OnChanged.java
  53. 119 13
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/observables/OnInvalidated.java
  54. 83 21
      materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/observables/When.java

+ 12 - 0
CHANGELOG.md

@@ -16,6 +16,18 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 
 [//]: ##[Unreleased]
 [//]: ##[Unreleased]
 
 
+## [11.15.0] - 17-03-2023
+
+## Changed
+
+- SizeBean: override toString() methods
+- Improved When, OnChanged and OnInvalidated constructs by importing them from the latest version of MFXCore
+
+## Added
+
+- Implemented API for MaterialFX styleable controls and SceneBuilder support
+- Added utility to detect SceneBuilder at runtime
+
 ## [11.14.0] - 16-03-2023
 ## [11.14.0] - 16-03-2023
 
 
 ## Changed
 ## Changed

+ 17 - 10
README.md

@@ -213,9 +213,6 @@ To run the main demo, execute the following command:
 
 
 **NOTE**: MaterialFX requires **Java 11** and above.
 **NOTE**: MaterialFX requires **Java 11** and above.
 
 
-**NOTE**: Starting from version 11.14.0 (next major version), MaterialFX will transition to
-Java 17 and bump version to 17.x.x. What will happen to version 11 is still to be decided
-
 ### Usage
 ### Usage
 
 
 ###### Gradle
 ###### Gradle
@@ -226,7 +223,7 @@ repositories {
 }
 }
 
 
 dependencies {
 dependencies {
-    implementation 'io.github.palexdev:materialfx:11.14.0'
+    implementation 'io.github.palexdev:materialfx:11.15.0'
 }
 }
 ```
 ```
 
 
@@ -237,7 +234,7 @@ dependencies {
 <dependency>
 <dependency>
     <groupId>io.github.palexdev</groupId>
     <groupId>io.github.palexdev</groupId>
     <artifactId>materialfx</artifactId>
     <artifactId>materialfx</artifactId>
-    <version>11.14.0</version>
+    <version>11.15.0</version>
 </dependency>
 </dependency>
 ```
 ```
 
 
@@ -330,7 +327,7 @@ There are two implementations of this interface:
 `MFXThemeManager` is a utility class that will help the user add/set themes and stylesheets (which implement `Theme`) on
 `MFXThemeManager` is a utility class that will help the user add/set themes and stylesheets (which implement `Theme`) on
 nodes or scenes.
 nodes or scenes.
 
 
-**Pros and Cons**
+**Pros**
 
 
 - The biggest pro is to have a more reliable styling system. With this users shouldn't hava any issue anymore while
 - The biggest pro is to have a more reliable styling system. With this users shouldn't hava any issue anymore while
   styling
   styling
@@ -341,6 +338,7 @@ nodes or scenes.
 - This change should have also impacted on memory usage in a good way as now controls do not store the "url" to their
 - This change should have also impacted on memory usage in a good way as now controls do not store the "url" to their
   stylesheet anymore
   stylesheet anymore
 
 
+**Cons**
 
 
 - One con is that now themes must be managed by the user. Since controls are not styled by default, the user must
 - One con is that now themes must be managed by the user. Since controls are not styled by default, the user must
   use the aforementioned manager or enumerators to load/add the themes on the App.  
   use the aforementioned manager or enumerators to load/add the themes on the App.  
@@ -361,10 +359,19 @@ nodes or scenes.
   **You have to add the Themes on every separate scene**.
   **You have to add the Themes on every separate scene**.
   To simplify things, MaterialFX automatically applies the Themes on its dialogs and popups, but since now they
   To simplify things, MaterialFX automatically applies the Themes on its dialogs and popups, but since now they
   are added to the `getStylesheets()` list it's easy to remove them and define your own
   are added to the `getStylesheets()` list it's easy to remove them and define your own
-- The last con I can think of is SceneBuilder. As of now there is no support for it, I have some ideas on how to style
-  controls inside of it though. The issue is that even if I figure out a way, I doubt the system will be flexible.
-  What I mean is, I can probably set the default themes on the SceneBuilder' scene, but it's very unlikely there will
-  be a way to choose which themes/stylesheets will be applied
+- ~~The last con I can think of is SceneBuilder. As of now there is no support for it, I have some ideas on how to style
+  controls inside of it though. The issue is that even if I figure out a way,~~ I doubt the system will be flexible
+  enough.
+  ~~What I mean is, I can probably set the default themes on the SceneBuilder' scene,~~ but it's very unlikely there
+  will
+  be a way to choose which themes/stylesheets will be applied.  
+  Since version 11.15.0, MaterialFX controls are capable of detecting if they are being used in SceneBuilder and can
+  automatically
+  style themselves. From my little testings, it seems that this doesn't break the styling system in any way, I was able
+  to style a button
+  by adding a custom stylesheet on itself or on its parent. There's also an emergency system to completely shut down the
+  SceneBuilder integration, more info
+  here: [Themable](https://github.com/palexdev/MaterialFX/blob/main/materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/Themable.java)
 
 
 <!-- CONTRIBUTING -->
 <!-- CONTRIBUTING -->
 
 

+ 1 - 1
gradle.properties

@@ -3,7 +3,7 @@
 #--------------------------------------#
 #--------------------------------------#
 jdk=11
 jdk=11
 testJdk=17
 testJdk=17
-materialfx=11.14.0
+materialfx=11.15.0
 
 
 # Plugins
 # Plugins
 jfxPlugin=0.0.13
 jfxPlugin=0.0.13

+ 1 - 1
materialfx/gradle.properties

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

+ 12 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/beans/SizeBean.java

@@ -73,4 +73,16 @@ public class SizeBean {
 	public void setHeight(double height) {
 	public void setHeight(double height) {
 		this.height.set(height);
 		this.height.set(height);
 	}
 	}
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+
+	@Override
+	public String toString() {
+		return "SizeBean{" +
+				"width=" + width +
+				", height=" + height +
+				'}';
+	}
 }
 }

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

@@ -19,6 +19,9 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.PositionBean;
 import io.github.palexdev.materialfx.beans.PositionBean;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.effects.DepthLevel;
 import io.github.palexdev.materialfx.effects.DepthLevel;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.enums.ButtonType;
 import io.github.palexdev.materialfx.enums.ButtonType;
@@ -28,6 +31,7 @@ import javafx.beans.property.*;
 import javafx.css.*;
 import javafx.css.*;
 import javafx.geometry.Pos;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Button;
 import javafx.scene.control.Button;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Color;
@@ -41,7 +45,7 @@ import java.util.List;
  * Extends {@code Button}, redefines the style class to "mfx-button" for usage in CSS and
  * Extends {@code Button}, redefines the style class to "mfx-button" for usage in CSS and
  * includes a {@code RippleGenerator} to generate ripple effects on click.
  * includes a {@code RippleGenerator} to generate ripple effects on click.
  */
  */
-public class MFXButton extends Button {
+public class MFXButton extends Button implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -80,6 +84,7 @@ public class MFXButton extends Button {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		setAlignment(Pos.CENTER);
 		setAlignment(Pos.CENTER);
 		setupRippleGenerator();
 		setupRippleGenerator();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	public MFXCircleRippleGenerator getRippleGenerator() {
 	public MFXCircleRippleGenerator getRippleGenerator() {
@@ -344,6 +349,25 @@ public class MFXButton extends Button {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.BUTTON;
+	}
+
+	@Override
+	public boolean sceneBuilderIntegration() {
+		if (Themable.super.sceneBuilderIntegration()) {
+			setText("Button");
+			return true;
+		}
+		return false;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXButtonSkin(this);
 		return new MFXButtonSkin(this);

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

@@ -20,6 +20,8 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
 import io.github.palexdev.materialfx.controls.cell.MFXCheckListCell;
 import io.github.palexdev.materialfx.controls.cell.MFXCheckListCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXListViewSkin;
 import io.github.palexdev.materialfx.skins.MFXListViewSkin;
 import io.github.palexdev.materialfx.utils.ListChangeProcessor;
 import io.github.palexdev.materialfx.utils.ListChangeProcessor;
 import io.github.palexdev.virtualizedfx.beans.NumberRange;
 import io.github.palexdev.virtualizedfx.beans.NumberRange;
@@ -213,6 +215,10 @@ public class MFXCheckListView<T> extends AbstractMFXListView<T, MFXCheckListCell
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CHECK_LIST_VIEW;
+	}
 
 
 	/**
 	/**
 	 * Sets the default factory for the cells.
 	 * Sets the default factory for the cells.

+ 7 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXCheckTreeItem.java

@@ -21,6 +21,8 @@ package io.github.palexdev.materialfx.controls;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeCell;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeCell;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
 import io.github.palexdev.materialfx.controls.cell.MFXCheckTreeCell;
 import io.github.palexdev.materialfx.controls.cell.MFXCheckTreeCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.selection.TreeCheckModel;
 import io.github.palexdev.materialfx.selection.TreeCheckModel;
 import io.github.palexdev.materialfx.selection.base.ITreeCheckModel;
 import io.github.palexdev.materialfx.selection.base.ITreeCheckModel;
 import io.github.palexdev.materialfx.skins.MFXCheckTreeItemSkin;
 import io.github.palexdev.materialfx.skins.MFXCheckTreeItemSkin;
@@ -113,6 +115,11 @@ public class MFXCheckTreeItem<T> extends MFXTreeItem<T> {
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
 
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TREE_ITEM;
+	}
+
 	/**
 	/**
 	 * Overridden to return the ITreeCheckModel instance of the MFXCheckTreeView.
 	 * Overridden to return the ITreeCheckModel instance of the MFXCheckTreeView.
 	 */
 	 */

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

@@ -19,9 +19,13 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.controls.base.MFXLabeled;
 import io.github.palexdev.materialfx.controls.base.MFXLabeled;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXCheckboxSkin;
 import io.github.palexdev.materialfx.skins.MFXCheckboxSkin;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import javafx.css.*;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.CheckBox;
 import javafx.scene.control.CheckBox;
 import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
@@ -39,7 +43,7 @@ import java.util.List;
  * <p> - {@link #gapProperty()}: to control the gap between the checkbox and the text
  * <p> - {@link #gapProperty()}: to control the gap between the checkbox and the text
  * <p> - {@link #textExpandProperty()}: to control the text size and the checkbox layout (see documentation)
  * <p> - {@link #textExpandProperty()}: to control the text size and the checkbox layout (see documentation)
  */
  */
-public class MFXCheckbox extends CheckBox implements MFXLabeled {
+public class MFXCheckbox extends CheckBox implements MFXLabeled, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -63,6 +67,7 @@ public class MFXCheckbox extends CheckBox implements MFXLabeled {
 	//================================================================================
 	//================================================================================
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	//================================================================================
 	//================================================================================
@@ -173,6 +178,24 @@ public class MFXCheckbox extends CheckBox implements MFXLabeled {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CHECKBOX;
+	}
+
+	@Override
+	public boolean sceneBuilderIntegration() {
+		if (Themable.super.sceneBuilderIntegration()) {
+			setText("Checkbox");
+			return true;
+		}
+		return false;
+	}
 
 
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {

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

@@ -19,6 +19,8 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.controls.base.AbstractMFXToggleNode;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXToggleNode;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.enums.TextPosition;
 import io.github.palexdev.materialfx.enums.TextPosition;
 import io.github.palexdev.materialfx.skins.MFXCircleToggleNodeSkin;
 import io.github.palexdev.materialfx.skins.MFXCircleToggleNodeSkin;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
@@ -185,6 +187,12 @@ public class MFXCircleToggleNode extends AbstractMFXToggleNode {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CIRCLE_TOGGLE_NODE;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXCircleToggleNodeSkin(this);
 		return new MFXCircleToggleNodeSkin(this);

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

@@ -28,6 +28,8 @@ import io.github.palexdev.materialfx.beans.properties.styleable.StyleableBoolean
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableIntegerProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableIntegerProperty;
 import io.github.palexdev.materialfx.controls.base.MFXCombo;
 import io.github.palexdev.materialfx.controls.base.MFXCombo;
 import io.github.palexdev.materialfx.controls.cell.MFXComboBoxCell;
 import io.github.palexdev.materialfx.controls.cell.MFXComboBoxCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.i18n.I18N;
 import io.github.palexdev.materialfx.i18n.I18N;
 import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel;
 import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel;
@@ -279,6 +281,12 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.COMBO_BOX;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXComboBoxSkin<>(this, boundField);
 		return new MFXComboBoxSkin<>(this, boundField);

+ 16 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXContextMenuItem.java

@@ -20,12 +20,16 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
 import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXContextMenuItemSkin;
 import io.github.palexdev.materialfx.skins.MFXContextMenuItemSkin;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.StringProperty;
 import javafx.beans.property.StringProperty;
 import javafx.event.ActionEvent;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.event.EventHandler;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Labeled;
 import javafx.scene.control.Labeled;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Tooltip;
 import javafx.scene.control.Tooltip;
@@ -52,7 +56,7 @@ import java.util.function.Supplier;
  * <p></p>
  * <p></p>
  * A {@link Builder} class is offered to easily build items with fluent api.
  * A {@link Builder} class is offered to easily build items with fluent api.
  */
  */
-public class MFXContextMenuItem extends Labeled {
+public class MFXContextMenuItem extends Labeled implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -93,6 +97,17 @@ public class MFXContextMenuItem extends Labeled {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CONTEXT_MENU_ITEM;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXContextMenuItemSkin(this);
 		return new MFXContextMenuItemSkin(this);

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

@@ -27,6 +27,8 @@ import io.github.palexdev.materialfx.beans.properties.functional.ConsumerPropert
 import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
 import io.github.palexdev.materialfx.controls.cell.MFXDateCell;
 import io.github.palexdev.materialfx.controls.cell.MFXDateCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.enums.FloatMode;
 import io.github.palexdev.materialfx.enums.FloatMode;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.skins.MFXDatePickerSkin;
 import io.github.palexdev.materialfx.skins.MFXDatePickerSkin;
@@ -281,6 +283,12 @@ public class MFXDatePicker extends MFXTextField {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.DATE_PICKER;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXDatePickerSkin(this, boundField);
 		return new MFXDatePickerSkin(this, boundField);

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

@@ -23,6 +23,8 @@ import io.github.palexdev.materialfx.collections.TransformableList;
 import io.github.palexdev.materialfx.collections.TransformableListWrapper;
 import io.github.palexdev.materialfx.collections.TransformableListWrapper;
 import io.github.palexdev.materialfx.controls.cell.MFXComboBoxCell;
 import io.github.palexdev.materialfx.controls.cell.MFXComboBoxCell;
 import io.github.palexdev.materialfx.controls.cell.MFXFilterComboBoxCell;
 import io.github.palexdev.materialfx.controls.cell.MFXFilterComboBoxCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXFilterComboBoxSkin;
 import io.github.palexdev.materialfx.skins.MFXFilterComboBoxSkin;
 import io.github.palexdev.materialfx.utils.StringUtils;
 import io.github.palexdev.materialfx.utils.StringUtils;
 import javafx.beans.InvalidationListener;
 import javafx.beans.InvalidationListener;
@@ -154,6 +156,12 @@ public class MFXFilterComboBox<T> extends MFXComboBox<T> {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.FILTER_COMBO_BOX;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXFilterComboBoxSkin<>(this, boundField);
 		return new MFXFilterComboBoxSkin<>(this, boundField);

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

@@ -19,6 +19,9 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.FilterBean;
 import io.github.palexdev.materialfx.beans.FilterBean;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.enums.ChainMode;
 import io.github.palexdev.materialfx.enums.ChainMode;
 import io.github.palexdev.materialfx.filter.base.AbstractFilter;
 import io.github.palexdev.materialfx.filter.base.AbstractFilter;
 import io.github.palexdev.materialfx.i18n.I18N;
 import io.github.palexdev.materialfx.i18n.I18N;
@@ -29,6 +32,7 @@ import javafx.beans.property.StringProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableList;
 import javafx.event.EventHandler;
 import javafx.event.EventHandler;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseEvent;
@@ -134,7 +138,7 @@ import java.util.function.Predicate;
  *
  *
  * @param <T>
  * @param <T>
  */
  */
-public class MFXFilterPane<T> extends Control {
+public class MFXFilterPane<T> extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -143,8 +147,10 @@ public class MFXFilterPane<T> extends Control {
 	private final ObservableList<AbstractFilter<T, ?>> filters = FXCollections.observableArrayList();
 	private final ObservableList<AbstractFilter<T, ?>> filters = FXCollections.observableArrayList();
 	private final ObservableList<FilterBean<T, ?>> activeFilters = FXCollections.observableArrayList();
 	private final ObservableList<FilterBean<T, ?>> activeFilters = FXCollections.observableArrayList();
 
 
-	private EventHandler<MouseEvent> onFilter = event -> {};
-	private EventHandler<MouseEvent> onReset = event -> {};
+	private EventHandler<MouseEvent> onFilter = event -> {
+	};
+	private EventHandler<MouseEvent> onReset = event -> {
+	};
 
 
 	//================================================================================
 	//================================================================================
 	// Constructors
 	// Constructors
@@ -158,6 +164,7 @@ public class MFXFilterPane<T> extends Control {
 	//================================================================================
 	//================================================================================
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -249,6 +256,17 @@ public class MFXFilterPane<T> extends Control {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.FILTER_PANE;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXFilterPaneSkin<>(this);
 		return new MFXFilterPaneSkin<>(this);

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

@@ -20,6 +20,8 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
 import io.github.palexdev.materialfx.controls.cell.MFXListCell;
 import io.github.palexdev.materialfx.controls.cell.MFXListCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXListViewSkin;
 import io.github.palexdev.materialfx.skins.MFXListViewSkin;
 import io.github.palexdev.materialfx.utils.ListChangeProcessor;
 import io.github.palexdev.materialfx.utils.ListChangeProcessor;
 import io.github.palexdev.virtualizedfx.beans.NumberRange;
 import io.github.palexdev.virtualizedfx.beans.NumberRange;
@@ -213,6 +215,12 @@ public class MFXListView<T> extends AbstractMFXListView<T, MFXListCell<T>> {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.LIST_VIEW;
+	}
+
 	@Override
 	@Override
 	protected void setDefaultCellFactory() {
 	protected void setDefaultCellFactory() {
 		setCellFactory(item -> new MFXListCell<>(this, item));
 		setCellFactory(item -> new MFXListCell<>(this, item));

+ 16 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXMagnifierPane.java

@@ -22,6 +22,9 @@ import io.github.palexdev.materialfx.beans.PositionBean;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableBooleanProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableBooleanProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXMagnifierPaneSkin;
 import io.github.palexdev.materialfx.skins.MFXMagnifierPaneSkin;
 import io.github.palexdev.materialfx.utils.ColorUtils;
 import io.github.palexdev.materialfx.utils.ColorUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
@@ -36,6 +39,7 @@ import javafx.css.StyleablePropertyFactory;
 import javafx.geometry.VPos;
 import javafx.geometry.VPos;
 import javafx.scene.Cursor;
 import javafx.scene.Cursor;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.image.Image;
 import javafx.scene.image.Image;
@@ -84,7 +88,7 @@ import java.util.List;
  * }
  * }
  * </pre>
  * </pre>
  */
  */
-public class MFXMagnifierPane extends Control {
+public class MFXMagnifierPane extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -117,6 +121,7 @@ public class MFXMagnifierPane extends Control {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		setCursor(Cursor.NONE);
 		setCursor(Cursor.NONE);
 		setSnapToPixel(false);
 		setSnapToPixel(false);
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -141,6 +146,16 @@ public class MFXMagnifierPane extends Control {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.MAGNIFIER;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXMagnifierPaneSkin(this);
 		return new MFXMagnifierPaneSkin(this);

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXNotificationCenter.java

@@ -20,7 +20,10 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.collections.TransformableListWrapper;
 import io.github.palexdev.materialfx.collections.TransformableListWrapper;
 import io.github.palexdev.materialfx.controls.base.MFXMenuControl;
 import io.github.palexdev.materialfx.controls.base.MFXMenuControl;
+import io.github.palexdev.materialfx.controls.base.Themable;
 import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell;
 import io.github.palexdev.materialfx.controls.cell.MFXNotificationCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.enums.NotificationCounterStyle;
 import io.github.palexdev.materialfx.enums.NotificationCounterStyle;
 import io.github.palexdev.materialfx.enums.NotificationState;
 import io.github.palexdev.materialfx.enums.NotificationState;
 import io.github.palexdev.materialfx.i18n.I18N;
 import io.github.palexdev.materialfx.i18n.I18N;
@@ -40,6 +43,7 @@ import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ListChangeListener;
 import javafx.event.EventHandler;
 import javafx.event.EventHandler;
 import javafx.geometry.Orientation;
 import javafx.geometry.Orientation;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Label;
 import javafx.scene.control.Label;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
@@ -89,7 +93,7 @@ import static io.github.palexdev.materialfx.enums.NotificationCounterStyle.NUMBE
  * As you can see it as a LOT to offer, and to be honest it's not everything I wanted to implement, but I decided to
  * As you can see it as a LOT to offer, and to be honest it's not everything I wanted to implement, but I decided to
  * restrain myself for now, as adding any other feature would add more and more complexity.
  * restrain myself for now, as adding any other feature would add more and more complexity.
  */
  */
-public class MFXNotificationCenter extends Control implements MFXMenuControl {
+public class MFXNotificationCenter extends Control implements MFXMenuControl, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -199,6 +203,7 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl {
 		});
 		});
 
 
 		startNotificationsUpdater(60, TimeUnit.SECONDS);
 		startNotificationsUpdater(60, TimeUnit.SECONDS);
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -704,6 +709,17 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.NOTIFICATION_CENTER;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXNotificationCenterSkin(this, virtualFlow);
 		return new MFXNotificationCenterSkin(this, virtualFlow);

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPagination.java

@@ -20,11 +20,15 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
+import io.github.palexdev.materialfx.controls.base.Themable;
 import io.github.palexdev.materialfx.controls.cell.MFXPage;
 import io.github.palexdev.materialfx.controls.cell.MFXPage;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXPaginationSkin;
 import io.github.palexdev.materialfx.skins.MFXPaginationSkin;
 import io.github.palexdev.materialfx.utils.NumberUtils;
 import io.github.palexdev.materialfx.utils.NumberUtils;
 import javafx.beans.property.*;
 import javafx.beans.property.*;
 import javafx.geometry.Orientation;
 import javafx.geometry.Orientation;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 
 
@@ -57,7 +61,7 @@ import java.util.function.Supplier;
  * <p>
  * <p>
  * The {@link #orientationProperty()} allows you to specify the pagination orientation.
  * The {@link #orientationProperty()} allows you to specify the pagination orientation.
  */
  */
-public class MFXPagination extends Control {
+public class MFXPagination extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -101,6 +105,7 @@ public class MFXPagination extends Control {
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		defaultIndexesSupplier();
 		defaultIndexesSupplier();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -194,6 +199,17 @@ public class MFXPagination extends Control {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PAGINATION;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXPaginationSkin(this);
 		return new MFXPaginationSkin(this);

+ 7 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXPasswordField.java

@@ -20,6 +20,8 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableBooleanProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableBooleanProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableStringProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableStringProperty;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.i18n.I18N;
 import io.github.palexdev.materialfx.i18n.I18N;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.NodeUtils;
@@ -266,6 +268,11 @@ public class MFXPasswordField extends MFXTextField {
 		boundField.selectAll();
 		boundField.selectAll();
 	}
 	}
 
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PASSWORD_FIELD;
+	}
+
 	//================================================================================
 	//================================================================================
 	// Styleable Properties
 	// Styleable Properties
 	//================================================================================
 	//================================================================================

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXProgressBar.java

@@ -19,12 +19,16 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.beans.NumberRange;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXProgressBarSkin;
 import io.github.palexdev.materialfx.skins.MFXProgressBarSkin;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import javafx.collections.FXCollections;
 import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableList;
 import javafx.css.*;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.ProgressBar;
 import javafx.scene.control.ProgressBar;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 
 
@@ -44,7 +48,7 @@ import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
  * <p>
  * <p>
  * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  */
  */
-public class MFXProgressBar extends ProgressBar {
+public class MFXProgressBar extends ProgressBar implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -78,6 +82,7 @@ public class MFXProgressBar extends ProgressBar {
 		setPrefWidth(200);
 		setPrefWidth(200);
 
 
 		addListeners();
 		addListeners();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	private void addListeners() {
 	private void addListeners() {
@@ -182,6 +187,17 @@ public class MFXProgressBar extends ProgressBar {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PROGRESS_BAR;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXProgressBarSkin(this);
 		return new MFXProgressBarSkin(this);

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

@@ -21,6 +21,9 @@ package io.github.palexdev.materialfx.controls;
 import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXProgressSpinnerSkin;
 import io.github.palexdev.materialfx.skins.MFXProgressSpinnerSkin;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import javafx.collections.FXCollections;
 import javafx.collections.FXCollections;
@@ -30,6 +33,7 @@ import javafx.css.CssMetaData;
 import javafx.css.PseudoClass;
 import javafx.css.PseudoClass;
 import javafx.css.Styleable;
 import javafx.css.Styleable;
 import javafx.css.StyleablePropertyFactory;
 import javafx.css.StyleablePropertyFactory;
+import javafx.scene.Parent;
 import javafx.scene.control.ProgressIndicator;
 import javafx.scene.control.ProgressIndicator;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.Region;
@@ -51,7 +55,7 @@ import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
  * <p>
  * <p>
  * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  */
  */
-public class MFXProgressSpinner extends ProgressIndicator {
+public class MFXProgressSpinner extends ProgressIndicator implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -82,6 +86,7 @@ public class MFXProgressSpinner extends ProgressIndicator {
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		addListeners();
 		addListeners();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	private void addListeners() {
 	private void addListeners() {
@@ -333,6 +338,17 @@ public class MFXProgressSpinner extends ProgressIndicator {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PROGRESS_SPINNER;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXProgressSpinnerSkin(this);
 		return new MFXProgressSpinnerSkin(this);

+ 26 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXRadioButton.java

@@ -19,9 +19,13 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.controls.base.MFXLabeled;
 import io.github.palexdev.materialfx.controls.base.MFXLabeled;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXRadioButtonSkin;
 import io.github.palexdev.materialfx.skins.MFXRadioButtonSkin;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import javafx.css.*;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.RadioButton;
 import javafx.scene.control.RadioButton;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
@@ -41,7 +45,7 @@ import java.util.List;
  * <p> - {@link #radiusProperty()}: to control the circles' radius
  * <p> - {@link #radiusProperty()}: to control the circles' radius
  * <p> - {@link #textExpandProperty()}: to control the text size and the checkbox layout (see documentation)
  * <p> - {@link #textExpandProperty()}: to control the text size and the checkbox layout (see documentation)
  */
  */
-public class MFXRadioButton extends RadioButton implements MFXLabeled {
+public class MFXRadioButton extends RadioButton implements MFXLabeled, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -65,6 +69,7 @@ public class MFXRadioButton extends RadioButton implements MFXLabeled {
 	//================================================================================
 	//================================================================================
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	//================================================================================
 	//================================================================================
@@ -240,6 +245,26 @@ public class MFXRadioButton extends RadioButton implements MFXLabeled {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.RADIO_BUTTON;
+	}
+
+	@Override
+	public boolean sceneBuilderIntegration() {
+		if (Themable.super.sceneBuilderIntegration()) {
+			setText("Radio");
+			return true;
+		}
+		return false;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXRadioButtonSkin(this);
 		return new MFXRadioButtonSkin(this);

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

@@ -19,6 +19,8 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.controls.base.AbstractMFXToggleNode;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXToggleNode;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.effects.ripple.RippleClipType;
 import io.github.palexdev.materialfx.effects.ripple.RippleClipType;
 import io.github.palexdev.materialfx.factories.RippleClipTypeFactory;
 import io.github.palexdev.materialfx.factories.RippleClipTypeFactory;
 import io.github.palexdev.materialfx.skins.MFXRectangleToggleNodeSkin;
 import io.github.palexdev.materialfx.skins.MFXRectangleToggleNodeSkin;
@@ -99,6 +101,12 @@ public class MFXRectangleToggleNode extends AbstractMFXToggleNode {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.RECTANGLE_TOGGLE_NODE;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXRectangleToggleNodeSkin(this);
 		return new MFXRectangleToggleNodeSkin(this);

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXScrollPane.java

@@ -18,11 +18,15 @@
 
 
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXScrollPaneSkin;
 import io.github.palexdev.materialfx.skins.MFXScrollPaneSkin;
 import io.github.palexdev.materialfx.utils.ColorUtils;
 import io.github.palexdev.materialfx.utils.ColorUtils;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.ScrollPane;
 import javafx.scene.control.ScrollPane;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Color;
@@ -33,7 +37,7 @@ import javafx.scene.paint.Paint;
  * <p>
  * <p>
  * Extends {@code ScrollPane} and redefines the style class to "mfx-scroll-pane" for usage in CSS.
  * Extends {@code ScrollPane} and redefines the style class to "mfx-scroll-pane" for usage in CSS.
  */
  */
-public class MFXScrollPane extends ScrollPane {
+public class MFXScrollPane extends ScrollPane implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -57,6 +61,7 @@ public class MFXScrollPane extends ScrollPane {
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		addListeners();
 		addListeners();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	//================================================================================
 	//================================================================================
@@ -152,6 +157,17 @@ public class MFXScrollPane extends ScrollPane {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.SCROLL_PANE;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXScrollPaneSkin(this);
 		return new MFXScrollPaneSkin(this);

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSlider.java

@@ -21,6 +21,9 @@ package io.github.palexdev.materialfx.controls;
 import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.beans.NumberRange;
 import io.github.palexdev.materialfx.beans.PositionBean;
 import io.github.palexdev.materialfx.beans.PositionBean;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.enums.SliderEnums.SliderMode;
 import io.github.palexdev.materialfx.enums.SliderEnums.SliderMode;
 import io.github.palexdev.materialfx.enums.SliderEnums.SliderPopupSide;
 import io.github.palexdev.materialfx.enums.SliderEnums.SliderPopupSide;
@@ -38,6 +41,7 @@ import javafx.css.*;
 import javafx.geometry.Orientation;
 import javafx.geometry.Orientation;
 import javafx.geometry.Pos;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Label;
 import javafx.scene.control.Label;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
@@ -108,7 +112,7 @@ import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
  * it is invalid but rather the value is clamped between the specified min and max values using {@link NumberUtils#clamp(double, double, double)}.
  * it is invalid but rather the value is clamped between the specified min and max values using {@link NumberUtils#clamp(double, double, double)}.
  * If you don't respect the order you'll end with an inconsistent state and most likely with a messed layout.
  * If you don't respect the order you'll end with an inconsistent state and most likely with a messed layout.
  */
  */
-public class MFXSlider extends Control {
+public class MFXSlider extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -197,6 +201,7 @@ public class MFXSlider extends Control {
 
 
 		defaultThumbSupplier();
 		defaultThumbSupplier();
 		defaultPopupSupplier();
 		defaultPopupSupplier();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	private void addListeners() {
 	private void addListeners() {
@@ -878,6 +883,17 @@ public class MFXSlider extends Control {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.SLIDER;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXSliderSkin(this);
 		return new MFXSliderSkin(this);

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXSpinner.java

@@ -21,12 +21,16 @@ package io.github.palexdev.materialfx.controls;
 import io.github.palexdev.materialfx.beans.properties.functional.BiFunctionProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.BiFunctionProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.ConsumerProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.ConsumerProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.SupplierProperty;
+import io.github.palexdev.materialfx.controls.base.Themable;
 import io.github.palexdev.materialfx.controls.models.spinner.SpinnerModel;
 import io.github.palexdev.materialfx.controls.models.spinner.SpinnerModel;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXSpinnerSkin;
 import io.github.palexdev.materialfx.skins.MFXSpinnerSkin;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.beans.property.*;
 import javafx.beans.property.*;
 import javafx.geometry.Orientation;
 import javafx.geometry.Orientation;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseEvent;
@@ -57,7 +61,7 @@ import java.util.function.Supplier;
  * <p> - You can easily change the icons, {@link #prevIconSupplierProperty()}, {@link #nextIconSupplierProperty()}
  * <p> - You can easily change the icons, {@link #prevIconSupplierProperty()}, {@link #nextIconSupplierProperty()}
  * <p> - You can specify the gap between the text and the icon, {@link #graphicTextGapProperty()}
  * <p> - You can specify the gap between the text and the icon, {@link #graphicTextGapProperty()}
  */
  */
-public class MFXSpinner<T> extends Control {
+public class MFXSpinner<T> extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -101,6 +105,7 @@ public class MFXSpinner<T> extends Control {
 		});
 		});
 
 
 		defaultIcons();
 		defaultIcons();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -139,6 +144,17 @@ public class MFXSpinner<T> extends Control {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.SPINNER;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXSpinnerSkin<>(this);
 		return new MFXSpinnerSkin<>(this);

+ 16 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepper.java

@@ -20,6 +20,9 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
 import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
 import io.github.palexdev.materialfx.controls.MFXStepperToggle.MFXStepperToggleEvent;
 import io.github.palexdev.materialfx.controls.MFXStepperToggle.MFXStepperToggleEvent;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.enums.StepperToggleState;
 import io.github.palexdev.materialfx.enums.StepperToggleState;
 import io.github.palexdev.materialfx.skins.MFXStepperSkin;
 import io.github.palexdev.materialfx.skins.MFXStepperSkin;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.NodeUtils;
@@ -65,7 +68,7 @@ import java.util.Objects;
  *
  *
  * @see MFXStepperToggle
  * @see MFXStepperToggle
  */
  */
-public class MFXStepper extends Control {
+public class MFXStepper extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -96,6 +99,7 @@ public class MFXStepper extends Control {
 		getStyleClass().setAll(STYLE_CLASS);
 		getStyleClass().setAll(STYLE_CLASS);
 		setMinHeight(400);
 		setMinHeight(400);
 		addListeners();
 		addListeners();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -672,6 +676,17 @@ public class MFXStepper extends Control {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.STEPPER;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXStepperSkin(this);
 		return new MFXStepperSkin(this);

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXStepperToggle.java

@@ -18,6 +18,9 @@
 
 
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.enums.StepperToggleState;
 import io.github.palexdev.materialfx.enums.StepperToggleState;
 import io.github.palexdev.materialfx.enums.TextPosition;
 import io.github.palexdev.materialfx.enums.TextPosition;
 import io.github.palexdev.materialfx.skins.MFXStepperSkin;
 import io.github.palexdev.materialfx.skins.MFXStepperSkin;
@@ -31,6 +34,7 @@ import javafx.event.Event;
 import javafx.event.EventType;
 import javafx.event.EventType;
 import javafx.geometry.Bounds;
 import javafx.geometry.Bounds;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 
 
@@ -57,7 +61,7 @@ import java.util.List;
  * add certain conditions/constraints (or even dependencies, other validators) that must be met in order for the state
  * add certain conditions/constraints (or even dependencies, other validators) that must be met in order for the state
  * to be COMPLETED and for the {@link MFXStepper} to go to the next toggle.
  * to be COMPLETED and for the {@link MFXStepper} to go to the next toggle.
  */
  */
-public class MFXStepperToggle extends Control implements Validated {
+public class MFXStepperToggle extends Control implements Validated, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -111,6 +115,7 @@ public class MFXStepperToggle extends Control implements Validated {
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().setAll(STYLE_CLASS);
 		getStyleClass().setAll(STYLE_CLASS);
 		addListeners();
 		addListeners();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -377,6 +382,17 @@ public class MFXStepperToggle extends Control implements Validated {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.STEPPER_TOGGLE;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXStepperToggleSkin(this);
 		return new MFXStepperToggleSkin(this);

+ 17 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTableView.java

@@ -21,7 +21,10 @@ package io.github.palexdev.materialfx.controls;
 import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
 import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
 import io.github.palexdev.materialfx.collections.TransformableList;
 import io.github.palexdev.materialfx.collections.TransformableList;
 import io.github.palexdev.materialfx.collections.TransformableListWrapper;
 import io.github.palexdev.materialfx.collections.TransformableListWrapper;
+import io.github.palexdev.materialfx.controls.base.Themable;
 import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
 import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.filter.base.AbstractFilter;
 import io.github.palexdev.materialfx.filter.base.AbstractFilter;
 import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
 import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
 import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
 import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
@@ -37,6 +40,7 @@ import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableList;
 import javafx.geometry.Orientation;
 import javafx.geometry.Orientation;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.layout.Priority;
 import javafx.scene.layout.Priority;
@@ -53,7 +57,7 @@ import java.util.function.Function;
  * @param <T> The type of the data within the table.
  * @param <T> The type of the data within the table.
  * @see MFXTableViewSkin
  * @see MFXTableViewSkin
  */
  */
-public class MFXTableView<T> extends Control {
+public class MFXTableView<T> extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -114,6 +118,7 @@ public class MFXTableView<T> extends Control {
 
 
 		getItems().addListener(itemsChanged);
 		getItems().addListener(itemsChanged);
 		getItems().addListener(itemsInvalid);
 		getItems().addListener(itemsInvalid);
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -284,6 +289,17 @@ public class MFXTableView<T> extends Control {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TABLE_VIEW;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXTableViewSkin<>(this, rowsFlow);
 		return new MFXTableViewSkin<>(this, rowsFlow);

+ 16 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTextField.java

@@ -23,6 +23,9 @@ import io.github.palexdev.materialfx.beans.properties.styleable.StyleableDoubleP
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableIntegerProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableIntegerProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
 import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
 import io.github.palexdev.materialfx.controls.base.MFXMenuControl;
 import io.github.palexdev.materialfx.controls.base.MFXMenuControl;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.enums.FloatMode;
 import io.github.palexdev.materialfx.enums.FloatMode;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.i18n.I18N;
 import io.github.palexdev.materialfx.i18n.I18N;
@@ -37,6 +40,7 @@ import javafx.css.Styleable;
 import javafx.css.StyleablePropertyFactory;
 import javafx.css.StyleablePropertyFactory;
 import javafx.event.Event;
 import javafx.event.Event;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.IndexRange;
 import javafx.scene.control.IndexRange;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Skin;
 import javafx.scene.control.TextField;
 import javafx.scene.control.TextField;
@@ -117,7 +121,7 @@ import java.util.List;
  * works for them. To make focus behavior consistent in CSS, MFXTextField introduces a new PseudoClass "focus-within" which will
  * works for them. To make focus behavior consistent in CSS, MFXTextField introduces a new PseudoClass "focus-within" which will
  * be activated every time the inner TextField is focused and deactivated when it loses focus
  * be activated every time the inner TextField is focused and deactivated when it loses focus
  */
  */
-public class MFXTextField extends TextField implements Validated, MFXMenuControl {
+public class MFXTextField extends TextField implements Validated, MFXMenuControl, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -217,6 +221,7 @@ public class MFXTextField extends TextField implements Validated, MFXMenuControl
 				boundField.deleteText(delegateGetSelection());
 				boundField.deleteText(delegateGetSelection());
 		});
 		});
 		defaultContextMenu();
 		defaultContextMenu();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	public void defaultContextMenu() {
 	public void defaultContextMenu() {
@@ -282,6 +287,16 @@ public class MFXTextField extends TextField implements Validated, MFXMenuControl
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TEXT_FIELD;
+	}
+
 	@Override
 	@Override
 	public MFXContextMenu getMFXContextMenu() {
 	public MFXContextMenu getMFXContextMenu() {
 		return contextMenu;
 		return contextMenu;

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

@@ -20,6 +20,9 @@ package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
 import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
 import io.github.palexdev.materialfx.controls.base.MFXLabeled;
 import io.github.palexdev.materialfx.controls.base.MFXLabeled;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXToggleButtonSkin;
 import io.github.palexdev.materialfx.skins.MFXToggleButtonSkin;
 import io.github.palexdev.materialfx.utils.ColorUtils;
 import io.github.palexdev.materialfx.utils.ColorUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
@@ -32,6 +35,7 @@ import javafx.css.*;
 import javafx.event.ActionEvent;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.event.EventHandler;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.*;
 import javafx.scene.control.*;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Color;
@@ -51,7 +55,7 @@ import java.util.List;
  * <p> - {@link #radiusProperty()}: to control the toggle's circle radius
  * <p> - {@link #radiusProperty()}: to control the toggle's circle radius
  * <p> - {@link #textExpandProperty()}: to control the text size and the checkbox layout (see documentation)
  * <p> - {@link #textExpandProperty()}: to control the text size and the checkbox layout (see documentation)
  */
  */
-public class MFXToggleButton extends Labeled implements Toggle, MFXLabeled {
+public class MFXToggleButton extends Labeled implements Toggle, MFXLabeled, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -88,6 +92,7 @@ public class MFXToggleButton extends Labeled implements Toggle, MFXLabeled {
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		setBehavior();
 		setBehavior();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	//================================================================================
 	//================================================================================
@@ -369,6 +374,26 @@ public class MFXToggleButton extends Labeled implements Toggle, MFXLabeled {
 	//================================================================================
 	//================================================================================
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TOGGLE_BUTTON;
+	}
+
+	@Override
+	public boolean sceneBuilderIntegration() {
+		if (Themable.super.sceneBuilderIntegration()) {
+			setText("Toggle");
+			return true;
+		}
+		return false;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXToggleButtonSkin(this);
 		return new MFXToggleButtonSkin(this);

+ 7 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTreeItem.java

@@ -21,6 +21,8 @@ package io.github.palexdev.materialfx.controls;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeCell;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeCell;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
 import io.github.palexdev.materialfx.controls.cell.MFXSimpleTreeCell;
 import io.github.palexdev.materialfx.controls.cell.MFXSimpleTreeCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.factories.InsetsFactory;
 import io.github.palexdev.materialfx.factories.InsetsFactory;
 import io.github.palexdev.materialfx.selection.TreeSelectionModel;
 import io.github.palexdev.materialfx.selection.TreeSelectionModel;
 import io.github.palexdev.materialfx.selection.base.ITreeSelectionModel;
 import io.github.palexdev.materialfx.selection.base.ITreeSelectionModel;
@@ -280,6 +282,11 @@ public class MFXTreeItem<T> extends AbstractMFXTreeItem<T> {
 		treeItems.forEach(item -> item.setItemParent(newParent));
 		treeItems.forEach(item -> item.setItemParent(newParent));
 	}
 	}
 
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TREE_ITEM;
+	}
+
 	@Override
 	@Override
 	protected Skin<?> createDefaultSkin() {
 	protected Skin<?> createDefaultSkin() {
 		return new MFXTreeItemSkin<>(this);
 		return new MFXTreeItemSkin<>(this);

+ 7 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTreeView.java

@@ -19,6 +19,8 @@
 package io.github.palexdev.materialfx.controls;
 package io.github.palexdev.materialfx.controls;
 
 
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.factories.InsetsFactory;
 import io.github.palexdev.materialfx.factories.InsetsFactory;
 import io.github.palexdev.materialfx.selection.TreeSelectionModel;
 import io.github.palexdev.materialfx.selection.TreeSelectionModel;
 import io.github.palexdev.materialfx.selection.base.ITreeSelectionModel;
 import io.github.palexdev.materialfx.selection.base.ITreeSelectionModel;
@@ -154,6 +156,11 @@ public class MFXTreeView<T> extends MFXScrollPane {
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
 
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TREE_VIEW;
+	}
+
 	/**
 	/**
 	 * Events class for tree views.
 	 * Events class for tree views.
 	 * <p>
 	 * <p>

+ 8 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXListView.java

@@ -29,6 +29,7 @@ import javafx.beans.property.SimpleObjectProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableList;
 import javafx.css.*;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import javafx.scene.paint.Paint;
@@ -42,7 +43,7 @@ import java.util.List;
  *
  *
  * @param <T> the type of data within the ListView
  * @param <T> the type of data within the ListView
  */
  */
-public abstract class AbstractMFXListView<T, C extends Cell<T>> extends Control implements IListView<T, C> {
+public abstract class AbstractMFXListView<T, C extends Cell<T>> extends Control implements IListView<T, C>, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -74,6 +75,7 @@ public abstract class AbstractMFXListView<T, C extends Cell<T>> extends Control
 	protected void initialize() {
 	protected void initialize() {
 		setDefaultCellFactory();
 		setDefaultCellFactory();
 		addBarsListeners();
 		addBarsListeners();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	protected void addBarsListeners() {
 	protected void addBarsListeners() {
@@ -180,6 +182,11 @@ public abstract class AbstractMFXListView<T, C extends Cell<T>> extends Control
 	//================================================================================
 	//================================================================================
 	// Getters/Setters
 	// Getters/Setters
 	//================================================================================
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
 	@Override
 	@Override
 	public ObservableList<T> getItems() {
 	public ObservableList<T> getItems() {
 		return items.get();
 		return items.get();

+ 20 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXToggleNode.java

@@ -21,9 +21,10 @@ package io.github.palexdev.materialfx.controls.base;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.ToggleButton;
 import javafx.scene.control.ToggleButton;
 
 
-public abstract class AbstractMFXToggleNode extends ToggleButton {
+public abstract class AbstractMFXToggleNode extends ToggleButton implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -54,6 +55,7 @@ public abstract class AbstractMFXToggleNode extends ToggleButton {
 	//================================================================================
 	//================================================================================
 	private void initialize() {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	public Node getLabelLeadingIcon() {
 	public Node getLabelLeadingIcon() {
@@ -85,4 +87,21 @@ public abstract class AbstractMFXToggleNode extends ToggleButton {
 	public void setLabelTrailingIcon(Node labelTrailingIcon) {
 	public void setLabelTrailingIcon(Node labelTrailingIcon) {
 		this.labelTrailingIcon.set(labelTrailingIcon);
 		this.labelTrailingIcon.set(labelTrailingIcon);
 	}
 	}
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public boolean sceneBuilderIntegration() {
+		if (Themable.super.sceneBuilderIntegration()) {
+			setText("Toggle");
+			return true;
+		}
+		return false;
+	}
 }
 }

+ 11 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeCell.java

@@ -23,6 +23,7 @@ import javafx.beans.property.*;
 import javafx.css.PseudoClass;
 import javafx.css.PseudoClass;
 import javafx.geometry.Pos;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.HBox;
 
 
 // TODO implement StringConverter (low priority)
 // TODO implement StringConverter (low priority)
@@ -47,7 +48,7 @@ import javafx.scene.layout.HBox;
  *
  *
  * @param <T> The type of the data within TreeItem.
  * @param <T> The type of the data within TreeItem.
  */
  */
-public abstract class AbstractMFXTreeCell<T> extends HBox {
+public abstract class AbstractMFXTreeCell<T> extends HBox implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -157,4 +158,13 @@ public abstract class AbstractMFXTreeCell<T> extends HBox {
 	 * is not empty, when it changes from empty to full or vice versa the icon is added/removed.
 	 * is not empty, when it changes from empty to full or vice versa the icon is added/removed.
 	 */
 	 */
 	public abstract void updateCell(MFXTreeItem<T> item);
 	public abstract void updateCell(MFXTreeItem<T> item);
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
 }
 }

+ 11 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeItem.java

@@ -24,6 +24,7 @@ import io.github.palexdev.materialfx.utils.TreeItemStream;
 import javafx.beans.property.*;
 import javafx.beans.property.*;
 import javafx.collections.FXCollections;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableList;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Control;
 import javafx.util.Callback;
 import javafx.util.Callback;
 
 
@@ -51,7 +52,7 @@ import java.util.List;
  * @see MFXTreeView
  * @see MFXTreeView
  * @see ITreeSelectionModel
  * @see ITreeSelectionModel
  */
  */
-public abstract class AbstractMFXTreeItem<T> extends Control {
+public abstract class AbstractMFXTreeItem<T> extends Control implements Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -70,6 +71,7 @@ public abstract class AbstractMFXTreeItem<T> extends Control {
 	//================================================================================
 	//================================================================================
 	public AbstractMFXTreeItem(T data) {
 	public AbstractMFXTreeItem(T data) {
 		this.data = data;
 		this.data = data;
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	//================================================================================
 	//================================================================================
@@ -346,4 +348,12 @@ public abstract class AbstractMFXTreeItem<T> extends Control {
 	public void setSelected(boolean selected) {
 	public void setSelected(boolean selected) {
 		this.selected.set(selected);
 		this.selected.set(selected);
 	}
 	}
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
 }
 }

+ 137 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/Themable.java

@@ -0,0 +1,137 @@
+package io.github.palexdev.materialfx.controls.base;
+
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
+import io.github.palexdev.materialfx.css.themes.Themes;
+import io.github.palexdev.materialfx.utils.SceneBuilderIntegration;
+import javafx.scene.Parent;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Public API for all controls that are integrated with the new Theming API, see {@link Theme}.
+ * <p></p>
+ * Ideally such controls should be instances of {@link Parent} for "proper" support in SceneBuilder, and they should specify
+ * which is their own "theme"/stylesheet.
+ * <p></p>
+ * <b>SceneBuilder Support</b>
+ * <p>
+ * By default, all controls implementing this will automatically check upon creation if they are being used inside SceneBuilder.
+ * When the software is detected, the control' stylesheet is added to itself, this is done on every new control added to the scene.
+ * <p>
+ * Despite appearances, this is the simplest and <b>fastest</b> solution.
+ * <p>
+ * Other approaches would involve adding a listener on the scene property of the node to detect when it was available.
+ * Then the {@link Themes} were added to the scene. This is not feasible as controls have two scenes in two separate moments:
+ * <p> 1) When the control is dragged into the main scene, the "drag effect" you see is basically a moving popup, so it's not the main scene
+ * <p> 2) When the mouse is released and the control is placed, it is then in the main scene
+ * <p>
+ * Styling only the main scene resulted in an "ugly" experience since controls in the drag popup would be un-styled.
+ * Styling both would lead to a huge performance drawback of the app, because now JavaFX would have to process the themes
+ * (the entire themes!) on TWO scenes.
+ * <p></p>
+ * This approach on the other hand not only is fast, but it should also be more flexible. From my tests, it seems that
+ * adding stylesheets to each component doesn't break styling, you will still be able to add stylesheets to each control
+ * individually or on its parent, and it should still work fine in any case.
+ * <p></p>
+ * As always, bugs and unexpected behaviors are behind the corner, that's why there's also an emergency switch to completely
+ * shut off the SceneBuilder integration.
+ * <p>
+ * To do so, it's just enough to create a file in the following directories:
+ * <p> - For Windows users: ~\AppData\Local\Scene Builder
+ * <p> - For MacOS users: ~/Library/Application Support/Scene Builder
+ * <p> - For Linux users: ~/.scenebuilder
+ * In the folder relative to your OS, create a file named exactly like this: MFX_SB_OFF
+ */
+public interface Themable {
+
+	/**
+	 * Implementations should return the {@link Parent} node onto which themes and stylesheets will be applied.
+	 * Most of the case its themselves.
+	 */
+	Parent toParent();
+
+	/**
+	 * Implementations of this should return the {@link Theme} responsible for styling themselves, most MaterialFX controls
+	 * return one of the constants offered by {@link Stylesheets}.
+	 */
+	Theme getTheme();
+
+	/**
+	 * This is the method responsible for SceneBuilder detection and integration.
+	 * <p></p>
+	 * By default, this adds the {@link Theme} returned by {@link #getTheme()} on the {@link Parent} returned by {@link #toParent()}.
+	 *
+	 * @return whether SceneBuilder was detected or not, allowing overrides of this to avoid calling the check again and just
+	 * checking the return of this method
+	 */
+	default boolean sceneBuilderIntegration() {
+		if (!SceneBuilderIntegration.isInSceneBuilder() || Helper.isInhibitSBSupport()) return false;
+		Helper.themeIt(this);
+		return true;
+	}
+
+	class Helper {
+		public static final Path SB_WIN_PATH = Path.of(System.getenv("APPDATA") + "/Scene Builder");
+		public static final Path SB_MAC_PATH = Path.of(System.getProperty("user.home") + "/Library/Application Support/Scene Builder");
+		public static final Path SB_LIN_PATH = Path.of(System.getProperty("user.home") + "/.scenebuilder");
+		private static OSType os = null;
+		private static Boolean inhibitSBSupport = null;
+
+		public enum OSType {
+			Windows, MacOS, Linux, Other
+		}
+
+		protected static void themeIt(Themable t) {
+			Parent parent = t.toParent();
+			Set<String> stylesheets = new HashSet<>(parent.getStylesheets());
+			String theme = t.getTheme().loadTheme();
+			if (stylesheets.contains(theme)) return;
+			parent.getStylesheets().add(theme);
+		}
+
+		protected static OSType detectOS() {
+			if (os == null) {
+				String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
+				if ((OS.contains("mac")) || (OS.contains("darwin"))) {
+					os = OSType.MacOS;
+				} else if (OS.contains("win")) {
+					os = OSType.Windows;
+				} else if (OS.contains("nux")) {
+					os = OSType.Linux;
+				} else {
+					os = OSType.Other;
+				}
+			}
+			return os;
+		}
+
+		protected static boolean isInhibitSBSupport() {
+			if (inhibitSBSupport == null) {
+				switch (detectOS()) {
+					case Windows: {
+						inhibitSBSupport = Files.exists(SB_WIN_PATH.resolve("MFX_SB_OFF"));
+						break;
+					}
+					case MacOS: {
+						inhibitSBSupport = Files.exists(SB_MAC_PATH.resolve("MFX_SB_OFF"));
+						break;
+					}
+					case Linux: {
+						inhibitSBSupport = Files.exists(SB_LIN_PATH.resolve("MFX_SB_OFF"));
+						break;
+					}
+					default: {
+						inhibitSBSupport = false;
+						break;
+					}
+				}
+			}
+			return inhibitSBSupport;
+		}
+	}
+}

+ 5 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckListCell.java

@@ -22,6 +22,8 @@ import io.github.palexdev.materialfx.beans.PositionBean;
 import io.github.palexdev.materialfx.controls.MFXCheckListView;
 import io.github.palexdev.materialfx.controls.MFXCheckListView;
 import io.github.palexdev.materialfx.controls.MFXCheckbox;
 import io.github.palexdev.materialfx.controls.MFXCheckbox;
 import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell;
 import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.Bindings;
@@ -87,6 +89,7 @@ public class MFXCheckListCell<T> extends AbstractMFXListCell<T> {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		setupRippleGenerator();
 		setupRippleGenerator();
 		render(getData());
 		render(getData());
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -168,7 +171,7 @@ public class MFXCheckListCell<T> extends AbstractMFXListCell<T> {
 	}
 	}
 
 
 	@Override
 	@Override
-	public Node getNode() {
-		return this;
+	public Theme getTheme() {
+		return Stylesheets.CHECK_LIST_CELL;
 	}
 	}
 }
 }

+ 11 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXCheckTreeCell.java

@@ -20,6 +20,8 @@ package io.github.palexdev.materialfx.controls.cell;
 
 
 import io.github.palexdev.materialfx.controls.MFXCheckTreeItem;
 import io.github.palexdev.materialfx.controls.MFXCheckTreeItem;
 import io.github.palexdev.materialfx.controls.MFXCheckbox;
 import io.github.palexdev.materialfx.controls.MFXCheckbox;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.SimpleBooleanProperty;
 import javafx.css.PseudoClass;
 import javafx.css.PseudoClass;
@@ -91,4 +93,13 @@ public class MFXCheckTreeCell<T> extends MFXSimpleTreeCell<T> {
 	public MFXCheckbox getCheckbox() {
 	public MFXCheckbox getCheckbox() {
 		return checkbox;
 		return checkbox;
 	}
 	}
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CHECK_TREE_CELL;
+	}
 }
 }

+ 16 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXComboBoxCell.java

@@ -20,12 +20,16 @@ package io.github.palexdev.materialfx.controls.cell;
 
 
 import io.github.palexdev.materialfx.controls.MFXComboBox;
 import io.github.palexdev.materialfx.controls.MFXComboBox;
 import io.github.palexdev.materialfx.controls.base.MFXCombo;
 import io.github.palexdev.materialfx.controls.base.MFXCombo;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.virtualizedfx.cell.Cell;
 import io.github.palexdev.virtualizedfx.cell.Cell;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.*;
 import javafx.beans.property.*;
 import javafx.css.PseudoClass;
 import javafx.css.PseudoClass;
 import javafx.geometry.Pos;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Label;
 import javafx.scene.control.Label;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseEvent;
@@ -43,7 +47,7 @@ import javafx.util.StringConverter;
  * {@link StringConverter} to convert the data to a String. In case it's null, toString() is
  * {@link StringConverter} to convert the data to a String. In case it's null, toString() is
  * called on the data.
  * called on the data.
  */
  */
-public class MFXComboBoxCell<T> extends HBox implements Cell<T> {
+public class MFXComboBoxCell<T> extends HBox implements Cell<T>, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -92,6 +96,7 @@ public class MFXComboBoxCell<T> extends HBox implements Cell<T> {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		setBehavior();
 		setBehavior();
 		render(getData());
 		render(getData());
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -141,6 +146,16 @@ public class MFXComboBoxCell<T> extends HBox implements Cell<T> {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.COMBO_BOX_CELL;
+	}
+
 	@Override
 	@Override
 	public Node getNode() {
 	public Node getNode() {
 		return this;
 		return this;

+ 16 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXDateCell.java

@@ -19,6 +19,9 @@
 package io.github.palexdev.materialfx.controls.cell;
 package io.github.palexdev.materialfx.controls.cell;
 
 
 import io.github.palexdev.materialfx.controls.MFXDatePicker;
 import io.github.palexdev.materialfx.controls.MFXDatePicker;
+import io.github.palexdev.materialfx.controls.base.Themable;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.virtualizedfx.cell.Cell;
 import io.github.palexdev.virtualizedfx.cell.Cell;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
@@ -27,6 +30,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.css.PseudoClass;
 import javafx.css.PseudoClass;
 import javafx.geometry.Pos;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Label;
 import javafx.scene.control.Label;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseEvent;
 
 
@@ -40,7 +44,7 @@ import java.time.LocalDate;
  * <p> - current: when the cell's value is equal to {@link MFXDatePicker#currentDateProperty()}
  * <p> - current: when the cell's value is equal to {@link MFXDatePicker#currentDateProperty()}
  * <p> - extra: to mark this cells as belonging to a different month
  * <p> - extra: to mark this cells as belonging to a different month
  */
  */
-public class MFXDateCell extends Label implements Cell<LocalDate> {
+public class MFXDateCell extends Label implements Cell<LocalDate>, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -74,6 +78,7 @@ public class MFXDateCell extends Label implements Cell<LocalDate> {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		setAlignment(Pos.CENTER);
 		setAlignment(Pos.CENTER);
 		setBehavior();
 		setBehavior();
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -116,6 +121,16 @@ public class MFXDateCell extends Label implements Cell<LocalDate> {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.DATE_CELL;
+	}
+
 	@Override
 	@Override
 	public Node getNode() {
 	public Node getNode() {
 		return this;
 		return this;

+ 10 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXListCell.java

@@ -21,10 +21,13 @@ package io.github.palexdev.materialfx.controls.cell;
 import io.github.palexdev.materialfx.beans.PositionBean;
 import io.github.palexdev.materialfx.beans.PositionBean;
 import io.github.palexdev.materialfx.controls.MFXListView;
 import io.github.palexdev.materialfx.controls.MFXListView;
 import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell;
 import io.github.palexdev.materialfx.controls.cell.base.AbstractMFXListCell;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.effects.ripple.MFXCircleRippleGenerator;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.ObjectExpression;
 import javafx.beans.binding.ObjectExpression;
 import javafx.scene.Node;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Label;
 import javafx.scene.control.Label;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseEvent;
@@ -82,6 +85,7 @@ public class MFXListCell<T> extends AbstractMFXListCell<T> {
 		getStyleClass().add(STYLE_CLASS);
 		getStyleClass().add(STYLE_CLASS);
 		setupRippleGenerator();
 		setupRippleGenerator();
 		render(getData());
 		render(getData());
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -132,10 +136,15 @@ public class MFXListCell<T> extends AbstractMFXListCell<T> {
 	}
 	}
 
 
 	@Override
 	@Override
-	public Node getNode() {
+	public Parent toParent() {
 		return this;
 		return this;
 	}
 	}
 
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.LIST_CELL;
+	}
+
 	@Override
 	@Override
 	public String toString() {
 	public String toString() {
 		String className = getClass().getName();
 		String className = getClass().getName();

+ 8 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/MFXSimpleTreeCell.java

@@ -22,6 +22,8 @@ import io.github.palexdev.materialfx.controls.MFXIconWrapper;
 import io.github.palexdev.materialfx.controls.MFXTreeItem;
 import io.github.palexdev.materialfx.controls.MFXTreeItem;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeCell;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeCell;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
+import io.github.palexdev.materialfx.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.font.MFXFontIcon;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.scene.Node;
 import javafx.scene.Node;
@@ -72,6 +74,7 @@ public class MFXSimpleTreeCell<T> extends AbstractMFXTreeCell<T> {
 				getChildren().set(0, (Node) newValue);
 				getChildren().set(0, (Node) newValue);
 			}
 			}
 		});
 		});
+		sceneBuilderIntegration();
 	}
 	}
 
 
 	/**
 	/**
@@ -149,4 +152,9 @@ public class MFXSimpleTreeCell<T> extends AbstractMFXTreeCell<T> {
 			disclosureNode.setRotate(90);
 			disclosureNode.setRotate(90);
 		}
 		}
 	}
 	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TREE_CELL;
+	}
 }
 }

+ 14 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/cell/base/AbstractMFXListCell.java

@@ -19,12 +19,15 @@
 package io.github.palexdev.materialfx.controls.cell.base;
 package io.github.palexdev.materialfx.controls.cell.base;
 
 
 import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXListView;
+import io.github.palexdev.materialfx.controls.base.Themable;
 import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
 import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
 import io.github.palexdev.virtualizedfx.cell.Cell;
 import io.github.palexdev.virtualizedfx.cell.Cell;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.*;
 import javafx.beans.property.*;
 import javafx.css.PseudoClass;
 import javafx.css.PseudoClass;
 import javafx.geometry.Pos;
 import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.HBox;
@@ -38,7 +41,7 @@ import javafx.scene.layout.HBox;
  *
  *
  * @param <T> the type of data within the ListView
  * @param <T> the type of data within the ListView
  */
  */
-public abstract class AbstractMFXListCell<T> extends HBox implements Cell<T> {
+public abstract class AbstractMFXListCell<T> extends HBox implements Cell<T>, Themable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -127,6 +130,16 @@ public abstract class AbstractMFXListCell<T> extends HBox implements Cell<T> {
 	// Override Methods
 	// Override Methods
 	//================================================================================
 	//================================================================================
 
 
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Node getNode() {
+		return this;
+	}
+
 	/**
 	/**
 	 * Updates the index property of the cell.
 	 * Updates the index property of the cell.
 	 * <p>
 	 * <p>

+ 7 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/css/themes/Stylesheets.java

@@ -2,6 +2,12 @@ package io.github.palexdev.materialfx.css.themes;
 
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.MFXResourcesLoader;
 
 
+/**
+ * Enumerator implementing {@link Theme} that exposes all the single stylesheets part of MaterialFX relative to each single
+ * control.
+ * <p></p>
+ * Makes use of the cache offered by {@link Theme}, subsequent loads of the same theme will be faster.
+ */
 public enum Stylesheets implements Theme {
 public enum Stylesheets implements Theme {
 	BUTTON("MFXButton.css"),
 	BUTTON("MFXButton.css"),
 	CHECKBOX("MFXCheckBox.css"),
 	CHECKBOX("MFXCheckBox.css"),
@@ -57,6 +63,6 @@ public enum Stylesheets implements Theme {
 	@Override
 	@Override
 	public String loadTheme() {
 	public String loadTheme() {
 		if (Helper.isCached(this)) return Helper.getCachedTheme(this);
 		if (Helper.isCached(this)) return Helper.getCachedTheme(this);
-		return Helper.cacheTheme(this, MFXResourcesLoader.load(baseDir() + getTheme()));
+		return Helper.cacheTheme(this, MFXResourcesLoader.load(mfxBaseDir() + getTheme()));
 	}
 	}
 }
 }

+ 42 - 3
materialfx/src/main/java/io/github/palexdev/materialfx/css/themes/Theme.java

@@ -3,29 +3,68 @@ package io.github.palexdev.materialfx.css.themes;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 
 
+/**
+ * Bare minimum API for themes.
+ * <p>
+ * Ideally every theme should expose their path and a way to load them.
+ * <p></p>
+ * This also offers an internal {@link Helper} class to cache loaded themes.
+ */
 public interface Theme {
 public interface Theme {
 
 
+	/**
+	 * @return the theme's path/url
+	 */
 	String getTheme();
 	String getTheme();
 
 
+	/**
+	 * Implementations of this should return the loaded theme as a String.
+	 */
 	String loadTheme();
 	String loadTheme();
 
 
-	default String baseDir() {
+	/**
+	 * @return the MaterialFX base dir containing all the stylesheets
+	 */
+	default String mfxBaseDir() {
 		return "css/";
 		return "css/";
 	}
 	}
 
 
 	class Helper {
 	class Helper {
 		private static final Map<Theme, String> CACHE = new HashMap<>();
 		private static final Map<Theme, String> CACHE = new HashMap<>();
 
 
-		protected static boolean isCached(Theme theme) {
+		/**
+		 * @return whether the given theme has already been loaded and cached before
+		 */
+		public static boolean isCached(Theme theme) {
 			return CACHE.containsKey(theme);
 			return CACHE.containsKey(theme);
 		}
 		}
 
 
+		/**
+		 * Loads the given theme and then caches it.
+		 *
+		 * @return the loaded theme or an empty string if the result was null
+		 */
+		public static String cacheTheme(Theme theme) {
+			String loaded = theme.loadTheme();
+			if (loaded == null) return "";
+			CACHE.put(theme, loaded);
+			return loaded;
+		}
+
+		/**
+		 * Caches the given loaded theme.
+		 *
+		 * @return the loaded theme
+		 */
 		protected static String cacheTheme(Theme theme, String loaded) {
 		protected static String cacheTheme(Theme theme, String loaded) {
 			CACHE.put(theme, loaded);
 			CACHE.put(theme, loaded);
 			return loaded;
 			return loaded;
 		}
 		}
 
 
-		protected static String getCachedTheme(Theme theme) {
+		/**
+		 * @return the loaded theme String from the given theme parameter, null if the theme was not cached before
+		 */
+		public static String getCachedTheme(Theme theme) {
 			return CACHE.get(theme);
 			return CACHE.get(theme);
 		}
 		}
 	}
 	}

+ 13 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/css/themes/Themes.java

@@ -2,8 +2,20 @@ package io.github.palexdev.materialfx.css.themes;
 
 
 import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.MFXResourcesLoader;
 
 
+/**
+ * Enumerator implementing {@link Theme} to expose all the themes part of MaterialFX.
+ * <p></p>
+ * Makes use of the cache offered by {@link Theme}, subsequent loads of the same theme will be faster.
+ */
 public enum Themes implements Theme {
 public enum Themes implements Theme {
+	/**
+	 * This theme contains all the stylesheets for the new MaterialFX controls.
+	 */
 	DEFAULT("DefaultTheme.css"),
 	DEFAULT("DefaultTheme.css"),
+
+	/**
+	 * This theme contains all the stylesheets for the legacy controls styled by MaterialFX.
+	 */
 	LEGACY("legacy/LegacyControls.css"),
 	LEGACY("legacy/LegacyControls.css"),
 	;
 	;
 
 
@@ -21,6 +33,6 @@ public enum Themes implements Theme {
 	@Override
 	@Override
 	public String loadTheme() {
 	public String loadTheme() {
 		if (Helper.isCached(this)) return Helper.getCachedTheme(this);
 		if (Helper.isCached(this)) return Helper.getCachedTheme(this);
-		return Helper.cacheTheme(this, MFXResourcesLoader.load(baseDir() + getTheme()));
+		return Helper.cacheTheme(this, MFXResourcesLoader.load(mfxBaseDir() + getTheme()));
 	}
 	}
 }
 }

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

@@ -433,7 +433,7 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
 					cell = getCell(i, j, null);
 					cell = getCell(i, j, null);
 					cell.updateItem(null);
 					cell.updateItem(null);
 					cells[i][j] = cell;
 					cells[i][j] = cell;
-					children.add(cell.getNode());
+					children.add(cell.toParent());
 					continue;
 					continue;
 				}
 				}
 
 
@@ -457,7 +457,7 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
 					cells[i][j] = cell;
 					cells[i][j] = cell;
 				}
 				}
 				cell.updateItem(date);
 				cell.updateItem(date);
-				children.add(cell.getNode());
+				children.add(cell.toParent());
 			}
 			}
 
 
 			if (!cellsInitialized) {
 			if (!cellsInitialized) {

+ 98 - 0
materialfx/src/main/java/io/github/palexdev/materialfx/utils/SceneBuilderIntegration.java

@@ -0,0 +1,98 @@
+package io.github.palexdev.materialfx.utils;
+
+import java.io.IOException;
+import java.lang.StackWalker.Option;
+import java.lang.StackWalker.StackFrame;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Set;
+
+/**
+ * This utility can be used by libraries that implement custom controls to allow them to detect whether they
+ * are being loaded by the SceneBuilder app. This allows to perform whatever action the user desires in such case.
+ * <p>
+ * For example MaterialFX uses this to set the buttons text to "Button" only if they are used in SceneBuilder, the caveat
+ * of such trick is that for some reason properties in the right pane are not updated, the Text property will appear blank,
+ * if you want to set a empty text, just use a whitespace character.
+ * <p></p>
+ * MaterialFX also does another interesting thing with this, since SceneBuilder doesn't offer any way to add custom themes,
+ * this utility helps with that by detecting the app and adding the stylesheet to the root Scene.
+ * <p></p>
+ * The utility is optimized to not run the detection more than one time, the result is cached after the first time.
+ * The 'search' can become invalid and thus repeated if the user changes the 'search' depth. What is the depth?
+ * This uses the {@link StackWalker} API to detect the caller of constructors/methods, the depth is the estimate number
+ * of {@link StackFrame}s to process.
+ */
+public class SceneBuilderIntegration {
+	//================================================================================
+	// Static Properties
+	//================================================================================
+	private static Boolean inSceneBuilder = null;
+	private static int depth = 10;
+	private static boolean isDepthInvalid = false;
+	public static final Path DEFAULT_DEBUG_FILE = Path.of(System.getProperty("user.home") + "/SceneBuilderIntegrationDebug.log");
+
+	//================================================================================
+	// Constructors
+	//================================================================================
+	private SceneBuilderIntegration() {
+	}
+
+	//================================================================================
+	// Static Methods
+	//================================================================================
+
+	/**
+	 * If {@link #isInSceneBuilder()} returns true executes the given action.
+	 */
+	public static void ifInSceneBuilder(Runnable action) {
+		if (isInSceneBuilder()) action.run();
+	}
+
+	/**
+	 * This is the core method responsible for detecting the caller of a constructor/method.
+	 * The {@link StackWalker} API used to do so, will detect all the classes involved in the calling.
+	 * SceneBuilder can be detected because classes loading custom controls are in a package containing the
+	 * following string: ".javafx.scenebuilder.kit."
+	 * <p></p>
+	 * Subsequent calls to this will return the cached result (if it was called at least once of course), so there
+	 * should be little to no performance impact in the app. If the search depth is changed the cached result is ignored
+	 * and the research is repeated.
+	 */
+	public static boolean isInSceneBuilder() {
+		if (inSceneBuilder == null || isDepthInvalid) {
+			StackWalker sw = StackWalker.getInstance(
+					Set.of(Option.RETAIN_CLASS_REFERENCE, Option.SHOW_REFLECT_FRAMES),
+					depth
+			);
+			inSceneBuilder = sw.walk(sfs -> sfs.anyMatch(sf ->
+					sf.getClassName() != null && sf.getClassName().contains(".javafx.scenebuilder.kit.")));
+			isDepthInvalid = false;
+		}
+		return inSceneBuilder;
+	}
+
+	/**
+	 * Sets the 'search' depth used by the {@link StackWalker} API.
+	 */
+	public static void setDepth(int depth) {
+		SceneBuilderIntegration.depth = depth;
+		SceneBuilderIntegration.isDepthInvalid = true;
+	}
+
+	/**
+	 * Creates a new file at the given {@link Path} if non-existent, then writes the given debug String
+	 * in it. The 'truncate' boolean flag specifies whether to append the string or clear the file content first.
+	 * <p></p>
+	 * For the path you can also use {@link #DEFAULT_DEBUG_FILE} which points at the 'user.home' directory, and
+	 * creates a 'SceneBuilderIntegrationDebug.log' file in it.
+	 */
+	public static void debug(Path file, boolean truncate, String debug) {
+		try {
+			StandardOpenOption wrOpt = truncate ? StandardOpenOption.TRUNCATE_EXISTING : StandardOpenOption.APPEND;
+			Files.writeString(file, debug + "\n", StandardOpenOption.CREATE, StandardOpenOption.WRITE, wrOpt);
+		} catch (IOException ignored) {
+		}
+	}
+}

+ 117 - 13
materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/observables/OnChanged.java

@@ -1,27 +1,32 @@
 /*
 /*
- * Copyright (C) 2022 Parisi Alessandro
- * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
+ * Copyright (C) 2022 Parisi Alessandro - alessandro.parisi406@gmail.com
+ * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX)
  *
  *
- * MaterialFX is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * MaterialFX is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 3 of the License,
+ * or (at your option) any later version.
  *
  *
  * MaterialFX is distributed in the hope that it will be useful,
  * MaterialFX is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
  *
  *
  * You should have received a copy of the GNU Lesser General Public License
  * You should have received a copy of the GNU Lesser General Public License
- * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
+ * along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
 package io.github.palexdev.materialfx.utils.others.observables;
 package io.github.palexdev.materialfx.utils.others.observables;
 
 
+import io.github.palexdev.materialfx.utils.others.TriConsumer;
+import javafx.beans.Observable;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.beans.value.ObservableValue;
 
 
+import java.lang.ref.WeakReference;
 import java.util.function.BiConsumer;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
 
 
 /**
 /**
  * Concrete implementation of {@link When} that uses {@link ChangeListener}s to
  * Concrete implementation of {@link When} that uses {@link ChangeListener}s to
@@ -30,6 +35,12 @@ import java.util.function.BiConsumer;
  * You can specify the action to perform when this happens using a {@link BiConsumer},
  * You can specify the action to perform when this happens using a {@link BiConsumer},
  * {@link #then(BiConsumer)}.
  * {@link #then(BiConsumer)}.
  * <p>
  * <p>
+ * You can also set a condition that has to be met for the action to be executed (see {@link #condition(BiFunction)}),
+ * and an "else" action that is executed when it is not met, (see {@link #otherwise(TriConsumer)}).
+ * <p></p>
+ * Optionally you could also tell the construct to execute te given action immediately, the {@link BiConsumer} will
+ * take null and {@link ObservableValue#getValue()} as the old and new values.
+ * <p></p>
  * To activate the construct do not forget to call {@link #listen()} at the end.
  * To activate the construct do not forget to call {@link #listen()} at the end.
  * <p></p>
  * <p></p>
  * An example:
  * An example:
@@ -37,7 +48,9 @@ import java.util.function.BiConsumer;
  * {@code
  * {@code
  *      IntegerProperty aNumber = new SimpleIntegerProperty(69);
  *      IntegerProperty aNumber = new SimpleIntegerProperty(69);
  *      When.onChanged(aNumber) // You can also use... OnChanged.forObservable(...)
  *      When.onChanged(aNumber) // You can also use... OnChanged.forObservable(...)
+ *              .condition(aCondition)
  *              .then((oldValue, newValue) -> System.out.println("Value switched from: " + oldValue + " to " + newValue))
  *              .then((oldValue, newValue) -> System.out.println("Value switched from: " + oldValue + " to " + newValue))
+ *              .otherwise((ref, oldValue, newValue) -> System.out.println("Condition not met, execution action B"))
  *              .oneShot()
  *              .oneShot()
  *              .listen();
  *              .listen();
  * }
  * }
@@ -49,6 +62,9 @@ public class OnChanged<T> extends When<T> {
 	//================================================================================
 	//================================================================================
 	private ChangeListener<T> listener;
 	private ChangeListener<T> listener;
 	private BiConsumer<T, T> action;
 	private BiConsumer<T, T> action;
+	private TriConsumer<WeakReference<When<T>>, T, T> otherwise = (w, o, n) -> {
+	};
+	private BiFunction<T, T, Boolean> condition = (o, n) -> true;
 
 
 	//================================================================================
 	//================================================================================
 	// Constructors
 	// Constructors
@@ -82,35 +98,123 @@ public class OnChanged<T> extends When<T> {
 		return this;
 		return this;
 	}
 	}
 
 
+	/**
+	 * Allows to set an action to perform when the given {@link #condition(BiFunction)} is not met.
+	 * <p></p>
+	 * This makes the "system" much more versatile. Imagine having a one-shot listener that you want to
+	 * dispose anyway even if the condition is not met, you can write something like this;
+	 * <pre>
+	 * {@code
+	 * When.onChanged(observable)
+	 *      .condition(aCondition)
+	 *      .then(action)
+	 *      .otherwise((w, o, n) -> Optional.ofNullable(w.get()).ifPresent(When::dispose)) // Note the null check
+	 *      .listen();
+	 *
+	 * }
+	 * </pre>
+	 * <p></p>
+	 * Also note that the otherwise action also carries the reference to this object wrapped in a {@link WeakReference}.
+	 */
+	public OnChanged<T> otherwise(TriConsumer<WeakReference<When<T>>, T, T> otherwise) {
+		this.otherwise = otherwise;
+		return this;
+	}
+
+	/**
+	 * Allows to specify a condition under which the set action (see {@link #then(BiConsumer)})
+	 * is to be executed.
+	 * <p></p>
+	 * The condition is specified through a {@link BiFunction} that provides both the old and new values
+	 * of the {@link ObservableValue}.
+	 * <p></p>
+	 * In case the condition is not met the {@link #otherwise(TriConsumer)} action is executed instead.
+	 * <p></p>
+	 * For one-shot listeners, the action is executed and the listener disposed only if the condition is met, else
+	 * the {@link #otherwise(TriConsumer)} action is executed instead.
+	 */
+	public OnChanged<T> condition(BiFunction<T, T, Boolean> condition) {
+		this.condition = condition;
+		return this;
+	}
+
+	/**
+	 * Executes the given action immediately with null as the old value and the current value of the
+	 * given {@link ObservableValue} as the new value.
+	 */
+	public OnChanged<T> executeNow() {
+		action.accept(null, observableValue.getValue());
+		return this;
+	}
+
+	/**
+	 * Calls {@link #executeNow()} if the given condition is true.
+	 */
+	public OnChanged<T> executeNow(Supplier<Boolean> condition) {
+		if (condition.get()) executeNow();
+		return this;
+	}
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+
 	/**
 	/**
 	 * Activates the {@code OnChanged} construct with the previously specified parameters.
 	 * Activates the {@code OnChanged} construct with the previously specified parameters.
 	 * So, builds the {@link ChangeListener} according to the {@link #isOneShot()} parameter,
 	 * So, builds the {@link ChangeListener} according to the {@link #isOneShot()} parameter,
 	 * then adds the listener to the specified {@link ObservableValue} and finally puts the Observable and
 	 * then adds the listener to the specified {@link ObservableValue} and finally puts the Observable and
 	 * the OnChanged construct in the map.
 	 * the OnChanged construct in the map.
+	 * <p></p>
+	 * Before activating the listener, it also activates all the invalidating sources added through {@link #invalidating(Observable)}.
 	 */
 	 */
 	@Override
 	@Override
 	public OnChanged<T> listen() {
 	public OnChanged<T> listen() {
 		if (oneShot) {
 		if (oneShot) {
 			listener = (observable, oldValue, newValue) -> {
 			listener = (observable, oldValue, newValue) -> {
-				action.accept(oldValue, newValue);
-				dispose();
+				if (condition.apply(oldValue, newValue)) {
+					action.accept(oldValue, newValue);
+					dispose();
+				} else {
+					otherwise.accept(new WeakReference<>(this), oldValue, newValue);
+				}
 			};
 			};
 		} else {
 		} else {
-			listener = (observable, oldValue, newValue) -> action.accept(oldValue, newValue);
+			listener = (observable, oldValue, newValue) -> {
+				if (condition.apply(oldValue, newValue)) {
+					action.accept(oldValue, newValue);
+				} else {
+					otherwise.accept(new WeakReference<>(this), oldValue, newValue);
+				}
+			};
 		}
 		}
 
 
+		invalidatingObservables.forEach(o -> o.addListener(invalidationListener));
+		register();
 		observableValue.addListener(listener);
 		observableValue.addListener(listener);
-		addConstruct(observableValue, this);
 		return this;
 		return this;
 	}
 	}
 
 
 	/**
 	/**
+	 * When one of the invalidating sources added through {@link #invalidating(Observable)} changes, this method will be
+	 * invoked and causes {@link #executeNow(Supplier)} to execute. The condition function is supplied with 'null' as the
+	 * old value, and {@link ObservableValue#getValue()} as the new value.
+	 */
+	@Override
+	protected When<T> invalidate() {
+		executeNow(() -> condition.apply(null, observableValue.getValue()));
+		return this;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p></p>
 	 * Disposes the {@code OnChanged} construct by removing the {@link ChangeListener}
 	 * Disposes the {@code OnChanged} construct by removing the {@link ChangeListener}
 	 * from the {@link ObservableValue}, then sets the listener to null and finally removes
 	 * from the {@link ObservableValue}, then sets the listener to null and finally removes
 	 * the observable from the map.
 	 * the observable from the map.
 	 */
 	 */
 	@Override
 	@Override
 	public void dispose() {
 	public void dispose() {
+		super.dispose();
 		if (observableValue != null && listener != null) {
 		if (observableValue != null && listener != null) {
 			observableValue.removeListener(listener);
 			observableValue.removeListener(listener);
 			listener = null;
 			listener = null;

+ 119 - 13
materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/observables/OnInvalidated.java

@@ -1,27 +1,32 @@
 /*
 /*
- * Copyright (C) 2022 Parisi Alessandro
- * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
+ * Copyright (C) 2022 Parisi Alessandro - alessandro.parisi406@gmail.com
+ * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX)
  *
  *
- * MaterialFX is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * MaterialFX is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 3 of the License,
+ * or (at your option) any later version.
  *
  *
  * MaterialFX is distributed in the hope that it will be useful,
  * MaterialFX is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
  *
  *
  * You should have received a copy of the GNU Lesser General Public License
  * You should have received a copy of the GNU Lesser General Public License
- * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
+ * along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
 package io.github.palexdev.materialfx.utils.others.observables;
 package io.github.palexdev.materialfx.utils.others.observables;
 
 
 import javafx.beans.InvalidationListener;
 import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
 import javafx.beans.value.ObservableValue;
 import javafx.beans.value.ObservableValue;
 
 
+import java.lang.ref.WeakReference;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 
 /**
 /**
  * Concrete implementation of {@link When} that uses {@link InvalidationListener}s to
  * Concrete implementation of {@link When} that uses {@link InvalidationListener}s to
@@ -30,6 +35,12 @@ import java.util.function.Consumer;
  * You can specify the action to perform when this happens using a {@link Consumer},
  * You can specify the action to perform when this happens using a {@link Consumer},
  * {@link #then(Consumer)}.
  * {@link #then(Consumer)}.
  * <p>
  * <p>
+ * You can also set a condition that has to be met for the action to be executed (see {@link #condition(Function)}),
+ * and an "else" action that is executed when it is not met, (see {@link #otherwise(BiConsumer)}).
+ * <p></p>
+ * Optionally you could also tell the construct to execute the given action immediately, the {@link Consumer} will
+ * take {@link ObservableValue#getValue()} as input.
+ * <p></p>
  * To activate the construct do not forget to call {@link #listen()} at the end.
  * To activate the construct do not forget to call {@link #listen()} at the end.
  * <p></p>
  * <p></p>
  * An example:
  * An example:
@@ -37,8 +48,11 @@ import java.util.function.Consumer;
  * {@code
  * {@code
  *      BooleanProperty aSwitch = new SimpleBooleanProperty(false);
  *      BooleanProperty aSwitch = new SimpleBooleanProperty(false);
  *      When.onInvalidated(aSwitch) // You can also use... OnInvalidated.forObservable(...)
  *      When.onInvalidated(aSwitch) // You can also use... OnInvalidated.forObservable(...)
+ *              .condition(aCondition)
  *              .then(value -> System.out.println("Value switched to: " + value))
  *              .then(value -> System.out.println("Value switched to: " + value))
+ *              .otherwise((ref, oldValue, newValue) -> System.out.println("Condition not met, execution action B"))
  *              .oneShot()
  *              .oneShot()
+ *              .executeNow() // This could also be moved after the listen method
  *              .listen();
  *              .listen();
  * }
  * }
  * </pre>
  * </pre>
@@ -49,6 +63,9 @@ public class OnInvalidated<T> extends When<T> {
 	//================================================================================
 	//================================================================================
 	private InvalidationListener listener;
 	private InvalidationListener listener;
 	private Consumer<T> action;
 	private Consumer<T> action;
+	private BiConsumer<WeakReference<When<T>>, T> otherwise = (w, t) -> {
+	};
+	private Function<T, Boolean> condition = t -> true;
 
 
 	//================================================================================
 	//================================================================================
 	// Constructors
 	// Constructors
@@ -82,35 +99,124 @@ public class OnInvalidated<T> extends When<T> {
 		return this;
 		return this;
 	}
 	}
 
 
+	/**
+	 * Allows to set an action to perform when the given {@link #condition(Function)} is not met.
+	 * <p></p>
+	 * This makes the "system" much more versatile. Imagine having a one-shot listener that you want to
+	 * dispose anyway even if the condition is not met, you can write something like this;
+	 * <pre>
+	 * {@code
+	 * When.onChanged(observable)
+	 *      .condition(aCondition)
+	 *      .then(action)
+	 *      .otherwise((w, t) -> Optional.ofNullable(w.get()).ifPresent(When::dispose)) // Note the null check
+	 *      .listen();
+	 *
+	 * }
+	 * </pre>
+	 * <p></p>
+	 * Also note that the otherwise action also carries the reference to this object wrapped in a {@link WeakReference}.
+	 */
+	public OnInvalidated<T> otherwise(BiConsumer<WeakReference<When<T>>, T> otherwise) {
+		this.otherwise = otherwise;
+		return this;
+	}
+
+	/**
+	 * Allows to specify a condition under which the set action (see {@link #then(Consumer)})
+	 * is to be executed.
+	 * <p></p>
+	 * The condition is specified through a {@link Function} that provides the current value
+	 * of the {@link ObservableValue}.
+	 * <p></p>
+	 * In case the condition is not met the {@link #otherwise(BiConsumer)} action is executed instead.
+	 * <p></p>
+	 * For one-shot listeners, the action is executed and the listener disposed only if the condition is met, else
+	 * the {@link #otherwise(BiConsumer)} action is executed instead.
+	 */
+	public OnInvalidated<T> condition(Function<T, Boolean> condition) {
+		this.condition = condition;
+		return this;
+	}
+
+	/**
+	 * Executes the given action immediately with the current value of the
+	 * given {@link ObservableValue}.
+	 */
+	public OnInvalidated<T> executeNow() {
+		action.accept(observableValue.getValue());
+		return this;
+	}
+
+	/**
+	 * Calls {@link #executeNow()} if the given condition is true.
+	 */
+	public OnInvalidated<T> executeNow(Supplier<Boolean> condition) {
+		if (condition.get()) executeNow();
+		return this;
+	}
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+
 	/**
 	/**
 	 * Activates the {@code OnInvalidated} construct with the previously specified parameters.
 	 * Activates the {@code OnInvalidated} construct with the previously specified parameters.
 	 * So, builds the {@link InvalidationListener} according to the {@link #isOneShot()} parameter,
 	 * So, builds the {@link InvalidationListener} according to the {@link #isOneShot()} parameter,
 	 * then adds the listener to the specified {@link ObservableValue} and finally puts the Observable and
 	 * then adds the listener to the specified {@link ObservableValue} and finally puts the Observable and
 	 * the OnInvalidated construct in the map.
 	 * the OnInvalidated construct in the map.
+	 * <p></p>
+	 * Before activating the listener, it also activates all the invalidating sources added through {@link #invalidating(Observable)}.
 	 */
 	 */
 	@Override
 	@Override
 	public OnInvalidated<T> listen() {
 	public OnInvalidated<T> listen() {
 		if (oneShot) {
 		if (oneShot) {
 			listener = invalidated -> {
 			listener = invalidated -> {
-				action.accept(observableValue.getValue());
-				dispose();
+				T value = observableValue.getValue();
+				if (condition.apply(value)) {
+					action.accept(value);
+					dispose();
+				} else {
+					otherwise.accept(new WeakReference<>(this), value);
+				}
 			};
 			};
 		} else {
 		} else {
-			listener = invalidated -> action.accept(observableValue.getValue());
+			listener = invalidated -> {
+				T value = observableValue.getValue();
+				if (condition.apply(value)) {
+					action.accept(value);
+				} else {
+					otherwise.accept(new WeakReference<>(this), value);
+				}
+			};
 		}
 		}
 
 
+		invalidatingObservables.forEach(o -> o.addListener(invalidationListener));
+		register();
 		observableValue.addListener(listener);
 		observableValue.addListener(listener);
-		addConstruct(observableValue, this);
 		return this;
 		return this;
 	}
 	}
 
 
 	/**
 	/**
+	 * When one of the invalidating sources added through {@link #invalidating(Observable)} changes, this method will be
+	 * invoked and causes {@link #executeNow(Supplier)} to execute. The condition function is supplied with {@link ObservableValue#getValue()}.
+	 */
+	@Override
+	protected When<T> invalidate() {
+		executeNow(() -> condition.apply(observableValue.getValue()));
+		return this;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p></p>
 	 * Disposes the {@code OnInvalidated} construct by removing the {@link InvalidationListener}
 	 * Disposes the {@code OnInvalidated} construct by removing the {@link InvalidationListener}
 	 * from the {@link ObservableValue}, then sets the listener to null and finally removes
 	 * from the {@link ObservableValue}, then sets the listener to null and finally removes
 	 * the observable from the map.
 	 * the observable from the map.
 	 */
 	 */
 	@Override
 	@Override
 	public void dispose() {
 	public void dispose() {
+		super.dispose();
 		if (observableValue != null && listener != null) {
 		if (observableValue != null && listener != null) {
 			observableValue.removeListener(listener);
 			observableValue.removeListener(listener);
 			listener = null;
 			listener = null;

+ 83 - 21
materialfx/src/main/java/io/github/palexdev/materialfx/utils/others/observables/When.java

@@ -1,27 +1,32 @@
 /*
 /*
- * Copyright (C) 2022 Parisi Alessandro
- * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
+ * Copyright (C) 2022 Parisi Alessandro - alessandro.parisi406@gmail.com
+ * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX)
  *
  *
- * MaterialFX is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * MaterialFX is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 3 of the License,
+ * or (at your option) any later version.
  *
  *
  * MaterialFX is distributed in the hope that it will be useful,
  * MaterialFX is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
  *
  *
  * You should have received a copy of the GNU Lesser General Public License
  * You should have received a copy of the GNU Lesser General Public License
- * along with MaterialFX.  If not, see <http://www.gnu.org/licenses/>.
+ * along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
 package io.github.palexdev.materialfx.utils.others.observables;
 package io.github.palexdev.materialfx.utils.others.observables;
 
 
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
 import javafx.beans.value.ObservableValue;
 import javafx.beans.value.ObservableValue;
 
 
 import java.lang.ref.WeakReference;
 import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.WeakHashMap;
+import java.util.function.Supplier;
 
 
 /**
 /**
  * Useful class to listen to changes for a given {@link ObservableValue} and perform any
  * Useful class to listen to changes for a given {@link ObservableValue} and perform any
@@ -29,8 +34,8 @@ import java.util.WeakHashMap;
  * <p>
  * <p>
  * You can read this construct as "When condition changes, then do this"
  * You can read this construct as "When condition changes, then do this"
  * <p>
  * <p>
- * This is just an abstract class that defines common properties and behavior but it has two concrete
- * implementation, {@link OnChanged} and {@link OnInvalidated}.
+ * This is just an abstract class that defines common properties and behavior, but it has two concrete
+ * implementations, {@link OnChanged} and {@link OnInvalidated}.
  * <p>
  * <p>
  * This construct also allows to define one-shot listeners, meaning that the
  * This construct also allows to define one-shot listeners, meaning that the
  * above phrase changes like this: "When condition changes, then do this, then dispose(remove listener)"
  * above phrase changes like this: "When condition changes, then do this, then dispose(remove listener)"
@@ -47,11 +52,17 @@ public abstract class When<T> {
 	protected final ObservableValue<T> observableValue;
 	protected final ObservableValue<T> observableValue;
 	protected boolean oneShot = false;
 	protected boolean oneShot = false;
 
 
+	protected final Set<Observable> invalidatingObservables;
+	protected InvalidationListener invalidationListener;
+
 	//================================================================================
 	//================================================================================
 	// Constructors
 	// Constructors
 	//================================================================================
 	//================================================================================
 	protected When(ObservableValue<T> observableValue) {
 	protected When(ObservableValue<T> observableValue) {
 		this.observableValue = observableValue;
 		this.observableValue = observableValue;
+
+		this.invalidatingObservables = new HashSet<>();
+		this.invalidationListener = o -> invalidate();
 	}
 	}
 
 
 	//================================================================================
 	//================================================================================
@@ -59,12 +70,56 @@ public abstract class When<T> {
 	//================================================================================
 	//================================================================================
 	public abstract When<T> listen();
 	public abstract When<T> listen();
 
 
-	public abstract void dispose();
-
 	//================================================================================
 	//================================================================================
 	// Methods
 	// Methods
 	//================================================================================
 	//================================================================================
 
 
+	/**
+	 * This is responsible for registering the {@code When} construct in a map that
+	 * keep references to all the built constructs. This is to avoid garbage collection and to
+	 * handle {@code When}s disposal easily.
+	 * <p></p>
+	 * This should be called by implementations of {@link #listen()}.
+	 */
+	protected final void register() {
+		if (whens.containsKey(observableValue))
+			throw new IllegalArgumentException("Cannot register this When construct as the given observable is already being observed");
+		whens.put(observableValue, new WeakReference<>(this));
+	}
+
+	/**
+	 * Adds an {@link Observable} to listen to, when it changes it will cause the invalidation of this construct
+	 * by calling {@link #invalidate()}.
+	 */
+	public When<T> invalidating(Observable obs) {
+		invalidatingObservables.add(obs);
+		return this;
+	}
+
+	/**
+	 * The default implementation does nothing.
+	 */
+	protected When<T> invalidate() {
+		return this;
+	}
+
+	/**
+	 * Implementation of this should allow executing the specified action before the
+	 * listener is attached to the observable.
+	 * By default, does nothing.
+	 */
+	public When<T> executeNow() {
+		return this;
+	}
+
+	/**
+	 * Calls {@link #executeNow()} if the given condition is true.
+	 */
+	public When<T> executeNow(Supplier<Boolean> condition) {
+		if (condition.get()) executeNow();
+		return this;
+	}
+
 	/**
 	/**
 	 * @return whether the construct is "one-shot"
 	 * @return whether the construct is "one-shot"
 	 * @see #oneShot()
 	 * @see #oneShot()
@@ -82,8 +137,15 @@ public abstract class When<T> {
 		return this;
 		return this;
 	}
 	}
 
 
-	protected void addConstruct(ObservableValue<?> observable, When<?> when) {
-		whens.put(observable, new WeakReference<>(when));
+	/**
+	 * Removes all the invalidating sources added through {@link #invalidating(Observable)} and removes the listener
+	 * from them.
+	 */
+	protected void dispose() {
+		invalidatingObservables.forEach(o -> o.removeListener(invalidationListener));
+		invalidatingObservables.clear();
+		if (invalidationListener != null)
+			invalidationListener = null;
 	}
 	}
 
 
 	//================================================================================
 	//================================================================================
@@ -105,12 +167,12 @@ public abstract class When<T> {
 	}
 	}
 
 
 	/**
 	/**
-	 * If a When constructs exists for the given {@link ObservableValue},
+	 * If a When construct exists for the given {@link ObservableValue},
 	 * {@link #dispose()} is invoked.
 	 * {@link #dispose()} is invoked.
 	 */
 	 */
-    public static void disposeFor(ObservableValue<?> observableValue) {
-	    WeakReference<When<?>> removedRef = whens.remove(observableValue);
-	    When<?> remove = removedRef != null ? removedRef.get() : null;
-        if (remove != null) remove.dispose();
-    }
+	public static void disposeFor(ObservableValue<?> observableValue) {
+		WeakReference<When<?>> ref = whens.remove(observableValue);
+		When<?> remove = ref != null ? ref.get() : null;
+		if (remove != null) remove.dispose();
+	}
 }
 }