智能选择 popup 弹出位置 (#4203)

This commit is contained in:
ENC_Euphony
2025-08-07 15:01:18 +08:00
committed by GitHub
parent ac4c3a7501
commit 7ba9d9eadd

View File

@@ -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<GameListItem> {
private static GameListItem currentSkinnable;
private static Lazy<JFXPopup> popup = new Lazy<>(() -> {
private static final Lazy<JFXPopup> popup = new Lazy<>(() -> {
PopupMenu menu = new PopupMenu();
JFXPopup popup = new JFXPopup(menu);
@@ -99,7 +104,9 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
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<GameListItem> {
}
} 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<Screen> 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
}
}