Subject: Re: Drop Shadow Effect for GTK2
To: None <tech-pkg@NetBSD.org>
From: Joel CARNAT <joel@carnat.net>
List: tech-pkg
Date: 08/02/2004 16:03:34
--a8Wt8u1KmwUX3Y2C
Content-Type: text/plain; charset=iso-8859-15
Content-Disposition: inline

humpf... now, the attachment is attached :)

SHA1 (patch-menushadow) = 1536b03553c9c94348cf20abc8df86a25ae6d884

On Mon, Aug 02 2004 - 16:00, Joel CARNAT wrote:
> Hi,
> 
> Find attached a patch that enables drop shadow on GTK2 menu.
> 
> It's taken from http://www.xfce.org/gtkmenu-shadow/index.html
> Tested on NetBSD-2.0-BETA/i386 with gtk+-2.4.2 (pkgsrc-2004Q2 branch)
> 
> It's pure eye-candy, but I love that effects.
> Maybe we can have it by default :)
> 
> Cheers,
> 	Jo
> 
> PS: I didn't subscribe to this ML, please Cc me for any direct remarks.

--a8Wt8u1KmwUX3Y2C
Content-Type: text/plain; charset=iso-8859-15
Content-Disposition: attachment; filename=patch-menushadow

--- gtk/gtkmenu.c.orig	2004-06-04 16:15:20.000000000 +0200
+++ gtk/gtkmenu.c	2004-08-02 15:37:19.000000000 +0200
@@ -50,4 +50,5 @@
 #define DEFAULT_POPUP_DELAY    225
 #define DEFAULT_POPDOWN_DELAY  1000
+#define DEFAULT_SHADOW_DELAY   50
 
 #define NAVIGATION_REGION_OVERSHOOT 50  /* How much the navigation region
@@ -91,4 +92,9 @@
   gint n_rows;
   gint n_columns;
+
+  /* Shadow patch addon */
+  GdkPixbuf *east, *south;
+  GdkWindow *east_shadow, *south_shadow;
+  guint32 timeout_id;
 };
 
@@ -123,4 +129,58 @@
 };
 
+enum side {
+  EAST_SIDE,
+  SOUTH_SIDE
+};
+
+const double shadow_strip_l[5] = {
+  .937, .831, .670, .478, .180
+};
+
+const double bottom_left_corner[25] = {
+  1.00, .682, .423, .333, .258,
+  1.00, .898, .800, .682, .584, 
+  1.00, .937, .874, .800, .737, 
+  1.00, .968, .937, .898, .866,
+  1.00, .988, .976, .960, .945
+};
+
+const double bottom_right_corner[25] = {
+  .258, .584, .737, .866, .945,
+  .584, .682, .800, .898, .960,
+  .737, .800, .874, .937, .976,
+  .866, .898, .937, .968, .988,
+  .945, .960, .976, .988, .996 
+};
+
+const double top_right_corner[25] = {
+  1.00, 1.00, 1.00, 1.00, 1.00, 
+  .686, .898, .937, .968, .988, 
+  .423, .803, .874, .937, .976, 
+  .333, .686, .800, .898, .960,
+  .258, .584, .737, .866, .945
+};
+
+const double top_left_corner[25] = {
+  .988, .968, .937, .898, .498,
+  .976, .937, .874, .803, .423, 
+  .960, .898, .800, .686, .333, 
+  .945, .866, .737, .584, .258,
+  .941, .847, .698, .521, .215
+}; 
+
+static GdkPixbuf *get_pixbuf               (GtkMenu *menu,
+					    int x,
+					    int y,
+					    int width,
+					    int height);
+static void     shadow_paint               (GtkWidget *widget,
+					    GdkRectangle *area, 
+					    enum side shadow);
+static void     pixbuf_add_shadow          (GdkPixbuf *pb,
+					    enum side shadow);
+static gboolean map_shadow_windows         (gpointer data);
+static void     shadow_add_timeout         (GtkWidget *widget);
+static void     shadow_remove_timeout      (GtkWidget *widget);
 static void     gtk_menu_class_init        (GtkMenuClass     *klass);
 static void     gtk_menu_init              (GtkMenu          *menu);
@@ -237,4 +297,7 @@
   GtkMenuPrivate *priv = (GtkMenuPrivate *)data;
 
+  if (priv->timeout_id > 0) 
+    g_source_remove (priv->timeout_id);
+
   g_free (priv->heights);
 
@@ -457,4 +520,305 @@
 }
 
+static GdkPixbuf *
+get_pixbuf (GtkMenu *menu,
+	    int x,
+	    int y,
+	    int width,
+	    int height)
+{
+  GdkPixbuf *dest, *src;
+  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET(menu));
+  GdkWindow *root = gdk_screen_get_root_window (screen);
+  gint screen_height = gdk_screen_get_height (screen);
+  gint screen_width = gdk_screen_get_width (screen);
+  gint original_width = width;
+  gint original_height = height;
+    
+  if (x < 0) 
+    {
+      width += x;
+      x = 0;
+    }
+
+  if (y < 0) 
+    {
+      height += y;
+      y = 0;
+    }
+
+  if (x + width > screen_width) 
+    {
+      width = screen_width - x;
+    }
+
+  if (y + height > screen_height) 
+    {
+      height = screen_height - y;
+    }
+
+  if (width <= 0 || height <= 0)
+    return NULL;
+
+  dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 
+                         original_width, original_height);
+  src = gdk_pixbuf_get_from_drawable (NULL, root, NULL, x, y, 0, 0, 
+                                      width, height);
+  gdk_pixbuf_copy_area (src, 0, 0, width, height, dest, 0, 0);
+
+  g_object_unref (G_OBJECT (src));
+  
+  return dest;
+}
+
+static void
+shadow_paint(GtkWidget *widget, GdkRectangle *area, enum side shadow)
+{
+  GtkMenu *menu = GTK_MENU (widget);
+  GtkMenuPrivate *private = gtk_menu_get_private (menu);
+  gint width, height;
+  GdkGC *gc = widget->style->black_gc;
+
+  switch (shadow) 
+    {
+      case EAST_SIDE:
+	if (private->east != NULL)
+	  {
+	    if (area)
+	      gdk_gc_set_clip_rectangle (gc, area);
+
+	    width = gdk_pixbuf_get_width (private->east);
+	    height = gdk_pixbuf_get_height (private->east);
+
+	    gdk_draw_pixbuf (private->east_shadow, gc, private->east, 0, 0, 0, 0,
+			     width, height, GDK_RGB_DITHER_NONE, 0, 0);
+
+	    if (area)
+	      gdk_gc_set_clip_rectangle (gc, NULL);
+	  }
+	break;
+      case SOUTH_SIDE:
+	if (private->south != NULL)
+	  {
+	    if (area)
+	      gdk_gc_set_clip_rectangle (gc, area);
+
+	    width = gdk_pixbuf_get_width (private->south);
+	    height = gdk_pixbuf_get_height (private->south);
+
+	    gdk_draw_pixbuf (private->south_shadow, gc, private->south, 0, 0, 0, 0,
+			     width, height, GDK_RGB_DITHER_NONE, 0, 0);
+
+	    if (area)
+	      gdk_gc_set_clip_rectangle (gc, NULL);
+	  }
+	break;
+      default:
+	break;
+    }
+}
+
+static void
+pixbuf_add_shadow (GdkPixbuf *pb,
+	           enum side shadow)
+{
+  gint width, rowstride, height;
+  gint i;
+  guchar *pixels, *p;
+
+  width = gdk_pixbuf_get_width (pb);
+  height = gdk_pixbuf_get_height (pb);
+  rowstride = gdk_pixbuf_get_rowstride (pb);
+  pixels = gdk_pixbuf_get_pixels (pb);
+
+  switch (shadow) 
+    {
+      case EAST_SIDE:
+	if (height > 5) 
+	  {
+	    for (i = 0; i < width; i++) 
+	      {
+		gint j, k;
+
+		p = pixels + (i * rowstride);
+		for (j = 0, k = 0; j < 3 * width; j += 3, k++) 
+		  {
+		    p[j] = (guchar) (p[j] * top_right_corner [i * width + k]);
+		    p[j + 1] = (guchar) (p[j + 1] * top_right_corner [i * width + k]);
+		    p[j + 2] = (guchar) (p[j + 2] * top_right_corner [i * width + k]);
+		  }
+	      }
+
+	    i = 5;
+	  } 
+	else 
+	  {
+	    i = 0;
+	  }
+
+	for (;i < height; i++) 
+	  {
+	    gint j, k;
+
+	    p = pixels + (i * rowstride);
+	    for (j = 0, k = 0; j < 3 * width; j += 3, k++) 
+	      {
+		p[j] = (guchar) (p[j] * shadow_strip_l[width - 1 - k]);
+		p[j + 1] = (guchar) (p[j + 1] * shadow_strip_l[width - 1 - k]);
+		p[j + 2] = (guchar) (p[j + 2] * shadow_strip_l[width - 1 - k]);
+	      }
+	  }
+	break;
+
+      case SOUTH_SIDE:
+	for (i = 0; i < height; i++) 
+	  {
+	    gint j, k;
+
+	    p = pixels + (i * rowstride);
+	    for (j = 0, k = 0; j < 3 * height; j += 3, k++) 
+	      {
+
+		p[j] = (guchar) (p[j] * bottom_left_corner[i * height + k]);
+		p[j + 1] = (guchar) (p[j + 1] * bottom_left_corner[i * height + k]);
+		p[j + 2] = (guchar) (p[j + 2] * bottom_left_corner[i * height + k]);
+	      }
+
+	    p = pixels + (i * rowstride) + 3 * height;
+	    for (j = 0, k = 0; j < (width * 3) - (6 * height); j += 3, k++)
+	      {
+		p[j] = (guchar) (p[j] * bottom_right_corner [i * height]);
+		p[j + 1] = (guchar) (p[j + 1] * bottom_right_corner [i * height]);
+		p[j + 2] = (guchar) (p[j + 2] * bottom_right_corner [i * height]);
+	      }
+
+	    p = pixels + (i * rowstride) + ((width * 3) - (3 * height));
+	    for (j = 0, k = 0; j < 3 * height; j += 3, k++) 
+	      {
+		p[j] = (guchar) (p[j] * bottom_right_corner[i * height + k]);
+		p[j + 1] = (guchar) (p[j + 1] * bottom_right_corner[i * height + k]);
+		p[j + 2] = (guchar) (p[j + 2] * bottom_right_corner[i * height + k]);
+	      }
+	  }
+	break;
+
+      default:
+	break;
+    }
+}
+
+static gboolean
+map_shadow_windows (gpointer data)
+{
+  GtkMenu *menu = GTK_MENU (data);
+  GtkMenuPrivate *private = gtk_menu_get_private (menu);
+  GtkWidget *widget = GTK_WIDGET (data);
+  GdkPixbuf *pixbuf;
+
+  pixbuf = get_pixbuf (menu, 
+		       private->x + widget->allocation.width, private->y,
+		       5, widget->allocation.height);
+  if (pixbuf != NULL) 
+    {
+      pixbuf_add_shadow (pixbuf, EAST_SIDE);
+      if (private->east != NULL) 
+	{
+	  g_object_unref (G_OBJECT (private->east));
+	}
+      private->east = pixbuf;
+    }
+  
+  pixbuf = get_pixbuf (menu, 
+		       private->x, private->y + widget->allocation.height,
+		       widget->allocation.width + 5, 5);
+  if (pixbuf != NULL) 
+    {
+      pixbuf_add_shadow (pixbuf, SOUTH_SIDE);
+      if (private->south != NULL) 
+	{
+	  g_object_unref (G_OBJECT (private->south));
+	}
+      private->south = pixbuf;
+    }
+
+  gdk_window_move_resize (private->east_shadow, 
+			  private->x + widget->allocation.width, private->y, 
+			  5, widget->allocation.height);
+
+  gdk_window_move_resize (private->south_shadow, 
+			  private->x, private->y + widget->allocation.height, 
+			  widget->allocation.width + 5, 5);
+
+  gdk_window_show (private->east_shadow);
+  gdk_window_show (private->south_shadow);
+
+  shadow_paint(widget, NULL, EAST_SIDE);
+  shadow_paint(widget, NULL, SOUTH_SIDE);
+
+  private->timeout_id = 0;
+  return FALSE;
+}
+
+static void
+shadow_add_timeout(GtkWidget *widget)
+{
+  GtkMenuPrivate *private = gtk_menu_get_private (GTK_MENU (widget));
+  gboolean menu_shadow;
+  gint shadow_delay;
+
+  if (private->have_position)
+    {
+      g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
+		"gtk-menu-drop-shadow", &menu_shadow, NULL);
+
+      if (menu_shadow)
+	{
+	  if (private->timeout_id > 0) 
+	    {
+	      g_source_remove (private->timeout_id);
+	    } 
+
+
+	  g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
+			"gtk-menu-shadow-delay", &shadow_delay,
+			NULL);
+
+	  private->timeout_id = g_timeout_add (shadow_delay, map_shadow_windows, widget);
+	}
+    }
+}
+
+static void
+shadow_remove_timeout (GtkWidget *widget)
+{
+  GtkMenu *menu = GTK_MENU (widget);
+  GtkMenuPrivate *private = gtk_menu_get_private (menu);
+
+  if (private->timeout_id > 0) 
+    {
+      g_source_remove (private->timeout_id);
+      private->timeout_id = 0;
+    } 
+  else 
+    {
+      if (private->east_shadow)
+	gdk_window_hide (private->east_shadow);
+	
+      if (private->south_shadow)
+	gdk_window_hide (private->south_shadow);
+
+      if (private->east)
+        {
+          g_object_unref (G_OBJECT (private->east));
+          private->east = NULL;
+        }
+
+      if (private->south)
+        {
+          g_object_unref (G_OBJECT (private->south));
+          private->south = NULL;
+        }
+    }
+}
+
 static void
 gtk_menu_class_init (GtkMenuClass *class)
@@ -687,4 +1051,18 @@
 						   G_PARAM_READWRITE));
 						   
+  gtk_settings_install_property (g_param_spec_boolean ("gtk-menu-drop-shadow",
+						       _("Display menu drop-shadow"),
+						       _("Whether menu drop-shadow should be displayed"),
+						       TRUE,
+						       G_PARAM_READWRITE));
+
+  gtk_settings_install_property (g_param_spec_int ("gtk-menu-shadow-delay",
+						   _("Delay before drop-shadow appear"),
+						   _("Minimum time before drop-shadow appear under the menu"),
+						   0,
+						   G_MAXINT,
+						   DEFAULT_SHADOW_DELAY,
+						   G_PARAM_READWRITE));
+
 }
 
@@ -893,4 +1271,13 @@
 
   priv->have_layout = FALSE;
+
+  /* Shadow patch */
+  priv->east_shadow = NULL;
+  priv->south_shadow = NULL;
+  
+  priv->east = NULL;
+  priv->south = NULL;
+  
+  priv->timeout_id = 0;
 }
 
@@ -959,4 +1346,5 @@
   GtkMenuPrivate *private = gtk_menu_get_private (menu);
 
+  shadow_remove_timeout(GTK_WIDGET(menu));
   if (menu->torn_off)
     {
@@ -1390,4 +1778,5 @@
   if (xgrab_shell == widget)
     popup_grab_on_window (widget->window, activate_time); /* Should always succeed */
+  shadow_add_timeout(GTK_WIDGET (menu));
   gtk_grab_add (GTK_WIDGET (menu));
 }
@@ -1400,5 +1789,5 @@
 
   g_return_if_fail (GTK_IS_MENU (menu));
-  
+
   menu_shell = GTK_MENU_SHELL (menu);
   private = gtk_menu_get_private (menu);
@@ -1465,4 +1854,5 @@
   gtk_grab_remove (GTK_WIDGET (menu));
 
+  shadow_remove_timeout(GTK_WIDGET (menu));
   menu_grab_transfer_window_destroy (menu);
 }
@@ -1929,8 +2319,14 @@
     {
       GtkMenu *menu = GTK_MENU (widget);
+      GtkMenuPrivate *private;
       
+      private = gtk_menu_get_private (menu);
+
       gtk_style_set_background (widget->style, menu->bin_window, GTK_STATE_NORMAL);
       gtk_style_set_background (widget->style, menu->view_window, GTK_STATE_NORMAL);
       gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+
+      gdk_window_set_back_pixmap (private->east_shadow, NULL, FALSE);
+      gdk_window_set_back_pixmap (private->south_shadow, NULL, FALSE);
     }
 }
@@ -1943,4 +2339,5 @@
   gint border_width;
   GtkMenu *menu;
+  GtkMenuPrivate *private;
   GtkWidget *child;
   GList *children;
@@ -1950,4 +2347,5 @@
 
   menu = GTK_MENU (widget);
+  private = gtk_menu_get_private (menu);
   
   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
@@ -2020,4 +2418,23 @@
   gdk_window_show (menu->bin_window);
   gdk_window_show (menu->view_window);
+
+  /* Drop shadow */
+
+  attributes.window_type = GDK_WINDOW_TEMP;
+  attributes.override_redirect = TRUE;
+
+  attributes_mask = GDK_WA_NOREDIR | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+  
+  /* East drop shadow */
+  private->east_shadow = gdk_window_new (gtk_widget_get_root_window (widget), 
+                                         &attributes, attributes_mask);
+  gdk_window_set_user_data (private->east_shadow, menu);
+  gdk_window_set_back_pixmap (private->east_shadow, NULL, FALSE);
+  
+  /* South drop shadow */
+  private->south_shadow = gdk_window_new (gtk_widget_get_root_window (widget), 
+                                          &attributes, attributes_mask);
+  gdk_window_set_user_data (private->south_shadow, menu);
+  gdk_window_set_back_pixmap (private->south_shadow, NULL, FALSE);
 }
 
@@ -2082,8 +2499,10 @@
 {
   GtkMenu *menu;
+  GtkMenuPrivate *private;
 
   g_return_if_fail (GTK_IS_MENU (widget));
 
   menu = GTK_MENU (widget);
+  private = gtk_menu_get_private (menu);
 
   menu_grab_transfer_window_destroy (menu);
@@ -2097,4 +2516,13 @@
   menu->bin_window = NULL;
 
+  /* Shadows */
+  gdk_window_set_user_data (private->east_shadow, NULL);
+  gdk_window_destroy (private->east_shadow);
+  private->east_shadow = NULL;
+
+  gdk_window_set_user_data (private->south_shadow, NULL);
+  gdk_window_destroy (private->south_shadow);
+  private->south_shadow = NULL;
+  
   (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
 }
@@ -2251,6 +2679,13 @@
 			      width,
 			      height);
-    }
 
+      if (GTK_WIDGET_MAPPED (widget))
+	{
+	  /* Remap the shadows as the menu size has changed */
+	  shadow_remove_timeout(widget);
+	  shadow_add_timeout(widget);
+	}
+    }
+    
   if (menu_shell->children)
     {
@@ -2349,5 +2784,5 @@
 	    }
 	}
-    }
+    } 
 }
 
@@ -2436,5 +2871,14 @@
 			   arrow_size, arrow_size);
 	}
-    }
+      }
+    else 
+      {
+	GtkMenuPrivate *private = gtk_menu_get_private (menu);
+
+	if (event->window == private->east_shadow)
+	  shadow_paint(widget, &event->area, EAST_SIDE);
+	else if (event->window == private->south_shadow) 
+	  shadow_paint(widget, &event->area, SOUTH_SIDE);
+      }
 }
 

--a8Wt8u1KmwUX3Y2C--