summaryrefslogtreecommitdiff
path: root/gtk/gtkpopover.c
diff options
context:
space:
mode:
authorCarlos Garnacho <carlosg@gnome.org>2015-01-09 16:10:29 +0100
committerCarlos Garnacho <carlosg@gnome.org>2015-02-20 14:44:17 +0100
commitcff1694c99227dbf0b52525d7436fc4771859e00 (patch)
treed371496dd2c56e97c33d739bf008f9fd1304e109 /gtk/gtkpopover.c
parent3a671994d4f4de61a48df6198353a188b7862baa (diff)
downloadgtk+-cff1694c99227dbf0b52525d7436fc4771859e00.tar.gz
popover: Add show/hide transitions
These have the same visual effect and timing than the gnome-shell ones. During the hide animation, the popover has been made to take focus elsewhere, and refuse to take any pointer/keyboard input until the popover is shown again. This has been based on work from Timm Bäder. https://bugzilla.gnome.org/show_bug.cgi?id=741405
Diffstat (limited to 'gtk/gtkpopover.c')
-rw-r--r--gtk/gtkpopover.c307
1 files changed, 290 insertions, 17 deletions
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 4d8b175d52..97d13aa861 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -103,8 +103,10 @@
#include "wayland/gdkwayland.h"
#endif
-#define TAIL_GAP_WIDTH 24
-#define TAIL_HEIGHT 12
+#define TAIL_GAP_WIDTH 24
+#define TAIL_HEIGHT 12
+#define TRANSITION_DIFF 20
+#define TRANSITION_DURATION 330 * 1000
#define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
@@ -112,7 +114,8 @@ enum {
PROP_RELATIVE_TO = 1,
PROP_POINTING_TO,
PROP_POSITION,
- PROP_MODAL
+ PROP_MODAL,
+ PROP_TRANSITIONS_ENABLED
};
enum {
@@ -120,6 +123,13 @@ enum {
N_SIGNALS
};
+enum {
+ STATE_SHOWING,
+ STATE_SHOWN,
+ STATE_HIDING,
+ STATE_HIDDEN
+};
+
struct _GtkPopoverPrivate
{
GtkWidget *widget;
@@ -144,6 +154,12 @@ struct _GtkPopoverPrivate
guint button_pressed : 1;
guint apply_shape : 1;
guint grab_notify_blocked : 1;
+ guint transitions_enabled : 1;
+ guint state : 2;
+ guint visible : 1;
+ gint64 start_time;
+ gint transition_diff;
+ guint tick_id;
};
static GQuark quark_widget_popovers = 0;
@@ -151,6 +167,8 @@ static guint signals[N_SIGNALS] = { 0 };
static void gtk_popover_update_relative_to (GtkPopover *popover,
GtkWidget *relative_to);
+static void gtk_popover_set_state (GtkPopover *popover,
+ guint state);
G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN)
@@ -165,6 +183,8 @@ gtk_popover_init (GtkPopover *popover)
popover->priv = gtk_popover_get_instance_private (popover);
popover->priv->modal = TRUE;
popover->priv->apply_shape = TRUE;
+ popover->priv->tick_id = 0;
+ popover->priv->transitions_enabled = TRUE;
context = gtk_widget_get_style_context (widget);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
@@ -195,6 +215,10 @@ gtk_popover_set_property (GObject *object,
gtk_popover_set_modal (GTK_POPOVER (object),
g_value_get_boolean (value));
break;
+ case PROP_TRANSITIONS_ENABLED:
+ gtk_popover_set_transitions_enabled (GTK_POPOVER (object),
+ g_value_get_boolean (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -222,11 +246,27 @@ gtk_popover_get_property (GObject *object,
case PROP_MODAL:
g_value_set_boolean (value, priv->modal);
break;
+ case PROP_TRANSITIONS_ENABLED:
+ g_value_set_boolean (value, priv->transitions_enabled);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
+static gboolean
+transitions_enabled (GtkPopover *popover)
+{
+ GtkPopoverPrivate *priv = popover->priv;
+ gboolean animations_enabled;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (popover)),
+ "gtk-enable-animations", &animations_enabled,
+ NULL);
+
+ return animations_enabled && priv->transitions_enabled;
+}
+
static void
gtk_popover_finalize (GObject *object)
{
@@ -263,8 +303,6 @@ gtk_popover_dispose (GObject *object)
GtkPopover *popover = GTK_POPOVER (object);
GtkPopoverPrivate *priv = popover->priv;
- gtk_widget_set_visible (GTK_WIDGET (object), FALSE);
-
if (priv->window)
_gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
@@ -421,25 +459,126 @@ gtk_popover_apply_modality (GtkPopover *popover,
if (priv->prev_focus_widget &&
gtk_widget_is_drawable (priv->prev_focus_widget))
gtk_widget_grab_focus (priv->prev_focus_widget);
- else
+ else if (priv->window)
gtk_widget_grab_focus (GTK_WIDGET (priv->window));
popover_unset_prev_focus (popover);
}
}
+/* From clutter-easing.c, based on Robert Penner's
+ * infamous easing equations, MIT license.
+ */
+static double
+ease_out_cubic (double t)
+{
+ double p = t - 1;
+
+ return p * p * p + 1;
+}
+
+static gboolean
+show_animate_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ GtkPopover *popover = GTK_POPOVER (widget);
+ GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+ gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+ gdouble t;
+
+ if (now < (priv->start_time + TRANSITION_DURATION))
+ t = (now - priv->start_time) / (gdouble) (TRANSITION_DURATION);
+ else
+ t = 1.0;
+
+ t = ease_out_cubic (t);
+
+ if (priv->state == STATE_SHOWING)
+ {
+ priv->transition_diff = TRANSITION_DIFF - (TRANSITION_DIFF * t);
+ gtk_widget_set_opacity (widget, t);
+ }
+ else if (priv->state == STATE_HIDING)
+ {
+ priv->transition_diff = -TRANSITION_DIFF * t;
+ gtk_widget_set_opacity (widget, 1.0 - t);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (popover));
+
+ if (t >= 1.0)
+ {
+ if (priv->state == STATE_SHOWING)
+ {
+ gtk_popover_set_state (popover, STATE_SHOWN);
+
+ if (!priv->visible)
+ gtk_popover_set_state (popover, STATE_HIDING);
+ }
+ else
+ gtk_popover_set_state (popover, STATE_HIDDEN);
+
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
static void
-gtk_popover_map (GtkWidget *widget)
+gtk_popover_start_transition (GtkPopover *popover)
{
- GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
+ GtkPopoverPrivate *priv = popover->priv;
+ GdkFrameClock *clock;
+
+ if (priv->tick_id != 0)
+ return;
+
+ clock = gtk_widget_get_frame_clock (GTK_WIDGET (popover));
+ priv->start_time = gdk_frame_clock_get_frame_time (clock);
+ priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (popover),
+ show_animate_cb,
+ popover, NULL);
+}
+
+static void
+gtk_popover_set_state (GtkPopover *popover,
+ guint state)
+{
+ GtkPopoverPrivate *priv = popover->priv;
+
+ if (!transitions_enabled (popover) ||
+ !gtk_widget_get_realized (GTK_WIDGET (popover)))
+ {
+ if (state == STATE_SHOWING)
+ state = STATE_SHOWN;
+ else if (state == STATE_HIDING)
+ state = STATE_HIDDEN;
+ }
+
+ priv->state = state;
+ if (state == STATE_SHOWING || state == STATE_HIDING)
+ gtk_popover_start_transition (popover);
+ else
+ {
+ if (priv->tick_id)
+ {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (popover), priv->tick_id);
+ priv->tick_id = 0;
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (popover), state == STATE_SHOWN);
+ }
+}
+
+static void
+gtk_popover_map (GtkWidget *widget)
+{
GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
gdk_window_show (gtk_widget_get_window (widget));
gtk_popover_update_position (GTK_POPOVER (widget));
-
- if (priv->modal)
- gtk_popover_apply_modality (GTK_POPOVER (widget), TRUE);
}
static void
@@ -449,13 +588,8 @@ gtk_popover_unmap (GtkWidget *widget)
priv->button_pressed = FALSE;
- if (priv->modal)
- gtk_popover_apply_modality (GTK_POPOVER (widget), FALSE);
-
gdk_window_hide (gtk_widget_get_window (widget));
GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
-
- g_signal_emit (widget, signals[CLOSED], 0);
}
static void
@@ -853,6 +987,22 @@ gtk_popover_update_position (GtkPopover *popover)
priv->final_position = pos;
}
+ switch (priv->final_position)
+ {
+ case GTK_POS_TOP:
+ rect.y += priv->transition_diff;
+ break;
+ case GTK_POS_BOTTOM:
+ rect.y -= priv->transition_diff;
+ break;
+ case GTK_POS_LEFT:
+ rect.x += priv->transition_diff;
+ break;
+ case GTK_POS_RIGHT:
+ rect.x -= priv->transition_diff;
+ break;
+ }
+
_gtk_window_set_popover_position (priv->window, widget,
priv->final_position, &rect);
@@ -1281,8 +1431,12 @@ gtk_popover_key_press (GtkWidget *widget,
static void
gtk_popover_grab_focus (GtkWidget *widget)
{
+ GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
GtkWidget *child;
+ if (!priv->visible)
+ return;
+
/* Focus the first natural child */
child = gtk_bin_get_child (GTK_BIN (widget));
@@ -1295,6 +1449,10 @@ gtk_popover_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkPopover *popover = GTK_POPOVER (widget);
+ GtkPopoverPrivate *priv = popover->priv;
+
+ if (!priv->visible)
+ return FALSE;
if (!GTK_WIDGET_CLASS (gtk_popover_parent_class)->focus (widget, direction))
{
@@ -1319,6 +1477,53 @@ gtk_popover_focus (GtkWidget *widget,
return TRUE;
}
+static void
+gtk_popover_show (GtkWidget *widget)
+{
+ GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
+
+ priv->visible = TRUE;
+
+ GTK_WIDGET_CLASS (gtk_popover_parent_class)->show (widget);
+
+ if (priv->modal)
+ gtk_popover_apply_modality (GTK_POPOVER (widget), TRUE);
+
+ gtk_popover_set_state (GTK_POPOVER (widget), STATE_SHOWING);
+
+ if (gtk_widget_get_realized (widget))
+ gdk_window_input_shape_combine_region (gtk_widget_get_parent_window (widget),
+ NULL, 0, 0);
+}
+
+static void
+gtk_popover_hide (GtkWidget *widget)
+{
+ GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
+ cairo_region_t *region;
+
+ if (priv->visible)
+ {
+ priv->visible = FALSE;
+ g_signal_emit (widget, signals[CLOSED], 0);
+
+ if (priv->modal)
+ gtk_popover_apply_modality (GTK_POPOVER (widget), FALSE);
+ }
+
+ if (gtk_widget_get_realized (widget))
+ {
+ region = cairo_region_create ();
+ gdk_window_input_shape_combine_region (gtk_widget_get_parent_window (widget),
+ region, 0, 0);
+ cairo_region_destroy (region);
+ }
+
+ if (!priv->window || priv->state == STATE_HIDDEN)
+ GTK_WIDGET_CLASS (gtk_popover_parent_class)->hide (widget);
+ else if (priv->state != STATE_SHOWING)
+ gtk_popover_set_state (GTK_POPOVER (widget), STATE_HIDING);
+}
static void
gtk_popover_class_init (GtkPopoverClass *klass)
@@ -1345,6 +1550,8 @@ gtk_popover_class_init (GtkPopoverClass *klass)
widget_class->key_press_event = gtk_popover_key_press;
widget_class->grab_focus = gtk_popover_grab_focus;
widget_class->focus = gtk_popover_focus;
+ widget_class->show = gtk_popover_show;
+ widget_class->hide = gtk_popover_hide;
/**
* GtkPopover:relative-to:
@@ -1405,6 +1612,21 @@ gtk_popover_class_init (GtkPopoverClass *klass)
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
+ /**
+ * GtkPopover:transitions-enabled
+ *
+ * Whether show/hide transitions are enabled for this popover.
+ *
+ * Since: 3.16
+ */
+ g_object_class_install_property (object_class,
+ PROP_TRANSITIONS_ENABLED,
+ g_param_spec_boolean ("transitions-enabled",
+ P_("Transitions enabled"),
+ P_("Whether show/hide transitions are enabled or not"),
+ TRUE,
+ GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
+
signals[CLOSED] =
g_signal_new (I_("closed"),
G_TYPE_FROM_CLASS (object_class),
@@ -1514,7 +1736,12 @@ static void
_gtk_popover_parent_unmap (GtkWidget *widget,
GtkPopover *popover)
{
- gtk_widget_unmap (GTK_WIDGET (popover));
+ GtkPopoverPrivate *priv = popover->priv;
+
+ if (priv->state == STATE_SHOWING)
+ priv->visible = FALSE;
+ else if (priv->state == STATE_SHOWN)
+ gtk_popover_set_state (popover, STATE_HIDING);
}
static void
@@ -1974,6 +2201,52 @@ gtk_popover_get_modal (GtkPopover *popover)
return popover->priv->modal;
}
+/**
+ * gtk_popover_set_transitions_enabled:
+ * @popover: a #GtkPopover
+ * @transitions_enabled: Whether transitions are enabled
+ *
+ * Sets whether show/hide transitions are enabled on this popover
+ *
+ * Since: 3.16
+ */
+void
+gtk_popover_set_transitions_enabled (GtkPopover *popover,
+ gboolean transitions_enabled)
+{
+ GtkPopoverPrivate *priv = popover->priv;
+
+ g_return_if_fail (GTK_IS_POPOVER (popover));
+
+ transitions_enabled = !!transitions_enabled;
+
+ if (priv->transitions_enabled == transitions_enabled)
+ return;
+
+ priv->transitions_enabled = transitions_enabled;
+ g_object_notify (G_OBJECT (popover), "transitions-enabled");
+}
+
+/**
+ * gtk_popover_get_transitions_enabled:
+ * @popover: a #GtkPopover
+ *
+ * Returns whether show/hide transitions are enabled on this popover.
+ *
+ * Returns: #TRUE if the show and hide transitions of the given
+ * popover are enabled, #FALSE otherwise.
+ *
+ * Since: 3.16
+ */
+gboolean
+gtk_popover_get_transitions_enabled (GtkPopover *popover)
+{
+ g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
+
+ return popover->priv->transitions_enabled;
+}
+
+
static void
back_to_main (GtkWidget *popover)
{