package ru.khasang.cachoeira.view;
import com.sun.javafx.scene.control.skin.ButtonSkin;
import javafx.animation.*;
import javafx.beans.property.DoubleProperty;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Skin;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.util.Duration;
public class MaterialButton extends Button {
private final static double DEFAULT_DROP_SHADOW_RADIUS = 4;
private final static double HOVER_DROP_SHADOW_RADIUS = 12;
private DropShadow dropShadow;
private Circle circleRipple;
private Rectangle rippleClip = new Rectangle();
private Duration rippleDuration = Duration.millis(250);
private double lastRippleHeight = 0;
private double lastRippleWidth = 0;
private Color rippleColor = new Color(0, 0, 0, 0.11);
public MaterialButton(String text) {
super(text);
this.setFont(Font.loadFont(getClass().getResource("/font/Roboto-Medium.ttf").toExternalForm(), 10.5));
dropShadow = createDropShadow();
this.setEffect(dropShadow);
this.enableDropShadowAnimationOnHover();
this.createRippleEffect();
}
public MaterialButton(String text, Node graphic) {
super(text, graphic);
this.setFont(Font.loadFont(getClass().getResource("/font/Roboto-Medium.ttf").toExternalForm(), 10.5));
dropShadow = createDropShadow();
this.setEffect(dropShadow);
this.enableDropShadowAnimationOnHover();
this.createRippleEffect();
}
@Override
protected Skin<?> createDefaultSkin() {
final ButtonSkin buttonSkin = new ButtonSkin(this);
// Adding circleRipple as fist node of button nodes to be on the bottom
this.getChildren().add(0, circleRipple);
return buttonSkin;
}
private void createRippleEffect() {
circleRipple = new Circle(0.1, rippleColor);
circleRipple.setOpacity(0.0);
// Optional box blur on ripple - smoother ripple effect
// circleRipple.setEffect(new BoxBlur(3, 3, 2));
// Fade effect bit longer to show edges on the end
final FadeTransition fadeTransition = new FadeTransition(rippleDuration, circleRipple);
fadeTransition.setInterpolator(Interpolator.EASE_OUT);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0.0);
final Timeline scaleRippleTimeline = new Timeline();
final SequentialTransition parallelTransition = new SequentialTransition();
parallelTransition.getChildren().addAll(
scaleRippleTimeline,
fadeTransition
);
parallelTransition.setOnFinished(event1 -> {
circleRipple.setOpacity(0.0);
circleRipple.setRadius(0.1);
});
this.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
parallelTransition.stop();
parallelTransition.getOnFinished().handle(null);
circleRipple.setCenterX(event.getX());
circleRipple.setCenterY(event.getY());
// Recalculate ripple size if size of button from last time was changed
if (getWidth() != lastRippleWidth || getHeight() != lastRippleHeight) {
lastRippleWidth = getWidth();
lastRippleHeight = getHeight();
rippleClip.setWidth(lastRippleWidth);
rippleClip.setHeight(lastRippleHeight);
rippleClip.setArcHeight(this.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius());
rippleClip.setArcWidth(this.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius());
circleRipple.setClip(rippleClip);
// Getting 45% of longest button's length, because we want edge of ripple effect always visible
double circleRippleRadius = Math.max(getHeight(), getWidth()) * 0.45;
final KeyValue keyValue = new KeyValue(circleRipple.radiusProperty(), circleRippleRadius, Interpolator.EASE_OUT);
final KeyFrame keyFrame = new KeyFrame(rippleDuration, keyValue);
scaleRippleTimeline.getKeyFrames().clear();
scaleRippleTimeline.getKeyFrames().add(keyFrame);
}
parallelTransition.playFromStart();
});
}
private DropShadow createDropShadow() {
DropShadow dropShadow = new DropShadow();
dropShadow.setColor(Color.rgb(0, 0, 0, 0.8));
dropShadow.setBlurType(BlurType.TWO_PASS_BOX);
dropShadow.setOffsetY(2);
dropShadow.setRadius(DEFAULT_DROP_SHADOW_RADIUS);
return dropShadow;
}
private void enableDropShadowAnimationOnHover() {
Timeline dropShadowOut = createAnimation(dropShadow.radiusProperty(), HOVER_DROP_SHADOW_RADIUS, Duration.millis(500));
Timeline dropShadowIn = createAnimation(dropShadow.radiusProperty(), DEFAULT_DROP_SHADOW_RADIUS, Duration.millis(300));
this.setOnMouseEntered(event -> {
dropShadowIn.stop();
dropShadowOut.play();
});
this.setOnMouseExited(event -> {
dropShadowOut.stop();
dropShadowIn.play();
});
}
private Timeline createAnimation(DoubleProperty property, double endValue, Duration millis) {
KeyValue endKeyValue = new KeyValue(property, endValue, Interpolator.SPLINE(0.4, 0, 0.2, 1));
KeyFrame endKeyFrame = new KeyFrame(millis, endKeyValue);
return new Timeline(endKeyFrame);
}
}