From 0a464b859cf91ab40f9201038e9eda194e12a0e4 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sun, 13 Sep 2026 10:20:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8DJFXPopup=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=85=B3=E9=97=AD=E5=B9=B6=E6=B7=BB=E5=8A=A0=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jfoenix/controls/JFXPopup.java | 14 +++++- .../java/com/jfoenix/skins/JFXPopupSkin.java | 50 +++++++++++++++++++ .../hmcl/ui/instances/GameListCell.java | 38 ++++++++++++-- 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java b/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java index 659c252ff94..bf405a236a0 100644 --- a/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java +++ b/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java @@ -170,8 +170,20 @@ public void show(Window window, double x, double y, PopupVPosition vAlign, Popup @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 c1daa775acb..a8ee9767cf5 100644 --- a/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java +++ b/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java @@ -49,6 +49,9 @@ public class JFXPopupSkin implements Skin { protected Node root; private Animation animation; + private Animation closeAnimation; + private boolean closing; + protected Scale scale; public JFXPopupSkin(JFXPopup control) { @@ -74,6 +77,8 @@ public void reset(PopupVPosition vAlign, PopupHPosition hAlign, double offsetX, } public final void animate() { + closing = false; + container.setMouseTransparent(false); if (animation != null) { if (animation.getStatus() == Status.STOPPED) { container.setOpacity(1); @@ -87,6 +92,41 @@ public final void animate() { } } + public final void animateClose(Runnable onFinished) { + if (closing) { + return; + } + + closing = true; + 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(); + } + @Override public JFXPopup getSkinnable() { return control; @@ -103,6 +143,10 @@ public void dispose() { animation.stop(); animation = null; } + if (closeAnimation != null) { + closeAnimation.stop(); + closeAnimation = null; + } container = null; control = null; popupContent = null; @@ -132,6 +176,12 @@ protected Animation getAnimation() { public void init() { if (animation != null) animation.stop(); + if (closeAnimation != null) { + closeAnimation.stop(); + closeAnimation = null; + } + closing = false; + 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 e3121aa88c4..26e245f5c44 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), From 0d41d80b50f40f6d6cf831dbdb2b32c0bb0d32c7 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Mon, 21 Sep 2026 11:22:54 +0800 Subject: [PATCH 2/2] fix --- .../java/com/jfoenix/controls/JFXPopup.java | 18 +++++++++++++---- .../java/com/jfoenix/skins/JFXPopupSkin.java | 20 +++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java b/HMCL/src/main/java/com/jfoenix/controls/JFXPopup.java index bf405a236a0..b1108f27fb8 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,6 +174,10 @@ 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)) { diff --git a/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java b/HMCL/src/main/java/com/jfoenix/skins/JFXPopupSkin.java index a8ee9767cf5..d503dcaf3e5 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; @@ -50,7 +51,6 @@ public class JFXPopupSkin implements Skin { private Animation animation; private Animation closeAnimation; - private boolean closing; protected Scale scale; @@ -77,7 +77,6 @@ public void reset(PopupVPosition vAlign, PopupHPosition hAlign, double offsetX, } public final void animate() { - closing = false; container.setMouseTransparent(false); if (animation != null) { if (animation.getStatus() == Status.STOPPED) { @@ -93,11 +92,10 @@ public final void animate() { } public final void animateClose(Runnable onFinished) { - if (closing) { + if (closeAnimation != null) { return; } - closing = true; container.setMouseTransparent(true); if (animation != null) { animation.stop(); @@ -127,6 +125,17 @@ public final void animateClose(Runnable onFinished) { 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; @@ -180,7 +189,6 @@ public void init() { closeAnimation.stop(); closeAnimation = null; } - closing = false; container.setMouseTransparent(false); container.setOpacity(0); scale.setX(1.0);