mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-22 10:43:57 -04:00
parent
913ce55d43
commit
228a1e4887
@ -31,7 +31,9 @@ import javafx.collections.ObservableMap;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
@ -48,6 +50,7 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
@ -1378,4 +1381,65 @@ public final class FXUtils {
|
||||
return new FileChooser.ExtensionFilter(i18n("extension.png"),
|
||||
IMAGE_EXTENSIONS.stream().map(ext -> "*." + ext).toArray(String[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Now handles first-time popup display by forcing layout measurement.
|
||||
*
|
||||
* @param root the root node to calculate position relative to
|
||||
* @param popupInstance the popup instance to position
|
||||
* @return the optimal vertical position for the popup menu
|
||||
*/
|
||||
public static JFXPopup.PopupVPosition determineOptimalPopupPosition(Node root, JFXPopup popupInstance) {
|
||||
// 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.getBoundsInLocal().getHeight();
|
||||
|
||||
// Get popup content and ensure it's properly measured
|
||||
Region popupContent = popupInstance.getPopupContent();
|
||||
|
||||
double menuHeight;
|
||||
if (popupContent.getHeight() <= 0) {
|
||||
// Force layout measurement if height is not yet available
|
||||
popupContent.autosize();
|
||||
popupContent.applyCss();
|
||||
popupContent.layout();
|
||||
|
||||
// Get the measured height, or use a reasonable fallback
|
||||
menuHeight = popupContent.getHeight();
|
||||
if (menuHeight <= 0) {
|
||||
// Fallback: estimate based on number of menu items
|
||||
// Each menu item is roughly 36px height + separators + padding
|
||||
menuHeight = 300; // Conservative estimate for the current menu structure
|
||||
}
|
||||
} else {
|
||||
menuHeight = popupContent.getHeight();
|
||||
}
|
||||
|
||||
// Add some margin for safety
|
||||
menuHeight += 20;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -20,15 +20,12 @@ 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;
|
||||
@ -38,8 +35,7 @@ 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.ui.FXUtils.determineOptimalPopupPosition;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
@ -105,7 +101,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
btnManage.setOnAction(e -> {
|
||||
currentSkinnable = skinnable;
|
||||
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root);
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup.get());
|
||||
popup.get().show(root, vPosition, JFXPopup.PopupHPosition.RIGHT, 0, vPosition == JFXPopup.PopupVPosition.TOP ? root.getHeight() : -root.getHeight());
|
||||
});
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
@ -132,45 +128,9 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
currentSkinnable = skinnable;
|
||||
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root);
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup.get());
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.parseColorEscapes;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@ -137,6 +138,8 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), item::export, popup),
|
||||
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.world"), item::reveal, popup));
|
||||
|
||||
popup.show(root, JFXPopup.PopupVPosition.TOP, hPosition, initOffsetX, initOffsetY);
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);
|
||||
|
||||
popup.show(root, vPosition, hPosition, initOffsetX, vPosition == JFXPopup.PopupVPosition.TOP ? initOffsetY : -initOffsetY);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user