简化 RipplerContainer (#5363)
This commit is contained in:
@@ -19,17 +19,12 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXRippler;
|
||||
import javafx.animation.Transition;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.css.*;
|
||||
import javafx.css.converter.PaintConverter;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
@@ -37,39 +32,41 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@DefaultProperty("container")
|
||||
public class RipplerContainer extends StackPane {
|
||||
private static final String DEFAULT_STYLE_CLASS = "rippler-container";
|
||||
private static final Duration DURATION = Duration.millis(200);
|
||||
private static final CornerRadii DEFAULT_RADII = new CornerRadii(3);
|
||||
private static final Color DEFAULT_RIPPLER_FILL = Color.rgb(0, 200, 255);
|
||||
|
||||
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL, this, "ripplerFill", null);
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false);
|
||||
private final Node container;
|
||||
|
||||
private final StackPane buttonContainer = new StackPane();
|
||||
private final JFXRippler buttonRippler = new JFXRippler(new StackPane()) {
|
||||
private static final Background DEFAULT_MASK_BACKGROUND = new Background(new BackgroundFill(Color.WHITE, DEFAULT_RADII, Insets.EMPTY));
|
||||
|
||||
@Override
|
||||
protected Node getMask() {
|
||||
StackPane mask = new StackPane();
|
||||
mask.shapeProperty().bind(buttonContainer.shapeProperty());
|
||||
mask.backgroundProperty().bind(Bindings.createObjectBinding(() -> new Background(new BackgroundFill(Color.WHITE, buttonContainer.getBackground() != null && buttonContainer.getBackground().getFills().size() > 0 ? buttonContainer.getBackground().getFills().get(0).getRadii() : defaultRadii, buttonContainer.getBackground() != 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());
|
||||
mask.setBackground(DEFAULT_MASK_BACKGROUND);
|
||||
mask.resize(
|
||||
buttonContainer.getWidth() - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(),
|
||||
buttonContainer.getHeight() - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset()
|
||||
);
|
||||
return mask;
|
||||
}
|
||||
};
|
||||
|
||||
private final CornerRadii defaultRadii = new CornerRadii(3);
|
||||
private Transition coverAnimation;
|
||||
|
||||
public RipplerContainer(@NamedArg("container") Node container) {
|
||||
setContainer(container);
|
||||
public RipplerContainer(Node container) {
|
||||
this.container = container;
|
||||
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
buttonRippler.setPosition(JFXRippler.RipplerPos.BACK);
|
||||
@@ -86,71 +83,79 @@ public class RipplerContainer extends StackPane {
|
||||
setPickOnBounds(false);
|
||||
|
||||
buttonContainer.setPickOnBounds(false);
|
||||
buttonRippler.ripplerFillProperty().bind(ripplerFillProperty());
|
||||
|
||||
containerProperty().addListener(o -> updateChildren());
|
||||
updateChildren();
|
||||
|
||||
InvalidationListener listener = o -> {
|
||||
if (isSelected()) setBackground(new Background(new BackgroundFill(getRipplerFill(), defaultRadii, null)));
|
||||
else setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, defaultRadii, null)));
|
||||
};
|
||||
|
||||
selectedProperty().addListener(listener);
|
||||
selectedProperty().addListener((a, b, newValue) ->
|
||||
pseudoClassStateChanged(PseudoClass.getPseudoClass("selected"), newValue));
|
||||
ripplerFillProperty().addListener(listener);
|
||||
|
||||
setShape(Lang.apply(new Rectangle(), rectangle -> {
|
||||
rectangle.widthProperty().bind(widthProperty());
|
||||
rectangle.heightProperty().bind(heightProperty());
|
||||
}));
|
||||
var shape = new Rectangle();
|
||||
shape.widthProperty().bind(widthProperty());
|
||||
shape.heightProperty().bind(heightProperty());
|
||||
setShape(shape);
|
||||
|
||||
EventHandler<MouseEvent> mouseEventHandler;
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
setOnMouseEntered(e -> new Transition() {
|
||||
{
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Motion.EASE_IN);
|
||||
mouseEventHandler = event -> {
|
||||
if (coverAnimation != null) {
|
||||
coverAnimation.stop();
|
||||
coverAnimation = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(frac);
|
||||
}
|
||||
}.play());
|
||||
if (event.getEventType() == MouseEvent.MOUSE_ENTERED) {
|
||||
coverAnimation = new Transition() {
|
||||
{
|
||||
setCycleDuration(Motion.SHORT4);
|
||||
setInterpolator(Motion.EASE_IN);
|
||||
}
|
||||
|
||||
setOnMouseExited(e -> new Transition() {
|
||||
{
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Motion.EASE_OUT);
|
||||
@Override
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(frac);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
coverAnimation = new Transition() {
|
||||
{
|
||||
setCycleDuration(Motion.SHORT4);
|
||||
setInterpolator(Motion.EASE_OUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(1 - frac);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(1 - frac);
|
||||
}
|
||||
}.play());
|
||||
coverAnimation.play();
|
||||
};
|
||||
} else {
|
||||
setOnMouseEntered(e -> interpolateBackground(1));
|
||||
setOnMouseExited(e -> interpolateBackground(0));
|
||||
mouseEventHandler = event ->
|
||||
interpolateBackground(event.getEventType() == MouseEvent.MOUSE_ENTERED ? 1 : 0);
|
||||
}
|
||||
|
||||
addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEventHandler);
|
||||
addEventHandler(MouseEvent.MOUSE_EXITED, mouseEventHandler);
|
||||
}
|
||||
|
||||
private void interpolateBackground(double frac) {
|
||||
Color onSurface = Themes.getColorScheme().getOnSurface();
|
||||
setBackground(new Background(new BackgroundFill(
|
||||
Color.color(onSurface.getRed(), onSurface.getGreen(), onSurface.getBlue(), frac * 0.04),
|
||||
CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
if (frac < 0.01) {
|
||||
setBackground(null);
|
||||
} else {
|
||||
Color onSurface = Themes.getColorScheme().getOnSurface();
|
||||
setBackground(new Background(new BackgroundFill(
|
||||
Color.color(onSurface.getRed(), onSurface.getGreen(), onSurface.getBlue(), frac * 0.04),
|
||||
CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateChildren() {
|
||||
if (buttonRippler.getPosition() == JFXRippler.RipplerPos.BACK)
|
||||
getChildren().setAll(buttonContainer, getContainer());
|
||||
else
|
||||
getChildren().setAll(getContainer(), buttonContainer);
|
||||
|
||||
for (int i = 1; i < getChildren().size(); ++i)
|
||||
getChildren().get(i).setPickOnBounds(false);
|
||||
Node container = getContainer();
|
||||
if (buttonRippler.getPosition() == JFXRippler.RipplerPos.BACK) {
|
||||
getChildren().setAll(buttonContainer, container);
|
||||
container.setPickOnBounds(false);
|
||||
} else {
|
||||
getChildren().setAll(container, buttonContainer);
|
||||
buttonContainer.setPickOnBounds(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPosition(JFXRippler.RipplerPos pos) {
|
||||
@@ -163,39 +168,41 @@ public class RipplerContainer extends StackPane {
|
||||
}
|
||||
|
||||
public Node getContainer() {
|
||||
return container.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> containerProperty() {
|
||||
return container;
|
||||
}
|
||||
|
||||
public void setContainer(Node container) {
|
||||
this.container.set(container);
|
||||
}
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new StyleableObjectProperty<>(DEFAULT_RIPPLER_FILL) {
|
||||
@Override
|
||||
public Object getBean() {
|
||||
return RipplerContainer.this;
|
||||
}
|
||||
|
||||
public Paint getRipplerFill() {
|
||||
return ripplerFill.get();
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ripplerFill";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CssMetaData<? extends Styleable, Paint> getCssMetaData() {
|
||||
return StyleableProperties.RIPPLER_FILL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
buttonRippler.setRipplerFill(get());
|
||||
}
|
||||
};
|
||||
|
||||
public StyleableObjectProperty<Paint> ripplerFillProperty() {
|
||||
return ripplerFill;
|
||||
}
|
||||
|
||||
public Paint getRipplerFill() {
|
||||
return ripplerFillProperty().get();
|
||||
}
|
||||
|
||||
public void setRipplerFill(Paint ripplerFill) {
|
||||
this.ripplerFill.set(ripplerFill);
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected.get();
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected.set(selected);
|
||||
ripplerFillProperty().set(ripplerFill);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -204,12 +211,28 @@ public class RipplerContainer extends StackPane {
|
||||
}
|
||||
|
||||
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
|
||||
return StyleableProperties.FACTORY.getCssMetaData();
|
||||
return StyleableProperties.STYLEABLES;
|
||||
}
|
||||
|
||||
private final static class StyleableProperties {
|
||||
private static final StyleablePropertyFactory<RipplerContainer> FACTORY = new StyleablePropertyFactory<>(StackPane.getClassCssMetaData());
|
||||
private static final CssMetaData<RipplerContainer, Paint> RIPPLER_FILL = new CssMetaData<>("-jfx-rippler-fill", PaintConverter.getInstance(), DEFAULT_RIPPLER_FILL) {
|
||||
@Override
|
||||
public boolean isSettable(RipplerContainer styleable) {
|
||||
return styleable.ripplerFill == null || !styleable.ripplerFill.isBound();
|
||||
}
|
||||
|
||||
private static final CssMetaData<RipplerContainer, Paint> RIPPLER_FILL = FACTORY.createPaintCssMetaData("-jfx-rippler-fill", s -> s.ripplerFill, Color.rgb(0, 200, 255));
|
||||
@Override
|
||||
public StyleableProperty<Paint> getStyleableProperty(RipplerContainer styleable) {
|
||||
return styleable.ripplerFillProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||
|
||||
static {
|
||||
var styleables = new ArrayList<>(StackPane.getClassCssMetaData());
|
||||
styleables.add(RIPPLER_FILL);
|
||||
STYLEABLES = List.copyOf(styleables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user