diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index e79a8cb52..73ab69e6b 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -668,3 +668,52 @@ StTooltip { height: 52px; background-image: url("corner-ripple.png"); } + +/* Workspace Switcher */ +.workspace-switcher-container { + background: rgba(0,0,0,0.8); + border: 1px solid rgba(128,128,128,0.40); + border-radius: 8px; + padding: 12px; +} + +.workspace-switcher { + background: transparent; + border: 0px; + border-radius: 0px; + padding: 4px; +} + +.ws-switcher-active-left { + width: 98px; + height: 98px; + border: 0px; + background: rgba(255,255,255,0.5); + background-image: url("ws-switch-arrow-left.svg"); + border-radius: 4px; +} + +.ws-switcher-active-right { + width: 98px; + height: 98px; + border: 0px; + background: rgba(255,255,255,0.5); + background-image: url("ws-switch-arrow-right.svg"); + border-radius: 4px; +} + +.ws-switcher-spacer { + width: 0.5px; + height: 96px; + border: 0px; + background: transparent; + padding: 4px; +} + +.ws-switcher-box { + width: 96px; + height: 96px; + border: 2px solid rgba(85,85,85,0.5); + background: transparent; + border-radius: 4px; +} diff --git a/data/theme/ws-switch-arrow-left.svg b/data/theme/ws-switch-arrow-left.svg new file mode 100644 index 000000000..a1e1b9cf0 --- /dev/null +++ b/data/theme/ws-switch-arrow-left.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/theme/ws-switch-arrow-right.svg b/data/theme/ws-switch-arrow-right.svg new file mode 100644 index 000000000..03d7f8350 --- /dev/null +++ b/data/theme/ws-switch-arrow-right.svg @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js index 136cd09c0..68779869a 100644 --- a/js/ui/windowManager.js +++ b/js/ui/windowManager.js @@ -7,6 +7,7 @@ const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const AltTab = imports.ui.altTab; +const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; const Main = imports.ui.main; const Tweener = imports.ui.tweener; @@ -42,6 +43,17 @@ WindowManager.prototype = { shellwm.takeover_keybinding('switch_windows'); shellwm.connect('keybinding::switch_windows', Lang.bind(this, this._startAppSwitcher)); + + this._workspaceSwitcherPopup = null; + shellwm.takeover_keybinding('switch_to_workspace_left'); + shellwm.takeover_keybinding('switch_to_workspace_right'); + shellwm.takeover_keybinding('switch_to_workspace_up'); + shellwm.takeover_keybinding('switch_to_workspace_down'); + shellwm.connect('keybinding::switch_to_workspace_left', Lang.bind(this, this._showWorkspaceSwitcher)); + shellwm.connect('keybinding::switch_to_workspace_right', Lang.bind(this, this._showWorkspaceSwitcher)); + shellwm.connect('keybinding::switch_to_workspace_up', Lang.bind(this, this._showWorkspaceSwitcher)); + shellwm.connect('keybinding::switch_to_workspace_down', Lang.bind(this, this._showWorkspaceSwitcher)); + }, _shouldAnimate : function(actor) { @@ -277,9 +289,45 @@ WindowManager.prototype = { }, _startAppSwitcher : function(shellwm, binding, window, backwards) { + /* prevent a corner case where both popups show up at once */ + if (this._workspaceSwitcherPopup != null) + this._workspaceSwitcherPopup.actor.hide(); + let tabPopup = new AltTab.AltTabPopup(); if (!tabPopup.show(backwards)) tabPopup.destroy(); + }, + + _showWorkspaceSwitcher : function(shellwm, binding, window, backwards) { + /* We don't support this kind of layout */ + if (binding == "switch_to_workspace_up" || binding == "switch_to_workspace_down") + return; + + if (global.screen.n_workspaces == 1) + return; + + if (this._workspaceSwitcherPopup == null) + this._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); + + let activeWorkspaceIndex = global.screen.get_active_workspace_index(); + + if (binding == "switch_to_workspace_left") { + if (activeWorkspaceIndex > 0) { + global.screen.get_workspace_by_index(activeWorkspaceIndex - 1).activate(global.get_current_time()); + this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.LEFT, activeWorkspaceIndex - 1); + } + else + this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.LEFT, activeWorkspaceIndex); + } + + if (binding == "switch_to_workspace_right") { + if (activeWorkspaceIndex < global.screen.n_workspaces - 1) { + global.screen.get_workspace_by_index(activeWorkspaceIndex + 1).activate(global.get_current_time()); + this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.RIGHT, activeWorkspaceIndex + 1); + } + else + this._workspaceSwitcherPopup.display(WorkspaceSwitcherPopup.RIGHT, activeWorkspaceIndex); + } } }; diff --git a/js/ui/workspaceSwitcherPopup.js b/js/ui/workspaceSwitcherPopup.js new file mode 100644 index 000000000..655779c70 --- /dev/null +++ b/js/ui/workspaceSwitcherPopup.js @@ -0,0 +1,100 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const Lang = imports.lang; +const Mainloop = imports.mainloop; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const Tweener = imports.ui.tweener; + +const ANIMATION_TIME = 0.075; +const DISPLAY_TIMEOUT = 600; + +const LEFT = -1; +const RIGHT = 1; + +function WorkspaceSwitcherPopup() { + this._init(); +} + +WorkspaceSwitcherPopup.prototype = { + _init : function() { + this.actor = new Clutter.Group({ reactive: true, + x: 0, + y: 0, + width: global.screen_width, + height: global.screen_height }); + global.stage.add_actor(this.actor); + + this._container = new St.BoxLayout({ style_class: "workspace-switcher-container" }); + this._list = new St.BoxLayout({ style_class: "workspace-switcher" }); + + this._redraw(); + + this._container.add(this._list); + this.actor.add_actor(this._container); + + this._position(); + + this.actor.show(); + this._timeoutId = Mainloop.timeout_add(DISPLAY_TIMEOUT, Lang.bind(this, this._onTimeout)); + }, + + _redraw : function(direction, activeWorkspaceIndex) { + this._list.destroy_children(); + + for (let i = 0; i < global.screen.n_workspaces; i++) { + let indicator = null; + + if (i == activeWorkspaceIndex && direction == LEFT) + indicator = new St.Bin({ style_class: 'ws-switcher-active-left' }); + else if(i == activeWorkspaceIndex && direction == RIGHT) + indicator = new St.Bin({ style_class: 'ws-switcher-active-right' }); + else + indicator = new St.Bin({ style_class: 'ws-switcher-box' }); + + this._list.add(indicator); + + if (i < global.screen.n_workspaces - 1) { + let spacer = new St.Bin({ style_class: 'ws-switcher-spacer' }); + this._list.add(spacer); + } + + } + }, + + _position: function() { + let focus = global.get_focus_monitor(); + this._container.x = focus.x + Math.floor((focus.width - this._container.width) / 2); + this._container.y = focus.y + Math.floor((focus.height - this._container.height) / 2); + }, + + _show : function() { + Tweener.addTween(this._container, { opacity: 255, + time: ANIMATION_TIME, + transition: "easeOutQuad" + }); + this._position(); + this.actor.show(); + }, + + display : function(direction, activeWorkspaceIndex) { + this._redraw(direction, activeWorkspaceIndex); + if (this._timeoutId != 0) + Mainloop.source_remove(this._timeoutId); + this._timeoutId = Mainloop.timeout_add(DISPLAY_TIMEOUT, Lang.bind(this, this._onTimeout)); + this._show(); + }, + + _onTimeout : function() { + Mainloop.source_remove(this._timeoutId); + this._timeoutId = 0; + Tweener.addTween(this._container, { opacity: 0.0, + time: ANIMATION_TIME, + transition: "easeOutQuad", + onComplete: function() { this.actor.hide() }, + onCompleteScope: this + }); + } +};