diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index cfcec9e0a..526b059b5 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -576,6 +576,10 @@ StTooltip StLabel { background-position: 10px 10px; } +.app-filter:focus { + outline: 1px solid #aaa; +} + .dash-item-container > .app-well-app { padding: 4px 8px; } diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 0433cec00..ffd49fa07 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -23,6 +23,7 @@ const Workspace = imports.ui.workspace; const Params = imports.misc.params; const MENU_POPUP_TIMEOUT = 600; +const SCROLL_TIME = 0.1; function AlphabeticalView() { this._init(); @@ -67,6 +68,7 @@ AlphabeticalView.prototype = { let appIcon = new AppWellIcon(this._appSystem.get_app(appInfo.get_id())); this._grid.addItem(appIcon.actor); + appIcon.actor.connect('key-focus-in', Lang.bind(this, this._ensureIconVisible)); appIcon._appInfo = appInfo; if (this._filterApp && !this._filterApp(appInfo)) @@ -75,6 +77,28 @@ AlphabeticalView.prototype = { this._apps.push(appIcon); }, + _ensureIconVisible: function(icon) { + let adjustment = this.actor.vscroll.adjustment; + let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values(); + + let offset = 0; + let vfade = this.actor.get_effect("vfade"); + if (vfade) + offset = vfade.fade_offset; + + if (icon.y < value + offset) + value = Math.max(0, icon.y - offset); + else if (icon.y + icon.height > value + pageSize - offset) + value = Math.min(upper, icon.y + icon.height + offset - pageSize); + else + return; + + Tweener.addTween(adjustment, + { value: value, + time: SCROLL_TIME, + transition: 'easeOutQuad' }); + }, + setFilter: function(filter) { this._filterApp = filter; for (let i = 0; i < this._apps.length; i++) @@ -128,6 +152,12 @@ ViewByCategories.prototype = { })); this._sections = []; + + // We need a dummy actor to catch the keyboard focus if the + // user Ctrl-Alt-Tabs here before the deferred work creates + // our real contents + this._focusDummy = new St.Bin({ can_focus: true }); + this.actor.add(this._focusDummy); }, _scrollFilter: function(actor, event) { @@ -166,7 +196,8 @@ ViewByCategories.prototype = { _addFilter: function(name, num) { let button = new St.Button({ label: GLib.markup_escape_text (name, -1), style_class: 'app-filter', - x_align: St.Align.START }); + x_align: St.Align.START, + can_focus: true }); this._filters.add(button, { expand: true, x_fill: true, y_fill: false }); button.connect('clicked', Lang.bind(this, function() { this._selectCategory(num); @@ -201,6 +232,14 @@ ViewByCategories.prototype = { this._addFilter(sections[i], i); this._selectCategory(-1); + + if (this._focusDummy) { + let focused = this._focusDummy.has_key_focus(); + this._focusDummy.destroy(); + this._focusDummy = null; + if (focused) + this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); + } } }; diff --git a/js/ui/overview.js b/js/ui/overview.js index ad02d1344..6d86bdae0 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -180,10 +180,10 @@ Overview.prototype = { this._group.add_actor(this.viewSelector.actor); this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay(); - this.viewSelector.addViewTab(_("Windows"), this._workspacesDisplay.actor); + this.viewSelector.addViewTab(_("Windows"), this._workspacesDisplay.actor, 'text-x-generic'); let appView = new AppDisplay.AllAppDisplay(); - this.viewSelector.addViewTab(_("Applications"), appView.actor); + this.viewSelector.addViewTab(_("Applications"), appView.actor, 'system-run'); // Default search providers this.viewSelector.addSearchProvider(new AppDisplay.AppSearchProvider()); diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js index 7e5fa2726..5cad9cd52 100644 --- a/js/ui/viewSelector.js +++ b/js/ui/viewSelector.js @@ -1,6 +1,7 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; +const Gtk = imports.gi.Gtk; const Mainloop = imports.mainloop; const Meta = imports.gi.Meta; const Signals = imports.signals; @@ -15,12 +16,12 @@ const Search = imports.ui.search; const SearchDisplay = imports.ui.searchDisplay; const Tweener = imports.ui.tweener; -function BaseTab(titleActor, pageActor) { - this._init(titleActor, pageActor); +function BaseTab(titleActor, pageActor, name, a11yIcon) { + this._init(titleActor, pageActor, name, a11yIcon); } BaseTab.prototype = { - _init: function(titleActor, pageActor) { + _init: function(titleActor, pageActor, name, a11yIcon) { this.title = titleActor; this.page = new St.Bin({ child: pageActor, x_align: St.Align.START, @@ -29,6 +30,14 @@ BaseTab.prototype = { y_fill: true, style_class: 'view-tab-page' }); + if (this.title.can_focus) { + Main.ctrlAltTabManager.addGroup(this.title, name, a11yIcon); + } else { + Main.ctrlAltTabManager.addGroup(this.page, name, a11yIcon, + { proxy: this.title, + focusCallback: Lang.bind(this, this._a11yFocus) }); + } + this.visible = false; }, @@ -56,6 +65,11 @@ BaseTab.prototype = { }); }, + _a11yFocus: function() { + this._activate(); + this.page.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); + }, + _activate: function() { this.emit('activated'); } @@ -63,19 +77,19 @@ BaseTab.prototype = { Signals.addSignalMethods(BaseTab.prototype); -function ViewTab(label, pageActor) { - this._init(label, pageActor); +function ViewTab(label, pageActor, a11yIcon) { + this._init(label, pageActor, a11yIcon); } ViewTab.prototype = { __proto__: BaseTab.prototype, - _init: function(label, pageActor) { + _init: function(label, pageActor, a11yIcon) { let titleActor = new St.Button({ label: label, style_class: 'view-tab-title' }); titleActor.connect('clicked', Lang.bind(this, this._activate)); - BaseTab.prototype._init.call(this, titleActor, pageActor); + BaseTab.prototype._init.call(this, titleActor, pageActor, label, a11yIcon); } }; @@ -101,7 +115,8 @@ SearchTab.prototype = { active; it should not exceed ~30 characters. */ hint_text: _("Type to search..."), - track_hover: true }); + track_hover: true, + can_focus: true }); this._text = this._entry.clutter_text; this._text.connect('key-press-event', Lang.bind(this, this._onKeyPress)); @@ -118,7 +133,9 @@ SearchTab.prototype = { this._searchResults = new SearchDisplay.SearchResults(this._searchSystem, this._openSearchSystem); BaseTab.prototype._init.call(this, this._entry, - this._searchResults.actor); + this._searchResults.actor, + _("Search"), + 'edit-find'); this._text.connect('text-changed', Lang.bind(this, this._onTextChanged)); this._text.connect('activate', Lang.bind(this, function (se) { @@ -366,8 +383,8 @@ ViewSelector.prototype = { })); }, - addViewTab: function(title, pageActor) { - let viewTab = new ViewTab(title, pageActor); + addViewTab: function(title, pageActor, a11yIcon) { + let viewTab = new ViewTab(title, pageActor, a11yIcon); this._tabs.push(viewTab); this._tabBox.add(viewTab.title); this._addTab(viewTab); diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c index 3f7c12a75..618648a36 100644 --- a/src/st/st-scroll-view.c +++ b/src/st/st-scroll-view.c @@ -176,7 +176,8 @@ st_scroll_view_set_vfade (StScrollView *self, if (priv->vfade_effect == NULL) priv->vfade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL); - clutter_actor_add_effect (CLUTTER_ACTOR (self), CLUTTER_EFFECT (priv->vfade_effect)); + clutter_actor_add_effect_with_name (CLUTTER_ACTOR (self), "vfade", + CLUTTER_EFFECT (priv->vfade_effect)); } else {