[StWidget] add (optional) hover tracking

If track-hover is set, update the hover property automatically, and
the "hover" pseudo class to match, as StClickable used to do. (Remove
the corresponding code in StClickable). Tweak the tooltip handling to
use track-hover, which also makes it slightly more reliable in the
presence of reactive children, etc.
This commit is contained in:
Dan Winship 2010-03-19 13:47:34 -04:00
parent 909b5ec43c
commit f9e4385e02
5 changed files with 257 additions and 67 deletions

View File

@ -536,6 +536,8 @@ AppWellIcon.prototype = {
},
_onMenuPoppedDown: function() {
this.actor.sync_hover();
if (this._didActivateWindow)
return;
if (!this._setWindowSelection)

View File

@ -484,10 +484,7 @@ NewWorkspaceArea.prototype = {
},
setStyle: function(isHover) {
if (isHover)
this._child1.add_style_pseudo_class('hover');
else
this._child1.remove_style_pseudo_class('hover');
this._child1.set_hover(isHover);
}
};

View File

@ -16,7 +16,6 @@ G_DEFINE_TYPE (StClickable, st_clickable, ST_TYPE_BIN);
struct _StClickablePrivate {
gboolean active;
gboolean held;
gboolean hover;
gboolean pressed;
guint initiating_button;
@ -33,7 +32,6 @@ enum {
PROP_0,
PROP_ACTIVE,
PROP_HOVER,
PROP_PRESSED,
};
@ -46,11 +44,6 @@ sync_pseudo_class (StClickable *self)
st_widget_add_style_pseudo_class (ST_WIDGET (self), "pressed");
else
st_widget_remove_style_pseudo_class (ST_WIDGET (self), "pressed");
if (self->priv->hover)
st_widget_add_style_pseudo_class (ST_WIDGET (self), "hover");
else
st_widget_remove_style_pseudo_class (ST_WIDGET (self), "hover");
}
static void
@ -64,17 +57,6 @@ set_active (StClickable *self,
g_object_notify (G_OBJECT (self), "active");
}
static void
set_hover (StClickable *self,
gboolean hover)
{
if (self->priv->hover == hover)
return;
self->priv->hover = hover;
sync_pseudo_class (self);
g_object_notify (G_OBJECT (self), "hover");
}
static void
set_pressed (StClickable *self,
gboolean pressed)
@ -102,21 +84,18 @@ st_clickable_enter_event (ClutterActor *actor,
ClutterCrossingEvent *event)
{
StClickable *self = ST_CLICKABLE (actor);
if (st_clickable_contains (self, event->related))
return TRUE;
if (!st_clickable_contains (self, event->source))
return TRUE;
gboolean result;
g_object_freeze_notify (G_OBJECT (actor));
if (self->priv->held)
set_pressed (self, TRUE);
set_hover (self, TRUE);
result = CLUTTER_ACTOR_CLASS (st_clickable_parent_class)->enter_event (actor, event);
/* We can't just assume get_hover() is TRUE; see st_widget_enter(). */
set_pressed (self, self->priv->held && st_widget_get_hover (ST_WIDGET (actor)));
g_object_thaw_notify (G_OBJECT (actor));
return TRUE;
return result;
}
static gboolean
@ -124,14 +103,18 @@ st_clickable_leave_event (ClutterActor *actor,
ClutterCrossingEvent *event)
{
StClickable *self = ST_CLICKABLE (actor);
gboolean result;
if (st_clickable_contains (self, event->related))
return TRUE;
g_object_freeze_notify (G_OBJECT (actor));
set_hover (self, FALSE);
set_pressed (self, FALSE);
result = CLUTTER_ACTOR_CLASS (st_clickable_parent_class)->leave_event (actor, event);
return TRUE;
/* As above, we can't just assume get_hover() is FALSE. */
set_pressed (self, self->priv->held && st_widget_get_hover (ST_WIDGET (actor)));
g_object_thaw_notify (G_OBJECT (actor));
return result;
}
static gboolean
@ -243,9 +226,6 @@ st_clickable_get_property (GObject *object,
case PROP_PRESSED:
g_value_set_boolean (value, self->priv->pressed);
break;
case PROP_HOVER:
g_value_set_boolean (value, self->priv->hover);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -297,20 +277,6 @@ st_clickable_class_init (StClickableClass *klass)
FALSE,
G_PARAM_READWRITE));
/**
* StClickable:hover
*
* This property tracks whether the mouse is over the button; note this
* state is independent of whether the button is pressed.
*/
g_object_class_install_property (gobject_class,
PROP_HOVER,
g_param_spec_boolean ("hover",
"Hovering state",
"Whether the mouse is over the button",
FALSE,
G_PARAM_READABLE));
/**
* StClickable:pressed
*
@ -333,4 +299,5 @@ st_clickable_init (StClickable *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ST_TYPE_CLICKABLE,
StClickablePrivate);
st_widget_set_track_hover (ST_WIDGET (self), TRUE);
}

View File

@ -76,6 +76,8 @@ struct _StWidgetPrivate
gboolean is_style_dirty : 1;
gboolean draw_bg_color : 1;
gboolean draw_border_internal : 1;
gboolean track_hover : 1;
gboolean hover : 1;
StTooltip *tooltip;
@ -101,11 +103,11 @@ enum
PROP_PSEUDO_CLASS,
PROP_STYLE_CLASS,
PROP_STYLE,
PROP_STYLABLE,
PROP_HAS_TOOLTIP,
PROP_TOOLTIP_TEXT
PROP_TOOLTIP_TEXT,
PROP_TRACK_HOVER,
PROP_HOVER
};
enum
@ -167,6 +169,14 @@ st_widget_set_property (GObject *gobject,
st_widget_set_tooltip_text (actor, g_value_get_string (value));
break;
case PROP_TRACK_HOVER:
st_widget_set_track_hover (actor, g_value_get_boolean (value));
break;
case PROP_HOVER:
st_widget_set_hover (actor, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
@ -212,6 +222,14 @@ st_widget_get_property (GObject *gobject,
g_value_set_string (value, st_widget_get_tooltip_text (actor));
break;
case PROP_TRACK_HOVER:
g_value_set_boolean (value, priv->track_hover);
break;
case PROP_HOVER:
g_value_set_boolean (value, priv->hover);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
@ -1125,15 +1143,34 @@ st_widget_get_theme_node (StWidget *widget)
return priv->theme_node;
}
static gboolean
actor_contains (ClutterActor *widget,
ClutterActor *other)
{
while (other != NULL && other != widget)
other = clutter_actor_get_parent (other);
return other != NULL;
}
static gboolean
st_widget_enter (ClutterActor *actor,
ClutterCrossingEvent *event)
{
StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
if (priv->has_tooltip)
st_widget_show_tooltip ((StWidget*) actor);
if (priv->track_hover)
{
if (actor_contains (actor, event->source))
st_widget_set_hover (ST_WIDGET (actor), TRUE);
else
{
/* The widget has a grab and is being told about an
* enter-event outside its hierarchy. Hopefully we already
* got a leave-event, but if not, handle it now.
*/
st_widget_set_hover (ST_WIDGET (actor), FALSE);
}
}
if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event)
return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->enter_event (actor, event);
@ -1147,8 +1184,11 @@ st_widget_leave (ClutterActor *actor,
{
StWidgetPrivate *priv = ST_WIDGET (actor)->priv;
if (priv->has_tooltip)
st_tooltip_hide (priv->tooltip);
if (priv->track_hover)
{
if (!actor_contains (actor, event->related))
st_widget_set_hover (ST_WIDGET (actor), FALSE);
}
if (CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event)
return CLUTTER_ACTOR_CLASS (st_widget_parent_class)->leave_event (actor, event);
@ -1267,8 +1307,9 @@ st_widget_class_init (StWidgetClass *klass)
/**
* StWidget:has-tooltip:
*
* Determines whether the widget has a tooltip. If set to TRUE, causes the
* widget to monitor enter and leave events (i.e. sets the widget reactive).
* Determines whether the widget has a tooltip. If set to %TRUE, causes the
* widget to monitor hover state (i.e. sets #ClutterActor:reactive and
* #StWidget:track-hover).
*/
pspec = g_param_spec_boolean ("has-tooltip",
"Has Tooltip",
@ -1292,6 +1333,40 @@ st_widget_class_init (StWidgetClass *klass)
ST_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_TOOLTIP_TEXT, pspec);
/**
* StWidget:track-hover:
*
* Determines whether the widget tracks pointer hover state. If
* %TRUE (and the widget is visible and reactive), the
* #StWidget:hover property and "hover" style pseudo class will be
* adjusted automatically as the pointer moves in and out of the
* widget.
*/
pspec = g_param_spec_boolean ("track-hover",
"Track hover",
"Determines whether the widget tracks hover state",
FALSE,
ST_PARAM_READWRITE);
g_object_class_install_property (gobject_class,
PROP_TRACK_HOVER,
pspec);
/**
* StWidget:hover:
*
* Whether or not the pointer is currently hovering over the widget. This is
* only tracked automatically if #StWidget:track-hover is %TRUE, but you can
* adjust it manually in any case.
*/
pspec = g_param_spec_boolean ("hover",
"Hover",
"Whether the pointer is hovering over the widget",
FALSE,
ST_PARAM_READWRITE);
g_object_class_install_property (gobject_class,
PROP_HOVER,
pspec);
/**
* StWidget::style-changed:
*
@ -1794,10 +1869,10 @@ st_widget_set_direction (StWidget *self, StTextDirection dir)
*
* Enables tooltip support on the #StWidget.
*
* Note that setting has-tooltip to %TRUE will cause the widget to be set
* reactive. If you no longer need tooltip support and do not need the widget
* to be reactive, you need to set ClutterActor::reactive to FALSE.
*
* Note that setting has-tooltip to %TRUE will cause
* #ClutterActor:reactive and #StWidget:track-hover to be set %TRUE as
* well, but you must clear these flags yourself (if appropriate) when
* setting it %FALSE.
*/
void
st_widget_set_has_tooltip (StWidget *widget,
@ -1814,6 +1889,7 @@ st_widget_set_has_tooltip (StWidget *widget,
if (has_tooltip)
{
clutter_actor_set_reactive ((ClutterActor*) widget, TRUE);
st_widget_set_track_hover (widget, TRUE);
if (!priv->tooltip)
{
@ -1948,3 +2024,143 @@ st_widget_hide_tooltip (StWidget *widget)
if (widget->priv->tooltip)
st_tooltip_hide (widget->priv->tooltip);
}
/**
* st_widget_set_track_hover:
* @widget: A #StWidget
* @track_hover: %TRUE if the widget should track the pointer hover state
*
* Enables hover tracking on the #StWidget.
*
* If hover tracking is enabled, and the widget is visible and
* reactive, then @widget's #StWidget:hover property will be updated
* automatically to reflect whether the pointer is in @widget (or one
* of its children), and @widget's #StWidget:pseudo-class will have
* the "hover" class added and removed from it accordingly.
*
* Note that currently it is not possible to correctly track the hover
* state when another actor has a pointer grab. You can use
* st_widget_sync_hover() to update the property manually in this
* case.
*/
void
st_widget_set_track_hover (StWidget *widget,
gboolean track_hover)
{
StWidgetPrivate *priv;
g_return_if_fail (ST_IS_WIDGET (widget));
priv = widget->priv;
if (priv->track_hover != track_hover)
{
priv->track_hover = track_hover;
g_object_notify (G_OBJECT (widget), "track-hover");
if (priv->track_hover)
st_widget_sync_hover (widget);
}
}
/**
* st_widget_get_track_hover:
* @widget: A #StWidget
*
* Returns the current value of the track-hover property. See
* st_tooltip_set_track_hover() for more information.
*
* Returns: current value of track-hover on @widget
*/
gboolean
st_widget_get_track_hover (StWidget *widget)
{
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
return widget->priv->track_hover;
}
/**
* st_widget_set_hover:
* @widget: A #StWidget
* @hover: whether the pointer is hovering over the widget
*
* Sets @widget's hover property and adds or removes "hover" from its
* pseudo class accordingly. If #StWidget:has-tooltip is %TRUE, this
* will also show or hide the tooltip, as appropriate.
*
* If you have set #StWidget:track-hover, you should not need to call
* this directly. You can call st_widget_sync_hover() if the hover
* state might be out of sync due to another actor's pointer grab.
*/
void
st_widget_set_hover (StWidget *widget,
gboolean hover)
{
StWidgetPrivate *priv;
g_return_if_fail (ST_IS_WIDGET (widget));
priv = widget->priv;
if (priv->hover != hover)
{
priv->hover = hover;
if (priv->hover)
{
st_widget_add_style_pseudo_class (widget, "hover");
if (priv->has_tooltip)
st_widget_show_tooltip (widget);
}
else
{
st_widget_remove_style_pseudo_class (widget, "hover");
if (priv->has_tooltip)
st_widget_hide_tooltip (widget);
}
g_object_notify (G_OBJECT (widget), "hover");
}
}
/**
* st_widget_sync_hover:
* @widget: A #StWidget
*
* Sets @widget's hover state according to the current pointer
* position. This can be used to ensure that it is correct after
* (or during) a pointer grab.
*/
void
st_widget_sync_hover (StWidget *widget)
{
ClutterDeviceManager *device_manager;
ClutterInputDevice *pointer;
ClutterActor *actor;
device_manager = clutter_device_manager_get_default ();
pointer = clutter_device_manager_get_core_device (device_manager,
CLUTTER_POINTER_DEVICE);
actor = clutter_input_device_get_pointer_actor (pointer);
while (actor && actor != (ClutterActor *)widget)
actor = clutter_actor_get_parent (actor);
st_widget_set_hover (widget, actor == (ClutterActor *)widget);
}
/**
* st_widget_get_hover:
* @widget: A #StWidget
*
* If #StWidget:track-hover is set, this returns whether the pointer
* is currently over the widget.
*
* Returns: current value of hover on @widget
*/
gboolean
st_widget_get_hover (StWidget *widget)
{
g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
return widget->priv->hover;
}

View File

@ -120,6 +120,14 @@ const gchar* st_widget_get_tooltip_text (StWidget *widg
void st_widget_show_tooltip (StWidget *widget);
void st_widget_hide_tooltip (StWidget *widget);
void st_widget_set_track_hover (StWidget *widget,
gboolean track_hover);
gboolean st_widget_get_track_hover (StWidget *widget);
void st_widget_set_hover (StWidget *widget,
gboolean hover);
void st_widget_sync_hover (StWidget *widget);
gboolean st_widget_get_hover (StWidget *widget);
void st_widget_ensure_style (StWidget *widget);
StTextDirection st_widget_get_default_direction (void);