Commit 9f4942e9 authored by Owen W. Taylor's avatar Owen W. Taylor
Browse files

Make shadows pretty and configurable

The current shadow code just uses a single fixed texture (the Gaussian
blur of a rectangle with a fixed blur radius) for drawing all window
shadows. This patch adds the ability

* Implement efficient blurring of arbitrary regions by approximating
  a Gaussian blur with multiple box blurs.

* Detect when multiple windows can use the same shadow texture by
  converting their shape into a size-invariant MetaWindowShape.

* Add properties shadow-radius, shadow-x-offset, shadow-y-offset,
  shadow-opacity to allow the shadow for a window to be configured.

* Add meta_window_actor_paint() and draw the shadow directly
  from there rather than using a child actor.

* Remove TidyTextureFrame, which is no longer used

https://bugzilla.gnome.org/show_bug.cgi?id=592382
parent 982a10ac
......@@ -28,6 +28,8 @@ mutter_SOURCES= \
compositor/meta-plugin.c \
compositor/meta-plugin-manager.c \
compositor/meta-plugin-manager.h \
compositor/meta-shadow-factory.c \
compositor/meta-shadow-factory.h \
compositor/meta-shaped-texture.c \
compositor/meta-shaped-texture.h \
compositor/meta-texture-tower.c \
......@@ -36,10 +38,10 @@ mutter_SOURCES= \
compositor/meta-window-actor-private.h \
compositor/meta-window-group.c \
compositor/meta-window-group.h \
compositor/shadow.c \
compositor/shadow.h \
compositor/tidy/tidy-texture-frame.c \
compositor/tidy/tidy-texture-frame.h \
compositor/meta-window-shape.c \
compositor/meta-window-shape.h \
compositor/region-utils.c \
compositor/region-utils.h \
include/compositor.h \
include/meta-plugin.h \
include/meta-window-actor.h \
......
This diff is collapsed.
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* MetaShadowFactory:
*
* Create and cache shadow textures for arbitrary window shapes
*
* Copyright (C) 2010 Red Hat, Inc.
*
* 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 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifndef __META_SHADOW_FACTORY_H__
#define __META_SHADOW_FACTORY_H__
#include <clutter/clutter.h>
#include "meta-window-shape.h"
/**
* MetaShadow:
* #MetaShadow holds a shadow texture along with information about how to
* apply that texture to draw a window texture. (E.g., it knows how big the
* unscaled borders are on each side of the shadow texture.)
*/
typedef struct _MetaShadow MetaShadow;
MetaShadow *meta_shadow_ref (MetaShadow *shadow);
void meta_shadow_unref (MetaShadow *shadow);
CoglHandle meta_shadow_get_texture (MetaShadow *shadow);
void meta_shadow_paint (MetaShadow *shadow,
int window_x,
int window_y,
int window_width,
int window_height,
guint8 opacity);
void meta_shadow_get_bounds (MetaShadow *shadow,
int window_x,
int window_y,
int window_width,
int window_height,
cairo_rectangle_int_t *bounds);
/**
* MetaShadowFactory:
* #MetaShadowFactory is used to create window shadows. It caches shadows internally
* so that multiple shadows created for the same shape with the same radius will
* share the same MetaShadow.
*/
typedef struct _MetaShadowFactory MetaShadowFactory;
MetaShadowFactory *meta_shadow_factory_get_default (void);
MetaShadowFactory *meta_shadow_factory_new (void);
void meta_shadow_factory_free (MetaShadowFactory *factory);
MetaShadow * meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
MetaWindowShape *shape,
int width,
int height,
int radius);
#endif /* __META_SHADOW_FACTORY_H__ */
......@@ -17,10 +17,9 @@
#include "xprops.h"
#include "compositor-private.h"
#include "meta-shadow-factory.h"
#include "meta-shaped-texture.h"
#include "meta-window-actor-private.h"
#include "shadow.h"
#include "tidy/tidy-texture-frame.h"
struct _MetaWindowActorPrivate
{
......@@ -31,12 +30,13 @@ struct _MetaWindowActorPrivate
MetaScreen *screen;
ClutterActor *actor;
ClutterActor *shadow;
MetaShadow *shadow;
Pixmap back_pixmap;
Damage damage;
guint8 opacity;
guint8 shadow_opacity;
gchar * desc;
......@@ -46,8 +46,15 @@ struct _MetaWindowActorPrivate
* texture */
cairo_region_t *bounding_region;
/* Extracted size-invariant shape used for shadows */
MetaWindowShape *shadow_shape;
gint freeze_count;
gint shadow_radius;
gint shadow_x_offset;
gint shadow_y_offset;
/*
* These need to be counters rather than flags, since more plugins
* can implement same effect; the practicality of stacking effects
......@@ -70,7 +77,9 @@ struct _MetaWindowActorPrivate
guint received_damage : 1;
guint needs_pixmap : 1;
guint needs_reshape : 1;
guint needs_reshape : 1;
guint recompute_shadow : 1;
guint paint_shadow : 1;
guint size_changed : 1;
guint needs_destroy : 1;
......@@ -87,8 +96,16 @@ enum
PROP_X_WINDOW,
PROP_X_WINDOW_ATTRIBUTES,
PROP_NO_SHADOW,
PROP_SHADOW_RADIUS,
PROP_SHADOW_X_OFFSET,
PROP_SHADOW_Y_OFFSET,
PROP_SHADOW_OPACITY
};
#define DEFAULT_SHADOW_RADIUS 12
#define DEFAULT_SHADOW_X_OFFSET 0
#define DEFAULT_SHADOW_Y_OFFSET 8
static void meta_window_actor_dispose (GObject *object);
static void meta_window_actor_finalize (GObject *object);
static void meta_window_actor_constructed (GObject *object);
......@@ -101,6 +118,8 @@ static void meta_window_actor_get_property (GObject *object,
GValue *value,
GParamSpec *pspec);
static void meta_window_actor_paint (ClutterActor *actor);
static void meta_window_actor_detach (MetaWindowActor *self);
static gboolean meta_window_actor_has_shadow (MetaWindowActor *self);
......@@ -161,6 +180,7 @@ static void
meta_window_actor_class_init (MetaWindowActorClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
GParamSpec *pspec;
g_type_class_add_private (klass, sizeof (MetaWindowActorPrivate));
......@@ -171,6 +191,8 @@ meta_window_actor_class_init (MetaWindowActorClass *klass)
object_class->get_property = meta_window_actor_get_property;
object_class->constructed = meta_window_actor_constructed;
actor_class->paint = meta_window_actor_paint;
pspec = g_param_spec_object ("meta-window",
"MetaWindow",
"The displayed MetaWindow",
......@@ -216,11 +238,52 @@ meta_window_actor_class_init (MetaWindowActorClass *klass)
"No shadow",
"Do not add shaddow to this window",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
G_PARAM_READWRITE);
g_object_class_install_property (object_class,
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-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);
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,
pspec);
}
static void
......@@ -232,6 +295,11 @@ meta_window_actor_init (MetaWindowActor *self)
META_TYPE_WINDOW_ACTOR,
MetaWindowActorPrivate);
priv->opacity = 0xff;
priv->shadow_radius = DEFAULT_SHADOW_RADIUS;
priv->shadow_x_offset = DEFAULT_SHADOW_X_OFFSET;
priv->shadow_y_offset = DEFAULT_SHADOW_Y_OFFSET;
priv->shadow_opacity = 0xff;
priv->paint_shadow = TRUE;
}
static void
......@@ -291,18 +359,6 @@ window_decorated_notify (MetaWindow *mw,
g_object_set (self, "x-window-attributes", &attrs, NULL);
if (priv->shadow)
{
ClutterActor *p = clutter_actor_get_parent (priv->shadow);
if (CLUTTER_IS_CONTAINER (p))
clutter_container_remove_actor (CLUTTER_CONTAINER (p), priv->shadow);
else
clutter_actor_unparent (priv->shadow);
priv->shadow = NULL;
}
/*
* Recreate the contents.
*/
......@@ -344,13 +400,6 @@ meta_window_actor_constructed (GObject *object)
meta_window_actor_update_opacity (self);
if (meta_window_actor_has_shadow (self))
{
priv->shadow = meta_create_shadow_frame (compositor);
clutter_container_add_actor (CLUTTER_CONTAINER (self), priv->shadow);
}
if (!priv->actor)
{
priv->actor = meta_shaped_texture_new ();
......@@ -407,6 +456,18 @@ meta_window_actor_dispose (GObject *object)
meta_window_actor_clear_shape_region (self);
meta_window_actor_clear_bounding_region (self);
if (priv->shadow != NULL)
{
meta_shadow_unref (priv->shadow);
priv->shadow = NULL;
}
if (priv->shadow_shape != NULL)
{
meta_window_shape_unref (priv->shadow_shape);
priv->shadow_shape = NULL;
}
if (priv->damage != None)
{
meta_error_trap_push (display);
......@@ -463,36 +524,60 @@ meta_window_actor_set_property (GObject *object,
break;
case PROP_NO_SHADOW:
{
gboolean oldv = priv->no_shadow ? TRUE : FALSE;
gboolean newv = g_value_get_boolean (value);
if (oldv == newv)
if (newv == priv->no_shadow)
return;
priv->no_shadow = newv;
if (newv && priv->shadow)
{
clutter_container_remove_actor (CLUTTER_CONTAINER (object),
priv->shadow);
priv->shadow = NULL;
}
else if (!newv && !priv->shadow && meta_window_actor_has_shadow (self))
{
gfloat w, h;
MetaDisplay *display = meta_screen_get_display (priv->screen);
MetaCompositor *compositor;
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_X_OFFSET:
{
gint newv = g_value_get_int (value);
if (newv == priv->shadow_x_offset)
return;
compositor = meta_display_get_compositor (display);
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);
clutter_actor_get_size (CLUTTER_ACTOR (self), &w, &h);
if (newv == priv->shadow_y_offset)
return;
priv->shadow = meta_create_shadow_frame (compositor);
priv->shadow_y_offset = newv;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
break;
case PROP_SHADOW_OPACITY:
{
guint newv = g_value_get_uint (value);
clutter_actor_set_size (priv->shadow, w, h);
if (newv == priv->shadow_opacity)
return;
clutter_container_add_actor (CLUTTER_CONTAINER (self), priv->shadow);
}
priv->shadow_opacity = newv;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
break;
default:
......@@ -526,12 +611,57 @@ 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_X_OFFSET:
g_value_set_int (value, priv->shadow_x_offset);
break;
case PROP_SHADOW_Y_OFFSET:
g_value_set_int (value, priv->shadow_y_offset);
break;
case PROP_SHADOW_OPACITY:
g_value_set_uint (value, priv->shadow_opacity);
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_window_actor_get_shape_bounds (MetaWindowActor *self,
cairo_rectangle_int_t *bounds)
{
MetaWindowActorPrivate *priv = self->priv;
if (priv->shaped)
cairo_region_get_extents (priv->shape_region, bounds);
else
cairo_region_get_extents (priv->bounding_region, bounds);
}
static void
meta_window_actor_paint (ClutterActor *actor)
{
MetaWindowActor *self = META_WINDOW_ACTOR (actor);
MetaWindowActorPrivate *priv = self->priv;
if (priv->shadow != NULL && priv->paint_shadow)
{
cairo_rectangle_int_t shape_bounds;
meta_window_actor_get_shape_bounds (self, &shape_bounds);
meta_shadow_paint (priv->shadow,
priv->shadow_x_offset + shape_bounds.x,
priv->shadow_y_offset + shape_bounds.y,
shape_bounds.width,
shape_bounds.height,
(clutter_actor_get_paint_opacity (actor) * priv->shadow_opacity) / 255);
}
CLUTTER_ACTOR_CLASS (meta_window_actor_parent_class)->paint (actor);
}
static gboolean
is_shaped (MetaDisplay *display, Window xwindow)
{
......@@ -560,10 +690,12 @@ meta_window_actor_has_shadow (MetaWindowActor *self)
if (priv->no_shadow)
return FALSE;
if (priv->shadow_radius == 0)
return FALSE;
/*
* Always put a shadow around windows with a frame - This should override
* the restriction about not putting a shadow around shaped windows
* as the frame might be the reason the window is shaped
* the restriction about not putting a shadow around ARGB windows.
*/
if (priv->window)
{
......@@ -576,7 +708,8 @@ meta_window_actor_has_shadow (MetaWindowActor *self)
}
/*
* Do not add shadows to ARGB windows (since they are probably transparent)
* Do not add shadows to ARGB windows; eventually we should generate a
* shadow from the input shape for such windows.
*/
if (priv->argb32 || priv->opacity != 0xff)
{
......@@ -585,19 +718,8 @@ meta_window_actor_has_shadow (MetaWindowActor *self)
return FALSE;
}
/*
* Never put a shadow around shaped windows
*/
if (priv->shaped)
{
meta_verbose ("Window 0x%x has no shadow as it is shaped\n",
(guint)priv->xwindow);
return FALSE;
}
/*
* Add shadows to override redirect windows (e.g., Gtk menus).
* This must have lower priority than window shape test.
*/
if (priv->attrs.override_redirect)
{
......@@ -1366,9 +1488,26 @@ meta_window_actor_update_bounding_region (MetaWindowActor *self,
MetaWindowActorPrivate *priv = self->priv;
cairo_rectangle_int_t bounding_rectangle = { 0, 0, width, height };
if (priv->bounding_region != NULL)
{
cairo_rectangle_int_t old_bounding_rectangle;
cairo_region_get_extents (priv->bounding_region, &old_bounding_rectangle);
if (old_bounding_rectangle.width == width && old_bounding_rectangle.height == height)
return;
}
meta_window_actor_clear_bounding_region (self);
priv->bounding_region = cairo_region_create_rectangle (&bounding_rectangle);
/* When we're shaped, we use the shape region to generate the shadow; the shape
* region only changes when we get ShapeNotify event; but for unshaped windows
* we generate the shadow from the bounding region, so we need to recompute
* the shadow when the size changes.
*/
if (!priv->shaped)
priv->recompute_shadow = TRUE;
}
static void
......@@ -1500,8 +1639,8 @@ meta_window_actor_set_visible_region_beneath (MetaWindowActor *self,
if (priv->shadow)
{
cairo_rectangle_int_t shadow_rect;
ClutterActorBox box;
cairo_rectangle_int_t shape_bounds;
cairo_rectangle_int_t shadow_bounds;
cairo_region_overlap_t overlap;
/* We could compute an full clip region as we do for the window
......@@ -1510,17 +1649,17 @@ meta_window_actor_set_visible_region_beneath (MetaWindowActor *self,
* the shadow is completely obscured and doesn't need to be drawn
* at all.
*/
clutter_actor_get_allocation_box (priv->shadow, &box);
shadow_rect.x = roundf (box.x1);
shadow_rect.y = roundf (box.y1);
shadow_rect.width = roundf (box.x2 - box.x1);
shadow_rect.height = roundf (box.y2 - box.y1);
meta_window_actor_get_shape_bounds (self, &shape_bounds);
overlap = cairo_region_contains_rectangle (beneath_region, &shadow_rect);
meta_shadow_get_bounds (priv->shadow,
priv->shadow_x_offset + shape_bounds.x,
priv->shadow_y_offset + shape_bounds.y,
shape_bounds.width,
shape_bounds.height,
&shadow_bounds);
tidy_texture_frame_set_needs_paint (TIDY_TEXTURE_FRAME (priv->shadow),
overlap != CAIRO_REGION_OVERLAP_OUT);
overlap = cairo_region_contains_rectangle (beneath_region, &shadow_bounds);
priv->paint_shadow = overlap != CAIRO_REGION_OVERLAP_OUT;
}
}
......@@ -1538,8 +1677,7 @@ meta_window_actor_reset_visible_regions (MetaWindowActor *self)
meta_shaped_texture_set_clip_region (META_SHAPED_TEXTURE (priv->actor),
NULL);
if (priv->shadow)
tidy_texture_frame_set_needs_paint (TIDY_TEXTURE_FRAME (priv->shadow), TRUE);
priv->paint_shadow = TRUE;
}
static void
......@@ -1622,9 +1760,6 @@ check_needs_pixmap (MetaWindowActor *self)
"pixmap-height", &pxm_height,
NULL);
if (priv->shadow)
clutter_actor_set_size (priv->shadow, pxm_width, pxm_height);
meta_window_actor_update_bounding_region (self, pxm_width, pxm_height);
full = TRUE;
......@@ -1635,6 +1770,58 @@ check_needs_pixmap (MetaWindowActor *self)
priv->needs_pixmap = FALSE;
}
static void
check_needs_shadow (MetaWindowActor *self)
{
MetaWindowActorPrivate *priv = self->priv;
MetaShadow *old_shadow = NULL;
gboolean should_have_shadow;
if (!priv->mapped)
return;
/* Calling meta_window_actor_has_shadow() here at every pre-paint is cheap
* and avoids the need to explicitly handle window type changes, which
* we would do if tried to keep track of when we might be adding or removing
* a shadow more explicitly. We only keep track of changes to the *shape* of
* the shadow with priv->recompute_shadow.
*/
should_have_shadow = meta_window_actor_has_shadow (self);
if (priv->shadow != NULL && (!should_have_shadow || priv->recompute_shadow))
{
old_shadow = priv->shadow;
priv->shadow = NULL;
}
if (priv->shadow == NULL && should_have_shadow)
{
MetaShadowFactory *factory = meta_shadow_factory_get_default ();
cairo_rectangle_int_t shape_bounds;
if (priv->shadow_shape == NULL)
{
if (priv->shaped)
priv->shadow_shape = meta_window_shape_new (priv->shape_region);
else
priv->shadow_shape = meta_window_shape_new (priv->bounding_region);
}
meta_window_actor_get_shape_bounds (self, &shape_bounds);
priv->shadow = meta_shadow_factory_get_shadow (factory,
priv->shadow_shape,
shape_bounds.width, shape_bounds.height,
priv->shadow_radius);
}
if (old_shadow != NULL)
meta_shadow_unref (old_shadow);