ffe4eaf00d
Cut down on boilerplate by using the (no longer that) new helper macros. We don't care about breaking ABI in private libraries, so use G_DECLARE_FINAL_TYPE even where the class struct used to be exposed in the header, except for types we inherit from ourselves (obviously) or where the class exposes any vfuncs (where changes could affect inheritance in extensions).
984 lines
28 KiB
C
984 lines
28 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
/*
|
|
* st-scroll-bar.c: Scroll bar actor
|
|
*
|
|
* Copyright 2008 OpenedHand
|
|
* Copyright 2008, 2009 Intel Corporation.
|
|
* Copyright 2009, 2010 Red Hat, Inc.
|
|
* Copyright 2010 Maxim Ermilov
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU Lesser General Public License,
|
|
* version 2.1, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:st-scroll-bar
|
|
* @short_description: a user interface element to control scrollable areas.
|
|
*
|
|
* The #StScrollBar allows users to scroll scrollable actors, either by
|
|
* the step or page amount, or by manually dragging the handle.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <math.h>
|
|
#include <clutter/clutter.h>
|
|
|
|
#include "st-scroll-bar.h"
|
|
#include "st-bin.h"
|
|
#include "st-enum-types.h"
|
|
#include "st-private.h"
|
|
#include "st-button.h"
|
|
|
|
#define PAGING_INITIAL_REPEAT_TIMEOUT 500
|
|
#define PAGING_SUBSEQUENT_REPEAT_TIMEOUT 200
|
|
|
|
typedef struct _StScrollBarPrivate StScrollBarPrivate;
|
|
struct _StScrollBarPrivate
|
|
{
|
|
StAdjustment *adjustment;
|
|
|
|
gboolean grabbed;
|
|
gfloat x_origin;
|
|
gfloat y_origin;
|
|
|
|
ClutterActor *trough;
|
|
ClutterActor *handle;
|
|
|
|
gfloat move_x;
|
|
gfloat move_y;
|
|
|
|
/* Trough-click handling. */
|
|
enum { NONE, UP, DOWN } paging_direction;
|
|
guint paging_source_id;
|
|
guint paging_event_no;
|
|
|
|
ClutterAnimation *paging_animation;
|
|
|
|
guint vertical : 1;
|
|
};
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (StScrollBar, st_scroll_bar, ST_TYPE_WIDGET)
|
|
|
|
#define ST_SCROLL_BAR_PRIVATE(sb) st_scroll_bar_get_instance_private (ST_SCROLL_BAR (sb))
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_ADJUSTMENT,
|
|
PROP_VERTICAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
SCROLL_START,
|
|
SCROLL_STOP,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0, };
|
|
|
|
extern gfloat st_slow_down_factor;
|
|
|
|
static gboolean
|
|
handle_button_press_event_cb (ClutterActor *actor,
|
|
ClutterButtonEvent *event,
|
|
StScrollBar *bar);
|
|
|
|
static void stop_scrolling (StScrollBar *bar);
|
|
|
|
static void
|
|
st_scroll_bar_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ADJUSTMENT:
|
|
g_value_set_object (value, priv->adjustment);
|
|
break;
|
|
|
|
case PROP_VERTICAL:
|
|
g_value_set_boolean (value, priv->vertical);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_set_property (GObject *gobject,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
StScrollBar *bar = ST_SCROLL_BAR (gobject);
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ADJUSTMENT:
|
|
st_scroll_bar_set_adjustment (bar, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_VERTICAL:
|
|
priv->vertical = g_value_get_boolean (value);
|
|
if (priv->vertical)
|
|
clutter_actor_set_name (CLUTTER_ACTOR (priv->handle),
|
|
"vhandle");
|
|
else
|
|
clutter_actor_set_name (CLUTTER_ACTOR (priv->handle),
|
|
"hhandle");
|
|
clutter_actor_queue_relayout ((ClutterActor*) gobject);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_dispose (GObject *gobject)
|
|
{
|
|
StScrollBar *bar = ST_SCROLL_BAR (gobject);
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
|
|
if (priv->adjustment)
|
|
st_scroll_bar_set_adjustment (bar, NULL);
|
|
|
|
if (priv->handle)
|
|
{
|
|
clutter_actor_destroy (priv->handle);
|
|
priv->handle = NULL;
|
|
}
|
|
|
|
if (priv->trough)
|
|
{
|
|
clutter_actor_destroy (priv->trough);
|
|
priv->trough = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (st_scroll_bar_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_unmap (ClutterActor *actor)
|
|
{
|
|
CLUTTER_ACTOR_CLASS (st_scroll_bar_parent_class)->unmap (actor);
|
|
|
|
stop_scrolling (ST_SCROLL_BAR (actor));
|
|
}
|
|
|
|
static void
|
|
scroll_bar_allocate_children (StScrollBar *bar,
|
|
const ClutterActorBox *box,
|
|
ClutterAllocationFlags flags)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (bar));
|
|
ClutterActorBox content_box, trough_box;
|
|
|
|
st_theme_node_get_content_box (theme_node, box, &content_box);
|
|
|
|
if (priv->vertical)
|
|
{
|
|
trough_box.x1 = content_box.x1;
|
|
trough_box.y1 = content_box.y1;
|
|
trough_box.x2 = content_box.x2;
|
|
trough_box.y2 = content_box.y2;
|
|
clutter_actor_allocate (priv->trough, &trough_box, flags);
|
|
}
|
|
else
|
|
{
|
|
trough_box.x1 = content_box.x1;
|
|
trough_box.y1 = content_box.y1;
|
|
trough_box.x2 = content_box.x2;
|
|
trough_box.y2 = content_box.y2;
|
|
clutter_actor_allocate (priv->trough, &trough_box, flags);
|
|
}
|
|
|
|
|
|
if (priv->adjustment)
|
|
{
|
|
float handle_size, position, avail_size;
|
|
gdouble value, lower, upper, page_size, increment, min_size, max_size;
|
|
ClutterActorBox handle_box = { 0, };
|
|
|
|
st_adjustment_get_values (priv->adjustment,
|
|
&value,
|
|
&lower,
|
|
&upper,
|
|
NULL,
|
|
NULL,
|
|
&page_size);
|
|
|
|
if ((upper == lower)
|
|
|| (page_size >= (upper - lower)))
|
|
increment = 1.0;
|
|
else
|
|
increment = page_size / (upper - lower);
|
|
|
|
min_size = 32.;
|
|
st_theme_node_lookup_length (theme_node, "min-size", FALSE, &min_size);
|
|
max_size = G_MAXINT16;
|
|
st_theme_node_lookup_length (theme_node, "max-size", FALSE, &max_size);
|
|
|
|
if (upper - lower - page_size <= 0)
|
|
position = 0;
|
|
else
|
|
position = (value - lower) / (upper - lower - page_size);
|
|
|
|
if (priv->vertical)
|
|
{
|
|
avail_size = content_box.y2 - content_box.y1;
|
|
handle_size = increment * avail_size;
|
|
handle_size = CLAMP (handle_size, min_size, max_size);
|
|
|
|
handle_box.x1 = content_box.x1;
|
|
handle_box.y1 = content_box.y1 + position * (avail_size - handle_size);
|
|
|
|
handle_box.x2 = content_box.x2;
|
|
handle_box.y2 = handle_box.y1 + handle_size;
|
|
}
|
|
else
|
|
{
|
|
avail_size = content_box.x2 - content_box.x1;
|
|
handle_size = increment * avail_size;
|
|
handle_size = CLAMP (handle_size, min_size, max_size);
|
|
|
|
handle_box.x1 = content_box.x1 + position * (avail_size - handle_size);
|
|
handle_box.y1 = content_box.y1;
|
|
|
|
handle_box.x2 = handle_box.x1 + handle_size;
|
|
handle_box.y2 = content_box.y2;
|
|
}
|
|
|
|
clutter_actor_allocate (priv->handle,
|
|
&handle_box,
|
|
flags);
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_get_preferred_width (ClutterActor *self,
|
|
gfloat for_height,
|
|
gfloat *min_width_p,
|
|
gfloat *natural_width_p)
|
|
{
|
|
StScrollBar *bar = ST_SCROLL_BAR (self);
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
|
|
gfloat trough_min_width, trough_natural_width;
|
|
gfloat handle_min_width, handle_natural_width;
|
|
|
|
st_theme_node_adjust_for_height (theme_node, &for_height);
|
|
|
|
_st_actor_get_preferred_width (priv->trough, for_height, TRUE,
|
|
&trough_min_width, &trough_natural_width);
|
|
|
|
_st_actor_get_preferred_width (priv->handle, for_height, TRUE,
|
|
&handle_min_width, &handle_natural_width);
|
|
|
|
if (priv->vertical)
|
|
{
|
|
if (min_width_p)
|
|
*min_width_p = MAX (trough_min_width, handle_min_width);
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p = MAX (trough_natural_width, handle_natural_width);
|
|
}
|
|
else
|
|
{
|
|
if (min_width_p)
|
|
*min_width_p = trough_min_width + handle_min_width;
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p = trough_natural_width + handle_natural_width;
|
|
}
|
|
|
|
st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_get_preferred_height (ClutterActor *self,
|
|
gfloat for_width,
|
|
gfloat *min_height_p,
|
|
gfloat *natural_height_p)
|
|
{
|
|
StScrollBar *bar = ST_SCROLL_BAR (self);
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
|
|
gfloat trough_min_height, trough_natural_height;
|
|
gfloat handle_min_height, handle_natural_height;
|
|
|
|
st_theme_node_adjust_for_width (theme_node, &for_width);
|
|
|
|
_st_actor_get_preferred_height (priv->trough, for_width, TRUE,
|
|
&trough_min_height, &trough_natural_height);
|
|
|
|
_st_actor_get_preferred_height (priv->handle, for_width, TRUE,
|
|
&handle_min_height, &handle_natural_height);
|
|
|
|
if (priv->vertical)
|
|
{
|
|
if (min_height_p)
|
|
*min_height_p = trough_min_height + handle_min_height;
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = trough_natural_height + handle_natural_height;
|
|
}
|
|
else
|
|
{
|
|
if (min_height_p)
|
|
*min_height_p = MAX (trough_min_height, handle_min_height);
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = MAX (trough_natural_height, handle_natural_height);
|
|
}
|
|
|
|
st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_allocate (ClutterActor *actor,
|
|
const ClutterActorBox *box,
|
|
ClutterAllocationFlags flags)
|
|
{
|
|
StScrollBar *bar = ST_SCROLL_BAR (actor);
|
|
|
|
clutter_actor_set_allocation (actor, box, flags);
|
|
|
|
scroll_bar_allocate_children (bar, box, flags);
|
|
}
|
|
|
|
static void
|
|
scroll_bar_update_positions (StScrollBar *bar)
|
|
{
|
|
ClutterActorBox box;
|
|
|
|
/* Due to a change in the adjustments, we need to reposition our
|
|
* children; since adjustments changes can come from allocation
|
|
* changes in the scrolled area, we can't just queue a new relayout -
|
|
* we may already be in a relayout cycle. On the other hand, if
|
|
* a relayout is already queued, we can't just go ahead and allocate
|
|
* our children, since we don't have a valid allocation, and calling
|
|
* clutter_actor_get_allocation_box() will trigger an immediate
|
|
* stage relayout. So what we do is go ahead and immediately
|
|
* allocate our children if we already have a valid allocation, and
|
|
* otherwise just wait for the queued relayout.
|
|
*/
|
|
if (!clutter_actor_has_allocation (CLUTTER_ACTOR (bar)))
|
|
return;
|
|
|
|
clutter_actor_get_allocation_box (CLUTTER_ACTOR (bar), &box);
|
|
scroll_bar_allocate_children (bar, &box, CLUTTER_ALLOCATION_NONE);
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_style_changed (StWidget *widget)
|
|
{
|
|
StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (widget);
|
|
|
|
st_widget_style_changed (ST_WIDGET (priv->trough));
|
|
st_widget_style_changed (ST_WIDGET (priv->handle));
|
|
|
|
ST_WIDGET_CLASS (st_scroll_bar_parent_class)->style_changed (widget);
|
|
}
|
|
|
|
static void
|
|
bar_reactive_notify_cb (GObject *gobject,
|
|
GParamSpec *arg1,
|
|
gpointer user_data)
|
|
{
|
|
StScrollBar *bar = ST_SCROLL_BAR (gobject);
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
|
|
clutter_actor_set_reactive (priv->handle,
|
|
clutter_actor_get_reactive (CLUTTER_ACTOR (bar)));
|
|
}
|
|
|
|
static GObject*
|
|
st_scroll_bar_constructor (GType type,
|
|
guint n_properties,
|
|
GObjectConstructParam *properties)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GObject *obj;
|
|
StScrollBar *bar;
|
|
|
|
gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class);
|
|
obj = gobject_class->constructor (type, n_properties, properties);
|
|
|
|
bar = ST_SCROLL_BAR (obj);
|
|
|
|
g_signal_connect (bar, "notify::reactive",
|
|
G_CALLBACK (bar_reactive_notify_cb), NULL);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static void
|
|
adjust_with_direction (StAdjustment *adj,
|
|
ClutterScrollDirection direction)
|
|
{
|
|
gdouble delta;
|
|
|
|
switch (direction)
|
|
{
|
|
case CLUTTER_SCROLL_UP:
|
|
case CLUTTER_SCROLL_LEFT:
|
|
delta = -1.0;
|
|
break;
|
|
case CLUTTER_SCROLL_RIGHT:
|
|
case CLUTTER_SCROLL_DOWN:
|
|
delta = 1.0;
|
|
break;
|
|
case CLUTTER_SCROLL_SMOOTH:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
st_adjustment_adjust_for_scroll_event (adj, delta);
|
|
}
|
|
|
|
static gboolean
|
|
st_scroll_bar_scroll_event (ClutterActor *actor,
|
|
ClutterScrollEvent *event)
|
|
{
|
|
StScrollBarPrivate *priv = ST_SCROLL_BAR_PRIVATE (actor);
|
|
|
|
if (clutter_event_is_pointer_emulated ((ClutterEvent *) event))
|
|
return TRUE;
|
|
|
|
switch (event->direction)
|
|
{
|
|
case CLUTTER_SCROLL_SMOOTH:
|
|
{
|
|
gdouble delta_x, delta_y;
|
|
clutter_event_get_scroll_delta ((ClutterEvent *)event, &delta_x, &delta_y);
|
|
|
|
if (priv->vertical)
|
|
st_adjustment_adjust_for_scroll_event (priv->adjustment, delta_y);
|
|
else
|
|
st_adjustment_adjust_for_scroll_event (priv->adjustment, delta_x);
|
|
}
|
|
break;
|
|
case CLUTTER_SCROLL_UP:
|
|
case CLUTTER_SCROLL_DOWN:
|
|
case CLUTTER_SCROLL_LEFT:
|
|
case CLUTTER_SCROLL_RIGHT:
|
|
adjust_with_direction (priv->adjustment, event->direction);
|
|
break;
|
|
default:
|
|
g_return_val_if_reached (FALSE);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_class_init (StScrollBarClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
|
|
GParamSpec *pspec;
|
|
|
|
object_class->get_property = st_scroll_bar_get_property;
|
|
object_class->set_property = st_scroll_bar_set_property;
|
|
object_class->dispose = st_scroll_bar_dispose;
|
|
object_class->constructor = st_scroll_bar_constructor;
|
|
|
|
actor_class->get_preferred_width = st_scroll_bar_get_preferred_width;
|
|
actor_class->get_preferred_height = st_scroll_bar_get_preferred_height;
|
|
actor_class->allocate = st_scroll_bar_allocate;
|
|
actor_class->scroll_event = st_scroll_bar_scroll_event;
|
|
actor_class->unmap = st_scroll_bar_unmap;
|
|
|
|
widget_class->style_changed = st_scroll_bar_style_changed;
|
|
|
|
g_object_class_install_property
|
|
(object_class,
|
|
PROP_ADJUSTMENT,
|
|
g_param_spec_object ("adjustment",
|
|
"Adjustment",
|
|
"The adjustment",
|
|
ST_TYPE_ADJUSTMENT,
|
|
ST_PARAM_READWRITE));
|
|
|
|
pspec = g_param_spec_boolean ("vertical",
|
|
"Vertical Orientation",
|
|
"Vertical Orientation",
|
|
FALSE,
|
|
ST_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_VERTICAL, pspec);
|
|
|
|
signals[SCROLL_START] =
|
|
g_signal_new ("scroll-start",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (StScrollBarClass, scroll_start),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
signals[SCROLL_STOP] =
|
|
g_signal_new ("scroll-stop",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (StScrollBarClass, scroll_stop),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
static void
|
|
move_slider (StScrollBar *bar,
|
|
gfloat x,
|
|
gfloat y)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
gdouble position, lower, upper, page_size;
|
|
gfloat ux, uy, pos, size;
|
|
|
|
if (!priv->adjustment)
|
|
return;
|
|
|
|
if (!clutter_actor_transform_stage_point (priv->trough, x, y, &ux, &uy))
|
|
return;
|
|
|
|
if (priv->vertical)
|
|
size = clutter_actor_get_height (priv->trough)
|
|
- clutter_actor_get_height (priv->handle);
|
|
else
|
|
size = clutter_actor_get_width (priv->trough)
|
|
- clutter_actor_get_width (priv->handle);
|
|
|
|
if (size == 0)
|
|
return;
|
|
|
|
if (priv->vertical)
|
|
pos = uy - priv->y_origin;
|
|
else
|
|
pos = ux - priv->x_origin;
|
|
pos = CLAMP (pos, 0, size);
|
|
|
|
st_adjustment_get_values (priv->adjustment,
|
|
NULL,
|
|
&lower,
|
|
&upper,
|
|
NULL,
|
|
NULL,
|
|
&page_size);
|
|
|
|
position = ((pos / size)
|
|
* (upper - lower - page_size))
|
|
+ lower;
|
|
|
|
st_adjustment_set_value (priv->adjustment, position);
|
|
}
|
|
|
|
static void
|
|
stop_scrolling (StScrollBar *bar)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
if (!priv->grabbed)
|
|
return;
|
|
|
|
st_widget_remove_style_pseudo_class (ST_WIDGET (priv->handle), "active");
|
|
|
|
clutter_ungrab_pointer ();
|
|
priv->grabbed = FALSE;
|
|
g_signal_emit (bar, signals[SCROLL_STOP], 0);
|
|
}
|
|
|
|
static gboolean
|
|
handle_motion_event_cb (ClutterActor *trough,
|
|
ClutterMotionEvent *event,
|
|
StScrollBar *bar)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
if (!priv->grabbed)
|
|
return FALSE;
|
|
|
|
move_slider (bar, event->x, event->y);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
handle_button_release_event_cb (ClutterActor *trough,
|
|
ClutterButtonEvent *event,
|
|
StScrollBar *bar)
|
|
{
|
|
if (event->button != 1)
|
|
return FALSE;
|
|
|
|
stop_scrolling (bar);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
handle_button_press_event_cb (ClutterActor *actor,
|
|
ClutterButtonEvent *event,
|
|
StScrollBar *bar)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (bar);
|
|
|
|
if (event->button != 1)
|
|
return FALSE;
|
|
|
|
if (!clutter_actor_transform_stage_point (priv->handle,
|
|
event->x,
|
|
event->y,
|
|
&priv->x_origin,
|
|
&priv->y_origin))
|
|
return FALSE;
|
|
|
|
st_widget_add_style_pseudo_class (ST_WIDGET (priv->handle), "active");
|
|
|
|
/* Account for the scrollbar-trough-handle nesting. */
|
|
priv->x_origin += clutter_actor_get_x (priv->trough);
|
|
priv->y_origin += clutter_actor_get_y (priv->trough);
|
|
|
|
g_assert (!priv->grabbed);
|
|
|
|
clutter_grab_pointer (priv->handle);
|
|
priv->grabbed = TRUE;
|
|
g_signal_emit (bar, signals[SCROLL_START], 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
animation_completed_cb (ClutterAnimation *animation,
|
|
StScrollBarPrivate *priv)
|
|
{
|
|
g_object_unref (priv->paging_animation);
|
|
priv->paging_animation = NULL;
|
|
}
|
|
|
|
static gboolean
|
|
trough_paging_cb (StScrollBar *self)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
|
|
gfloat handle_pos, event_pos, tx, ty;
|
|
gdouble value;
|
|
gdouble page_increment;
|
|
gboolean ret;
|
|
|
|
gulong mode;
|
|
ClutterAnimation *a;
|
|
GValue v = { 0, };
|
|
ClutterTimeline *t;
|
|
|
|
if (priv->paging_event_no == 0)
|
|
{
|
|
/* Scroll on after initial timeout. */
|
|
mode = CLUTTER_EASE_OUT_CUBIC;
|
|
ret = FALSE;
|
|
priv->paging_event_no = 1;
|
|
priv->paging_source_id = g_timeout_add (
|
|
PAGING_INITIAL_REPEAT_TIMEOUT,
|
|
(GSourceFunc) trough_paging_cb,
|
|
self);
|
|
g_source_set_name_by_id (priv->paging_source_id, "[gnome-shell] trough_paging_cb");
|
|
}
|
|
else if (priv->paging_event_no == 1)
|
|
{
|
|
/* Scroll on after subsequent timeout. */
|
|
ret = FALSE;
|
|
mode = CLUTTER_EASE_IN_CUBIC;
|
|
priv->paging_event_no = 2;
|
|
priv->paging_source_id = g_timeout_add (
|
|
PAGING_SUBSEQUENT_REPEAT_TIMEOUT,
|
|
(GSourceFunc) trough_paging_cb,
|
|
self);
|
|
g_source_set_name_by_id (priv->paging_source_id, "[gnome-shell] trough_paging_cb");
|
|
}
|
|
else
|
|
{
|
|
/* Keep scrolling. */
|
|
ret = TRUE;
|
|
mode = CLUTTER_LINEAR;
|
|
priv->paging_event_no++;
|
|
}
|
|
|
|
/* Do the scrolling */
|
|
st_adjustment_get_values (priv->adjustment,
|
|
&value, NULL, NULL,
|
|
NULL, &page_increment, NULL);
|
|
|
|
if (priv->vertical)
|
|
handle_pos = clutter_actor_get_y (priv->handle);
|
|
else
|
|
handle_pos = clutter_actor_get_x (priv->handle);
|
|
|
|
clutter_actor_transform_stage_point (CLUTTER_ACTOR (priv->trough),
|
|
priv->move_x,
|
|
priv->move_y,
|
|
&tx, &ty);
|
|
|
|
if (priv->vertical)
|
|
event_pos = ty;
|
|
else
|
|
event_pos = tx;
|
|
|
|
if (event_pos > handle_pos)
|
|
{
|
|
if (priv->paging_direction == NONE)
|
|
{
|
|
/* Remember direction. */
|
|
priv->paging_direction = DOWN;
|
|
}
|
|
if (priv->paging_direction == UP)
|
|
{
|
|
/* Scrolled far enough. */
|
|
return FALSE;
|
|
}
|
|
value += page_increment;
|
|
}
|
|
else
|
|
{
|
|
if (priv->paging_direction == NONE)
|
|
{
|
|
/* Remember direction. */
|
|
priv->paging_direction = UP;
|
|
}
|
|
if (priv->paging_direction == DOWN)
|
|
{
|
|
/* Scrolled far enough. */
|
|
return FALSE;
|
|
}
|
|
value -= page_increment;
|
|
}
|
|
|
|
if (priv->paging_animation)
|
|
{
|
|
clutter_animation_completed (priv->paging_animation);
|
|
}
|
|
|
|
/* FIXME: Creating a new animation for each scroll is probably not the best
|
|
* idea, but it's a lot less involved than extenind the current animation */
|
|
a = priv->paging_animation = g_object_new (CLUTTER_TYPE_ANIMATION,
|
|
"object", priv->adjustment,
|
|
"duration", (guint)(PAGING_SUBSEQUENT_REPEAT_TIMEOUT * st_slow_down_factor),
|
|
"mode", mode,
|
|
NULL);
|
|
g_value_init (&v, G_TYPE_DOUBLE);
|
|
g_value_set_double (&v, value);
|
|
clutter_animation_bind (priv->paging_animation, "value", &v);
|
|
t = clutter_animation_get_timeline (priv->paging_animation);
|
|
g_signal_connect (a, "completed", G_CALLBACK (animation_completed_cb), priv);
|
|
clutter_timeline_start (t);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
trough_button_press_event_cb (ClutterActor *actor,
|
|
ClutterButtonEvent *event,
|
|
StScrollBar *self)
|
|
{
|
|
StScrollBarPrivate *priv;
|
|
|
|
g_return_val_if_fail (self, FALSE);
|
|
|
|
if (event->button != 1)
|
|
return FALSE;
|
|
|
|
priv = st_scroll_bar_get_instance_private (self);
|
|
if (priv->adjustment == NULL)
|
|
return FALSE;
|
|
|
|
priv->move_x = event->x;
|
|
priv->move_y = event->y;
|
|
priv->paging_direction = NONE;
|
|
priv->paging_event_no = 0;
|
|
trough_paging_cb (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
trough_button_release_event_cb (ClutterActor *actor,
|
|
ClutterButtonEvent *event,
|
|
StScrollBar *self)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
|
|
|
|
if (event->button != 1)
|
|
return FALSE;
|
|
|
|
if (priv->paging_source_id)
|
|
{
|
|
g_source_remove (priv->paging_source_id);
|
|
priv->paging_source_id = 0;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
trough_leave_event_cb (ClutterActor *actor,
|
|
ClutterEvent *event,
|
|
StScrollBar *self)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
|
|
|
|
if (priv->paging_source_id)
|
|
{
|
|
g_source_remove (priv->paging_source_id);
|
|
priv->paging_source_id = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_notify_reactive (StScrollBar *self)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
|
|
|
|
gboolean reactive = clutter_actor_get_reactive (CLUTTER_ACTOR (self));
|
|
|
|
clutter_actor_set_reactive (CLUTTER_ACTOR (priv->trough), reactive);
|
|
clutter_actor_set_reactive (CLUTTER_ACTOR (priv->handle), reactive);
|
|
}
|
|
|
|
static void
|
|
st_scroll_bar_init (StScrollBar *self)
|
|
{
|
|
StScrollBarPrivate *priv = st_scroll_bar_get_instance_private (self);
|
|
|
|
priv->trough = (ClutterActor *) st_bin_new ();
|
|
clutter_actor_set_reactive ((ClutterActor *) priv->trough, TRUE);
|
|
clutter_actor_set_name (CLUTTER_ACTOR (priv->trough), "trough");
|
|
clutter_actor_add_child (CLUTTER_ACTOR (self),
|
|
CLUTTER_ACTOR (priv->trough));
|
|
g_signal_connect (priv->trough, "button-press-event",
|
|
G_CALLBACK (trough_button_press_event_cb), self);
|
|
g_signal_connect (priv->trough, "button-release-event",
|
|
G_CALLBACK (trough_button_release_event_cb), self);
|
|
g_signal_connect (priv->trough, "leave-event",
|
|
G_CALLBACK (trough_leave_event_cb), self);
|
|
|
|
priv->handle = (ClutterActor *) st_button_new ();
|
|
clutter_actor_set_name (CLUTTER_ACTOR (priv->handle), "hhandle");
|
|
clutter_actor_add_child (CLUTTER_ACTOR (self),
|
|
CLUTTER_ACTOR (priv->handle));
|
|
g_signal_connect (priv->handle, "button-press-event",
|
|
G_CALLBACK (handle_button_press_event_cb), self);
|
|
g_signal_connect (priv->handle, "button-release-event",
|
|
G_CALLBACK (handle_button_release_event_cb), self);
|
|
g_signal_connect (priv->handle, "motion-event",
|
|
G_CALLBACK (handle_motion_event_cb), self);
|
|
|
|
clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
|
|
|
|
g_signal_connect (self, "notify::reactive",
|
|
G_CALLBACK (st_scroll_bar_notify_reactive), NULL);
|
|
}
|
|
|
|
StWidget *
|
|
st_scroll_bar_new (StAdjustment *adjustment)
|
|
{
|
|
return g_object_new (ST_TYPE_SCROLL_BAR,
|
|
"adjustment", adjustment,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
on_notify_value (GObject *object,
|
|
GParamSpec *pspec,
|
|
StScrollBar *bar)
|
|
{
|
|
scroll_bar_update_positions (bar);
|
|
}
|
|
|
|
static void
|
|
on_changed (StAdjustment *adjustment,
|
|
StScrollBar *bar)
|
|
{
|
|
scroll_bar_update_positions (bar);
|
|
}
|
|
|
|
void
|
|
st_scroll_bar_set_adjustment (StScrollBar *bar,
|
|
StAdjustment *adjustment)
|
|
{
|
|
StScrollBarPrivate *priv;
|
|
|
|
g_return_if_fail (ST_IS_SCROLL_BAR (bar));
|
|
|
|
priv = st_scroll_bar_get_instance_private (bar);
|
|
|
|
if (adjustment == priv->adjustment)
|
|
return;
|
|
|
|
if (priv->adjustment)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (priv->adjustment,
|
|
on_notify_value,
|
|
bar);
|
|
g_signal_handlers_disconnect_by_func (priv->adjustment,
|
|
on_changed,
|
|
bar);
|
|
g_object_unref (priv->adjustment);
|
|
priv->adjustment = NULL;
|
|
}
|
|
|
|
if (adjustment)
|
|
{
|
|
priv->adjustment = g_object_ref (adjustment);
|
|
|
|
g_signal_connect (priv->adjustment, "notify::value",
|
|
G_CALLBACK (on_notify_value),
|
|
bar);
|
|
g_signal_connect (priv->adjustment, "changed",
|
|
G_CALLBACK (on_changed),
|
|
bar);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (bar));
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (bar), "adjustment");
|
|
}
|
|
|
|
/**
|
|
* st_scroll_bar_get_adjustment:
|
|
* @bar: a #StScrollbar
|
|
*
|
|
* Gets the adjustment object that stores the current position
|
|
* of the scrollbar.
|
|
*
|
|
* Return value: (transfer none): the adjustment
|
|
*/
|
|
StAdjustment *
|
|
st_scroll_bar_get_adjustment (StScrollBar *bar)
|
|
{
|
|
g_return_val_if_fail (ST_IS_SCROLL_BAR (bar), NULL);
|
|
|
|
return ((StScrollBarPrivate *)ST_SCROLL_BAR_PRIVATE (bar))->adjustment;
|
|
}
|
|
|