Commit 1bbaec81 authored by Owen W. Taylor's avatar Owen W. Taylor
Browse files

Make window shadows globally configurable

Instead of setting shadow parameters on individual windows, add the
idea of a "shadow class". Windows have default shadow classes based
on their frame and window type, which can be overriden by setting
the shadow-class property.

Each shadow class has separably configurable parameters for the
focused and unfocused state. New shadow classes can be defined with
arbitrary names.

https://bugzilla.gnome.org/show_bug.cgi?id=592382
parent 7952feb4
......@@ -11,6 +11,7 @@
#include "compositor-mutter.h"
#include "xprops.h"
#include "prefs.h"
#include "meta-shadow-factory.h"
#include "meta-window-actor-private.h"
#include "meta-window-group.h"
#include "../core/window-private.h" /* to check window->hidden */
......@@ -1017,6 +1018,26 @@ meta_repaint_func (gpointer data)
return TRUE;
}
static void
on_shadow_factory_changed (MetaShadowFactory *factory,
MetaCompositor *compositor)
{
GSList *screens = meta_display_get_screens (compositor->display);
GList *l;
GSList *sl;
for (sl = screens; sl; sl = sl->next)
{
MetaScreen *screen = sl->data;
MetaCompScreen *info = meta_screen_get_compositor_data (screen);
if (!info)
continue;
for (l = info->windows; l; l = l->next)
meta_window_actor_invalidate_shadow (l->data);
}
}
/**
* meta_compositor_new: (skip)
*
......@@ -1047,6 +1068,11 @@ meta_compositor_new (MetaDisplay *display)
XInternAtoms (xdisplay, atom_names, G_N_ELEMENTS (atom_names),
False, atoms);
g_signal_connect (meta_shadow_factory_get_default (),
"changed",
G_CALLBACK (on_shadow_factory_changed),
compositor);
compositor->atom_x_root_pixmap = atoms[0];
compositor->atom_x_set_root = atoms[1];
compositor->atom_net_wm_window_opacity = atoms[2];
......
......@@ -56,11 +56,11 @@ void meta_shadow_get_bounds (MetaShadow *shadow,
MetaShadowFactory *meta_shadow_factory_new (void);
MetaShadow * meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
MetaWindowShape *shape,
int width,
int height,
int radius,
int top_fade);
MetaShadow *meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
MetaWindowShape *shape,
int width,
int height,
const char *class_name,
gboolean focused);
#endif /* __META_SHADOW_FACTORY_PRIVATE_H__ */
......@@ -48,7 +48,8 @@
* - We approximate the 1D gaussian blur as 3 successive box filters.
*/
typedef struct _MetaShadowCacheKey MetaShadowCacheKey;
typedef struct _MetaShadowCacheKey MetaShadowCacheKey;
typedef struct _MetaShadowClassInfo MetaShadowClassInfo;
struct _MetaShadowCacheKey
{
......@@ -82,6 +83,13 @@ struct _MetaShadow
guint scale_height : 1;
};
struct _MetaShadowClassInfo
{
const char *name; /* const so we can reuse for static definitions */
MetaShadowParams focused;
MetaShadowParams unfocused;
};
struct _MetaShadowFactory
{
GObject parent_instance;
......@@ -89,6 +97,9 @@ struct _MetaShadowFactory
/* MetaShadowCacheKey => MetaShadow; the shadows are not referenced
* by the factory, they are simply removed from the table when freed */
GHashTable *shadows;
/* class name => MetaShadowClassInfo */
GHashTable *shadow_classes;
};
struct _MetaShadowFactoryClass
......@@ -96,6 +107,31 @@ struct _MetaShadowFactoryClass
GObjectClass parent_class;
};
enum
{
CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
/* The first element in this array also defines the default parameters
* for newly created classes */
MetaShadowClassInfo default_shadow_classes[] = {
{ "normal", { 12, -1, 0, 8, 255 }, { 6, -1, 0, 4, 255 } },
{ "dialog", { 12, -1, 0, 8, 255 }, { 6, -1, 0, 4, 255 } },
{ "modal_dialog", { 12, -1, 0, 8, 255 }, { 6, -1, 0, 4, 255 } },
{ "utility", { 12, -1, 0, 8, 255 }, { 6, -1, 0, 4, 255 } },
{ "border", { 12, -1, 0, 8, 255 }, { 6, -1, 0, 4, 255 } },
{ "menu", { 12, -1, 0, 8, 255 }, { 6, -1, 0, 4, 255 } },
{ "popup-menu", { 6, -1, 0, 4, 255 }, { 6, -1, 0, 4, 255 } },
{ "dropdown-menu", { 6, 25, 0, 4, 255 }, { 6, 100, 0, 4, 255 } },
{ "attached", { 6, 25, 0, 4, 255 }, { 6, 100, 0, 4, 255 } }
};
G_DEFINE_TYPE (MetaShadowFactory, meta_shadow_factory, G_TYPE_OBJECT);
static guint
......@@ -262,11 +298,36 @@ meta_shadow_get_bounds (MetaShadow *shadow,
bounds->height = window_height + shadow->outer_border_top + shadow->outer_border_bottom;
}
static void
meta_shadow_class_info_free (MetaShadowClassInfo *class_info)
{
g_free ((char *)class_info->name);
g_slice_free (MetaShadowClassInfo, class_info);
}
static void
meta_shadow_factory_init (MetaShadowFactory *factory)
{
guint i;
factory->shadows = g_hash_table_new (meta_shadow_cache_key_hash,
meta_shadow_cache_key_equal);
factory->shadow_classes = g_hash_table_new_full (g_str_hash,
g_str_equal,
NULL,
(GDestroyNotify)meta_shadow_class_info_free);
for (i = 0; i < G_N_ELEMENTS (default_shadow_classes); i++)
{
MetaShadowClassInfo *class_info = g_slice_new (MetaShadowClassInfo);
*class_info = default_shadow_classes[i];
class_info->name = g_strdup (class_info->name);
g_hash_table_insert (factory->shadow_classes,
(char *)class_info->name, class_info);
}
}
static void
......@@ -286,6 +347,7 @@ meta_shadow_factory_finalize (GObject *object)
}
g_hash_table_destroy (factory->shadows);
g_hash_table_destroy (factory->shadow_classes);
G_OBJECT_CLASS (meta_shadow_factory_parent_class)->finalize (object);
}
......@@ -296,6 +358,15 @@ meta_shadow_factory_class_init (MetaShadowFactoryClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = meta_shadow_factory_finalize;
signals[CHANGED] =
g_signal_new ("changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
MetaShadowFactory *
......@@ -657,15 +728,45 @@ make_shadow (MetaShadow *shadow,
cogl_material_set_layer (shadow->material, 0, shadow->texture);
}
static MetaShadowParams *
get_shadow_params (MetaShadowFactory *factory,
const char *class_name,
gboolean focused,
gboolean create)
{
MetaShadowClassInfo *class_info = g_hash_table_lookup (factory->shadow_classes,
class_name);
if (class_info == NULL)
{
if (create)
{
class_info = g_slice_new0 (MetaShadowClassInfo);
*class_info = default_shadow_classes[0];
class_info->name = g_strdup (class_info->name);
g_hash_table_insert (factory->shadow_classes,
(char *)class_info->name, class_info);
}
else
{
class_info = &default_shadow_classes[0];
}
}
if (focused)
return &class_info->focused;
else
return &class_info->unfocused;
}
/**
* meta_shadow_factory_get_shadow:
* @factory: a #MetaShadowFactory
* @shape: the size-invariant shape of the window's region
* @width: the actual width of the window's region
* @width: the actual height of the window's region
* @radius: the radius (gaussian standard deviation) of the shadow
* @top_fade: if >= 0, the shadow doesn't extend above the top
* of the shape, and fades out over the given number of pixels
* @height: the actual height of the window's region
* @class_name: name of the class of window shadows
* @focused: whether the shadow is for a focused window
*
* Gets the appropriate shadow object for drawing shadows for the
* specified window shape. The region that we are shadowing is specified
......@@ -677,13 +778,14 @@ make_shadow (MetaShadow *shadow,
* meta_shadow_unref()
*/
MetaShadow *
meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
MetaWindowShape *shape,
int width,
int height,
int radius,
int top_fade)
meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
MetaWindowShape *shape,
int width,
int height,
const char *class_name,
gboolean focused)
{
MetaShadowParams *params;
MetaShadowCacheKey key;
MetaShadow *shadow;
cairo_region_t *region;
......@@ -721,15 +823,18 @@ meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
* In the case where we are fading a the top, that also has to fit
* within the top unscaled border.
*/
spread = get_shadow_spread (radius);
params = get_shadow_params (factory, class_name, focused, FALSE);
spread = get_shadow_spread (params->radius);
meta_window_shape_get_borders (shape,
&shape_border_top,
&shape_border_right,
&shape_border_bottom,
&shape_border_left);
inner_border_top = MAX (shape_border_top + spread, top_fade);
outer_border_top = top_fade >= 0 ? 0 : spread;
inner_border_top = MAX (shape_border_top + spread, params->top_fade);
outer_border_top = params->top_fade >= 0 ? 0 : spread;
inner_border_right = shape_border_right + spread;
outer_border_right = spread;
inner_border_bottom = shape_border_bottom + spread;
......@@ -744,8 +849,8 @@ meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
if (cacheable)
{
key.shape = shape;
key.radius = radius;
key.top_fade = top_fade;
key.radius = params->radius;
key.top_fade = params->top_fade;
shadow = g_hash_table_lookup (factory->shadows, &key);
if (shadow)
......@@ -757,8 +862,8 @@ meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
shadow->ref_count = 1;
shadow->factory = factory;
shadow->key.shape = meta_window_shape_ref (shape);
shadow->key.radius = radius;
shadow->key.top_fade = top_fade;
shadow->key.radius = params->radius;
shadow->key.top_fade = params->top_fade;
shadow->outer_border_top = outer_border_top;
shadow->inner_border_top = inner_border_top;
......@@ -793,3 +898,69 @@ meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
return shadow;
}
/**
* meta_shadow_factory_set_params:
* @factory: a #MetaShadowFactory
* @class_name: name of the class of shadow to set the params for.
* the default shadow classes are the names of the different
* theme frame types (normal, dialog, modal_dialog, utility,
* border, menu, attached) and in addition, popup-menu
* and dropdown-menu.
* @focused: whether the shadow is for a focused window
* @params: new parameter values
*
* Updates the shadow parameters for a particular class of shadows
* for either the focused or unfocused state. If the class name
* does not name an existing class, a new class will be created
* (the other focus state for that class will have default values
* assigned to it.)
*/
void
meta_shadow_factory_set_params (MetaShadowFactory *factory,
const char *class_name,
gboolean focused,
MetaShadowParams *params)
{
MetaShadowParams *stored_params;
g_return_if_fail (META_IS_SHADOW_FACTORY (factory));
g_return_if_fail (class_name != NULL);
g_return_if_fail (params != NULL);
g_return_if_fail (params->radius >= 0);
stored_params = get_shadow_params (factory, class_name, focused, TRUE);
*stored_params = *params;
g_signal_emit (factory, signals[CHANGED], 0);
}
/**
* meta_shadow_factory_get_params:
* @factory: a #MetaShadowFactory
* @class_name: name of the class of shadow to get the params for
* @focused: whether the shadow is for a focused window
* @params: (out caller-allocates): location to store the current parameter values
*
* Gets the shadow parameters for a particular class of shadows
* for either the focused or unfocused state. If the class name
* does not name an existing class, default values will be returned
* without printing an error.
*/
void
meta_shadow_factory_get_params (MetaShadowFactory *factory,
const char *class_name,
gboolean focused,
MetaShadowParams *params)
{
MetaShadowParams *stored_params;
g_return_if_fail (META_IS_SHADOW_FACTORY (factory));
g_return_if_fail (class_name != NULL);
stored_params = get_shadow_params (factory, class_name, focused, FALSE);
if (params)
*params = *stored_params;
}
......@@ -28,6 +28,8 @@ void meta_window_actor_process_damage (MetaWindowActor *self,
XDamageNotifyEvent *event);
void meta_window_actor_pre_paint (MetaWindowActor *self);
void meta_window_actor_invalidate_shadow (MetaWindowActor *self);
gboolean meta_window_actor_effect_in_progress (MetaWindowActor *self);
void meta_window_actor_sync_actor_position (MetaWindowActor *self);
void meta_window_actor_sync_visibility (MetaWindowActor *self);
......
......@@ -30,7 +30,21 @@ struct _MetaWindowActorPrivate
MetaScreen *screen;
ClutterActor *actor;
MetaShadow *shadow;
/* MetaShadowFactory only caches shadows that are actually in use;
* to avoid unnecessary recomputation we do two things: 1) we store
* both a focused and unfocused shadow for the window. If the window
* doesn't have different focused and unfocused shadow parameters,
* these will be the same. 2) when the shadow potentially changes we
* don't immediately unreference the old shadow, we just flag it as
* dirty and recompute it when we next need it (recompute_focused_shadow,
* recompute_unfocused_shadow.) Because of our extraction of
* size-invariant window shape, we'll often find that the new shadow
* is the same as the old shadow.
*/
MetaShadow *focused_shadow;
MetaShadow *unfocused_shadow;
Pixmap back_pixmap;
Damage damage;
......@@ -51,10 +65,7 @@ struct _MetaWindowActorPrivate
gint freeze_count;
gint shadow_radius;
gint shadow_top_fade;
gint shadow_x_offset;
gint shadow_y_offset;
char * shadow_class;
/*
* These need to be counters rather than flags, since more plugins
......@@ -79,7 +90,8 @@ struct _MetaWindowActorPrivate
guint needs_pixmap : 1;
guint needs_reshape : 1;
guint recompute_shadow : 1;
guint recompute_focused_shadow : 1;
guint recompute_unfocused_shadow : 1;
guint paint_shadow : 1;
guint size_changed : 1;
......@@ -97,11 +109,7 @@ enum
PROP_X_WINDOW,
PROP_X_WINDOW_ATTRIBUTES,
PROP_NO_SHADOW,
PROP_SHADOW_RADIUS,
PROP_SHADOW_TOP_FADE,
PROP_SHADOW_X_OFFSET,
PROP_SHADOW_Y_OFFSET,
PROP_SHADOW_OPACITY
PROP_SHADOW_CLASS
};
#define DEFAULT_SHADOW_RADIUS 12
......@@ -246,56 +254,14 @@ meta_window_actor_class_init (MetaWindowActorClass *klass)
PROP_NO_SHADOW,
pspec);
pspec = g_param_spec_int ("shadow-radius",
"Shadow Radius",
"Radius (standard deviation of gaussian blur) of window's shadow",
0, 128, DEFAULT_SHADOW_RADIUS,
G_PARAM_READWRITE);
g_object_class_install_property (object_class,
PROP_SHADOW_RADIUS,
pspec);
pspec = g_param_spec_int ("shadow-top-fade",
"Shadow Top Fade",
"If >= 0, the shadow doesn't extend above the top "
"of the window, and fades out over the given number of pixels",
-1, G_MAXINT, -1,
G_PARAM_READWRITE);
g_object_class_install_property (object_class,
PROP_SHADOW_TOP_FADE,
pspec);
pspec = g_param_spec_int ("shadow-x-offset",
"Shadow X Offset",
"Distance shadow is offset in the horizontal direction in pixels",
G_MININT, G_MAXINT, DEFAULT_SHADOW_X_OFFSET,
G_PARAM_READWRITE);
pspec = g_param_spec_string ("shadow-class",
"Name of the shadow class for this window.",
"NULL means to use the default shadow class for this window type",
NULL,
G_PARAM_READWRITE);
g_object_class_install_property (object_class,
PROP_SHADOW_X_OFFSET,
pspec);
pspec = g_param_spec_int ("shadow-y-offset",
"Shadow Y Offset",
"Distance shadow is offset in the vertical direction in piyels",
G_MININT, G_MAXINT, DEFAULT_SHADOW_Y_OFFSET,
G_PARAM_READWRITE);
g_object_class_install_property (object_class,
PROP_SHADOW_Y_OFFSET,
pspec);
pspec = g_param_spec_uint ("shadow-opacity",
"Shadow Opacity",
"Opacity of the window's shadow",
0, 255,
255,
G_PARAM_READWRITE);
g_object_class_install_property (object_class,
PROP_SHADOW_OPACITY,
PROP_SHADOW_CLASS,
pspec);
}
......@@ -308,11 +274,7 @@ meta_window_actor_init (MetaWindowActor *self)
META_TYPE_WINDOW_ACTOR,
MetaWindowActorPrivate);
priv->opacity = 0xff;
priv->shadow_radius = DEFAULT_SHADOW_RADIUS;
priv->shadow_top_fade = -1;
priv->shadow_x_offset = DEFAULT_SHADOW_X_OFFSET;
priv->shadow_y_offset = DEFAULT_SHADOW_Y_OFFSET;
priv->shadow_opacity = 0xff;
priv->shadow_class = NULL;
priv->paint_shadow = TRUE;
}
......@@ -470,10 +432,22 @@ meta_window_actor_dispose (GObject *object)
meta_window_actor_clear_shape_region (self);
meta_window_actor_clear_bounding_region (self);
if (priv->shadow != NULL)
if (priv->shadow_class != NULL)
{
g_free (priv->shadow_class);
priv->shadow_class = NULL;
}
if (priv->focused_shadow != NULL)
{
meta_shadow_unref (priv->shadow);
priv->shadow = NULL;
meta_shadow_unref (priv->focused_shadow);
priv->focused_shadow = NULL;
}
if (priv->unfocused_shadow != NULL)
{
meta_shadow_unref (priv->unfocused_shadow);
priv->unfocused_shadow = NULL;
}
if (priv->shadow_shape != NULL)
......@@ -545,65 +519,20 @@ meta_window_actor_set_property (GObject *object,
priv->no_shadow = newv;
priv->recompute_shadow = TRUE;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
break;
case PROP_SHADOW_RADIUS:
{
gint newv = g_value_get_int (value);
if (newv == priv->shadow_radius)
return;
priv->shadow_radius = newv;
priv->recompute_shadow = TRUE;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
break;
case PROP_SHADOW_TOP_FADE:
{
gint newv = g_value_get_int (value);
if (newv == priv->shadow_top_fade)
return;
priv->shadow_top_fade = newv;
priv->recompute_shadow = TRUE;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
meta_window_actor_invalidate_shadow (self);
}
break;
case PROP_SHADOW_X_OFFSET:
case PROP_SHADOW_CLASS:
{
gint newv = g_value_get_int (value);
const char *newv = g_value_get_string (value);
if (newv == priv->shadow_x_offset)
if (g_strcmp0 (newv, priv->shadow_class) == 0)
return;
priv->shadow_x_offset = newv;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
break;
case PROP_SHADOW_Y_OFFSET:
{
gint newv = g_value_get_int (value);
if (newv == priv->shadow_y_offset)
return;
priv->shadow_y_offset = newv;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
break;
case PROP_SHADOW_OPACITY:
{
guint newv = g_value_get_uint (value);
if (newv == priv->shadow_opacity)
return;
g_free (priv->shadow_class);
priv->shadow_class = g_strdup (newv);
priv->shadow_opacity = newv;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
meta_window_actor_invalidate_shadow (self);
}
break;
default:
......@@ -637,26 +566,53 @@ meta_window_actor_get_property (GObject *object,
case PROP_NO_SHADOW:
g_value_set_boolean (value, priv->no_shadow);
break;
case PROP_SHADOW_RADIUS:
g_value_set_int (value, priv->shadow_radius);
break;
case PROP_SHADOW_TOP_FADE:
g_value_set_int (value, priv->shadow_top_fade);
break;