/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.jfoenix.skins;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXButton.ButtonType;
import com.jfoenix.controls.JFXRippler;
import com.jfoenix.effects.JFXDepthManager;
import com.jfoenix.transitions.CachedTransition;
import com.sun.javafx.scene.control.skin.ButtonSkin;
import com.sun.javafx.scene.control.skin.LabeledText;
import javafx.animation.*;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
/**
* <h1>Material Design Button Skin</h1>
*
* @author Shadi Shaheen
* @version 1.0
* @since 2016-03-09
*/
public class JFXButtonSkin extends ButtonSkin {
private final StackPane buttonContainer = new StackPane();
private JFXRippler buttonRippler;
private Transition clickedAnimation;
private final CornerRadii defaultRadii = new CornerRadii(3);
private boolean invalid = true;
private Runnable releaseManualRippler = null;
public JFXButtonSkin(JFXButton button) {
super(button);
buttonRippler = new JFXRippler(new StackPane()) {
@Override
protected Node getMask() {
StackPane mask = new StackPane();
mask.shapeProperty().bind(buttonContainer.shapeProperty());
mask.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
return new Background(new BackgroundFill(Color.WHITE,
buttonContainer.backgroundProperty()
.get() != null && buttonContainer.getBackground()
.getFills()
.size() > 0 ? buttonContainer
.getBackground()
.getFills()
.get(0)
.getRadii() : defaultRadii,
buttonContainer.backgroundProperty()
.get() != null && buttonContainer.getBackground()
.getFills()
.size() > 0 ? buttonContainer
.getBackground()
.getFills()
.get(0)
.getInsets() : Insets.EMPTY));
}, buttonContainer.backgroundProperty()));
mask.resize(buttonContainer.getWidth() - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(),
buttonContainer.getHeight() - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset());
return mask;
}
@Override
protected void initListeners() {
ripplerPane.setOnMousePressed((event) -> {
if (releaseManualRippler != null) {
releaseManualRippler.run();
}
releaseManualRippler = null;
createRipple(event.getX(), event.getY());
});
}
};
getSkinnable().armedProperty().addListener((o, oldVal, newVal) -> {
if (newVal) {
releaseManualRippler = buttonRippler.createManualRipple();
if (clickedAnimation != null) {
clickedAnimation.setRate(1);
clickedAnimation.play();
}
} else {
if (releaseManualRippler != null) {
releaseManualRippler.run();
}
if (clickedAnimation != null) {
clickedAnimation.setRate(-1);
clickedAnimation.play();
}
}
});
buttonContainer.getChildren().add(buttonRippler);
// add listeners to the button and bind properties
button.buttonTypeProperty().addListener((o, oldVal, newVal) -> updateButtonType(newVal));
button.setOnMousePressed(e -> {
if (clickedAnimation != null) {
clickedAnimation.setRate(1);
clickedAnimation.play();
}
});
button.setOnMouseReleased(e -> {
if (clickedAnimation != null) {
clickedAnimation.setRate(-1);
clickedAnimation.play();
}
});
// show focused state
button.focusedProperty().addListener((o, oldVal, newVal) -> {
if (newVal) {
if (!getSkinnable().isPressed()) {
buttonRippler.showOverlay();
}
} else {
buttonRippler.hideOverlay();
}
});
button.pressedProperty().addListener((o, oldVal, newVal) -> buttonRippler.hideOverlay());
/*
* disable action when clicking on the button shadow
*/
button.setPickOnBounds(false);
buttonContainer.setPickOnBounds(false);
buttonContainer.shapeProperty().bind(getSkinnable().shapeProperty());
buttonContainer.borderProperty().bind(getSkinnable().borderProperty());
buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
// reset button background to transparent if its set to java default values
if (button.getBackground() == null || isJavaDefaultBackground(button.getBackground()) || isJavaDefaultClickedBackground(
button.getBackground())) {
button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
}
try {
//Insets always Empty
if (getSkinnable().getBackground() != null && getSkinnable().getBackground()
.getFills()
.get(0)
.getInsets()
.equals(new Insets(-0.2,
-0.2,
-0.2,
-0.2))) {
return new Background(new BackgroundFill(getSkinnable().getBackground() != null ? getSkinnable().getBackground()
.getFills()
.get(
0)
.getFill() : Color.TRANSPARENT,
getSkinnable().backgroundProperty()
.get() != null ? getSkinnable().getBackground()
.getFills()
.get(0)
.getRadii() : defaultRadii,
Insets.EMPTY/*new Insets(0,0,-1.0,0)*/));
} else {
return new Background(new BackgroundFill(getSkinnable().getBackground() != null ? getSkinnable().getBackground()
.getFills()
.get(
0)
.getFill() : Color.TRANSPARENT,
getSkinnable().getBackground() != null ? getSkinnable().getBackground()
.getFills()
.get(
0)
.getRadii() : defaultRadii,
Insets.EMPTY));
}
} catch (Exception ignored) {
return getSkinnable().getBackground();
}
}, getSkinnable().backgroundProperty()));
button.ripplerFillProperty().addListener((o, oldVal, newVal) -> buttonRippler.setRipplerFill(newVal));
// set default background to transparent
if (button.getBackground() == null || isJavaDefaultBackground(button.getBackground())) {
button.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
}
updateButtonType(button.getButtonType());
updateChildren();
}
@Override
protected void updateChildren() {
super.updateChildren();
if (buttonContainer != null) {
getChildren().add(0, buttonContainer);
}
for (int i = 1; i < getChildren().size(); i++) {
getChildren().get(i).setMouseTransparent(true);
}
}
@Override
protected void layoutChildren(final double x, final double y, final double w, final double h) {
if (invalid) {
if (((JFXButton) getSkinnable()).getRipplerFill() == null) {
// change rippler fill according to the last LabeledText/Label child
for (int i = getChildren().size() - 1; i >= 1; i--) {
if (getChildren().get(i) instanceof LabeledText) {
buttonRippler.setRipplerFill(((LabeledText) getChildren().get(i)).getFill());
((LabeledText) getChildren().get(i)).fillProperty()
.addListener((o, oldVal, newVal) -> buttonRippler.setRipplerFill(
newVal));
break;
} else if (getChildren().get(i) instanceof Label) {
buttonRippler.setRipplerFill(((Label) getChildren().get(i)).getTextFill());
((Label) getChildren().get(i)).textFillProperty()
.addListener((o, oldVal, newVal) -> buttonRippler.setRipplerFill(
newVal));
break;
}
}
} else {
buttonRippler.setRipplerFill(((JFXButton) getSkinnable()).getRipplerFill());
}
invalid = false;
}
double shift = 1;
buttonContainer.resizeRelocate(getSkinnable().getLayoutBounds().getMinX() - shift,
getSkinnable().getLayoutBounds().getMinY() - shift,
getSkinnable().getWidth() + (2 * shift),
getSkinnable().getHeight() + (2 * shift));
layoutLabelInArea(x, y, w, h);
}
private boolean isJavaDefaultBackground(Background background) {
try {
final String firstFill = background.getFills().get(0).getFill().toString();
return "0xffffffba".equals(firstFill)
|| "0xffffffbf".equals(firstFill)
|| "0xffffffbd".equals(firstFill);
} catch (Exception ignored) {
return false;
}
}
private boolean isJavaDefaultClickedBackground(Background background) {
try {
return "0x039ed3ff".equals(background.getFills().get(0).getFill().toString());
} catch (Exception ignored) {
return false;
}
}
private void updateButtonType(ButtonType type) {
switch (type) {
case RAISED:
JFXDepthManager.setDepth(buttonContainer, 2);
clickedAnimation = new ButtonClickTransition();
break;
default:
buttonContainer.setEffect(null);
break;
}
}
private class ButtonClickTransition extends CachedTransition {
public ButtonClickTransition() {
super(buttonContainer, new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(((DropShadow) buttonContainer.getEffect()).radiusProperty(),
JFXDepthManager.getShadowAt(2).radiusProperty().get(),
Interpolator.EASE_BOTH),
new KeyValue(((DropShadow) buttonContainer.getEffect()).spreadProperty(),
JFXDepthManager.getShadowAt(2).spreadProperty().get(),
Interpolator.EASE_BOTH),
new KeyValue(((DropShadow) buttonContainer.getEffect()).offsetXProperty(),
JFXDepthManager.getShadowAt(2).offsetXProperty().get(),
Interpolator.EASE_BOTH),
new KeyValue(((DropShadow) buttonContainer.getEffect()).offsetYProperty(),
JFXDepthManager.getShadowAt(2).offsetYProperty().get(),
Interpolator.EASE_BOTH)
),
new KeyFrame(Duration.millis(1000),
new KeyValue(((DropShadow) buttonContainer.getEffect()).radiusProperty(),
JFXDepthManager.getShadowAt(5).radiusProperty().get(),
Interpolator.EASE_BOTH),
new KeyValue(((DropShadow) buttonContainer.getEffect()).spreadProperty(),
JFXDepthManager.getShadowAt(5).spreadProperty().get(),
Interpolator.EASE_BOTH),
new KeyValue(((DropShadow) buttonContainer.getEffect()).offsetXProperty(),
JFXDepthManager.getShadowAt(5).offsetXProperty().get(),
Interpolator.EASE_BOTH),
new KeyValue(((DropShadow) buttonContainer.getEffect()).offsetYProperty(),
JFXDepthManager.getShadowAt(5).offsetYProperty().get(),
Interpolator.EASE_BOTH)
)
)
);
// reduce the number to increase the shifting , increase number to reduce shifting
setCycleDuration(Duration.seconds(0.2));
setDelay(Duration.seconds(0));
}
}
}