gtkrevealer.c 28.5 KB
Newer Older
Matthias Clasen's avatar
Matthias Clasen committed
1
2
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
3
 * Copyright 2013, 2015 Red Hat, Inc.
Matthias Clasen's avatar
Matthias Clasen committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * This program 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 program 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 program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Author: Alexander Larsson <alexl@redhat.com>
20
 *         Carlos Soriano <csoriano@gnome.org>
Matthias Clasen's avatar
Matthias Clasen committed
21
22
23
 */

#include "config.h"
Benjamin Otte's avatar
Benjamin Otte committed
24

Matthias Clasen's avatar
Matthias Clasen committed
25
#include "gtkrevealer.h"
Benjamin Otte's avatar
Benjamin Otte committed
26
27

#include "gtkintl.h"
Matthias Clasen's avatar
Matthias Clasen committed
28
#include "gtkprivate.h"
29
#include "gtkprogresstrackerprivate.h"
Benjamin Otte's avatar
Benjamin Otte committed
30
31
#include "gtksettingsprivate.h"
#include "gtktypebuiltins.h"
32
#include "gtkwidgetprivate.h"
33
#include "gtkbuildable.h"
Chun-wei Fan's avatar
Chun-wei Fan committed
34

Matthias Clasen's avatar
Matthias Clasen committed
35
/**
Matthias Clasen's avatar
Matthias Clasen committed
36
 * GtkRevealer:
Matthias Clasen's avatar
Matthias Clasen committed
37
 *
Matthias Clasen's avatar
Matthias Clasen committed
38
 * A `GtkRevealer` animates the transition of its child from invisible to visible.
Matthias Clasen's avatar
Matthias Clasen committed
39
40
 *
 * The style of transition can be controlled with
Matthias Clasen's avatar
Matthias Clasen committed
41
 * [method@Gtk.Revealer.set_transition_type].
Matthias Clasen's avatar
Matthias Clasen committed
42
 *
Matthias Clasen's avatar
Matthias Clasen committed
43
 * These animations respect the [property@Gtk.Settings:gtk-enable-animations]
44
45
 * setting.
 *
Matthias Clasen's avatar
Matthias Clasen committed
46
47
 * # CSS nodes
 *
Matthias Clasen's avatar
Matthias Clasen committed
48
49
50
51
 * `GtkRevealer` has a single CSS node with name revealer.
 * When styling `GtkRevealer` using CSS, remember that it only hides its contents,
 * not itself. That means applied margin, padding and borders will be visible even
 * when the [property@Gtk.Revealer:reveal-child] property is set to %FALSE.
52
53
54
 *
 * # Accessibility
 *
Matthias Clasen's avatar
Matthias Clasen committed
55
 * `GtkRevealer` uses the %GTK_ACCESSIBLE_ROLE_GROUP role.
56
 *
Matthias Clasen's avatar
Matthias Clasen committed
57
 * The child of `GtkRevealer`, if set, is always available in the accessibility
58
 * tree, regardless of the state of the revealer widget.
Matthias Clasen's avatar
Matthias Clasen committed
59
60
61
62
63
64
65
66
67
68
 */

/**
 * GtkRevealerTransitionType:
 * @GTK_REVEALER_TRANSITION_TYPE_NONE: No transition
 * @GTK_REVEALER_TRANSITION_TYPE_CROSSFADE: Fade in
 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT: Slide in from the left
 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT: Slide in from the right
 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP: Slide in from the bottom
 * @GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN: Slide in from the top
69
70
71
72
 * @GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT: Floop in from the left
 * @GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT: Floop in from the right
 * @GTK_REVEALER_TRANSITION_TYPE_SWING_UP: Floop in from the bottom
 * @GTK_REVEALER_TRANSITION_TYPE_SWING_DOWN: Floop in from the top
Matthias Clasen's avatar
Matthias Clasen committed
73
74
 *
 * These enumeration values describe the possible transitions
Matthias Clasen's avatar
Matthias Clasen committed
75
 * when the child of a `GtkRevealer` widget is shown or hidden.
Matthias Clasen's avatar
Matthias Clasen committed
76
77
 */

78
79
struct _GtkRevealer
{
80
  GtkWidget parent_instance;
Matthias Clasen's avatar
Matthias Clasen committed
81

82
83
  GtkWidget *child;

Matthias Clasen's avatar
Matthias Clasen committed
84
85
86
  GtkRevealerTransitionType transition_type;
  guint transition_duration;

87
88
89
  double current_pos;
  double source_pos;
  double target_pos;
Matthias Clasen's avatar
Matthias Clasen committed
90
91

  guint tick_id;
92
  GtkProgressTracker tracker;
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
};

typedef struct
{
  GtkWidgetClass parent_class;
} GtkRevealerClass;

enum  {
  PROP_0,
  PROP_TRANSITION_TYPE,
  PROP_TRANSITION_DURATION,
  PROP_REVEAL_CHILD,
  PROP_CHILD_REVEALED,
  PROP_CHILD,
  LAST_PROP
};
Matthias Clasen's avatar
Matthias Clasen committed
109

110
static GParamSpec *props[LAST_PROP] = { NULL, };
Matthias Clasen's avatar
Matthias Clasen committed
111

112
113
114
115
static void gtk_revealer_size_allocate (GtkWidget     *widget,
                                        int            width,
                                        int            height,
                                        int            baseline);
116
117
118
119
120
121
122
static void gtk_revealer_measure (GtkWidget      *widget,
                                  GtkOrientation  orientation,
                                  int             for_size,
                                  int            *minimum,
                                  int            *natural,
                                  int            *minimum_baseline,
                                  int            *natural_baseline);
Matthias Clasen's avatar
Matthias Clasen committed
123

124
static void     gtk_revealer_set_position (GtkRevealer *revealer,
125
                                           double       pos);
126

127
128
129
130
131
132
133
134
135
136
137
138
static void gtk_revealer_buildable_iface_init (GtkBuildableIface *iface);

G_DEFINE_TYPE_WITH_CODE (GtkRevealer, gtk_revealer, GTK_TYPE_WIDGET,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_revealer_buildable_iface_init))

static GtkBuildableIface *parent_buildable_iface;

static void
gtk_revealer_buildable_add_child (GtkBuildable *buildable,
                                  GtkBuilder   *builder,
                                  GObject      *child,
Benjamin Otte's avatar
Benjamin Otte committed
139
                                  const char   *type)
140
141
142
143
144
145
146
147
148
149
150
151
152
153
{
  if (GTK_IS_WIDGET (child))
    gtk_revealer_set_child (GTK_REVEALER (buildable), GTK_WIDGET (child));
  else
    parent_buildable_iface->add_child (buildable, builder, child, type);
}

static void
gtk_revealer_buildable_iface_init (GtkBuildableIface *iface)
{
  parent_buildable_iface = g_type_interface_peek_parent (iface);

  iface->add_child = gtk_revealer_buildable_add_child;
}
Matthias Clasen's avatar
Matthias Clasen committed
154
155
156
157

static void
gtk_revealer_init (GtkRevealer *revealer)
{
158
159
160
161
  revealer->transition_type = GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN;
  revealer->transition_duration = 250;
  revealer->current_pos = 0.0;
  revealer->target_pos = 0.0;
Matthias Clasen's avatar
Matthias Clasen committed
162

163
  gtk_widget_set_overflow (GTK_WIDGET (revealer), GTK_OVERFLOW_HIDDEN);
Matthias Clasen's avatar
Matthias Clasen committed
164
165
}

166
167
168
169
170
static void
gtk_revealer_dispose (GObject *obj)
{
  GtkRevealer *revealer = GTK_REVEALER (obj);

171
  g_clear_pointer (&revealer->child, gtk_widget_unparent);
172
173
174
175

  G_OBJECT_CLASS (gtk_revealer_parent_class)->dispose (obj);
}

Matthias Clasen's avatar
Matthias Clasen committed
176
177
178
179
180
static void
gtk_revealer_finalize (GObject *obj)
{
  GtkRevealer *revealer = GTK_REVEALER (obj);

181
182
183
  if (revealer->tick_id != 0)
    gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), revealer->tick_id);
  revealer->tick_id = 0;
Matthias Clasen's avatar
Matthias Clasen committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209

  G_OBJECT_CLASS (gtk_revealer_parent_class)->finalize (obj);
}

static void
gtk_revealer_get_property (GObject    *object,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  GtkRevealer *revealer = GTK_REVEALER (object);

  switch (property_id)
   {
    case PROP_TRANSITION_TYPE:
      g_value_set_enum (value, gtk_revealer_get_transition_type (revealer));
      break;
    case PROP_TRANSITION_DURATION:
      g_value_set_uint (value, gtk_revealer_get_transition_duration (revealer));
      break;
    case PROP_REVEAL_CHILD:
      g_value_set_boolean (value, gtk_revealer_get_reveal_child (revealer));
      break;
    case PROP_CHILD_REVEALED:
      g_value_set_boolean (value, gtk_revealer_get_child_revealed (revealer));
      break;
210
211
212
    case PROP_CHILD:
      g_value_set_object (value, gtk_revealer_get_child (revealer));
      break;
Matthias Clasen's avatar
Matthias Clasen committed
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_revealer_set_property (GObject      *object,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
  GtkRevealer *revealer = GTK_REVEALER (object);

  switch (property_id)
    {
    case PROP_TRANSITION_TYPE:
      gtk_revealer_set_transition_type (revealer, g_value_get_enum (value));
      break;
    case PROP_TRANSITION_DURATION:
      gtk_revealer_set_transition_duration (revealer, g_value_get_uint (value));
      break;
    case PROP_REVEAL_CHILD:
      gtk_revealer_set_reveal_child (revealer, g_value_get_boolean (value));
      break;
238
239
240
    case PROP_CHILD:
      gtk_revealer_set_child (revealer, g_value_get_object (value));
      break;
Matthias Clasen's avatar
Matthias Clasen committed
241
242
243
244
245
246
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

247
248
249
250
251
252
253
254
static void
gtk_revealer_unmap (GtkWidget *widget)
{
  GtkRevealer *revealer = GTK_REVEALER (widget);

  GTK_WIDGET_CLASS (gtk_revealer_parent_class)->unmap (widget);

  /* Finish & stop the animation */
255
256
  if (revealer->current_pos != revealer->target_pos)
    gtk_revealer_set_position (revealer, revealer->target_pos);
257

258
  if (revealer->tick_id != 0)
259
    {
260
261
      gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), revealer->tick_id);
      revealer->tick_id = 0;
262
263
264
    }
}

265
266
267
268
269
270
271
static void
gtk_revealer_compute_expand (GtkWidget *widget,
                             gboolean  *hexpand,
                             gboolean  *vexpand)
{
  GtkRevealer *revealer = GTK_REVEALER (widget);

272
  if (revealer->child)
273
    {
274
275
      *hexpand = gtk_widget_compute_expand (revealer->child, GTK_ORIENTATION_HORIZONTAL);
      *vexpand = gtk_widget_compute_expand (revealer->child, GTK_ORIENTATION_VERTICAL);
276
277
278
279
280
281
282
283
284
285
286
287
288
    }
  else
    {
      *hexpand = FALSE;
      *vexpand = FALSE;
    }
}

static GtkSizeRequestMode
gtk_revealer_get_request_mode (GtkWidget *widget)
{
  GtkRevealer *revealer = GTK_REVEALER (widget);

289
290
  if (revealer->child)
    return gtk_widget_get_request_mode (revealer->child);
291
292
293
294
  else
    return GTK_SIZE_REQUEST_CONSTANT_SIZE;
}

Matthias Clasen's avatar
Matthias Clasen committed
295
296
297
298
299
300
static void
gtk_revealer_class_init (GtkRevealerClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);

301
302
  object_class->dispose = gtk_revealer_dispose;
  object_class->finalize = gtk_revealer_finalize;
Matthias Clasen's avatar
Matthias Clasen committed
303
304
305
  object_class->get_property = gtk_revealer_get_property;
  object_class->set_property = gtk_revealer_set_property;

306
  widget_class->unmap = gtk_revealer_unmap;
307
  widget_class->size_allocate = gtk_revealer_size_allocate;
308
  widget_class->measure = gtk_revealer_measure;
309
310
  widget_class->compute_expand = gtk_revealer_compute_expand;
  widget_class->get_request_mode = gtk_revealer_get_request_mode;
Matthias Clasen's avatar
Matthias Clasen committed
311

Matthias Clasen's avatar
Matthias Clasen committed
312
313
314
315
316
  /**
   * GtkRevealer:transition-type: (attributes org.gtk.Property.get=gtk_revealer_get_transition_type org.gtk.Property.set=gtk_revealer_set_transition_type)
   *
   * The type of animation used to transition.
   */
317
  props[PROP_TRANSITION_TYPE] =
318
    g_param_spec_enum ("transition-type", NULL, NULL,
319
320
                       GTK_TYPE_REVEALER_TRANSITION_TYPE,
                       GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
321
                       GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
322

Matthias Clasen's avatar
Matthias Clasen committed
323
324
325
326
327
  /**
   * GtkRevealer:transition-duration: (attributes org.gtk.Property.get=gtk_revealer_get_transition_duration org.gtk.Property.set=gtk_revealer_set_transition_duration)
   *
   * The animation duration, in milliseconds.
   */
328
  props[PROP_TRANSITION_DURATION] =
329
    g_param_spec_uint ("transition-duration", NULL, NULL,
330
                       0, G_MAXUINT, 250,
331
                       GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
332

Matthias Clasen's avatar
Matthias Clasen committed
333
334
335
336
337
  /**
   * GtkRevealer:reveal-child: (attributes org.gtk.Proeprty.get=gtk_revealer_get_reveal_child org.gtk.Property.set=gtk_revealer_set_reveal_child)
   *
   * Whether the revealer should reveal the child.
   */
338
  props[PROP_REVEAL_CHILD] =
339
    g_param_spec_boolean ("reveal-child", NULL, NULL,
340
                          FALSE,
341
                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
342

Matthias Clasen's avatar
Matthias Clasen committed
343
344
345
346
347
  /**
   * GtkRevealer:child-revealed: (attributes org.gtk.Property.get=gtk_revealer_get_child_revealed)
   *
   * Whether the child is revealed and the animation target reached.
   */
348
  props[PROP_CHILD_REVEALED] =
349
    g_param_spec_boolean ("child-revealed", NULL, NULL,
350
                          FALSE,
Matthias Clasen's avatar
Matthias Clasen committed
351
                          GTK_PARAM_READABLE);
352

Matthias Clasen's avatar
Matthias Clasen committed
353
354
355
356
357
  /**
   * GtkRevealer:child: (attributes org.gtk.Property.get=gtk_revealer_get_child org.gtk.Property.set=gtk_revealer_set_child)
   *
   * The child widget.
   */
358
  props[PROP_CHILD] =
359
    g_param_spec_object ("child", NULL, NULL,
360
361
362
363
                         GTK_TYPE_WIDGET,
                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);


364
  g_object_class_install_properties (object_class, LAST_PROP, props);
365

Matthias Clasen's avatar
Matthias Clasen committed
366
  gtk_widget_class_set_css_name (widget_class, I_("revealer"));
367
  gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP);
Matthias Clasen's avatar
Matthias Clasen committed
368
369
}

Matthias Clasen's avatar
Matthias Clasen committed
370
371
372
/**
 * gtk_revealer_new:
 *
Matthias Clasen's avatar
Matthias Clasen committed
373
 * Creates a new `GtkRevealer`.
Matthias Clasen's avatar
Matthias Clasen committed
374
 *
Matthias Clasen's avatar
Matthias Clasen committed
375
 * Returns: a newly created `GtkRevealer`
Matthias Clasen's avatar
Matthias Clasen committed
376
 */
Matthias Clasen's avatar
Matthias Clasen committed
377
378
379
380
381
382
GtkWidget *
gtk_revealer_new (void)
{
  return g_object_new (GTK_TYPE_REVEALER, NULL);
}

383
384
385
386
387
static GtkRevealerTransitionType
effective_transition (GtkRevealer *revealer)
{
  if (gtk_widget_get_direction (GTK_WIDGET (revealer)) == GTK_TEXT_DIR_RTL)
    {
388
      if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT)
389
        return GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT;
390
      else if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
391
        return GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT;
392
      if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT)
393
        return GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT;
394
      else if (revealer->transition_type == GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT)
395
        return GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT;
396
397
    }

398
  return revealer->transition_type;
399
400
}

401
402
403
static double
get_child_size_scale (GtkRevealer    *revealer,
                      GtkOrientation  orientation)
Matthias Clasen's avatar
Matthias Clasen committed
404
{
405
406
407
408
409
  switch (effective_transition (revealer))
    {
    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT:
    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT:
      if (orientation == GTK_ORIENTATION_HORIZONTAL)
410
        return revealer->current_pos;
411
412
      else
        return 1.0;
Matthias Clasen's avatar
Matthias Clasen committed
413

414
415
416
    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN:
    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP:
      if (orientation == GTK_ORIENTATION_VERTICAL)
417
        return revealer->current_pos;
418
419
      else
        return 1.0;
Matthias Clasen's avatar
Matthias Clasen committed
420

421
422
423
    case GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT:
    case GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT:
      if (orientation == GTK_ORIENTATION_HORIZONTAL)
424
        return sin (G_PI * revealer->current_pos / 2);
425
426
427
428
429
430
      else
        return 1.0;

    case GTK_REVEALER_TRANSITION_TYPE_SWING_DOWN:
    case GTK_REVEALER_TRANSITION_TYPE_SWING_UP:
      if (orientation == GTK_ORIENTATION_VERTICAL)
431
        return sin (G_PI * revealer->current_pos / 2);
432
433
434
      else
        return 1.0;

435
436
437
438
439
    case GTK_REVEALER_TRANSITION_TYPE_NONE:
    case GTK_REVEALER_TRANSITION_TYPE_CROSSFADE:
    default:
      return 1.0;
    }
Matthias Clasen's avatar
Matthias Clasen committed
440
441
442
}

static void
443
444
445
446
gtk_revealer_size_allocate (GtkWidget *widget,
                            int        width,
                            int        height,
                            int        baseline)
Matthias Clasen's avatar
Matthias Clasen committed
447
448
{
  GtkRevealer *revealer = GTK_REVEALER (widget);
449
450
451
  GskTransform *transform;
  double hscale, vscale;
  int child_width, child_height;
Matthias Clasen's avatar
Matthias Clasen committed
452

453
  if (revealer->child == NULL || !gtk_widget_get_visible (revealer->child))
454
455
    return;

456
  if (revealer->current_pos >= 1.0)
457
    {
458
      gtk_widget_allocate (revealer->child, width, height, baseline, NULL);
459
460
461
462
463
464
465
466
467
468
469
470
      return;
    }

  hscale = get_child_size_scale (revealer, GTK_ORIENTATION_HORIZONTAL);
  vscale = get_child_size_scale (revealer, GTK_ORIENTATION_VERTICAL);
  if (hscale <= 0 || vscale <= 0)
    {
      /* don't allocate anything, the child is invisible and the numbers
       * don't make sense. */
      return;
    }

471
  /* We request a different size than the child requested scaled by
472
   * this scale as it will render smaller from the transition.
473
474
475
476
477
   * However, we still want to allocate the child widget with its
   * unscaled size so it renders right instead of e.g. ellipsizing or
   * some other form of clipping. We do this by reverse-applying
   * the scale when size allocating the child.
   *
478
   * Unfortunately this causes precision issues.
479
   *
480
   * So we assume that the fully expanded revealer will likely get
481
482
483
484
485
   * an allocation that matches the child's minimum or natural allocation,
   * so we special-case these two values.
   * So when - due to the precision loss - multiple sizes would match
   * the current allocation, we don't pick one at random, we prefer the
   * min and nat size.
486
487
488
489
490
491
492
493
   *
   * On top, the scaled size request is always rounded up to an integer.
   * For instance if natural with is 100, and scale is 0.001, we would
   * request a natural size of ceil(0.1) == 1, but reversing this would
   * result in 1 / 0.001 == 1000 (rather than 100).
   * In the swing case we can get the scale arbitrarily near 0 causing
   * arbitrary large problems.
   * These also get avoided by the preference.
494
495
   */

496
  if (hscale < 1.0)
Timm Bäder's avatar
Timm Bäder committed
497
    {
498
      int min, nat;
499
      g_assert (vscale == 1.0);
500
      gtk_widget_measure (revealer->child, GTK_ORIENTATION_HORIZONTAL, height, &min, &nat, NULL, NULL);
501
502
503
504
505
      if (ceil (nat * hscale) == width)
        child_width = nat;
      else if (ceil (min * hscale) == width)
        child_width = min;
      else
506
        child_width = floor (width / hscale);
Benjamin Otte's avatar
Benjamin Otte committed
507
      child_height = height;
Timm Bäder's avatar
Timm Bäder committed
508
    }
509
510
  else if (vscale < 1.0)
    {
511
      int min, nat;
Benjamin Otte's avatar
Benjamin Otte committed
512
      child_width = width;
513
      gtk_widget_measure (revealer->child, GTK_ORIENTATION_VERTICAL, width, &min, &nat, NULL, NULL);
514
515
516
517
518
      if (ceil (nat * vscale) == height)
        child_height = nat;
      else if (ceil (min * vscale) == height)
        child_height = min;
      else
519
        child_height = floor (height / vscale);
520
    }
Benjamin Otte's avatar
Benjamin Otte committed
521
522
523
524
525
  else
    {
      child_width = width;
      child_height = height;
    }
526
527
528
529
530
531
532
533
534
535

  transform = NULL;
  switch (effective_transition (revealer))
    {
    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT:
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (width - child_width, 0));
      break;

    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN:
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, height - child_height));
536
      break;
537
538
539
540

    case GTK_REVEALER_TRANSITION_TYPE_SWING_LEFT:
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (width, height / 2));
      transform = gsk_transform_perspective (transform, 2 * MAX (width, height));
541
      transform = gsk_transform_rotate_3d (transform, -90 * (1.0 - revealer->current_pos), graphene_vec3_y_axis ());
542
543
544
545
546
547
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (- child_width, - child_height / 2));
      break;

    case GTK_REVEALER_TRANSITION_TYPE_SWING_RIGHT:
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, height / 2));
      transform = gsk_transform_perspective (transform, 2 * MAX (width, height));
548
      transform = gsk_transform_rotate_3d (transform, 90 * (1.0 - revealer->current_pos), graphene_vec3_y_axis ());
549
550
551
552
553
554
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, - child_height / 2));
      break;

    case GTK_REVEALER_TRANSITION_TYPE_SWING_DOWN:
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (width / 2, 0));
      transform = gsk_transform_perspective (transform, 2 * MAX (width, height));
555
      transform = gsk_transform_rotate_3d (transform, -90 * (1.0 - revealer->current_pos), graphene_vec3_x_axis ());
556
557
558
559
560
561
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (- child_width / 2, 0));
      break;

    case GTK_REVEALER_TRANSITION_TYPE_SWING_UP:
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (width / 2, height));
      transform = gsk_transform_perspective (transform, 2 * MAX (width, height));
562
      transform = gsk_transform_rotate_3d (transform, 90 * (1.0 - revealer->current_pos), graphene_vec3_x_axis ());
563
564
565
566
567
568
569
570
571
572
573
      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (- child_width / 2, - child_height));
      break;

    case GTK_REVEALER_TRANSITION_TYPE_NONE:
    case GTK_REVEALER_TRANSITION_TYPE_CROSSFADE:
    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT:
    case GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP:
    default:
      break;
    }

574
  gtk_widget_allocate (revealer->child, child_width, child_height, -1, transform);
Matthias Clasen's avatar
Matthias Clasen committed
575
576
577
578
}

static void
gtk_revealer_set_position (GtkRevealer *revealer,
579
                           double       pos)
Matthias Clasen's avatar
Matthias Clasen committed
580
581
{
  gboolean new_visible;
582
  GtkRevealerTransitionType transition;
Matthias Clasen's avatar
Matthias Clasen committed
583

584
  revealer->current_pos = pos;
Matthias Clasen's avatar
Matthias Clasen committed
585

586
  new_visible = revealer->current_pos != 0.0;
Matthias Clasen's avatar
Matthias Clasen committed
587

588
589
  if (revealer->child != NULL &&
      new_visible != gtk_widget_get_child_visible (revealer->child))
590
    {
591
      gtk_widget_set_child_visible (revealer->child, new_visible);
592
593
      gtk_widget_queue_resize (GTK_WIDGET (revealer));
    }
Matthias Clasen's avatar
Matthias Clasen committed
594

595
  transition = effective_transition (revealer);
596
597
598
599
600
  if (transition == GTK_REVEALER_TRANSITION_TYPE_NONE)
    {
      gtk_widget_queue_draw (GTK_WIDGET (revealer));
    }
  else if (transition == GTK_REVEALER_TRANSITION_TYPE_CROSSFADE)
601
    {
602
      gtk_widget_set_opacity (GTK_WIDGET (revealer), revealer->current_pos);
603
604
605
606
607
608
      gtk_widget_queue_draw (GTK_WIDGET (revealer));
    }
  else
    {
      gtk_widget_queue_resize (GTK_WIDGET (revealer));
    }
Matthias Clasen's avatar
Matthias Clasen committed
609

610
  if (revealer->current_pos == revealer->target_pos)
611
    g_object_notify_by_pspec (G_OBJECT (revealer), props[PROP_CHILD_REVEALED]);
Matthias Clasen's avatar
Matthias Clasen committed
612
613
614
}

static gboolean
615
gtk_revealer_animate_cb (GtkWidget     *widget,
Matthias Clasen's avatar
Matthias Clasen committed
616
617
618
                         GdkFrameClock *frame_clock,
                         gpointer       user_data)
{
619
  GtkRevealer *revealer = GTK_REVEALER (widget);
620
  double ease;
Matthias Clasen's avatar
Matthias Clasen committed
621

622
  gtk_progress_tracker_advance_frame (&revealer->tracker,
623
                                      gdk_frame_clock_get_frame_time (frame_clock));
624
  ease = gtk_progress_tracker_get_ease_out_cubic (&revealer->tracker, FALSE);
625
  gtk_revealer_set_position (revealer,
626
                             revealer->source_pos + (ease * (revealer->target_pos - revealer->source_pos)));
627

628
  if (gtk_progress_tracker_get_state (&revealer->tracker) == GTK_PROGRESS_STATE_AFTER)
Matthias Clasen's avatar
Matthias Clasen committed
629
    {
630
      revealer->tick_id = 0;
Matthias Clasen's avatar
Matthias Clasen committed
631
632
633
634
635
636
637
638
      return FALSE;
    }

  return TRUE;
}

static void
gtk_revealer_start_animation (GtkRevealer *revealer,
639
                              double       target)
Matthias Clasen's avatar
Matthias Clasen committed
640
641
{
  GtkWidget *widget = GTK_WIDGET (revealer);
642
  GtkRevealerTransitionType transition;
Matthias Clasen's avatar
Matthias Clasen committed
643

644
  if (revealer->target_pos == target)
Matthias Clasen's avatar
Matthias Clasen committed
645
646
    return;

647
  revealer->target_pos = target;
648
  g_object_notify_by_pspec (G_OBJECT (revealer), props[PROP_REVEAL_CHILD]);
Matthias Clasen's avatar
Matthias Clasen committed
649

650
  transition = effective_transition (revealer);
Matthias Clasen's avatar
Matthias Clasen committed
651
  if (gtk_widget_get_mapped (widget) &&
652
      revealer->transition_duration != 0 &&
653
      transition != GTK_REVEALER_TRANSITION_TYPE_NONE &&
654
      gtk_settings_get_enable_animations (gtk_widget_get_settings (widget)))
Matthias Clasen's avatar
Matthias Clasen committed
655
    {
656
657
658
      revealer->source_pos = revealer->current_pos;
      if (revealer->tick_id == 0)
        revealer->tick_id =
659
          gtk_widget_add_tick_callback (widget, gtk_revealer_animate_cb, revealer, NULL);
660
661
      gtk_progress_tracker_start (&revealer->tracker,
                                  revealer->transition_duration * 1000,
662
663
                                  0,
                                  1.0);
Matthias Clasen's avatar
Matthias Clasen committed
664
665
666
667
668
669
670
    }
  else
    {
      gtk_revealer_set_position (revealer, target);
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
671
/**
Matthias Clasen's avatar
Matthias Clasen committed
672
673
 * gtk_revealer_set_reveal_child: (attributes org.gtk.Method.set_property=reveal-child)
 * @revealer: a `GtkRevealer`
Matthias Clasen's avatar
Matthias Clasen committed
674
675
 * @reveal_child: %TRUE to reveal the child
 *
Matthias Clasen's avatar
Matthias Clasen committed
676
 * Tells the `GtkRevealer` to reveal or conceal its child.
Matthias Clasen's avatar
Matthias Clasen committed
677
678
679
680
 *
 * The transition will be animated with the current
 * transition type of @revealer.
 */
Matthias Clasen's avatar
Matthias Clasen committed
681
682
683
684
685
686
687
688
689
690
691
692
void
gtk_revealer_set_reveal_child (GtkRevealer *revealer,
                               gboolean     reveal_child)
{
  g_return_if_fail (GTK_IS_REVEALER (revealer));

  if (reveal_child)
    gtk_revealer_start_animation (revealer, 1.0);
  else
    gtk_revealer_start_animation (revealer, 0.0);
}

Matthias Clasen's avatar
Matthias Clasen committed
693
/**
Matthias Clasen's avatar
Matthias Clasen committed
694
695
 * gtk_revealer_get_reveal_child: (attributes org.gtk.Method.get_property=reveal-child)
 * @revealer: a `GtkRevealer`
Matthias Clasen's avatar
Matthias Clasen committed
696
 *
Matthias Clasen's avatar
Matthias Clasen committed
697
 * Returns whether the child is currently revealed.
Matthias Clasen's avatar
Matthias Clasen committed
698
699
700
701
 *
 * This function returns %TRUE as soon as the transition
 * is to the revealed state is started. To learn whether
 * the child is fully revealed (ie the transition is completed),
Matthias Clasen's avatar
Matthias Clasen committed
702
 * use [method@Gtk.Revealer.get_child_revealed].
Matthias Clasen's avatar
Matthias Clasen committed
703
 *
704
 * Returns: %TRUE if the child is revealed.
Matthias Clasen's avatar
Matthias Clasen committed
705
 */
Matthias Clasen's avatar
Matthias Clasen committed
706
707
708
709
710
gboolean
gtk_revealer_get_reveal_child (GtkRevealer *revealer)
{
  g_return_val_if_fail (GTK_IS_REVEALER (revealer), FALSE);

711
  return revealer->target_pos != 0.0;
Matthias Clasen's avatar
Matthias Clasen committed
712
713
}

Matthias Clasen's avatar
Matthias Clasen committed
714
/**
Matthias Clasen's avatar
Matthias Clasen committed
715
716
717
718
 * gtk_revealer_get_child_revealed: (attributes org.gtk.Method.get_property=child-revealed)
 * @revealer: a `GtkRevealer`
 *
 * Returns whether the child is fully revealed.
Matthias Clasen's avatar
Matthias Clasen committed
719
 *
Matthias Clasen's avatar
Matthias Clasen committed
720
721
 * In other words, this returns whether the transition
 * to the revealed state is completed.
Matthias Clasen's avatar
Matthias Clasen committed
722
 *
723
 * Returns: %TRUE if the child is fully revealed
Matthias Clasen's avatar
Matthias Clasen committed
724
 */
Matthias Clasen's avatar
Matthias Clasen committed
725
726
727
gboolean
gtk_revealer_get_child_revealed (GtkRevealer *revealer)
{
728
  gboolean animation_finished = (revealer->target_pos == revealer->current_pos);
Matthias Clasen's avatar
Matthias Clasen committed
729
730
731
732
733
734
735
736
  gboolean reveal_child = gtk_revealer_get_reveal_child (revealer);

  if (animation_finished)
    return reveal_child;
  else
    return !reveal_child;
}

737
static void
738
739
740
741
742
743
744
gtk_revealer_measure (GtkWidget      *widget,
                      GtkOrientation  orientation,
                      int             for_size,
                      int            *minimum,
                      int            *natural,
                      int            *minimum_baseline,
                      int            *natural_baseline)
Matthias Clasen's avatar
Matthias Clasen committed
745
{
746
  GtkRevealer *revealer = GTK_REVEALER (widget);
747
748
  double scale;

749
  scale = get_child_size_scale (revealer, OPPOSITE_ORIENTATION (orientation));
750
751
752
753
754
755
756
757
758

  if (for_size >= 0)
    {
      if (scale == 0)
        return;
      else
        for_size = MIN (G_MAXINT, ceil (for_size / scale));
    }

759
  if (revealer->child != NULL && _gtk_widget_get_visible (revealer->child))
760
    {
761
      gtk_widget_measure (revealer->child,
762
763
764
765
766
767
768
769
770
771
                          orientation,
                          for_size,
                          minimum, natural,
                          NULL, NULL);
    }
  else
    {
      *minimum = 0;
      *natural = 0;
    }
772

773
  scale = get_child_size_scale (revealer, orientation);
774
775
  *minimum = ceil (*minimum * scale);
  *natural = ceil (*natural * scale);
Matthias Clasen's avatar
Matthias Clasen committed
776
777
}

Matthias Clasen's avatar
Matthias Clasen committed
778
/**
Matthias Clasen's avatar
Matthias Clasen committed
779
780
 * gtk_revealer_get_transition_duration: (attributes org.gtk.Method.get_property=transition-duration)
 * @revealer: a `GtkRevealer`
Matthias Clasen's avatar
Matthias Clasen committed
781
782
783
784
785
786
 *
 * Returns the amount of time (in milliseconds) that
 * transitions will take.
 *
 * Returns: the transition duration
 */
Matthias Clasen's avatar
Matthias Clasen committed
787
788
789
790
791
guint
gtk_revealer_get_transition_duration (GtkRevealer *revealer)
{
  g_return_val_if_fail (GTK_IS_REVEALER (revealer), 0);

792
  return revealer->transition_duration;
Matthias Clasen's avatar
Matthias Clasen committed
793
794
}

Matthias Clasen's avatar
Matthias Clasen committed
795
/**
Matthias Clasen's avatar
Matthias Clasen committed
796
797
 * gtk_revealer_set_transition_duration: (attributes org.gtk.Method.set_property=transition-duration)
 * @revealer: a `GtkRevealer`
Matthias Clasen's avatar
Matthias Clasen committed
798
799
800
801
 * @duration: the new duration, in milliseconds
 *
 * Sets the duration that transitions will take.
 */
Matthias Clasen's avatar
Matthias Clasen committed
802
803
804
805
806
807
void
gtk_revealer_set_transition_duration (GtkRevealer *revealer,
                                      guint        value)
{
  g_return_if_fail (GTK_IS_REVEALER (revealer));

808
  if (revealer->transition_duration == value)
809
810
    return;

811
  revealer->transition_duration = value;
812
  g_object_notify_by_pspec (G_OBJECT (revealer), props[PROP_TRANSITION_DURATION]);
Matthias Clasen's avatar
Matthias Clasen committed
813
814
}

Matthias Clasen's avatar
Matthias Clasen committed
815
/**
Matthias Clasen's avatar
Matthias Clasen committed
816
817
 * gtk_revealer_get_transition_type: (attributes org.gtk.Method.get_property=transition-type)
 * @revealer: a `GtkRevealer`
Matthias Clasen's avatar
Matthias Clasen committed
818
819
820
821
 *
 * Gets the type of animation that will be used
 * for transitions in @revealer.
 *
822
 * Returns: the current transition type of @revealer
Matthias Clasen's avatar
Matthias Clasen committed
823
 */
Matthias Clasen's avatar
Matthias Clasen committed
824
825
826
827
828
GtkRevealerTransitionType
gtk_revealer_get_transition_type (GtkRevealer *revealer)
{
  g_return_val_if_fail (GTK_IS_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE);

829
  return revealer->transition_type;
Matthias Clasen's avatar
Matthias Clasen committed
830
831
}

Matthias Clasen's avatar
Matthias Clasen committed
832
/**
Matthias Clasen's avatar
Matthias Clasen committed
833
834
 * gtk_revealer_set_transition_type: (attributes org.gtk.Method.set_property=transition-type)
 * @revealer: a `GtkRevealer`
Matthias Clasen's avatar
Matthias Clasen committed
835
836
837
 * @transition: the new transition type
 *
 * Sets the type of animation that will be used for
Matthias Clasen's avatar
Matthias Clasen committed
838
839
840
 * transitions in @revealer.
 *
 * Available types include various kinds of fades and slides.
Matthias Clasen's avatar
Matthias Clasen committed
841
 */
Matthias Clasen's avatar
Matthias Clasen committed
842
843
844
845
846
847
void
gtk_revealer_set_transition_type (GtkRevealer               *revealer,
                                  GtkRevealerTransitionType  transition)
{
  g_return_if_fail (GTK_IS_REVEALER (revealer));

848
  if (revealer->transition_type == transition)
849
850
    return;

851
  revealer->transition_type = transition;
Matthias Clasen's avatar
Matthias Clasen committed
852
  gtk_widget_queue_resize (GTK_WIDGET (revealer));
853
  g_object_notify_by_pspec (G_OBJECT (revealer), props[PROP_TRANSITION_TYPE]);
Matthias Clasen's avatar
Matthias Clasen committed
854
}
855
856

/**
Matthias Clasen's avatar
Matthias Clasen committed
857
858
 * gtk_revealer_set_child: (attributes org.gtk.Method.set_property=child)
 * @revealer: a `GtkRevealer`
859
 * @child: (nullable): the child widget
860
861
862
863
864
865
866
 *
 * Sets the child widget of @revealer.
 */
void
gtk_revealer_set_child (GtkRevealer *revealer,
                        GtkWidget   *child)
{
867
868
869
  g_return_if_fail (GTK_IS_REVEALER (revealer));
  g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));

870
  g_clear_pointer (&revealer->child, gtk_widget_unparent);
871
872
873
874

  if (child)
    {
      gtk_widget_set_parent (child, GTK_WIDGET (revealer));
875
876
      gtk_widget_set_child_visible (child, revealer->current_pos != 0.0);
      revealer->child = child;
877
   }
878
879
880
881
882

  g_object_notify_by_pspec (G_OBJECT (revealer), props[PROP_CHILD]);
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
883
884
 * gtk_revealer_get_child: (attributes org.gtk.Method.get_property=child)
 * @revealer: a `GtkRevealer`
885
886
887
888
889
890
891
892
 *
 * Gets the child widget of @revealer.
 *
 * Returns: (nullable) (transfer none): the child widget of @revealer
 */
GtkWidget *
gtk_revealer_get_child (GtkRevealer *revealer)
{
893
894
  g_return_val_if_fail (GTK_IS_REVEALER (revealer), NULL);

895
  return revealer->child;
896
}