io.github.palexdev.mfxcomponents.layout.LayoutStrategy Maven / Gradle / Ivy
Show all versions of materialfx-all Show documentation
/*
* Copyright (C) 2023 Parisi Alessandro - [email protected]
* 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 .
*/
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:
* 1) The minimum width
*
2) The minimum height
*
3) The preferred width
*
4) The preferred height
*
5) The maximum width
*
6) The maximum height
*
* ...of a component that implements {@link MFXResizable}.
*
* An internal class, {@link Defaults}, offers a series of default layout functions that replicate the JavaFX's
* algorithm.
*
* A good usage of this is to start from the defaults and then extend them using the {@link Function#andThen(Function)}
* feature.
*
* 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).
*
*
* In general the JavaFX's layout algorithm works like this:
* - Given the min, pref and max widths/heights for a Node
*
1) Extracts the maximum between pref and min
*
2) Extracts the maximum between min and max
*
3) Returns the minimum between the two computed values
*/
public class LayoutStrategy {
//================================================================================
// Properties
//================================================================================
private Function minWidthFunction = Defaults.DEF_MIN_WIDTH_FUNCTION;
private Function minHeightFunction = Defaults.DEF_MIN_HEIGHT_FUNCTION;
private Function prefWidthFunction = Defaults.DEF_PREF_WIDTH_FUNCTION;
private Function prefHeightFunction = Defaults.DEF_PREF_HEIGHT_FUNCTION;
private Function maxWidthFunction = Defaults.DEF_MAX_WIDTH_FUNCTION;
private Function 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 getMinWidthFunction() {
return minWidthFunction;
}
public LayoutStrategy setMinWidthFunction(Function minWidthFunction) {
this.minWidthFunction = minWidthFunction;
return this;
}
public Function getMinHeightFunction() {
return minHeightFunction;
}
public LayoutStrategy setMinHeightFunction(Function minHeightFunction) {
this.minHeightFunction = minHeightFunction;
return this;
}
public Function getPrefWidthFunction() {
return prefWidthFunction;
}
public LayoutStrategy setPrefWidthFunction(Function prefWidthFunction) {
this.prefWidthFunction = prefWidthFunction;
return this;
}
public Function getPrefHeightFunction() {
return prefHeightFunction;
}
public LayoutStrategy setPrefHeightFunction(Function prefHeightFunction) {
this.prefHeightFunction = prefHeightFunction;
return this;
}
public Function getMaxWidthFunction() {
return maxWidthFunction;
}
public LayoutStrategy setMaxWidthFunction(Function maxWidthFunction) {
this.maxWidthFunction = maxWidthFunction;
return this;
}
public Function getMaxHeightFunction() {
return maxHeightFunction;
}
public LayoutStrategy setMaxHeightFunction(Function maxHeightFunction) {
this.maxHeightFunction = maxHeightFunction;
return this;
}
public static class Defaults {
public static final Function DEF_MIN_WIDTH_FUNCTION;
public static final Function DEF_MIN_HEIGHT_FUNCTION;
public static final Function DEF_PREF_WIDTH_FUNCTION;
public static final Function DEF_PREF_HEIGHT_FUNCTION;
public static final Function DEF_MAX_WIDTH_FUNCTION;
public static final Function 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 createFunction(
Function otherSize,
TriFunction, 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()
);
}
}
}