diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 98771dbef..edbd02331 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -264,7 +264,7 @@ StTooltip StLabel { } .panel-corner:active, -.panel-corner:checked, +.panel-corner:overview, .panel-corner:focus { -panel-corner-inner-border-color: rgba(255,255,255,0.8); } @@ -301,7 +301,7 @@ StTooltip StLabel { } .panel-button:active, -.panel-button:checked, +.panel-button:overview, .panel-button:focus { border-image: url("panel-button-border.svg") 10 10 0 2; background-image: url("panel-button-highlight-wide.svg"); diff --git a/js/ui/panel.js b/js/ui/panel.js index d679cd63b..86e83dbfe 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -544,37 +544,41 @@ AppMenuButton.prototype = { Signals.addSignalMethods(AppMenuButton.prototype); -// Activities button. +// Activities button. Because everything else in the top bar is a +// PanelMenu.Button, it simplifies some things to make this be one too. +// We just hack it up to not actually have a menu attached to it. function ActivitiesButton() { this._init.apply(this, arguments); } ActivitiesButton.prototype = { + __proto__: PanelMenu.Button.prototype, + _init: function() { + PanelMenu.Button.prototype._init.call(this, 0.0); + /* Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". */ let label = new St.Label({ text: _("Activities") }); - this.actor = new St.Button({ name: 'panelActivities', - style_class: 'panel-button', - reactive: true, - can_focus: true }); - this.actor.set_child(label); - this.actor._delegate = this; + this.actor.child = label; + this.actor.name = 'panelActivities'; - this.actor.connect('clicked', Lang.bind(this, function(b) { - if (!Main.overview.animationInProgress) { - this._hotCorner.maybeToggleOverviewOnClick(); - return true; - } else { - return false; - } - })); + // Hack up our menu... + this.menu.open = Lang.bind(this, this._onMenuOpenRequest); + this.menu.close = Lang.bind(this, this._onMenuCloseRequest); + this.menu.toggle = Lang.bind(this, this._onMenuToggleRequest); + + this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent)); + this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease)); + this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease)); Main.overview.connect('showing', Lang.bind(this, function() { - this.actor.checked = true; + this.actor.add_style_pseudo_class('overview'); + this._escapeMenuGrab(); })); Main.overview.connect('hiding', Lang.bind(this, function() { - this.actor.checked = false; + this.actor.remove_style_pseudo_class('overview'); + this._escapeMenuGrab(); })); this._hotCorner = null; @@ -595,6 +599,50 @@ ActivitiesButton.prototype = { Lang.bind(this, this._xdndShowOverview, actor)); }, + _escapeMenuGrab: function() { + if (this.menu.isOpen) + this.menu.close(); + }, + + _onCapturedEvent: function(actor, event) { + if (event.type() == Clutter.EventType.BUTTON_PRESS) { + if (!this._hotCorner.shouldToggleOverviewOnClick()) + return true; + } + return false; + }, + + _onMenuOpenRequest: function() { + this.menu.isOpen = true; + this.menu.emit('open-state-changed', true); + }, + + _onMenuCloseRequest: function() { + this.menu.isOpen = false; + this.menu.emit('open-state-changed', false); + }, + + _onMenuToggleRequest: function() { + this.menu.isOpen = !this.menu.isOpen; + this.menu.emit('open-state-changed', this.menu.isOpen); + }, + + _onButtonRelease: function() { + if (this.menu.isOpen) { + this.menu.close(); + Main.overview.toggle(); + } + }, + + _onKeyRelease: function(actor, event) { + let symbol = event.get_key_symbol(); + if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) { + if (this.menu.isOpen) + this.menu.close(); + Main.overview.toggle(); + } + }, + _xdndShowOverview: function(actor) { let [x, y, mask] = global.get_pointer(); let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); @@ -841,9 +889,9 @@ HotCorner.prototype = { }, _onCornerClicked : function() { - if (!Main.overview.animationInProgress) - this.maybeToggleOverviewOnClick(); - return true; + if (this.shouldToggleOverviewOnClick()) + Main.overview.toggle(); + return true; }, _onCornerLeft : function(actor, event) { @@ -862,13 +910,18 @@ HotCorner.prototype = { return false; }, - // Toggles the overview unless this is the first click on the Activities button within the HOT_CORNER_ACTIVATION_TIMEOUT time - // of the hot corner being triggered. This check avoids opening and closing the overview if the user both triggered the hot corner - // and clicked the Activities button. - maybeToggleOverviewOnClick: function() { + // Checks if the Activities button is currently sensitive to + // clicks. The first call to this function within the + // HOT_CORNER_ACTIVATION_TIMEOUT time of the hot corner being + // triggered will return false. This avoids opening and closing + // the overview if the user both triggered the hot corner and + // clicked the Activities button. + shouldToggleOverviewOnClick: function() { + if (Main.overview.animationInProgress) + return false; if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > HOT_CORNER_ACTIVATION_TIMEOUT) - Main.overview.toggle(); - this._activationTime = 0; + return true; + return false; } } @@ -980,6 +1033,10 @@ Panel.prototype = { this._activities = this.button = this._activitiesButton.actor; this._leftBox.add(this._activities); + // The activities button has a pretend menu, so as to integrate + // more cleanly with the rest of the panel + this._menus.addMenu(this._activitiesButton.menu); + // Synchronize the button's pseudo classes with its corner this.button.connect('style-changed', Lang.bind(this, function(actor) {