From 7ba9d9eaddd64960eed78c7d234e9ab55bdb206f Mon Sep 17 00:00:00 2001 From: ENC_Euphony <47242104+pynickle@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:01:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=BA=E8=83=BD=E9=80=89=E6=8B=A9=20popup=20?= =?UTF-8?q?=E5=BC=B9=E5=87=BA=E4=BD=8D=E7=BD=AE=20(#4203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameListItemSkin.java | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java index 0812d10c6..c8e812579 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java @@ -20,12 +20,15 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXRadioButton; +import javafx.geometry.Bounds; import javafx.geometry.Pos; +import javafx.geometry.Rectangle2D; import javafx.scene.Cursor; import javafx.scene.control.SkinBase; import javafx.scene.input.MouseButton; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.stage.Screen; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -35,11 +38,13 @@ import org.jackhuang.hmcl.ui.construct.PopupMenu; import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.util.Lazy; +import java.util.List; + import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameListItemSkin extends SkinBase { private static GameListItem currentSkinnable; - private static Lazy popup = new Lazy<>(() -> { + private static final Lazy popup = new Lazy<>(() -> { PopupMenu menu = new PopupMenu(); JFXPopup popup = new JFXPopup(menu); @@ -99,7 +104,9 @@ public class GameListItemSkin extends SkinBase { JFXButton btnManage = new JFXButton(); btnManage.setOnAction(e -> { currentSkinnable = skinnable; - popup.get().show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()); + + JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root); + popup.get().show(root, vPosition, JFXPopup.PopupHPosition.RIGHT, 0, vPosition == JFXPopup.PopupVPosition.TOP ? root.getHeight() : -root.getHeight()); }); btnManage.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnManage, Pos.CENTER); @@ -124,8 +131,46 @@ public class GameListItemSkin extends SkinBase { } } else if (e.getButton() == MouseButton.SECONDARY) { currentSkinnable = skinnable; - popup.get().show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY()); + + JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root); + popup.get().show(root, vPosition, JFXPopup.PopupHPosition.LEFT, e.getX(), vPosition == JFXPopup.PopupVPosition.TOP ? e.getY() : e.getY() - root.getHeight()); } }); } + + /** + * Intelligently determines the popup position to prevent the menu from exceeding screen boundaries. + * Supports multi-monitor setups by detecting the current screen where the component is located. + * + * @param root the root node to calculate position relative to + * @return the optimal vertical position for the popup menu + */ + private static JFXPopup.PopupVPosition determineOptimalPopupPosition(BorderPane root) { + // Get the screen bounds in screen coordinates + Bounds screenBounds = root.localToScreen(root.getBoundsInLocal()); + + // Convert Bounds to Rectangle2D for getScreensForRectangle method + Rectangle2D boundsRect = new Rectangle2D( + screenBounds.getMinX(), screenBounds.getMinY(), + screenBounds.getWidth(), screenBounds.getHeight() + ); + + // Find the screen that contains this component (supports multi-monitor) + List screens = Screen.getScreensForRectangle(boundsRect); + Screen currentScreen = screens.isEmpty() ? Screen.getPrimary() : screens.get(0); + Rectangle2D visualBounds = currentScreen.getVisualBounds(); + + double screenHeight = visualBounds.getHeight(); + double screenMinY = visualBounds.getMinY(); + double itemScreenY = screenBounds.getMinY(); + + // Calculate available space relative to the current screen + double availableSpaceAbove = itemScreenY - screenMinY; + double availableSpaceBelow = screenMinY + screenHeight - itemScreenY - root.getHeight(); + double menuHeight = popup.get().getPopupContent().getHeight(); + + return (availableSpaceAbove > menuHeight && availableSpaceBelow < menuHeight) + ? JFXPopup.PopupVPosition.BOTTOM // Show menu below the button, expanding downward + : JFXPopup.PopupVPosition.TOP; // Show menu above the button, expanding upward + } }