[wayland-protocols] xdg-shell: Add support for synchronized popup moving

Submitted by Jonas Ådahl on July 25, 2019, 1:55 p.m.

Details

Message ID 20190725135513.25293-1-jadahl@gmail.com
State New
Headers show
Series "xdg-shell: Add support for synchronized popup moving" ( rev: 1 ) in Wayland

Not browsing as part of any series.

Commit Message

Jonas Ådahl July 25, 2019, 1:55 p.m.
This commit adds protocol additions making it possible to move an
already mapped popup.

Moving can be done in two ways: implicit and explicit.

Implicit popup moving is done by setting a adjustment flag on the
positioner used to create it that will cause the compositor to adjust
the position as the conditions used to constrain it change.

These changes may include, for example, changes in the position of the
parent window or the geometry of the work area. To allow the client to
update its content in response to the updated position, the client must
ack the configure event, optionally with new content. Until the client
acks this configure event, the existing positioner will continue to be
used.

Explicit popup moving is done using a new request on xdg_popup:
xdg_popup.move. What it does is change the parameters used for
positioning a popup by providing a new xdg_positioner object. This
request is coupled with a new event; xdg_popup.moved, sent together with
the configure events (xdg_popup.configure and xdg_surface.configure) to
notify about the completion of the move request. The move request also
takes a token that is later passed via the moved event; this is done so
that a client may determine for which move request the compositor has
sent configure events.

Both these methods by themself are racy regarding inter-surface
synchronization. In order to avoid race conditions when applying new
states, a new request is also added to xdg_surface:
xdg_surface.sync_with_popup. This request is a one-shot request to defer
application of the to be committed surface state until the state of the
passed popup surface is applied.

Lastly, a request to couple a xdg_positioner object with a configure
event is added in order for a compositor to pair a popup move request
with a pending configure event. This is necessary to, for example,
properly constrain a popup given a yet-to-be-applied parent state. An
example of when this may be necessary is an interactive resize where
both the toplevel position and the relative popup position changes.

Signed-off-by: Jonas Ådahl <jadahl@gmail.com>
Reviewed-by: Mike Blumenkrantz <michael.blumenkrantz@gmail.com>
---

Hi,

These additions aims to make it possible to move a xdg_popup in relation
to its parent in a way that creates a synchronized update between both
the parent and child.

A couple of examples of use cases this is needed for:

 * Popover surfaces

A popover in Gtk can, in contrast to GtkMenu, move around. These include
the popover itself changing size, the widget it is aligned to moves or
changes size, the parent window is resized causing the widget the
popover is aligned to moving along with it. In Gtk3 popovers has been
implemented using subsurfaces, but this means we don't get the
constraint logic resulting in popovers tending to end up outside of the
screen estate.

 * Auto-completion surfaces

In GNOME Builder there has been a long standing issue regarding popups
due to their inability to move once mapped.

The currently available work-arounds for these types issues are
currently either to use subsurfaces, or remapping with a new positioner
object. Both these work-arounds are of course inadequate.


They way moving intends to be done depends on how the move is initiated.

A couple of examples describing how moving is intended to be performed
to get a synchronized result:

Example A:

 (1) Consider the following window (illustrated with ASCII graphics).
     Assume the widget is aligned to the right edge of the parent
     window. The popover is positioned so that it pops up south of
     the widget, horizontally centered to it.


   +---------------------+
   | Toplevel       ___. . . . Widget
   |                | |  |
   |                ---  |
   |                _^_  |
   +---------------|   |-+
                   |   |. . . Popover
                    ---

 (2) If the toplevel is resized horizontally, the widget moves as it is
     resized. A final or intermediate result may be:

   +---------------------------+
   | Toplevel             ___. . . . Widget
   |                      | |  |
   |                      ---  |
   |                      _^_  |
   +---------------------|   |-+
                         |   |. . . Popover
                          ---

The protocol flow for this could, seen from a clients perspective be:

 --> xdg_toplevel.configure(200, 600)
 --> xdg_popup_surface.configure(3)
 <-- xdg_popup_surface.ack_configure(3)
 <-- xdg_popup_surface.sync_with_popup(xdg_popup)
 <-- xdg_positioner = xdg_wm_base.create_positioner()
     ... set positioner parameters ...
 <-- xdg_positioner.set_parent_configure_serial(3)
 <-- xdg_popup.move(xdg_positioner, 0)
     ... draw and attach new toplevel state ...
 <-- wl_toplevel_surface.commit()
 --> xdg_popup.moved(0)
 --> xdg_popup.configure(520, 20, 100, 200)
 --> xdg_popup_surface.configure(2)
 <-- xdg_popup_surface.ack_configure(2)
     .. draw and attach new popup state ...
 <-- wl_popup_surface.commit()

As the parent explicitly synchronized with the popup, once the popup is
committed, both state will be applied atomically.


Example B:

 (1) Consider the following window. The Popover is a menu is aligned
     northward from a widget, horizontally centered to it. If the number
     elements changes (e.g. one goes into a submenu), the size of the
     popover changes, thus needs to be moved to still be aligned
     properly.

          ___  . . . Popover
         | - |
         | - |
   +-----| - |----+
   |     | - |    | . . . Toplevel
   |      -v-     |
   |       _      |
   |      |_| . . . Widget
   +--------------+
        

 (2) The end result may for example be as following. The popover
     position effectively changed.

          ___  . . . Popover
   +-----| - |----+
   |     | - |    | . . . Toplevel
   |      -v-     |
   |       _      |
   |      |_| . . . Widget
   +--------------+

The protocol flow for this could, seen from a clients perspective be:

 <-- xdg_positioner = xdg_wm_base.create_positioner()
     ... set positioner parameters for the updated size ...
 <-- xdg_popup.move(xdg_positioner, 10)
 --> xdg_popup.moved(10)
 --> xdg_popup.configure(150, -20, 100, 200)
 --> xdg_popup_surface.configure(12)
 <-- xdg_popup_surface.ack_configure(12)
     .. draw and attach new popup state ...
 <-- wl_popup_surface.commit()



Example C:

 (1) Consider the following window setup. The popover is aligned
     northward by default, but has the y-flip flag set. If set reactive,
     the popup should flip its Y position would it be moved upwards.

Patch hide | download patch | download mbox

==========================  . . . Top screen edge
          ___  . . . Popover
         | - |
         | - |
   +-----| - |----+
   |     | - |    | . . . Toplevel
   |      -v-     |
   |       _      |
   |      |_| . . . Widget
   +--------------+

 (2) The post-y-flipped state.

==========================  . . . Top screen edge
   +--------------+
   |              | . . . Toplevel
   |              |
   |       _      |
   |      |_| . . . Widget
   +----- _^_ ----+
         | - |
         | - |
         | - | . . . Popover
         | - |
          --- 

The protocol flow for this could, seen from a clients perspective be:

 <-- xdg_positioner.set_reactive()
 <-- xdg_surface.get_popup(..., xdg_positioner, ..)
 <-- wl_popup_surface.commit()
 --> xdg_popup.configure(200, -75)
 --> xdg_popup_surface.configure(12)
 <-- xdg_popup_surface.ack_configure(12)
     ... drawn etc ...
 <-- wl_popup_surface.commit()
     ... indirectly dragged by its parent ...
 --> xdg_popup.configure(200, 200)
 --> xdg_popup_surface.configure(123)
 <-- xdg_popup_surface.ack_configure(123)
     ... draw and attach new popup state ...
 <-- wl_popup_surface.commit()


At the time of writing this, there is a Gtk4 implementation of this
available here: https://gitlab.gnome.org/jadahl/gtk/commits/wip/xdg-popup-move

I also have the server side implemented, but not in a presentable state
quite yet.


Jonas

 stable/xdg-shell/xdg-shell.xml | 84 ++++++++++++++++++++++++++++++++--
 1 file changed, 79 insertions(+), 5 deletions(-)

diff --git a/stable/xdg-shell/xdg-shell.xml b/stable/xdg-shell/xdg-shell.xml
index 3a87a9e..e9cd72b 100644
--- a/stable/xdg-shell/xdg-shell.xml
+++ b/stable/xdg-shell/xdg-shell.xml
@@ -29,7 +29,7 @@ 
     DEALINGS IN THE SOFTWARE.
   </copyright>
 
-  <interface name="xdg_wm_base" version="2">
+  <interface name="xdg_wm_base" version="3">
     <description summary="create desktop-style surfaces">
       The xdg_wm_base interface is exposed as a global object enabling clients
       to turn their wl_surfaces into windows in a desktop environment. It
@@ -115,7 +115,7 @@ 
     </event>
   </interface>
 
-  <interface name="xdg_positioner" version="2">
+  <interface name="xdg_positioner" version="3">
     <description summary="child surface positioner">
       The xdg_positioner provides a collection of rules for the placement of a
       child surface relative to a parent surface. Rules can be defined to ensure
@@ -357,9 +357,32 @@ 
       <arg name="x" type="int" summary="surface position x offset"/>
       <arg name="y" type="int" summary="surface position y offset"/>
     </request>
+
+    <!-- Version 3 additions -->
+
+    <request name="set_reactive" since="3">
+      <description summary="continuously reconstrain the surface">
+	When set reactive, the surface is reconstrained if the conditions used
+	for constraining changed, e.g. the parent window moved.
+
+	When set reactive, an xdg_popup.configure event is sent with updated
+	geometry, followed by an xdg_surface.configure event, which will
+	reflect the newly-calculated constrained geometry for the popup.
+      </description>
+    </request>
+
+    <request name="set_parent_configure_serial" since="3">
+      <description summary="set serial number of parent configure event">
+	Set the serial of a xdg_surface.configure event this positioner will be
+	used in response to. The compositor can use this to make assumptions about
+	how the popup will be positioned on the next commit.
+      </description>
+      <arg name="serial" type="uint"
+	   summary="serial of parent configure event"/>
+    </request>
   </interface>
 
-  <interface name="xdg_surface" version="2">
+  <interface name="xdg_surface" version="3">
     <description summary="desktop user interface surface base interface">
       An interface that may be implemented by a wl_surface, for
       implementations that provide a desktop-style user interface.
@@ -526,9 +549,24 @@ 
       </description>
       <arg name="serial" type="uint" summary="serial of the configure event"/>
     </event>
+
+    <!-- Version 3 additions -->
+
+    <request name="sync_with_popup" since="3">
+      <description summary="synchronize with popup state">
+	Defer applying this surface's state until the state of the specified
+	xdg_popup surface is next applied.
+
+	Repeatedly calling this method has no additional effect until after the
+	next commit.
+
+	The direct parent of the popup must be this surface.
+      </description>
+      <arg name="popup" type="object" interface="xdg_popup"/>
+    </request>
   </interface>
 
-  <interface name="xdg_toplevel" version="2">
+  <interface name="xdg_toplevel" version="3">
     <description summary="toplevel surface">
       This interface defines an xdg_surface role which allows a surface to,
       among other things, set window-like properties such as maximize,
@@ -1019,7 +1057,7 @@ 
     </event>
   </interface>
 
-  <interface name="xdg_popup" version="2">
+  <interface name="xdg_popup" version="3">
     <description summary="short-lived, popup surfaces for menus">
       A popup surface is a short-lived, temporary surface. It can be used to
       implement for example menus, popovers, tooltips and other similar user
@@ -1143,5 +1181,41 @@ 
       </description>
     </event>
 
+    <!-- Version 3 additions -->
+
+    <request name="move" since="3">
+      <description summary="recalculate the popup's location">
+	Move an already-mapped popup. The popup will be placed given the
+	details in the passed xdg_positioner object, and a xdg_popup.moved
+	followed by xdg_popup.configure and xdg_surface.configure will be
+	emitted in response.
+
+	The passed token will be sent in the corresponding xdg_popup.moved
+	event. The new popup position will not take effect until the
+	corresponding configure event is acknowledged by the client. See
+	xdg_popup.moved for details. The token itself is opaque, and has no
+	other special meaning.
+      </description>
+      <arg name="positioner" type="object" interface="xdg_positioner"/>
+      <arg name="token" type="uint" summary="move request token"/>
+    </request>
+
+    <event name="moved" since="3">
+      <description summary="signal the completion of a move request">
+	The moved event is to notify about the completion of a xdg_popup.move
+	request. The token argument is the token passed in the xdg_popup.move
+	request.
+
+	Immediately after this event is emitted, xdg_popup.configure and
+	xdg_surface.configure will be sent with the updated size and position,
+	as well as a new configure serial.
+
+	The client should optionally update the content of the popup, but must
+	acknowledge the new popup configuration for the new position to take
+	effect. See xdg_surface.ack_configure for details.
+      </description>
+      <arg name="token" type="uint" summary="move request token"/>
+    </event>
+
   </interface>
 </protocol>