gtkoverlaylayout.c 13.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* gtkoverlaylayout.c: Overlay layout manager
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * Copyright 2019 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

Matthias Clasen's avatar
Matthias Clasen committed
23
#include "gtkoverlaylayout.h"
24
25
26
27
28
29
30
31

#include "gtklayoutchild.h"
#include "gtkoverlay.h"
#include "gtkprivate.h"
#include "gtkwidgetprivate.h"

#include <graphene-gobject.h>

32
33

/**
Matthias Clasen's avatar
Matthias Clasen committed
34
35
36
 * GtkOverlayLayout:
 *
 * `GtkOverlayLayout` is the layout manager used by `GtkOverlay`.
37
38
39
40
 *
 * It places widgets as overlays on top of the main child.
 *
 * This is not a reusable layout manager, since it expects its widget
Matthias Clasen's avatar
Matthias Clasen committed
41
 * to be a `GtkOverlay`. It only listed here so that its layout
42
43
44
 * properties get documented.
 */

45
46
47
48
49
50
/**
 * GtkOverlayLayoutChild:
 *
 * `GtkLayoutChild` subclass for children in a `GtkOverlayLayout`.
 */

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
struct _GtkOverlayLayout
{
  GtkLayoutManager parent_instance;
};

struct _GtkOverlayLayoutChild
{
  GtkLayoutChild parent_instance;

  guint measure : 1;
  guint clip_overlay : 1;
};

enum
{
  PROP_MEASURE = 1,
  PROP_CLIP_OVERLAY,

  N_CHILD_PROPERTIES
};

static GParamSpec *child_props[N_CHILD_PROPERTIES];

G_DEFINE_TYPE (GtkOverlayLayoutChild, gtk_overlay_layout_child, GTK_TYPE_LAYOUT_CHILD)

static void
gtk_overlay_layout_child_set_property (GObject      *gobject,
                                       guint         prop_id,
                                       const GValue *value,
                                       GParamSpec   *pspec)
{
  GtkOverlayLayoutChild *self = GTK_OVERLAY_LAYOUT_CHILD (gobject);

  switch (prop_id)
    {
    case PROP_MEASURE:
      gtk_overlay_layout_child_set_measure (self, g_value_get_boolean (value));
      break;

    case PROP_CLIP_OVERLAY:
      gtk_overlay_layout_child_set_clip_overlay (self, g_value_get_boolean (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
gtk_overlay_layout_child_get_property (GObject    *gobject,
                                       guint       prop_id,
                                       GValue     *value,
                                       GParamSpec *pspec)
{
  GtkOverlayLayoutChild *self = GTK_OVERLAY_LAYOUT_CHILD (gobject);

  switch (prop_id)
    {
    case PROP_MEASURE:
      g_value_set_boolean (value, self->measure);
      break;

    case PROP_CLIP_OVERLAY:
      g_value_set_boolean (value, self->clip_overlay);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
gtk_overlay_layout_child_class_init (GtkOverlayLayoutChildClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->set_property = gtk_overlay_layout_child_set_property;
  gobject_class->get_property = gtk_overlay_layout_child_get_property;

132
  /**
Matthias Clasen's avatar
Matthias Clasen committed
133
   * GtkOverlayLayoutChild:measure: (attributes org.gtk.Property.get=gtk_overlay_layout_child_get_measure org.gtk.Property.set=gtk_overlay_layout_child_set_measure)
134
   *
Matthias Clasen's avatar
Matthias Clasen committed
135
   * Whether the child size should contribute to the `GtkOverlayLayout`'s
136
137
   * measurement.
   */
138
  child_props[PROP_MEASURE] =
139
    g_param_spec_boolean ("measure", NULL, NULL,
140
141
                          FALSE,
                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
Matthias Clasen's avatar
Matthias Clasen committed
142

143
  /**
Matthias Clasen's avatar
Matthias Clasen committed
144
   * GtkOverlayLayoutChild:clip-overlay: (attributes org.gtk.Property.get=gtk_overlay_layout_child_get_clip_overlay org.gtk.Property.set=gtk_overlay_layout_child_set_clip_overlay)
145
146
147
   *
   * Whether the child should be clipped to fit the parent's size.
   */
148
  child_props[PROP_CLIP_OVERLAY] =
149
    g_param_spec_boolean ("clip-overlay", NULL, NULL,
150
151
152
153
154
155
156
157
158
159
160
161
                          FALSE,
                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (gobject_class, N_CHILD_PROPERTIES, child_props);
}

static void
gtk_overlay_layout_child_init (GtkOverlayLayoutChild *self)
{
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
162
163
 * gtk_overlay_layout_child_set_measure: (attributes org.gtk.Method.set_property=measure)
 * @child: a `GtkOverlayLayoutChild`
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
 * @measure: whether to measure this child
 *
 * Sets whether to measure this child.
 */
void
gtk_overlay_layout_child_set_measure (GtkOverlayLayoutChild *child,
                                      gboolean               measure)
{
  GtkLayoutManager *layout;

  g_return_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child));

  if (child->measure == measure)
    return;

  child->measure = measure;

  layout = gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child));
  gtk_layout_manager_layout_changed (layout);

  g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_MEASURE]);
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
188
189
 * gtk_overlay_layout_child_get_measure: (attributes org.gtk.Method.get_property=measure)
 * @child: a `GtkOverlayLayoutChild`
190
191
192
 *
 * Retrieves whether the child is measured.
 *
Emmanuele Bassi's avatar
Emmanuele Bassi committed
193
 * Returns: whether the child is measured
194
195
196
197
198
199
200
201
202
203
 */
gboolean
gtk_overlay_layout_child_get_measure (GtkOverlayLayoutChild *child)
{
  g_return_val_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child), FALSE);

  return child->measure;
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
204
205
 * gtk_overlay_layout_child_set_clip_overlay: (attributes org.gtk.Method.set_property=clip-overlay)
 * @child: a `GtkOverlayLayoutChild`
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
 * @clip_overlay: whether to clip this child
 *
 * Sets whether to clip this child.
 */
void
gtk_overlay_layout_child_set_clip_overlay (GtkOverlayLayoutChild *child,
                                           gboolean               clip_overlay)
{
  GtkLayoutManager *layout;

  g_return_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child));

  if (child->clip_overlay == clip_overlay)
    return;

  child->clip_overlay = clip_overlay;

  layout = gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child));
  gtk_layout_manager_layout_changed (layout);

  g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_CLIP_OVERLAY]);
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
230
231
 * gtk_overlay_layout_child_get_clip_overlay: (attributes org.gtk.Method.get_property=clip-overlay)
 * @child: a `GtkOverlayLayoutChild`
232
233
234
 *
 * Retrieves whether the child is clipped.
 *
Emmanuele Bassi's avatar
Emmanuele Bassi committed
235
 * Returns: whether the child is clipped
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
 */
gboolean
gtk_overlay_layout_child_get_clip_overlay (GtkOverlayLayoutChild *child)
{
  g_return_val_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child), FALSE);

  return child->clip_overlay;
}

G_DEFINE_TYPE (GtkOverlayLayout, gtk_overlay_layout, GTK_TYPE_LAYOUT_MANAGER)

static void
gtk_overlay_layout_measure (GtkLayoutManager *layout_manager,
                            GtkWidget        *widget,
                            GtkOrientation    orientation,
                            int               for_size,
                            int              *minimum,
                            int              *natural,
                            int              *minimum_baseline,
                            int              *natural_baseline)
{
  GtkOverlayLayoutChild *child_info;
  GtkWidget *child;
  int min, nat;
  GtkWidget *main_widget;

262
  main_widget = gtk_overlay_get_child (GTK_OVERLAY (widget));
263
264
265
266
267
268
269
270

  min = 0;
  nat = 0;

  for (child = _gtk_widget_get_first_child (widget);
       child != NULL;
       child = _gtk_widget_get_next_sibling (child))
    {
271
272
273
      if (!gtk_widget_should_layout (child))
        continue;

274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
      child_info = GTK_OVERLAY_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (layout_manager, child));

      if (child == main_widget || child_info->measure)
        {
          int child_min, child_nat, child_min_baseline, child_nat_baseline;

          gtk_widget_measure (child,
                              orientation,
                              for_size,
                              &child_min, &child_nat,
                              &child_min_baseline, &child_nat_baseline);

          min = MAX (min, child_min);
          nat = MAX (nat, child_nat);
        }
    }

  if (minimum != NULL)
    *minimum = min;
  if (natural != NULL)
    *natural = nat;
}

static void
gtk_overlay_compute_child_allocation (GtkOverlay            *overlay,
                                      GtkWidget             *widget,
                                      GtkOverlayLayoutChild *child,
                                      GtkAllocation         *widget_allocation)
{
  GtkAllocation allocation;
  gboolean result;

  g_signal_emit_by_name (overlay, "get-child-position",
                         widget, &allocation, &result);

  widget_allocation->x = allocation.x;
  widget_allocation->y = allocation.y;
  widget_allocation->width = allocation.width;
  widget_allocation->height = allocation.height;
}

static GtkAlign
effective_align (GtkAlign         align,
                 GtkTextDirection direction)
{
  switch (align)
    {
    case GTK_ALIGN_START:
      return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_END : GTK_ALIGN_START;
    case GTK_ALIGN_END:
      return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_START : GTK_ALIGN_END;
    case GTK_ALIGN_FILL:
    case GTK_ALIGN_CENTER:
    case GTK_ALIGN_BASELINE:
    default:
      return align;
    }
}

static void
gtk_overlay_child_update_style_classes (GtkOverlay *overlay,
                                        GtkWidget *child,
                                        GtkAllocation *child_allocation)
{
338
  GtkWidget *widget = GTK_WIDGET (overlay);
339
340
341
342
343
  int width, height;
  GtkAlign valign, halign;
  gboolean is_left, is_right, is_top, is_bottom;
  gboolean has_left, has_right, has_top, has_bottom;

344
345
346
347
  has_left = gtk_widget_has_css_class (child, "left");
  has_right = gtk_widget_has_css_class (child, "right");
  has_top = gtk_widget_has_css_class (child, "top");
  has_bottom = gtk_widget_has_css_class (child, "bottom");
348
349
350

  is_left = is_right = is_top = is_bottom = FALSE;

351
352
  width = gtk_widget_get_width (widget);
  height = gtk_widget_get_height (widget);
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369

  halign = effective_align (gtk_widget_get_halign (child),
                            gtk_widget_get_direction (child));

  if (halign == GTK_ALIGN_START)
    is_left = (child_allocation->x == 0);
  else if (halign == GTK_ALIGN_END)
    is_right = (child_allocation->x + child_allocation->width == width);

  valign = gtk_widget_get_valign (child);

  if (valign == GTK_ALIGN_START)
    is_top = (child_allocation->y == 0);
  else if (valign == GTK_ALIGN_END)
    is_bottom = (child_allocation->y + child_allocation->height == height);

  if (has_left && !is_left)
370
    gtk_widget_remove_css_class (child, "left");
371
  else if (!has_left && is_left)
372
    gtk_widget_add_css_class (child, "left");
373
374

  if (has_right && !is_right)
375
    gtk_widget_remove_css_class (child, "right");
376
  else if (!has_right && is_right)
377
    gtk_widget_add_css_class (child, "right");
378
379

  if (has_top && !is_top)
380
    gtk_widget_remove_css_class (child, "top");
381
  else if (!has_top && is_top)
382
    gtk_widget_add_css_class (child, "top");
383
384

  if (has_bottom && !is_bottom)
385
    gtk_widget_remove_css_class (child, "bottom");
386
  else if (!has_bottom && is_bottom)
387
    gtk_widget_add_css_class (child, "bottom");
388
389
390
391
392
393
394
395
396
}

static void
gtk_overlay_child_allocate (GtkOverlay            *overlay,
                            GtkWidget             *widget,
                            GtkOverlayLayoutChild *child)
{
  GtkAllocation child_allocation;

397
  if (!gtk_widget_should_layout (widget))
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
    return;

  gtk_overlay_compute_child_allocation (overlay, widget, child, &child_allocation);

  gtk_overlay_child_update_style_classes (overlay, widget, &child_allocation);
  gtk_widget_size_allocate (widget, &child_allocation, -1);
}

static void
gtk_overlay_layout_allocate (GtkLayoutManager *layout_manager,
                             GtkWidget        *widget,
                             int               width,
                             int               height,
                             int               baseline)
{
  GtkWidget *child;
  GtkWidget *main_widget;

416
  main_widget = gtk_overlay_get_child (GTK_OVERLAY (widget));
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
  if (main_widget && gtk_widget_get_visible (main_widget))
    gtk_widget_size_allocate (main_widget,
                              &(GtkAllocation) { 0, 0, width, height },
                              -1);

  for (child = _gtk_widget_get_first_child (widget);
       child != NULL;
       child = _gtk_widget_get_next_sibling (child))
    {
      if (child != main_widget)
        {
          GtkOverlayLayoutChild *child_data;
          child_data = GTK_OVERLAY_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (layout_manager, child));

          gtk_overlay_child_allocate (GTK_OVERLAY (widget), child, child_data);
        }
    }
}

static void
gtk_overlay_layout_class_init (GtkOverlayLayoutClass *klass)
{
  GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (klass);

441
  layout_class->layout_child_type = GTK_TYPE_OVERLAY_LAYOUT_CHILD;
442
443
444
445
446
447
448
449
450
  layout_class->measure = gtk_overlay_layout_measure;
  layout_class->allocate = gtk_overlay_layout_allocate;
}

static void
gtk_overlay_layout_init (GtkOverlayLayout *self)
{
}

451
452
453
/**
 * gtk_overlay_layout_new:
 *
Matthias Clasen's avatar
Matthias Clasen committed
454
 * Creates a new `GtkOverlayLayout` instance.
455
456
457
 *
 * Returns: the newly created instance
 */
458
459
460
461
462
GtkLayoutManager *
gtk_overlay_layout_new (void)
{
  return g_object_new (GTK_TYPE_OVERLAY_LAYOUT, NULL);
}