[gtk+/wip/attach-params: 1/20] gdkattachparams: add GdkAttachParams



commit 21acbf028efaaa213fb304b42b4b1cb363d0ab34
Author: William Hua <william hua canonical com>
Date:   Wed Jan 6 12:00:00 2016 -0500

    gdkattachparams: add GdkAttachParams
    
    https://bugzilla.gnome.org/show_bug.cgi?id=756579

 docs/reference/gdk/Makefile.am                |    2 +
 docs/reference/gdk/gdk-docs.sgml              |    1 +
 docs/reference/gdk/gdk3-sections.txt          |   27 +
 docs/reference/gdk/images/gdkattachparams.png |  Bin 0 -> 19297 bytes
 docs/reference/gdk/images/gdkattachparams.svg |  729 +++++++++++++++++++++
 docs/reference/gdk/images/gdkattachrule.png   |  Bin 0 -> 29487 bytes
 gdk/Makefile.am                               |    3 +
 gdk/gdk.h                                     |    1 +
 gdk/gdkattachparams.c                         |  847 +++++++++++++++++++++++++
 gdk/gdkattachparams.h                         |  153 +++++
 gdk/gdkattachparamsprivate.h                  |   75 +++
 11 files changed, 1838 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gdk/Makefile.am b/docs/reference/gdk/Makefile.am
index aef7cab..857c8db 100644
--- a/docs/reference/gdk/Makefile.am
+++ b/docs/reference/gdk/Makefile.am
@@ -85,6 +85,8 @@ HTML_IMAGES =                                 \
        images/draped_box.png           \
        images/exchange.png             \
        images/fleur.png                \
+       images/gdkattachparams.png      \
+       images/gdkattachrule.png        \
        images/gobbler.png              \
        images/gumby.png                \
        images/hand1.png                \
diff --git a/docs/reference/gdk/gdk-docs.sgml b/docs/reference/gdk/gdk-docs.sgml
index fdd36a4..6962b4d 100644
--- a/docs/reference/gdk/gdk-docs.sgml
+++ b/docs/reference/gdk/gdk-docs.sgml
@@ -47,6 +47,7 @@
     <xi:include href="xml/wayland_interaction.xml" />
     <xi:include href="xml/gdkapplaunchcontext.xml" />
     <xi:include href="xml/gdktestutils.xml" />
+    <xi:include href="xml/gdkattachparams.xml" />
   </reference>
 
   <reference>
diff --git a/docs/reference/gdk/gdk3-sections.txt b/docs/reference/gdk/gdk3-sections.txt
index 7b1b9f7..5bb6217 100644
--- a/docs/reference/gdk/gdk3-sections.txt
+++ b/docs/reference/gdk/gdk3-sections.txt
@@ -551,6 +551,33 @@ gdk_fullscreen_mode_get_type
 </SECTION>
 
 <SECTION>
+<TITLE>Attachment Parameters</TITLE>
+<FILE>gdkattachparams</FILE>
+GdkAttachParams
+GdkAttachRule
+GdkAttachCallback
+gdk_attach_params_new
+gdk_attach_params_free
+gdk_attach_params_set_attach_rect
+gdk_attach_params_set_attach_margin
+gdk_attach_params_set_window_padding
+gdk_attach_params_set_window_offset
+gdk_attach_params_add_primary_rules
+gdk_attach_params_add_secondary_rules
+gdk_attach_params_add_primary_rules_valist
+gdk_attach_params_add_secondary_rules_valist
+gdk_attach_params_primary_rules_foreach
+gdk_attach_params_secondary_rules_foreach
+gdk_attach_params_set_position_callback
+
+<SUBSECTION Private>
+_GdkAttachParams
+gdk_attach_params_choose_position
+gdk_attach_params_choose_position_for_window
+gdk_attach_params_move_window
+</SECTION>
+
+<SECTION>
 <TITLE>Selections</TITLE>
 <FILE>selections</FILE>
 GDK_SELECTION_PRIMARY
diff --git a/docs/reference/gdk/images/gdkattachparams.png b/docs/reference/gdk/images/gdkattachparams.png
new file mode 100644
index 0000000..8695098
Binary files /dev/null and b/docs/reference/gdk/images/gdkattachparams.png differ
diff --git a/docs/reference/gdk/images/gdkattachparams.svg b/docs/reference/gdk/images/gdkattachparams.svg
new file mode 100644
index 0000000..2d9c965
--- /dev/null
+++ b/docs/reference/gdk/images/gdkattachparams.svg
@@ -0,0 +1,729 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="520"
+   height="540"
+   viewBox="0 0 520.00001 540"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="gdkattachparams.svg">
+  <defs
+     id="defs4">
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker4541"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow2Mend">
+      <path
+         inkscape:connector-curvature="0"
+         transform="scale(-0.6,-0.6)"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 
-1.7354408,5.6174519 -6e-7,8.035443 z"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+         id="path4543" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker4477"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="DotM">
+      <path
+         inkscape:connector-curvature="0"
+         transform="matrix(0.4,0,0,0.4,2.96,0.4)"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+         id="path4479" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Mstart"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow2Mstart"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path4354"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 
-1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="scale(0.6,0.6)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Mstart"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Mstart"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path4336"
+         d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(0.4,0,0,0.4,4,0)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker9510"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow2Mend">
+      <path
+         transform="scale(-0.6,-0.6)"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 
-1.7354408,5.6174519 -6e-7,8.035443 z"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+         id="path9512"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="DotM"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="marker8880"
+       style="overflow:visible"
+       inkscape:isstock="true">
+      <path
+         id="path8882"
+         d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(0.4,0,0,0.4,2.96,0.4)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker4896"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="Arrow2Mend"
+       inkscape:collect="always">
+      <path
+         transform="scale(-0.6,-0.6)"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 
-1.7354408,5.6174519 -6e-7,8.035443 z"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+         id="path4898"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:isstock="true"
+       style="overflow:visible"
+       id="marker4868"
+       refX="0"
+       refY="0"
+       orient="auto"
+       inkscape:stockid="DotM"
+       inkscape:collect="always">
+      <path
+         transform="matrix(0.4,0,0,0.4,2.96,0.4)"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+         id="path4870"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Mend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow2Mend"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path4357"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+         d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 
-1.7354408,5.6174519 -6e-7,8.035443 z"
+         transform="scale(-0.6,-0.6)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       inkscape:stockid="DotM"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="DotM"
+       style="overflow:visible"
+       inkscape:isstock="true"
+       inkscape:collect="always">
+      <path
+         id="path4394"
+         d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+         
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+         transform="matrix(0.4,0,0,0.4,2.96,0.4)"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Drop Shadow"
+       id="filter4230">
+      <feFlood
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         id="feFlood4232" />
+      <feComposite
+         in="flood"
+         in2="SourceGraphic"
+         operator="in"
+         result="composite1"
+         id="feComposite4234" />
+      <feGaussianBlur
+         in="composite1"
+         stdDeviation="10"
+         result="blur"
+         id="feGaussianBlur4236" />
+      <feOffset
+         dx="0"
+         dy="0"
+         result="offset"
+         id="feOffset4238" />
+      <feComposite
+         in="SourceGraphic"
+         in2="offset"
+         operator="over"
+         result="fbSourceGraphic"
+         id="feComposite4240" />
+      <feColorMatrix
+         result="fbSourceGraphicAlpha"
+         in="fbSourceGraphic"
+         values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+         id="feColorMatrix4242" />
+      <feFlood
+         id="feFlood4244"
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         in="fbSourceGraphic" />
+      <feComposite
+         id="feComposite4246"
+         in2="fbSourceGraphic"
+         in="flood"
+         operator="out"
+         result="composite1" />
+      <feGaussianBlur
+         id="feGaussianBlur4248"
+         in="composite1"
+         stdDeviation="10"
+         result="blur" />
+      <feOffset
+         id="feOffset4250"
+         dx="0"
+         dy="0"
+         result="offset" />
+      <feComposite
+         id="feComposite4252"
+         in2="fbSourceGraphic"
+         in="offset"
+         operator="atop"
+         result="fbSourceGraphic" />
+      <feColorMatrix
+         result="fbSourceGraphicAlpha"
+         in="fbSourceGraphic"
+         values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+         id="feColorMatrix4254" />
+      <feFlood
+         id="feFlood4256"
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         in="fbSourceGraphic" />
+      <feComposite
+         id="feComposite4258"
+         in2="fbSourceGraphic"
+         in="flood"
+         operator="out"
+         result="composite1" />
+      <feGaussianBlur
+         id="feGaussianBlur4260"
+         in="composite1"
+         stdDeviation="10"
+         result="blur" />
+      <feOffset
+         id="feOffset4262"
+         dx="0"
+         dy="0"
+         result="offset" />
+      <feComposite
+         id="feComposite4264"
+         in2="fbSourceGraphic"
+         in="offset"
+         operator="atop"
+         result="composite2" />
+    </filter>
+    <filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Drop Shadow"
+       id="filter4268">
+      <feFlood
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         id="feFlood4270" />
+      <feComposite
+         in="flood"
+         in2="SourceGraphic"
+         operator="in"
+         result="composite1"
+         id="feComposite4272" />
+      <feGaussianBlur
+         in="composite1"
+         stdDeviation="20"
+         result="blur"
+         id="feGaussianBlur4274" />
+      <feOffset
+         dx="0"
+         dy="0"
+         result="offset"
+         id="feOffset4276" />
+      <feComposite
+         in="SourceGraphic"
+         in2="offset"
+         operator="over"
+         result="fbSourceGraphic"
+         id="feComposite4278" />
+      <feColorMatrix
+         result="fbSourceGraphicAlpha"
+         in="fbSourceGraphic"
+         values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+         id="feColorMatrix4280" />
+      <feFlood
+         id="feFlood4282"
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         in="fbSourceGraphic" />
+      <feComposite
+         id="feComposite4284"
+         in2="fbSourceGraphic"
+         in="flood"
+         operator="in"
+         result="composite1" />
+      <feGaussianBlur
+         id="feGaussianBlur4286"
+         in="composite1"
+         stdDeviation="20"
+         result="blur" />
+      <feOffset
+         id="feOffset4288"
+         dx="0"
+         dy="0"
+         result="offset" />
+      <feComposite
+         id="feComposite4290"
+         in2="offset"
+         in="offset"
+         operator="atop"
+         result="composite2" />
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="260"
+     inkscape:cy="269.99998"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1215"
+     inkscape:window-height="776"
+     inkscape:window-x="65"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1"
+     inkscape:snap-path-clip="true"
+     inkscape:snap-path-mask="true"
+     objecttolerance="10000"
+     inkscape:snap-bbox="true"
+     units="px" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-512.36216)">
+    <g
+       id="g7389"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <rect
+         y="772.36218"
+         x="0"
+         height="280"
+         width="440"
+         id="rect14784"
+         
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
 />
+      <g
+         id="g7355">
+        <rect
+           
style="fill:#0000ff;fill-opacity:0.50196078;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           id="rect3336"
+           width="99"
+           height="19"
+           x="220.5"
+           y="817.70105" />
+        <rect
+           
style="fill:#ff00ff;fill-opacity:0.50196078;stroke:#000000;stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           id="rect3338"
+           width="199"
+           height="99"
+           x="220.5"
+           y="837.70105" />
+        <path
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2,
 2;stroke-dashoffset:0;stroke-opacity:1"
+           d="m 220,937.20109 0,50"
+           id="path4292"
+           inkscape:connector-curvature="0" />
+        <text
+           xml:space="preserve"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           x="220.55762"
+           y="999.84265"
+           id="text4294"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4296"
+             x="220.55762"
+             y="999.84265">(GDK_ATTACH_AXIS_X |</tspan><tspan
+             sodipodi:role="line"
+             x="220.55762"
+             y="1012.3427"
+             id="tspan4304">GDK_ATTACH_RECT_MIN |</tspan><tspan
+             sodipodi:role="line"
+             x="220.55762"
+             y="1024.8427"
+             id="tspan4300">GDK_ATTACH_WINDOW_MIN)</tspan></text>
+        <text
+           xml:space="preserve"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           x="163.67676"
+           y="827.34265"
+           id="text4306"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4308"
+             x="163.67676"
+             y="827.34265">(GDK_ATTACH_AXIS_Y |</tspan><tspan
+             sodipodi:role="line"
+             x="163.67676"
+             y="839.84265"
+             id="tspan4310">GDK_ATTACH_RECT_MAX |</tspan><tspan
+             sodipodi:role="line"
+             x="163.67676"
+             y="852.34265"
+             id="tspan4312">GDK_ATTACH_WINDOW_MIN)</tspan></text>
+        <path
+           inkscape:connector-curvature="0"
+           id="path4314"
+           d="m 170,837.20109 50,0"
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2,
 2;stroke-dashoffset:0;stroke-opacity:1" />
+        <text
+           xml:space="preserve"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           x="299.92188"
+           y="805.12097"
+           id="text4316"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4318"
+             x="299.92188"
+             y="805.12097">Attachment Rectangle</tspan></text>
+        <text
+           xml:space="preserve"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           x="358.66797"
+           y="957.05945"
+           id="text4320"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan4322"
+             x="358.66797"
+             y="957.05945">Window</tspan></text>
+        <path
+           inkscape:connector-curvature="0"
+           id="path4866"
+           d="m 294.97321,802.29289 -19.04081,23.801"
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotM);marker-end:url(#Arrow2Mend)"
+           sodipodi:nodetypes="cc" />
+        <path
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker4868);marker-end:url(#marker4896)"
+           d="m 354.06249,953.42949 -14.28061,-31.338"
+           id="path4324"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="cc" />
+        <g
+           id="g14739"
+           transform="translate(236,-218.16109)">
+          <path
+             sodipodi:nodetypes="ccc"
+             inkscape:connector-curvature="0"
+             d="m -60,1102.3622 -40,0 0,40"
+             
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart);marker-end:url(#marker9510)"
+             id="path9508" />
+          <text
+             sodipodi:linespacing="125%"
+             id="text14727"
+             y="1153.2821"
+             x="-107.05957"
+             
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             xml:space="preserve"><tspan
+               id="tspan14735"
+               y="1153.2821"
+               x="-107.05957"
+               sodipodi:role="line">+y</tspan></text>
+          <text
+             sodipodi:linespacing="125%"
+             id="text14731"
+             y="1105.3622"
+             x="-56.05957"
+             
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             xml:space="preserve"><tspan
+               y="1105.3622"
+               x="-56.05957"
+               id="tspan14733"
+               sodipodi:role="line">+x</tspan></text>
+        </g>
+      </g>
+    </g>
+    <g
+       id="g7314"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90">
+      <rect
+         y="512.36218"
+         x="6.2500001e-09"
+         height="260"
+         width="520"
+         id="rect15010"
+         
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
 />
+      <g
+         id="g7275">
+        <rect
+           
style="fill:#7f7fff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+           id="rect14833"
+           width="159"
+           height="79"
+           x="195.9628"
+           y="614.86218" />
+        <text
+           xml:space="preserve"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           x="140.84619"
+           y="612.00378"
+           id="text14908"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan14910"
+             x="140.84619"
+             y="612.00378">GDK_ATTACH_AXIS_Y |</tspan><tspan
+             sodipodi:role="line"
+             x="140.84619"
+             y="624.50378"
+             id="tspan14912">GDK_ATTACH_RECT_MIN</tspan></text>
+        <text
+           sodipodi:linespacing="125%"
+           id="text14841"
+           y="543.75378"
+           x="196.36768"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           xml:space="preserve"><tspan
+             y="543.75378"
+             x="196.36768"
+             id="tspan14843"
+             sodipodi:role="line">GDK_ATTACH_AXIS_X |</tspan><tspan
+             id="tspan14845"
+             y="556.25378"
+             x="196.36768"
+             sodipodi:role="line">GDK_ATTACH_RECT_MIN</tspan></text>
+        <text
+           sodipodi:linespacing="125%"
+           id="text14914"
+           y="730.75256"
+           x="275.47546"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           xml:space="preserve"
+           transform="scale(0.9999983,1.0000017)"><tspan
+             y="730.75256"
+             x="275.47546"
+             id="tspan14916"
+             sodipodi:role="line">GDK_ATTACH_AXIS_X |</tspan><tspan
+             id="tspan14918"
+             y="743.25256"
+             x="275.47546"
+             sodipodi:role="line">GDK_ATTACH_RECT_MID</tspan></text>
+        <text
+           xml:space="preserve"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           x="382.88916"
+           y="650.75378"
+           id="text14902"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan14904"
+             x="382.88916"
+             y="650.75378">GDK_ATTACH_AXIS_Y |</tspan><tspan
+             sodipodi:role="line"
+             x="382.88916"
+             y="663.25378"
+             id="tspan14906">GDK_ATTACH_RECT_MID</tspan></text>
+        <text
+           xml:space="preserve"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           x="352.75311"
+           y="543.75378"
+           id="text14920"
+           sodipodi:linespacing="125%"><tspan
+             sodipodi:role="line"
+             id="tspan14922"
+             x="352.75311"
+             y="543.75378">GDK_ATTACH_AXIS_X |</tspan><tspan
+             sodipodi:role="line"
+             x="352.75311"
+             y="556.25378"
+             id="tspan14924">GDK_ATTACH_RECT_MAX</tspan></text>
+        <text
+           sodipodi:linespacing="125%"
+           id="text14847"
+           y="690.00378"
+           x="140.18213"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           xml:space="preserve"><tspan
+             y="690.00378"
+             x="140.18213"
+             id="tspan14849"
+             sodipodi:role="line">GDK_ATTACH_AXIS_Y |</tspan><tspan
+             id="tspan14851"
+             y="702.50378"
+             x="140.18213"
+             sodipodi:role="line">GDK_ATTACH_RECT_MAX</tspan></text>
+        <path
+           inkscape:connector-curvature="0"
+           id="path14928"
+           d="m 145.45068,693.61218 50,0"
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2,
 2;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2,
 2;stroke-dashoffset:0;stroke-opacity:1"
+           d="m 279.45068,654.61218 100,0"
+           id="path14932"
+           inkscape:connector-curvature="0" />
+        <path
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2,
 2;stroke-dashoffset:0;stroke-opacity:1"
+           d="m 196.45068,614.61218 0,-50"
+           id="path14934"
+           inkscape:connector-curvature="0" />
+        <path
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2,
 2;stroke-dashoffset:0;stroke-opacity:1"
+           d="m 354.45068,614.61218 0,-50"
+           id="path14936"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path14930"
+           d="m 275.45068,717.6122 0,-60"
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.00000002,
 2.00000002;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2,
 2;stroke-dashoffset:0;stroke-opacity:1"
+           d="m 145.45068,615.61218 50,0"
+           id="path14926"
+           inkscape:connector-curvature="0" />
+        <g
+           id="g14944"
+           transform="translate(94.710564,20.967103)">
+          <path
+             
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+             d="m 186.04342,628.34178 -10.6066,10.6066"
+             id="path14942"
+             inkscape:connector-curvature="0" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path14938"
+             d="m 186.04342,638.94838 -10.6066,-10.6066"
+             
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+        </g>
+        <g
+           transform="translate(511.45068,-406.74998)"
+           id="g14998">
+          <path
+             id="path15000"
+             
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart);marker-end:url(#marker9510)"
+             d="m -60,1102.3622 -40,0 0,40"
+             inkscape:connector-curvature="0"
+             sodipodi:nodetypes="ccc" />
+          <text
+             xml:space="preserve"
+             
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             x="-107.05957"
+             y="1153.2821"
+             id="text15002"
+             sodipodi:linespacing="125%"><tspan
+               sodipodi:role="line"
+               x="-107.05957"
+               y="1153.2821"
+               id="tspan15004">+y</tspan></text>
+          <text
+             xml:space="preserve"
+             
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             x="-56.05957"
+             y="1105.3622"
+             id="text15006"
+             sodipodi:linespacing="125%"><tspan
+               sodipodi:role="line"
+               id="tspan15008"
+               x="-56.05957"
+               y="1105.3622">+x</tspan></text>
+        </g>
+        <text
+           sodipodi:linespacing="125%"
+           id="text4471"
+           y="610.2821"
+           x="379.92188"
+           
style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           xml:space="preserve"><tspan
+             y="610.2821"
+             x="379.92188"
+             id="tspan4473"
+             sodipodi:role="line">Attachment Rectangle</tspan></text>
+        <path
+           sodipodi:nodetypes="cc"
+           
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker4477);marker-end:url(#marker4541)"
+           d="m 374.97321,608.29289 -33.04081,17.801"
+           id="path4475"
+           inkscape:connector-curvature="0" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/docs/reference/gdk/images/gdkattachrule.png b/docs/reference/gdk/images/gdkattachrule.png
new file mode 100644
index 0000000..54c1351
Binary files /dev/null and b/docs/reference/gdk/images/gdkattachrule.png differ
diff --git a/gdk/Makefile.am b/gdk/Makefile.am
index 84942ce..4314865 100644
--- a/gdk/Makefile.am
+++ b/gdk/Makefile.am
@@ -63,6 +63,7 @@ gdk_public_h_sources =                                \
        gdk.h                                   \
        gdk-autocleanup.h                       \
        gdkapplaunchcontext.h                   \
+       gdkattachparams.h                       \
        gdkcairo.h                              \
        gdkcursor.h                             \
        gdkdevice.h                             \
@@ -103,6 +104,7 @@ gdk_h_sources =                                     \
 gdk_private_headers =                          \
        gdk-private.h                           \
        gdkapplaunchcontextprivate.h            \
+       gdkattachparamsprivate.h                \
        gdkcursorprivate.h                      \
        gdkdevicemanagerprivate.h               \
        gdkdeviceprivate.h                      \
@@ -129,6 +131,7 @@ gdk_c_sources =                             \
        gdk-private.c                           \
        gdk.c                                   \
        gdkapplaunchcontext.c                   \
+       gdkattachparams.c                       \
        gdkcairo.c                              \
        gdkcursor.c                             \
        gdkdeprecated.c                         \
diff --git a/gdk/gdk.h b/gdk/gdk.h
index 81ba765..869f6bf 100644
--- a/gdk/gdk.h
+++ b/gdk/gdk.h
@@ -30,6 +30,7 @@
 #include <gdk/gdkconfig.h>
 #include <gdk/gdkversionmacros.h>
 #include <gdk/gdkapplaunchcontext.h>
+#include <gdk/gdkattachparams.h>
 #include <gdk/gdkcairo.h>
 #include <gdk/gdkcursor.h>
 #include <gdk/gdkdevice.h>
diff --git a/gdk/gdkattachparams.c b/gdk/gdkattachparams.c
new file mode 100644
index 0000000..bedee37
--- /dev/null
+++ b/gdk/gdkattachparams.c
@@ -0,0 +1,847 @@
+#include "config.h"
+
+#include "gdkattachparamsprivate.h"
+#include "gdkscreen.h"
+#include "gdkwindow.h"
+
+/**
+ * SECTION: gdkattachparams
+ * @section_id: gdkattachparams
+ * @title: Attachment Parameters
+ * @short_description: Describing relative window position
+ * @stability: Unstable
+ * @include: gdk/gdkattachparams.h
+ *
+ * A full description of how a window should be positioned relative to an
+ * attachment rectangle.
+ *
+ * Certain widgets such as menus and combo boxes don't require explicit
+ * absolute positioning; they only need to be aligned with respect to another
+ * anchoring widget, such as a menu item, in such a way to not overflow
+ * off-screen. GTK+ cannot always determine such an optimal position since it
+ * requires knowledge of the geometry of the monitor workarea as well as the
+ * ability to position windows in absolute root window coordinates, which some
+ * GDK backends do not support.
+ *
+ * A minimal #GdkAttachParams description should have an attachment rectangle,
+ * a list of primary constraints, and a list of secondary constraints. The
+ * attachment rectangle is the allocation of the anchoring widget, which can
+ * be a menu item, menu button, combo box, etc. It can even be a 1x1 pixel at
+ * the current cursor position. The primary constraints are a list of
+ * #GdkAttachRules in descending priority that the GDK backend can try in order
+ * to fix the position of the window either horizontally or vertically. The
+ * secondary constraints are a list of #GdkAttachRules in descending priority
+ * to fix the position of the window on the other axis.
+ *
+ * ![](gdkattachrule.png)
+ *
+ * A #GdkAttachRule is a bit mask that constrains the position of a window on a
+ * single axis: %GDK_ATTACH_AXIS_X or %GDK_ATTACH_AXIS_Y. It must also specify
+ * one of %GDK_ATTACH_RECT_MIN, %GDK_ATTACH_RECT_MID, or %GDK_ATTACH_RECT_MAX,
+ * and one of %GDK_ATTACH_WINDOW_MIN, %GDK_ATTACH_WINDOW_MID, or
+ * %GDK_ATTACH_WINDOW_MAX. The backend will make a best effort to align the
+ * window position to the attachment rectangle position on that axis, or move
+ * on to the next constraint if available.
+ *
+ * ![](gdkattachparams.png)
+ *
+ * There are also additional parameters that can be set to fine tune the
+ * positioning of the window, such as margins and paddings, as well as a
+ * callback to obtain the final position of the window.
+ *
+ * Since: 3.20
+ */
+
+/**
+ * gdk_attach_params_new:
+ *
+ * Creates a new #GdkAttachParams for describing the position of a #GdkWindow
+ * relative to an attachment #GdkRectangle.
+ *
+ * Returns: (transfer full): a new #GdkAttachParams, to be freed with
+ *          gdk_attach_params_free()
+ *
+ * Since: 3.20
+ */
+GdkAttachParams *
+gdk_attach_params_new (void)
+{
+  GdkAttachParams *params = g_new0 (GdkAttachParams, 1);
+
+  params->primary_rules = g_array_new (TRUE, TRUE, sizeof (GdkAttachRule));
+  params->secondary_rules = g_array_new (TRUE, TRUE, sizeof (GdkAttachRule));
+
+  return params;
+}
+
+/**
+ * gdk_attach_params_free:
+ * @data: the #GdkAttachParams to free
+ *
+ * Releases @data.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_free (gpointer data)
+{
+  GdkAttachParams *params;
+
+  g_return_if_fail (data);
+
+  params = data;
+
+  if (params->attach_user_data && params->attach_destroy_notify)
+    params->attach_destroy_notify (params->attach_user_data);
+
+  g_array_unref (params->secondary_rules);
+  g_array_unref (params->primary_rules);
+
+  g_clear_object (&params->attach_parent);
+
+  g_free (params);
+}
+
+/**
+ * gdk_attach_params_set_attach_rect:
+ * @params: a #GdkAttachParams
+ * @rectangle: (nullable): the attachment rectangle
+ * @parent: (nullable): the #GdkWindow that @rectangle is relative to
+ *
+ * Sets the attachment rectangle the window needs to be aligned relative to.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_set_attach_rect (GdkAttachParams    *params,
+                                   const GdkRectangle *rectangle,
+                                   GdkWindow          *parent)
+{
+  g_return_if_fail (params);
+
+  if (rectangle)
+    {
+      params->has_attach_rect = TRUE;
+      params->attach_rect = *rectangle;
+      g_set_object (&params->attach_parent, parent);
+    }
+  else
+    {
+      params->has_attach_rect = FALSE;
+      g_clear_object (&params->attach_parent);
+    }
+}
+
+/**
+ * gdk_attach_params_set_attach_margin:
+ * @params: a #GdkAttachParams
+ * @margin: (nullable): the space around the attachment rectangle
+ *
+ * Sets the amount of space to leave around the attachment rectangle.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_set_attach_margin (GdkAttachParams *params,
+                                     const GdkBorder *margin)
+{
+  GdkBorder zero = { 0 };
+
+  g_return_if_fail (params);
+
+  params->attach_margin = margin ? *margin : zero;
+}
+
+/**
+ * gdk_attach_params_set_window_padding:
+ * @params: a #GdkAttachParams
+ * @padding: (nullable): the space between the window and its
+ *           contents.
+ *
+ * Sets the amount of space between the window and its contents.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_set_window_padding (GdkAttachParams *params,
+                                      const GdkBorder *padding)
+{
+  GdkBorder zero = { 0 };
+
+  g_return_if_fail (params);
+
+  params->window_padding = padding ? *padding : zero;
+}
+
+/**
+ * gdk_attach_params_set_window_offset:
+ * @params: a #GdkAttachParams
+ * @x: horizontal displacement
+ * @y: vertical displacement
+ *
+ * Sets the offset to displace the window by.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_set_window_offset (GdkAttachParams *params,
+                                     gint             x,
+                                     gint             y)
+{
+  g_return_if_fail (params);
+
+  params->offset_x = x;
+  params->offset_y = y;
+}
+
+static void
+add_rules (GArray        *array,
+           GdkAttachRule  first_rule,
+           va_list        args)
+{
+  GdkAttachRule rule;
+
+  for (rule = first_rule; rule; rule = va_arg (args, GdkAttachRule))
+    g_array_append_val (array, rule);
+}
+
+/**
+ * gdk_attach_params_add_primary_rules_valist:
+ * @params: a #GdkAttachParams
+ * @first_rule: first primary rule
+ * @args: a #va_list of the remaining primary rules
+ *
+ * Non-variadic version of gdk_attach_params_add_primary_rules().
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_add_primary_rules_valist (GdkAttachParams *params,
+                                            GdkAttachRule    first_rule,
+                                            va_list          args)
+{
+  add_rules (params->primary_rules, first_rule, args);
+}
+
+/**
+ * gdk_attach_params_add_secondary_rules_valist:
+ * @params: a #GdkAttachParams
+ * @first_rule: first secondary rule
+ * @args: a #va_list of the remaining secondary rules
+ *
+ * Non-variadic version of gdk_attach_params_add_secondary_rules().
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_add_secondary_rules_valist (GdkAttachParams *params,
+                                              GdkAttachRule    first_rule,
+                                              va_list          args)
+{
+  add_rules (params->secondary_rules, first_rule, args);
+}
+
+/**
+ * gdk_attach_params_add_primary_rules:
+ * @params: a #GdkAttachParams
+ * @first_rule: first primary rule
+ * @...: a %NULL-terminated list of rules
+ *
+ * Appends to the list of primary positioning rules to try.
+ *
+ * A typical backend will try each primary rule in the order they're added. If
+ * a rule can be satisfied, it will then try each secondary rule until it
+ * finds a satisfiable secondary rule that doesn't conflict with the primary
+ * rule. If it finds a pair of satisfiable non-conflicting rules, then it will
+ * place the window there. If it cannot find a pair, it proceeds to the next
+ * primary rule and tries again.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_add_primary_rules (GdkAttachParams *params,
+                                     GdkAttachRule    first_rule,
+                                     ...)
+{
+  va_list args;
+
+  g_return_if_fail (params);
+
+  va_start (args, first_rule);
+
+  gdk_attach_params_add_primary_rules_valist (params, first_rule, args);
+
+  va_end (args);
+}
+
+/**
+ * gdk_attach_params_add_secondary_rules:
+ * @params: a #GdkAttachParams
+ * @first_rule: first secondary rule
+ * @...: a %NULL-terminated list of rules
+ *
+ * Appends to the list of secondary positioning rules to try.
+ *
+ * A typical backend will try each primary rule in the order they're added. If
+ * a rule can be satisfied, it will then try each secondary rule until it
+ * finds a satisfiable secondary rule that doesn't conflict with the primary
+ * rule. If it finds a pair of satisfiable non-conflicting rules, then it will
+ * place the window there. If it cannot find a pair, it proceeds to the next
+ * primary rule and tries again.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_add_secondary_rules (GdkAttachParams *params,
+                                       GdkAttachRule    first_rule,
+                                       ...)
+{
+  va_list args;
+
+  g_return_if_fail (params);
+
+  va_start (args, first_rule);
+
+  gdk_attach_params_add_secondary_rules_valist (params, first_rule, args);
+
+  va_end (args);
+}
+
+static void
+rules_foreach (GArray   *array,
+               GFunc     func,
+               gpointer  user_data)
+{
+  gint i;
+
+  for (i = 0; i < array->len; i++)
+    func (&g_array_index (array, GdkAttachRule, i), user_data);
+}
+
+/**
+ * gdk_attach_params_primary_rules_foreach:
+ * @params: a #GdkAttachParams
+ * @func: the function to call with each primary rule
+ * @user_data: (nullable): user data to pass to the function
+ *
+ * Applies @func to each primary rule in @params.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_primary_rules_foreach (GdkAttachParams *params,
+                                         GFunc            func,
+                                         gpointer         user_data)
+{
+  g_return_if_fail (params);
+  g_return_if_fail (func);
+
+  rules_foreach (params->primary_rules, func, user_data);
+}
+
+/**
+ * gdk_attach_params_secondary_rules_foreach:
+ * @params: a #GdkAttachParams
+ * @func: the function to call with each secondary rule
+ * @user_data: (nullable): user data to pass to the function
+ *
+ * Applies @func to each secondary rule in @params.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_secondary_rules_foreach (GdkAttachParams *params,
+                                           GFunc            func,
+                                           gpointer         user_data)
+{
+  g_return_if_fail (params);
+  g_return_if_fail (func);
+
+  rules_foreach (params->secondary_rules, func, user_data);
+}
+
+/**
+ * gdk_attach_params_set_position_callback:
+ * @params: a #GdkAttachParams
+ * @callback: (nullable): a function to be called when the final position of
+ *            the window is known
+ * @user_data: (transfer full) (nullable): additional data to pass to @callback
+ * @destroy_notify: (nullable): a function to release @user_data
+ *
+ * Sets the function to be called when the final position of the window is
+ * known.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_set_position_callback (GdkAttachParams   *params,
+                                         GdkAttachCallback  callback,
+                                         gpointer           user_data,
+                                         GDestroyNotify     destroy_notify)
+{
+  g_return_if_fail (params);
+
+  params->attach_callback = callback;
+
+  if (user_data != params->attach_user_data)
+    {
+      if (params->attach_user_data && params->attach_destroy_notify)
+        params->attach_destroy_notify (params->attach_user_data);
+
+      params->attach_user_data = user_data;
+      params->attach_destroy_notify = destroy_notify;
+    }
+  else if (user_data)
+    g_warning ("%s (): params already owns user data", G_STRFUNC);
+}
+
+static gboolean
+is_satisfiable (GdkAttachRule          rule,
+                const GdkAttachParams *params,
+                gint                   width,
+                gint                   height,
+                const GdkRectangle    *bounds,
+                gint                  *value)
+{
+  gboolean use_attach_margin;
+  gboolean use_window_padding;
+
+  g_return_val_if_fail (params, FALSE);
+  g_return_val_if_fail (params->has_attach_rect, FALSE);
+
+  use_attach_margin = (((rule & GDK_ATTACH_RECT_MASK)   == GDK_ATTACH_RECT_MIN &&
+                        (rule & GDK_ATTACH_WINDOW_MASK) == GDK_ATTACH_WINDOW_MAX) ||
+                       ((rule & GDK_ATTACH_RECT_MASK)   == GDK_ATTACH_RECT_MAX &&
+                        (rule & GDK_ATTACH_WINDOW_MASK) == GDK_ATTACH_WINDOW_MIN));
+
+  use_window_padding = TRUE;
+
+  *value = 0;
+
+  switch (rule & GDK_ATTACH_AXIS_MASK)
+    {
+    case GDK_ATTACH_AXIS_X:
+      if (params->attach_parent)
+        gdk_window_get_origin (params->attach_parent, value, NULL);
+
+      switch (rule & GDK_ATTACH_RECT_MASK)
+        {
+        case GDK_ATTACH_RECT_MIN:
+          *value += params->attach_rect.x;
+
+          if (use_attach_margin)
+            *value -= params->attach_margin.left;
+
+          break;
+
+        case GDK_ATTACH_RECT_MID:
+          *value += params->attach_rect.x + params->attach_rect.width / 2;
+          break;
+
+        case GDK_ATTACH_RECT_MAX:
+          *value += params->attach_rect.x + params->attach_rect.width;
+
+          if (use_attach_margin)
+            *value += params->attach_margin.right;
+
+          break;
+        }
+
+      switch (rule & GDK_ATTACH_WINDOW_MASK)
+        {
+        case GDK_ATTACH_WINDOW_MIN:
+          if (use_window_padding)
+            *value -= params->window_padding.left;
+
+          break;
+
+        case GDK_ATTACH_WINDOW_MID:
+          *value -= width / 2;
+          break;
+
+        case GDK_ATTACH_WINDOW_MAX:
+          *value -= width;
+
+          if (use_window_padding)
+            *value += params->window_padding.right;
+
+          break;
+        }
+
+      *value += params->offset_x;
+
+      return !bounds || (bounds->x <= *value && *value + width <= bounds->x + bounds->width);
+
+    case GDK_ATTACH_AXIS_Y:
+      if (params->attach_parent)
+        gdk_window_get_origin (params->attach_parent, NULL, value);
+
+      switch (rule & GDK_ATTACH_RECT_MASK)
+        {
+        case GDK_ATTACH_RECT_MIN:
+          *value += params->attach_rect.y;
+
+          if (use_attach_margin)
+            *value -= params->attach_margin.top;
+
+          break;
+
+        case GDK_ATTACH_RECT_MID:
+          *value += params->attach_rect.y + params->attach_rect.height / 2;
+          break;
+
+        case GDK_ATTACH_RECT_MAX:
+          *value += params->attach_rect.y + params->attach_rect.height;
+
+          if (use_attach_margin)
+            *value += params->attach_margin.bottom;
+
+          break;
+        }
+
+      switch (rule & GDK_ATTACH_WINDOW_MASK)
+        {
+        case GDK_ATTACH_WINDOW_MIN:
+          if (use_window_padding)
+            *value -= params->window_padding.top;
+
+          break;
+
+        case GDK_ATTACH_WINDOW_MID:
+          *value -= height / 2;
+          break;
+
+        case GDK_ATTACH_WINDOW_MAX:
+          *value -= height;
+
+          if (use_window_padding)
+            *value += params->window_padding.bottom;
+
+          break;
+        }
+
+      *value += params->offset_y;
+
+      return !bounds || (bounds->y <= *value && *value + height <= bounds->y + bounds->height);
+    }
+
+  return FALSE;
+}
+
+#define BEST      0
+#define GOOD      1
+#define PRIMARY   0
+#define SECONDARY 1
+#define X         0
+#define Y         1
+
+/**
+ * gdk_attach_params_choose_position:
+ * @params: a #GdkAttachParams
+ * @width: window width
+ * @height: window height
+ * @bounds: (nullable): monitor geometry
+ * @x: (out) (optional): the best x-coordinate for the window
+ * @y: (out) (optional): the best y-coordinate for the window
+ * @offset_x: (out) (optional): the horizontal displacement needed to push the
+ *            window on-screen
+ * @offset_y: (out) (optional): the vertical displacement needed to push the
+ *            window on-screen
+ * @primary_rule: (out) (optional): the best primary rule
+ * @secondary_rule: (out) (optional): the best secondary rule
+ *
+ * Finds the best position for a window of size @width and @height on a screen
+ * with @bounds using the given @params.
+ *
+ * Returns: %TRUE if there is a pair of satisfiable primary and secondary
+ *          rules that do not conflict with each other
+ *
+ * Since: 3.20
+ */
+gboolean
+gdk_attach_params_choose_position (const GdkAttachParams *params,
+                                   gint                   width,
+                                   gint                   height,
+                                   const GdkRectangle    *bounds,
+                                   gint                  *x,
+                                   gint                  *y,
+                                   gint                  *offset_x,
+                                   gint                  *offset_y,
+                                   GdkAttachRule         *primary_rule,
+                                   GdkAttachRule         *secondary_rule)
+{
+  gint tmp_x;
+  gint tmp_y;
+  gint tmp_offset_x;
+  gint tmp_offset_y;
+  GdkAttachRule pr;
+  GdkAttachRule sr;
+  GdkAttachRule rules[2][2][2] = { 0 };
+  gint axes[2][2] = { 0 };
+  gint values[2][2][2] = { 0 };
+  GArray *arrays[2];
+  gint i;
+  gint j;
+  gint k;
+  GdkAttachRule rule;
+  gint axis;
+  gint value;
+  gboolean satisfiable;
+  gboolean success;
+
+  g_return_val_if_fail (params, FALSE);
+  g_return_val_if_fail (params->has_attach_rect, FALSE);
+
+  if (!x)
+    x = &tmp_x;
+
+  if (!y)
+    y = &tmp_y;
+
+  if (!offset_x)
+    offset_x = &tmp_offset_x;
+
+  if (!offset_y)
+    offset_y = &tmp_offset_y;
+
+  if (!primary_rule)
+    primary_rule = &pr;
+
+  if (!secondary_rule)
+    secondary_rule = &sr;
+
+  arrays[PRIMARY] = params->primary_rules;
+  arrays[SECONDARY] = params->secondary_rules;
+
+  for (i = PRIMARY; i <= SECONDARY; i++)
+    {
+      for (j = 0; j < arrays[i]->len; j++)
+        {
+          rule = g_array_index (arrays[i], GdkAttachRule, j);
+
+          switch (rule & GDK_ATTACH_AXIS_MASK)
+            {
+            case GDK_ATTACH_AXIS_X:
+              axis = X;
+              break;
+
+            case GDK_ATTACH_AXIS_Y:
+              axis = Y;
+              break;
+
+            default:
+              axis = -1;
+              break;
+            }
+
+          if (axis < 0)
+            {
+              g_warning ("%s (): invalid constraint axis: 0x%X", G_STRFUNC, rule);
+              continue;
+            }
+
+          satisfiable = is_satisfiable (rule, params, width, height, bounds, &value);
+
+          if (satisfiable && !rules[i][BEST][axis])
+            {
+              rules[i][BEST][axis] = rule;
+              values[i][BEST][axis] = value;
+
+              if (rules[i][BEST][!axis])
+                break;
+              else
+                axes[i][BEST] = axis;
+            }
+          else if (!rules[i][GOOD][axis])
+            {
+              rules[i][GOOD][axis] = rule;
+              values[i][GOOD][axis] = value;
+
+              if (!rules[i][GOOD][!axis])
+                axes[i][GOOD] = axis;
+            }
+        }
+    }
+
+  success = FALSE;
+
+  for (i = BEST; i <= GOOD; i++)
+    {
+      for (j = BEST; j <= GOOD; j++)
+        {
+          for (k = Y; k >= X; k--)
+            {
+              if (rules[PRIMARY][i][axes[PRIMARY][i] == k] && rules[SECONDARY][j][axes[PRIMARY][i] != k])
+                {
+                  *primary_rule = rules[PRIMARY][i][axes[PRIMARY][i] == k];
+                  *secondary_rule = rules[SECONDARY][j][axes[PRIMARY][i] != k];
+
+                  if ((axes[PRIMARY][i] == k) == X)
+                    {
+                      *x = values[PRIMARY][i][X];
+                      *y = values[SECONDARY][j][Y];
+                    }
+                  else
+                    {
+                      *x = values[SECONDARY][j][X];
+                      *y = values[PRIMARY][i][Y];
+                    }
+
+                  *offset_x = 0;
+                  *offset_y = 0;
+                  success = TRUE;
+                  break;
+                }
+            }
+
+          if (success)
+            break;
+        }
+
+      if (success)
+        break;
+    }
+
+  if (success && bounds)
+    {
+      if (*x + width > bounds->x + bounds->width)
+        {
+          *offset_x += bounds->x + bounds->width - width - *x;
+          *x = bounds->x + bounds->width - width;
+        }
+
+      if (*x < bounds->x)
+        {
+          *offset_x += bounds->x - *x;
+          *x = bounds->x;
+        }
+
+      if (*y + height > bounds->y + bounds->height)
+        {
+          *offset_y += bounds->y + bounds->height - height - *y;
+          *y = bounds->y + bounds->height - height;
+        }
+
+      if (*y < bounds->y)
+        {
+          *offset_y += bounds->y - *y;
+          *y = bounds->y;
+        }
+    }
+
+  return success;
+}
+
+/**
+ * gdk_attach_params_choose_position_for_window:
+ * @params: a #GdkAttachParams
+ * @window: (transfer none) (not nullable): the #GdkWindow to find the best
+ *          position for
+ * @x: (out) (optional): the best x-coordinate for the window
+ * @y: (out) (optional): the best y-coordinate for the window
+ * @offset_x: (out) (optional): the horizontal displacement needed to push the
+ *            window on-screen
+ * @offset_y: (out) (optional): the vertical displacement needed to push the
+ *            window on-screen
+ * @primary_rule: (out) (optional): the best primary rule
+ * @secondary_rule: (out) (optional): the best secondary rule
+ *
+ * Finds the best position for @window according to @params.
+ *
+ * Returns: %TRUE if there's a best position
+ *
+ * Since: 3.20
+ */
+gboolean
+gdk_attach_params_choose_position_for_window (const GdkAttachParams *params,
+                                              GdkWindow             *window,
+                                              gint                  *x,
+                                              gint                  *y,
+                                              gint                  *offset_x,
+                                              gint                  *offset_y,
+                                              GdkAttachRule         *primary_rule,
+                                              GdkAttachRule         *secondary_rule)
+{
+  GdkScreen *screen;
+  gint origin_x;
+  gint origin_y;
+  gint center_x;
+  gint center_y;
+  gint monitor;
+  GdkRectangle bounds;
+  gint width;
+  gint height;
+
+  g_return_val_if_fail (params, FALSE);
+  g_return_val_if_fail (params->has_attach_rect, FALSE);
+  g_return_val_if_fail (window, FALSE);
+
+  screen = gdk_window_get_screen (window);
+  gdk_window_get_origin (window, &origin_x, &origin_y);
+  center_x = origin_x + params->attach_rect.x + params->attach_rect.width / 2;
+  center_y = origin_y + params->attach_rect.y + params->attach_rect.height / 2;
+  monitor = gdk_screen_get_monitor_at_point (screen, center_x, center_y);
+  gdk_screen_get_monitor_workarea (screen, monitor, &bounds);
+  width = gdk_window_get_width (window);
+  height = gdk_window_get_height (window);
+
+  return gdk_attach_params_choose_position (params,
+                                            width,
+                                            height,
+                                            &bounds,
+                                            x,
+                                            y,
+                                            offset_x,
+                                            offset_y,
+                                            primary_rule,
+                                            secondary_rule);
+}
+
+/**
+ * gdk_attach_params_move_window:
+ * @params: a #GdkAttachParams
+ * @window: (transfer none) (not nullable): the #GdkWindow to position
+ *
+ * Moves @window to the best position according to @params.
+ *
+ * Since: 3.20
+ */
+void
+gdk_attach_params_move_window (const GdkAttachParams *params,
+                               GdkWindow             *window)
+{
+  gint x;
+  gint y;
+  gint offset_x;
+  gint offset_y;
+  GdkAttachRule primary_rule;
+  GdkAttachRule secondary_rule;
+
+  g_return_if_fail (GDK_IS_WINDOW (window));
+
+  if (!params || !params->has_attach_rect)
+    return;
+
+  if (gdk_attach_params_choose_position_for_window (params,
+                                                    window,
+                                                    &x,
+                                                    &y,
+                                                    &offset_x,
+                                                    &offset_y,
+                                                    &primary_rule,
+                                                    &secondary_rule))
+    {
+      gdk_window_move (window, x, y);
+
+      if (params->attach_callback)
+        params->attach_callback (window,
+                                 params,
+                                 x,
+                                 y,
+                                 offset_x,
+                                 offset_y,
+                                 primary_rule,
+                                 secondary_rule,
+                                 params->attach_user_data);
+    }
+}
diff --git a/gdk/gdkattachparams.h b/gdk/gdkattachparams.h
new file mode 100644
index 0000000..cc30c60
--- /dev/null
+++ b/gdk/gdkattachparams.h
@@ -0,0 +1,153 @@
+#ifndef __GDK_ATTACH_PARAMS_H__
+#define __GDK_ATTACH_PARAMS_H__
+
+#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
+#error "Only <gdk/gdk.h> can be included directly."
+#endif
+
+#include <gdk/gdktypes.h>
+#include <gdk/gdkversionmacros.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GdkAttachRule:
+ * @GDK_ATTACH_UNKNOWN: not a constraint
+ * @GDK_ATTACH_AXIS_X: constrain horizontally
+ * @GDK_ATTACH_AXIS_Y: constrain vertically
+ * @GDK_ATTACH_AXIS_MASK: mask for constraint axis
+ * @GDK_ATTACH_RECT_MIN: left/top edge of rectangle
+ * @GDK_ATTACH_RECT_MID: center of rectangle
+ * @GDK_ATTACH_RECT_MAX: right/bottom edge of rectangle
+ * @GDK_ATTACH_RECT_MASK: mask for rectangle anchor
+ * @GDK_ATTACH_WINDOW_MIN: left/top edge of window
+ * @GDK_ATTACH_WINDOW_MID: center of window
+ * @GDK_ATTACH_WINDOW_MAX: right/bottom edge of window
+ * @GDK_ATTACH_WINDOW_MASK: mask for window anchor
+ *
+ * Constraints on the position of the window relative to its attachment
+ * rectangle.
+ *
+ * ![](gdkattachrule.png)
+ *
+ * Since: 3.20
+ */
+typedef enum _GdkAttachRule
+{
+  GDK_ATTACH_UNKNOWN     = 0,
+  GDK_ATTACH_AXIS_X      = 1 << 0,
+  GDK_ATTACH_AXIS_Y      = 1 << 1,
+  GDK_ATTACH_AXIS_MASK   = GDK_ATTACH_AXIS_X | GDK_ATTACH_AXIS_Y,
+  GDK_ATTACH_RECT_MIN    = 1 << 2,
+  GDK_ATTACH_RECT_MID    = 2 << 2,
+  GDK_ATTACH_RECT_MAX    = 3 << 2,
+  GDK_ATTACH_RECT_MASK   = GDK_ATTACH_RECT_MIN | GDK_ATTACH_RECT_MID | GDK_ATTACH_RECT_MAX,
+  GDK_ATTACH_WINDOW_MIN  = 1 << 4,
+  GDK_ATTACH_WINDOW_MID  = 2 << 4,
+  GDK_ATTACH_WINDOW_MAX  = 3 << 4,
+  GDK_ATTACH_WINDOW_MASK = GDK_ATTACH_WINDOW_MIN | GDK_ATTACH_WINDOW_MID | GDK_ATTACH_WINDOW_MAX
+} GdkAttachRule;
+
+/**
+ * GdkAttachParams:
+ *
+ * Opaque type containing the information needed to position a window relative
+ * to an attachment rectangle.
+ *
+ * Since: 3.20
+ */
+typedef struct _GdkAttachParams GdkAttachParams;
+
+/**
+ * GdkAttachCallback:
+ * @window: the #GdkWindow that was moved
+ * @params: (transfer none) (nullable): the #GdkAttachParams that was used
+ * @x: the final x-coordinate of @window
+ * @y: the final y-coordinate of @window
+ * @offset_x: the horizontal displacement applied to keep @window on-screen
+ * @offset_y: the vertical displacement applied to keep @window on-screen
+ * @primary_rule: the primary rule that was used for positioning. If unknown,
+ *                this will be %GDK_ATTACH_UNKNOWN
+ * @secondary_rule: the secondary rule that was used for positioning. If
+ *                  unknown, this will be %GDK_ATTACH_UNKNOWN
+ * @user_data: (transfer none) (nullable): the user data that was set on
+ *             @params
+ *
+ * A function that can be used to receive information about the final position
+ * of a window.
+ *
+ * Since: 3.20
+ */
+typedef void (*GdkAttachCallback) (GdkWindow             *window,
+                                   const GdkAttachParams *params,
+                                   gint                   x,
+                                   gint                   y,
+                                   gint                   offset_x,
+                                   gint                   offset_y,
+                                   GdkAttachRule          primary_rule,
+                                   GdkAttachRule          secondary_rule,
+                                   gpointer               user_data);
+
+GDK_AVAILABLE_IN_3_20
+GdkAttachParams * gdk_attach_params_new                        (void);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_free                       (gpointer               data);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_set_attach_rect            (GdkAttachParams       *params,
+                                                                const GdkRectangle    *rectangle,
+                                                                GdkWindow             *parent);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_set_attach_margin          (GdkAttachParams       *params,
+                                                                const GdkBorder       *margin);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_set_window_padding         (GdkAttachParams       *params,
+                                                                const GdkBorder       *padding);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_set_window_offset          (GdkAttachParams       *params,
+                                                                gint                   x,
+                                                                gint                   y);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_add_primary_rules_valist   (GdkAttachParams       *params,
+                                                                GdkAttachRule          first_rule,
+                                                                va_list                args);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_add_secondary_rules_valist (GdkAttachParams       *params,
+                                                                GdkAttachRule          first_rule,
+                                                                va_list                args);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_add_primary_rules          (GdkAttachParams       *params,
+                                                                GdkAttachRule          first_rule,
+                                                                ...) G_GNUC_NULL_TERMINATED;
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_add_secondary_rules        (GdkAttachParams       *params,
+                                                                GdkAttachRule          first_rule,
+                                                                ...) G_GNUC_NULL_TERMINATED;
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_primary_rules_foreach      (GdkAttachParams       *params,
+                                                                GFunc                  func,
+                                                                gpointer               user_data);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_secondary_rules_foreach    (GdkAttachParams       *params,
+                                                                GFunc                  func,
+                                                                gpointer               user_data);
+
+GDK_AVAILABLE_IN_3_20
+void              gdk_attach_params_set_position_callback      (GdkAttachParams       *params,
+                                                                GdkAttachCallback      callback,
+                                                                gpointer               user_data,
+                                                                GDestroyNotify         destroy_notify);
+
+G_END_DECLS
+
+#endif /* __GDK_ATTACH_PARAMS_H__ */
diff --git a/gdk/gdkattachparamsprivate.h b/gdk/gdkattachparamsprivate.h
new file mode 100644
index 0000000..1ccf45d
--- /dev/null
+++ b/gdk/gdkattachparamsprivate.h
@@ -0,0 +1,75 @@
+#ifndef __GDK_ATTACH_PARAMS_PRIVATE_H__
+#define __GDK_ATTACH_PARAMS_PRIVATE_H__
+
+#include "gdkattachparams.h"
+
+G_BEGIN_DECLS
+
+/*
+ * GdkAttachParams:
+ * @has_attach_rect: %TRUE if @attach_rect is valid
+ * @attach_rect: the attachment rectangle to attach the window to
+ * @attach_parent: the #GdkWindow that @attach_rect is relative to
+ * @attach_margin: the space to leave around @attach_rect
+ * @window_padding: the space between the window and its contents
+ * @offset_x: the horizontal offset to displace the window by
+ * @offset_y: the vertical offset to displace the window by
+ * @primary_rules: an array of primary #GdkAttachRule
+ * @secondary_rules: an array of secondary #GdkAttachRule
+ * @position_callback: a function to call when the final position is known
+ * @position_callback_user_data: additional data to pass to @position_callback
+ * @position_callback_destroy_notify: a function to free
+ *                                    @position_callback_user_data
+ *
+ * Opaque type containing the information needed to position a window relative
+ * to an attachment rectangle.
+ *
+ * Since: 3.20
+ */
+struct _GdkAttachParams
+{
+  /*< private >*/
+  gboolean has_attach_rect;
+  GdkRectangle attach_rect;
+  GdkWindow *attach_parent;
+
+  GdkBorder attach_margin;
+  GdkBorder window_padding;
+
+  gint offset_x;
+  gint offset_y;
+
+  GArray *primary_rules;
+  GArray *secondary_rules;
+
+  GdkAttachCallback attach_callback;
+  gpointer attach_user_data;
+  GDestroyNotify attach_destroy_notify;
+};
+
+gboolean gdk_attach_params_choose_position            (const GdkAttachParams *params,
+                                                       gint                   width,
+                                                       gint                   height,
+                                                       const GdkRectangle    *bounds,
+                                                       gint                  *x,
+                                                       gint                  *y,
+                                                       gint                  *offset_x,
+                                                       gint                  *offset_y,
+                                                       GdkAttachRule         *primary_rule,
+                                                       GdkAttachRule         *secondary_rule);
+
+gboolean gdk_attach_params_choose_position_for_window (const GdkAttachParams *params,
+                                                       GdkWindow             *window,
+                                                       gint                  *x,
+                                                       gint                  *y,
+                                                       gint                  *offset_x,
+                                                       gint                  *offset_y,
+                                                       GdkAttachRule         *primary_rule,
+                                                       GdkAttachRule         *secondary_rule);
+
+void     gdk_attach_params_move_window                (const GdkAttachParams *params,
+                                                       GdkWindow             *window);
+
+G_END_DECLS
+
+#endif /* __GDK_ATTACH_PARAMS_PRIVATE_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]