io.github.palexdev.mfxcomponents.theming.base.WithVariants 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.theming.base;
import io.github.palexdev.mfxcomponents.controls.base.MFXControl;
import io.github.palexdev.mfxcomponents.controls.base.MFXLabeled;
import io.github.palexdev.mfxcomponents.controls.base.MFXStyleable;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Simple API for components that have variants of themselves.
*
* A clarification must be made here. Components typically have a number of default style classes, for example
* if they implement {@link MFXStyleable}, and additionally they can have variants that change their properties/features/appearance.
* This API can be used when creating separate classes is not feasible or just useless.
*
* To make this more comprehensible consider this example:
*
* Material Design 3 buttons have five variants: elevated, filled, outlined, text, and tonal filled. Apart from the
* elevated variant that includes the 'shadow elevation mechanism', the other ones do not add/change any feature/behavior.
* So technically they are good candidates for this API, however they are different types of buttons, each with its
* usage/use cases.
*
* Now on the other hand, consider FABs. Floating Action Buttons mainly have two variants: small and large.
* They are the same exact component, what changes is just their sizes, in other words it's not worth defining two
* new classes to represent these variants, it's enough to just define the variants by changing the 'base' style classes.
*
* Another way to see is as follows:
* MFXElevatedButton has '.mfx-button.elevated' as selector, MFXFilledButton has '.mfx-button.filled' (same is true
* for other buttons), notice how the class defining the button style ('.elevated' and '.filled') are different,
* the difference between the two buttons is more explicit.
*
* MFXFab with standard size has '.mfx-button.fab' as selector, and in its small variant has '.mfx-button.fab.small'
* as selector, notice how the difference between the two is way less emphasized, they are the same component even in style,
* just different size.
*
* The cons in using this mechanism is a less 'comfortable' integration with SceneBuilder. Having separate classes
* allows SceneBuilder to detect the variant and add it to the Custom Controls section. This way variants can still be used
* by adding the variant style class in the properties inspector, but the user is required to remember/check which
* are the applicable classes.
*
* Since components may have multiple variants that can even be combined, this API offers two different ways to apply the
* variants:
* - A 'set' method, which is intended to be implemented so that the component style classes are first reset to its base ones
* and only then the variants are added
*
- An 'add' method, which is intended to be implemented so that the variants are added to the style classes already
* set on the component. A recommendation, is to filter the style classes in a {@code LinkedHashSet} to avoid duplicates
* and unwanted behaviors.
*
* Uses the {@link Variant} API.
*/
public interface WithVariants {
/**
* Adds the given variants to the component.
*/
N addVariants(V... variants);
/**
* Clears the component's variants then adds all the provided ones.
*/
N setVariants(V... variants);
/**
* Removes all the given variants from the component.
*/
N removeVariants(V... variants);
/**
* Adds all the given variants to the given control.
*
* Style classes are filtered by a {@link LinkedHashSet} to avoid duplicates while keeping the specified order.
*/
@SafeVarargs
static , V extends Variant> C addVariants(C control, V... variants) {
Set classes = new LinkedHashSet<>(control.getStyleClass());
for (V variant : variants) {
classes.add(variant.variantStyleClass());
}
control.getStyleClass().setAll(classes);
return control;
}
/**
* Replaces the given control' style classes with its base ones, then adds the specified variants.
*
* Style classes are filtered by a {@link LinkedHashSet} to avoid duplicates while keeping the specified order.
*/
@SafeVarargs
static , V extends Variant> C setVariants(C control, V... variants) {
Set classes = new LinkedHashSet<>(control.defaultStyleClasses());
for (V variant : variants) {
classes.add(variant.variantStyleClass());
}
control.getStyleClass().setAll(classes);
return control;
}
/**
* Removes all the given variants from the given control.
*/
@SafeVarargs
static , V extends Variant> C removeVariants(C control, V... variants) {
ObservableList styleClass = control.getStyleClass();
for (V variant : variants) {
styleClass.remove(variant.variantStyleClass());
}
return control;
}
/**
* Adds all the given variants to the given labeled.
*
* Style classes are filtered by a {@link LinkedHashSet} to avoid duplicates while keeping the specified order.
*/
@SafeVarargs
static , V extends Variant> L addVariants(L labeled, V... variants) {
Set classes = new LinkedHashSet<>(labeled.getStyleClass());
for (V variant : variants) {
classes.add(variant.variantStyleClass());
}
labeled.getStyleClass().setAll(classes);
return labeled;
}
/**
* Replaces the given labeled' style classes with its base ones, then adds the specified variants.
*
* Style classes are filtered by a {@link LinkedHashSet} to avoid duplicates while keeping the specified order.
*/
@SafeVarargs
static , V extends Variant> L setVariants(L labeled, V... variants) {
Set classes = new LinkedHashSet<>(labeled.defaultStyleClasses());
for (V variant : variants) {
classes.add(variant.variantStyleClass());
}
labeled.getStyleClass().setAll(classes);
return labeled;
}
/**
* Removes all the given variants from the given labeled.
*/
@SafeVarargs
static , V extends Variant> L removeVariants(L labeled, V... variants) {
ObservableList styleClass = labeled.getStyleClass();
for (V variant : variants) {
styleClass.remove(variant.variantStyleClass());
}
return labeled;
}
}