diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java b/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java index 659c252ff9..b1108f27fb 100644 --- a/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java @@ -131,7 +131,8 @@ public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double } public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY, boolean attachToNode) { - if (!isShowing()) { + boolean relocate = cancelCloseAnimation(); + if (!isShowing() || relocate) { Scene scene = node.getScene(); if (scene == null || scene.getWindow() == null) { throw new IllegalStateException("Can not show popup. The node must be attached to a scene/window."); @@ -144,10 +145,14 @@ public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double double anchorX = parent.getX() + scene.getX() + origin.getX() + (hAlign == PopupHPosition.RIGHT ? ((Region) node).getWidth() : 0); double anchorY = parent.getY() + origin.getY() + scene.getY() + (vAlign == PopupVPosition.BOTTOM ? ((Region) node).getHeight() : 0); - if (attachToNode) + if (relocate) { + setAnchorX(anchorX); + setAnchorY(anchorY); + } else if (attachToNode) { this.show(node, anchorX, anchorY); - else + } else { this.show(parent, anchorX, anchorY); + } ((JFXPopupSkin) getSkin()).reset(vAlign, isRTL ? hAlign.getOpposite() : hAlign, isRTL ? -initOffsetX : initOffsetX, initOffsetY); Platform.runLater(() -> ((JFXPopupSkin) getSkin()).animate()); @@ -155,7 +160,8 @@ public void show(Node node, PopupVPosition vAlign, PopupHPosition hAlign, double } public void show(Window window, double x, double y, PopupVPosition vAlign, PopupHPosition hAlign, double initOffsetX, double initOffsetY) { - if (!isShowing()) { + boolean relocate = cancelCloseAnimation(); + if (!isShowing() || relocate) { if (window == null) { throw new IllegalStateException("Can not show popup. The node must be attached to a scene/window."); } @@ -168,10 +174,26 @@ public void show(Window window, double x, double y, PopupVPosition vAlign, Popup } } + public boolean cancelCloseAnimation() { + return isShowing() && getSkin() instanceof JFXPopupSkin skin && skin.cancelCloseAnimation(); + } + @Override public void hide() { + if (!(getSkin() instanceof JFXPopupSkin skin)) { + super.hide(); + return; + } + + if (isShowing()) { + skin.animateClose(this::hideImmediately); + } else { + skin.init(); + } + } + + private void hideImmediately() { super.hide(); - ((JFXPopupSkin) getSkin()).init(); } /*************************************************************************** diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java index c1daa775ac..d503dcaf3e 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java @@ -29,7 +29,8 @@ import javafx.animation.Animation.Status; import javafx.scene.Node; import javafx.scene.control.Skin; -import javafx.scene.layout.*; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; import javafx.scene.transform.Scale; import javafx.util.Duration; import org.jackhuang.hmcl.ui.animation.AnimationUtils; @@ -49,6 +50,8 @@ public class JFXPopupSkin implements Skin { protected Node root; private Animation animation; + private Animation closeAnimation; + protected Scale scale; public JFXPopupSkin(JFXPopup control) { @@ -74,6 +77,7 @@ public void reset(PopupVPosition vAlign, PopupHPosition hAlign, double offsetX, } public final void animate() { + container.setMouseTransparent(false); if (animation != null) { if (animation.getStatus() == Status.STOPPED) { container.setOpacity(1); @@ -87,6 +91,51 @@ public final void animate() { } } + public final void animateClose(Runnable onFinished) { + if (closeAnimation != null) { + return; + } + + container.setMouseTransparent(true); + if (animation != null) { + animation.stop(); + } + + if (!AnimationUtils.isAnimationEnabled()) { + onFinished.run(); + init(); + return; + } + + Interpolator interpolator = Motion.EASE; + closeAnimation = new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(popupContent.opacityProperty(), popupContent.getOpacity(), interpolator), + new KeyValue(scale.xProperty(), scale.getX(), interpolator), + new KeyValue(scale.yProperty(), scale.getY(), interpolator)), + new KeyFrame(Motion.SHORT4, + new KeyValue(popupContent.opacityProperty(), 0, interpolator), + new KeyValue(scale.xProperty(), 0, interpolator), + new KeyValue(scale.yProperty(), 0.01, interpolator))); + closeAnimation.setOnFinished(event -> { + closeAnimation = null; + onFinished.run(); + init(); + }); + closeAnimation.play(); + } + + public final boolean cancelCloseAnimation() { + if (closeAnimation == null) { + return false; + } + + closeAnimation.stop(); + closeAnimation = null; + container.setMouseTransparent(false); + return true; + } + @Override public JFXPopup getSkinnable() { return control; @@ -103,6 +152,10 @@ public void dispose() { animation.stop(); animation = null; } + if (closeAnimation != null) { + closeAnimation.stop(); + closeAnimation = null; + } container = null; control = null; popupContent = null; @@ -132,6 +185,11 @@ protected Animation getAnimation() { public void init() { if (animation != null) animation.stop(); + if (closeAnimation != null) { + closeAnimation.stop(); + closeAnimation = null; + } + container.setMouseTransparent(false); container.setOpacity(0); scale.setX(1.0); scale.setY(0.01); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameListCell.java index e3121aa88c..26e245f5c4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/GameListCell.java @@ -53,6 +53,10 @@ public final class GameListCell extends ListCell { private final StringProperty tag = new SimpleStringProperty(); + private JFXPopup managementPopup; + + private GameListItem managementPopupItem; + public GameListCell() { BorderPane root = new BorderPane(); root.getStyleClass().add("md-list-cell"); @@ -132,7 +136,12 @@ public void fire() { if (item == null) return; - JFXPopup popup = getPopup(item); + JFXPopup popup = getManagementPopup(item); + if (popup.isShowing()) { + popup.hide(); + return; + } + JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup); popup.show(root, vPosition, JFXPopup.PopupHPosition.RIGHT, 0, vPosition == JFXPopup.PopupVPosition.TOP ? root.getHeight() : -root.getHeight()); }); @@ -152,7 +161,12 @@ public void fire() { item.modifyGameSettings(); } } else if (e.getButton() == MouseButton.SECONDARY) { - JFXPopup popup = getPopup(item); + JFXPopup popup = getManagementPopup(item); + if (popup.isShowing()) { + popup.hide(); + return; + } + JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup); popup.show(root, vPosition, JFXPopup.PopupHPosition.LEFT, e.getX(), vPosition == JFXPopup.PopupVPosition.TOP ? e.getY() : e.getY() - root.getHeight()); } @@ -168,6 +182,12 @@ public void updateItem(GameListItem item, boolean empty) { if (oldItem == item && oldEmpty == empty) return; + if (managementPopup != null) { + managementPopup.hide(); + managementPopup = null; + managementPopupItem = null; + } + this.graphic.releaseRippleImmediately(); this.imageView.imageProperty().unbind(); @@ -193,9 +213,21 @@ public void updateItem(GameListItem item, boolean empty) { } } - private static JFXPopup getPopup(GameListItem item) { + private JFXPopup getManagementPopup(GameListItem item) { + if (managementPopup == null || managementPopupItem != item) { + if (managementPopup != null) { + managementPopup.hide(); + } + managementPopup = createPopup(item); + managementPopupItem = item; + } + return managementPopup; + } + + private static JFXPopup createPopup(GameListItem item) { PopupMenu menu = new PopupMenu(); JFXPopup popup = new JFXPopup(menu); + popup.setConsumeAutoHidingEvents(true); menu.getContent().setAll( new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("instance.launch.test"), item::testGame, popup),