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
74struct _GtkFixedLayout
75{
76 GtkLayoutManager parent_instance;
77};
78
79struct _GtkFixedLayoutChild
80{
81 GtkLayoutChild parent_instance;
82
83 GskTransform *transform;
84};
85
86enum
87{
88 PROP_CHILD_TRANSFORM = 1,
89
90 N_CHILD_PROPERTIES
91};
92
93static GParamSpec *child_props[N_CHILD_PROPERTIES];
94
95G_DEFINE_TYPE (GtkFixedLayoutChild, gtk_fixed_layout_child, GTK_TYPE_LAYOUT_CHILD)
96
97static void
98gtk_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
117static void
118gtk_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
137static void
138gtk_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
147static void
148gtk_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
173static void
174gtk_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 */
185void
186gtk_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 */
210GskTransform *
211gtk_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
218G_DEFINE_TYPE (GtkFixedLayout, gtk_fixed_layout, GTK_TYPE_LAYOUT_MANAGER)
219
220static GtkSizeRequestMode
221gtk_fixed_layout_get_request_mode (GtkLayoutManager *layout_manager,
222 GtkWidget *widget)
223{
224 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
225}
226
227static void
228gtk_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
300static void
301gtk_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
330static GtkLayoutChild *
331gtk_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
341static void
342gtk_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
354static void
355gtk_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 */
366GtkLayoutManager *
367gtk_fixed_layout_new (void)
368{
369 return g_object_new (GTK_TYPE_FIXED_LAYOUT, NULL);
370}
371

source code of gtk/gtk/gtkfixedlayout.c