All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.palexdev.materialfx.effects.ripple.base.AbstractMFXRippleGenerator Maven / Gradle / Ivy

There is a newer version: 11.17.0
Show newest version
/*
 * Copyright (C) 2022 Parisi Alessandro
 * 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.materialfx.effects.ripple.base;

import io.github.palexdev.materialfx.beans.PositionBean;
import io.github.palexdev.materialfx.beans.properties.EventHandlerProperty;
import io.github.palexdev.materialfx.collections.ObservableStack;
import io.github.palexdev.materialfx.effects.DepthLevel;
import io.github.palexdev.materialfx.skins.MFXCheckboxSkin;
import javafx.animation.Animation;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ListChangeListener;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Bounds;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.shape.Shape;

import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Abstract class that defines all the properties and behaviors a RippleGenerator should have.
 * 

* Also defines the style class ("mfx-ripple-generator") for all generators that extend this class. *

* When generating ripples, four are three important information: *

- Region: the generator must have a reference to the {@code Region} in which it will generate ripples. *

- Position: the generator must know where you want to generate the ripple, so x and y coordinates ({@link #positionFunction}. *

- Ripple type/shape: the generator must know what kind of ripple you want to generate (circle, rectangle...), {@link #rippleSupplier}. *

- Clip/Ripple Bounds: the generator should know the bounds beyond which the ripple must not go. In JavaFX to achieve such behavior there is the clip concept, {@link #clipSupplier}. * * @param the types of ripple accepted by the generator */ public abstract class AbstractMFXRippleGenerator extends Region implements IRippleGenerator { //================================================================================ // Properties //================================================================================ protected final String STYLE_CLASS = "mfx-ripple-generator"; protected final ObservableStack animationsStack; protected final Region region; protected Supplier clipSupplier; protected Supplier rippleSupplier; protected Function positionFunction; protected final BooleanProperty animateBackground = new SimpleBooleanProperty(true); protected final BooleanProperty animateShadow = new SimpleBooleanProperty(false); protected final BooleanProperty checkBounds = new SimpleBooleanProperty(true); protected final IntegerProperty depthLevelOffset = new SimpleIntegerProperty(1); //================================================================================ // Constructors //================================================================================ protected AbstractMFXRippleGenerator(Region region) { this.animationsStack = new ObservableStack<>(); this.region = region; } //================================================================================ // Abstract Methods //================================================================================ /** * Abstract method. Every generator must provide a way to generate ripples. */ public abstract void generateRipple(MouseEvent event); //================================================================================ // Methods //================================================================================ /** * This method must be called by all subclasses in the constructor after the super call. * It is responsible for setting the style class, the region size and firing {@link RippleGeneratorEvent} events. */ protected void initialize() { getStyleClass().setAll(STYLE_CLASS); prefWidthProperty().bind(region.widthProperty()); prefHeightProperty().bind(region.heightProperty()); setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE); animationsStack.addListener((ListChangeListener) change -> { if (change.getList().isEmpty()) { fireGeneratorEvent(RippleGeneratorEvent.ANIMATION_FINISHED_EVENT); } }); } /** * Checks if the mouse event coordinates are within the {@link Bounds} of the region. */ protected boolean isWithinBounds(MouseEvent event) { if (event == null) { return true; } double eventX = event.getX(); double eventY = event.getY(); Bounds bounds = region.getLayoutBounds(); return bounds.contains(eventX, eventY); } public boolean isAnimateBackground() { return animateBackground.get(); } /** * Specifies if the background of the region should be animated too. *

* The animation generally consists in temporarily adding a shape to the generator, * set its fill same as the ripple color, and manipulate its opacity with a timeline. */ public BooleanProperty animateBackgroundProperty() { return animateBackground; } public void setAnimateBackground(boolean animateBackground) { this.animateBackground.set(animateBackground); } public boolean isAnimateShadow() { return animateShadow.get(); } /** * Specifies if the {@link DropShadow} effect of the region should be animates too. *

* Mostly used for {@code MFXButtons}. */ public BooleanProperty animateShadowProperty() { return animateShadow; } public void setAnimateShadow(boolean animateShadow) { this.animateShadow.set(animateShadow); } public boolean isCheckBounds() { return checkBounds.get(); } /** * Specifies if {@link #isWithinBounds(MouseEvent)} should be called before generating the ripple. *

* The purpose of this property is to disable/bypass the bounds check, it may happen in some cases * that the check must be disabled to make the generator work properly. An example is the {@link MFXCheckboxSkin}. */ public BooleanProperty checkBoundsProperty() { return checkBounds; } public void setCheckBounds(boolean checkBounds) { this.checkBounds.set(checkBounds); } public int getDepthLevelOffset() { return depthLevelOffset.get(); } /** * Specifies by how many levels the shadow should be increased. * For example if the {@link DropShadow} effect is of {@link DepthLevel#LEVEL1} and the * offset is set to 2 then the shadow will shift to {@link DepthLevel#LEVEL3}, * (reverted at the end of the animation of course). */ public IntegerProperty depthLevelOffsetProperty() { return depthLevelOffset; } public void setDepthLevelOffset(int depthLevelOffset) { this.depthLevelOffset.set(depthLevelOffset); } //================================================================================ // Events //================================================================================ /** * Events class for RippleGenerators. *

* Defines a new EventType: *

* - ANIMATION_FINISHED_EVENT: when the ripple animation has finished this event is fired by the generator. The tricky part, * When the ripple animation is finished? For this purpose an {@link ObservableStack} is used. When a ripple is generated its animation should be added * to the stack and removed only when the animation is finished. A change listener added to the stack checks if it is empty. When it's empty it means * that all ripple animations have ended, at that point the event is fired

*

* These events are automatically fired by the generator so they should not be fired by users. */ public static class RippleGeneratorEvent extends Event { public static final EventType ANIMATION_FINISHED_EVENT = new EventType<>(ANY, "ANIMATION_FINISHED_EVENT"); public RippleGeneratorEvent(EventType eventType) { super(eventType); } } public final EventHandlerProperty onAnimationFinished = new EventHandlerProperty<>() { @Override protected void invalidated() { setEventHandler(RippleGeneratorEvent.ANIMATION_FINISHED_EVENT, get()); } }; public EventHandler getOnAnimationFinished() { return onAnimationFinished.get(); } /** * Specifies the action to perform when a {@link RippleGeneratorEvent#ANIMATION_FINISHED_EVENT} is fired. * * @see RippleGeneratorEvent */ public EventHandlerProperty onAnimationFinishedProperty() { return onAnimationFinished; } public void setOnAnimationFinished(EventHandler onAnimationFinished) { this.onAnimationFinished.set(onAnimationFinished); } /** * Convenience method to fire {@link RippleGeneratorEvent} events. */ public void fireGeneratorEvent(EventType eventType) { fireEvent(new RippleGeneratorEvent(eventType)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy