GtkScrolledWindow: Add overlay scrollbars

This commit adds a mode to GtkScrolledWindow in which it puts
narrow, auto-hiding scrollbars over the content, instead of
allocating room for the scrollbars outside of the content. 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. The
environment variable GTK_OVERLAY_SCROLLING can also be used
to force overlay scrolling on or off.
This commit is contained in:
Matthias Clasen 2014-10-08 23:34:32 -04:00
parent 05ab0f2227
commit 4455266c5b
2 changed files with 603 additions and 29 deletions

View File

@ -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;
@ -200,7 +228,8 @@ enum {
PROP_SHADOW_TYPE,
PROP_MIN_CONTENT_WIDTH,
PROP_MIN_CONTENT_HEIGHT,
PROP_KINETIC_SCROLLING
PROP_KINETIC_SCROLLING,
PROP_OVERLAY_SCROLLING
};
/* Signals */
@ -269,6 +298,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);
@ -286,6 +317,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)
@ -348,6 +386,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;
@ -499,6 +539,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
@ -857,6 +905,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",
@ -1460,6 +1510,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)),
@ -1552,6 +1605,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;
@ -1601,6 +1658,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;
@ -1789,6 +1849,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;
@ -1991,7 +2059,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;
@ -2007,7 +2075,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 ||
@ -2184,25 +2253,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 */
@ -2212,7 +2281,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);
}
@ -2245,25 +2314,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 */
@ -2273,7 +2342,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);
}
@ -2337,11 +2406,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;
@ -2363,6 +2440,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);
}
@ -2375,11 +2462,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;
@ -2406,6 +2501,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);
}
@ -3071,7 +3176,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);
@ -3079,7 +3184,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);
@ -3214,6 +3319,440 @@ 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 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, "hovering");
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, "hovering");
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);
if (indicator->window)
gdk_window_hide (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;
const gchar *env_overlay_scrolling;
const gchar *env_test_touchscreen;
env_overlay_scrolling = g_getenv ("GTK_OVERLAY_SCROLLING");
env_test_touchscreen = g_getenv ("GTK_TEST_TOUCHSCREEN");
if (!priv->overlay_scrolling || g_strcmp0 (env_overlay_scrolling, "0") == 0)
touch_mode = FALSE;
else if ((gtk_get_debug_flags () & GTK_DEBUG_TOUCHSCREEN) != 0 ||
env_test_touchscreen != NULL ||
g_strcmp0 (env_overlay_scrolling, "1") == 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
@ -3335,3 +3874,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_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), TRUE);
return scrolled_window->priv->overlay_scrolling;
}

View File

@ -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