toolbar: add theatrics animation to the operations button

We have been going back and forth of a solution to make the operations
button noticeable enough once it appears.

First we implemented a highlight inside the button. But that wasn't
enough, so we had to show the operations popover at the start.

That is not really good because it appears in every window, and the user
has to explicitly close it, which is annoying.

This patch implements another animation highlight, which is more
visually bold to try to catch the attention, in order to not need to
show the operations popover.

https://bugzilla.gnome.org/show_bug.cgi?id=753728
parent ff83776e
......@@ -138,6 +138,14 @@ nautilus_built_sources = \
$(NULL)
nautilus_no_main_sources = \
animation/egg-animation.c \
animation/egg-animation.h \
animation/egg-frame-source.c \
animation/egg-frame-source.h \
animation/ide-box-theatric.c \
animation/ide-box-theatric.h \
animation/ide-cairo.c \
animation/ide-cairo.h \
gtk/nautilusgtkplacesview.c \
gtk/nautilusgtkplacesviewprivate.h \
gtk/nautilusgtkplacesviewrow.c \
......
This diff is collapsed.
/* egg-animation.h
*
* Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
*
* This file 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.1 of the License, or (at your option)
* any later version.
*
* This file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EGG_ANIMATION_H
#define EGG_ANIMATION_H
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define EGG_TYPE_ANIMATION (egg_animation_get_type())
#define EGG_TYPE_ANIMATION_MODE (egg_animation_mode_get_type())
G_DECLARE_FINAL_TYPE (EggAnimation, egg_animation,
EGG, ANIMATION, GInitiallyUnowned)
typedef enum _EggAnimationMode EggAnimationMode;
enum _EggAnimationMode
{
EGG_ANIMATION_LINEAR,
EGG_ANIMATION_EASE_IN_QUAD,
EGG_ANIMATION_EASE_OUT_QUAD,
EGG_ANIMATION_EASE_IN_OUT_QUAD,
EGG_ANIMATION_EASE_IN_CUBIC,
EGG_ANIMATION_EASE_OUT_CUBIC,
EGG_ANIMATION_EASE_IN_OUT_CUBIC,
EGG_ANIMATION_LAST
};
GType egg_animation_mode_get_type (void);
void egg_animation_start (EggAnimation *animation);
void egg_animation_stop (EggAnimation *animation);
void egg_animation_add_property (EggAnimation *animation,
GParamSpec *pspec,
const GValue *value);
EggAnimation *egg_object_animatev (gpointer object,
EggAnimationMode mode,
guint duration_msec,
GdkFrameClock *frame_clock,
const gchar *first_property,
va_list args);
EggAnimation* egg_object_animate (gpointer object,
EggAnimationMode mode,
guint duration_msec,
GdkFrameClock *frame_clock,
const gchar *first_property,
...) G_GNUC_NULL_TERMINATED;
EggAnimation* egg_object_animate_full (gpointer object,
EggAnimationMode mode,
guint duration_msec,
GdkFrameClock *frame_clock,
GDestroyNotify notify,
gpointer notify_data,
const gchar *first_property,
...) G_GNUC_NULL_TERMINATED;
G_END_DECLS
#endif /* EGG_ANIMATION_H */
/* egg-frame-source.c
*
* Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
*
* This file 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.1 of the License, or (at your option)
* any later version.
*
* This file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "animation/egg-frame-source.h"
typedef struct
{
GSource parent;
guint fps;
guint frame_count;
gint64 start_time;
} EggFrameSource;
static gboolean
egg_frame_source_prepare (GSource *source,
gint *timeout_)
{
EggFrameSource *fsource = (EggFrameSource *)(gpointer)source;
gint64 current_time;
guint elapsed_time;
guint new_frame_num;
guint frame_time;
current_time = g_source_get_time(source) / 1000;
elapsed_time = current_time - fsource->start_time;
new_frame_num = elapsed_time * fsource->fps / 1000;
/* If time has gone backwards or the time since the last frame is
* greater than the two frames worth then reset the time and do a
* frame now */
if (new_frame_num < fsource->frame_count ||
new_frame_num - fsource->frame_count > 2) {
/* Get the frame time rounded up to the nearest ms */
frame_time = (1000 + fsource->fps - 1) / fsource->fps;
/* Reset the start time */
fsource->start_time = current_time;
/* Move the start time as if one whole frame has elapsed */
fsource->start_time -= frame_time;
fsource->frame_count = 0;
*timeout_ = 0;
return TRUE;
} else if (new_frame_num > fsource->frame_count) {
*timeout_ = 0;
return TRUE;
} else {
*timeout_ = (fsource->frame_count + 1) * 1000 / fsource->fps - elapsed_time;
return FALSE;
}
}
static gboolean
egg_frame_source_check (GSource *source)
{
gint timeout_;
return egg_frame_source_prepare(source, &timeout_);
}
static gboolean
egg_frame_source_dispatch (GSource *source,
GSourceFunc source_func,
gpointer user_data)
{
EggFrameSource *fsource = (EggFrameSource *)(gpointer)source;
gboolean ret;
if ((ret = source_func(user_data)))
fsource->frame_count++;
return ret;
}
static GSourceFuncs source_funcs = {
egg_frame_source_prepare,
egg_frame_source_check,
egg_frame_source_dispatch,
};
/**
* egg_frame_source_add:
* @frames_per_sec: (in): Target frames per second.
* @callback: (in) (scope notified): A #GSourceFunc to execute.
* @user_data: (in): User data for @callback.
*
* Creates a new frame source that will execute when the timeout interval
* for the source has elapsed. The timing will try to synchronize based
* on the end time of the animation.
*
* Returns: A source id that can be removed with g_source_remove().
*/
guint
egg_frame_source_add (guint frames_per_sec,
GSourceFunc callback,
gpointer user_data)
{
EggFrameSource *fsource;
GSource *source;
guint ret;
g_return_val_if_fail (frames_per_sec > 0, 0);
g_return_val_if_fail (frames_per_sec <= 120, 0);
source = g_source_new(&source_funcs, sizeof(EggFrameSource));
fsource = (EggFrameSource *)(gpointer)source;
fsource->fps = frames_per_sec;
fsource->frame_count = 0;
fsource->start_time = g_get_monotonic_time() / 1000;
g_source_set_callback(source, callback, user_data, NULL);
g_source_set_name(source, "EggFrameSource");
ret = g_source_attach(source, NULL);
g_source_unref(source);
return ret;
}
/* egg-frame-source.h
*
* Copyright (C) 2010-2016 Christian Hergert <christian@hergert.me>
*
* This file 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.1 of the License, or (at your option)
* any later version.
*
* This file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EGG_FRAME_SOURCE_H
#define EGG_FRAME_SOURCE_H
#include <glib.h>
G_BEGIN_DECLS
guint egg_frame_source_add (guint frames_per_sec,
GSourceFunc callback,
gpointer user_data);
G_END_DECLS
#endif /* EGG_FRAME_SOURCE_H */
/* ide-box-theatric.c
*
* Copyright (C) 2014 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "ide-box-theatric"
#include <animation/egg-animation.h>
#include <glib/gi18n.h>
#include "animation/ide-box-theatric.h"
#include "animation/ide-cairo.h"
struct _IdeBoxTheatric
{
GObject parent_instance;
GtkWidget *target;
GtkWidget *toplevel;
GIcon *icon;
cairo_surface_t *icon_surface;
guint icon_surface_size;
GdkRectangle area;
GdkRectangle last_area;
GdkRGBA background_rgba;
gdouble alpha;
guint draw_handler;
guint background_set : 1;
guint pixbuf_failed : 1;
};
enum {
PROP_0,
PROP_ALPHA,
PROP_BACKGROUND,
PROP_HEIGHT,
PROP_ICON,
PROP_TARGET,
PROP_WIDTH,
PROP_X,
PROP_Y,
LAST_PROP
};
G_DEFINE_TYPE (IdeBoxTheatric, ide_box_theatric, G_TYPE_OBJECT)
static GParamSpec *properties[LAST_PROP];
static void
get_toplevel_rect (IdeBoxTheatric *theatric,
GdkRectangle *area)
{
gtk_widget_translate_coordinates (theatric->target, theatric->toplevel,
theatric->area.x, theatric->area.y,
&area->x, &area->y);
area->width = theatric->area.width;
area->height = theatric->area.height;
}
static gboolean
on_toplevel_draw (GtkWidget *widget,
cairo_t *cr,
IdeBoxTheatric *self)
{
GdkRectangle area;
g_assert (IDE_IS_BOX_THEATRIC (self));
get_toplevel_rect (self, &area);
#if 0
g_print ("Drawing on %s at %d,%d %dx%d\n",
g_type_name (G_TYPE_FROM_INSTANCE (widget)),
area.x, area.y, area.width, area.height);
#endif
if (self->background_set)
{
GdkRGBA bg;
bg = self->background_rgba;
bg.alpha = self->alpha;
ide_cairo_rounded_rectangle (cr, &area, 3, 3);
gdk_cairo_set_source_rgba (cr, &bg);
cairo_fill (cr);
}
/* Load an icon if necessary and cache the surface */
if (self->icon != NULL && self->icon_surface == NULL && !self->pixbuf_failed)
{
g_autoptr(GtkIconInfo) icon_info = NULL;
GtkIconTheme *icon_theme;
gint width;
width = area.width * 4;
icon_theme = gtk_icon_theme_get_default ();
icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme,
self->icon,
width,
GTK_ICON_LOOKUP_FORCE_SIZE);
if (icon_info != NULL)
{
GdkWindow *window = gtk_widget_get_window (widget);
g_autoptr(GdkPixbuf) pixbuf = NULL;
GtkStyleContext *context;
context = gtk_widget_get_style_context (self->target);
pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context, NULL, NULL);
if (pixbuf != NULL)
{
self->icon_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 0, window);
self->icon_surface_size = width;
self->pixbuf_failed = FALSE;
}
else
{
self->pixbuf_failed = TRUE;
}
}
}
if (self->icon_surface != NULL)
{
cairo_translate (cr, area.x, area.y);
cairo_rectangle (cr, 0, 0, area.width, area.height);
cairo_scale (cr,
area.width / (gdouble)self->icon_surface_size,
area.height / (gdouble)self->icon_surface_size);
cairo_set_source_surface (cr, self->icon_surface, 0, 0);
cairo_paint_with_alpha (cr, self->alpha);
}
self->last_area = area;
return FALSE;
}
static void
ide_box_theatric_notify (GObject *object,
GParamSpec *pspec)
{
IdeBoxTheatric *self = IDE_BOX_THEATRIC (object);
if (G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify)
G_OBJECT_CLASS (ide_box_theatric_parent_class)->notify (object, pspec);
if (self->target && self->toplevel)
{
GdkWindow *window;
GdkRectangle area;
get_toplevel_rect (IDE_BOX_THEATRIC (object), &area);
#if 0
g_print (" Invalidate : %d,%d %dx%d\n",
area.x, area.y, area.width, area.height);
#endif
window = gtk_widget_get_window (self->toplevel);
if (window != NULL)
{
gdk_window_invalidate_rect (window, &self->last_area, TRUE);
gdk_window_invalidate_rect (window, &area, TRUE);
}
}
}
static void
ide_box_theatric_dispose (GObject *object)
{
IdeBoxTheatric *self = IDE_BOX_THEATRIC (object);
if (self->target)
{
if (self->draw_handler && self->toplevel)
{
g_signal_handler_disconnect (self->toplevel, self->draw_handler);
self->draw_handler = 0;
}
g_object_remove_weak_pointer (G_OBJECT (self->target),
(gpointer *) &self->target);
self->target = NULL;
}
g_clear_pointer (&self->icon_surface, cairo_surface_destroy);
g_clear_object (&self->icon);
G_OBJECT_CLASS (ide_box_theatric_parent_class)->dispose (object);
}
static void
ide_box_theatric_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
switch (prop_id)
{
case PROP_ALPHA:
g_value_set_double (value, theatric->alpha);
break;
case PROP_BACKGROUND:
g_value_take_string (value,
gdk_rgba_to_string (&theatric->background_rgba));
break;
case PROP_HEIGHT:
g_value_set_int (value, theatric->area.height);
break;
case PROP_ICON:
g_value_set_object (value, theatric->icon);
break;
case PROP_TARGET:
g_value_set_object (value, theatric->target);
break;
case PROP_WIDTH:
g_value_set_int (value, theatric->area.width);
break;
case PROP_X:
g_value_set_int (value, theatric->area.x);
break;
case PROP_Y:
g_value_set_int (value, theatric->area.y);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_box_theatric_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeBoxTheatric *theatric = IDE_BOX_THEATRIC (object);
switch (prop_id)
{
case PROP_ALPHA:
theatric->alpha = g_value_get_double (value);
break;
case PROP_BACKGROUND:
{
const gchar *str = g_value_get_string (value);
if (str == NULL)
{
gdk_rgba_parse (&theatric->background_rgba, "#000000");
theatric->background_rgba.alpha = 0;
theatric->background_set = FALSE;
}
else
{
gdk_rgba_parse (&theatric->background_rgba, str);
theatric->background_set = TRUE;
}
}
break;
case PROP_HEIGHT:
theatric->area.height = g_value_get_int (value);
break;
case PROP_ICON:
g_clear_pointer (&theatric->icon_surface, cairo_surface_destroy);
g_clear_object (&theatric->icon);
theatric->icon = g_value_dup_object (value);
theatric->pixbuf_failed = FALSE;
break;
case PROP_TARGET:
theatric->target = g_value_get_object (value);
theatric->toplevel = gtk_widget_get_toplevel (theatric->target);
g_object_add_weak_pointer (G_OBJECT (theatric->target),
(gpointer *) &theatric->target);
theatric->draw_handler =
g_signal_connect_after (theatric->toplevel,
"draw",
G_CALLBACK (on_toplevel_draw),
theatric);
break;
case PROP_WIDTH:
theatric->area.width = g_value_get_int (value);
break;
case PROP_X:
theatric->area.x = g_value_get_int (value);
break;
case PROP_Y:
theatric->area.y = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
g_object_notify_by_pspec (object, pspec);
}
static void
ide_box_theatric_class_init (IdeBoxTheatricClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->dispose = ide_box_theatric_dispose;
object_class->notify = ide_box_theatric_notify;
object_class->get_property = ide_box_theatric_get_property;
object_class->set_property = ide_box_theatric_set_property;
properties[PROP_ALPHA] =
g_param_spec_double ("alpha",
"Alpha",
"Alpha",
0.0,
1.0,
1.0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_BACKGROUND] =
g_param_spec_string ("background",
"background",
"background",
"#000000",
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_HEIGHT] =
g_param_spec_int ("height",
"height",
"height",
0,
G_MAXINT,
0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_ICON] =
g_param_spec_object ("icon",
"Icon",
"The GIcon to render over the background",
G_TYPE_ICON,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_TARGET] =
g_param_spec_object ("target",
"Target",
"Target",
GTK_TYPE_WIDGET,
(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
properties[PROP_WIDTH] =
g_param_spec_int ("width",
"width",
"width",
0,
G_MAXINT,
0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_X] =
g_param_spec_int ("x",
"x",
"x",
G_MININT,
G_MAXINT,
0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_Y] =
g_param_spec_int ("y",
"y",
"y",
G_MININT,
G_MAXINT,
0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, LAST_PROP, properties);
}
static void
ide_box_theatric_init (IdeBoxTheatric *theatric)
{
}
/* ide-box-theatric.h
*
* Copyright (C) 2014 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IDE_BOX_THEATRIC_H
#define IDE_BOX_THEATRIC_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define IDE_TYPE_BOX_THEATRIC (ide_box_theatric_get_type())
G_DECLARE_FINAL_TYPE (IdeBoxTheatric, ide_box_theatric,
IDE, BOX_THEATRIC, GObject)
G_END_DECLS
#endif /* IDE_BOX_THEATRIC_H */
/* ide-cairo.c
*
* Copyright (C) 2014 Christian Hergert <christian@hergert.me>
*
* This file 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.1 of the License, or (at your option) any later version.
*
* This file 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ide-cairo.h"