[gtk+/wip/overlay-scrollbar: 3/5] Add an initial implementation of overlay scrollbars



commit 48a0e8d6cde240387d2823b0e05af864e73d087b
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Oct 8 23:34:32 2014 -0400

    Add an initial implementation of overlay scrollbars
    
    This commit adds a mode to GtkScrolledWindow in which it puts
    translucent scrollbars over the content, instead of allocating
    room for the scrollbars. We use traditional scrollbars if we
    find a mouse or if overlay scrolling has explicitly turned off.
    For test purposes, GTK_TEST_TOUCHSCREEN can be used to get
    overlay scrolling even in the presence of a mouse.

 gtk/gtkscrolledwindow.c |  609 +++++++++++++++++++++++++++++++++++++++++++++--
 gtk/gtkscrolledwindow.h |   18 +-
 2 files changed, 598 insertions(+), 29 deletions(-)
---
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index b7d52c4..b584d6f 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -136,11 +136,37 @@
 /* Animated scrolling */
 #define ANIMATION_DURATION 200
 
+/* Overlay scrollbars */
+#define INDICATOR_FADE_OUT_DELAY 1000
+#define INDICATOR_FADE_OUT_DURATION 1000
+#define INDICATOR_FADE_OUT_TIME 500
+
+typedef struct
+{
+  GtkWidget *scrollbar;
+  GdkWindow *window;
+  gboolean   dragging;
+  gboolean   over;
+  gboolean   enabled;
+  gint64     last_scroll_time;
+  guint      conceil_timer;
+
+  gdouble    current_pos;
+  gdouble    source_pos;
+  gdouble    target_pos;
+  gint64     start_time;
+  gint64     end_time;
+  guint      tick_id;
+} Indicator;
+
 struct _GtkScrolledWindowPrivate
 {
   GtkWidget     *hscrollbar;
   GtkWidget     *vscrollbar;
 
+  Indicator hindicator;
+  Indicator vindicator;
+
   GtkCornerType  window_placement;
   guint16  shadow_type;
 
@@ -149,6 +175,8 @@ struct _GtkScrolledWindowPrivate
   guint    hscrollbar_visible     : 1;
   guint    vscrollbar_visible     : 1;
   guint    focus_out              : 1; /* Flag used by ::move-focus-out implementation */
+  guint    overlay_scrolling      : 1;
+  guint    touch_mode             : 1;
 
   gint     min_content_width;
   gint     min_content_height;
@@ -198,7 +226,8 @@ enum {
   PROP_SHADOW_TYPE,
   PROP_MIN_CONTENT_WIDTH,
   PROP_MIN_CONTENT_HEIGHT,
-  PROP_KINETIC_SCROLLING
+  PROP_KINETIC_SCROLLING,
+  PROP_OVERLAY_SCROLLING
 };
 
 /* Signals */
@@ -266,6 +295,8 @@ static void  gtk_scrolled_window_get_preferred_width_for_height  (GtkWidget
 
 static void  gtk_scrolled_window_map                   (GtkWidget           *widget);
 static void  gtk_scrolled_window_unmap                 (GtkWidget           *widget);
+static void  gtk_scrolled_window_realize               (GtkWidget           *widget);
+static void  gtk_scrolled_window_unrealize             (GtkWidget           *widget);
 
 static void  gtk_scrolled_window_grab_notify           (GtkWidget           *widget,
                                                         gboolean             was_grabbed);
@@ -285,6 +316,13 @@ static gboolean _gtk_scrolled_window_get_overshoot (GtkScrolledWindow *scrolled_
 static void     gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window);
 static gint     _gtk_scrolled_window_get_scrollbar_spacing (GtkScrolledWindow *scrolled_window);
 
+static void     remove_indicator     (GtkScrolledWindow *sw,
+                                      Indicator         *indicator);
+static void     indicator_stop_fade  (Indicator         *indicator);
+static gboolean maybe_hide_indicator (gpointer data);
+
+
+
 static guint signals[LAST_SIGNAL] = {0};
 
 G_DEFINE_TYPE_WITH_PRIVATE (GtkScrolledWindow, gtk_scrolled_window, GTK_TYPE_BIN)
@@ -347,6 +385,8 @@ gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
   widget_class->map = gtk_scrolled_window_map;
   widget_class->unmap = gtk_scrolled_window_unmap;
   widget_class->grab_notify = gtk_scrolled_window_grab_notify;
+  widget_class->realize = gtk_scrolled_window_realize;
+  widget_class->unrealize = gtk_scrolled_window_unrealize;
 
   container_class->add = gtk_scrolled_window_add;
   container_class->remove = gtk_scrolled_window_remove;
@@ -498,6 +538,14 @@ gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
                                                          TRUE,
                                                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
 
+  g_object_class_install_property (gobject_class,
+                                   PROP_OVERLAY_SCROLLING,
+                                   g_param_spec_boolean ("overlay-scrolling",
+                                                         P_("Overlay Scrolling"),
+                                                         P_("Overlay scrolling mode"),
+                                                         TRUE,
+                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
+
   /**
    * GtkScrolledWindow::scroll-child:
    * @scrolled_window: a #GtkScrolledWindow
@@ -830,6 +878,8 @@ gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
   priv->min_content_width = -1;
   priv->min_content_height = -1;
 
+  priv->overlay_scrolling = TRUE;
+
   priv->drag_gesture = gtk_gesture_drag_new (widget);
   gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->drag_gesture), TRUE);
   g_signal_connect_swapped (priv->drag_gesture, "drag-begin",
@@ -1433,6 +1483,9 @@ gtk_scrolled_window_destroy (GtkWidget *widget)
   GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
   GtkScrolledWindowPrivate *priv = scrolled_window->priv;
 
+  remove_indicator (scrolled_window, &priv->hindicator);
+  remove_indicator (scrolled_window, &priv->vindicator);
+
   if (priv->hscrollbar)
     {
       g_signal_handlers_disconnect_by_func (gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar)),
@@ -1519,6 +1572,10 @@ gtk_scrolled_window_set_property (GObject      *object,
       gtk_scrolled_window_set_kinetic_scrolling (scrolled_window,
                                                  g_value_get_boolean (value));
       break;
+    case PROP_OVERLAY_SCROLLING:
+      gtk_scrolled_window_set_overlay_scrolling (scrolled_window,
+                                                 g_value_get_boolean (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1568,6 +1625,9 @@ gtk_scrolled_window_get_property (GObject    *object,
     case PROP_KINETIC_SCROLLING:
       g_value_set_boolean (value, priv->kinetic_scrolling);
       break;
+    case PROP_OVERLAY_SCROLLING:
+      g_value_set_boolean (value, priv->overlay_scrolling);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1754,6 +1814,14 @@ gtk_scrolled_window_draw (GtkWidget *widget,
 
   GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->draw (widget, cr);
 
+  if (priv->hindicator.enabled &&
+      gtk_cairo_should_draw_window (cr, priv->hindicator.window))
+    gtk_container_propagate_draw (GTK_CONTAINER (scrolled_window), priv->hscrollbar, cr);
+
+  if (priv->vindicator.enabled &&
+      gtk_cairo_should_draw_window (cr, priv->vindicator.window))
+    gtk_container_propagate_draw (GTK_CONTAINER (scrolled_window), priv->vscrollbar, cr);
+
   gtk_scrolled_window_draw_overshoot (scrolled_window, cr);
 
   return FALSE;
@@ -1956,7 +2024,7 @@ gtk_scrolled_window_relative_allocation (GtkWidget     *widget,
       gtk_style_context_restore (context);
     }
 
-  if (priv->vscrollbar_visible)
+  if (priv->vscrollbar_visible && !priv->touch_mode)
     {
       gboolean is_rtl;
 
@@ -1972,7 +2040,8 @@ gtk_scrolled_window_relative_allocation (GtkWidget     *widget,
 
       allocation->width = MAX (1, allocation->width - (sb_width + sb_spacing));
     }
-  if (priv->hscrollbar_visible)
+
+  if (priv->hscrollbar_visible && !priv->touch_mode)
     {
 
       if (priv->window_placement == GTK_CORNER_BOTTOM_LEFT ||
@@ -2149,25 +2218,25 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
                  /* Does the content width fit the allocation with minus a possible scrollbar ? */
                  priv->hscrollbar_visible = 
                    child_scroll_width > allocation->width - 
-                   (priv->vscrollbar_visible ? sb_width + sb_spacing : 0);
+                   (priv->vscrollbar_visible && !priv->touch_mode ? sb_width + sb_spacing : 0);
                  
                  /* Now that we've guessed the hscrollbar, does the content height fit
                   * the possible new allocation height ? */
                  priv->vscrollbar_visible = 
                    child_scroll_height > allocation->height - 
-                   (priv->hscrollbar_visible ? sb_height + sb_spacing : 0);
+                   (priv->hscrollbar_visible && !priv->touch_mode ? sb_height + sb_spacing : 0);
                  
                  /* Now that we've guessed the vscrollbar, does the content width fit
                   * the possible new allocation width ? */
                  priv->hscrollbar_visible = 
                    child_scroll_width > allocation->width - 
-                   (priv->vscrollbar_visible ? sb_width + sb_spacing : 0);
+                   (priv->vscrollbar_visible && !priv->touch_mode ? sb_width + sb_spacing : 0);
                }
              else /* priv->hscrollbar_policy != GTK_POLICY_AUTOMATIC */
                {
                  priv->hscrollbar_visible = policy_may_be_visible (priv->hscrollbar_policy);
                  priv->vscrollbar_visible = child_scroll_height > allocation->height - 
-                   (priv->hscrollbar_visible ? sb_height + sb_spacing : 0);
+                   (priv->hscrollbar_visible && !priv->touch_mode ? sb_height + sb_spacing : 0);
                }
            }
          else /* priv->vscrollbar_policy != GTK_POLICY_AUTOMATIC */
@@ -2177,7 +2246,7 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
              if (priv->hscrollbar_policy == GTK_POLICY_AUTOMATIC)
                priv->hscrollbar_visible = 
                  child_scroll_width > allocation->width - 
-                 (priv->vscrollbar_visible ? 0 : sb_width + sb_spacing);
+                 (priv->vscrollbar_visible && !priv->touch_mode ? 0 : sb_width + sb_spacing);
              else
                priv->hscrollbar_visible = policy_may_be_visible (priv->hscrollbar_policy);
            }
@@ -2210,25 +2279,25 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
                  /* Does the content height fit the allocation with minus a possible scrollbar ? */
                  priv->vscrollbar_visible = 
                    child_scroll_height > allocation->height - 
-                   (priv->hscrollbar_visible ? sb_height + sb_spacing : 0);
+                   (priv->hscrollbar_visible && !priv->touch_mode ? sb_height + sb_spacing : 0);
                  
                  /* Now that we've guessed the vscrollbar, does the content width fit
                   * the possible new allocation width ? */
                  priv->hscrollbar_visible = 
                    child_scroll_width > allocation->width - 
-                   (priv->vscrollbar_visible ? sb_width + sb_spacing : 0);
+                   (priv->vscrollbar_visible && !priv->touch_mode ? sb_width + sb_spacing : 0);
                  
                  /* Now that we've guessed the hscrollbar, does the content height fit
                   * the possible new allocation height ? */
                  priv->vscrollbar_visible = 
                    child_scroll_height > allocation->height - 
-                   (priv->hscrollbar_visible ? sb_height + sb_spacing : 0);
+                   (priv->hscrollbar_visible && !priv->touch_mode ? sb_height + sb_spacing : 0);
                }
              else /* priv->vscrollbar_policy != GTK_POLICY_AUTOMATIC */
                {
                  priv->vscrollbar_visible = policy_may_be_visible (priv->vscrollbar_policy);
                  priv->hscrollbar_visible = child_scroll_width > allocation->width - 
-                   (priv->vscrollbar_visible ? sb_width + sb_spacing : 0);
+                   (priv->vscrollbar_visible && !priv->touch_mode ? sb_width + sb_spacing : 0);
                }
            }
          else /* priv->hscrollbar_policy != GTK_POLICY_AUTOMATIC */
@@ -2238,7 +2307,7 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
              if (priv->vscrollbar_policy == GTK_POLICY_AUTOMATIC)
                priv->vscrollbar_visible = 
                  child_scroll_height > allocation->height - 
-                 (priv->hscrollbar_visible ? 0 : sb_height + sb_spacing);
+                 (priv->hscrollbar_visible && !priv->touch_mode ? sb_height + sb_spacing : 0);
              else
                priv->vscrollbar_visible = policy_may_be_visible (priv->vscrollbar_policy);
            }
@@ -2302,11 +2371,19 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
       child_allocation.x = relative_allocation.x;
       if (priv->window_placement == GTK_CORNER_TOP_LEFT ||
          priv->window_placement == GTK_CORNER_TOP_RIGHT)
-       child_allocation.y = (relative_allocation.y +
-                             relative_allocation.height +
-                             sb_spacing);
+        {
+          if (priv->touch_mode)
+           child_allocation.y = relative_allocation.y + relative_allocation.height - sb_height;
+          else
+           child_allocation.y = relative_allocation.y + relative_allocation.height + sb_spacing;
+        }
       else
-       child_allocation.y = relative_allocation.y - sb_spacing - sb_height;
+        {
+          if (priv->touch_mode)
+           child_allocation.y = relative_allocation.y;
+          else
+           child_allocation.y = relative_allocation.y - sb_spacing - sb_height;
+        }
 
       child_allocation.width = relative_allocation.width;
       child_allocation.height = sb_height;
@@ -2328,6 +2405,16 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
             }
        }
 
+      if (priv->hindicator.enabled)
+        {
+          gdk_window_move_resize (priv->hindicator.window,
+                                  child_allocation.x,
+                                  child_allocation.y,
+                                  child_allocation.width,
+                                  child_allocation.height);
+          child_allocation.x = 0;
+          child_allocation.y = 0;
+        }
       gtk_widget_size_allocate (priv->hscrollbar, &child_allocation);
     }
 
@@ -2340,11 +2427,19 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
          (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR && 
           (priv->window_placement == GTK_CORNER_TOP_LEFT ||
            priv->window_placement == GTK_CORNER_BOTTOM_LEFT)))
-       child_allocation.x = (relative_allocation.x +
-                             relative_allocation.width +
-                             sb_spacing);
+        {
+          if (priv->touch_mode)
+           child_allocation.x = relative_allocation.x + relative_allocation.width - sb_width;
+          else
+           child_allocation.x = relative_allocation.x + relative_allocation.width + sb_spacing;
+        }
       else
-       child_allocation.x = relative_allocation.x - sb_spacing - sb_width;
+        {
+          if (priv->touch_mode)
+           child_allocation.x = relative_allocation.x;
+          else
+           child_allocation.x = relative_allocation.x - sb_spacing - sb_width;
+        }
 
       child_allocation.y = relative_allocation.y;
       child_allocation.width = sb_width;
@@ -2371,6 +2466,16 @@ gtk_scrolled_window_size_allocate (GtkWidget     *widget,
             }
         }
 
+      if (priv->vindicator.enabled)
+        {
+          gdk_window_move_resize (priv->vindicator.window,
+                                  child_allocation.x,
+                                  child_allocation.y,
+                                  child_allocation.width,
+                                  child_allocation.height);
+          child_allocation.x = 0;
+          child_allocation.y = 0;
+        }
       gtk_widget_size_allocate (priv->vscrollbar, &child_allocation);
     }
 
@@ -3002,7 +3107,7 @@ gtk_scrolled_window_get_preferred_size (GtkWidget      *widget,
        }
     }
 
-  if (policy_may_be_visible (priv->hscrollbar_policy))
+  if (policy_may_be_visible (priv->hscrollbar_policy) && !priv->touch_mode)
     {
       minimum_req.width = MAX (minimum_req.width, hscrollbar_requisition.width);
       natural_req.width = MAX (natural_req.width, hscrollbar_requisition.width);
@@ -3010,7 +3115,7 @@ gtk_scrolled_window_get_preferred_size (GtkWidget      *widget,
        extra_height = scrollbar_spacing + hscrollbar_requisition.height;
     }
 
-  if (policy_may_be_visible (priv->vscrollbar_policy))
+  if (policy_may_be_visible (priv->vscrollbar_policy) && !priv->touch_mode)
     {
       minimum_req.height = MAX (minimum_req.height, vscrollbar_requisition.height);
       natural_req.height = MAX (natural_req.height, vscrollbar_requisition.height);
@@ -3145,6 +3250,435 @@ gtk_scrolled_window_unmap (GtkWidget *widget)
   GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->unmap (widget);
 
   gtk_scrolled_window_update_animating (scrolled_window);
+
+  indicator_stop_fade (&scrolled_window->priv->hindicator);
+  indicator_stop_fade (&scrolled_window->priv->vindicator);
+}
+
+static GdkWindow *
+create_indicator_window (GtkScrolledWindow *scrolled_window,
+                         GtkWidget         *child)
+{
+  GtkWidget *widget = GTK_WIDGET (scrolled_window);
+  GtkAllocation allocation;
+  GdkWindow *window;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+
+  gtk_widget_get_allocation (child, &allocation);
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+
+  attributes.width = allocation.width;
+  attributes.height = allocation.height;
+  attributes.x = allocation.x;
+  attributes.y = allocation.y;
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+  attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
+
+  window = gdk_window_new (gtk_widget_get_window (widget),
+                           &attributes, attributes_mask);
+  gtk_widget_register_window (widget, window);
+  gtk_style_context_set_background (gtk_widget_get_style_context (widget), window);
+
+  if (scrolled_window->priv->touch_mode)
+    gtk_widget_set_parent_window (child, window);
+
+  return window;
+}
+
+static gboolean maybe_hide_indicator (gpointer data);
+
+static void
+indicator_set_fade (Indicator *indicator,
+                    gdouble    pos)
+{
+  gboolean visible;
+  indicator->current_pos = pos;
+
+  visible = indicator->current_pos != 0.0 || indicator->target_pos != 0.0;
+
+  if (visible && !gdk_window_is_visible (indicator->window))
+    {
+      gdk_window_show (indicator->window);
+      indicator->conceil_timer = g_timeout_add (INDICATOR_FADE_OUT_TIME, maybe_hide_indicator, indicator);
+    }
+  if (!visible && gdk_window_is_visible (indicator->window))
+    {
+      gdk_window_hide (indicator->window);
+      g_source_remove (indicator->conceil_timer);
+      indicator->conceil_timer = 0;
+    }
+
+  gtk_widget_set_opacity (indicator->scrollbar, indicator->current_pos);
+  gtk_widget_queue_draw (indicator->scrollbar);
+}
+
+static double
+ease_out_cubic (double t)
+{
+  double p = t - 1;
+  return p * p * p + 1;
+}
+
+static void
+indicator_fade_step (Indicator *indicator,
+                     gint64     now)
+{
+  gdouble t;
+
+  if (now < indicator->end_time)
+    t = (now - indicator->start_time) / (gdouble) (indicator->end_time - indicator->start_time);
+  else
+    t = 1.0;
+  t = ease_out_cubic (t);
+
+  indicator_set_fade (indicator,
+                      indicator->source_pos + (t * (indicator->target_pos - indicator->source_pos)));
+}
+
+static gboolean
+indicator_fade_cb (GtkWidget     *widget,
+                   GdkFrameClock *frame_clock,
+                   gpointer       user_data)
+{
+  Indicator *indicator = user_data;
+  gint64 now;
+
+  now = gdk_frame_clock_get_frame_time (frame_clock);
+  indicator_fade_step (indicator, now);
+  if (indicator->current_pos == indicator->target_pos)
+    {
+      indicator->tick_id = 0;
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+indicator_start_fade (Indicator *indicator,
+                      gdouble    target)
+{
+  gboolean animations_enabled;
+
+  if (indicator->target_pos == target)
+    return;
+
+  indicator->target_pos = target;
+
+  g_object_get (gtk_widget_get_settings (indicator->scrollbar),
+                "gtk-enable-animations", &animations_enabled,
+                NULL);
+
+  if (gtk_widget_get_mapped (indicator->scrollbar) && animations_enabled)
+    {
+      indicator->source_pos = indicator->current_pos;
+      indicator->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock 
(indicator->scrollbar));
+      indicator->end_time = indicator->start_time + INDICATOR_FADE_OUT_DURATION * 1000;
+      if (indicator->tick_id == 0)
+        indicator->tick_id = gtk_widget_add_tick_callback (indicator->scrollbar, indicator_fade_cb, 
indicator, NULL);
+
+      indicator_fade_step (indicator, indicator->start_time);
+    }
+  else
+    indicator_set_fade (indicator, target);
+}
+
+static void
+indicator_stop_fade (Indicator *indicator)
+{
+  if (indicator->tick_id != 0)
+    {
+      indicator_set_fade (indicator, indicator->target_pos);
+      gtk_widget_remove_tick_callback (indicator->scrollbar, indicator->tick_id);
+      indicator->tick_id = 0;
+    }
+}
+
+static gboolean
+maybe_hide_indicator (gpointer data)
+{
+  Indicator *indicator = data;
+
+  if (g_get_monotonic_time () - indicator->last_scroll_time >= INDICATOR_FADE_OUT_DELAY * 1000 &&
+      indicator->enabled && !indicator->over && !indicator->dragging)
+    indicator_start_fade (indicator, 0.0);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+indicator_value_changed (GtkAdjustment *adjustment,
+                         Indicator     *indicator)
+{
+  indicator->last_scroll_time = g_get_monotonic_time ();
+  if (indicator->enabled)
+    indicator_start_fade (indicator, 1.0);
+}
+
+static gboolean
+indicator_enter_notify (GtkWidget        *scrollbar,
+                        GdkEventCrossing *event,
+                        Indicator        *indicator)
+{
+  GtkStyleContext *context;
+
+  context = gtk_widget_get_style_context (scrollbar);
+  gtk_style_context_add_class (context, "locked");
+  gtk_widget_queue_resize (scrollbar);
+  indicator->over = TRUE;
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+indicator_leave_notify (GtkWidget        *scrollbar,
+                        GdkEventCrossing *event,
+                        Indicator        *indicator)
+{
+  GtkStyleContext *context;
+
+  context = gtk_widget_get_style_context (scrollbar);
+  gtk_style_context_remove_class (context, "locked");
+  gtk_widget_queue_resize (scrollbar);
+  indicator->over = FALSE;
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+indicator_style_changed (GtkStyleContext *context,
+                         Indicator       *indicator)
+{
+  if (gtk_style_context_has_class (context, "dragging"))
+    indicator->dragging = TRUE;
+  else
+    indicator->dragging = FALSE;
+}
+
+static void
+setup_indicator (GtkScrolledWindow *scrolled_window,
+                 Indicator         *indicator,
+                 GtkWidget         *scrollbar)
+{
+  GtkStyleContext *context;
+  GtkAdjustment *adjustment;
+
+  if (scrollbar == NULL)
+    return;
+
+  context = gtk_widget_get_style_context (scrollbar);
+  adjustment = gtk_range_get_adjustment (GTK_RANGE (scrollbar));
+
+  indicator->enabled = TRUE;
+  indicator->scrollbar = scrollbar;
+
+  g_object_ref (scrollbar);
+  gtk_widget_unparent (scrollbar);
+  gtk_widget_set_parent_window (scrollbar, indicator->window);
+  gtk_widget_set_parent (scrollbar, GTK_WIDGET (scrolled_window));
+  g_object_unref (scrollbar);
+
+  gtk_style_context_add_class (context, "overlay-indicator");
+  g_signal_connect (context, "changed",
+                    G_CALLBACK (indicator_style_changed), indicator);
+  g_signal_connect (scrollbar, "enter-notify-event",
+                    G_CALLBACK (indicator_enter_notify), indicator);
+  g_signal_connect (scrollbar, "leave-notify-event",
+                    G_CALLBACK (indicator_leave_notify), indicator);
+  g_signal_connect (adjustment, "value-changed",
+                    G_CALLBACK (indicator_value_changed), indicator);
+
+  gdk_window_hide (indicator->window);
+  gtk_widget_set_opacity (scrollbar, 0.0);
+  indicator->current_pos = 0.0;
+}
+
+static void
+remove_indicator (GtkScrolledWindow *scrolled_window,
+                  Indicator         *indicator)
+{
+  GtkWidget *scrollbar;
+  GtkStyleContext *context;
+  GtkAdjustment *adjustment;
+
+  if (indicator->scrollbar == NULL)
+    return;
+
+  scrollbar = indicator->scrollbar;
+  indicator->scrollbar = NULL;
+
+  context = gtk_widget_get_style_context (scrollbar);
+  adjustment = gtk_range_get_adjustment (GTK_RANGE (scrollbar));
+
+  gtk_style_context_remove_class (context, "overlay-indicator");
+  g_signal_handlers_disconnect_by_func (context, indicator_style_changed, indicator);
+  g_signal_handlers_disconnect_by_func (scrollbar, indicator_enter_notify, indicator);
+  g_signal_handlers_disconnect_by_func (scrollbar, indicator_leave_notify, indicator);
+  g_signal_handlers_disconnect_by_func (adjustment, indicator_value_changed, indicator);
+  indicator->enabled = FALSE;
+
+  if (indicator->conceil_timer)
+    {
+      g_source_remove (indicator->conceil_timer);
+      indicator->conceil_timer = 0;
+    }
+
+  if (indicator->tick_id)
+    {
+      gtk_widget_remove_tick_callback (scrollbar, indicator->tick_id);
+      indicator->tick_id = 0;
+    }
+
+  g_object_ref (scrollbar);
+  gtk_widget_unparent (scrollbar);
+  gtk_widget_set_parent (scrollbar, GTK_WIDGET (scrolled_window));
+  g_object_unref (scrollbar);
+
+  gdk_window_show (indicator->window);
+  gtk_widget_set_opacity (scrollbar, 1.0);
+  indicator->current_pos = 1.0;
+}
+
+static gboolean
+device_manager_has_mouse (GdkDeviceManager *dm)
+{
+  GdkDevice *cp;
+  GList *slaves, *s;
+  GdkDevice *device;
+  gboolean found;
+
+  found = FALSE;
+
+  cp = gdk_device_manager_get_client_pointer (dm);
+  slaves = gdk_device_list_slave_devices (cp);
+  for (s = slaves; s; s = s->next)
+    {
+      device = s->data;
+
+      if (gdk_device_get_source (device) != GDK_SOURCE_MOUSE)
+        continue;
+
+      if (strstr (gdk_device_get_name (device), "XTEST"))
+        continue;
+
+      if (strstr (gdk_device_get_name (device), "TrackPoint"))
+        continue;
+
+      if (g_object_get_data (G_OBJECT (device), "removed"))
+        continue;
+
+      found = TRUE;
+      break;
+    }
+  g_list_free (slaves);
+
+  return found;
+}
+
+static void
+gtk_scrolled_window_update_touch_mode (GtkScrolledWindow *scrolled_window)
+{
+  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+  gboolean touch_mode;
+
+  if (!priv->overlay_scrolling || g_getenv ("NO_OVERLAY_SCROLLING"))
+    touch_mode = FALSE;
+  else if (g_getenv ("GTK_TEST_TOUCHSCREEN") ||
+      (gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN) != 0)
+    touch_mode = TRUE;
+  else
+    {
+      GdkDeviceManager *dm;
+      dm = gdk_display_get_device_manager (gtk_widget_get_display (GTK_WIDGET (scrolled_window)));
+      touch_mode = !device_manager_has_mouse (dm);
+    }
+
+  if (priv->touch_mode != touch_mode)
+    {
+      priv->touch_mode = touch_mode;
+
+      if (priv->touch_mode)
+        {
+          setup_indicator (scrolled_window, &priv->hindicator, priv->hscrollbar);
+          setup_indicator (scrolled_window, &priv->vindicator, priv->vscrollbar);
+        }
+      else
+        {
+          remove_indicator (scrolled_window, &priv->hindicator);
+          remove_indicator (scrolled_window, &priv->vindicator);
+        }
+
+      gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
+    }
+}
+
+static void
+gtk_scrolled_window_device_added (GdkDeviceManager  *dm,
+                                  GdkDevice         *device,
+                                  GtkScrolledWindow *scrolled_window)
+{
+  gtk_scrolled_window_update_touch_mode (scrolled_window);
+}
+
+static void
+gtk_scrolled_window_device_removed (GdkDeviceManager  *dm,
+                                    GdkDevice         *device,
+                                    GtkScrolledWindow *scrolled_window)
+{
+  /* We need to work around the fact that ::device-removed is emitted
+   * before the device is removed from the list.
+   */
+  g_object_set_data (G_OBJECT (device), "removed", GINT_TO_POINTER (1));
+  gtk_scrolled_window_update_touch_mode (scrolled_window);
+}
+
+static void
+gtk_scrolled_window_realize (GtkWidget *widget)
+{
+  GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+  GdkDeviceManager *dm;
+
+  GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->realize (widget);
+
+  priv->hindicator.window = create_indicator_window (scrolled_window, priv->hscrollbar);
+  priv->vindicator.window = create_indicator_window (scrolled_window, priv->vscrollbar);
+
+  gtk_scrolled_window_update_touch_mode (scrolled_window);
+
+  dm = gdk_display_get_device_manager (gtk_widget_get_display (widget));
+  g_signal_connect (dm, "device-added",
+                    G_CALLBACK (gtk_scrolled_window_device_added), scrolled_window);
+  g_signal_connect (dm, "device-removed",
+                    G_CALLBACK (gtk_scrolled_window_device_removed), scrolled_window);
+}
+
+static void
+gtk_scrolled_window_unrealize (GtkWidget *widget)
+{
+  GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+  GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+  GdkDeviceManager *dm;
+
+  dm = gdk_display_get_device_manager (gtk_widget_get_display (widget));
+  g_signal_handlers_disconnect_by_func (dm, gtk_scrolled_window_device_added, scrolled_window);
+  g_signal_handlers_disconnect_by_func (dm, gtk_scrolled_window_device_removed, scrolled_window);
+
+  gtk_widget_set_parent_window (priv->hscrollbar, NULL);
+  gtk_widget_unregister_window (widget, priv->hindicator.window);
+  gdk_window_destroy (priv->hindicator.window);
+  priv->hindicator.window = NULL;
+
+  gtk_widget_set_parent_window (priv->vscrollbar, NULL);
+  gtk_widget_unregister_window (widget, priv->vindicator.window);
+  gdk_window_destroy (priv->vindicator.window);
+  priv->vindicator.window = NULL;
+
+  GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->unrealize (widget);
 }
 
 static void
@@ -3266,3 +3800,32 @@ gtk_scrolled_window_set_min_content_height (GtkScrolledWindow *scrolled_window,
       g_object_notify (G_OBJECT (scrolled_window), "min-content-height");
     }
 }
+
+void
+gtk_scrolled_window_set_overlay_scrolling (GtkScrolledWindow *scrolled_window,
+                                           gboolean           overlay_scrolling)
+{
+  GtkScrolledWindowPrivate *priv;
+
+  g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
+
+  priv = scrolled_window->priv;
+
+  if (priv->overlay_scrolling != overlay_scrolling)
+    {
+      priv->overlay_scrolling = overlay_scrolling;
+
+      if (gtk_widget_get_realized (GTK_WIDGET (scrolled_window)))
+        gtk_scrolled_window_update_touch_mode (scrolled_window);
+
+      g_object_notify (G_OBJECT (scrolled_window), "overlay-scrolling");
+    }
+}
+
+gboolean
+gtk_scrolled_window_get_overlay_scrolling (GtkScrolledWindow *scrolled_window)
+{
+  g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
+
+  return scrolled_window->priv->overlay_scrolling;
+}
diff --git a/gtk/gtkscrolledwindow.h b/gtk/gtkscrolledwindow.h
index f6f6df5..1a9e4d5 100644
--- a/gtk/gtkscrolledwindow.h
+++ b/gtk/gtkscrolledwindow.h
@@ -197,16 +197,22 @@ GDK_AVAILABLE_IN_ALL
 void           gtk_scrolled_window_set_min_content_height (GtkScrolledWindow *scrolled_window,
                                                            gint               height);
 GDK_AVAILABLE_IN_3_4
-void           gtk_scrolled_window_set_kinetic_scrolling  (GtkScrolledWindow        *scrolled_window,
-                                                           gboolean                  kinetic_scrolling);
+void           gtk_scrolled_window_set_kinetic_scrolling  (GtkScrolledWindow *scrolled_window,
+                                                           gboolean           kinetic_scrolling);
 GDK_AVAILABLE_IN_3_4
-gboolean       gtk_scrolled_window_get_kinetic_scrolling  (GtkScrolledWindow        *scrolled_window);
+gboolean       gtk_scrolled_window_get_kinetic_scrolling  (GtkScrolledWindow *scrolled_window);
 
 GDK_AVAILABLE_IN_3_4
-void           gtk_scrolled_window_set_capture_button_press (GtkScrolledWindow      *scrolled_window,
-                                                             gboolean                capture_button_press);
+void           gtk_scrolled_window_set_capture_button_press (GtkScrolledWindow *scrolled_window,
+                                                             gboolean           capture_button_press);
 GDK_AVAILABLE_IN_3_4
-gboolean       gtk_scrolled_window_get_capture_button_press (GtkScrolledWindow      *scrolled_window);
+gboolean       gtk_scrolled_window_get_capture_button_press (GtkScrolledWindow *scrolled_window);
+
+GDK_AVAILABLE_IN_3_16
+void           gtk_scrolled_window_set_overlay_scrolling  (GtkScrolledWindow *scrolled_window,
+                                                           gboolean           overlay_scrolling);
+GDK_AVAILABLE_IN_3_16
+gboolean       gtk_scrolled_window_get_overlay_scrolling (GtkScrolledWindow   *scrolled_window);
 
 
 G_END_DECLS


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