1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 2020 Red Hat
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  */
18 
19 #include "config.h"
20 
21 #include "gdkpopuplayout.h"
22 
23 #include "gdksurface.h"
24 
25 /**
26  * GdkPopupLayout:
27  *
28  * The `GdkPopupLayout` struct contains information that is
29  * necessary position a [iface@Gdk.Popup] relative to its parent.
30  *
31  * The positioning requires a negotiation with the windowing system,
32  * since it depends on external constraints, such as the position of
33  * the parent surface, and the screen dimensions.
34  *
35  * The basic ingredients are a rectangle on the parent surface,
36  * and the anchor on both that rectangle and the popup. The anchors
37  * specify a side or corner to place next to each other.
38  *
39  * ![Popup anchors](popup-anchors.png)
40  *
41  * For cases where placing the anchors next to each other would make
42  * the popup extend offscreen, the layout includes some hints for how
43  * to resolve this problem. The hints may suggest to flip the anchor
44  * position to the other side, or to 'slide' the popup along a side,
45  * or to resize it.
46  *
47  * ![Flipping popups](popup-flip.png)
48  *
49  * ![Sliding popups](popup-slide.png)
50  *
51  * These hints may be combined.
52  *
53  * Ultimatively, it is up to the windowing system to determine the position
54  * and size of the popup. You can learn about the result by calling
55  * [method@Gdk.Popup.get_position_x], [method@Gdk.Popup.get_position_y],
56  * [method@Gdk.Popup.get_rect_anchor] and [method@Gdk.Popup.get_surface_anchor]
57  * after the popup has been presented. This can be used to adjust the rendering.
58  * For example, [class@Gtk.Popover] changes its arrow position accordingly.
59  * But you have to be careful avoid changing the size of the popover, or it
60  * has to be presented again.
61  */
62 
63 struct _GdkPopupLayout
64 {
65   /* < private >*/
66   grefcount ref_count;
67 
68   GdkRectangle anchor_rect;
69   GdkGravity rect_anchor;
70   GdkGravity surface_anchor;
71   GdkAnchorHints anchor_hints;
72   int dx;
73   int dy;
74   int shadow_left;
75   int shadow_right;
76   int shadow_top;
77   int shadow_bottom;
78 };
79 
G_DEFINE_BOXED_TYPE(GdkPopupLayout,gdk_popup_layout,gdk_popup_layout_ref,gdk_popup_layout_unref)80 G_DEFINE_BOXED_TYPE (GdkPopupLayout, gdk_popup_layout,
81                      gdk_popup_layout_ref,
82                      gdk_popup_layout_unref)
83 
84 /**
85  * gdk_popup_layout_new: (constructor)
86  * @anchor_rect:  (not nullable): the anchor `GdkRectangle` to align @surface with
87  * @rect_anchor: the point on @anchor_rect to align with @surface's anchor point
88  * @surface_anchor: the point on @surface to align with @rect's anchor point
89  *
90  * Create a popup layout description.
91  *
92  * Used together with [method@Gdk.Popup.present] to describe how a popup
93  * surface should be placed and behave on-screen.
94  *
95  * @anchor_rect is relative to the top-left corner of the surface's parent.
96  * @rect_anchor and @surface_anchor determine anchor points on @anchor_rect
97  * and surface to pin together.
98  *
99  * The position of @anchor_rect's anchor point can optionally be offset using
100  * [method@Gdk.PopupLayout.set_offset], which is equivalent to offsetting the
101  * position of surface.
102  *
103  * Returns: (transfer full): newly created instance of `GdkPopupLayout`
104  */
105 GdkPopupLayout *
106 gdk_popup_layout_new (const GdkRectangle *anchor_rect,
107                       GdkGravity          rect_anchor,
108                       GdkGravity          surface_anchor)
109 {
110   GdkPopupLayout *layout;
111 
112   layout = g_new0 (GdkPopupLayout, 1);
113   g_ref_count_init (&layout->ref_count);
114   layout->anchor_rect = *anchor_rect;
115   layout->rect_anchor = rect_anchor;
116   layout->surface_anchor = surface_anchor;
117 
118   return layout;
119 }
120 
121 /**
122  * gdk_popup_layout_ref:
123  * @layout: a `GdkPopupLayout`
124  *
125  * Increases the reference count of @value.
126  *
127  * Returns: the same @layout
128  */
129 GdkPopupLayout *
gdk_popup_layout_ref(GdkPopupLayout * layout)130 gdk_popup_layout_ref (GdkPopupLayout *layout)
131 {
132   g_ref_count_inc (&layout->ref_count);
133   return layout;
134 }
135 
136 /**
137  * gdk_popup_layout_unref:
138  * @layout: a `GdkPopupLayout`
139  *
140  * Decreases the reference count of @value.
141  */
142 void
gdk_popup_layout_unref(GdkPopupLayout * layout)143 gdk_popup_layout_unref (GdkPopupLayout *layout)
144 {
145   if (g_ref_count_dec (&layout->ref_count))
146     g_free (layout);
147 }
148 
149 /**
150  * gdk_popup_layout_copy:
151  * @layout: a `GdkPopupLayout`
152  *
153  * Makes a copy of @layout.
154  *
155  * Returns: (transfer full): a copy of @layout.
156  */
157 GdkPopupLayout *
gdk_popup_layout_copy(GdkPopupLayout * layout)158 gdk_popup_layout_copy (GdkPopupLayout *layout)
159 {
160   GdkPopupLayout *new_layout;
161 
162   new_layout = g_new0 (GdkPopupLayout, 1);
163   g_ref_count_init (&new_layout->ref_count);
164 
165   new_layout->anchor_rect = layout->anchor_rect;
166   new_layout->rect_anchor = layout->rect_anchor;
167   new_layout->surface_anchor = layout->surface_anchor;
168   new_layout->anchor_hints = layout->anchor_hints;
169   new_layout->dx = layout->dx;
170   new_layout->dy = layout->dy;
171   new_layout->shadow_left = layout->shadow_left;
172   new_layout->shadow_right = layout->shadow_right;
173   new_layout->shadow_top = layout->shadow_top;
174   new_layout->shadow_bottom = layout->shadow_bottom;
175 
176   return new_layout;
177 }
178 
179 /**
180  * gdk_popup_layout_equal:
181  * @layout: a `GdkPopupLayout`
182  * @other: another `GdkPopupLayout`
183  *
184  * Check whether @layout and @other has identical layout properties.
185  *
186  * Returns: %TRUE if @layout and @other have identical layout properties,
187  *   otherwise %FALSE.
188  */
189 gboolean
gdk_popup_layout_equal(GdkPopupLayout * layout,GdkPopupLayout * other)190 gdk_popup_layout_equal (GdkPopupLayout *layout,
191                         GdkPopupLayout *other)
192 {
193   g_return_val_if_fail (layout, FALSE);
194   g_return_val_if_fail (other, FALSE);
195 
196   return (gdk_rectangle_equal (&layout->anchor_rect, &other->anchor_rect) &&
197           layout->rect_anchor == other->rect_anchor &&
198           layout->surface_anchor == other->surface_anchor &&
199           layout->anchor_hints == other->anchor_hints &&
200           layout->dx == other->dx &&
201           layout->dy == other->dy &&
202           layout->shadow_left == other->shadow_left &&
203           layout->shadow_right == other->shadow_right &&
204           layout->shadow_top == other->shadow_top &&
205           layout->shadow_bottom == other->shadow_bottom);
206 }
207 
208 /**
209  * gdk_popup_layout_set_anchor_rect:
210  * @layout: a `GdkPopupLayout`
211  * @anchor_rect: the new anchor rectangle
212  *
213  * Set the anchor rectangle.
214  */
215 void
gdk_popup_layout_set_anchor_rect(GdkPopupLayout * layout,const GdkRectangle * anchor_rect)216 gdk_popup_layout_set_anchor_rect (GdkPopupLayout     *layout,
217                                   const GdkRectangle *anchor_rect)
218 {
219   layout->anchor_rect = *anchor_rect;
220 }
221 
222 /**
223  * gdk_popup_layout_get_anchor_rect:
224  * @layout: a `GdkPopupLayout`
225  *
226  * Get the anchor rectangle.
227  *
228  * Returns: The anchor rectangle
229  */
230 const GdkRectangle *
gdk_popup_layout_get_anchor_rect(GdkPopupLayout * layout)231 gdk_popup_layout_get_anchor_rect (GdkPopupLayout *layout)
232 {
233   return &layout->anchor_rect;
234 }
235 
236 /**
237  * gdk_popup_layout_set_rect_anchor:
238  * @layout: a `GdkPopupLayout`
239  * @anchor: the new rect anchor
240  *
241  * Set the anchor on the anchor rectangle.
242  */
243 void
gdk_popup_layout_set_rect_anchor(GdkPopupLayout * layout,GdkGravity anchor)244 gdk_popup_layout_set_rect_anchor (GdkPopupLayout *layout,
245                                   GdkGravity      anchor)
246 {
247   layout->rect_anchor = anchor;
248 }
249 
250 /**
251  * gdk_popup_layout_get_rect_anchor:
252  * @layout: a `GdkPopupLayout`
253  *
254  * Returns the anchor position on the anchor rectangle.
255  *
256  * Returns: the anchor on the anchor rectangle.
257  */
258 GdkGravity
gdk_popup_layout_get_rect_anchor(GdkPopupLayout * layout)259 gdk_popup_layout_get_rect_anchor (GdkPopupLayout *layout)
260 {
261   return layout->rect_anchor;
262 }
263 
264 /**
265  * gdk_popup_layout_set_surface_anchor:
266  * @layout: a `GdkPopupLayout`
267  * @anchor: the new popup surface anchor
268  *
269  * Set the anchor on the popup surface.
270  */
271 void
gdk_popup_layout_set_surface_anchor(GdkPopupLayout * layout,GdkGravity anchor)272 gdk_popup_layout_set_surface_anchor (GdkPopupLayout *layout,
273                                      GdkGravity      anchor)
274 {
275   layout->surface_anchor = anchor;
276 }
277 
278 /**
279  * gdk_popup_layout_get_surface_anchor:
280  * @layout: a `GdkPopupLayout`
281  *
282  * Returns the anchor position on the popup surface.
283  *
284  * Returns: the anchor on the popup surface.
285  */
286 GdkGravity
gdk_popup_layout_get_surface_anchor(GdkPopupLayout * layout)287 gdk_popup_layout_get_surface_anchor (GdkPopupLayout *layout)
288 {
289   return layout->surface_anchor;
290 }
291 
292 /**
293  * gdk_popup_layout_set_anchor_hints:
294  * @layout: a `GdkPopupLayout`
295  * @anchor_hints: the new `GdkAnchorHints`
296  *
297  * Set new anchor hints.
298  *
299  * The set @anchor_hints determines how @surface will be moved
300  * if the anchor points cause it to move off-screen. For example,
301  * %GDK_ANCHOR_FLIP_X will replace %GDK_GRAVITY_NORTH_WEST with
302  * %GDK_GRAVITY_NORTH_EAST and vice versa if @surface extends
303  * beyond the left or right edges of the monitor.
304  */
305 void
gdk_popup_layout_set_anchor_hints(GdkPopupLayout * layout,GdkAnchorHints anchor_hints)306 gdk_popup_layout_set_anchor_hints (GdkPopupLayout *layout,
307                                    GdkAnchorHints  anchor_hints)
308 {
309   layout->anchor_hints = anchor_hints;
310 }
311 
312 /**
313  * gdk_popup_layout_get_anchor_hints:
314  * @layout: a `GdkPopupLayout`
315  *
316  * Get the `GdkAnchorHints`.
317  *
318  * Returns: the `GdkAnchorHints`
319  */
320 GdkAnchorHints
gdk_popup_layout_get_anchor_hints(GdkPopupLayout * layout)321 gdk_popup_layout_get_anchor_hints (GdkPopupLayout *layout)
322 {
323   return layout->anchor_hints;
324 }
325 
326 /**
327  * gdk_popup_layout_set_offset:
328  * @layout: a `GdkPopupLayout`
329  * @dx: x delta to offset the anchor rectangle with
330  * @dy: y delta to offset the anchor rectangle with
331  *
332  * Offset the position of the anchor rectangle with the given delta.
333  */
334 void
gdk_popup_layout_set_offset(GdkPopupLayout * layout,int dx,int dy)335 gdk_popup_layout_set_offset (GdkPopupLayout *layout,
336                              int             dx,
337                              int             dy)
338 {
339   layout->dx = dx;
340   layout->dy = dy;
341 }
342 
343 /**
344  * gdk_popup_layout_get_offset:
345  * @layout: a `GdkPopupLayout`
346  * @dx: (out): return location for the delta X coordinate
347  * @dy: (out): return location for the delta Y coordinate
348  *
349  * Retrieves the offset for the anchor rectangle.
350  */
351 void
gdk_popup_layout_get_offset(GdkPopupLayout * layout,int * dx,int * dy)352 gdk_popup_layout_get_offset (GdkPopupLayout *layout,
353                              int            *dx,
354                              int            *dy)
355 {
356   if (dx)
357     *dx = layout->dx;
358   if (dy)
359     *dy = layout->dy;
360 }
361 
362 /**
363  * gdk_popup_layout_set_shadow_width:
364  * @layout: a `GdkPopupLayout`
365  * @left: width of the left part of the shadow
366  * @right: width of the right part of the shadow
367  * @top: height of the top part of the shadow
368  * @bottom: height of the bottom part of the shadow
369  *
370  * Sets the shadow width of the popup.
371  *
372  * The shadow width corresponds to the part of the computed
373  * surface size that would consist of the shadow margin
374  * surrounding the window, would there be any.
375  *
376  * Since: 4.2
377  */
378 void
gdk_popup_layout_set_shadow_width(GdkPopupLayout * layout,int left,int right,int top,int bottom)379 gdk_popup_layout_set_shadow_width (GdkPopupLayout *layout,
380                                    int             left,
381                                    int             right,
382                                    int             top,
383                                    int             bottom)
384 {
385   layout->shadow_left = left;
386   layout->shadow_right = right;
387   layout->shadow_top = top;
388   layout->shadow_bottom = bottom;
389 }
390 
391 /**
392  * gdk_popup_layout_get_shadow_width:
393  * @layout: a `GdkPopupLayout`
394  * @left: (out): return location for the left shadow width
395  * @right: (out): return location for the right shadow width
396  * @top: (out): return location for the top shadow width
397  * @bottom: (out): return location for the bottom shadow width
398  *
399  * Obtains the shadow widths of this layout.
400  *
401  * Since: 4.2
402  */
403 void
gdk_popup_layout_get_shadow_width(GdkPopupLayout * layout,int * left,int * right,int * top,int * bottom)404 gdk_popup_layout_get_shadow_width (GdkPopupLayout *layout,
405                                    int            *left,
406                                    int            *right,
407                                    int            *top,
408                                    int            *bottom)
409 {
410   if (left)
411     *left = layout->shadow_left;
412   if (right)
413     *right = layout->shadow_right;
414   if (top)
415     *top = layout->shadow_top;
416   if (bottom)
417     *bottom = layout->shadow_bottom;
418 }
419