[gnome-shell] st-theme-node: Add support for inset box-shadows



commit a3a6650e664c665bae97c3d335b5ce5c983598c3
Author: Florian Müllner <fmuellner gnome org>
Date:   Mon Feb 14 02:34:00 2011 +0100

    st-theme-node: Add support for inset box-shadows
    
    Implement inset box-shadows as in the CSS3 draft[0]. As the shadow
    should appear beneath the node's border, we pick the slow cairo based
    rendering path (though a cogl based path could be added in case the
    node has a solid background with no borders).
    
    [0] http://www.w3.org/TR/css3-background/#box-shadow
    
    https://bugzilla.gnome.org/show_bug.cgi?id=642334

 src/st/st-private.c            |   44 +++++++
 src/st/st-theme-node-drawing.c |  245 ++++++++++++++++++++++++++++++++++------
 src/st/st-theme-node.c         |    8 --
 3 files changed, 253 insertions(+), 44 deletions(-)
---
diff --git a/src/st/st-private.c b/src/st/st-private.c
index 54d9a4b..e265789 100644
--- a/src/st/st-private.c
+++ b/src/st/st-private.c
@@ -636,6 +636,23 @@ _st_create_shadow_material_from_actor (StShadow     *shadow_spec,
   return shadow_material;
 }
 
+/**
+ * _st_create_shadow_cairo_pattern:
+ * @shadow_spec: the definition of the shadow
+ * @src_pattern: surface pattern for which we create the shadow
+ *               (must be a surface pattern)
+ *
+ * This is a utility function for creating shadows used by
+ * st-theme-node.c; it's in this file to share the gaussian
+ * blur implementation. The usage of this function is quite different
+ * depending on whether shadow_spec->inset is %TRUE or not. If
+ * shadow_spec->inset is %TRUE, the caller should pass in a @src_pattern
+ * which is the <i>inverse</i> of what they want shadowed, and must take
+ * care of the spread and offset from the shadow spec themselves. If
+ * shadow_spec->inset is %FALSE then the caller should pass in what they
+ * want shadowed directly, and this function takes care of the spread and
+ * the offset.
+ */
 cairo_pattern_t *
 _st_create_shadow_cairo_pattern (StShadow        *shadow_spec,
                                  cairo_pattern_t *src_pattern)
@@ -649,6 +666,7 @@ _st_create_shadow_cairo_pattern (StShadow        *shadow_spec,
   gint             width_in, height_in, rowstride_in;
   gint             width_out, height_out, rowstride_out;
   cairo_matrix_t   shadow_matrix;
+  int i, j;
 
   g_return_val_if_fail (shadow_spec != NULL, NULL);
   g_return_val_if_fail (src_pattern != NULL, NULL);
@@ -684,6 +702,17 @@ _st_create_shadow_cairo_pattern (StShadow        *shadow_spec,
                             &width_out, &height_out, &rowstride_out);
   cairo_surface_destroy (surface_in);
 
+  /* Invert pixels for inset shadows */
+  if (shadow_spec->inset)
+    {
+      for (j = 0; j < height_out; j++)
+        {
+          guchar *p = pixels_out + rowstride_out * j;
+          for (i = 0; i < width_out; i++, p++)
+            *p = ~*p;
+        }
+    }
+
   surface_out = cairo_image_surface_create_for_data (pixels_out,
                                                      CAIRO_FORMAT_A8,
                                                      width_out,
@@ -695,6 +724,21 @@ _st_create_shadow_cairo_pattern (StShadow        *shadow_spec,
 
   cairo_pattern_get_matrix (src_pattern, &shadow_matrix);
 
+  if (shadow_spec->inset)
+    {
+      /* For inset shadows, offsets and spread radius have already been
+       * applied to the original pattern, so all left to do is shift the
+       * blurred image left, so that it aligns centered under the
+       * unblurred one
+       */
+      cairo_matrix_translate (&shadow_matrix,
+                              (width_out - width_in) / 2.0,
+                              (height_out - height_in) / 2.0);
+      cairo_pattern_set_matrix (dst_pattern, &shadow_matrix);
+
+      return dst_pattern;
+    }
+
   /* Read all the code from the cairo_pattern_set_matrix call
    * at the end of this function to here from bottom to top,
    * because each new affine transformation is applied in
diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c
index 8c322cf..b47032d 100644
--- a/src/st/st-theme-node-drawing.c
+++ b/src/st/st-theme-node-drawing.c
@@ -602,6 +602,89 @@ create_cairo_pattern_of_background_image (StThemeNode *node,
   return pattern;
 }
 
+/* fill_exterior = TRUE means that pattern is a surface pattern and
+ * we should extend the pattern with a solid fill from its edges.
+ * This is a bit of a hack; the alternative would be to make the
+ * surface of the surface pattern 1 pixel bigger and use CAIRO_EXTEND_PAD.
+ */
+static void
+paint_shadow_pattern_to_cairo_context (StShadow *shadow_spec,
+                                       cairo_pattern_t *pattern,
+                                       gboolean         fill_exterior,
+                                       cairo_t         *cr,
+                                       cairo_path_t    *interior_path,
+                                       cairo_path_t    *outline_path)
+{
+  /* If there are borders, clip the shadow to the interior
+   * of the borders; if there is a visible outline, clip the shadow to
+   * that outline
+   */
+  cairo_path_t *path = (interior_path != NULL) ? interior_path : outline_path;
+  double x1, x2, y1, y2;
+
+  /* fill_exterior only makes sense if we're clipping the shadow - filling
+   * to the edges of the surface would be silly */
+  g_assert (!(fill_exterior && path == NULL));
+
+  cairo_save (cr);
+  if (path != NULL)
+    {
+      cairo_append_path (cr, path);
+
+      /* There's no way to invert a path in cairo, so we need bounds for
+       * the area we are drawing in order to create the "exterior" region.
+       * Pixel align to hit fast paths.
+       */
+      if (fill_exterior)
+        {
+          cairo_path_extents (cr, &x1, &y1, &x2, &y2);
+          x1 = floor (x1);
+          y1 = floor (y1);
+          x2 = ceil (x2);
+          y2 = ceil (y2);
+        }
+
+      cairo_clip (cr);
+    }
+
+  cairo_set_source_rgba (cr,
+                         shadow_spec->color.red / 255.0,
+                         shadow_spec->color.green / 255.0,
+                         shadow_spec->color.blue / 255.0,
+                         shadow_spec->color.alpha / 255.0);
+  if (fill_exterior)
+    {
+      cairo_surface_t *surface;
+      int width, height;
+      cairo_matrix_t matrix;
+
+      cairo_save (cr);
+
+      /* Start with a rectangle enclosing the bounds of the clipped
+       * region */
+      cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+
+      /* Then subtract out the bounds of the surface in the surface
+       * pattern; we transform the context by the inverse of the
+       * pattern matrix to get to surface coordinates */
+      cairo_pattern_get_surface (pattern, &surface);
+      width = cairo_image_surface_get_width  (surface);
+      height = cairo_image_surface_get_height (surface);
+
+      cairo_pattern_get_matrix (pattern, &matrix);
+      cairo_matrix_invert (&matrix);
+      cairo_transform (cr, &matrix);
+
+      cairo_rectangle (cr, 0, height, width, - height);
+      cairo_fill (cr);
+
+      cairo_restore (cr);
+    }
+
+  cairo_mask (cr, pattern);
+  cairo_restore (cr);
+}
+
 static void
 paint_background_image_shadow_to_cairo_context (StThemeNode     *node,
                                                 StShadow        *shadow_spec,
@@ -661,52 +744,128 @@ paint_background_image_shadow_to_cairo_context (StThemeNode     *node,
                                                         pattern);
     }
 
-  /* Stamp the shadow pattern out in the appropriate color
-   * in a new layer
-   */
-  cairo_push_group (cr);
-  cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
-  cairo_paint (cr);
+  paint_shadow_pattern_to_cairo_context (shadow_spec,
+                                         shadow_pattern, FALSE,
+                                         cr,
+                                         interior_path,
+                                         outline_path);
+  cairo_pattern_destroy (shadow_pattern);
+}
 
-  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
-  cairo_set_source_rgba (cr,
-                         shadow_spec->color.red / 255.0,
-                         shadow_spec->color.green / 255.0,
-                         shadow_spec->color.blue / 255.0,
-                         shadow_spec->color.alpha / 255.0);
-  cairo_paint (cr);
+/* gets the extents of a cairo_path_t; slightly inefficient, but much simpler than
+ * computing from the raw path data */
+static void
+path_extents (cairo_path_t *path,
+              double       *x1,
+              double       *y1,
+              double       *x2,
+              double       *y2)
 
-  cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN);
+{
+  cairo_surface_t *dummy = cairo_image_surface_create (CAIRO_FORMAT_A8, 1, 1);
+  cairo_t *cr = cairo_create (dummy);
 
-  cairo_set_source (cr, shadow_pattern);
-  cairo_paint (cr);
-  cairo_pattern_destroy (shadow_pattern);
+  cairo_append_path (cr, path);
+  cairo_path_extents (cr, x1, y1, x2, y2);
 
-  cairo_pop_group_to_source (cr);
+  cairo_destroy (cr);
+  cairo_surface_destroy (dummy);
+}
 
-  /* mask and merge the shadow
+static void
+paint_inset_box_shadow_to_cairo_context (StThemeNode     *node,
+                                         StShadow        *shadow_spec,
+                                         cairo_t         *cr,
+                                         cairo_path_t    *shadow_outline)
+{
+  cairo_surface_t *shadow_surface;
+  cairo_pattern_t *shadow_pattern;
+  double extents_x1, extents_y1, extents_x2, extents_y2;
+  double shrunk_extents_x1, shrunk_extents_y1, shrunk_extents_x2, shrunk_extents_y2;
+  gboolean fill_exterior;
+
+  g_assert (shadow_spec != NULL);
+  g_assert (shadow_outline != NULL);
+
+  /* Create the pattern used to create the inset shadow; as the shadow
+   * should be drawn as if everything outside the outline was opaque,
+   * we use a temporary surface to draw the background as a solid shape,
+   * which is inverted when creating the shadow pattern.
    */
-  cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
-  cairo_save (cr);
-  if (interior_path != NULL)
+
+  /* First we need to find the size of the temporary surface
+   */
+  path_extents (shadow_outline,
+                &extents_x1, &extents_y1, &extents_x2, &extents_y2);
+
+  /* Shrink the extents by the spread, and offset */
+  shrunk_extents_x1 = extents_x1 + shadow_spec->xoffset + shadow_spec->spread;
+  shrunk_extents_y1 = extents_y1 + shadow_spec->yoffset + shadow_spec->spread;
+  shrunk_extents_x2 = extents_x2 + shadow_spec->xoffset - shadow_spec->spread;
+  shrunk_extents_y2 = extents_y2 + shadow_spec->yoffset - shadow_spec->spread;
+
+  if (shrunk_extents_x1 >= shrunk_extents_x2 || shrunk_extents_y1 >= shrunk_extents_x2)
     {
-      /* If there are borders, clip the shadow to the interior
-       * of the borders
-       */
-      cairo_append_path (cr, interior_path);
-      cairo_clip (cr);
+      /* Shadow occupies entire area within border */
+      shadow_pattern = cairo_pattern_create_rgb (0., 0., 0.);
+      fill_exterior = FALSE;
     }
-  else if (outline_path != NULL)
+  else
     {
-      /* If there is a visible outline, clip the shadow to
-       * that outline
-       */
-      cairo_append_path (cr, outline_path);
-      cairo_clip (cr);
+      /* Bounds of temporary surface */
+      int surface_x = floor (shrunk_extents_x1);
+      int surface_y = floor (shrunk_extents_y1);
+      int surface_width = ceil (shrunk_extents_x2) - surface_x;
+      int surface_height = ceil (shrunk_extents_y2) - surface_y;
+
+      /* Center of the original path */
+      double x_center = (extents_x1 + extents_x2) / 2;
+      double y_center = (extents_y1 + extents_y2) / 2;
+
+      cairo_pattern_t *pattern;
+      cairo_t *temp_cr;
+      cairo_matrix_t matrix;
+
+      shadow_surface = cairo_image_surface_create (CAIRO_FORMAT_A8, surface_width, surface_height);
+      temp_cr = cairo_create (shadow_surface);
+
+      /* Match the coordinates in the temporary context to the parent context */
+      cairo_translate (temp_cr, - surface_x, - surface_y);
+
+      /* Shadow offset */
+      cairo_translate (temp_cr, shadow_spec->xoffset, shadow_spec->yoffset);
+
+      /* Scale the path around the center to match the shrunk bounds */
+      cairo_translate (temp_cr, x_center, y_center);
+      cairo_scale (temp_cr,
+                   (shrunk_extents_x2 - shrunk_extents_x1) / (extents_x2 - extents_x1),
+                   (shrunk_extents_y2 - shrunk_extents_y1) / (extents_y2 - extents_y1));
+      cairo_translate (temp_cr, - x_center, - y_center);
+
+      cairo_append_path (temp_cr, shadow_outline);
+      cairo_fill (temp_cr);
+      cairo_destroy (temp_cr);
+
+      pattern = cairo_pattern_create_for_surface (shadow_surface);
+      cairo_surface_destroy (shadow_surface);
+
+      /* The pattern needs to be offset back to coordinates in the parent context */
+      cairo_matrix_init_translate (&matrix, - surface_x, - surface_y);
+      cairo_pattern_set_matrix (pattern, &matrix);
+
+      shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, pattern);
+      fill_exterior = TRUE;
+
+      cairo_pattern_destroy (pattern);
     }
 
-  cairo_paint (cr);
-  cairo_restore (cr);
+  paint_shadow_pattern_to_cairo_context (shadow_spec,
+                                         shadow_pattern, fill_exterior,
+                                         cr,
+                                         shadow_outline,
+                                         NULL);
+
+  cairo_pattern_destroy (shadow_pattern);
 }
 
 /* In order for borders to be smoothly blended with non-solid backgrounds,
@@ -722,6 +881,7 @@ st_theme_node_prerender_background (StThemeNode *node)
   cairo_t *cr;
   cairo_surface_t *surface;
   StShadow *shadow_spec;
+  StShadow *box_shadow_spec;
   cairo_pattern_t *pattern = NULL;
   cairo_path_t *outline_path = NULL;
   gboolean draw_solid_background = TRUE;
@@ -740,6 +900,7 @@ st_theme_node_prerender_background (StThemeNode *node)
   border_image = st_theme_node_get_border_image (node);
 
   shadow_spec = st_theme_node_get_background_image_shadow (node);
+  box_shadow_spec = st_theme_node_get_box_shadow (node);
 
   actor_box.x1 = 0;
   actor_box.x2 = node->alloc_width;
@@ -1019,6 +1180,15 @@ st_theme_node_prerender_background (StThemeNode *node)
       cairo_pattern_destroy (pattern);
     }
 
+  if (box_shadow_spec && box_shadow_spec->inset)
+    {
+      paint_inset_box_shadow_to_cairo_context (node,
+                                               box_shadow_spec,
+                                               cr,
+                                               interior_path ? interior_path
+                                                             : outline_path);
+    }
+
   if (outline_path != NULL)
     cairo_path_destroy (outline_path);
 
@@ -1107,6 +1277,7 @@ st_theme_node_render_resources (StThemeNode   *node,
   StBorderImage *border_image;
   gboolean has_border;
   gboolean has_border_radius;
+  gboolean has_inset_box_shadow;
   StShadow *box_shadow_spec;
   StShadow *background_image_shadow_spec;
   const char *background_image;
@@ -1126,6 +1297,7 @@ st_theme_node_render_resources (StThemeNode   *node,
   _st_theme_node_ensure_geometry (node);
 
   box_shadow_spec = st_theme_node_get_box_shadow (node);
+  has_inset_box_shadow = box_shadow_spec && box_shadow_spec->inset;
 
   if (node->border_width[ST_SIDE_TOP] > 0 ||
       node->border_width[ST_SIDE_LEFT] > 0 ||
@@ -1170,6 +1342,7 @@ st_theme_node_render_resources (StThemeNode   *node,
    * then we could use cogl for that case.
    */
   if ((node->background_gradient_type != ST_GRADIENT_NONE)
+      || (has_inset_box_shadow && (has_border || node->background_color.alpha > 0))
       || (background_image && (has_border || has_border_radius)))
     node->prerendered_texture = st_theme_node_prerender_background (node);
 
@@ -1178,7 +1351,7 @@ st_theme_node_render_resources (StThemeNode   *node,
   else
     node->prerendered_material = COGL_INVALID_HANDLE;
 
-  if (box_shadow_spec)
+  if (box_shadow_spec && !has_inset_box_shadow)
     {
       if (node->border_slices_texture != COGL_INVALID_HANDLE)
         node->box_shadow_material = _st_create_shadow_material (box_shadow_spec,
diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c
index f7f922d..2a857ab 100644
--- a/src/st/st-theme-node.c
+++ b/src/st/st-theme-node.c
@@ -2886,14 +2886,6 @@ st_theme_node_get_box_shadow (StThemeNode *node)
                                    FALSE,
                                    &shadow))
     {
-      if (shadow->inset)
-        {
-          g_warning ("Inset shadows are not implemented for the box-shadow "
-                     "property");
-          st_shadow_unref (shadow);
-          shadow = NULL;
-        }
-
       node->box_shadow = shadow;
 
       return node->box_shadow;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]