io.github.palexdev.mfxcore.controls.Control 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.mfxcore.controls;
import io.github.palexdev.mfxcore.base.properties.functional.SupplierProperty;
import io.github.palexdev.mfxcore.behavior.BehaviorBase;
import io.github.palexdev.mfxcore.behavior.WithBehavior;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import java.util.Optional;
import java.util.function.Supplier;
/**
* Base class that can be used as a starting point to implement UI components that perfectly integrate with the new Behavior
* API, see {@link BehaviorBase}.
*
* Extends {@link javafx.scene.control.Control} and implements, {@link WithBehavior}.
*
* The integration with the new Behavior API is achieved by having a specific property, {@link #behaviorProviderProperty()},
* which allows to change at any time the component's behavior. The property automatically handles initialization and disposal
* of behaviors. A reference to the current built behavior object is kept to be retrieved via {@link #getBehavior()}.
*
* Enforces the use of {@link SkinBase} 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()}.
*
* I wanted to avoid adding a listener of the skin property for memory and performance reasons. Every time a skin is created,
* it's needed to pass the current built behavior to the skin for initialization. A good hook place for this call was the
* {@link #createDefaultSkin()} method, but this would make it harder for users to override it because then you would also
* have to take into account the behavior initialization. Having a new method maintains the usual simplicity of setting
* custom skins while avoiding listeners for better performance. For this reason, if you want to change the skin while still
* making use of the behavior API, then the correct way to do it is to use {@link #changeSkin(SkinBase)} as it will
* ensure the initialization of the behavior and overall the correct state of the component.
*
* As a consequence, components that inherit from this do not support the "-fx-skin" CSS property. You'll have to do it in code.
*
* Unfortunately, I cannot prevent users from still using the aforementioned method, but I can guarantee you using that
* will cause issues and undesired behaviors. You have been warned.
*
* @param the behavior type used by the component
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class Control> extends javafx.scene.control.Control implements WithBehavior {
//================================================================================
// Properties
//================================================================================
private B behavior;
private final SupplierProperty behaviorProvider = new SupplierProperty<>() {
@Override
protected void invalidated() {
if (behavior != null) behavior.dispose();
behavior = Optional.ofNullable(get()).map(Supplier::get).orElse(null);
SkinBase skin = ((SkinBase) getSkin());
if (skin != null && behavior != null) skin.initBehavior(behavior);
}
};
//================================================================================
// Abstract Methods
//================================================================================
/**
* Create a new instance of the default skin for this component.
*/
protected abstract SkinBase, ?> buildSkin();
//================================================================================
// Methods
//================================================================================
/**
* Since this is deeply integrated with the new behavior API, and since the {@link #setSkin(Skin)} method cannot
* be overridden, and finally to avoid adding listeners, this is the method to use when you want to change the skin.
*
* Unfortunately, I cannot prevent users from still using the aforementioned method, but I can guarantee you using that
* will cause issues and undesired behaviors. You have been warned.
*/
public void changeSkin(SkinBase, ?> skin) {
if (skin == null)
throw new IllegalArgumentException("The new skin cannot be null!");
if (behavior != null) behavior.dispose();
behavior = getBehaviorProvider().get();
((SkinBase) skin).initBehavior(behavior);
setSkin(skin);
}
/**
* Subclasses can change the actions to perform if the component is being used in SceneBuilder
* by overriding this method. Typically called automatically on components' initialization.
*/
protected void sceneBuilderIntegration() {}
//================================================================================
// Overridden Methods
//================================================================================
@Override
protected final SkinBase, ?> createDefaultSkin() {
SkinBase skin = buildSkin();
if (behavior != null) skin.initBehavior(behavior);
return skin;
}
//================================================================================
// Getters/Setters
//================================================================================
@Override
public B getBehavior() {
return behavior;
}
@Override
public Supplier getBehaviorProvider() {
return behaviorProvider.get();
}
@Override
public SupplierProperty behaviorProviderProperty() {
return behaviorProvider;
}
@Override
public void setBehaviorProvider(Supplier behaviorProvider) {
this.behaviorProvider.set(behaviorProvider);
}
}