1 | /* gtkfixedlayout.c: Fixed positioning layout manager |
2 | * |
3 | * SPDX-License-Identifier: LGPL-2.1-or-later |
4 | * |
5 | * Copyright 2019 GNOME Foundation |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | /** |
22 | * GtkFixedLayout: |
23 | * |
24 | * `GtkFixedLayout` is a layout manager which can place child widgets |
25 | * at fixed positions. |
26 | * |
27 | * Most applications should never use this layout manager; fixed positioning |
28 | * and sizing requires constant recalculations on where children need to be |
29 | * positioned and sized. Other layout managers perform this kind of work |
30 | * internally so that application developers don't need to do it. Specifically, |
31 | * widgets positioned in a fixed layout manager will need to take into account: |
32 | * |
33 | * - Themes, which may change widget sizes. |
34 | * |
35 | * - Fonts other than the one you used to write the app will of course |
36 | * change the size of widgets containing text; keep in mind that |
37 | * users may use a larger font because of difficulty reading the |
38 | * default, or they may be using a different OS that provides different |
39 | * fonts. |
40 | * |
41 | * - Translation of text into other languages changes its size. Also, |
42 | * display of non-English text will use a different font in many |
43 | * cases. |
44 | * |
45 | * In addition, `GtkFixedLayout` does not pay attention to text direction and |
46 | * thus may produce unwanted results if your app is run under right-to-left |
47 | * languages such as Hebrew or Arabic. That is: normally GTK will order |
48 | * containers appropriately depending on the text direction, e.g. to put labels |
49 | * to the right of the thing they label when using an RTL language; |
50 | * `GtkFixedLayout` won't be able to do that for you. |
51 | * |
52 | * Finally, fixed positioning makes it kind of annoying to add/remove UI |
53 | * elements, since you have to reposition all the other elements. This is a |
54 | * long-term maintenance problem for your application. |
55 | */ |
56 | |
57 | /** |
58 | * GtkFixedLayoutChild: |
59 | * |
60 | * `GtkLayoutChild` subclass for children in a `GtkFixedLayout`. |
61 | */ |
62 | |
63 | #include "config.h" |
64 | |
65 | #include "gtkfixedlayout.h" |
66 | |
67 | #include "gtkintl.h" |
68 | #include "gtklayoutchild.h" |
69 | #include "gtkprivate.h" |
70 | #include "gtkwidgetprivate.h" |
71 | |
72 | #include <graphene-gobject.h> |
73 | |
74 | struct _GtkFixedLayout |
75 | { |
76 | GtkLayoutManager parent_instance; |
77 | }; |
78 | |
79 | struct _GtkFixedLayoutChild |
80 | { |
81 | GtkLayoutChild parent_instance; |
82 | |
83 | GskTransform *transform; |
84 | }; |
85 | |
86 | enum |
87 | { |
88 | PROP_CHILD_TRANSFORM = 1, |
89 | |
90 | N_CHILD_PROPERTIES |
91 | }; |
92 | |
93 | static GParamSpec *child_props[N_CHILD_PROPERTIES]; |
94 | |
95 | G_DEFINE_TYPE (GtkFixedLayoutChild, gtk_fixed_layout_child, GTK_TYPE_LAYOUT_CHILD) |
96 | |
97 | static void |
98 | gtk_fixed_layout_child_set_property (GObject *gobject, |
99 | guint prop_id, |
100 | const GValue *value, |
101 | GParamSpec *pspec) |
102 | { |
103 | GtkFixedLayoutChild *self = GTK_FIXED_LAYOUT_CHILD (ptr: gobject); |
104 | |
105 | switch (prop_id) |
106 | { |
107 | case PROP_CHILD_TRANSFORM: |
108 | gtk_fixed_layout_child_set_transform (child: self, transform: g_value_get_boxed (value)); |
109 | break; |
110 | |
111 | default: |
112 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
113 | break; |
114 | } |
115 | } |
116 | |
117 | static void |
118 | gtk_fixed_layout_child_get_property (GObject *gobject, |
119 | guint prop_id, |
120 | GValue *value, |
121 | GParamSpec *pspec) |
122 | { |
123 | GtkFixedLayoutChild *self = GTK_FIXED_LAYOUT_CHILD (ptr: gobject); |
124 | |
125 | switch (prop_id) |
126 | { |
127 | case PROP_CHILD_TRANSFORM: |
128 | g_value_set_boxed (value, v_boxed: &self->transform); |
129 | break; |
130 | |
131 | default: |
132 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
133 | break; |
134 | } |
135 | } |
136 | |
137 | static void |
138 | gtk_fixed_layout_child_finalize (GObject *gobject) |
139 | { |
140 | GtkFixedLayoutChild *self = GTK_FIXED_LAYOUT_CHILD (ptr: gobject); |
141 | |
142 | gsk_transform_unref (self: self->transform); |
143 | |
144 | G_OBJECT_CLASS (gtk_fixed_layout_child_parent_class)->finalize (gobject); |
145 | } |
146 | |
147 | static void |
148 | gtk_fixed_layout_child_class_init (GtkFixedLayoutChildClass *klass) |
149 | { |
150 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
151 | |
152 | gobject_class->set_property = gtk_fixed_layout_child_set_property; |
153 | gobject_class->get_property = gtk_fixed_layout_child_get_property; |
154 | gobject_class->finalize = gtk_fixed_layout_child_finalize; |
155 | |
156 | /** |
157 | * GtkFixedLayoutChild:transform: (attributes org.gtk.Property.get=gtk_fixed_layout_child_get_transform org.gtk.Property.set=gtk_fixed_layout_child_set_transform) |
158 | * |
159 | * The transform of the child. |
160 | */ |
161 | child_props[PROP_CHILD_TRANSFORM] = |
162 | g_param_spec_boxed (name: "transform" , |
163 | P_("transform" ), |
164 | P_("The transform of a child of a fixed layout" ), |
165 | GSK_TYPE_TRANSFORM, |
166 | flags: G_PARAM_READWRITE | |
167 | G_PARAM_STATIC_STRINGS | |
168 | G_PARAM_EXPLICIT_NOTIFY); |
169 | |
170 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_CHILD_PROPERTIES, pspecs: child_props); |
171 | } |
172 | |
173 | static void |
174 | gtk_fixed_layout_child_init (GtkFixedLayoutChild *self) |
175 | { |
176 | } |
177 | |
178 | /** |
179 | * gtk_fixed_layout_child_set_transform: (attributes org.gtk.Method.set_property=transform) |
180 | * @child: a `GtkFixedLayoutChild` |
181 | * @transform: a `GskTransform` |
182 | * |
183 | * Sets the transformation of the child of a `GtkFixedLayout`. |
184 | */ |
185 | void |
186 | gtk_fixed_layout_child_set_transform (GtkFixedLayoutChild *child, |
187 | GskTransform *transform) |
188 | { |
189 | GtkLayoutManager *layout; |
190 | |
191 | g_return_if_fail (GTK_IS_FIXED_LAYOUT_CHILD (child)); |
192 | |
193 | gsk_transform_unref (self: child->transform); |
194 | child->transform = gsk_transform_ref (self: transform); |
195 | |
196 | layout = gtk_layout_child_get_layout_manager (layout_child: GTK_LAYOUT_CHILD (ptr: child)); |
197 | gtk_layout_manager_layout_changed (manager: layout); |
198 | |
199 | g_object_notify_by_pspec (G_OBJECT (child), pspec: child_props[PROP_CHILD_TRANSFORM]); |
200 | } |
201 | |
202 | /** |
203 | * gtk_fixed_layout_child_get_transform: |
204 | * @child: a `GtkFixedLayoutChild` |
205 | * |
206 | * Retrieves the transformation of the child. |
207 | * |
208 | * Returns: (transfer none) (nullable): a `GskTransform` |
209 | */ |
210 | GskTransform * |
211 | gtk_fixed_layout_child_get_transform (GtkFixedLayoutChild *child) |
212 | { |
213 | g_return_val_if_fail (GTK_IS_FIXED_LAYOUT_CHILD (child), NULL); |
214 | |
215 | return child->transform; |
216 | } |
217 | |
218 | G_DEFINE_TYPE (GtkFixedLayout, gtk_fixed_layout, GTK_TYPE_LAYOUT_MANAGER) |
219 | |
220 | static GtkSizeRequestMode |
221 | gtk_fixed_layout_get_request_mode (GtkLayoutManager *layout_manager, |
222 | GtkWidget *widget) |
223 | { |
224 | return GTK_SIZE_REQUEST_CONSTANT_SIZE; |
225 | } |
226 | |
227 | static void |
228 | gtk_fixed_layout_measure (GtkLayoutManager *layout_manager, |
229 | GtkWidget *widget, |
230 | GtkOrientation orientation, |
231 | int for_size, |
232 | int *minimum, |
233 | int *natural, |
234 | int *minimum_baseline, |
235 | int *natural_baseline) |
236 | { |
237 | GtkFixedLayoutChild *child_info; |
238 | GtkWidget *child; |
239 | int minimum_size = 0; |
240 | int natural_size = 0; |
241 | |
242 | for (child = _gtk_widget_get_first_child (widget); |
243 | child != NULL; |
244 | child = _gtk_widget_get_next_sibling (widget: child)) |
245 | { |
246 | int child_min = 0, child_nat = 0; |
247 | int child_min_opp = 0, child_nat_opp = 0; |
248 | graphene_rect_t min_rect, nat_rect; |
249 | |
250 | if (!gtk_widget_should_layout (widget: child)) |
251 | continue; |
252 | |
253 | child_info = GTK_FIXED_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout_manager, child)); |
254 | |
255 | gtk_widget_measure (widget: child, orientation, for_size: -1, |
256 | minimum: &child_min, natural: &child_nat, |
257 | NULL, NULL); |
258 | gtk_widget_measure (widget: child, OPPOSITE_ORIENTATION (orientation), for_size: -1, |
259 | minimum: &child_min_opp, natural: &child_nat_opp, |
260 | NULL, NULL); |
261 | |
262 | min_rect.origin.x = min_rect.origin.y = 0; |
263 | nat_rect.origin.x = nat_rect.origin.y = 0; |
264 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
265 | { |
266 | min_rect.size.width = child_min; |
267 | min_rect.size.height = child_min_opp; |
268 | nat_rect.size.width = child_nat; |
269 | nat_rect.size.height = child_nat_opp; |
270 | } |
271 | else |
272 | { |
273 | min_rect.size.width = child_min_opp; |
274 | min_rect.size.height = child_min; |
275 | nat_rect.size.width = child_nat_opp; |
276 | nat_rect.size.height = child_nat; |
277 | } |
278 | |
279 | gsk_transform_transform_bounds (self: child_info->transform, rect: &min_rect, out_rect: &min_rect); |
280 | gsk_transform_transform_bounds (self: child_info->transform, rect: &nat_rect, out_rect: &nat_rect); |
281 | |
282 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
283 | { |
284 | minimum_size = MAX (minimum_size, min_rect.origin.x + min_rect.size.width); |
285 | natural_size = MAX (natural_size, nat_rect.origin.x + nat_rect.size.width); |
286 | } |
287 | else |
288 | { |
289 | minimum_size = MAX (minimum_size, min_rect.origin.y + min_rect.size.height); |
290 | natural_size = MAX (natural_size, nat_rect.origin.y + nat_rect.size.height); |
291 | } |
292 | } |
293 | |
294 | if (minimum != NULL) |
295 | *minimum = minimum_size; |
296 | if (natural != NULL) |
297 | *natural = natural_size; |
298 | } |
299 | |
300 | static void |
301 | gtk_fixed_layout_allocate (GtkLayoutManager *layout_manager, |
302 | GtkWidget *widget, |
303 | int width, |
304 | int height, |
305 | int baseline) |
306 | { |
307 | GtkFixedLayoutChild *child_info; |
308 | GtkWidget *child; |
309 | |
310 | for (child = _gtk_widget_get_first_child (widget); |
311 | child != NULL; |
312 | child = _gtk_widget_get_next_sibling (widget: child)) |
313 | { |
314 | GtkRequisition child_req; |
315 | |
316 | if (!gtk_widget_should_layout (widget: child)) |
317 | continue; |
318 | |
319 | child_info = GTK_FIXED_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout_manager, child)); |
320 | gtk_widget_get_preferred_size (widget: child, minimum_size: &child_req, NULL); |
321 | |
322 | gtk_widget_allocate (widget: child, |
323 | width: child_req.width, |
324 | height: child_req.height, |
325 | baseline: -1, |
326 | transform: gsk_transform_ref (self: child_info->transform)); |
327 | } |
328 | } |
329 | |
330 | static GtkLayoutChild * |
331 | gtk_fixed_layout_create_layout_child (GtkLayoutManager *manager, |
332 | GtkWidget *widget, |
333 | GtkWidget *for_child) |
334 | { |
335 | return g_object_new (GTK_TYPE_FIXED_LAYOUT_CHILD, |
336 | first_property_name: "layout-manager" , manager, |
337 | "child-widget" , for_child, |
338 | NULL); |
339 | } |
340 | |
341 | static void |
342 | gtk_fixed_layout_class_init (GtkFixedLayoutClass *klass) |
343 | { |
344 | GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (ptr: klass); |
345 | |
346 | layout_class->layout_child_type = GTK_TYPE_FIXED_LAYOUT_CHILD; |
347 | |
348 | layout_class->get_request_mode = gtk_fixed_layout_get_request_mode; |
349 | layout_class->measure = gtk_fixed_layout_measure; |
350 | layout_class->allocate = gtk_fixed_layout_allocate; |
351 | layout_class->create_layout_child = gtk_fixed_layout_create_layout_child; |
352 | } |
353 | |
354 | static void |
355 | gtk_fixed_layout_init (GtkFixedLayout *self) |
356 | { |
357 | } |
358 | |
359 | /** |
360 | * gtk_fixed_layout_new: |
361 | * |
362 | * Creates a new `GtkFixedLayout`. |
363 | * |
364 | * Returns: the newly created `GtkFixedLayout` |
365 | */ |
366 | GtkLayoutManager * |
367 | gtk_fixed_layout_new (void) |
368 | { |
369 | return g_object_new (GTK_TYPE_FIXED_LAYOUT, NULL); |
370 | } |
371 | |