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