Smooth scrolling



I am attaching a patch that changes gtk+ scrolling to work much like
it does in Internet Explorer. That is the widgets seem to scroll one
pixel at the time. Scrolling this way makes it easier to track the
content of the widgets. You can actually scroll and read the text at
the same time.

The main disadvantage is that it requires much more CPU than before.
The expose method is called much more often, and not all widgets are
prepared for this. When a widget has trouble keeping up with the
screen's refresh rate, the result is jitter.

Another disadvantage is that self-scrolling widgets have to be updated
to make their keyboard navigation take advantage of the smooth
scrolling. Until third-party widgets are updated, they will scroll
smoothly when you are using the scrollbar or the mouse wheel, but
jerky when using the widget's own keyboard navigation.

Also, this patch changes how far a click with the mouse wheel moves a
widget. Currently, a wheel step is half a page, which is far too much,
cf. bug 89259. The new value is 

        pow (adj->page_size, 2.0/3.0), 

ie. the third root of the square of the page size. I got this strange
formula by plotting good values for the scroll amount at different
page sizes. Much to my suprise, the third root of the squared page
size was an almost perfect fit to the points.

The patch introduces this new API:

        void       gtk_adjustment_goto_value    (GtkAdjustment *adjustment,
        					 gdouble        value);
        void	   gtk_adjustment_home		(GtkAdjustment *adjustment);
        void       gtk_adjustment_end           (GtkAdjustment *adjustment);
        void       gtk_adjustment_step_up       (GtkAdjustment *adjustment);
        void       gtk_adjustment_step_down     (GtkAdjustment *adjustment);
        void       gtk_adjustment_wheel_up      (GtkAdjustment *adjustment);
        void       gtk_adjustment_wheel_down    (GtkAdjustment *adjustment);
        void       gtk_adjustment_page_up       (GtkAdjustment *adjustment);
        void       gtk_adjustment_page_down     (GtkAdjustment *adjustment);
 
To do smooth scrolling, it is necessary for the adjustment to keep
track of how far it has already scrolled. This means that clients of
the adjustment can't any longer just call gtk_adjustment_set_value().
Most of these functions are just trivial wrappers around
gtk_adjustment_change_value(). It would be possible to just export
that function instead, but having the wrappers seem cleaner to
me. Also, without the wheel_up/down() wrappers, the pow (size, 2/3)
formula would have to appear both in gtkscrolledwindow.c and in
gtkrange.c.

In fact pow (size, 2/3) does appear in gtkrange.c even with the
wrapper. This is because GtkRange has a feature (round_digits) that
allows it to filter the values it can input into an adjustment. When
this feature is used, smooth scrolling can't be used because there is
no guarantee that the intermediate points are valid according to the
filter.

In addition, this patch makes GtkRange scroll slightly faster when you
press a stepper and hold it. This is just something I think is an
improvement. It doesn't really have much to do with smooth scrolling.

This patch is not final because

        - it should be possible to turn off smooth scrolling. Some
          people don't like the effect, and some may not have a fast
          enough computer for it. There are probably also
          acccessibility reasons to turn it off.

        - the TreeView and the TextView should be updated so that
          their keyboard navigation smooth scrolls.

Søren

Index: gtkadjustment.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkadjustment.c,v
retrieving revision 1.27
diff -u -p -u -r1.27 gtkadjustment.c
--- gtkadjustment.c	9 Oct 2002 22:25:17 -0000	1.27
+++ gtkadjustment.c	17 Jan 2003 20:04:30 -0000
@@ -26,7 +26,7 @@
 
 #include "gtkadjustment.h"
 #include "gtkmarshalers.h"
-
+#include <math.h>
 
 enum {
   CHANGED,
@@ -35,9 +35,19 @@ enum {
 };
 
 
-static void gtk_adjustment_class_init (GtkAdjustmentClass *klass);
-static void gtk_adjustment_init       (GtkAdjustment      *adjustment);
+typedef struct _GtkAdjustmentPrivate GtkAdjustmentPrivate;
+struct _GtkAdjustmentPrivate
+{
+  GTimer *timer;
+  gdouble start_value;
+  gdouble goal_value;
+  guint idle_id;
+};
 
+static void                  gtk_adjustment_private_finalize   (GtkAdjustmentPrivate *private);
+static void                  gtk_adjustment_class_init         (GtkAdjustmentClass   *klass);
+static void                  gtk_adjustment_init               (GtkAdjustment        *adjustment);
+static GtkAdjustmentPrivate *gtk_adjustment_get_private        (GtkAdjustment        *adjustment);
 
 static guint adjustment_signals[LAST_SIGNAL] = { 0 };
 
@@ -75,7 +85,7 @@ gtk_adjustment_class_init (GtkAdjustment
 {
   class->changed = NULL;
   class->value_changed = NULL;
-
+  
   adjustment_signals[CHANGED] =
     g_signal_new ("changed",
 		  G_OBJECT_CLASS_TYPE (class),
@@ -105,6 +115,44 @@ gtk_adjustment_init (GtkAdjustment *adju
   adjustment->page_size = 0.0;
 }
 
+static GtkAdjustmentPrivate *
+gtk_adjustment_get_private (GtkAdjustment *adjustment)
+{
+  GtkAdjustmentPrivate *private;
+  static GQuark private_quark = 0;
+
+  if (!private_quark)
+    private_quark = g_quark_from_static_string ("gtk-adjustment-private");
+
+  private = g_object_get_qdata (G_OBJECT (adjustment), private_quark);
+
+  if (!private)
+    {
+      private = g_new0 (GtkAdjustmentPrivate, 1);
+
+      private->timer = g_timer_new ();
+      private->start_value = 0.0;
+      private->goal_value = 0.0;
+      private->idle_id = 0;
+      
+      g_object_set_qdata_full (G_OBJECT (adjustment), private_quark,
+			       private,
+                               (GDestroyNotify) gtk_adjustment_private_finalize);
+    }
+
+  return private;
+}
+
+static void
+gtk_adjustment_private_finalize (GtkAdjustmentPrivate *private)
+{
+  if (private->idle_id)
+    g_source_remove (private->idle_id);
+
+  g_timer_destroy (private->timer);
+  g_free (private);
+}
+
 GtkObject*
 gtk_adjustment_new (gdouble value,
 		    gdouble lower,
@@ -168,12 +216,27 @@ gtk_adjustment_changed (GtkAdjustment   
   g_signal_emit (adjustment, adjustment_signals[CHANGED], 0);
 }
 
+static void
+gtk_adjustment_value_changed_unchecked (GtkAdjustment *adjustment)
+{
+  g_signal_emit (adjustment, adjustment_signals[VALUE_CHANGED], 0);
+}
+
 void
 gtk_adjustment_value_changed (GtkAdjustment        *adjustment)
 {
+  GtkAdjustmentPrivate *priv;
+  
   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
 
-  g_signal_emit (adjustment, adjustment_signals[VALUE_CHANGED], 0);
+  priv = gtk_adjustment_get_private (adjustment);
+  if (priv->idle_id)
+    {
+      g_source_remove (priv->idle_id);
+      priv->idle_id = 0;
+    }
+  
+  gtk_adjustment_value_changed_unchecked (adjustment);
 }
 
 void
@@ -203,4 +266,160 @@ gtk_adjustment_clamp_page (GtkAdjustment
 
   if (need_emission)
     gtk_adjustment_value_changed (adjustment);
+}
+
+#define UPDATE_TIME 0.1
+
+static gboolean
+adjustment_update_idle (gpointer data)
+{
+  GtkAdjustment *adj = data;
+  GtkAdjustmentPrivate *priv = gtk_adjustment_get_private (adj);
+
+  gdouble new_value;
+  gdouble elapsed = g_timer_elapsed (priv->timer, NULL);
+
+  new_value = priv->start_value +
+    (elapsed / UPDATE_TIME) * (priv->goal_value - priv->start_value);
+
+  /* make sure new_value is betweeen start_value and goal_value */
+  if (!((new_value >= priv->start_value && new_value <= priv->goal_value) ||
+	(new_value <= priv->start_value && new_value >= priv->goal_value)))
+    {
+      new_value = priv->goal_value;
+    }
+
+  if (new_value > adj->upper - adj->page_size)
+    new_value = adj->upper - adj->page_size;
+  
+  if (new_value < adj->lower)
+    new_value = adj->lower;
+
+  if (new_value != adj->value)
+    {
+      adj->value = new_value;
+
+      GDK_THREADS_ENTER();
+      gtk_adjustment_value_changed_unchecked (adj);
+      GDK_THREADS_LEAVE();
+    }
+  
+  if (adj->value == priv->goal_value ||
+      adj->value == adj->lower ||
+      adj->value == adj->upper - adj->page_size)
+    {
+      priv->idle_id = 0;
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+gtk_adjustment_change_value (GtkAdjustment *adjustment,
+			     gdouble        delta)
+{
+  GtkAdjustmentPrivate *priv;
+
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  priv = gtk_adjustment_get_private (adjustment);
+
+  if (delta > -1 && delta < 1)
+    return;
+
+  priv->start_value = adjustment->value;
+  g_timer_reset (priv->timer);
+  
+  if (!priv->idle_id)
+    {
+      priv->idle_id = g_idle_add_full (GDK_PRIORITY_REDRAW + 3, adjustment_update_idle, adjustment, NULL);
+      priv->goal_value = adjustment->value + delta;
+    }
+  else
+    priv->goal_value = priv->goal_value + delta;
+}
+
+void
+gtk_adjustment_step_up (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  gtk_adjustment_change_value (adjustment, -adjustment->step_increment);
+}
+
+void
+gtk_adjustment_step_down (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  gtk_adjustment_change_value (adjustment, adjustment->step_increment);
+}
+
+void
+gtk_adjustment_wheel_up (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+
+  gtk_adjustment_change_value (adjustment,
+			       -pow (adjustment->page_size, 2.0/3.0));
+}
+
+void
+gtk_adjustment_wheel_down (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+
+  gtk_adjustment_change_value (adjustment,
+			       pow (adjustment->page_size, 2.0/3.0));
+}
+
+void
+gtk_adjustment_page_up (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+
+  gtk_adjustment_change_value (adjustment, -adjustment->page_increment);
+}
+
+void
+gtk_adjustment_page_down (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  gtk_adjustment_change_value (adjustment, adjustment->page_increment);
+}
+
+void
+gtk_adjustment_home (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  gtk_adjustment_goto_value (adjustment, 0.0);
+}
+
+void
+gtk_adjustment_end (GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  gtk_adjustment_goto_value (adjustment, adjustment->upper - adjustment->page_size);
+}
+
+void
+gtk_adjustment_goto_value (GtkAdjustment *adjustment,
+			   gdouble        value)
+{
+  GtkAdjustmentPrivate *priv;
+  
+  g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+
+  priv = gtk_adjustment_get_private (adjustment);
+
+  priv->start_value = adjustment->value;
+  priv->goal_value = value;
+  g_timer_reset (priv->timer);
+
+  if (!priv->idle_id)
+    priv->idle_id = g_idle_add_full (GDK_PRIORITY_REDRAW + 3, adjustment_update_idle, adjustment, NULL);
 }
Index: gtkadjustment.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkadjustment.h,v
retrieving revision 1.18
diff -u -p -u -r1.18 gtkadjustment.h
--- gtkadjustment.h	9 Oct 2002 22:25:17 -0000	1.18
+++ gtkadjustment.h	17 Jan 2003 20:04:30 -0000
@@ -75,21 +75,32 @@ struct _GtkAdjustmentClass
 };
 
 
-GType	   gtk_adjustment_get_type		(void) G_GNUC_CONST;
-GtkObject* gtk_adjustment_new			(gdouble	  value,
-						 gdouble	  lower,
-						 gdouble	  upper,
-						 gdouble	  step_increment,
-						 gdouble	  page_increment,
-						 gdouble	  page_size);
-void	   gtk_adjustment_changed		(GtkAdjustment	 *adjustment);
-void	   gtk_adjustment_value_changed		(GtkAdjustment	 *adjustment);
-void	   gtk_adjustment_clamp_page		(GtkAdjustment	 *adjustment,
-						 gdouble	  lower,
-						 gdouble	  upper);
-gdouble	   gtk_adjustment_get_value		(GtkAdjustment   *adjustment);
-void	   gtk_adjustment_set_value		(GtkAdjustment	 *adjustment,
-						 gdouble	  value);
+GType	   gtk_adjustment_get_type	(void) G_GNUC_CONST;
+GtkObject* gtk_adjustment_new           (gdouble        value,
+					 gdouble        lower,
+					 gdouble        upper,
+					 gdouble        step_increment,
+					 gdouble        page_increment,
+					 gdouble        page_size);
+void       gtk_adjustment_changed       (GtkAdjustment *adjustment);
+void       gtk_adjustment_value_changed (GtkAdjustment *adjustment);
+void       gtk_adjustment_clamp_page    (GtkAdjustment *adjustment,
+					 gdouble        lower,
+					 gdouble        upper);
+gdouble    gtk_adjustment_get_value     (GtkAdjustment *adjustment);
+void       gtk_adjustment_set_value     (GtkAdjustment *adjustment,
+					 gdouble        value);
+
+void       gtk_adjustment_goto_value    (GtkAdjustment *adjustment,
+					 gdouble        value);
+void	   gtk_adjustment_home		(GtkAdjustment *adjustment);
+void       gtk_adjustment_end           (GtkAdjustment *adjustment);
+void       gtk_adjustment_step_up       (GtkAdjustment *adjustment);
+void       gtk_adjustment_step_down     (GtkAdjustment *adjustment);
+void       gtk_adjustment_wheel_up      (GtkAdjustment *adjustment);
+void       gtk_adjustment_wheel_down    (GtkAdjustment *adjustment);
+void       gtk_adjustment_page_up       (GtkAdjustment *adjustment);
+void       gtk_adjustment_page_down     (GtkAdjustment *adjustment);
 
 #ifdef __cplusplus
 }
Index: gtkscrolledwindow.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkscrolledwindow.c,v
retrieving revision 1.66
diff -u -p -u -r1.66 gtkscrolledwindow.c
--- gtkscrolledwindow.c	20 Oct 2002 19:08:17 -0000	1.66
+++ gtkscrolledwindow.c	17 Jan 2003 20:04:34 -0000
@@ -793,98 +793,65 @@ gtk_scrolled_window_scroll_child (GtkScr
 				  GtkScrollType      scroll,
 				  gboolean           horizontal)
 {
-  GtkAdjustment *adjustment = NULL;
+  GtkAdjustment *hadj = NULL;
+  GtkAdjustment *vadj = NULL;
+
+  if (scrolled_window->hscrollbar)
+    hadj = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar));
+
+  if (scrolled_window->vscrollbar)
+    vadj = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar));
   
   switch (scroll)
     {
     case GTK_SCROLL_STEP_UP:
-      scroll = GTK_SCROLL_STEP_BACKWARD;
-      horizontal = FALSE;
+    case GTK_SCROLL_STEP_BACKWARD:
+      if (vadj)
+	gtk_adjustment_step_up (vadj);
       break;
     case GTK_SCROLL_STEP_DOWN:
-      scroll = GTK_SCROLL_STEP_FORWARD;
-      horizontal = FALSE;
+    case GTK_SCROLL_STEP_FORWARD:
+      if (vadj)
+	gtk_adjustment_step_down (vadj);
       break;
     case GTK_SCROLL_STEP_LEFT:
-      scroll = GTK_SCROLL_STEP_BACKWARD;
-      horizontal = TRUE;
+      if (hadj)
+	gtk_adjustment_step_up (hadj);
       break;
     case GTK_SCROLL_STEP_RIGHT:
-      scroll = GTK_SCROLL_STEP_FORWARD;
-      horizontal = TRUE;
+      if (hadj)
+	gtk_adjustment_step_down (hadj);
       break;
     case GTK_SCROLL_PAGE_UP:
-      scroll = GTK_SCROLL_PAGE_BACKWARD;
-      horizontal = FALSE;
+    case GTK_SCROLL_PAGE_BACKWARD:
+      if (vadj)
+	gtk_adjustment_page_up (vadj);
       break;
     case GTK_SCROLL_PAGE_DOWN:
-      scroll = GTK_SCROLL_PAGE_FORWARD;
-      horizontal = FALSE;
+    case GTK_SCROLL_PAGE_FORWARD:
+      if (vadj)
+	gtk_adjustment_page_down (vadj);
       break;
     case GTK_SCROLL_PAGE_LEFT:
-      scroll = GTK_SCROLL_STEP_BACKWARD;
-      horizontal = TRUE;
+      if (hadj)
+	gtk_adjustment_page_up (hadj);
       break;
     case GTK_SCROLL_PAGE_RIGHT:
-      scroll = GTK_SCROLL_STEP_FORWARD;
-      horizontal = TRUE;
+      if (hadj)
+	gtk_adjustment_page_down (hadj);
       break;
-    case GTK_SCROLL_STEP_BACKWARD:
-    case GTK_SCROLL_STEP_FORWARD:
-    case GTK_SCROLL_PAGE_BACKWARD:
-    case GTK_SCROLL_PAGE_FORWARD:
     case GTK_SCROLL_START:
+      if (vadj)
+	gtk_adjustment_home (vadj);
+      break;
     case GTK_SCROLL_END:
+      if (vadj)
+	gtk_adjustment_end (vadj);
       break;
     default:
       g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
       return;
     }
-
-  if (horizontal)
-    {
-      if (scrolled_window->hscrollbar)
-	adjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar));
-    }
-  else
-    {
-      if (scrolled_window->vscrollbar)
-	adjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar));
-    }
-
-  if (adjustment)
-    {
-      gdouble value = adjustment->value;
-      
-      switch (scroll)
-	{
-	case GTK_SCROLL_STEP_FORWARD:
-	  value += adjustment->step_increment;
-	  break;
-	case GTK_SCROLL_STEP_BACKWARD:
-	  value -= adjustment->step_increment;
-	  break;
-	case GTK_SCROLL_PAGE_FORWARD:
-	  value += adjustment->page_increment;
-	  break;
-	case GTK_SCROLL_PAGE_BACKWARD:
-	  value -= adjustment->page_increment;
-	  break;
-	case GTK_SCROLL_START:
-	  value = adjustment->lower;
-	  break;
-	case GTK_SCROLL_END:
-	  value = adjustment->upper;
-	  break;
-	default:
-	  g_assert_not_reached ();
-	  break;
-	}
-
-      value = CLAMP (value, adjustment->lower, adjustment->upper - adjustment->page_size);
-      
-      gtk_adjustment_set_value (adjustment, value);
-    }
 }
 
 static void
@@ -1214,16 +1181,17 @@ gtk_scrolled_window_scroll_event (GtkWid
 
   if (range && GTK_WIDGET_VISIBLE (range))
     {
-      GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
-      gdouble new_value;
+      GtkAdjustment *adjustment = GTK_RANGE (range)->adjustment;
 
-      if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT)
-	new_value = adj->value - adj->page_increment / 2;
+      if (event->direction == GDK_SCROLL_UP ||
+	  event->direction == GDK_SCROLL_LEFT)
+	{
+	  gtk_adjustment_wheel_up (adjustment);
+	}
       else
-	new_value = adj->value + adj->page_increment / 2;
-
-      new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
-      gtk_adjustment_set_value (adj, new_value);
+	{
+	  gtk_adjustment_wheel_down (adjustment);
+	}
 
       return TRUE;
     }
Index: gtkrange.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkrange.c,v
retrieving revision 1.88
diff -u -p -u -r1.88 gtkrange.c
--- gtkrange.c	4 Oct 2002 08:02:14 -0000	1.88
+++ gtkrange.c	17 Jan 2003 20:04:40 -0000
@@ -31,9 +31,10 @@
 #include "gtkmarshalers.h"
 #include "gtkrange.h"
 #include "gtkintl.h"
+#include <math.h>
 
-#define SCROLL_INITIAL_DELAY 250  /* must hold button this long before ... */
-#define SCROLL_LATER_DELAY   100  /* ... it starts repeating at this rate  */
+#define SCROLL_INITIAL_DELAY 225  /* must hold button this long before ... */
+#define SCROLL_LATER_DELAY   75   /* ... it starts repeating at this rate  */
 #define UPDATE_DELAY         300  /* Delay for queued update */
 
 enum {
@@ -693,6 +694,12 @@ should_invert (GtkRange *range)
     return range->inverted;
 }
 
+static gboolean
+can_be_smooth (GtkRange *range)
+{
+  return (range->round_digits < 0 && range->update_policy == GTK_UPDATE_CONTINUOUS);
+}
+
 static void
 gtk_range_finalize (GObject *object)
 {
@@ -1365,22 +1372,52 @@ gtk_range_scroll_event (GtkWidget      *
   if (GTK_WIDGET_REALIZED (range))
     {
       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
-      gdouble increment = ((event->direction == GDK_SCROLL_UP ||
-			    event->direction == GDK_SCROLL_LEFT) ? 
-			   -adj->page_increment / 2: 
-			   adj->page_increment / 2);
-      
-      if (range->inverted)
-	increment = -increment;
+      GdkScrollDirection direction = event->direction;
 	  
-      gtk_range_internal_set_value (range, adj->value + increment);
+      if (direction == GDK_SCROLL_UP ||
+	  direction == GDK_SCROLL_LEFT)
+	{
+	  direction = GDK_SCROLL_UP;
+	}
+      else
+	{
+	  direction = GDK_SCROLL_DOWN;
+	}
+      
+      if (should_invert (range))
+	{
+	  if (direction == GDK_SCROLL_UP)
+	    direction = GDK_SCROLL_DOWN;
+	  else
+	    direction = GDK_SCROLL_UP;
+	}
 
-      /* Policy DELAYED makes sense with scroll events,
-       * but DISCONTINUOUS doesn't, so we update immediately
-       * for DISCONTINOUS
-       */
-      if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
-        gtk_range_update_value (range);
+      if (can_be_smooth (range))
+	{
+	  if (direction == GDK_SCROLL_UP)
+	    gtk_adjustment_wheel_up (adj);
+	  else
+	    gtk_adjustment_wheel_down (adj);
+
+	  range->need_recalc = TRUE;
+	  gtk_widget_queue_draw (GTK_WIDGET (range));
+	}
+      else
+	{
+	  gdouble increment = pow (adj->page_size, 2.0/3.0);
+
+	  if (direction == GDK_SCROLL_UP)
+	    increment *= -1;
+
+	  gtk_range_internal_set_value (range, adj->value + increment);
+
+	  /* Policy DELAYED makes sense with scroll events,
+	   * but DISCONTINUOUS doesn't, so we update immediately
+	   * for DISCONTINUOUS
+	   */
+	  if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
+	    gtk_range_update_value (range);
+	}
     }
 
   return TRUE;
@@ -1496,40 +1533,81 @@ gtk_range_style_set (GtkWidget *widget,
 static void
 step_back (GtkRange *range)
 {
-  gdouble newval;
-  
-  newval = range->adjustment->value - range->adjustment->step_increment;
-  gtk_range_internal_set_value (range, newval);
+  GtkAdjustment *adj = range->adjustment;
+
+  if (can_be_smooth (range))
+    {
+      gtk_adjustment_step_up (adj);
+      range->need_recalc = TRUE;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+    }
+  else
+    {
+      gdouble newval;
+
+      newval = adj->value - adj->step_increment;
+      gtk_range_internal_set_value (range, newval);
+    }
 }
 
 static void
 step_forward (GtkRange *range)
 {
-  gdouble newval;
+  GtkAdjustment *adj = range->adjustment;
 
-  newval = range->adjustment->value + range->adjustment->step_increment;
+  if (can_be_smooth (range))
+    {
+      gtk_adjustment_step_down (adj);
+      range->need_recalc = TRUE;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+    }
+  else
+    {
+      gdouble newval;
 
-  gtk_range_internal_set_value (range, newval);
+      newval = adj->value + adj->step_increment;
+      gtk_range_internal_set_value (range, newval);
+    }
 }
 
-
 static void
 page_back (GtkRange *range)
 {
-  gdouble newval;
+  GtkAdjustment *adj = range->adjustment;
+
+  if (can_be_smooth (range))
+    {
+      gtk_adjustment_page_up (adj);
+      range->need_recalc = TRUE;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+    }
+  else
+    {
+      gdouble newval;
 
-  newval = range->adjustment->value - range->adjustment->page_increment;
-  gtk_range_internal_set_value (range, newval);
+      newval = adj->value - adj->page_increment;
+      gtk_range_internal_set_value (range, newval);
+    }
 }
 
 static void
 page_forward (GtkRange *range)
 {
-  gdouble newval;
+  GtkAdjustment *adj = range->adjustment;
 
-  newval = range->adjustment->value + range->adjustment->page_increment;
+  if (can_be_smooth (range))
+    {
+      gtk_adjustment_page_down (adj);
+      range->need_recalc = TRUE;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+    }
+  else
+    {
+      gdouble newval;
 
-  gtk_range_internal_set_value (range, newval);
+      newval = adj->value - adj->page_increment;
+      gtk_range_internal_set_value (range, newval);
+    }
 }
 
 static void
@@ -1611,13 +1689,27 @@ gtk_range_scroll (GtkRange     *range,
       break;
 
     case GTK_SCROLL_START:
-      gtk_range_internal_set_value (range,
-                                    range->adjustment->lower);
+      if (can_be_smooth (range))
+	{
+	  gtk_adjustment_home (range->adjustment);
+	  range->need_recalc = TRUE;
+	  gtk_widget_queue_draw (GTK_WIDGET (range));
+	}
+      else
+	gtk_range_internal_set_value (range,
+				      range->adjustment->lower);
       break;
 
     case GTK_SCROLL_END:
-      gtk_range_internal_set_value (range,
-                                    range->adjustment->upper - range->adjustment->page_size);
+      if (can_be_smooth (range))
+	{
+	  gtk_adjustment_end (range->adjustment);
+	  range->need_recalc = TRUE;
+	  gtk_widget_queue_draw (GTK_WIDGET (range));
+	}
+      else
+	gtk_range_internal_set_value (range,
+				      range->adjustment->upper - range->adjustment->page_size);
       break;
 
     case GTK_SCROLL_JUMP:


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