1bd8c67041
If enabled, scrollbars take away from the allocation given to the view's content. This is usually preferrable to painting the bars on top of the content, but there are exceptions, for instance when the content needs to be centered with regard to the view as a whole. Add a :overlay-scrollbars property to account for those cases. https://bugzilla.gnome.org/show_bug.cgi?id=694261
1194 lines
37 KiB
C
1194 lines
37 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
/*
|
|
* st-scroll-view.h: Container with scroll-bars
|
|
*
|
|
* Copyright 2008 OpenedHand
|
|
* Copyright 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-view
|
|
* @short_description: a container for scrollable children
|
|
*
|
|
* #StScrollView is a single child container for actors that implement
|
|
* #StScrollable. It provides scrollbars around the edge of the child to
|
|
* allow the user to move around the scrollable area.
|
|
*/
|
|
|
|
/* TODO: The code here currently only deals with height-for-width
|
|
* allocation; width-for-height allocation would need a second set of
|
|
* code paths through get_preferred_height()/get_preferred_width()/allocate()
|
|
* that reverse the roles of the horizontal and vertical scrollbars.
|
|
*
|
|
* TODO: The multiple layout passes with and without scrollbars when
|
|
* using the automatic policy causes considerable inefficiency because
|
|
* it breaks request caching; we should saved the last size passed
|
|
* into allocate() and if it's the same as previous size not repeat
|
|
* the determination of scrollbar visibility. This requires overriding
|
|
* queue_relayout() so we know when to discard the saved value.
|
|
*
|
|
* The size negotiation between the #StScrollView and the child is
|
|
* described in the documentation for #StScrollable; the significant
|
|
* part to note there is that reported minimum sizes for a scrolled
|
|
* child are the minimum sizes when no scrollbar is needed. This allows
|
|
* us to determine what scrollbars are visible without a need to look
|
|
* inside the #StAdjustment.
|
|
*
|
|
* The second simplification that we make that allows us to implement
|
|
* a straighforward height-for-width negotiation without multiple
|
|
* allocate passes is that when the scrollbar policy is
|
|
* AUTO, we always reserve space for the scrollbar in the
|
|
* reported minimum and natural size.
|
|
*
|
|
* See https://bugzilla.gnome.org/show_bug.cgi?id=611740 for a more
|
|
* detailed description of the considerations involved.
|
|
*/
|
|
|
|
#include "st-scroll-view.h"
|
|
#include "st-scroll-bar.h"
|
|
#include "st-scrollable.h"
|
|
#include "st-scroll-view-fade.h"
|
|
#include <clutter/clutter.h>
|
|
#include <math.h>
|
|
|
|
static void clutter_container_iface_init (ClutterContainerIface *iface);
|
|
|
|
static ClutterContainerIface *st_scroll_view_parent_iface = NULL;
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN,
|
|
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
|
|
clutter_container_iface_init))
|
|
|
|
#define SCROLL_VIEW_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
|
|
ST_TYPE_SCROLL_VIEW, \
|
|
StScrollViewPrivate))
|
|
|
|
struct _StScrollViewPrivate
|
|
{
|
|
/* a pointer to the child; this is actually stored
|
|
* inside StBin:child, but we keep it to avoid
|
|
* calling st_bin_get_child() every time we need it
|
|
*/
|
|
ClutterActor *child;
|
|
|
|
StAdjustment *hadjustment;
|
|
ClutterActor *hscroll;
|
|
StAdjustment *vadjustment;
|
|
ClutterActor *vscroll;
|
|
|
|
GtkPolicyType hscrollbar_policy;
|
|
GtkPolicyType vscrollbar_policy;
|
|
|
|
gfloat row_size;
|
|
gfloat column_size;
|
|
|
|
StScrollViewFade *fade_effect;
|
|
|
|
gboolean row_size_set : 1;
|
|
gboolean column_size_set : 1;
|
|
guint mouse_scroll : 1;
|
|
guint overlay_scrollbars : 1;
|
|
guint hscrollbar_visible : 1;
|
|
guint vscrollbar_visible : 1;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
|
|
PROP_HSCROLL,
|
|
PROP_VSCROLL,
|
|
PROP_HSCROLLBAR_POLICY,
|
|
PROP_VSCROLLBAR_POLICY,
|
|
PROP_HSCROLLBAR_VISIBLE,
|
|
PROP_VSCROLLBAR_VISIBLE,
|
|
PROP_MOUSE_SCROLL,
|
|
PROP_OVERLAY_SCROLLBARS,
|
|
};
|
|
|
|
static void
|
|
st_scroll_view_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
StScrollViewPrivate *priv = ((StScrollView *) object)->priv;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_HSCROLL:
|
|
g_value_set_object (value, priv->hscroll);
|
|
break;
|
|
case PROP_VSCROLL:
|
|
g_value_set_object (value, priv->vscroll);
|
|
break;
|
|
case PROP_HSCROLLBAR_POLICY:
|
|
g_value_set_enum (value, priv->hscrollbar_policy);
|
|
break;
|
|
case PROP_VSCROLLBAR_POLICY:
|
|
g_value_set_enum (value, priv->vscrollbar_policy);
|
|
break;
|
|
case PROP_HSCROLLBAR_VISIBLE:
|
|
g_value_set_boolean (value, priv->hscrollbar_visible);
|
|
break;
|
|
case PROP_VSCROLLBAR_VISIBLE:
|
|
g_value_set_boolean (value, priv->vscrollbar_visible);
|
|
break;
|
|
case PROP_MOUSE_SCROLL:
|
|
g_value_set_boolean (value, priv->mouse_scroll);
|
|
break;
|
|
case PROP_OVERLAY_SCROLLBARS:
|
|
g_value_set_boolean (value, priv->overlay_scrollbars);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_scroll_view_update_fade_effect:
|
|
* @self: a #StScrollView
|
|
* @vfade_offset: The length of the veritcal fade effect, in pixels.
|
|
* @hfade_offset: The length of the horizontal fade effect, in pixels.
|
|
*
|
|
* Sets the height of the fade area area in pixels. A value of 0
|
|
* disables the effect.
|
|
*/
|
|
static void
|
|
st_scroll_view_update_fade_effect (StScrollView *self,
|
|
float vfade_offset,
|
|
float hfade_offset)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv;
|
|
|
|
/* A fade amount of more than 0 enables the effect. */
|
|
if (vfade_offset > 0. || hfade_offset > 0.)
|
|
{
|
|
if (priv->fade_effect == NULL) {
|
|
priv->fade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL);
|
|
|
|
clutter_actor_add_effect_with_name (CLUTTER_ACTOR (self), "fade",
|
|
CLUTTER_EFFECT (priv->fade_effect));
|
|
}
|
|
|
|
g_object_set (priv->fade_effect,
|
|
"vfade-offset", vfade_offset,
|
|
NULL);
|
|
g_object_set (priv->fade_effect,
|
|
"hfade-offset", hfade_offset,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
if (priv->fade_effect != NULL) {
|
|
clutter_actor_remove_effect (CLUTTER_ACTOR (self), CLUTTER_EFFECT (priv->fade_effect));
|
|
priv->fade_effect = NULL;
|
|
}
|
|
}
|
|
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
StScrollView *self = ST_SCROLL_VIEW (object);
|
|
StScrollViewPrivate *priv = self->priv;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_MOUSE_SCROLL:
|
|
st_scroll_view_set_mouse_scrolling (self,
|
|
g_value_get_boolean (value));
|
|
break;
|
|
case PROP_OVERLAY_SCROLLBARS:
|
|
st_scroll_view_set_overlay_scrollbars (self,
|
|
g_value_get_boolean (value));
|
|
break;
|
|
case PROP_HSCROLLBAR_POLICY:
|
|
st_scroll_view_set_policy (self,
|
|
g_value_get_enum (value),
|
|
priv->vscrollbar_policy);
|
|
break;
|
|
case PROP_VSCROLLBAR_POLICY:
|
|
st_scroll_view_set_policy (self,
|
|
priv->hscrollbar_policy,
|
|
g_value_get_enum (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_dispose (GObject *object)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv;
|
|
|
|
if (priv->fade_effect)
|
|
{
|
|
clutter_actor_remove_effect (CLUTTER_ACTOR (object), CLUTTER_EFFECT (priv->fade_effect));
|
|
priv->fade_effect = NULL;
|
|
}
|
|
|
|
if (priv->vscroll)
|
|
clutter_actor_destroy (priv->vscroll);
|
|
|
|
if (priv->hscroll)
|
|
clutter_actor_destroy (priv->hscroll);
|
|
|
|
/* For most reliable freeing of memory, an object with signals
|
|
* like StAdjustment should be explicitly disposed. Since we own
|
|
* the adjustments, we take care of that. This also disconnects
|
|
* the signal handlers that we established on creation.
|
|
*/
|
|
if (priv->hadjustment)
|
|
{
|
|
g_object_run_dispose (G_OBJECT (priv->hadjustment));
|
|
g_object_unref (priv->hadjustment);
|
|
priv->hadjustment = NULL;
|
|
}
|
|
|
|
if (priv->vadjustment)
|
|
{
|
|
g_object_run_dispose (G_OBJECT (priv->vadjustment));
|
|
g_object_unref (priv->vadjustment);
|
|
priv->vadjustment = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_paint (ClutterActor *actor)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
|
|
|
|
st_widget_paint_background (ST_WIDGET (actor));
|
|
|
|
if (priv->child)
|
|
clutter_actor_paint (priv->child);
|
|
if (priv->hscrollbar_visible)
|
|
clutter_actor_paint (priv->hscroll);
|
|
if (priv->vscrollbar_visible)
|
|
clutter_actor_paint (priv->vscroll);
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_pick (ClutterActor *actor,
|
|
const ClutterColor *color)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
|
|
|
|
/* Chain up so we get a bounding box pained (if we are reactive) */
|
|
CLUTTER_ACTOR_CLASS (st_scroll_view_parent_class)->pick (actor, color);
|
|
|
|
if (priv->child)
|
|
clutter_actor_paint (priv->child);
|
|
if (priv->hscrollbar_visible)
|
|
clutter_actor_paint (priv->hscroll);
|
|
if (priv->vscrollbar_visible)
|
|
clutter_actor_paint (priv->vscroll);
|
|
}
|
|
|
|
static double
|
|
get_scrollbar_width (StScrollView *scroll,
|
|
gfloat for_height)
|
|
{
|
|
StScrollViewPrivate *priv = scroll->priv;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll))
|
|
{
|
|
gfloat min_size;
|
|
|
|
clutter_actor_get_preferred_width (CLUTTER_ACTOR (priv->vscroll), for_height,
|
|
&min_size, NULL);
|
|
return min_size;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static double
|
|
get_scrollbar_height (StScrollView *scroll,
|
|
gfloat for_width)
|
|
{
|
|
StScrollViewPrivate *priv = scroll->priv;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll))
|
|
{
|
|
gfloat min_size;
|
|
|
|
clutter_actor_get_preferred_height (CLUTTER_ACTOR (priv->hscroll), for_width,
|
|
&min_size, NULL);
|
|
|
|
return min_size;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_get_preferred_width (ClutterActor *actor,
|
|
gfloat for_height,
|
|
gfloat *min_width_p,
|
|
gfloat *natural_width_p)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
|
|
gboolean account_for_vscrollbar = FALSE;
|
|
gfloat min_width = 0, natural_width;
|
|
gfloat child_min_width, child_natural_width;
|
|
|
|
if (!priv->child)
|
|
return;
|
|
|
|
st_theme_node_adjust_for_height (theme_node, &for_height);
|
|
|
|
clutter_actor_get_preferred_width (priv->child, -1,
|
|
&child_min_width, &child_natural_width);
|
|
|
|
natural_width = child_natural_width;
|
|
|
|
switch (priv->hscrollbar_policy)
|
|
{
|
|
case GTK_POLICY_NEVER:
|
|
min_width = child_min_width;
|
|
break;
|
|
case GTK_POLICY_ALWAYS:
|
|
case GTK_POLICY_AUTOMATIC:
|
|
/* Should theoretically use the min width of the hscrollbar,
|
|
* but that's not cleanly defined at the moment */
|
|
min_width = 0;
|
|
break;
|
|
}
|
|
|
|
switch (priv->vscrollbar_policy)
|
|
{
|
|
case GTK_POLICY_NEVER:
|
|
account_for_vscrollbar = FALSE;
|
|
break;
|
|
case GTK_POLICY_ALWAYS:
|
|
account_for_vscrollbar = !priv->overlay_scrollbars;
|
|
break;
|
|
case GTK_POLICY_AUTOMATIC:
|
|
/* For automatic scrollbars, we always request space for the vertical
|
|
* scrollbar; we won't know whether we actually need one until our
|
|
* height is assigned in allocate().
|
|
*/
|
|
account_for_vscrollbar = !priv->overlay_scrollbars;
|
|
break;
|
|
}
|
|
|
|
if (account_for_vscrollbar)
|
|
{
|
|
float sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), for_height);
|
|
|
|
min_width += sb_width;
|
|
natural_width += sb_width;
|
|
}
|
|
|
|
if (min_width_p)
|
|
*min_width_p = min_width;
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p = natural_width;
|
|
|
|
st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_get_preferred_height (ClutterActor *actor,
|
|
gfloat for_width,
|
|
gfloat *min_height_p,
|
|
gfloat *natural_height_p)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
|
|
gboolean account_for_hscrollbar = FALSE;
|
|
gfloat min_height = 0, natural_height;
|
|
gfloat child_min_height, child_natural_height;
|
|
gfloat child_min_width;
|
|
gfloat sb_width;
|
|
|
|
if (!priv->child)
|
|
return;
|
|
|
|
st_theme_node_adjust_for_width (theme_node, &for_width);
|
|
|
|
clutter_actor_get_preferred_width (priv->child, -1,
|
|
&child_min_width, NULL);
|
|
|
|
if (min_height_p)
|
|
*min_height_p = 0;
|
|
|
|
sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1);
|
|
|
|
switch (priv->vscrollbar_policy)
|
|
{
|
|
case GTK_POLICY_NEVER:
|
|
break;
|
|
case GTK_POLICY_ALWAYS:
|
|
case GTK_POLICY_AUTOMATIC:
|
|
/* We've requested space for the scrollbar, subtract it back out */
|
|
for_width -= sb_width;
|
|
break;
|
|
}
|
|
|
|
switch (priv->hscrollbar_policy)
|
|
{
|
|
case GTK_POLICY_NEVER:
|
|
account_for_hscrollbar = FALSE;
|
|
break;
|
|
case GTK_POLICY_ALWAYS:
|
|
account_for_hscrollbar = !priv->overlay_scrollbars;
|
|
break;
|
|
case GTK_POLICY_AUTOMATIC:
|
|
/* For automatic scrollbars, we always request space for the horizontal
|
|
* scrollbar; we won't know whether we actually need one until our
|
|
* width is assigned in allocate().
|
|
*/
|
|
account_for_hscrollbar = !priv->overlay_scrollbars;
|
|
break;
|
|
}
|
|
|
|
clutter_actor_get_preferred_height (priv->child, for_width,
|
|
&child_min_height, &child_natural_height);
|
|
|
|
natural_height = child_natural_height;
|
|
|
|
switch (priv->vscrollbar_policy)
|
|
{
|
|
case GTK_POLICY_NEVER:
|
|
min_height = child_min_height;
|
|
break;
|
|
case GTK_POLICY_ALWAYS:
|
|
case GTK_POLICY_AUTOMATIC:
|
|
/* Should theoretically use the min height of the vscrollbar,
|
|
* but that's not cleanly defined at the moment */
|
|
min_height = 0;
|
|
break;
|
|
}
|
|
|
|
if (account_for_hscrollbar)
|
|
{
|
|
float sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), for_width);
|
|
|
|
min_height += sb_height;
|
|
natural_height += sb_height;
|
|
}
|
|
|
|
if (min_height_p)
|
|
*min_height_p = min_height;
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = natural_height;
|
|
|
|
st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_allocate (ClutterActor *actor,
|
|
const ClutterActorBox *box,
|
|
ClutterAllocationFlags flags)
|
|
{
|
|
ClutterActorBox content_box, child_box;
|
|
gfloat avail_width, avail_height, sb_width, sb_height;
|
|
gboolean hscrollbar_visible, vscrollbar_visible;
|
|
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (actor)->priv;
|
|
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
|
|
|
|
clutter_actor_set_allocation (actor, box, flags);
|
|
|
|
st_theme_node_get_content_box (theme_node, box, &content_box);
|
|
|
|
avail_width = content_box.x2 - content_box.x1;
|
|
avail_height = content_box.y2 - content_box.y1;
|
|
|
|
if (clutter_actor_get_request_mode (actor) == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH)
|
|
{
|
|
sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), -1);
|
|
sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), sb_width);
|
|
}
|
|
else
|
|
{
|
|
sb_height = get_scrollbar_height (ST_SCROLL_VIEW (actor), -1);
|
|
sb_width = get_scrollbar_width (ST_SCROLL_VIEW (actor), sb_height);
|
|
}
|
|
|
|
/* Determine what scrollbars are visible. The basic idea of the
|
|
* handling of an automatic scrollbars is that we start off with the
|
|
* assumption that we don't need any scrollbars, see if that works,
|
|
* and if not add horizontal and vertical scrollbars until we are no
|
|
* longer overflowing.
|
|
*/
|
|
if (priv->child)
|
|
{
|
|
gfloat child_min_width;
|
|
gfloat child_min_height;
|
|
|
|
clutter_actor_get_preferred_width (priv->child, -1,
|
|
&child_min_width, NULL);
|
|
|
|
if (priv->vscrollbar_policy == GTK_POLICY_AUTOMATIC)
|
|
{
|
|
if (priv->hscrollbar_policy == GTK_POLICY_AUTOMATIC)
|
|
{
|
|
/* Pass one, try without a vertical scrollbar */
|
|
clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL);
|
|
vscrollbar_visible = child_min_height > avail_height;
|
|
hscrollbar_visible = child_min_width > avail_width - (vscrollbar_visible ? sb_width : 0);
|
|
vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0);
|
|
|
|
/* Pass two - if we needed a vertical scrollbar, get a new preferred height */
|
|
if (vscrollbar_visible)
|
|
{
|
|
clutter_actor_get_preferred_height (priv->child, MAX (avail_width - sb_width, 0),
|
|
&child_min_height, NULL);
|
|
hscrollbar_visible = child_min_width > avail_width - sb_width;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hscrollbar_visible = priv->hscrollbar_policy != GTK_POLICY_NEVER;
|
|
|
|
/* try without a vertical scrollbar */
|
|
clutter_actor_get_preferred_height (priv->child, avail_width, &child_min_height, NULL);
|
|
vscrollbar_visible = child_min_height > avail_height - (hscrollbar_visible ? sb_height : 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vscrollbar_visible = priv->vscrollbar_policy != GTK_POLICY_NEVER;
|
|
|
|
if (priv->hscrollbar_policy == GTK_POLICY_AUTOMATIC)
|
|
hscrollbar_visible = child_min_width > avail_height - (vscrollbar_visible ? 0 : sb_width);
|
|
else
|
|
hscrollbar_visible = priv->hscrollbar_policy != GTK_POLICY_NEVER;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hscrollbar_visible = priv->hscrollbar_policy != GTK_POLICY_NEVER;
|
|
vscrollbar_visible = priv->vscrollbar_policy != GTK_POLICY_NEVER;
|
|
}
|
|
|
|
/* Whether or not we show the scrollbars, if the scrollbars are visible
|
|
* actors, we need to give them some allocation, so we unconditionally
|
|
* give them the "right" allocation; that might overlap the child when
|
|
* the scrollbars are not visible, but it doesn't matter because we
|
|
* don't include them in pick or paint.
|
|
*/
|
|
|
|
/* Vertical scrollbar */
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll))
|
|
{
|
|
if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
|
|
{
|
|
child_box.x1 = content_box.x1;
|
|
child_box.x2 = content_box.x1 + sb_width;
|
|
}
|
|
else
|
|
{
|
|
child_box.x1 = content_box.x2 - sb_width;
|
|
child_box.x2 = content_box.x2;
|
|
}
|
|
child_box.y1 = content_box.y1;
|
|
child_box.y2 = content_box.y2 - (hscrollbar_visible ? sb_height : 0);
|
|
|
|
clutter_actor_allocate (priv->vscroll, &child_box, flags);
|
|
}
|
|
|
|
/* Horizontal scrollbar */
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (priv->hscroll))
|
|
{
|
|
if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
|
|
{
|
|
child_box.x1 = content_box.x1 + (vscrollbar_visible ? sb_width : 0);
|
|
child_box.x2 = content_box.x2;
|
|
}
|
|
else
|
|
{
|
|
child_box.x1 = content_box.x1;
|
|
child_box.x2 = content_box.x2 - (vscrollbar_visible ? sb_width : 0);
|
|
}
|
|
child_box.y1 = content_box.y2 - sb_height;
|
|
child_box.y2 = content_box.y2;
|
|
|
|
clutter_actor_allocate (priv->hscroll, &child_box, flags);
|
|
}
|
|
|
|
/* In case the scrollbar policy is NEVER or scrollbars should be
|
|
* overlayed, we don't trim the content box allocation by the
|
|
* scrollbar size.
|
|
* Fold this into the scrollbar sizes to simplify the rest of the
|
|
* computations.
|
|
*/
|
|
if (priv->hscrollbar_policy == GTK_POLICY_NEVER || priv->overlay_scrollbars)
|
|
sb_height = 0;
|
|
if (priv->vscrollbar_policy == GTK_POLICY_NEVER || priv->overlay_scrollbars)
|
|
sb_width = 0;
|
|
|
|
/* Child */
|
|
if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
|
|
{
|
|
child_box.x1 = content_box.x1 + sb_width;
|
|
child_box.x2 = content_box.x2;
|
|
}
|
|
else
|
|
{
|
|
child_box.x1 = content_box.x1;
|
|
child_box.x2 = content_box.x2 - sb_width;
|
|
}
|
|
child_box.y1 = content_box.y1;
|
|
child_box.y2 = content_box.y2 - sb_height;
|
|
|
|
if (priv->child)
|
|
clutter_actor_allocate (priv->child, &child_box, flags);
|
|
|
|
if (priv->hscrollbar_visible != hscrollbar_visible)
|
|
{
|
|
g_object_freeze_notify (G_OBJECT (actor));
|
|
priv->hscrollbar_visible = hscrollbar_visible;
|
|
g_object_notify (G_OBJECT (actor), "hscrollbar-visible");
|
|
g_object_thaw_notify (G_OBJECT (actor));
|
|
}
|
|
|
|
if (priv->vscrollbar_visible != vscrollbar_visible)
|
|
{
|
|
g_object_freeze_notify (G_OBJECT (actor));
|
|
priv->vscrollbar_visible = vscrollbar_visible;
|
|
g_object_notify (G_OBJECT (actor), "vscrollbar-visible");
|
|
g_object_thaw_notify (G_OBJECT (actor));
|
|
}
|
|
|
|
}
|
|
|
|
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 void
|
|
st_scroll_view_style_changed (StWidget *widget)
|
|
{
|
|
StScrollView *self = ST_SCROLL_VIEW (widget);
|
|
StScrollViewPrivate *priv = self->priv;
|
|
|
|
StThemeNode *theme_node = st_widget_get_theme_node (widget);
|
|
gdouble vfade_offset = st_theme_node_get_length (theme_node, "-st-vfade-offset");
|
|
gdouble hfade_offset = st_theme_node_get_length (theme_node, "-st-hfade-offset");
|
|
st_scroll_view_update_fade_effect (self, vfade_offset, hfade_offset);
|
|
|
|
st_widget_style_changed (ST_WIDGET (priv->hscroll));
|
|
st_widget_style_changed (ST_WIDGET (priv->vscroll));
|
|
|
|
ST_WIDGET_CLASS (st_scroll_view_parent_class)->style_changed (widget);
|
|
}
|
|
|
|
static gboolean
|
|
st_scroll_view_scroll_event (ClutterActor *self,
|
|
ClutterScrollEvent *event)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv;
|
|
|
|
/* don't handle scroll events if requested not to */
|
|
if (!priv->mouse_scroll)
|
|
return FALSE;
|
|
|
|
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);
|
|
st_adjustment_adjust_for_scroll_event (priv->hadjustment, delta_x);
|
|
st_adjustment_adjust_for_scroll_event (priv->vadjustment, delta_y);
|
|
}
|
|
break;
|
|
case CLUTTER_SCROLL_UP:
|
|
case CLUTTER_SCROLL_DOWN:
|
|
adjust_with_direction (priv->vadjustment, event->direction);
|
|
break;
|
|
case CLUTTER_SCROLL_LEFT:
|
|
case CLUTTER_SCROLL_RIGHT:
|
|
adjust_with_direction (priv->hadjustment, event->direction);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_class_init (StScrollViewClass *klass)
|
|
{
|
|
GParamSpec *pspec;
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
|
|
|
|
g_type_class_add_private (klass, sizeof (StScrollViewPrivate));
|
|
|
|
object_class->get_property = st_scroll_view_get_property;
|
|
object_class->set_property = st_scroll_view_set_property;
|
|
object_class->dispose = st_scroll_view_dispose;
|
|
|
|
actor_class->paint = st_scroll_view_paint;
|
|
actor_class->pick = st_scroll_view_pick;
|
|
actor_class->get_preferred_width = st_scroll_view_get_preferred_width;
|
|
actor_class->get_preferred_height = st_scroll_view_get_preferred_height;
|
|
actor_class->allocate = st_scroll_view_allocate;
|
|
actor_class->scroll_event = st_scroll_view_scroll_event;
|
|
|
|
widget_class->style_changed = st_scroll_view_style_changed;
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_HSCROLL,
|
|
g_param_spec_object ("hscroll",
|
|
"StScrollBar",
|
|
"Horizontal scroll indicator",
|
|
ST_TYPE_SCROLL_BAR,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_VSCROLL,
|
|
g_param_spec_object ("vscroll",
|
|
"StScrollBar",
|
|
"Vertical scroll indicator",
|
|
ST_TYPE_SCROLL_BAR,
|
|
G_PARAM_READABLE));
|
|
|
|
|
|
pspec = g_param_spec_enum ("vscrollbar-policy",
|
|
"Vertical Scrollbar Policy",
|
|
"When the vertical scrollbar is displayed",
|
|
GTK_TYPE_POLICY_TYPE,
|
|
GTK_POLICY_AUTOMATIC,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_VSCROLLBAR_POLICY, pspec);
|
|
|
|
pspec = g_param_spec_enum ("hscrollbar-policy",
|
|
"Horizontal Scrollbar Policy",
|
|
"When the horizontal scrollbar is displayed",
|
|
GTK_TYPE_POLICY_TYPE,
|
|
GTK_POLICY_AUTOMATIC,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_HSCROLLBAR_POLICY, pspec);
|
|
|
|
pspec = g_param_spec_boolean ("hscrollbar-visible",
|
|
"Horizontal Scrollbar Visibility",
|
|
"Whether the horizontal scrollbar is visible",
|
|
TRUE,
|
|
G_PARAM_READABLE);
|
|
g_object_class_install_property (object_class, PROP_HSCROLLBAR_VISIBLE, pspec);
|
|
|
|
pspec = g_param_spec_boolean ("vscrollbar-visible",
|
|
"Vertical Scrollbar Visibility",
|
|
"Whether the vertical scrollbar is visible",
|
|
TRUE,
|
|
G_PARAM_READABLE);
|
|
g_object_class_install_property (object_class, PROP_VSCROLLBAR_VISIBLE, pspec);
|
|
|
|
pspec = g_param_spec_boolean ("enable-mouse-scrolling",
|
|
"Enable Mouse Scrolling",
|
|
"Enable automatic mouse wheel scrolling",
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class,
|
|
PROP_MOUSE_SCROLL,
|
|
pspec);
|
|
|
|
pspec = g_param_spec_boolean ("overlay-scrollbars",
|
|
"Use Overlay Scrollbars",
|
|
"Overlay scrollbars over the content",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class,
|
|
PROP_OVERLAY_SCROLLBARS,
|
|
pspec);
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_init (StScrollView *self)
|
|
{
|
|
StScrollViewPrivate *priv = self->priv = SCROLL_VIEW_PRIVATE (self);
|
|
|
|
priv->hscrollbar_policy = GTK_POLICY_AUTOMATIC;
|
|
priv->vscrollbar_policy = GTK_POLICY_AUTOMATIC;
|
|
|
|
priv->hadjustment = g_object_new (ST_TYPE_ADJUSTMENT, NULL);
|
|
priv->hscroll = g_object_new (ST_TYPE_SCROLL_BAR,
|
|
"adjustment", priv->hadjustment,
|
|
"vertical", FALSE,
|
|
NULL);
|
|
|
|
priv->vadjustment = g_object_new (ST_TYPE_ADJUSTMENT, NULL);
|
|
priv->vscroll = g_object_new (ST_TYPE_SCROLL_BAR,
|
|
"adjustment", priv->vadjustment,
|
|
"vertical", TRUE,
|
|
NULL);
|
|
|
|
clutter_actor_add_child (CLUTTER_ACTOR (self), priv->hscroll);
|
|
clutter_actor_add_child (CLUTTER_ACTOR (self), priv->vscroll);
|
|
|
|
/* mouse scroll is enabled by default, so we also need to be reactive */
|
|
priv->mouse_scroll = TRUE;
|
|
g_object_set (G_OBJECT (self), "reactive", TRUE, NULL);
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_add (ClutterContainer *container,
|
|
ClutterActor *actor)
|
|
{
|
|
StScrollView *self = ST_SCROLL_VIEW (container);
|
|
StScrollViewPrivate *priv = self->priv;
|
|
|
|
if (ST_IS_SCROLLABLE (actor))
|
|
{
|
|
priv->child = actor;
|
|
|
|
/* chain up to StBin::add() */
|
|
st_scroll_view_parent_iface->add (container, actor);
|
|
|
|
st_scrollable_set_adjustments (ST_SCROLLABLE (actor),
|
|
priv->hadjustment, priv->vadjustment);
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Attempting to add an actor of type %s to "
|
|
"a StScrollView, but the actor does "
|
|
"not implement StScrollable.",
|
|
g_type_name (G_OBJECT_TYPE (actor)));
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_remove (ClutterContainer *container,
|
|
ClutterActor *actor)
|
|
{
|
|
StScrollView *self = ST_SCROLL_VIEW (container);
|
|
StScrollViewPrivate *priv = self->priv;
|
|
|
|
if (actor == priv->child)
|
|
{
|
|
g_object_ref (priv->child);
|
|
|
|
/* chain up to StBin::remove() */
|
|
st_scroll_view_parent_iface->remove (container, actor);
|
|
|
|
st_scrollable_set_adjustments (ST_SCROLLABLE (priv->child),
|
|
NULL, NULL);
|
|
|
|
g_object_unref (priv->child);
|
|
priv->child = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (actor == priv->vscroll)
|
|
priv->vscroll = NULL;
|
|
else if (actor == priv->hscroll)
|
|
priv->hscroll = NULL;
|
|
else
|
|
g_assert ("Unknown child removed from StScrollView");
|
|
|
|
clutter_actor_remove_child (CLUTTER_ACTOR (container), actor);
|
|
}
|
|
}
|
|
|
|
static void
|
|
st_scroll_view_foreach_with_internals (ClutterContainer *container,
|
|
ClutterCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
StScrollViewPrivate *priv = ST_SCROLL_VIEW (container)->priv;
|
|
|
|
if (priv->child != NULL)
|
|
callback (priv->child, user_data);
|
|
|
|
if (priv->hscroll != NULL)
|
|
callback (priv->hscroll, user_data);
|
|
|
|
if (priv->vscroll != NULL)
|
|
callback (priv->vscroll, user_data);
|
|
}
|
|
|
|
static void
|
|
clutter_container_iface_init (ClutterContainerIface *iface)
|
|
{
|
|
/* store a pointer to the StBin implementation of
|
|
* ClutterContainer so that we can chain up when
|
|
* overriding the methods
|
|
*/
|
|
st_scroll_view_parent_iface = g_type_interface_peek_parent (iface);
|
|
|
|
iface->add = st_scroll_view_add;
|
|
iface->remove = st_scroll_view_remove;
|
|
iface->foreach_with_internals = st_scroll_view_foreach_with_internals;
|
|
}
|
|
|
|
StWidget *
|
|
st_scroll_view_new (void)
|
|
{
|
|
return g_object_new (ST_TYPE_SCROLL_VIEW, NULL);
|
|
}
|
|
|
|
/**
|
|
* st_scroll_view_get_hscroll_bar:
|
|
* @scroll: a #StScrollView
|
|
*
|
|
* Gets the horizontal scrollbar of the scrollbiew
|
|
*
|
|
* Return value: (transfer none): the horizontal #StScrollBar
|
|
*/
|
|
ClutterActor *
|
|
st_scroll_view_get_hscroll_bar (StScrollView *scroll)
|
|
{
|
|
g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL);
|
|
|
|
return scroll->priv->hscroll;
|
|
}
|
|
|
|
/**
|
|
* st_scroll_view_get_vscroll_bar:
|
|
* @scroll: a #StScrollView
|
|
*
|
|
* Gets the vertical scrollbar of the scrollbiew
|
|
*
|
|
* Return value: (transfer none): the vertical #StScrollBar
|
|
*/
|
|
ClutterActor *
|
|
st_scroll_view_get_vscroll_bar (StScrollView *scroll)
|
|
{
|
|
g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), NULL);
|
|
|
|
return scroll->priv->vscroll;
|
|
}
|
|
|
|
gfloat
|
|
st_scroll_view_get_column_size (StScrollView *scroll)
|
|
{
|
|
gdouble column_size;
|
|
|
|
g_return_val_if_fail (scroll, 0);
|
|
|
|
g_object_get (scroll->priv->hadjustment,
|
|
"step-increment", &column_size,
|
|
NULL);
|
|
|
|
return column_size;
|
|
}
|
|
|
|
void
|
|
st_scroll_view_set_column_size (StScrollView *scroll,
|
|
gfloat column_size)
|
|
{
|
|
g_return_if_fail (scroll);
|
|
|
|
if (column_size < 0)
|
|
{
|
|
scroll->priv->column_size_set = FALSE;
|
|
scroll->priv->column_size = -1;
|
|
}
|
|
else
|
|
{
|
|
scroll->priv->column_size_set = TRUE;
|
|
scroll->priv->column_size = column_size;
|
|
|
|
g_object_set (scroll->priv->hadjustment,
|
|
"step-increment", (gdouble) scroll->priv->column_size,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
gfloat
|
|
st_scroll_view_get_row_size (StScrollView *scroll)
|
|
{
|
|
gdouble row_size;
|
|
|
|
g_return_val_if_fail (scroll, 0);
|
|
|
|
g_object_get (scroll->priv->vadjustment,
|
|
"step-increment", &row_size,
|
|
NULL);
|
|
|
|
return row_size;
|
|
}
|
|
|
|
void
|
|
st_scroll_view_set_row_size (StScrollView *scroll,
|
|
gfloat row_size)
|
|
{
|
|
g_return_if_fail (scroll);
|
|
|
|
if (row_size < 0)
|
|
{
|
|
scroll->priv->row_size_set = FALSE;
|
|
scroll->priv->row_size = -1;
|
|
}
|
|
else
|
|
{
|
|
scroll->priv->row_size_set = TRUE;
|
|
scroll->priv->row_size = row_size;
|
|
|
|
g_object_set (scroll->priv->vadjustment,
|
|
"step-increment", (gdouble) scroll->priv->row_size,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
st_scroll_view_set_mouse_scrolling (StScrollView *scroll,
|
|
gboolean enabled)
|
|
{
|
|
StScrollViewPrivate *priv;
|
|
|
|
g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
|
|
|
|
priv = ST_SCROLL_VIEW (scroll)->priv;
|
|
|
|
if (priv->mouse_scroll != enabled)
|
|
{
|
|
priv->mouse_scroll = enabled;
|
|
|
|
/* make sure we can receive mouse wheel events */
|
|
if (enabled)
|
|
clutter_actor_set_reactive ((ClutterActor *) scroll, TRUE);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
st_scroll_view_get_mouse_scrolling (StScrollView *scroll)
|
|
{
|
|
StScrollViewPrivate *priv;
|
|
|
|
g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
|
|
|
|
priv = ST_SCROLL_VIEW (scroll)->priv;
|
|
|
|
return priv->mouse_scroll;
|
|
}
|
|
|
|
/**
|
|
* st_scroll_view_set_overlay_scrollbars:
|
|
* @scroll: A #StScrollView
|
|
* @enabled: Whether to enable overlay scrollbars
|
|
*
|
|
* Sets whether scrollbars are painted on top of the content.
|
|
*/
|
|
void
|
|
st_scroll_view_set_overlay_scrollbars (StScrollView *scroll,
|
|
gboolean enabled)
|
|
{
|
|
StScrollViewPrivate *priv;
|
|
|
|
g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
|
|
|
|
priv = ST_SCROLL_VIEW (scroll)->priv;
|
|
|
|
if (priv->overlay_scrollbars != enabled)
|
|
{
|
|
priv->overlay_scrollbars = enabled;
|
|
g_object_notify (G_OBJECT (scroll), "overlay-scrollbars");
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* st_scroll_view_get_overlay_scrollbars:
|
|
* @scroll: A #StScrollView
|
|
*
|
|
* Gets the value set by st_scroll_view_set_overlay_scrollbars().
|
|
*/
|
|
gboolean
|
|
st_scroll_view_get_overlay_scrollbars (StScrollView *scroll)
|
|
{
|
|
StScrollViewPrivate *priv;
|
|
|
|
g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
|
|
|
|
priv = ST_SCROLL_VIEW (scroll)->priv;
|
|
|
|
return priv->overlay_scrollbars;
|
|
}
|
|
|
|
/**
|
|
* st_scroll_view_set_policy:
|
|
* @scroll: A #StScrollView
|
|
* @hscroll: Whether to enable horizontal scrolling
|
|
* @vscroll: Whether to enable vertical scrolling
|
|
*
|
|
* Set the scroll policy.
|
|
*/
|
|
void
|
|
st_scroll_view_set_policy (StScrollView *scroll,
|
|
GtkPolicyType hscroll,
|
|
GtkPolicyType vscroll)
|
|
{
|
|
StScrollViewPrivate *priv;
|
|
|
|
g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
|
|
|
|
priv = ST_SCROLL_VIEW (scroll)->priv;
|
|
|
|
if (priv->hscrollbar_policy == hscroll && priv->vscrollbar_policy == vscroll)
|
|
return;
|
|
|
|
g_object_freeze_notify ((GObject *) scroll);
|
|
|
|
if (priv->hscrollbar_policy != hscroll)
|
|
{
|
|
priv->hscrollbar_policy = hscroll;
|
|
g_object_notify ((GObject *) scroll, "hscrollbar-policy");
|
|
}
|
|
|
|
if (priv->vscrollbar_policy != vscroll)
|
|
{
|
|
priv->vscrollbar_policy = vscroll;
|
|
g_object_notify ((GObject *) scroll, "vscrollbar-policy");
|
|
}
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (scroll));
|
|
|
|
g_object_thaw_notify ((GObject *) scroll);
|
|
}
|