Browse Source

:arrow_up: Upgrade components module

Controls Package
:bug: Fixed critical bug related to the initWidth and initHeight new styleable properties. For some reason they were causing the CSS to be reapplied continuously, causing memory leaks and a huge performance hit over time. Override invalidated() instead on set(...)
:recycle: Make both MFXControl and MFXLabeled implement the new MFXResizable API for integration with LayoutStrategy
:recycle: MFXSkinBase: make size computation methods public for integration with MFXResizable and LayoutStrategy
:recycle: Reworked and renamed applyInitSizes(...) to onInitSizesChanged(). Init sizes now are taken into account by the new LayoutStrategy API

Layout Package
:boom: New API to allow MaterialFX controls to easily change their sizing/layout strategy by simply setting a property instead of creating custom skins, custom components or overriding methods inline

Skins Package
:recycle: MFXFabSkin: do not take into account the init width as it is used by the LayoutStrategy now

Tests
:white_check_mark: Added tests for the new LayoutStrategy and MFXResizable APIs

Signed-off-by: Alessadro Parisi <alessandro.parisi406@gmail.com>
Alessadro Parisi 2 years ago
parent
commit
4f52c297d2

+ 11 - 3
.idea/session-manager.json

@@ -1,10 +1,18 @@
 [
 [
   {
   {
     "name": "Default",
     "name": "Default",
-    "desc": "Write documentation for the new added Curves, and document the all the other related changes (add videos from Flutter too)",
-    "fFile": "/modules/effects/src/main/java/io/github/palexdev/mfxeffects/animations/motion/Motion.java",
+    "desc": "I was finishing the tests on the new LayoutStrategy API",
+    "fFile": "/modules/components/src/test/java/interactive/TestLayoutStrategies.java",
     "files": [
     "files": [
-      "/modules/effects/src/main/java/io/github/palexdev/mfxeffects/animations/motion/Motion.java"
+      "/modules/components/src/main/java/io/github/palexdev/mfxcomponents/layout/MFXResizable.java",
+      "/modules/components/src/test/java/interactive/TestLayoutStrategies.java",
+      "/modules/components/src/test/java/interactive/TestInitSize.java",
+      "/modules/core/src/main/java/io/github/palexdev/mfxcore/utils/fx/LayoutUtils.java",
+      "/modules/components/src/main/java/io/github/palexdev/mfxcomponents/behaviors/MFXFabBehavior.java",
+      "/modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/base/MFXControl.java",
+      "/modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/base/MFXLabeled.java",
+      "/modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/fab/MFXFabBase.java",
+      "/modules/components/src/main/java/io/github/palexdev/mfxcomponents/layout/LayoutStrategy.java"
     ]
     ]
   }
   }
 ]
 ]

+ 14 - 0
modules/components/CHANGELOG.md

@@ -27,6 +27,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 - Added preliminary implementation of MFXThemeManager (atm just to simplify dev)
 - Added preliminary implementation of MFXThemeManager (atm just to simplify dev)
 - Added a new class that is capable of generating CSS stylesheets via code and it's super useful. It even allows you to
 - Added a new class that is capable of generating CSS stylesheets via code and it's super useful. It even allows you to
   use multiple selectors and pseudo classes
   use multiple selectors and pseudo classes
+- New API to allow MaterialFX controls to easily change their sizing/layout strategy by simply setting a property
+  instead of creating custom skins, custom components or overriding methods inline
+- Added tests for the new LayoutStrategy and MFXResizable APIs
 
 
 ## Changed
 ## Changed
 
 
@@ -44,11 +47,22 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
   instance of MFXFabBehavior
   instance of MFXFabBehavior
 - MFXButtonSkin: bind text node opacity to the new property of MFXLabeled
 - MFXButtonSkin: bind text node opacity to the new property of MFXLabeled
 - MFXButtonSkin: no need for the listener on the behaviorProvider property (good for memory and performance)
 - MFXButtonSkin: no need for the listener on the behaviorProvider property (good for memory and performance)
+- Make both MFXControl and MFXLabeled implement the new MFXResizable API for integration with LayoutStrategy
+- MFXSkinBase: make size computation methods public for integration with MFXResizable and LayoutStrategy
+- Reworked and renamed applyInitSizes(...) to onInitSizesChanged(). Init sizes now are taken into account by the new
+  LayoutStrategy API
+- MFXFabSkin: do not take into account the init width as it is used by the LayoutStrategy now
 
 
 ## Removed
 ## Removed
 
 
 - MFXExtendedFab has been removed to avoid code duplication
 - MFXExtendedFab has been removed to avoid code duplication
 
 
+## Fixed
+
+- Fixed critical bug related to the initWidth and initHeight new styleable properties. For some reason they were causing
+  the CSS to be reapplied continuously, causing memory leaks and a huge performance hit over time. Override
+  invalidated() instead on set(...)
+
 ## [11.16.0] - 09-02-2023
 ## [11.16.0] - 09-02-2023
 
 
 ## Added
 ## Added

+ 1 - 1
modules/components/src/main/java/io/github/palexdev/mfxcomponents/behaviors/MFXFabBehavior.java

@@ -177,7 +177,7 @@ public class MFXFabBehavior extends MFXButtonBehavior {
 	@SuppressWarnings("JavadocReference")
 	@SuppressWarnings("JavadocReference")
 	protected double computeWidth() {
 	protected double computeWidth() {
 		MFXFabBase fab = getFab();
 		MFXFabBase fab = getFab();
-		return fab.computePrefWidth(-1);
+		return fab.computePrefWidth(fab.getHeight());
 	}
 	}
 
 
 	/**
 	/**

+ 59 - 29
modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/base/MFXControl.java

@@ -19,11 +19,15 @@
 package io.github.palexdev.mfxcomponents.controls.base;
 package io.github.palexdev.mfxcomponents.controls.base;
 
 
 import io.github.palexdev.mfxcomponents.behaviors.MFXFabBehavior;
 import io.github.palexdev.mfxcomponents.behaviors.MFXFabBehavior;
+import io.github.palexdev.mfxcomponents.layout.LayoutStrategy;
+import io.github.palexdev.mfxcomponents.layout.MFXResizable;
 import io.github.palexdev.mfxcore.base.properties.functional.SupplierProperty;
 import io.github.palexdev.mfxcore.base.properties.functional.SupplierProperty;
 import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.mfxcore.behavior.BehaviorBase;
 import io.github.palexdev.mfxcore.behavior.BehaviorBase;
 import io.github.palexdev.mfxcore.behavior.WithBehavior;
 import io.github.palexdev.mfxcore.behavior.WithBehavior;
 import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
 import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.css.CssMetaData;
 import javafx.css.CssMetaData;
 import javafx.css.Styleable;
 import javafx.css.Styleable;
 import javafx.css.StyleablePropertyFactory;
 import javafx.css.StyleablePropertyFactory;
@@ -37,7 +41,7 @@ import java.util.function.Supplier;
  * Base class for MaterialFX controls. The idea is to have a separate hierarchy of components from the JavaFX one,
  * Base class for MaterialFX controls. The idea is to have a separate hierarchy of components from the JavaFX one,
  * that perfectly integrates with the new Behavior and Theming APIs.
  * that perfectly integrates with the new Behavior and Theming APIs.
  * <p>
  * <p>
- * Extends {@link Control} and implements both {@link WithBehavior} and {@link MFXStyleable}.
+ * Extends {@link Control} and implements, {@link WithBehavior}, {@link MFXStyleable} and {@link MFXResizable}.
  * Enforces the use of {@link MFXSkinBase} instances as Skin implementations and makes the {@link #createDefaultSkin()}
  * Enforces the use of {@link MFXSkinBase} instances as Skin implementations and makes the {@link #createDefaultSkin()}
  * final thus denying users to override it. To set custom skins you should override the new provided method {@link #buildSkin()}.
  * final thus denying users to override it. To set custom skins you should override the new provided method {@link #buildSkin()}.
  * <p>
  * <p>
@@ -59,11 +63,16 @@ import java.util.function.Supplier;
  * <p></p>
  * <p></p>
  * Design guidelines (like MD3), may specify in the components' specs the initial/minimum sizes for each component.
  * Design guidelines (like MD3), may specify in the components' specs the initial/minimum sizes for each component.
  * For this specific purpose, there are two properties that can be set in CSS: {@link #initHeightProperty()}, {@link #initWidthProperty()}.
  * For this specific purpose, there are two properties that can be set in CSS: {@link #initHeightProperty()}, {@link #initWidthProperty()}.
+ * <p>
+ * Since this always implements {@link MFXResizable}, it redefines the JavaFX's layout strategy by extending it to take
+ * into account the aforementioned sizes.
  *
  *
  * @param <B> the behavior type used by the control
  * @param <B> the behavior type used by the control
+ * @see MFXSkinBase
+ * @see MFXResizable
  */
  */
 @SuppressWarnings({"unchecked", "rawtypes"})
 @SuppressWarnings({"unchecked", "rawtypes"})
-public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends Control implements WithBehavior<B>, MFXStyleable {
+public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends Control implements WithBehavior<B>, MFXStyleable, MFXResizable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -80,6 +89,13 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 		}
 		}
 	};
 	};
 
 
+	private final ObjectProperty<LayoutStrategy> layoutStrategy = new SimpleObjectProperty<>(LayoutStrategy.defaultStrategy()) {
+		@Override
+		protected void invalidated() {
+			onLayoutStrategyChanged();
+		}
+	};
+
 	//================================================================================
 	//================================================================================
 	// Abstract Methods
 	// Abstract Methods
 	//================================================================================
 	//================================================================================
@@ -90,20 +106,14 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 	//================================================================================
 	//================================================================================
 
 
 	/**
 	/**
-	 * Applies the sizes specified by {@link #initHeightProperty()} and {@link #initWidthProperty()},
-	 * both set in a CSS stylesheet.
-	 * <p>
-	 * By default, this sets the component's pref height and width, can be overridden to change the behavior.
+	 * This is automatically invoked when either {@link #initHeightProperty()} or {@link #initWidthProperty()} change.
+	 * By default, this method triggers a layout request.
 	 * <p></p>
 	 * <p></p>
-	 * By default, the values are set only if the pref height and width are greater than 0, because
-	 * with 'init' sizes we suppose that the components sizes upon creation are still 0
-	 * The 'force' boolean parameter will skip the check and set them anyway.
+	 * The consequence is that the current set {@link LayoutStrategy} will be used to re-compute the component's sizes, and
+	 * if it takes into account those init sizes, the component will resize accordingly.
 	 */
 	 */
-	protected void applyInitSizes(boolean force) {
-		double ih = getInitHeight();
-		double iw = getInitWidth();
-		if (force || getPrefHeight() <= 0.0) setPrefHeight(ih);
-		if (force || getPrefWidth() <= 0.0) setPrefWidth(iw);
+	protected void onInitSizesChanged() {
+		requestLayout();
 	}
 	}
 
 
 	/**
 	/**
@@ -116,34 +126,39 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public void onLayoutStrategyChanged() {
+		requestLayout();
+	}
+
 	@Override
 	@Override
 	public double computeMinWidth(double height) {
 	public double computeMinWidth(double height) {
-		return super.computeMinWidth(height);
+		return getLayoutStrategy().computeMinWidth(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computeMinHeight(double width) {
 	public double computeMinHeight(double width) {
-		return super.computeMinHeight(width);
+		return getLayoutStrategy().computeMinHeight(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computePrefWidth(double height) {
 	public double computePrefWidth(double height) {
-		return super.computePrefWidth(height);
+		return getLayoutStrategy().computePrefWidth(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computePrefHeight(double width) {
 	public double computePrefHeight(double width) {
-		return super.computePrefHeight(width);
+		return getLayoutStrategy().computePrefHeight(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computeMaxWidth(double height) {
 	public double computeMaxWidth(double height) {
-		return super.computeMaxWidth(height);
+		return getLayoutStrategy().computeMaxWidth(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computeMaxHeight(double width) {
 	public double computeMaxHeight(double width) {
-		return super.computeMaxHeight(width);
+		return getLayoutStrategy().computeMaxHeight(this);
 	}
 	}
 
 
 	@Override
 	@Override
@@ -163,9 +178,8 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 			USE_COMPUTED_SIZE
 			USE_COMPUTED_SIZE
 	) {
 	) {
 		@Override
 		@Override
-		public void set(double v) {
-			super.set(v);
-			applyInitSizes(false);
+		public void invalidated() {
+			onInitSizesChanged();
 		}
 		}
 	};
 	};
 
 
@@ -176,9 +190,8 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 			USE_COMPUTED_SIZE
 			USE_COMPUTED_SIZE
 	) {
 	) {
 		@Override
 		@Override
-		public void set(double v) {
-			super.set(v);
-			applyInitSizes(false);
+		public void invalidated() {
+			onInitSizesChanged();
 		}
 		}
 	};
 	};
 
 
@@ -187,7 +200,7 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 	}
 	}
 
 
 	/**
 	/**
-	 * Specifies the component's initial height when created.
+	 * Specifies the component's initial height upon creation.
 	 * <p></p>
 	 * <p></p>
 	 * This can be useful when using components that define certain sizes by specs, in
 	 * This can be useful when using components that define certain sizes by specs, in
 	 * SceneBuilder and other similar cases. One could also use the '-fx-pref-height' CSS
 	 * SceneBuilder and other similar cases. One could also use the '-fx-pref-height' CSS
@@ -195,7 +208,8 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * just offers a way to specify the height in CSS and still apply it via code.
 	 * just offers a way to specify the height in CSS and still apply it via code.
 	 * <p>
 	 * <p>
-	 * The way initial sizes are applied is managed by {@link #applyInitSizes(boolean)}.
+	 * The way initial sizes are applied depends on the set {@link LayoutStrategy}, when this changes the layout request
+	 * is automatically triggered by {@link #onInitSizesChanged()}.
 	 * <p></p>
 	 * <p></p>
 	 * Can be set in CSS via the property: '-mfx-init-height'.
 	 * Can be set in CSS via the property: '-mfx-init-height'.
 	 */
 	 */
@@ -220,7 +234,8 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * just offers a way to specify the width in CSS and still apply it via code.
 	 * just offers a way to specify the width in CSS and still apply it via code.
 	 * <p>
 	 * <p>
-	 * The way initial sizes are applied is managed by {@link #applyInitSizes(boolean)}.
+	 * The way initial sizes are applied depends on the set {@link LayoutStrategy}, when this changes the layout request
+	 * is automatically triggered by {@link #onInitSizesChanged()}.
 	 * <p></p>
 	 * <p></p>
 	 * Can be set in CSS via the property: '-mfx-init-width'.
 	 * Can be set in CSS via the property: '-mfx-init-width'.
 	 */
 	 */
@@ -292,4 +307,19 @@ public abstract class MFXControl<B extends BehaviorBase<? extends Node>> extends
 	public void setBehaviorProvider(Supplier<B> behaviorProvider) {
 	public void setBehaviorProvider(Supplier<B> behaviorProvider) {
 		this.behaviorProvider.set(behaviorProvider);
 		this.behaviorProvider.set(behaviorProvider);
 	}
 	}
+
+	@Override
+	public LayoutStrategy getLayoutStrategy() {
+		return layoutStrategy.get();
+	}
+
+	@Override
+	public ObjectProperty<LayoutStrategy> layoutStrategyProperty() {
+		return layoutStrategy;
+	}
+
+	@Override
+	public void setLayoutStrategy(LayoutStrategy layoutStrategy) {
+		this.layoutStrategy.set(layoutStrategy);
+	}
 }
 }

+ 65 - 39
modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/base/MFXLabeled.java

@@ -19,11 +19,16 @@
 package io.github.palexdev.mfxcomponents.controls.base;
 package io.github.palexdev.mfxcomponents.controls.base;
 
 
 import io.github.palexdev.mfxcomponents.behaviors.MFXFabBehavior;
 import io.github.palexdev.mfxcomponents.behaviors.MFXFabBehavior;
+import io.github.palexdev.mfxcomponents.layout.LayoutStrategy;
+import io.github.palexdev.mfxcomponents.layout.LayoutStrategy.Defaults;
+import io.github.palexdev.mfxcomponents.layout.MFXResizable;
 import io.github.palexdev.mfxcore.base.properties.functional.SupplierProperty;
 import io.github.palexdev.mfxcore.base.properties.functional.SupplierProperty;
 import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
 import io.github.palexdev.mfxcore.behavior.BehaviorBase;
 import io.github.palexdev.mfxcore.behavior.BehaviorBase;
 import io.github.palexdev.mfxcore.behavior.WithBehavior;
 import io.github.palexdev.mfxcore.behavior.WithBehavior;
 import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
 import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.css.CssMetaData;
 import javafx.css.CssMetaData;
 import javafx.css.Styleable;
 import javafx.css.Styleable;
 import javafx.css.StyleablePropertyFactory;
 import javafx.css.StyleablePropertyFactory;
@@ -37,7 +42,7 @@ import java.util.function.Supplier;
  * Base class for MaterialFX controls that are text based. The idea is to have a separate hierarchy of components from the JavaFX one,
  * Base class for MaterialFX controls that are text based. The idea is to have a separate hierarchy of components from the JavaFX one,
  * that perfectly integrates with the new Behavior, Skin and Theming APIs.
  * that perfectly integrates with the new Behavior, Skin and Theming APIs.
  * <p>
  * <p>
- * Extends {@link Labeled} and implements both {@link WithBehavior} and {@link MFXStyleable}.
+ * Extends {@link Labeled} and implements {@link WithBehavior}, {@link MFXStyleable} and {@link MFXResizable}.
  * Enforces the use of {@link MFXSkinBase} instances as Skin implementations and makes the {@link #createDefaultSkin()}
  * Enforces the use of {@link MFXSkinBase} instances as Skin implementations and makes the {@link #createDefaultSkin()}
  * final thus denying users to override it. To set custom skins you should override the new provided method {@link #buildSkin()}.
  * final thus denying users to override it. To set custom skins you should override the new provided method {@link #buildSkin()}.
  * <p>
  * <p>
@@ -57,12 +62,16 @@ import java.util.function.Supplier;
  * <p></p>
  * <p></p>
  * Design guidelines (like MD3), may specify in the components' specs the initial/minimum sizes for each component.
  * Design guidelines (like MD3), may specify in the components' specs the initial/minimum sizes for each component.
  * For this specific purpose, there are two properties that can be set in CSS: {@link #initHeightProperty()}, {@link #initWidthProperty()}.
  * For this specific purpose, there are two properties that can be set in CSS: {@link #initHeightProperty()}, {@link #initWidthProperty()}.
+ * <p>
+ * Since this always implements {@link MFXResizable}, it redefines the JavaFX's layout strategy by extending it to take
+ * into account the aforementioned sizes.
  *
  *
  * @param <B> the behavior type used by the control
  * @param <B> the behavior type used by the control
  * @see MFXSkinBase
  * @see MFXSkinBase
+ * @see MFXResizable
  */
  */
 @SuppressWarnings({"unchecked", "rawtypes"})
 @SuppressWarnings({"unchecked", "rawtypes"})
-public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends Labeled implements WithBehavior<B>, MFXStyleable {
+public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends Labeled implements WithBehavior<B>, MFXStyleable, MFXResizable {
 	//================================================================================
 	//================================================================================
 	// Properties
 	// Properties
 	//================================================================================
 	//================================================================================
@@ -79,6 +88,13 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 		}
 		}
 	};
 	};
 
 
+	private final ObjectProperty<LayoutStrategy> layoutStrategy = new SimpleObjectProperty<>(defaultLayoutStrategy()) {
+		@Override
+		protected void invalidated() {
+			onLayoutStrategyChanged();
+		}
+	};
+
 	//================================================================================
 	//================================================================================
 	// Constructors
 	// Constructors
 	//================================================================================
 	//================================================================================
@@ -107,20 +123,14 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 	//================================================================================
 	//================================================================================
 
 
 	/**
 	/**
-	 * Applies the sizes specified by {@link #initHeightProperty()} and {@link #initWidthProperty()},
-	 * both set in a CSS stylesheet.
-	 * <p>
-	 * By default, this sets the component's pref height and width, can be overridden to change the behavior.
+	 * This is automatically invoked when either {@link #initHeightProperty()} or {@link #initWidthProperty()} change.
+	 * By default, this method triggers a layout request.
 	 * <p></p>
 	 * <p></p>
-	 * By default, the values are set only if the pref height and width are greater than 0, because
-	 * with 'init' sizes we suppose that the components sizes upon creation are still 0
-	 * The 'force' boolean parameter will skip the check and set them anyway.
+	 * The consequence is that the current set {@link LayoutStrategy} will be used to re-compute the component's sizes, and
+	 * if it takes into account those init sizes, the component will resize accordingly.
 	 */
 	 */
-	protected void applyInitSizes(boolean force) {
-		double ih = getInitHeight();
-		double iw = getInitWidth();
-		if (force || getPrefHeight() <= 0.0) setPrefHeight(ih);
-		if (force || getPrefWidth() <= 0.0) setPrefWidth(iw);
+	protected void onInitSizesChanged() {
+		requestLayout();
 	}
 	}
 
 
 	/**
 	/**
@@ -133,34 +143,46 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+	@Override
+	public void onLayoutStrategyChanged() {
+		requestLayout();
+	}
+
+	@Override
+	public LayoutStrategy defaultLayoutStrategy() {
+		return LayoutStrategy.defaultStrategy()
+				.setPrefWidthFunction(Defaults.DEF_PREF_WIDTH_FUNCTION.andThen(r -> Math.max(r, getInitWidth())))
+				.setPrefHeightFunction(Defaults.DEF_PREF_HEIGHT_FUNCTION.andThen(r -> Math.max(r, getInitHeight())));
+	}
+
 	@Override
 	@Override
 	public double computeMinWidth(double height) {
 	public double computeMinWidth(double height) {
-		return super.computeMinWidth(height);
+		return getLayoutStrategy().computeMinWidth(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computeMinHeight(double width) {
 	public double computeMinHeight(double width) {
-		return super.computeMinHeight(width);
+		return getLayoutStrategy().computeMinHeight(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computePrefWidth(double height) {
 	public double computePrefWidth(double height) {
-		return super.computePrefWidth(height);
+		return getLayoutStrategy().computePrefWidth(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computePrefHeight(double width) {
 	public double computePrefHeight(double width) {
-		return super.computePrefHeight(width);
+		return getLayoutStrategy().computePrefHeight(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computeMaxWidth(double height) {
 	public double computeMaxWidth(double height) {
-		return super.computeMaxWidth(height);
+		return getLayoutStrategy().computeMaxWidth(this);
 	}
 	}
 
 
 	@Override
 	@Override
 	public double computeMaxHeight(double width) {
 	public double computeMaxHeight(double width) {
-		return super.computeMaxHeight(width);
+		return getLayoutStrategy().computeMaxHeight(this);
 	}
 	}
 
 
 	/**
 	/**
@@ -180,17 +202,6 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 	//================================================================================
 	//================================================================================
 	// Styleable Properties
 	// Styleable Properties
 	//================================================================================
 	//================================================================================
-	// TODO should this be handled by the skin? (could also be handled in the overridden methods above though)
-	// TODO if so, should we add a switch to override this behavior so that a user doesn't have to create a custom skin?
-	// TODO One idea is to have 'Layout Strategies'. The problem is, let's suppose component A overrides the computePrefWidth method
-	// TODO and then a component B want to override it again, but it needs to original computation, the one produced by the superclass of A
-	// TODO there would be no way to regain the old computation unless copy-paste of code.
-	// TODO With 'Layout Strategies' components could define functions for each of the computation methods (min, pref and max sizes),
-	// TODO avoiding at least code duplication
-	//
-	// TODO these are officially DEPRECATED as for some reason they cause a huge performance overhead
-	// TODO 'Layout Strategies' may be a good alternative at this point, although values would be hard coded, still pretty
-	// TODO easy to replace though
 	private final StyleableDoubleProperty initHeight = new StyleableDoubleProperty(
 	private final StyleableDoubleProperty initHeight = new StyleableDoubleProperty(
 			StyleableProperties.INIT_HEIGHT,
 			StyleableProperties.INIT_HEIGHT,
 			this,
 			this,
@@ -198,9 +209,8 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 			USE_COMPUTED_SIZE
 			USE_COMPUTED_SIZE
 	) {
 	) {
 		@Override
 		@Override
-		public void set(double v) {
-			super.set(v);
-			applyInitSizes(false);
+		public void invalidated() {
+			onInitSizesChanged();
 		}
 		}
 	};
 	};
 
 
@@ -211,9 +221,8 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 			USE_COMPUTED_SIZE
 			USE_COMPUTED_SIZE
 	) {
 	) {
 		@Override
 		@Override
-		public void set(double v) {
-			super.set(v);
-			applyInitSizes(false);
+		public void invalidated() {
+			onInitSizesChanged();
 		}
 		}
 	};
 	};
 
 
@@ -237,7 +246,8 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * just offers a way to specify the height in CSS and still apply it via code.
 	 * just offers a way to specify the height in CSS and still apply it via code.
 	 * <p>
 	 * <p>
-	 * The way initial sizes are applied is managed by {@link #applyInitSizes(boolean)}.
+	 * The way initial sizes are applied depends on the set {@link LayoutStrategy}, when this changes the layout request
+	 * is automatically triggered by {@link #onInitSizesChanged()}.
 	 * <p></p>
 	 * <p></p>
 	 * Can be set in CSS via the property: '-mfx-init-height'.
 	 * Can be set in CSS via the property: '-mfx-init-height'.
 	 */
 	 */
@@ -262,7 +272,8 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * overwrite the value in some cases. To overcome this, the size can be set via code, this property
 	 * just offers a way to specify the width in CSS and still apply it via code.
 	 * just offers a way to specify the width in CSS and still apply it via code.
 	 * <p>
 	 * <p>
-	 * The way initial sizes are applied is managed by {@link #applyInitSizes(boolean)}.
+	 * The way initial sizes are applied depends on the set {@link LayoutStrategy}, when this changes the layout request
+	 * is automatically triggered by {@link #onInitSizesChanged()}.
 	 * <p></p>
 	 * <p></p>
 	 * Can be set in CSS via the property: '-mfx-init-width'.
 	 * Can be set in CSS via the property: '-mfx-init-width'.
 	 */
 	 */
@@ -359,4 +370,19 @@ public abstract class MFXLabeled<B extends BehaviorBase<? extends Node>> extends
 	public void setBehaviorProvider(Supplier<B> behaviorProvider) {
 	public void setBehaviorProvider(Supplier<B> behaviorProvider) {
 		this.behaviorProvider.set(behaviorProvider);
 		this.behaviorProvider.set(behaviorProvider);
 	}
 	}
+
+	@Override
+	public LayoutStrategy getLayoutStrategy() {
+		return layoutStrategy.get();
+	}
+
+	@Override
+	public ObjectProperty<LayoutStrategy> layoutStrategyProperty() {
+		return layoutStrategy;
+	}
+
+	@Override
+	public void setLayoutStrategy(LayoutStrategy layoutStrategy) {
+		this.layoutStrategy.set(layoutStrategy);
+	}
 }
 }

+ 40 - 1
modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/base/MFXSkinBase.java

@@ -18,6 +18,7 @@
 
 
 package io.github.palexdev.mfxcomponents.controls.base;
 package io.github.palexdev.mfxcomponents.controls.base;
 
 
+import io.github.palexdev.mfxcomponents.layout.MFXResizable;
 import io.github.palexdev.mfxcore.behavior.BehaviorBase;
 import io.github.palexdev.mfxcore.behavior.BehaviorBase;
 import io.github.palexdev.mfxcore.behavior.WithBehavior;
 import io.github.palexdev.mfxcore.behavior.WithBehavior;
 import javafx.beans.InvalidationListener;
 import javafx.beans.InvalidationListener;
@@ -43,7 +44,7 @@ import java.util.Optional;
  * <p> - the Behavior, defines what the component can do and how
  * <p> - the Behavior, defines what the component can do and how
  * <p>
  * <p>
  * So, as you may guess, there must be an 'infrastructure' that makes all these three parts communicate with each other.
  * So, as you may guess, there must be an 'infrastructure' that makes all these three parts communicate with each other.
- * The behavior may need to be connected with the specs of the component, as well as with its subcomponents defined in
+ * The behavior may need to be connected with the specs of the component, as well as with the subcomponents defined in
  * its view.
  * its view.
  * <p>
  * <p>
  * {@link MFXControl} and {@link MFXLabeled} are a bridge between these three parts. They retain the reference of the current
  * {@link MFXControl} and {@link MFXLabeled} are a bridge between these three parts. They retain the reference of the current
@@ -57,6 +58,9 @@ import java.util.Optional;
  * <p> - Having a behavior class and set the provider on the component
  * <p> - Having a behavior class and set the provider on the component
  * <p> - Override the {@link #initBehavior(BehaviorBase)} to initialize the behavior if needed
  * <p> - Override the {@link #initBehavior(BehaviorBase)} to initialize the behavior if needed
  * <p> - Initialization and changes to the behavior provider are automatically handled, hassle-free
  * <p> - Initialization and changes to the behavior provider are automatically handled, hassle-free
+ * <p></p>
+ * Last but not least, this skin makes all the methods responsible for computing the component' sizes {@code public}, this
+ * is for the integration with the {@link MFXResizable} API.
  */
  */
 public abstract class MFXSkinBase<C extends Control & WithBehavior<B>, B extends BehaviorBase<C>> extends javafx.scene.control.SkinBase<C> {
 public abstract class MFXSkinBase<C extends Control & WithBehavior<B>, B extends BehaviorBase<C>> extends javafx.scene.control.SkinBase<C> {
 
 
@@ -119,6 +123,41 @@ public abstract class MFXSkinBase<C extends Control & WithBehavior<B>, B extends
 		Optional.ofNullable(getBehavior()).ifPresent(b -> b.filter(node, eventType, handler));
 		Optional.ofNullable(getBehavior()).ifPresent(b -> b.filter(node, eventType, handler));
 	}
 	}
 
 
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+
+	@Override
+	public double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+		return super.computeMinWidth(height, topInset, rightInset, bottomInset, leftInset);
+	}
+
+	@Override
+	public double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+		return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
+	}
+
+	@Override
+	public double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+		return super.computeMaxWidth(height, topInset, rightInset, bottomInset, leftInset);
+	}
+
+	@Override
+	public double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+		return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset);
+	}
+
+	@Override
+	public double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+		return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
+	}
+
+	@Override
+	public double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+		return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
+	}
+
+
 	//================================================================================
 	//================================================================================
 	// Getters/Setters
 	// Getters/Setters
 	//================================================================================
 	//================================================================================

+ 2 - 2
modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/fab/MFXFab.java

@@ -117,14 +117,14 @@ public class MFXFab extends MFXFabBase implements WithVariants<MFXFab, FABVarian
 	@Override
 	@Override
 	public MFXFab addVariants(FABVariants... variants) {
 	public MFXFab addVariants(FABVariants... variants) {
 		WithVariants.addVariants(this, variants);
 		WithVariants.addVariants(this, variants);
-		applyInitSizes(true);
+		onInitSizesChanged();
 		return this;
 		return this;
 	}
 	}
 
 
 	@Override
 	@Override
 	public MFXFab setVariants(FABVariants... variants) {
 	public MFXFab setVariants(FABVariants... variants) {
 		WithVariants.setVariants(this, variants);
 		WithVariants.setVariants(this, variants);
-		applyInitSizes(true);
+		onInitSizesChanged();
 		return this;
 		return this;
 	}
 	}
 
 

+ 9 - 2
modules/components/src/main/java/io/github/palexdev/mfxcomponents/controls/fab/MFXFabBase.java

@@ -140,9 +140,16 @@ public class MFXFabBase extends MFXElevatedButton {
 	//================================================================================
 	//================================================================================
 	// Overridden Methods
 	// Overridden Methods
 	//================================================================================
 	//================================================================================
+
+	@Override
+	public void onLayoutStrategyChanged() {
+		if (getSkin() == null) return;
+		getFabBehavior().ifPresent(b -> b.extend(false));
+	}
+
 	@Override
 	@Override
-	protected void applyInitSizes(boolean force) {
-		super.applyInitSizes(force);
+	protected void onInitSizesChanged() {
+		super.onInitSizesChanged();
 
 
 		// This usually happens when the FAB changes between standard and extended
 		// This usually happens when the FAB changes between standard and extended
 		// In such cases it's important to ensure minimum sizes are correct
 		// In such cases it's important to ensure minimum sizes are correct

+ 277 - 0
modules/components/src/main/java/io/github/palexdev/mfxcomponents/layout/LayoutStrategy.java

@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2023 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 distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package io.github.palexdev.mfxcomponents.layout;
+
+import io.github.palexdev.mfxcomponents.controls.base.MFXSkinBase;
+import io.github.palexdev.mfxcore.base.TriFunction;
+import io.github.palexdev.mfxcore.builders.InsetsBuilder;
+import javafx.geometry.Insets;
+import javafx.scene.control.Skin;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A {@code LayoutStrategy} defines a series of {@link Function}s that are responsible for computing:
+ * <p> 1) The minimum width
+ * <p> 2) The minimum height
+ * <p> 3) The preferred width
+ * <p> 4) The preferred height
+ * <p> 5) The maximum width
+ * <p> 6) The maximum height
+ * <p>
+ * ...of a component that implements {@link MFXResizable}.
+ * <p></p>
+ * An internal class, {@link Defaults}, offers a series of default layout functions that replicate the JavaFX's
+ * algorithm.
+ * <p></p>
+ * A good usage of this is to start from the defaults and then extend them using the {@link Function#andThen(Function)}
+ * feature.
+ * <p></p>
+ * When creating a new {@code LayoutStrategy} object through no-arg constructor or {@link #defaultStrategy()}, the six
+ * functions are set to the one in {@link Defaults} (JavaFX algorithm).
+ */
+public class LayoutStrategy {
+	//================================================================================
+	// Properties
+	//================================================================================
+	private Function<MFXResizable, Double> minWidthFunction = Defaults.DEF_MIN_WIDTH_FUNCTION;
+	private Function<MFXResizable, Double> minHeightFunction = Defaults.DEF_MIN_HEIGHT_FUNCTION;
+	private Function<MFXResizable, Double> prefWidthFunction = Defaults.DEF_PREF_WIDTH_FUNCTION;
+	private Function<MFXResizable, Double> prefHeightFunction = Defaults.DEF_PREF_HEIGHT_FUNCTION;
+	private Function<MFXResizable, Double> maxWidthFunction = Defaults.DEF_MAX_WIDTH_FUNCTION;
+	private Function<MFXResizable, Double> maxHeightFunction = Defaults.DEF_MAX_HEIGHT_FUNCTION;
+
+	//================================================================================
+	// Static Methods
+	//================================================================================
+
+	/**
+	 * @return a new {@code LayoutStrategy} instance that uses JavaFX's algorithm for all sizes
+	 */
+	public static LayoutStrategy defaultStrategy() {
+		return new LayoutStrategy();
+	}
+
+	//================================================================================
+	// Methods
+	//================================================================================
+
+	/**
+	 * Computes the minimum width for the specified {@link MFXResizable} by invoking the set {@link #getMinWidthFunction()}.
+	 *
+	 * @return the computed width or 0.0 if the function is null
+	 */
+	public double computeMinWidth(MFXResizable resizable) {
+		return minWidthFunction != null ? minWidthFunction.apply(resizable) : 0.0;
+	}
+
+	/**
+	 * Computes the minimum height for the specified {@link MFXResizable} by invoking the set {@link #getMinHeightFunction()}.
+	 *
+	 * @return the computed height or 0.0 if the function is null
+	 */
+	public double computeMinHeight(MFXResizable resizable) {
+		return minHeightFunction != null ? minHeightFunction.apply(resizable) : 0.0;
+	}
+
+	/**
+	 * Computes the preferred width for the specified {@link MFXResizable} by invoking the set {@link #getPrefWidthFunction()}.
+	 *
+	 * @return the computed width or 0.0 if the function is null
+	 */
+	public double computePrefWidth(MFXResizable resizable) {
+		return prefWidthFunction != null ? prefWidthFunction.apply(resizable) : 0.0;
+	}
+
+	/**
+	 * Computes the preferred height for the specified {@link MFXResizable} by invoking the set {@link #getPrefHeightFunction()}.
+	 *
+	 * @return the computed height or 0.0 if the function is null
+	 */
+	public double computePrefHeight(MFXResizable resizable) {
+		return prefHeightFunction != null ? prefHeightFunction.apply(resizable) : 0.0;
+	}
+
+	/**
+	 * Computes the maximum width for the specified {@link MFXResizable} by invoking the set {@link #getMaxWidthFunction()}.
+	 *
+	 * @return the computed width or 0.0 if the function is null
+	 */
+	public double computeMaxWidth(MFXResizable resizable) {
+		return maxWidthFunction != null ? maxWidthFunction.apply(resizable) : 0.0;
+	}
+
+	/**
+	 * Computes the maximum height for the specified {@link MFXResizable} by invoking the set {@link #getMaxHeightFunction()}.
+	 *
+	 * @return the computed height or 0.0 if the function is null
+	 */
+	public double computeMaxHeight(MFXResizable resizable) {
+		return maxHeightFunction != null ? maxHeightFunction.apply(resizable) : 0.0;
+	}
+
+	/**
+	 * Fluent API to set this {@code LayoutStrategy} on the given {@link MFXResizable}.
+	 *
+	 * @see MFXResizable#setLayoutStrategy(LayoutStrategy)
+	 */
+	public LayoutStrategy setOn(MFXResizable resizable) {
+		resizable.setLayoutStrategy(this);
+		return this;
+	}
+
+	//================================================================================
+	// Overridden Methods
+	//================================================================================
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		LayoutStrategy that = (LayoutStrategy) o;
+		return Objects.equals(getMinWidthFunction(), that.getMinWidthFunction()) &&
+				Objects.equals(getMinHeightFunction(), that.getMinHeightFunction()) &&
+				Objects.equals(getPrefWidthFunction(), that.getPrefWidthFunction()) &&
+				Objects.equals(getPrefHeightFunction(), that.getPrefHeightFunction()) &&
+				Objects.equals(getMaxWidthFunction(), that.getMaxWidthFunction()) &&
+				Objects.equals(getMaxHeightFunction(), that.getMaxHeightFunction());
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(
+				getMinWidthFunction(), getMinHeightFunction(),
+				getPrefWidthFunction(), getPrefHeightFunction(),
+				getMaxWidthFunction(), getMaxHeightFunction()
+		);
+	}
+
+	//================================================================================
+	// Getters/Setters
+	//================================================================================
+	public Function<MFXResizable, Double> getMinWidthFunction() {
+		return minWidthFunction;
+	}
+
+	public LayoutStrategy setMinWidthFunction(Function<MFXResizable, Double> minWidthFunction) {
+		this.minWidthFunction = minWidthFunction;
+		return this;
+	}
+
+	public Function<MFXResizable, Double> getMinHeightFunction() {
+		return minHeightFunction;
+	}
+
+	public LayoutStrategy setMinHeightFunction(Function<MFXResizable, Double> minHeightFunction) {
+		this.minHeightFunction = minHeightFunction;
+		return this;
+	}
+
+	public Function<MFXResizable, Double> getPrefWidthFunction() {
+		return prefWidthFunction;
+	}
+
+	public LayoutStrategy setPrefWidthFunction(Function<MFXResizable, Double> prefWidthFunction) {
+		this.prefWidthFunction = prefWidthFunction;
+		return this;
+	}
+
+	public Function<MFXResizable, Double> getPrefHeightFunction() {
+		return prefHeightFunction;
+	}
+
+	public LayoutStrategy setPrefHeightFunction(Function<MFXResizable, Double> prefHeightFunction) {
+		this.prefHeightFunction = prefHeightFunction;
+		return this;
+	}
+
+	public Function<MFXResizable, Double> getMaxWidthFunction() {
+		return maxWidthFunction;
+	}
+
+	public LayoutStrategy setMaxWidthFunction(Function<MFXResizable, Double> maxWidthFunction) {
+		this.maxWidthFunction = maxWidthFunction;
+		return this;
+	}
+
+	public Function<MFXResizable, Double> getMaxHeightFunction() {
+		return maxHeightFunction;
+	}
+
+	public LayoutStrategy setMaxHeightFunction(Function<MFXResizable, Double> maxHeightFunction) {
+		this.maxHeightFunction = maxHeightFunction;
+		return this;
+	}
+
+	public static class Defaults {
+		public static final Function<MFXResizable, Double> DEF_MIN_WIDTH_FUNCTION;
+		public static final Function<MFXResizable, Double> DEF_MIN_HEIGHT_FUNCTION;
+		public static final Function<MFXResizable, Double> DEF_PREF_WIDTH_FUNCTION;
+		public static final Function<MFXResizable, Double> DEF_PREF_HEIGHT_FUNCTION;
+		public static final Function<MFXResizable, Double> DEF_MAX_WIDTH_FUNCTION;
+		public static final Function<MFXResizable, Double> DEF_MAX_HEIGHT_FUNCTION;
+
+		static {
+			DEF_MIN_WIDTH_FUNCTION = createFunction(
+					MFXResizable::getHeight,
+					(s, h, i) -> s.computeMinWidth(h, i.getTop(), i.getRight(), i.getBottom(), i.getLeft())
+			);
+			DEF_MIN_HEIGHT_FUNCTION = createFunction(
+					MFXResizable::getWidth,
+					(s, w, i) -> s.computeMinHeight(w, i.getTop(), i.getRight(), i.getBottom(), i.getLeft())
+			);
+			DEF_PREF_WIDTH_FUNCTION = createFunction(
+					MFXResizable::getHeight,
+					(s, h, i) -> s.computePrefWidth(h, i.getTop(), i.getRight(), i.getBottom(), i.getLeft())
+			);
+			DEF_PREF_HEIGHT_FUNCTION = createFunction(
+					MFXResizable::getWidth,
+					(s, w, i) -> s.computePrefHeight(w, i.getTop(), i.getRight(), i.getBottom(), i.getLeft())
+			);
+			DEF_MAX_WIDTH_FUNCTION = createFunction(
+					MFXResizable::getHeight,
+					(s, h, i) -> s.computeMaxWidth(h, i.getTop(), i.getRight(), i.getBottom(), i.getLeft())
+			);
+			DEF_MAX_HEIGHT_FUNCTION = createFunction(
+					MFXResizable::getWidth,
+					(s, w, i) -> s.computeMaxHeight(w, i.getTop(), i.getRight(), i.getBottom(), i.getLeft())
+			);
+		}
+
+		private static Function<MFXResizable, Double> createFunction(
+				Function<MFXResizable, Double> otherSize,
+				TriFunction<MFXSkinBase<?, ?>, Double, Insets, Double> fn) {
+			return c -> {
+				Skin<?> skin = c.getSkin();
+				if (!(skin instanceof MFXSkinBase)) return 0.0;
+				Double oSize = otherSize.apply(c);
+				return fn.apply(((MFXSkinBase<?, ?>) skin), oSize, getSnappedInsets(c));
+			};
+		}
+
+		private static Insets getSnappedInsets(MFXResizable res) {
+			return InsetsBuilder.of(
+					res.snappedTopInset(),
+					res.snappedRightInset(),
+					res.snappedBottomInset(),
+					res.snappedLeftInset()
+			);
+		}
+	}
+}

+ 117 - 0
modules/components/src/main/java/io/github/palexdev/mfxcomponents/layout/MFXResizable.java

@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 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 distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package io.github.palexdev.mfxcomponents.layout;
+
+import io.github.palexdev.mfxcomponents.controls.base.MFXControl;
+import io.github.palexdev.mfxcomponents.controls.base.MFXLabeled;
+import io.github.palexdev.mfxcomponents.controls.base.MFXSkinBase;
+import javafx.beans.property.ObjectProperty;
+import javafx.scene.control.Control;
+import javafx.scene.control.Skin;
+
+/**
+ * This API allows MaterialFX components, descendants of {@link MFXControl} and {@link MFXLabeled}, which also use the
+ * new base skin {@link MFXSkinBase}, to define a quick and easy way to change their layout strategy through a property.
+ * <p>
+ * The pro of such API is that a user doesn't have to necessarily create a custom skin or override the methods inline
+ * to change the component sizing, it's enough to define a new strategy, which is more elegant indeed.
+ * <p></p>
+ * Some methods present in this API are already defined by JavaFX controls, but forces them to be {@code public} since
+ * they may be needed by the {@link LayoutStrategy} when computing the sizes.
+ */
+public interface MFXResizable {
+
+	/**
+	 * @return the instance of the current {@link LayoutStrategy}
+	 */
+	LayoutStrategy getLayoutStrategy();
+
+	/**
+	 * Specifies the {@link LayoutStrategy} used by the component to compute its sizes.
+	 */
+	ObjectProperty<LayoutStrategy> layoutStrategyProperty();
+
+	/**
+	 * Sets the {@link LayoutStrategy} used by the component to compute its sizes.
+	 */
+	void setLayoutStrategy(LayoutStrategy strategy);
+
+	/**
+	 * By default, does nothing.
+	 * <p>
+	 * Implementations of this should perform the actions needed to 'activate' the new layout strategy,
+	 * for example a component may invoke a layout request through {@link Control#requestLayout()}.
+	 */
+	default void onLayoutStrategyChanged() {
+	}
+
+	/**
+	 * By defaults, returns {@link LayoutStrategy#defaultStrategy()}.
+	 * <p></p>
+	 * Components may override this to specify what is their default layout strategy.
+	 */
+	default LayoutStrategy defaultLayoutStrategy() {
+		return LayoutStrategy.defaultStrategy();
+	}
+
+	/**
+	 * Calls {@link #setLayoutStrategy(LayoutStrategy)} with {@link #defaultLayoutStrategy()} as parameter.
+	 * In other words, resets the component's {@link LayoutStrategy} to its default one.
+	 */
+	default void setDefaultLayoutStrategy() {
+		setLayoutStrategy(defaultLayoutStrategy());
+	}
+
+	/**
+	 * Calls {@link #setLayoutStrategy(LayoutStrategy)} with {@link LayoutStrategy#defaultStrategy()} as parameter.
+	 * In other words, resets the component's {@link LayoutStrategy} to the one used by JavaFX.
+	 * <p>
+	 * This may be useful in case {@link #defaultLayoutStrategy()} has been overridden.
+	 */
+	default void setJavaFXLayoutStrategy() {
+		setLayoutStrategy(LayoutStrategy.defaultStrategy());
+	}
+
+	double getWidth();
+
+	double getHeight();
+
+	Skin<?> getSkin();
+
+	double snappedTopInset();
+
+	double snappedRightInset();
+
+	double snappedBottomInset();
+
+	double snappedLeftInset();
+
+	double computeMinWidth(double height);
+
+	double computeMinHeight(double width);
+
+	double computePrefWidth(double height);
+
+	double computePrefHeight(double width);
+
+	double computeMaxWidth(double height);
+
+	double computeMaxHeight(double width);
+
+}

+ 4 - 4
modules/components/src/main/java/io/github/palexdev/mfxcomponents/skins/MFXButtonSkin.java

@@ -112,7 +112,7 @@ public class MFXButtonSkin extends MFXSkinBase<MFXButton, MFXButtonBehavior> {
 	}
 	}
 
 
 	@Override
 	@Override
-	protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+	public double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 		MFXButton button = getSkinnable();
 		MFXButton button = getSkinnable();
 		double insets = leftInset + rightInset;
 		double insets = leftInset + rightInset;
 		double tW = TextUtils.computeTextWidth(label.getFont(), label.getText());
 		double tW = TextUtils.computeTextWidth(label.getFont(), label.getText());
@@ -122,7 +122,7 @@ public class MFXButtonSkin extends MFXSkinBase<MFXButton, MFXButtonBehavior> {
 	}
 	}
 
 
 	@Override
 	@Override
-	protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+	public double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 		MFXButton button = getSkinnable();
 		MFXButton button = getSkinnable();
 		double insets = topInset + bottomInset;
 		double insets = topInset + bottomInset;
 		double tH = TextUtils.computeTextHeight(label.getFont(), label.getText());
 		double tH = TextUtils.computeTextHeight(label.getFont(), label.getText());
@@ -131,12 +131,12 @@ public class MFXButtonSkin extends MFXSkinBase<MFXButton, MFXButtonBehavior> {
 	}
 	}
 
 
 	@Override
 	@Override
-	protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+	public double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 		return getSkinnable().prefWidth(height);
 		return getSkinnable().prefWidth(height);
 	}
 	}
 
 
 	@Override
 	@Override
-	protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+	public double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 		return getSkinnable().prefHeight(width);
 		return getSkinnable().prefHeight(width);
 	}
 	}
 
 

+ 3 - 4
modules/components/src/main/java/io/github/palexdev/mfxcomponents/skins/MFXFabSkin.java

@@ -80,19 +80,18 @@ public class MFXFabSkin extends MFXButtonSkin {
 	}
 	}
 
 
 	@Override
 	@Override
-	protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+	public double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 		return Region.USE_COMPUTED_SIZE;
 		return Region.USE_COMPUTED_SIZE;
 	}
 	}
 
 
 	@Override
 	@Override
-	protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+	public double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 		MFXFabBase fab = getFab();
 		MFXFabBase fab = getFab();
 		MFXFontIcon icon = fab.getIcon();
 		MFXFontIcon icon = fab.getIcon();
 		double iW = (icon != null) ? icon.getLayoutBounds().getWidth() : 0.0;
 		double iW = (icon != null) ? icon.getLayoutBounds().getWidth() : 0.0;
-		double w = fab.isExtended() ?
+		return fab.isExtended() ?
 				leftInset + iW + TextUtils.computeTextWidth(fab.getFont(), fab.getText()) + rightInset :
 				leftInset + iW + TextUtils.computeTextWidth(fab.getFont(), fab.getText()) + rightInset :
 				leftInset + iW + rightInset;
 				leftInset + iW + rightInset;
-		return Math.max(w, fab.getInitWidth());
 	}
 	}
 
 
 	@Override
 	@Override

+ 3 - 0
modules/components/src/main/java/module-info.java

@@ -14,6 +14,9 @@ module mfx.components {
 	exports io.github.palexdev.mfxcomponents.controls.buttons;
 	exports io.github.palexdev.mfxcomponents.controls.buttons;
 	exports io.github.palexdev.mfxcomponents.controls.fab;
 	exports io.github.palexdev.mfxcomponents.controls.fab;
 
 
+	// Layout
+	exports io.github.palexdev.mfxcomponents.layout;
+
 	// Skins
 	// Skins
 	exports io.github.palexdev.mfxcomponents.skins;
 	exports io.github.palexdev.mfxcomponents.skins;
 
 

+ 2 - 2
modules/components/src/test/java/interactive/TestInitSize.java

@@ -22,7 +22,7 @@ import io.github.palexdev.mfxcomponents.controls.buttons.MFXButton;
 import io.github.palexdev.mfxcomponents.controls.buttons.MFXFilledButton;
 import io.github.palexdev.mfxcomponents.controls.buttons.MFXFilledButton;
 import io.github.palexdev.mfxcomponents.controls.fab.MFXFab;
 import io.github.palexdev.mfxcomponents.controls.fab.MFXFab;
 import io.github.palexdev.mfxcomponents.theming.enums.FABVariants;
 import io.github.palexdev.mfxcomponents.theming.enums.FABVariants;
-import io.github.palexdev.mfxresources.MFXResources;
+import io.github.palexdev.mfxcomponents.theming.enums.MFXThemeManager;
 import javafx.scene.Scene;
 import javafx.scene.Scene;
 import javafx.scene.layout.StackPane;
 import javafx.scene.layout.StackPane;
 import javafx.stage.Stage;
 import javafx.stage.Stage;
@@ -114,7 +114,7 @@ public class TestInitSize {
 	private StackPane setupStage() {
 	private StackPane setupStage() {
 		try {
 		try {
 			Scene scene = new Scene(new StackPane(), 200, 200);
 			Scene scene = new Scene(new StackPane(), 200, 200);
-			scene.getStylesheets().add(MFXResources.load("sass/md3/mfx-light.css")); // TODO change once Theme Manager API is done
+			MFXThemeManager.LIGHT.addOn(scene);
 			FxToolkit.setupStage(s -> s.setScene(scene));
 			FxToolkit.setupStage(s -> s.setScene(scene));
 		} catch (TimeoutException e) {
 		} catch (TimeoutException e) {
 			throw new RuntimeException(e);
 			throw new RuntimeException(e);

+ 450 - 0
modules/components/src/test/java/interactive/TestLayoutStrategies.java

@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2023 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 distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with MaterialFX. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package interactive;
+
+import io.github.palexdev.mfxcomponents.controls.base.MFXSkinBase;
+import io.github.palexdev.mfxcomponents.controls.fab.MFXFab;
+import io.github.palexdev.mfxcomponents.layout.LayoutStrategy;
+import io.github.palexdev.mfxcomponents.layout.LayoutStrategy.Defaults;
+import io.github.palexdev.mfxcomponents.layout.MFXResizable;
+import io.github.palexdev.mfxcomponents.theming.enums.MFXThemeManager;
+import io.github.palexdev.mfxcore.base.properties.NodeProperty;
+import io.github.palexdev.mfxcore.behavior.BehaviorBase;
+import io.github.palexdev.mfxcore.observables.When;
+import io.github.palexdev.mfxresources.fonts.MFXFontIcon;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.Control;
+import javafx.scene.control.Skin;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.testfx.api.FxRobot;
+import org.testfx.api.FxToolkit;
+import org.testfx.framework.junit5.ApplicationExtension;
+import org.testfx.framework.junit5.Start;
+
+import java.util.concurrent.TimeoutException;
+
+import static io.github.palexdev.mfxresources.fonts.IconsProviders.FONTAWESOME_SOLID;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+@ExtendWith(ApplicationExtension.class)
+public class TestLayoutStrategies {
+	private static Stage stage;
+
+	@Start
+	void start(Stage stage) {
+		TestLayoutStrategies.stage = stage;
+		stage.show();
+	}
+
+	@Test
+	void testDefaultStrategy(FxRobot robot) {
+		StackPane root = setupStage();
+		ResControl rc = new ResControl();
+		robot.interact(() -> root.getChildren().setAll(rc));
+
+		assertEquals(root.getWidth(), rc.getWidth());
+		assertEquals(root.getHeight(), rc.getHeight());
+	}
+
+	@Test
+	void testStrategyMin(FxRobot robot) {
+		StackPane root = setupStage();
+		ResControl rc = new ResControl();
+		robot.interact(() -> root.getChildren().setAll(rc));
+
+		// Exactly as above because in a StackPane...
+		// Let's set the max to use the pref...
+		assertEquals(root.getWidth(), rc.getWidth());
+		assertEquals(root.getHeight(), rc.getHeight());
+
+		robot.interact(() -> rc.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE));
+		assertEquals(0, Math.abs(rc.getWidth()));
+		assertEquals(0, Math.abs(rc.getWidth()));
+		// 0 because there's no content
+
+		robot.interact(() -> rc.setContent(new MFXFontIcon("fas-circle", 24)));
+		assertEquals(24, rc.getWidth());
+		assertEquals(24, rc.getHeight());
+
+		// Finally, let's test the strategy
+		LayoutStrategy strategy = LayoutStrategy.defaultStrategy()
+				.setMinWidthFunction(Defaults.DEF_MIN_WIDTH_FUNCTION.andThen(r -> Math.max(r, 48)))
+				.setMinHeightFunction(Defaults.DEF_MIN_HEIGHT_FUNCTION.andThen(r -> Math.max(r, 48)));
+		robot.interact(() -> {
+			rc.setLayoutStrategy(strategy);
+			rc.requestLayout();
+		});
+		assertEquals(48, rc.getWidth());
+		assertEquals(48, rc.getHeight());
+	}
+
+	@Test
+	void testStrategyPref(FxRobot robot) {
+		StackPane root = setupStage();
+		ResControl rc = new ResControl();
+		rc.setContent(new MFXFontIcon("fas-circle", 24));
+		rc.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+		robot.interact(() -> root.getChildren().setAll(rc));
+
+		assertEquals(24.0, rc.getWidth());
+		assertEquals(24.0, rc.getHeight());
+
+		LayoutStrategy strategy = LayoutStrategy.defaultStrategy()
+				.setPrefWidthFunction(Defaults.DEF_PREF_WIDTH_FUNCTION.andThen(r -> Math.max(r, 64.0)))
+				.setPrefHeightFunction(Defaults.DEF_PREF_HEIGHT_FUNCTION.andThen(r -> Math.max(r, 64.0)));
+		robot.interact(() -> {
+			rc.setLayoutStrategy(strategy);
+			rc.requestLayout();
+		});
+
+		assertEquals(64.0, rc.getWidth());
+		assertEquals(64.0, rc.getHeight());
+
+		robot.interact(() -> rc.setMaxSize(30, 30));
+		assertEquals(30.0, rc.getWidth());
+		assertEquals(30.0, rc.getHeight());
+
+		robot.interact(() -> rc.setMinSize(45, 45));
+		assertEquals(45.0, rc.getWidth());
+		assertEquals(45.0, rc.getHeight());
+	}
+
+	@Test
+	void testStrategyMax(FxRobot robot) {
+		StackPane root = setupStage();
+		ResControl rc = new ResControl();
+		rc.setContent(new MFXFontIcon("fas-circle", 40.0));
+		robot.interact(() -> root.getChildren().setAll(rc));
+
+		LayoutStrategy strategy = LayoutStrategy.defaultStrategy()
+				.setMaxWidthFunction(Defaults.DEF_MAX_WIDTH_FUNCTION.andThen(r -> Math.min(r, 50.0)))
+				.setMaxHeightFunction(Defaults.DEF_MAX_HEIGHT_FUNCTION.andThen(r -> Math.min(r, 50.0)));
+		robot.interact(() -> {
+			rc.setLayoutStrategy(strategy);
+			rc.requestLayout();
+		});
+
+		// Notice the difference between the other tests, the strategy is using Math.min
+		assertEquals(50.0, rc.getWidth());
+		assertEquals(50.0, rc.getHeight());
+
+		robot.interact(() -> rc.setPrefSize(100, 100));
+		assertEquals(50.0, rc.getWidth());
+		assertEquals(50.0, rc.getHeight());
+
+		// Remember, min sizes always prevail on max
+		robot.interact(() -> rc.setMinSize(70, 70));
+		assertEquals(70.0, rc.getWidth());
+		assertEquals(70.0, rc.getHeight());
+	}
+
+	@Test
+	void testFABDefaultStrategy(FxRobot robot) {
+		StackPane root = setupStage();
+		MFXFab fab = MFXFab.extended();
+		fab.setIcon(FONTAWESOME_SOLID.randomIcon());
+		robot.interact(() -> {
+			fab.setJavaFXLayoutStrategy();
+			root.getChildren().setAll(fab);
+		});
+
+		// Default strategy, same as JavaFX
+		// Do not count insets for the sanity of the below 'comparison'
+		assertEquals(fab.getIcon().getLayoutBounds().getWidth(), fab.getWidth() - getLRInsets(fab));
+		assertEquals(fab.getIcon().getLayoutBounds().getHeight(), fab.getHeight() - getTBInsets(fab));
+
+		// Double check with a similar container setup...
+		StackPane sp = new StackPane(new MFXFontIcon(fab.getIcon().getDescription(), fab.getIcon().getSize()));
+		sp.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+		robot.interact(() -> root.getChildren().add(sp));
+		assertEquals(fab.getIcon().getLayoutBounds().getWidth(), sp.getWidth());
+		assertEquals(fab.getIcon().getLayoutBounds().getHeight(), sp.getHeight());
+	}
+
+	@Test
+	void testFABStrategyMin(FxRobot robot) {
+		StackPane root = setupStage();
+		MFXFab fab = MFXFab.extended();
+		fab.setIcon(new MFXFontIcon("fas-circle"));
+		robot.interact(() -> root.getChildren().setAll(fab));
+
+		// Define a strategy with minimum sizes
+		LayoutStrategy strategy = LayoutStrategy.defaultStrategy()
+				.setMinWidthFunction(Defaults.DEF_MIN_WIDTH_FUNCTION.andThen(r -> Math.max(r, 64.0)))
+				.setMinHeightFunction(Defaults.DEF_MIN_HEIGHT_FUNCTION.andThen(r -> Math.max(r, 64.0)));
+		robot.interact(() -> fab.setLayoutStrategy(strategy));
+
+		assertEquals(64.0, fab.getWidth());
+		assertEquals(64.0, fab.getHeight());
+
+		// What happens if I set the pref?
+		robot.interact(() -> fab.setPrefSize(100, 100));
+		assertEquals(100.0, fab.getWidth());
+		assertEquals(100.0, fab.getHeight());
+
+		// What happens if I also set the max?
+		robot.interact(() -> fab.setMaxSize(40, 40));
+		assertEquals(64.0, fab.getWidth());
+		assertEquals(64.0, fab.getHeight());
+		// Still 64, does this happen with JavaFX nodes too?
+
+		StackPane sp = new StackPane() {
+			@Override
+			protected double computeMinWidth(double height) {
+				return Math.max(super.computeMinWidth(height), 64.0);
+			}
+
+			@Override
+			protected double computeMinHeight(double width) {
+				return Math.max(super.computeMinHeight(width), 64.0);
+			}
+		};
+		sp.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
+		robot.interact(() -> root.getChildren().setAll(sp));
+
+		assertEquals(64.0, sp.getWidth());
+		assertEquals(64.0, sp.getHeight());
+
+		robot.interact(() -> sp.setPrefSize(100, 100));
+		assertEquals(100.0, sp.getWidth());
+		assertEquals(100.0, sp.getHeight());
+
+		robot.interact(() -> sp.setMaxSize(40, 40));
+		assertEquals(64.0, sp.getWidth());
+		assertEquals(64.0, sp.getHeight());
+
+		/*
+		 * Yes, so, let me get this straight. Layout:
+		 * Min > Pref > Max
+		 * Which means that even if Max is lesser than Min, the latter will prevail
+		 * But first the minimum between pref and max is computed
+		 *
+		 * The formula seems to be:
+		 * 1) Maximum between Pref and Min
+		 * 2) Maximum between Min and Max
+		 * 3) Minimum between these two results
+		 */
+	}
+
+	@Test
+	void testFABStrategyPref(FxRobot robot) {
+		StackPane root = setupStage();
+		MFXFab fab = MFXFab.extended();
+		fab.setIcon(new MFXFontIcon("fas-circle"));
+		robot.interact(() -> root.getChildren().setAll(fab));
+
+		/*
+		 * At this point the layout strategy is still not set and the extend() method
+		 * in the FAB behavior is called causing the pref width to be overridden through
+		 * the setPrefWidth method
+		 */
+		assertNotEquals(64.0, fab.getWidth());
+
+		// Define a strategy with pref sizes
+		LayoutStrategy strategy = LayoutStrategy.defaultStrategy()
+				.setPrefWidthFunction(Defaults.DEF_PREF_WIDTH_FUNCTION.andThen(r -> Math.max(r, 64.0)))
+				.setPrefHeightFunction(Defaults.DEF_PREF_HEIGHT_FUNCTION.andThen(r -> Math.max(r, 64.0)));
+		robot.interact(() -> fab.setLayoutStrategy(strategy));
+
+		/*
+		 * This is different from using setPrefSize(...)!
+		 * And different from setting a minimum size strategy, so...
+		 */
+		assertEquals(64.0, fab.getWidth());
+		assertEquals(64.0, fab.getHeight());
+
+		// What happens if I set a max?
+		robot.interact(() -> fab.setMaxSize(30, 30));
+		assertEquals(30.0, fab.getWidth());
+		assertEquals(30.0, fab.getHeight());
+
+		// What happens if I set a min?
+		robot.interact(() -> fab.setMinSize(45, 45));
+		assertEquals(45.0, fab.getWidth());
+		assertEquals(45.0, fab.getHeight());
+	}
+
+	@Test
+	void testFABStrategyMax(FxRobot robot) {
+		StackPane root = setupStage();
+		MFXFab fab = MFXFab.extended();
+		fab.setIcon(new MFXFontIcon("fas-circle"));
+		robot.interact(() -> root.getChildren().setAll(fab));
+
+		// Define a strategy with max sizes
+		LayoutStrategy strategy = LayoutStrategy.defaultStrategy()
+				.setMaxWidthFunction(Defaults.DEF_MAX_WIDTH_FUNCTION.andThen(r -> Math.max(r, 64.0)))
+				.setMaxHeightFunction(Defaults.DEF_MAX_HEIGHT_FUNCTION.andThen(r -> Math.max(r, 64.0)));
+		robot.interact(() -> fab.setLayoutStrategy(strategy));
+
+		/*
+		 * Here we have a special case. We set a strategy that constraints the node to be at max 64px
+		 * But remember that the node is inside a StackPane so it will occupy all the space possible within its constraints
+		 * So we have to checks to do: 1) the pref size 2) the actual size
+		 *
+		 * We don't check for the prefHeight because only the pref width is overridden by the
+		 * extend() method in the FAB behavior
+		 */
+		assertEquals(60.0, fab.getPrefWidth());
+		assertEquals(64.0, fab.getWidth());
+		assertEquals(64.0, fab.getHeight());
+
+		// What happens if I set pref?
+		robot.interact(() -> fab.setPrefSize(100, 100));
+		assertEquals(100.0, fab.getWidth());
+		assertEquals(100.0, fab.getHeight());
+		/*
+		 * Why 100?
+		 * Buttons by default use the prefSize for the computation of the maxSize methods
+		 * This means that the above layout strategy is ignored because at the end of the
+		 * algorithm the Math.min operation is evaluated between the same value (comes from pref size)
+		 * Let's see if the strategy changes what happens...
+		 */
+
+		LayoutStrategy newStrategy = LayoutStrategy.defaultStrategy()
+				.setMaxWidthFunction(r -> 64.0)
+				.setMaxHeightFunction(r -> 64.0);
+		robot.interact(() -> fab.setLayoutStrategy(newStrategy));
+		assertEquals(64.0, fab.getWidth());
+		assertEquals(64.0, fab.getHeight());
+		// Exactly as expected...
+	}
+
+	private double getLRInsets(Region region) {
+		return region.snappedLeftInset() + region.snappedRightInset();
+	}
+
+	private double getTBInsets(Region region) {
+		return region.snappedTopInset() + region.snappedBottomInset();
+	}
+
+	private StackPane setupStage() {
+		try {
+			Scene scene = new Scene(new StackPane(), 200, 200);
+			MFXThemeManager.LIGHT.addOn(scene);
+			FxToolkit.setupStage(s -> s.setScene(scene));
+		} catch (TimeoutException e) {
+			throw new RuntimeException(e);
+		}
+		return (StackPane) stage.getScene().getRoot();
+	}
+
+	private static class ResControl extends Control implements MFXResizable {
+		private final ObjectProperty<LayoutStrategy> layoutStrategy = new SimpleObjectProperty<>(defaultLayoutStrategy());
+		private final NodeProperty content = new NodeProperty();
+
+		@Override
+		public LayoutStrategy getLayoutStrategy() {
+			return layoutStrategy.get();
+		}
+
+		@Override
+		public ObjectProperty<LayoutStrategy> layoutStrategyProperty() {
+			return layoutStrategy;
+		}
+
+		@Override
+		public void setLayoutStrategy(LayoutStrategy strategy) {
+			layoutStrategy.set(strategy);
+		}
+
+		@Override
+		public double computeMinWidth(double height) {
+			return getLayoutStrategy().computeMinWidth(this);
+		}
+
+		@Override
+		public double computeMinHeight(double width) {
+			return getLayoutStrategy().computeMinWidth(this);
+		}
+
+		@Override
+		public double computePrefWidth(double height) {
+			return getLayoutStrategy().computePrefWidth(this);
+		}
+
+		@Override
+		public double computePrefHeight(double width) {
+			return getLayoutStrategy().computePrefHeight(this);
+		}
+
+		@Override
+		public double computeMaxWidth(double height) {
+			return getLayoutStrategy().computeMaxWidth(this);
+		}
+
+		@Override
+		public double computeMaxHeight(double width) {
+			return getLayoutStrategy().computeMaxHeight(this);
+		}
+
+		public Node getContent() {
+			return content.get();
+		}
+
+		public NodeProperty contentProperty() {
+			return content;
+		}
+
+		public void setContent(Node content) {
+			this.content.set(content);
+		}
+
+		@SuppressWarnings({"rawtypes", "unchecked"})
+		@Override
+		protected Skin<?> createDefaultSkin() {
+			return new MFXSkinBase(this) {
+				@Override
+				protected void initBehavior(BehaviorBase behavior) {
+				}
+
+				final Pane pane = new Pane();
+
+				{
+					When.onChanged(contentProperty())
+							.then((o, n) -> {
+								if (n == null) {
+									pane.getChildren().clear();
+									return;
+								}
+								pane.getChildren().setAll(n);
+							})
+							.executeNow()
+							.listen();
+					getChildren().add(pane);
+				}
+
+				@Override
+				public void dispose() {
+					When.disposeFor(contentProperty());
+					super.dispose();
+				}
+			};
+		}
+	}
+}