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