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 |
64 | { |
65 | /* < private >*/ |
66 | grefcount ; |
67 | |
68 | GdkRectangle ; |
69 | GdkGravity ; |
70 | GdkGravity ; |
71 | GdkAnchorHints ; |
72 | int ; |
73 | int ; |
74 | int shadow_left; |
75 | int shadow_right; |
76 | int shadow_top; |
77 | int shadow_bottom; |
78 | }; |
79 | |
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 | (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 (rc: &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 * |
130 | (GdkPopupLayout *layout) |
131 | { |
132 | g_ref_count_inc (rc: &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 |
143 | (GdkPopupLayout *layout) |
144 | { |
145 | if (g_ref_count_dec (rc: &layout->ref_count)) |
146 | g_free (mem: 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 * |
158 | (GdkPopupLayout *layout) |
159 | { |
160 | GdkPopupLayout *new_layout; |
161 | |
162 | new_layout = g_new0 (GdkPopupLayout, 1); |
163 | g_ref_count_init (rc: &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 |
190 | (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 (rect1: &layout->anchor_rect, rect2: &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 |
216 | (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 * |
231 | (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 |
244 | (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 |
259 | (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 |
272 | (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 |
287 | (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 |
306 | (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 |
321 | (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 |
335 | (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 |
352 | (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 |
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 |
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 | |