Browse Source

: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 2 years ago
parent
commit
b590762e48
54 changed files with 1212 additions and 96 deletions
  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]
 
+## [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
 
 ## 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**: 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
 
 ###### Gradle
@@ -226,7 +223,7 @@ repositories {
 }
 
 dependencies {
-    implementation 'io.github.palexdev:materialfx:11.14.0'
+    implementation 'io.github.palexdev:materialfx:11.15.0'
 }
 ```
 
@@ -237,7 +234,7 @@ dependencies {
 <dependency>
     <groupId>io.github.palexdev</groupId>
     <artifactId>materialfx</artifactId>
-    <version>11.14.0</version>
+    <version>11.15.0</version>
 </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
 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
   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
   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
   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**.
   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
-- 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 -->
 

+ 1 - 1
gradle.properties

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

+ 1 - 1
materialfx/gradle.properties

@@ -1,6 +1,6 @@
 GROUP=io.github.palexdev
 POM_ARTIFACT_ID=materialfx
-VERSION_NAME=11.14.0
+VERSION_NAME=11.15.0
 
 POM_NAME=materialfx
 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) {
 		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;
 
 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.ripple.MFXCircleRippleGenerator;
 import io.github.palexdev.materialfx.enums.ButtonType;
@@ -28,6 +31,7 @@ import javafx.beans.property.*;
 import javafx.css.*;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Button;
 import javafx.scene.control.Skin;
 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
  * includes a {@code RippleGenerator} to generate ripple effects on click.
  */
-public class MFXButton extends Button {
+public class MFXButton extends Button implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -80,6 +84,7 @@ public class MFXButton extends Button {
 		getStyleClass().add(STYLE_CLASS);
 		setAlignment(Pos.CENTER);
 		setupRippleGenerator();
+		sceneBuilderIntegration();
 	}
 
 	public MFXCircleRippleGenerator getRippleGenerator() {
@@ -344,6 +349,25 @@ public class MFXButton extends Button {
 	//================================================================================
 	// 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
 	protected Skin<?> createDefaultSkin() {
 		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.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.utils.ListChangeProcessor;
 import io.github.palexdev.virtualizedfx.beans.NumberRange;
@@ -213,6 +215,10 @@ public class MFXCheckListView<T> extends AbstractMFXListView<T, MFXCheckListCell
 	//================================================================================
 	// Override Methods
 	//================================================================================
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CHECK_LIST_VIEW;
+	}
 
 	/**
 	 * 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.AbstractMFXTreeItem;
 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.base.ITreeCheckModel;
 import io.github.palexdev.materialfx.skins.MFXCheckTreeItemSkin;
@@ -113,6 +115,11 @@ public class MFXCheckTreeItem<T> extends MFXTreeItem<T> {
 	// Override Methods
 	//================================================================================
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TREE_ITEM;
+	}
+
 	/**
 	 * 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;
 
 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.utils.StyleablePropertiesUtils;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.CheckBox;
 import javafx.scene.control.ContentDisplay;
 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 #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
 	//================================================================================
@@ -63,6 +67,7 @@ public class MFXCheckbox extends CheckBox implements MFXLabeled {
 	//================================================================================
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 
 	//================================================================================
@@ -173,6 +178,24 @@ public class MFXCheckbox extends CheckBox implements MFXLabeled {
 	//================================================================================
 	// 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
 	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;
 
 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.skins.MFXCircleToggleNodeSkin;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
@@ -185,6 +187,12 @@ public class MFXCircleToggleNode extends AbstractMFXToggleNode {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CIRCLE_TOGGLE_NODE;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.controls.base.MFXCombo;
 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.i18n.I18N;
 import io.github.palexdev.materialfx.selection.ComboBoxSelectionModel;
@@ -279,6 +281,12 @@ public class MFXComboBox<T> extends MFXTextField implements MFXCombo<T> {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.COMBO_BOX;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.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 javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.StringProperty;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Labeled;
 import javafx.scene.control.Skin;
 import javafx.scene.control.Tooltip;
@@ -52,7 +56,7 @@ import java.util.function.Supplier;
  * <p></p>
  * 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
 	//================================================================================
@@ -93,6 +97,17 @@ public class MFXContextMenuItem extends Labeled {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.CONTEXT_MENU_ITEM;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.SupplierProperty;
 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.font.MFXFontIcon;
 import io.github.palexdev.materialfx.skins.MFXDatePickerSkin;
@@ -281,6 +283,12 @@ public class MFXDatePicker extends MFXTextField {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.DATE_PICKER;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.controls.cell.MFXComboBoxCell;
 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.utils.StringUtils;
 import javafx.beans.InvalidationListener;
@@ -154,6 +156,12 @@ public class MFXFilterComboBox<T> extends MFXComboBox<T> {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.FILTER_COMBO_BOX;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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;
 
 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.filter.base.AbstractFilter;
 import io.github.palexdev.materialfx.i18n.I18N;
@@ -29,6 +32,7 @@ import javafx.beans.property.StringProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.event.EventHandler;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.input.MouseEvent;
@@ -134,7 +138,7 @@ import java.util.function.Predicate;
  *
  * @param <T>
  */
-public class MFXFilterPane<T> extends Control {
+public class MFXFilterPane<T> extends Control implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -143,8 +147,10 @@ public class MFXFilterPane<T> extends Control {
 	private final ObservableList<AbstractFilter<T, ?>> filters = 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
@@ -158,6 +164,7 @@ public class MFXFilterPane<T> extends Control {
 	//================================================================================
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -249,6 +256,17 @@ public class MFXFilterPane<T> extends Control {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.FILTER_PANE;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.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.utils.ListChangeProcessor;
 import io.github.palexdev.virtualizedfx.beans.NumberRange;
@@ -213,6 +215,12 @@ public class MFXListView<T> extends AbstractMFXListView<T, MFXListCell<T>> {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.LIST_VIEW;
+	}
+
 	@Override
 	protected void setDefaultCellFactory() {
 		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.StyleableDoubleProperty;
 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.utils.ColorUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
@@ -36,6 +39,7 @@ import javafx.css.StyleablePropertyFactory;
 import javafx.geometry.VPos;
 import javafx.scene.Cursor;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.image.Image;
@@ -84,7 +88,7 @@ import java.util.List;
  * }
  * </pre>
  */
-public class MFXMagnifierPane extends Control {
+public class MFXMagnifierPane extends Control implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -117,6 +121,7 @@ public class MFXMagnifierPane extends Control {
 		getStyleClass().add(STYLE_CLASS);
 		setCursor(Cursor.NONE);
 		setSnapToPixel(false);
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -141,6 +146,16 @@ public class MFXMagnifierPane extends Control {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.MAGNIFIER;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.controls.base.MFXMenuControl;
+import io.github.palexdev.materialfx.controls.base.Themable;
 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.NotificationState;
 import io.github.palexdev.materialfx.i18n.I18N;
@@ -40,6 +43,7 @@ import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
 import javafx.event.EventHandler;
 import javafx.geometry.Orientation;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Label;
 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
  * 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
 	//================================================================================
@@ -199,6 +203,7 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl {
 		});
 
 		startNotificationsUpdater(60, TimeUnit.SECONDS);
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -704,6 +709,17 @@ public class MFXNotificationCenter extends Control implements MFXMenuControl {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.NOTIFICATION_CENTER;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.SupplierProperty;
+import io.github.palexdev.materialfx.controls.base.Themable;
 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.utils.NumberUtils;
 import javafx.beans.property.*;
 import javafx.geometry.Orientation;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 
@@ -57,7 +61,7 @@ import java.util.function.Supplier;
  * <p>
  * The {@link #orientationProperty()} allows you to specify the pagination orientation.
  */
-public class MFXPagination extends Control {
+public class MFXPagination extends Control implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -101,6 +105,7 @@ public class MFXPagination extends Control {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		defaultIndexesSupplier();
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -194,6 +199,17 @@ public class MFXPagination extends Control {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PAGINATION;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.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.i18n.I18N;
 import io.github.palexdev.materialfx.utils.NodeUtils;
@@ -266,6 +268,11 @@ public class MFXPasswordField extends MFXTextField {
 		boundField.selectAll();
 	}
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PASSWORD_FIELD;
+	}
+
 	//================================================================================
 	// 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;
 
 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.utils.StyleablePropertiesUtils;
 import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.ProgressBar;
 import javafx.scene.control.Skin;
 
@@ -44,7 +48,7 @@ import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
  * <p>
  * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  */
-public class MFXProgressBar extends ProgressBar {
+public class MFXProgressBar extends ProgressBar implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -78,6 +82,7 @@ public class MFXProgressBar extends ProgressBar {
 		setPrefWidth(200);
 
 		addListeners();
+		sceneBuilderIntegration();
 	}
 
 	private void addListeners() {
@@ -182,6 +187,17 @@ public class MFXProgressBar extends ProgressBar {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PROGRESS_BAR;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.properties.styleable.StyleableDoubleProperty;
 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.utils.StyleablePropertiesUtils;
 import javafx.collections.FXCollections;
@@ -30,6 +33,7 @@ import javafx.css.CssMetaData;
 import javafx.css.PseudoClass;
 import javafx.css.Styleable;
 import javafx.css.StyleablePropertyFactory;
+import javafx.scene.Parent;
 import javafx.scene.control.ProgressIndicator;
 import javafx.scene.control.Skin;
 import javafx.scene.layout.Region;
@@ -51,7 +55,7 @@ import static io.github.palexdev.materialfx.utils.NodeUtils.isPseudoClassActive;
  * <p>
  * I know this may seem a strange approach, but it is much more flexible and allows for a lot more customization.
  */
-public class MFXProgressSpinner extends ProgressIndicator {
+public class MFXProgressSpinner extends ProgressIndicator implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -82,6 +86,7 @@ public class MFXProgressSpinner extends ProgressIndicator {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		addListeners();
+		sceneBuilderIntegration();
 	}
 
 	private void addListeners() {
@@ -333,6 +338,17 @@ public class MFXProgressSpinner extends ProgressIndicator {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.PROGRESS_SPINNER;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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;
 
 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.utils.StyleablePropertiesUtils;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.RadioButton;
 import javafx.scene.control.Skin;
@@ -41,7 +45,7 @@ import java.util.List;
  * <p> - {@link #radiusProperty()}: to control the circles' radius
  * <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
 	//================================================================================
@@ -65,6 +69,7 @@ public class MFXRadioButton extends RadioButton implements MFXLabeled {
 	//================================================================================
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 
 	//================================================================================
@@ -240,6 +245,26 @@ public class MFXRadioButton extends RadioButton implements MFXLabeled {
 	//================================================================================
 	// 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
 	protected Skin<?> createDefaultSkin() {
 		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;
 
 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.factories.RippleClipTypeFactory;
 import io.github.palexdev.materialfx.skins.MFXRectangleToggleNodeSkin;
@@ -99,6 +101,12 @@ public class MFXRectangleToggleNode extends AbstractMFXToggleNode {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.RECTANGLE_TOGGLE_NODE;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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;
 
+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.utils.ColorUtils;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.ScrollPane;
 import javafx.scene.control.Skin;
 import javafx.scene.paint.Color;
@@ -33,7 +37,7 @@ import javafx.scene.paint.Paint;
  * <p>
  * 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
 	//================================================================================
@@ -57,6 +61,7 @@ public class MFXScrollPane extends ScrollPane {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		addListeners();
+		sceneBuilderIntegration();
 	}
 
 	//================================================================================
@@ -152,6 +157,17 @@ public class MFXScrollPane extends ScrollPane {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.SCROLL_PANE;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.PositionBean;
 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.enums.SliderEnums.SliderMode;
 import io.github.palexdev.materialfx.enums.SliderEnums.SliderPopupSide;
@@ -38,6 +41,7 @@ import javafx.css.*;
 import javafx.geometry.Orientation;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Label;
 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)}.
  * 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
 	//================================================================================
@@ -197,6 +201,7 @@ public class MFXSlider extends Control {
 
 		defaultThumbSupplier();
 		defaultPopupSupplier();
+		sceneBuilderIntegration();
 	}
 
 	private void addListeners() {
@@ -878,6 +883,17 @@ public class MFXSlider extends Control {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.SLIDER;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.ConsumerProperty;
 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.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.skins.MFXSpinnerSkin;
 import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.beans.property.*;
 import javafx.geometry.Orientation;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 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 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
 	//================================================================================
@@ -101,6 +105,7 @@ public class MFXSpinner<T> extends Control {
 		});
 
 		defaultIcons();
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -139,6 +144,17 @@ public class MFXSpinner<T> extends Control {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.SPINNER;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.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.skins.MFXStepperSkin;
 import io.github.palexdev.materialfx.utils.NodeUtils;
@@ -65,7 +68,7 @@ import java.util.Objects;
  *
  * @see MFXStepperToggle
  */
-public class MFXStepper extends Control {
+public class MFXStepper extends Control implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -96,6 +99,7 @@ public class MFXStepper extends Control {
 		getStyleClass().setAll(STYLE_CLASS);
 		setMinHeight(400);
 		addListeners();
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -672,6 +676,17 @@ public class MFXStepper extends Control {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.STEPPER;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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;
 
+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.TextPosition;
 import io.github.palexdev.materialfx.skins.MFXStepperSkin;
@@ -31,6 +34,7 @@ import javafx.event.Event;
 import javafx.event.EventType;
 import javafx.geometry.Bounds;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 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
  * 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
 	//================================================================================
@@ -111,6 +115,7 @@ public class MFXStepperToggle extends Control implements Validated {
 	private void initialize() {
 		getStyleClass().setAll(STYLE_CLASS);
 		addListeners();
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -377,6 +382,17 @@ public class MFXStepperToggle extends Control implements Validated {
 	//================================================================================
 	// Override Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.STEPPER_TOGGLE;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.collections.TransformableList;
 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.css.themes.Stylesheets;
+import io.github.palexdev.materialfx.css.themes.Theme;
 import io.github.palexdev.materialfx.filter.base.AbstractFilter;
 import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
 import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
@@ -37,6 +40,7 @@ import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
 import javafx.geometry.Orientation;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.layout.Priority;
@@ -53,7 +57,7 @@ import java.util.function.Function;
  * @param <T> The type of the data within the table.
  * @see MFXTableViewSkin
  */
-public class MFXTableView<T> extends Control {
+public class MFXTableView<T> extends Control implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -114,6 +118,7 @@ public class MFXTableView<T> extends Control {
 
 		getItems().addListener(itemsChanged);
 		getItems().addListener(itemsInvalid);
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -284,6 +289,17 @@ public class MFXTableView<T> extends Control {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TABLE_VIEW;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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.StyleableObjectProperty;
 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.font.MFXFontIcon;
 import io.github.palexdev.materialfx.i18n.I18N;
@@ -37,6 +40,7 @@ import javafx.css.Styleable;
 import javafx.css.StyleablePropertyFactory;
 import javafx.event.Event;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.IndexRange;
 import javafx.scene.control.Skin;
 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
  * 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
 	//================================================================================
@@ -217,6 +221,7 @@ public class MFXTextField extends TextField implements Validated, MFXMenuControl
 				boundField.deleteText(delegateGetSelection());
 		});
 		defaultContextMenu();
+		sceneBuilderIntegration();
 	}
 
 	public void defaultContextMenu() {
@@ -282,6 +287,16 @@ public class MFXTextField extends TextField implements Validated, MFXMenuControl
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TEXT_FIELD;
+	}
+
 	@Override
 	public MFXContextMenu getMFXContextMenu() {
 		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.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.utils.ColorUtils;
 import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
@@ -32,6 +35,7 @@ import javafx.css.*;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.*;
 import javafx.scene.input.MouseEvent;
 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 #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
 	//================================================================================
@@ -88,6 +92,7 @@ public class MFXToggleButton extends Labeled implements Toggle, MFXLabeled {
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
 		setBehavior();
+		sceneBuilderIntegration();
 	}
 
 	//================================================================================
@@ -369,6 +374,26 @@ public class MFXToggleButton extends Labeled implements Toggle, MFXLabeled {
 	//================================================================================
 	// 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
 	protected Skin<?> createDefaultSkin() {
 		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.AbstractMFXTreeItem;
 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.selection.TreeSelectionModel;
 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));
 	}
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TREE_ITEM;
+	}
+
 	@Override
 	protected Skin<?> createDefaultSkin() {
 		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;
 
 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.selection.TreeSelectionModel;
 import io.github.palexdev.materialfx.selection.base.ITreeSelectionModel;
@@ -154,6 +156,11 @@ public class MFXTreeView<T> extends MFXScrollPane {
 	// Override Methods
 	//================================================================================
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.TREE_VIEW;
+	}
+
 	/**
 	 * Events class for tree views.
 	 * <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.ObservableList;
 import javafx.css.*;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
@@ -42,7 +43,7 @@ import java.util.List;
  *
  * @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
 	//================================================================================
@@ -74,6 +75,7 @@ public abstract class AbstractMFXListView<T, C extends Cell<T>> extends Control
 	protected void initialize() {
 		setDefaultCellFactory();
 		addBarsListeners();
+		sceneBuilderIntegration();
 	}
 
 	protected void addBarsListeners() {
@@ -180,6 +182,11 @@ public abstract class AbstractMFXListView<T, C extends Cell<T>> extends Control
 	//================================================================================
 	// Getters/Setters
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
 	@Override
 	public ObservableList<T> getItems() {
 		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.SimpleObjectProperty;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.ToggleButton;
 
-public abstract class AbstractMFXToggleNode extends ToggleButton {
+public abstract class AbstractMFXToggleNode extends ToggleButton implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -54,6 +55,7 @@ public abstract class AbstractMFXToggleNode extends ToggleButton {
 	//================================================================================
 	private void initialize() {
 		getStyleClass().add(STYLE_CLASS);
+		sceneBuilderIntegration();
 	}
 
 	public Node getLabelLeadingIcon() {
@@ -85,4 +87,21 @@ public abstract class AbstractMFXToggleNode extends ToggleButton {
 	public void setLabelTrailingIcon(Node 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.geometry.Pos;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.layout.HBox;
 
 // TODO implement StringConverter (low priority)
@@ -47,7 +48,7 @@ import javafx.scene.layout.HBox;
  *
  * @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
 	//================================================================================
@@ -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.
 	 */
 	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.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.scene.Parent;
 import javafx.scene.control.Control;
 import javafx.util.Callback;
 
@@ -51,7 +52,7 @@ import java.util.List;
  * @see MFXTreeView
  * @see ITreeSelectionModel
  */
-public abstract class AbstractMFXTreeItem<T> extends Control {
+public abstract class AbstractMFXTreeItem<T> extends Control implements Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -70,6 +71,7 @@ public abstract class AbstractMFXTreeItem<T> extends Control {
 	//================================================================================
 	public AbstractMFXTreeItem(T data) {
 		this.data = data;
+		sceneBuilderIntegration();
 	}
 
 	//================================================================================
@@ -346,4 +348,12 @@ public abstract class AbstractMFXTreeItem<T> extends Control {
 	public void setSelected(boolean 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.MFXCheckbox;
 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.utils.NodeUtils;
 import javafx.beans.binding.Bindings;
@@ -87,6 +89,7 @@ public class MFXCheckListCell<T> extends AbstractMFXListCell<T> {
 		getStyleClass().add(STYLE_CLASS);
 		setupRippleGenerator();
 		render(getData());
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -168,7 +171,7 @@ public class MFXCheckListCell<T> extends AbstractMFXListCell<T> {
 	}
 
 	@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.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.SimpleBooleanProperty;
 import javafx.css.PseudoClass;
@@ -91,4 +93,13 @@ public class MFXCheckTreeCell<T> extends MFXSimpleTreeCell<T> {
 	public MFXCheckbox getCheckbox() {
 		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.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 javafx.beans.binding.Bindings;
 import javafx.beans.property.*;
 import javafx.css.PseudoClass;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Label;
 import javafx.scene.input.MouseButton;
 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
  * called on the data.
  */
-public class MFXComboBoxCell<T> extends HBox implements Cell<T> {
+public class MFXComboBoxCell<T> extends HBox implements Cell<T>, Themable {
 	//================================================================================
 	// Properties
 	//================================================================================
@@ -92,6 +96,7 @@ public class MFXComboBoxCell<T> extends HBox implements Cell<T> {
 		getStyleClass().add(STYLE_CLASS);
 		setBehavior();
 		render(getData());
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -141,6 +146,16 @@ public class MFXComboBoxCell<T> extends HBox implements Cell<T> {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.COMBO_BOX_CELL;
+	}
+
 	@Override
 	public Node getNode() {
 		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;
 
 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 javafx.beans.binding.Bindings;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
@@ -27,6 +30,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.css.PseudoClass;
 import javafx.geometry.Pos;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Label;
 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> - 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
 	//================================================================================
@@ -74,6 +78,7 @@ public class MFXDateCell extends Label implements Cell<LocalDate> {
 		getStyleClass().add(STYLE_CLASS);
 		setAlignment(Pos.CENTER);
 		setBehavior();
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -116,6 +121,16 @@ public class MFXDateCell extends Label implements Cell<LocalDate> {
 	//================================================================================
 	// Overridden Methods
 	//================================================================================
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.DATE_CELL;
+	}
+
 	@Override
 	public Node getNode() {
 		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.controls.MFXListView;
 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 javafx.beans.binding.Bindings;
 import javafx.beans.binding.ObjectExpression;
 import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.control.Label;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
@@ -82,6 +85,7 @@ public class MFXListCell<T> extends AbstractMFXListCell<T> {
 		getStyleClass().add(STYLE_CLASS);
 		setupRippleGenerator();
 		render(getData());
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -132,10 +136,15 @@ public class MFXListCell<T> extends AbstractMFXListCell<T> {
 	}
 
 	@Override
-	public Node getNode() {
+	public Parent toParent() {
 		return this;
 	}
 
+	@Override
+	public Theme getTheme() {
+		return Stylesheets.LIST_CELL;
+	}
+
 	@Override
 	public String toString() {
 		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.base.AbstractMFXTreeCell;
 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.utils.NodeUtils;
 import javafx.scene.Node;
@@ -72,6 +74,7 @@ public class MFXSimpleTreeCell<T> extends AbstractMFXTreeCell<T> {
 				getChildren().set(0, (Node) newValue);
 			}
 		});
+		sceneBuilderIntegration();
 	}
 
 	/**
@@ -149,4 +152,9 @@ public class MFXSimpleTreeCell<T> extends AbstractMFXTreeCell<T> {
 			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;
 
 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.virtualizedfx.cell.Cell;
 import javafx.beans.binding.Bindings;
 import javafx.beans.property.*;
 import javafx.css.PseudoClass;
 import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.Parent;
 import javafx.scene.input.MouseButton;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.HBox;
@@ -38,7 +41,7 @@ import javafx.scene.layout.HBox;
  *
  * @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
 	//================================================================================
@@ -127,6 +130,16 @@ public abstract class AbstractMFXListCell<T> extends HBox implements Cell<T> {
 	// Override Methods
 	//================================================================================
 
+	@Override
+	public Parent toParent() {
+		return this;
+	}
+
+	@Override
+	public Node getNode() {
+		return this;
+	}
+
 	/**
 	 * Updates the index property of the cell.
 	 * <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;
 
+/**
+ * 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 {
 	BUTTON("MFXButton.css"),
 	CHECKBOX("MFXCheckBox.css"),
@@ -57,6 +63,6 @@ public enum Stylesheets implements Theme {
 	@Override
 	public String loadTheme() {
 		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.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 {
 
+	/**
+	 * @return the theme's path/url
+	 */
 	String getTheme();
 
+	/**
+	 * Implementations of this should return the loaded theme as a String.
+	 */
 	String loadTheme();
 
-	default String baseDir() {
+	/**
+	 * @return the MaterialFX base dir containing all the stylesheets
+	 */
+	default String mfxBaseDir() {
 		return "css/";
 	}
 
 	class Helper {
 		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);
 		}
 
+		/**
+		 * 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) {
 			CACHE.put(theme, 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);
 		}
 	}

+ 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;
 
+/**
+ * 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 {
+	/**
+	 * This theme contains all the stylesheets for the new MaterialFX controls.
+	 */
 	DEFAULT("DefaultTheme.css"),
+
+	/**
+	 * This theme contains all the stylesheets for the legacy controls styled by MaterialFX.
+	 */
 	LEGACY("legacy/LegacyControls.css"),
 	;
 
@@ -21,6 +33,6 @@ public enum Themes implements Theme {
 	@Override
 	public String loadTheme() {
 		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.updateItem(null);
 					cells[i][j] = cell;
-					children.add(cell.getNode());
+					children.add(cell.toParent());
 					continue;
 				}
 
@@ -457,7 +457,7 @@ public class MFXDatePickerSkin extends MFXTextFieldSkin {
 					cells[i][j] = cell;
 				}
 				cell.updateItem(date);
-				children.add(cell.getNode());
+				children.add(cell.toParent());
 			}
 
 			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,
  * 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
- * 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;
 
+import io.github.palexdev.materialfx.utils.others.TriConsumer;
+import javafx.beans.Observable;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 
+import java.lang.ref.WeakReference;
 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
@@ -30,6 +35,12 @@ import java.util.function.BiConsumer;
  * You can specify the action to perform when this happens using a {@link BiConsumer},
  * {@link #then(BiConsumer)}.
  * <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.
  * <p></p>
  * An example:
@@ -37,7 +48,9 @@ import java.util.function.BiConsumer;
  * {@code
  *      IntegerProperty aNumber = new SimpleIntegerProperty(69);
  *      When.onChanged(aNumber) // You can also use... OnChanged.forObservable(...)
+ *              .condition(aCondition)
  *              .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()
  *              .listen();
  * }
@@ -49,6 +62,9 @@ public class OnChanged<T> extends When<T> {
 	//================================================================================
 	private ChangeListener<T> listener;
 	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
@@ -82,35 +98,123 @@ public class OnChanged<T> extends When<T> {
 		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.
 	 * 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
 	 * 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
 	public OnChanged<T> listen() {
 		if (oneShot) {
 			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 {
-			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);
-		addConstruct(observableValue, 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}
 	 * from the {@link ObservableValue}, then sets the listener to null and finally removes
 	 * the observable from the map.
 	 */
 	@Override
 	public void dispose() {
+		super.dispose();
 		if (observableValue != null && listener != null) {
 			observableValue.removeListener(listener);
 			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,
  * 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
- * 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;
 
 import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
 import javafx.beans.value.ObservableValue;
 
+import java.lang.ref.WeakReference;
+import java.util.function.BiConsumer;
 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
@@ -30,6 +35,12 @@ import java.util.function.Consumer;
  * You can specify the action to perform when this happens using a {@link Consumer},
  * {@link #then(Consumer)}.
  * <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.
  * <p></p>
  * An example:
@@ -37,8 +48,11 @@ import java.util.function.Consumer;
  * {@code
  *      BooleanProperty aSwitch = new SimpleBooleanProperty(false);
  *      When.onInvalidated(aSwitch) // You can also use... OnInvalidated.forObservable(...)
+ *              .condition(aCondition)
  *              .then(value -> System.out.println("Value switched to: " + value))
+ *              .otherwise((ref, oldValue, newValue) -> System.out.println("Condition not met, execution action B"))
  *              .oneShot()
+ *              .executeNow() // This could also be moved after the listen method
  *              .listen();
  * }
  * </pre>
@@ -49,6 +63,9 @@ public class OnInvalidated<T> extends When<T> {
 	//================================================================================
 	private InvalidationListener listener;
 	private Consumer<T> action;
+	private BiConsumer<WeakReference<When<T>>, T> otherwise = (w, t) -> {
+	};
+	private Function<T, Boolean> condition = t -> true;
 
 	//================================================================================
 	// Constructors
@@ -82,35 +99,124 @@ public class OnInvalidated<T> extends When<T> {
 		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.
 	 * 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
 	 * 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
 	public OnInvalidated<T> listen() {
 		if (oneShot) {
 			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 {
-			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);
-		addConstruct(observableValue, 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}
 	 * from the {@link ObservableValue}, then sets the listener to null and finally removes
 	 * the observable from the map.
 	 */
 	@Override
 	public void dispose() {
+		super.dispose();
 		if (observableValue != null && listener != null) {
 			observableValue.removeListener(listener);
 			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,
  * 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
- * 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;
 
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
 import javafx.beans.value.ObservableValue;
 
 import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.function.Supplier;
 
 /**
  * Useful class to listen to changes for a given {@link ObservableValue} and perform any
@@ -29,8 +34,8 @@ import java.util.WeakHashMap;
  * <p>
  * You can read this construct as "When condition changes, then do this"
  * <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>
  * 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)"
@@ -47,11 +52,17 @@ public abstract class When<T> {
 	protected final ObservableValue<T> observableValue;
 	protected boolean oneShot = false;
 
+	protected final Set<Observable> invalidatingObservables;
+	protected InvalidationListener invalidationListener;
+
 	//================================================================================
 	// Constructors
 	//================================================================================
 	protected When(ObservableValue<T> 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 void dispose();
-
 	//================================================================================
 	// 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"
 	 * @see #oneShot()
@@ -82,8 +137,15 @@ public abstract class When<T> {
 		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.
 	 */
-    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();
+	}
 }