Index: gtkpathbar.c
===================================================================
--- gtkpathbar.c	(revision 18042)
+++ gtkpathbar.c	(working copy)
@@ -34,6 +34,7 @@
 #include "gtkmain.h"
 #include "gtkmarshalers.h"
 #include "gtkalias.h"
+#include "gtktimeline.h"
 
 enum {
   PATH_CLICKED,
@@ -50,6 +51,7 @@ typedef enum {
 #define BUTTON_DATA(x) ((ButtonData *)(x))
 
 #define SCROLL_DELAY_FACTOR 5
+#define PIXELS_PER_FRAME 8
 
 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
 
@@ -93,10 +95,10 @@ static void gtk_path_bar_forall         
 						   gboolean          include_internals,
 						   GtkCallback       callback,
 						   gpointer          callback_data);
-static void gtk_path_bar_scroll_up                (GtkWidget        *button,
-						   GtkPathBar       *path_bar);
-static void gtk_path_bar_scroll_down              (GtkWidget        *button,
-						   GtkPathBar       *path_bar);
+static void gtk_path_bar_scroll_up                (GtkPathBar       *path_bar);
+static void gtk_path_bar_scroll_down              (GtkPathBar       *path_bar);
+static void gtk_path_bar_scroll_up_animation      (GtkPathBar       *path_bar);
+static void gtk_path_bar_scroll_down_animation    (GtkPathBar       *path_bar);
 static void gtk_path_bar_stop_scrolling           (GtkPathBar       *path_bar);
 static gboolean gtk_path_bar_slider_up_defocus    (GtkWidget        *widget,
 						   GdkEventButton   *event,
@@ -122,14 +124,24 @@ static void gtk_path_bar_check_icon_them
 static void gtk_path_bar_update_button_appearance (GtkPathBar       *path_bar,
 						   ButtonData       *button_data,
 						   gboolean          current_dir);
+static gboolean gtk_path_bar_scroll_timeout       (GtkPathBar       *path_bar);
+
 
 static void
 on_slider_unmap (GtkWidget  *widget,
 		 GtkPathBar *path_bar)
 {
-  if (path_bar->timer &&
-      ((widget == path_bar->up_slider_button && path_bar->scrolling_up) ||
-       (widget == path_bar->down_slider_button && path_bar->scrolling_down)))
+  if (widget == path_bar->up_slider_button && path_bar->scrolling_up)
+    path_bar->scrolling_up = FALSE;
+  else if (widget == path_bar->down_slider_button && path_bar->scrolling_down)
+    path_bar->scrolling_down = FALSE;
+  else
+    {
+      /* nothing to do here */
+      return;
+    }
+
+  if (path_bar->timer)
     gtk_path_bar_stop_scrolling (path_bar);
 }
 
@@ -174,9 +186,7 @@ gtk_path_bar_init (GtkPathBar *path_bar)
   path_bar->up_slider_button = get_slider_button (path_bar, GTK_ARROW_LEFT);
   path_bar->down_slider_button = get_slider_button (path_bar, GTK_ARROW_RIGHT);
   path_bar->icon_size = FALLBACK_ICON_SIZE;
-  
-  g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
-  g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
+  path_bar->first_button_proportion = 1.0;
 
   g_signal_connect (path_bar->up_slider_button, "focus_out_event", G_CALLBACK (gtk_path_bar_slider_up_defocus), path_bar);
   g_signal_connect (path_bar->down_slider_button, "focus_out_event", G_CALLBACK (gtk_path_bar_slider_down_defocus), path_bar);
@@ -258,6 +268,9 @@ gtk_path_bar_finalize (GObject *object)
   if (path_bar->file_system)
     g_object_unref (path_bar->file_system);
 
+  if (path_bar->timeline)
+    g_object_unref (path_bar->timeline);
+
   G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
 }
 
@@ -345,8 +358,11 @@ gtk_path_bar_update_slider_buttons (GtkP
       button = BUTTON_DATA (path_bar->button_list->data)->button;
       if (gtk_widget_get_child_visible (button))
 	{
-	  gtk_path_bar_stop_scrolling (path_bar);
+	  if (path_bar->scrolling_down)
+	    gtk_path_bar_stop_scrolling (path_bar);
+
 	  gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
+	  path_bar->scrolling_down = FALSE;
 	}
       else
 	gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
@@ -354,8 +370,11 @@ gtk_path_bar_update_slider_buttons (GtkP
       button = BUTTON_DATA (g_list_last (path_bar->button_list)->data)->button;
       if (gtk_widget_get_child_visible (button))
 	{
-	  gtk_path_bar_stop_scrolling (path_bar);
+	  if (path_bar->scrolling_up)
+	    gtk_path_bar_stop_scrolling (path_bar);
+
 	  gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
+	  path_bar->scrolling_up = FALSE;
 	}
       else
 	gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
@@ -376,8 +395,8 @@ static void
 gtk_path_bar_size_allocate (GtkWidget     *widget,
 			    GtkAllocation *allocation)
 {
-  GtkWidget *child;
   GtkPathBar *path_bar = GTK_PATH_BAR (widget);
+  GtkWidget *child;
   GtkTextDirection direction;
   GtkAllocation child_allocation;
   GList *list, *first_button;
@@ -418,7 +437,18 @@ gtk_path_bar_size_allocate (GtkWidget   
       if (path_bar->fake_root)
 	first_button = path_bar->fake_root;
       else
-	first_button = g_list_last (path_bar->button_list);
+	{
+	  if (path_bar->timeline)
+	    {
+	      /* This will happen if it's moving from the fake root to
+	       * the root directory, we want the animation to be smooth
+	       */
+	      path_bar->animation_destination = g_list_last (path_bar->button_list);
+	      first_button = path_bar->first_scrolled_button;
+	    }
+	  else
+	    first_button = g_list_last (path_bar->button_list);
+	}
     }
   else
     {
@@ -475,7 +505,7 @@ gtk_path_bar_size_allocate (GtkWidget   
 
   /* Now, we allocate space to the buttons */
   child_allocation.y = allocation->y + border_width;
-  child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
+  child_allocation.height = MAX (1, allocation->height - border_width * 2);
 
   if (direction == GTK_TEXT_DIR_RTL)
     {
@@ -499,10 +529,15 @@ gtk_path_bar_size_allocate (GtkWidget   
   for (list = first_button; list; list = list->prev)
     {
       child = BUTTON_DATA (list->data)->button;
-
       child_allocation.width = child->requisition.width;
+
       if (direction == GTK_TEXT_DIR_RTL)
-	child_allocation.x -= child_allocation.width;
+	{
+	  if (list == first_button)
+	    child_allocation.x -= path_bar->first_button_proportion * child_allocation.width;
+	  else
+	    child_allocation.x -= child_allocation.width;
+	}
 
       /* Check to see if we've don't have any more space to allocate buttons */
       if (need_sliders && direction == GTK_TEXT_DIR_RTL)
@@ -517,7 +552,10 @@ gtk_path_bar_size_allocate (GtkWidget   
 	    break;
 	}
 
-      gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, TRUE);
+      if (list != first_button ||
+	  path_bar->first_button_proportion == 1.0)
+	gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, TRUE);
+
       gtk_widget_size_allocate (child, &child_allocation);
 
       if (direction == GTK_TEXT_DIR_RTL)
@@ -530,7 +568,11 @@ gtk_path_bar_size_allocate (GtkWidget   
 	{
 	  down_slider_offset = child_allocation.x - widget->allocation.x;
 	  down_slider_offset = allocation->width - border_width - path_bar->slider_width;
-	  child_allocation.x += child_allocation.width + path_bar->spacing;
+
+	  if (list == first_button)
+	    child_allocation.x += path_bar->first_button_proportion * child_allocation.width + path_bar->spacing;
+	  else
+	    child_allocation.x += child_allocation.width + path_bar->spacing;
 	}
     }
   /* Now we go hide all the widgets that don't fit */
@@ -544,6 +586,9 @@ gtk_path_bar_size_allocate (GtkWidget   
       gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE);
     }
 
+  if (path_bar->first_button_proportion < 1.0)
+    gtk_widget_set_child_visible (BUTTON_DATA (first_button->data)->button, FALSE);
+
   if (need_sliders || path_bar->fake_root)
     {
       child_allocation.width = path_bar->slider_width;
@@ -555,7 +600,7 @@ gtk_path_bar_size_allocate (GtkWidget   
     }
   else
     gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
-      
+
   if (need_sliders)
     {
       child_allocation.width = path_bar->slider_width;
@@ -679,7 +724,80 @@ gtk_path_bar_forall (GtkContainer *conta
 }
 
 static void
-gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
+gtk_path_bar_scroll_frame (GtkTimeline *timeline,
+			   gdouble      progress,
+			   GtkPathBar  *path_bar)
+{
+  path_bar->first_button_proportion = progress;
+  gtk_widget_queue_resize (GTK_WIDGET (path_bar));
+}
+
+static gboolean
+gtk_path_bar_scroll_down_idle (GtkPathBar *path_bar)
+{
+  path_bar->first_button_proportion = 1.0;
+
+  if (path_bar->first_scrolled_button != path_bar->animation_destination)
+    {
+      path_bar->first_scrolled_button = path_bar->first_scrolled_button->prev;
+
+      if (path_bar->first_scrolled_button != path_bar->animation_destination)
+	{
+	  gtk_path_bar_scroll_down_animation (path_bar);
+	  return FALSE;
+	}
+    }
+
+  if (path_bar->scrolling_down)
+    gtk_path_bar_scroll_down (path_bar);
+
+  if (path_bar->first_scrolled_button == path_bar->animation_destination)
+    path_bar->ignore_click = FALSE;
+
+  return FALSE;
+}
+
+static void
+gtk_path_bar_scroll_down_finished (GtkTimeline *timeline,
+				   GtkPathBar  *path_bar)
+{
+  g_object_unref (path_bar->timeline);
+  path_bar->timeline = NULL;
+
+  /* decide whether to continue the animation or not
+   * after the last queue_resize has been processed
+   */
+  g_idle_add_full (GTK_PRIORITY_RESIZE + 5,
+		   (GSourceFunc) gtk_path_bar_scroll_down_idle,
+		   path_bar, NULL);
+}
+
+static void
+gtk_path_bar_scroll_down_animation (GtkPathBar *path_bar)
+{
+  GdkScreen *screen;
+  gint width;
+
+  g_assert (!path_bar->timeline);
+
+  screen = gtk_widget_get_screen (GTK_WIDGET (path_bar));
+  width = BUTTON_DATA (path_bar->first_scrolled_button->data)->button->requisition.width;
+  path_bar->first_button_proportion = 1.0;
+
+  path_bar->timeline = gtk_timeline_new_for_screen (width / PIXELS_PER_FRAME, screen);
+  gtk_timeline_set_direction (path_bar->timeline, GTK_TIMELINE_DIRECTION_BACKWARD);
+  gtk_timeline_rewind (path_bar->timeline);
+
+  g_signal_connect (path_bar->timeline, "frame",
+		    G_CALLBACK (gtk_path_bar_scroll_frame), path_bar);
+  g_signal_connect (path_bar->timeline, "finished",
+		    G_CALLBACK (gtk_path_bar_scroll_down_finished), path_bar);
+
+  gtk_timeline_start (path_bar->timeline);
+}
+
+static void
+gtk_path_bar_scroll_down (GtkPathBar *path_bar)
 {
   GList *list;
   GList *down_button = NULL;
@@ -687,19 +805,14 @@ gtk_path_bar_scroll_down (GtkWidget *but
   gint space_available;
   gint space_needed;
   gint border_width;
-  GtkTextDirection direction;
+  GtkSettings *settings;
+  gboolean enable_animations;
 
-  if (path_bar->ignore_click)
-    {
-      path_bar->ignore_click = FALSE;
-      return;   
-    }
+  border_width = GTK_CONTAINER (path_bar)->border_width;
 
-  gtk_widget_queue_resize (GTK_WIDGET (path_bar));
+  settings = gtk_widget_get_settings (GTK_WIDGET (path_bar));
+  g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
 
-  border_width = GTK_CONTAINER (path_bar)->border_width;
-  direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
-  
   /* We find the button at the 'down' end that we have to make
    * visible */
   for (list = path_bar->button_list; list; list = list->next)
@@ -710,52 +823,106 @@ gtk_path_bar_scroll_down (GtkWidget *but
 	  break;
 	}
     }
-  
-  /* Find the last visible button on the 'up' end
+
+  up_button = down_button;
+  space_available = GTK_WIDGET (path_bar)->allocation.width - 2 * border_width;
+
+  /* We check if down_button might be NULL in cases where the pathbar's horizontal size is smaller
+   * than the folder button and it doesn't get displayed.
    */
-  for (list = g_list_last (path_bar->button_list); list; list = list->prev)
+  space_needed = down_button ? BUTTON_DATA (down_button->data)->button->allocation.width : path_bar->spacing;
+
+  if (gtk_widget_get_child_visible (path_bar->down_slider_button))
+    space_available -= path_bar->slider_width + path_bar->spacing;
+
+  if (gtk_widget_get_child_visible (path_bar->up_slider_button))
+    space_available -= path_bar->slider_width + path_bar->spacing;
+
+  /* run up the list from down_button, see how many fit in the widget */
+  while (up_button->next && space_available >= space_needed)
     {
-      if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
-	{
-	  up_button = list;
-	  break;
-	}
+      space_needed += BUTTON_DATA (up_button->next->data)->button->allocation.width + path_bar->spacing;
+
+      if (space_available >= space_needed)
+	up_button = up_button->next;
     }
 
-  /* We check if down_button might be NULL in cases where the pathbar's horizontal size is smaller
-   * than the button and it doesn't get displayed. e.g., on Windows it might be "My Documents and Settings"
-   */
-  space_needed = down_button ? BUTTON_DATA (down_button->data)->button->allocation.width : 0 + path_bar->spacing;
-  if (direction == GTK_TEXT_DIR_RTL)
-    space_available = path_bar->down_slider_button->allocation.x - GTK_WIDGET (path_bar)->allocation.x;
-  else
-    space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
-      (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
+  if (enable_animations)
+    {
+      path_bar->animation_destination = up_button;
 
-  /* We have space_available extra space that's not being used.  We
-   * need space_needed space to make the button fit.  So we walk down
-   * from the end, removing buttons until we get all the space we
-   * need. */
-  while (space_available < space_needed)
+      if (path_bar->first_scrolled_button != path_bar->animation_destination)
+	gtk_path_bar_scroll_down_animation (path_bar);
+    }
+  else
     {
-      space_available += BUTTON_DATA (up_button->data)->button->allocation.width + path_bar->spacing;
-      up_button = up_button->prev;
       path_bar->first_scrolled_button = up_button;
+      gtk_widget_queue_resize (GTK_WIDGET (path_bar));
     }
 }
 
-static void
-gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
+static gboolean
+gtk_path_bar_scroll_up_idle (GtkPathBar *path_bar)
 {
-  GList *list;
+  g_object_unref (path_bar->timeline);
+  path_bar->timeline = NULL;
 
-  if (path_bar->ignore_click)
+  if (path_bar->first_scrolled_button != path_bar->animation_destination)
     {
-      path_bar->ignore_click = FALSE;
-      return;   
+      path_bar->first_scrolled_button = path_bar->first_scrolled_button->next;
+      gtk_path_bar_scroll_up_animation (path_bar);
     }
+  else if (path_bar->scrolling_up)
+    gtk_path_bar_scroll_up (path_bar);
+  else
+    path_bar->ignore_click = FALSE;
 
-  gtk_widget_queue_resize (GTK_WIDGET (path_bar));
+  return FALSE;
+}
+
+static void
+gtk_path_bar_scroll_up_finished (GtkTimeline *timeline,
+				 GtkPathBar  *path_bar)
+{
+  /* decide whether to continue the animation or not
+   * after the last queue_resize has been processed
+   */
+  g_idle_add_full (GTK_PRIORITY_RESIZE + 5,
+		   (GSourceFunc) gtk_path_bar_scroll_up_idle,
+		   path_bar, NULL);
+}
+
+static void
+gtk_path_bar_scroll_up_animation (GtkPathBar *path_bar)
+{
+  GdkScreen *screen;
+  gint width;
+
+  g_assert (!path_bar->timeline);
+
+  width = BUTTON_DATA (path_bar->first_scrolled_button->data)->button->requisition.width;
+  path_bar->first_button_proportion = 0.0;
+
+  screen = gtk_widget_get_screen (GTK_WIDGET (path_bar));
+  path_bar->timeline = gtk_timeline_new_for_screen (width / PIXELS_PER_FRAME, screen);
+
+  g_signal_connect (path_bar->timeline, "frame",
+		    G_CALLBACK (gtk_path_bar_scroll_frame), path_bar);
+  g_signal_connect (path_bar->timeline, "finished",
+		    G_CALLBACK (gtk_path_bar_scroll_up_finished), path_bar);
+
+  gtk_timeline_start (path_bar->timeline);
+}
+
+static void
+gtk_path_bar_scroll_up (GtkPathBar *path_bar)
+{
+  GList *list;
+  GtkSettings *settings;
+  gboolean enable_animations;
+
+  settings = gtk_widget_get_settings (GTK_WIDGET (path_bar));
+  g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
 
   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
     {
@@ -763,10 +930,16 @@ gtk_path_bar_scroll_up (GtkWidget *butto
 	{
 	  if (list->prev == path_bar->fake_root)
 	    path_bar->fake_root = NULL;
-	  path_bar->first_scrolled_button = list;
-	  return;
+
+	  path_bar->animation_destination = path_bar->first_scrolled_button = list;
+	  break;
 	}
     }
+
+  if (enable_animations)
+    gtk_path_bar_scroll_up_animation (path_bar);
+  else
+    gtk_widget_queue_resize (GTK_WIDGET (path_bar));
 }
 
 static gboolean
@@ -777,9 +950,9 @@ gtk_path_bar_scroll_timeout (GtkPathBar 
   if (path_bar->timer)
     {
       if (path_bar->scrolling_up)
-	gtk_path_bar_scroll_up (path_bar->up_slider_button, path_bar);
+	gtk_path_bar_scroll_up (path_bar);
       else if (path_bar->scrolling_down)
-	gtk_path_bar_scroll_down (path_bar->down_slider_button, path_bar);
+	gtk_path_bar_scroll_down (path_bar);
 
       if (path_bar->need_timer) 
 	{
@@ -873,35 +1046,46 @@ gtk_path_bar_slider_button_press (GtkWid
 				  GdkEventButton *event,
 				  GtkPathBar     *path_bar)
 {
+  GtkSettings *settings;
+  gboolean enable_animations;
+
   if (event->type != GDK_BUTTON_PRESS || event->button != 1)
     return FALSE;
 
-  path_bar->ignore_click = FALSE;
+  settings = gtk_widget_get_settings (widget);
+  g_object_get (settings, "gtk-enable-animations", &enable_animations, NULL);
+
+  if (enable_animations)
+    {
+      if (path_bar->ignore_click)
+	return FALSE;
+
+      path_bar->ignore_click = TRUE;
+    }
 
   if (widget == path_bar->up_slider_button)
     {
       path_bar->scrolling_down = FALSE;
       path_bar->scrolling_up = TRUE;
-      gtk_path_bar_scroll_up (path_bar->up_slider_button, path_bar);
+      gtk_path_bar_scroll_up (path_bar);
     }
   else if (widget == path_bar->down_slider_button)
     {
       path_bar->scrolling_up = FALSE;
       path_bar->scrolling_down = TRUE;
-      gtk_path_bar_scroll_down (path_bar->down_slider_button, path_bar);
+      gtk_path_bar_scroll_down (path_bar);
     }
 
-  if (!path_bar->timer)
+  if (!enable_animations && !path_bar->timer)
     {
-      GtkSettings *settings = gtk_widget_get_settings (widget);
-      guint        timeout;
+      guint timeout;
 
       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
 
       path_bar->need_timer = TRUE;
       path_bar->timer = gdk_threads_add_timeout (timeout,
-				       (GSourceFunc)gtk_path_bar_scroll_timeout,
-				       path_bar);
+						 (GSourceFunc)gtk_path_bar_scroll_timeout,
+						 path_bar);
     }
 
   return FALSE;
@@ -915,7 +1099,11 @@ gtk_path_bar_slider_button_release (GtkW
   if (event->type != GDK_BUTTON_RELEASE)
     return FALSE;
 
-  path_bar->ignore_click = TRUE;
+  if (widget == path_bar->up_slider_button)
+    path_bar->scrolling_up = FALSE;
+  else if (widget == path_bar->down_slider_button)
+    path_bar->scrolling_down = FALSE;
+
   gtk_path_bar_stop_scrolling (path_bar);
 
   return FALSE;
Index: gtkpathbar.h
===================================================================
--- gtkpathbar.h	(revision 18042)
+++ gtkpathbar.h	(working copy)
@@ -22,6 +22,7 @@
 
 #include "gtkcontainer.h"
 #include "gtkfilesystem.h"
+#include "gtktimeline.h"
 
 G_BEGIN_DECLS
 
@@ -45,6 +46,9 @@ struct _GtkPathBar
   GtkFilePath *home_path;
   GtkFilePath *desktop_path;
 
+  GtkTimeline *timeline;
+  gdouble first_button_proportion;
+
   GtkFileSystemHandle *set_path_handle;
 
   GdkPixbuf *root_icon;
@@ -54,6 +58,7 @@ struct _GtkPathBar
   GList *button_list;
   GList *first_scrolled_button;
   GList *fake_root;
+  GList *animation_destination;
   GtkWidget *up_slider_button;
   GtkWidget *down_slider_button;
   guint settings_signal_id;
