From f9e4385e02fc55aac5317f68641d4095d90246d6 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Fri, 19 Mar 2010 13:47:34 -0400 Subject: [PATCH] [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. --- js/ui/appDisplay.js | 2 + js/ui/workspacesView.js | 5 +- src/st/st-clickable.c | 65 +++-------- src/st/st-widget.c | 244 +++++++++++++++++++++++++++++++++++++--- src/st/st-widget.h | 8 ++ 5 files changed, 257 insertions(+), 67 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 9ec427f1a..a8f878efa 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -536,6 +536,8 @@ AppWellIcon.prototype = { }, _onMenuPoppedDown: function() { + this.actor.sync_hover(); + if (this._didActivateWindow) return; if (!this._setWindowSelection) diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js index 5712509e3..f8d92dfd3 100644 --- a/js/ui/workspacesView.js +++ b/js/ui/workspacesView.js @@ -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); } }; diff --git a/src/st/st-clickable.c b/src/st/st-clickable.c index ee52e3738..2ff875817 100644 --- a/src/st/st-clickable.c +++ b/src/st/st-clickable.c @@ -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); } diff --git a/src/st/st-widget.c b/src/st/st-widget.c index 8a3b03406..01bee64fb 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -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; +} diff --git a/src/st/st-widget.h b/src/st/st-widget.h index 660c5e584..b6e2766a1 100644 --- a/src/st/st-widget.h +++ b/src/st/st-widget.h @@ -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);