JBR-7879 Wayland: Self-moving quick-doc popup in nightly

- Position popups at the exact offset given; this is achieved by
  using the XDG_POSITIONER_ANCHOR_TOP_LEFT anchor
- Update of popups location is done in sync with all other updates
  that affect the size (like the surface size update)
- Maintain a popup's location relative to the popup's parent, not
  its toplevel window
This commit is contained in:
Maxim Kartashev
2024-11-19 18:57:09 +04:00
parent a139bc7866
commit 4a50c3cc82
2 changed files with 72 additions and 73 deletions

View File

@@ -123,6 +123,7 @@ public class WLComponentPeer implements ComponentPeer {
int displayScale; // protected by dataLock
double effectiveScale; // protected by dataLock
private final WLSize wlSize = new WLSize();
boolean repositionPopup = false; // protected by dataLock
static {
initIDs();
@@ -278,6 +279,12 @@ public class WLComponentPeer implements ComponentPeer {
return null;
}
private static Component realParentFor(Component c) {
return (c instanceof Window window && isWlPopup(window))
? AWTAccessor.getWindowAccessor().getPopupParent(window)
: c.getParent();
}
static Point getRelativeLocation(Component c, Window toplevel) {
Objects.requireNonNull(c);
@@ -286,26 +293,24 @@ public class WLComponentPeer implements ComponentPeer {
}
int x = 0, y = 0;
while (c != null) {
if (c instanceof Window window) {
// The location of non-popup windows has no relevance since
// there are no absolute coordinates in Wayland.
// The popup windows position, on the other hand, is set relative to their
// parent toplevel.
if (isWlPopup(window)) {
x += c.getX();
y += c.getY();
}
break;
}
while (c != null && c != toplevel) {
x += c.getX();
y += c.getY();
c = c.getParent();
c = realParentFor(c);
}
return new Point(x, y);
}
Point nativeLocationForPopup(Window popup, Component popupParent, Window toplevel) {
// We need to provide popup's "parent" location relative to the surface this parent is painted upon:
Point parentLocation = javaUnitsToSurfaceUnits(getRelativeLocation(popupParent, toplevel));
// Offset is relative to the top-left corner of the "parent".
Point offsetFromParent = javaUnitsToSurfaceUnits(popup.getLocation());
return new Point(parentLocation.x + offsetFromParent.x, parentLocation.y + offsetFromParent.y);
}
protected void wlSetVisible(boolean v) {
synchronized (getStateLock()) {
if (this.visible == v) return;
@@ -327,30 +332,12 @@ public class WLComponentPeer implements ComponentPeer {
performLocked(() -> {
if (isWlPopup) {
Window popup = (Window) target;
final Component popupParent = AWTAccessor.getWindowAccessor().getPopupParent(popup);
final Window toplevel = getToplevelFor(popupParent);
// We need to provide popup "parent" location relative to
// the surface it is painted upon:
final Point toplevelLocation = getRelativeLocation(popupParent, toplevel);
final int parentX = javaUnitsToSurfaceUnits(toplevelLocation.x);
final int parentY = javaUnitsToSurfaceUnits(toplevelLocation.y);
// Offset must be relative to the top-left corner of the "parent".
final Point offsetFromParent = popup.getLocation();
final int offsetX = javaUnitsToSurfaceUnits(offsetFromParent.x);
final int offsetY = javaUnitsToSurfaceUnits(offsetFromParent.y);
if (popupLog.isLoggable(PlatformLogger.Level.FINE)) {
popupLog.fine("New popup: " + popup);
popupLog.fine("\tparent:" + popupParent);
popupLog.fine("\ttoplevel: " + toplevel);
popupLog.fine("\toffset of anchor from toplevel: " + toplevelLocation);
popupLog.fine("\toffset from anchor: " + offsetFromParent);
}
Component popupParent = AWTAccessor.getWindowAccessor().getPopupParent(popup);
Window toplevel = getToplevelFor(popupParent);
Point nativeLocation = nativeLocationForPopup(popup, popupParent, toplevel);
nativeCreateWLPopup(nativePtr, getNativePtrFor(toplevel),
thisWidth, thisHeight,
parentX + offsetX, parentY + offsetY);
nativeLocation.x, nativeLocation.y);
} else {
int xNative = javaUnitsToSurfaceUnits(target.getX());
int yNative = javaUnitsToSurfaceUnits(target.getY());
@@ -421,6 +408,18 @@ public class WLComponentPeer implements ComponentPeer {
if (surfaceMaxSize != null) {
nativeSetMaximumSize(nativePtr, surfaceMaxSize.width, surfaceMaxSize.height);
}
if (popupNeedsReposition()) {
popupRepositioned();
// Since popup's reposition request includes both its size and location, the request
// needs to be in sync with all the other sizes this method is responsible for updating.
Window popup = (Window) target;
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);
}
}
void configureWLSurface() {
@@ -505,11 +504,28 @@ public class WLComponentPeer implements ComponentPeer {
}
}
private void setLocationTo(int newX, int newY) {
private void resetTargetLocationTo(int newX, int newY) {
var acc = AWTAccessor.getComponentAccessor();
acc.setLocation(target, newX, newY);
}
private boolean popupNeedsReposition() {
synchronized (dataLock) {
return repositionPopup;
}
}
private void markPopupNeedsReposition() {
synchronized (dataLock) {
repositionPopup = true;
}
}
private void popupRepositioned() {
synchronized (dataLock) {
repositionPopup = false;
}
}
public void setBounds(int newX, int newY, int newWidth, int newHeight, int op) {
Dimension newSize = constrainSize(newWidth, newHeight);
boolean positionChanged = (op == SET_BOUNDS || op == SET_LOCATION);
@@ -527,9 +543,7 @@ public class WLComponentPeer implements ComponentPeer {
if ((positionChanged || sizeChanged) && isPopup && visible) {
// Need to update the location and size even if does not (yet) have a surface
// as the initial configure event needs to have the latest data on the location/size.
repositionWlPopup(newX, newY, newSize.width, newSize.height);
// the location will be updated in notifyConfigured() following
// the xdg_popup::repositioned event
markPopupNeedsReposition();
}
if (positionChanged) {
@@ -546,9 +560,9 @@ public class WLComponentPeer implements ComponentPeer {
layout();
WLToolkit.postEvent(new ComponentEvent(getTarget(), ComponentEvent.COMPONENT_RESIZED));
}
postPaintEvent();
postPaintEvent(); // no need to repaint after being moved, only when resized
}
}
boolean isSizeBeingConfigured() {
@@ -564,43 +578,17 @@ public class WLComponentPeer implements ComponentPeer {
}
private void setSizeTo(int newWidth, int newHeight) {
Dimension newSize = constrainSize(newWidth, newHeight);
if (isSizeBeingConfigured() && wlSize.hasPixelSizeSet()) {
// Must be careful not to override the size of the Wayland surface because
// some implementations (Weston) react badly when the size of the surface
// mismatches the configured size. We can't always precisely derive the surface
// size from the Java (client) size because of scaling rounding errors.
wlSize.setJavaSize(newSize.width, newSize.height);
wlSize.setJavaSize(newWidth, newHeight);
} else {
wlSize.deriveFromJavaSize(newSize.width, newSize.height);
wlSize.deriveFromJavaSize(newWidth, newHeight);
}
}
private void repositionWlPopup(int newX, int newY, int newWidth, int newHeight) {
performLocked(() -> {
Window popup = (Window) target;
final Component popupParent = AWTAccessor.getWindowAccessor().getPopupParent(popup);
final Window toplevel = getToplevelFor(popupParent);
// We need to provide popup "parent" location relative to
// the surface it is painted upon:
final Point toplevelLocation = getRelativeLocation(popupParent, toplevel);
final int parentX = javaUnitsToSurfaceUnits(toplevelLocation.x);
final int parentY = javaUnitsToSurfaceUnits(toplevelLocation.y);
int newXNative = javaUnitsToSurfaceUnits(newX);
int newYNative = javaUnitsToSurfaceUnits(newY);
int newWidthNative = javaUnitsToSurfaceUnits(newWidth);
int newHeightNative = javaUnitsToSurfaceUnits(newHeight);
if (popupLog.isLoggable(Level.FINE)) {
popupLog.fine("Repositioning popup: " + popup);
popupLog.fine("\tparent:" + popupParent);
popupLog.fine("\ttoplevel: " + toplevel);
popupLog.fine("\toffset of anchor from toplevel: " + toplevelLocation);
popupLog.fine("\toffset from anchor: " + newX + ", " + newY);
}
nativeRepositionWLPopup(nativePtr, newWidthNative, newHeightNative, parentX + newXNative, parentY + newYNative);
} );
}
public int getBufferWidth() {
return wlSize.getPixelWidth();
}
@@ -1548,23 +1536,34 @@ public class WLComponentPeer implements ComponentPeer {
}
}
Point javaUnitsToSurfaceUnits(Point p) {
return new Point(javaUnitsToSurfaceUnits(p.x), javaUnitsToSurfaceUnits(p.y));
}
Dimension javaUnitsToSurfaceUnits(Dimension d) {
return new Dimension(javaUnitsToSurfaceUnits(d.width), javaUnitsToSurfaceUnits(d.height));
}
void notifyConfigured(int newSurfaceX, int newSurfaceY, int newSurfaceWidth, int newSurfaceHeight, boolean active, boolean maximized) {
// NB: The width and height, as well as X and Y arguments specify the size and the location
// NB: The width and height, as well as X and Y arguments, specify the size and the location
// of the window in surface-local coordinates.
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine(String.format("%s configured to %dx%d surface units", this, newSurfaceWidth, newSurfaceHeight));
}
boolean isWlPopup = targetIsWlPopup();
if (isWlPopup) { // Only popups provide (relative) location
int newX = surfaceUnitsToJavaUnits(newSurfaceX);
int newY = surfaceUnitsToJavaUnits(newSurfaceY);
setLocationTo(newX, newY);
// The popup itself stores its location relative to its parent, but what we've got is
// the location relative to the toplevel. Let's convert:
Window popup = (Window) target;
Component popupParent = AWTAccessor.getWindowAccessor().getPopupParent(popup);
Window toplevel = getToplevelFor(popupParent);
Point parentLocation = getRelativeLocation(popupParent, toplevel);
Point locationRelativeToParent = new Point(newX - parentLocation.x, newY - parentLocation.y);
resetTargetLocationTo(locationRelativeToParent.x, locationRelativeToParent.y);
}
// From xdg-shell.xml: "If the width or height arguments are zero,

View File

@@ -513,7 +513,7 @@ newPositioner
xdg_positioner_set_size(xdg_positioner, width, height);
xdg_positioner_set_anchor_rect(xdg_positioner, offsetX, offsetY, 1, 1);
xdg_positioner_set_offset(xdg_positioner, 0, 0);
xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT);
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