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);