JBR-9733 Wayland: enable unconstrained popup positioning

Use getRootPane()
.putClientProperty("wlawt.popup_position_unconstrained", Boolean.TRUE)
to enable unconstrained popup positioning.
This commit is contained in:
Maxim Kartashev
2025-12-02 15:06:36 +04:00
committed by jbrbot
parent 2a88cbe4cf
commit 682558af02
3 changed files with 134 additions and 14 deletions

View File

@@ -88,6 +88,8 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
private static final PlatformLogger focusLog = PlatformLogger.getLogger("sun.awt.wl.focus.WLComponentPeer");
private static final PlatformLogger popupLog = PlatformLogger.getLogger("sun.awt.wl.popup.WLComponentPeer");
public static final String POPUP_POSITION_UNCONSTRAINED_CLIENT_PROPERTY = "wlawt.popup_position_unconstrained";
protected static final int MINIMUM_WIDTH = 1;
protected static final int MINIMUM_HEIGHT = 1;
@@ -351,6 +353,18 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
return popupBounds.getLocation();
}
private boolean isPopupPositionUnconstrained() {
if (SunToolkit.isInstanceOf(target, "javax.swing.RootPaneContainer")) {
javax.swing.JRootPane rootpane = ((javax.swing.RootPaneContainer) target).getRootPane();
Object prop = rootpane.getClientProperty(POPUP_POSITION_UNCONSTRAINED_CLIENT_PROPERTY);
if (prop instanceof Boolean booleanProp) {
return booleanProp;
}
}
return false;
}
protected void wlSetVisible(boolean v) {
// TODO: this whole method should be moved to WLWindowPeer
synchronized (getStateLock()) {
@@ -370,6 +384,7 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
: Frame.NORMAL;
boolean isMaximized = (state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH;
boolean isMinimized = (state & Frame.ICONIFIED) == Frame.ICONIFIED;
boolean isUnconstrained = isPopupPositionUnconstrained();
performLocked(() -> {
assert wlSurface == null;
@@ -381,7 +396,7 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
Window toplevel = getToplevelFor(popupParent);
Point nativeLocation = nativeLocationForPopup(popup, popupParent, toplevel);
nativeCreatePopup(nativePtr, getNativePtrFor(toplevel), wlSurfacePtr,
thisWidth, thisHeight, nativeLocation.x, nativeLocation.y);
thisWidth, thisHeight, nativeLocation.x, nativeLocation.y, isUnconstrained);
} else {
nativeCreateWindow(nativePtr, getParentNativePtr(target), wlSurfacePtr,
isModal, isMaximized, isMinimized, title, WLToolkit.getApplicationID());
@@ -500,7 +515,8 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
final Component popupParent = AWTAccessor.getWindowAccessor().getPopupParent(popup);
final Window toplevel = getToplevelFor(popupParent);
Point nativeLocation = nativeLocationForPopup(popup, popupParent, toplevel);
nativeRepositionWLPopup(nativePtr, surfaceWidth, surfaceHeight, nativeLocation.x, nativeLocation.y);
boolean isUnconstrained = isPopupPositionUnconstrained();
nativeRepositionWLPopup(nativePtr, surfaceWidth, surfaceHeight, nativeLocation.x, nativeLocation.y, isUnconstrained);
}
}
@@ -1150,12 +1166,14 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
String title, String appID);
protected native void nativeCreatePopup(long ptr, long parentPtr, long wlSurfacePtr,
int width, int height,
int offsetX, int offsetY);
int width, int height,
int offsetX, int offsetY,
boolean isUnconstrained);
protected native void nativeRepositionWLPopup(long ptr,
int width, int height,
int offsetX, int offsetY);
int offsetX, int offsetY,
boolean isUnconstrained);
protected native void nativeHideFrame(long ptr);
protected native void nativeDisposeFrame(long ptr);

View File

@@ -387,7 +387,7 @@ Java_sun_awt_wl_WLComponentPeer_nativeCreateWindow
static struct xdg_positioner *
newPositioner
(jint width, jint height, jint offsetX, jint offsetY)
(jint width, jint height, jint offsetX, jint offsetY, jboolean isUnconstrained)
{
struct xdg_positioner *xdg_positioner = xdg_wm_base_create_positioner(xdg_wm_base);
CHECK_NULL_RETURN(xdg_positioner, NULL);
@@ -400,10 +400,15 @@ newPositioner
xdg_positioner_set_offset(xdg_positioner, 0, 0);
xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
xdg_positioner_set_constraint_adjustment(xdg_positioner,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
if (isUnconstrained) {
xdg_positioner_set_constraint_adjustment(xdg_positioner,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE);
} else {
xdg_positioner_set_constraint_adjustment(xdg_positioner,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
}
return xdg_positioner;
}
@@ -411,7 +416,8 @@ JNIEXPORT void JNICALL
Java_sun_awt_wl_WLComponentPeer_nativeCreatePopup
(JNIEnv *env, jobject obj, jlong ptr, jlong parentPtr, jlong wlSurfacePtr,
jint width, jint height,
jint offsetX, jint offsetY)
jint offsetX, jint offsetY,
jboolean isUnconstrained)
{
struct WLFrame *frame = (struct WLFrame *) ptr;
struct WLFrame *parentFrame = (struct WLFrame*) parentPtr;
@@ -423,7 +429,7 @@ Java_sun_awt_wl_WLComponentPeer_nativeCreatePopup
frame->toplevel = JNI_FALSE;
assert(parentFrame);
struct xdg_positioner *xdg_positioner = newPositioner(width, height, offsetX, offsetY);
struct xdg_positioner *xdg_positioner = newPositioner(width, height, offsetX, offsetY, isUnconstrained);
CHECK_NULL(xdg_positioner);
JNU_RUNTIME_ASSERT(env, parentFrame->toplevel, "Popup's parent surface must be a toplevel");
frame->xdg_popup = xdg_surface_get_popup(frame->xdg_surface, parentFrame->xdg_surface, xdg_positioner);
@@ -436,13 +442,14 @@ JNIEXPORT void JNICALL
Java_sun_awt_wl_WLComponentPeer_nativeRepositionWLPopup
(JNIEnv *env, jobject obj, jlong ptr,
jint width, jint height,
jint offsetX, jint offsetY)
jint offsetX, jint offsetY,
jboolean isUnconstrained)
{
struct WLFrame *frame = jlong_to_ptr(ptr);
assert (!frame->toplevel);
if (wl_proxy_get_version((struct wl_proxy *)xdg_wm_base) >= 3) {
struct xdg_positioner *xdg_positioner = newPositioner(width, height, offsetX, offsetY);
struct xdg_positioner *xdg_positioner = newPositioner(width, height, offsetX, offsetY, isUnconstrained);
CHECK_NULL(xdg_positioner);
static int token = 42; // This will be received by xdg_popup_repositioned(); unused for now.
xdg_popup_reposition(frame->xdg_popup, xdg_positioner, token++);

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2025 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.util.concurrent.CompletableFuture;
/**
* @test
* @summary Verifies popups can be located regardless of the screen edge proximity
* @requires os.family == "linux"
* @key headful
* @modules java.desktop/sun.awt
* @run main/manual WLPopupUnconstrained
*/
public class WLPopupUnconstrained {
static final CompletableFuture<RuntimeException> swingError = new CompletableFuture<>();
public static void main(String[] args) throws Exception {
SwingUtilities.invokeAndWait(WLPopupUnconstrained::showUI);
swingError.get();
}
private static void showUI() {
JFrame frame = new JFrame("Unconstrained popup test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Right-click here for a popup.");
JPanel popupContents = new JPanel();
popupContents.add(new JLabel("test popup"));
JWindow popup = new JWindow(frame);
popup.setType(Window.Type.POPUP);
sun.awt.AWTAccessor.getWindowAccessor().setPopupParent(popup, label);
popup.getRootPane().putClientProperty("wlawt.popup_position_unconstrained", Boolean.TRUE);
popup.setSize(300, 250);
popup.add(popupContents);
label.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.setLocation(e.getX(), e.getY());
popup.setVisible(true);
} else {
popup.setVisible(false);
}
}
});
JPanel content = new JPanel();
var layout = new GridLayout(3, 2, 10, 10);
content.setLayout(layout);
content.add(new JLabel("<html><h1>INSTRUCTIONS</h1>" +
"<p>Locate this window close to the bottom-right edge of the screen.</p>" +
"<p>Make the popup appear (on the right) such that it is partially outside the screen.</p>" +
"<p>Press Pass iff the popup indeed crossed the screen edge.</p>" +
"<p>Otherwise press Fail.</p></html>"));
content.add(label);
JButton passButton = new JButton("Pass");
passButton.addActionListener(e -> {swingError.complete(null);});
JButton failButton = new JButton("Fail");
failButton.addActionListener(e -> {swingError.completeExceptionally(new RuntimeException("The tester has pressed FAILED"));});
content.add(failButton);
content.add(passButton);
frame.setContentPane(content);
frame.pack();
frame.setVisible(true);
}
}