diff --git a/README.md b/README.md index 8cc9a6be..6626fad0 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,7 @@ Keybindings.bindkey("j", "my-favorite-width", See `examples/keybindings.js` for more examples. -### Window Position Bar (colored bar segment added to Gnome Top Bar) +## Window Position Bar (colored bar segment in Top Bar) @@ -282,7 +282,7 @@ See `examples/keybindings.js` for more examples. https://user-images.githubusercontent.com/30424662/221416159-464d7512-5174-451b-9035-0ee84f9eb4ec.mp4 -The the window position bar can be _disabled_ from `PaperWM extension settings` or via `dconf`, e.g. by executing the following command a terminal: +The the window position bar can be _disabled_ from `PaperWM extension settings` or via `dconf`, e.g. by executing the following command in a terminal: ``` dconf write /org/gnome/shell/extensions/paperwm/show-window-position-bar false @@ -292,6 +292,21 @@ You can style both the coloured position bar and the dimmed "position bar backdr _Note: PaperWM overrides the default Gnome Top Bar style to be completely transparent so that the dimmed `window-position-bar-backdrop` and`window-position-bar` elements are visible._ +## Window Focus Mode ## + +[#482](https://github.com/paperwm/PaperWM/pull/482) added the concept of `window focus modes` to PaperWM. A `focus mode` controls how windows are "focused". For example, the `CENTER` focus mode causes all windows to be centered horizontally on selection, whereas the `DEFAULT` focus mode is the traditional PaperWM behaviour. + +Focus modes can be toggled by user-settable keybinding (default is `Super`+`Shift`+`c`), or by clicking the new focus-mode button in the topbar: + +![Focus mode button](media/focus-mode-button.png) + +You may prefer to hide the focus mode icon. You can do so by executing the following command in a terminal: + +``` +dconf write /org/gnome/shell/extensions/paperwm/show-focus-mode-icon false +``` + + ## Fixed Window Size ## See the [Winprops](#winprops) section for a way to set the default _width_ of windows identified by their `wm_class` window property. diff --git a/keybindings.js b/keybindings.js index bb756a21..e5f9de72 100644 --- a/keybindings.js +++ b/keybindings.js @@ -160,6 +160,10 @@ function init() { Scratch), Meta.KeyBindingFlags.PER_WINDOW); + registerPaperAction("switch-focus-mode", + dynamic_function_ref("switchToNextFocusMode", + Tiling)); + registerPaperAction("develop-set-globals", dynamic_function_ref("setDevGlobals", Utils)); diff --git a/media/focus-mode-button.png b/media/focus-mode-button.png new file mode 100644 index 00000000..f7405fc8 Binary files /dev/null and b/media/focus-mode-button.png differ diff --git a/minimap.js b/minimap.js index b235a925..29975301 100644 --- a/minimap.js +++ b/minimap.js @@ -122,7 +122,7 @@ var Minimap = class Minimap extends Array { } this.layout(); - let time = animate ? 0.25 : 0; + let time = animate ? prefs.animation_time : 0; this.actor.show(); Tweener.addTween(this.actor, {opacity: 255, time, mode: Clutter.AnimationMode.EASE_OUT_EXPO}); @@ -131,7 +131,7 @@ var Minimap = class Minimap extends Array { hide(animate) { if (this.destroyed) return; - let time = animate ? 0.25 : 0; + let time = animate ? prefs.animation_time : 0; Tweener.addTween(this.actor, {opacity: 0, time, mode: Clutter.AnimationMode.EASE_OUT_EXPO, onComplete: () => this.actor.hide() }); diff --git a/navigator.js b/navigator.js index 200515fb..94811d44 100644 --- a/navigator.js +++ b/navigator.js @@ -261,7 +261,7 @@ class NavigatorClass { this._block = Main.wm._blockAnimations; Main.wm._blockAnimations = true; // Meta.disable_unredirect_for_screen(screen); - this.space = Tiling.spaces.spaceOf(workspaceManager.get_active_workspace()); + this.space = Tiling.spaces.getActiveSpace(); this._startWindow = this.space.selectedWindow; this.from = this.space; diff --git a/prefsKeybinding.js b/prefsKeybinding.js index 94b63524..55ad66f3 100644 --- a/prefsKeybinding.js +++ b/prefsKeybinding.js @@ -37,6 +37,7 @@ const actions = { 'switch-last', 'live-alt-tab', 'live-alt-tab-backward', + 'switch-focus-mode', 'move-left', 'move-right', 'move-up', diff --git a/resources/ICONS.info b/resources/ICONS.info new file mode 100644 index 00000000..8cb9afe6 --- /dev/null +++ b/resources/ICONS.info @@ -0,0 +1,9 @@ +SVG icons adapted from `sidebar-show-right-symbolic.svg` and `preferences-desktop-multitasking-symbolic.svg` icons from "GNOME Project" adwaita-icon-theme (https://github.com/GNOME/adwaita-icon-theme). + +See http://www.gnome.org for more information. + +The icons are licensed under Creative Commons Attribution-Share Alike 3.0 United States License. + +To view a copy of the CC-BY-SA licence, visit +http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative +Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. \ No newline at end of file diff --git a/resources/focus-mode-center-symbolic.svg b/resources/focus-mode-center-symbolic.svg new file mode 100644 index 00000000..50b8a84b --- /dev/null +++ b/resources/focus-mode-center-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/focus-mode-default-symbolic.svg b/resources/focus-mode-default-symbolic.svg new file mode 100644 index 00000000..7a2224df --- /dev/null +++ b/resources/focus-mode-default-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index b83ff315..51ab5202 100644 Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ diff --git a/schemas/org.gnome.shell.extensions.paperwm.gschema.xml b/schemas/org.gnome.shell.extensions.paperwm.gschema.xml index 48e83239..ef69bcc2 100644 --- a/schemas/org.gnome.shell.extensions.paperwm.gschema.xml +++ b/schemas/org.gnome.shell.extensions.paperwm.gschema.xml @@ -108,6 +108,11 @@ t']]]> Take the window, dropping it when finished navigating + + + c']]]> + Switch between Window Focus Modes (e.g. default, center) + period']]]> @@ -243,7 +248,7 @@ true - Wether to hide the top bar or not + Show the topbar '' @@ -374,6 +379,11 @@ Show the window position bar in the topbar + + true + Show the focus mode icon in the topbar + + 0.25 Duration of animations in seconds diff --git a/settings.js b/settings.js index 59afe6d7..8cbf86ad 100644 --- a/settings.js +++ b/settings.js @@ -35,7 +35,7 @@ var prefs = {}; 'workspace-colors', 'default-background', 'animation-time', 'use-workspace-name', 'pressure-barrier', 'default-show-top-bar', 'swipe-sensitivity', 'swipe-friction', 'cycle-width-steps', 'cycle-height-steps', 'topbar-follow-focus', 'minimap-scale', - 'winprops', 'show-window-position-bar'] + 'winprops', 'show-window-position-bar', 'show-focus-mode-icon'] .forEach((k) => setState(null, k)); prefs.__defineGetter__("minimum_margin", function() { return Math.min(15, this.horizontal_margin) }); diff --git a/stylesheet.css b/stylesheet.css index 099ecca8..190ca5bb 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,3 +1,25 @@ +.space-label-wrapper { + padding: 0 10px 0 0; + background-color: transparent; + border-image: none; + background-image: none; + border: none; +} + +.space-focus-mode-icon { + icon-size: 16px; + padding: 0 18px 0 18px; + margin-left: 3px; + background-color: transparent; +} + +.focus-button-tooltip { + background-color: rgba(0, 0, 0, 0.7); + padding: 8px; + border-radius: 8px; + font-weight: 600; +} + .topbar-clear { background-color: rgba(0, 0, 0, 0); } diff --git a/tiling.js b/tiling.js index 3e733762..41183bee 100644 --- a/tiling.js +++ b/tiling.js @@ -62,6 +62,9 @@ var sizeSlack = 30; var PreviewMode = {NONE: 0, STACK: 1, SEQUENTIAL: 2}; var inPreview = PreviewMode.NONE; +// DEFAULT mode is normal/original PaperWM window focus behaviour +var FocusModes = {DEFAULT: 0, CENTER: 1}; + var signals, oldSpaces, backgroundGroup, oldMonitors, WindowCloneLayout, grabSignals; function init() { @@ -137,11 +140,25 @@ var Space = class Space extends Array { this.workspace = workspace; this.signals = new utils.Signals(); - // The windows that should be represented by their WindowActor + // windows that should be represented by their WindowActor this.visible = []; this._floating = []; this._populated = false; + // default focusMode (can be overriden by saved user pref in Space.init method) + this.focusMode = FocusModes.DEFAULT; + this.focusModeIcon = new TopBar.FocusIcon({ + name: 'panel', + style_class: 'space-focus-mode-icon', + }) + .setMode(this.focusMode) + .setClickFunction(() => { + switchToNextFocusMode(this); + }) + .setVisible(false); // hide by default + this.showFocusModeIcon(); + this.unfocusXPosition = null; // init + let clip = new Clutter.Actor({name: "clip"}); this.clip = clip; let actor = new Clutter.Actor({name: "space-actor"}); @@ -155,16 +172,15 @@ var Space = class Space extends Array { let cloneContainer = new St.Widget({name: "clone-container"}); this.cloneContainer = cloneContainer; - let labelParent = new St.Widget({name: 'panel'}); - labelParent.style = ` - background-color: transparent; - border-image: none; - background-image: none; - border: none; - `; - this.labelParent = labelParent; + let labelWrapper = new St.Widget({ + reactive: true, + name: 'panel', + style_class: 'space-label-wrapper', + }); + labelWrapper.connect('button-press-event', () => Main.overview.toggle()); + this.labelWrapper = labelWrapper; let label = new St.Label(); - labelParent.add_actor(label); + labelWrapper.add_actor(label); this.label = label; label.hide(); @@ -179,7 +195,8 @@ var Space = class Space extends Array { container.add_actor(clip); clip.add_actor(actor); - actor.add_actor(labelParent); + actor.add_actor(labelWrapper); + actor.add_child(this.focusModeIcon); actor.add_actor(cloneClip); cloneClip.add_actor(cloneContainer); @@ -238,7 +255,7 @@ var Space = class Space extends Array { this.targetX = oldSpace.targetX; } this.cloneContainer.x = this.targetX; - + // init window position bar and space topbar elements this.windowPositionBarBackdrop.width = this.monitor.width; this.windowPositionBarBackdrop.height = TopBar.panelBox.height; @@ -247,7 +264,6 @@ var Space = class Space extends Array { this.getWindows().forEach(w => { animateWindow(w); }); - this.layout(false); let selected = this.selectedWindow; if (selected) { @@ -586,7 +602,7 @@ var Space = class Space extends Array { isFullyVisible(metaWindow) { let clone = metaWindow.clone; - let x = clone.targetX + this.targetX; + let x = this.visibleX(metaWindow); let workArea = this.workArea(); let min = workArea.x; @@ -594,9 +610,8 @@ var Space = class Space extends Array { } visibleRatio(metaWindow) { - let clone = metaWindow.clone; - let x = clone.targetX + this.targetX; + let x = this.visibleX(metaWindow); let workArea = this.workArea(); let min = workArea.x; @@ -608,7 +623,7 @@ var Space = class Space extends Array { isPlaceable(metaWindow) { let clone = metaWindow.clone; - let x = clone.targetX + this.targetX; + let x = this.visibleX(metaWindow); let workArea = Main.layoutManager.getWorkAreaForMonitor(this.monitor.index); let min = workArea.x - this.monitor.x; @@ -677,7 +692,7 @@ var Space = class Space extends Array { return let f = w.get_frame_rect(); let clone = w.clone; - let x = clone.targetX + this.targetX; + let x = this.visibleX(w); let y = this.monitor.y + clone.targetY; x = Math.min(this.width - stack_margin, Math.max(stack_margin - f.width, x)); x += this.monitor.x; @@ -873,6 +888,20 @@ var Space = class Space extends Array { ensureViewport(metaWindow, space); } + /** + * Return the x position of the visible element of this window. + */ + visibleX(metaWindow) { + return metaWindow.clone.targetX + this.targetX; + } + + /** + * Return the y position of the visible element of this window. + */ + visibleY(metaWindow) { + return metaWindow.clone.targetY + this.monitor.y; + } + positionOf(metaWindow) { metaWindow = metaWindow || this.selectedWindow; let index, row; @@ -947,10 +976,9 @@ var Space = class Space extends Array { if (unMovable) return; - let clone = w.clone; let f = w.get_frame_rect(); - let x = clone.targetX + this.targetX; - let y = monitor.y + clone.targetY; + let x = this.visibleX(w); + let y = this.visibleY(w); x = Math.max(stack_margin - f.width, x); x = Math.min(this.width - stack_margin, x); x += monitor.x; @@ -1239,7 +1267,7 @@ border-radius: ${borderWidth}px; // show space duplicate elements if not primary monitor if (!this.hasTopBar()) { - this.labelParent.raise_top(); + this.labelWrapper.raise_top(); this.label.show(); } @@ -1282,8 +1310,28 @@ border-radius: ${borderWidth}px; } } - visible && this.labelParent.raise_top(); - visible ? this.label.show() : this.label.hide(); + if (visible) { + this.labelWrapper.raise_top(); + this.label.show(); + this.showFocusModeIcon(true); + } + else { + this.label.hide(); + this.showFocusModeIcon(false); + } + } + + /** + * Shows the focusModeIcon space element. + * @param {boolean} show + */ + showFocusModeIcon(show=true) { + if (show && prefs.show_focus_mode_icon) { + this.focusModeIcon.raise_top(); + this.focusModeIcon.show(); + } else { + this.focusModeIcon.hide(); + } } createBackground() { @@ -1610,7 +1658,7 @@ var Spaces = class Spaces extends Map { this.switchWorkspace.bind(this)); this.signals.connect(this.overrideSettings, 'changed::workspaces-only-on-primary', - this.monitorsChanged.bind(this)); + this.monitorsChanged.bind(this)); // Clone and hook up existing windows display.get_tab_list(Meta.TabList.NORMAL_ALL, null) @@ -1629,6 +1677,19 @@ var Spaces = class Spaces extends Map { // Initialize spaces _after_ monitors are set up this.forEach(space => space.init()); + /** + * After spaces have init, do a final layout on active space and activate selected window. + * Ensures correct window layout with multi-monitors, and if windows already exist on init, + * (e.g. resetting gnome-shell) then will ensure selectedWindow is activated. + */ + imports.mainloop.timeout_add(200, () => { + const space = spaces.getActiveSpace(); + if (space.selectedWindow) { + space.layout(false); + Main.activateWindow(space.selectedWindow); + } + }); + this.stack = this.mru(); } @@ -1936,6 +1997,17 @@ var Spaces = class Spaces extends Map { }); } + /** + * Sets the focusIconPosition for all spaces. + * @param {int} x + * @param {int} y + */ + setFocusIconPosition(x=0, y=0) { + this.forEach(s => { + s.focusModeIcon.set_position(x, y); + }); + } + _getOrderedSpaces(monitor) { let nWorkspaces = workspaceManager.n_workspaces; let out = []; @@ -2023,7 +2095,7 @@ var Spaces = class Spaces extends Map { return; } - let currentSpace = this.spaceOf(workspaceManager.get_active_workspace()); + let currentSpace = this.getActiveSpace(); let monitorSpaces = this._getOrderedSpaces(currentSpace.monitor); if (!inPreview) { @@ -2088,8 +2160,9 @@ var Spaces = class Spaces extends Map { } _initWorkspaceStack() { - if (inPreview) + if (inPreview) { return; + } inPreview = PreviewMode.STACK; this.setSpaceTopbarElementsVisible(); @@ -2097,7 +2170,7 @@ var Spaces = class Spaces extends Map { // Always show the topbar when using the workspace stack TopBar.fixTopBar(); const scale = 0.9; - let space = this.spaceOf(workspaceManager.get_active_workspace()); + let space = this.getActiveSpace(); let mru = [...this.stack]; this.monitors.forEach(space => mru.splice(mru.indexOf(space), 1)); mru = [space, ...mru]; @@ -2171,14 +2244,13 @@ var Spaces = class Spaces extends Map { } selectStackSpace(direction, move) { - // if in sequence preview do not run stack preview if (inPreview === PreviewMode.SEQUENTIAL) { return; } const scale = 0.9; - let space = this.spaceOf(workspaceManager.get_active_workspace()); + let space = this.getActiveSpace(); let mru = [...this.stack]; this.monitors.forEach(space => mru.splice(mru.indexOf(space), 1)); @@ -2250,7 +2322,6 @@ var Spaces = class Spaces extends Map { } animateToSpace(to, from, callback) { - let currentPreviewMode = inPreview; inPreview = PreviewMode.NONE; @@ -2378,6 +2449,22 @@ var Spaces = class Spaces extends Map { return this.get(workspace); }; + /** + * Returns the currently active space. + */ + getActiveSpace() { + return this.spaceOf(workspaceManager.get_active_workspace()); + } + + /** + * Returns true if the space is the currently active space. + * @param {Space} space + * @returns + */ + isActiveSpace(space) { + return space === this.getActiveSpace(); + } + /** Return an array of Space's ordered in most recently used order. */ @@ -2472,6 +2559,7 @@ var Spaces = class Spaces extends Map { TopBar.fixStyle(); } } + Signals.addSignalMethods(Spaces.prototype); function is_override_redirect(metaWindow) { @@ -2490,10 +2578,9 @@ function registerWindow(metaWindow) { } if (metaWindow.clone) { - // Should no longer be possible, but leave a trace just to be sure + // Can now happen when setting session-modes to "unlock-dialog" or + // resetting gnome-shell in-place (e.g. on X11) utils.warn("window already registered", metaWindow.title); - utils.print_stacktrace(); - // Can now happen when setting session-modes to "unlock-dialog" return false } @@ -2881,12 +2968,15 @@ function ensuredX(meta_window, space) { let workArea = space.workArea(); let min = workArea.x; let max = min + workArea.width; - if (meta_window.fullscreen) { + + if (space.focusMode == FocusModes.CENTER) { + // window switching should centre focus + x = workArea.x + Math.round(workArea.width/2 - frame.width/2); + } else if (meta_window.fullscreen) { x = 0; } else if (frame.width > workArea.width*0.9 - 2*(prefs.horizontal_margin + prefs.window_gap)) { // Consider the window to be wide and center it x = min + Math.round((workArea.width - frame.width)/2); - } else if (x + frame.width > max) { // Align to the right prefs.horizontal_margin x = max - prefs.horizontal_margin - frame.width; @@ -3406,18 +3496,18 @@ function cycleWindowHeight(metaWindow) { } function activateNthWindow(n, space) { - space = space || spaces.spaceOf(workspaceManager.get_active_workspace()); + space = space || spaces.getActiveSpace(); let nth = space[n][0]; ensureViewport(nth, space); } function activateFirstWindow(mw, space) { - space = space || spaces.spaceOf(workspaceManager.get_active_workspace()); + space = space || spaces.getActiveSpace(); activateNthWindow(0, space); } function activateLastWindow(mw, space) { - space = space || spaces.spaceOf(workspaceManager.get_active_workspace()); + space = space || spaces.getActiveSpace(); activateNthWindow(space.length - 1, space); } @@ -3434,12 +3524,17 @@ function activateWindowAfterRendered(actor, mw) { }); } +/** + * Centers the currently selected window. + */ function centerWindowHorizontally(metaWindow) { const frame = metaWindow.get_frame_rect(); const space = spaces.spaceOfWindow(metaWindow); + const index = space.indexOf(metaWindow); const monitor = space.monitor; const workArea = space.workArea(); - const targetX = workArea.x + Math.round(workArea.width/2 - frame.width/2); + + const targetX = workArea.x + Math.round((workArea.width - frame.width)/2); const dx = targetX - (metaWindow.clone.targetX + space.targetX); let [pointerX, pointerY, mask] = global.get_pointer(); @@ -3456,6 +3551,71 @@ function centerWindowHorizontally(metaWindow) { } } +/** + * Sets the focus mode for a space. + * @param {FocusModes} mode + * @param {Space} space + */ +function setFocusMode(mode, space) { + space = space ?? spaces.getActiveSpace(); + space.focusMode = mode; + space.focusModeIcon.setMode(mode); + if (space.hasTopBar()) { + TopBar.focusButton.setFocusMode(mode); + } + + const workArea = space.workArea(); + const selectedWin = space.selectedWindow; + // if centre also center selectedWindow + if (mode === FocusModes.CENTER) { + if (selectedWin) { + // check it closer to min or max of workArea + const frame = selectedWin.get_frame_rect(); + const winMidpoint = space.visibleX(selectedWin) + frame.width/2; + const workAreaMidpoint = workArea.width/2; + if (winMidpoint <= workAreaMidpoint) { + space.unfocusXPosition = 0; + } else { + space.unfocusXPosition = workArea.width; + } + centerWindowHorizontally(selectedWin); + } + } + + // if normal and has saved x position from previous + if (mode === FocusModes.DEFAULT && space.unfocusXPosition != null) { + // if window is first, move to left edge + let position; + if (space.indexOf(selectedWin) == 0) { + position = 0; + } + // if windows is last, move to right edge + else if (space.indexOf(selectedWin) == space.length - 1) { + position = workArea.width; + } + else { + position = space.unfocusXPosition; + } + // do the move + move_to(space, space.selectedWindow, {x:position}); + ensureViewport(space.selectedWindow, space, true); + space.unfocusXPosition = null; + } +} + +/** + * Switches to the next focus mode for a space. + * @param {Space} space + */ +function switchToNextFocusMode(space) { + space = space ?? spaces.getActiveSpace(); + const numModes = Object.keys(FocusModes).length; + // for currMode we switch to 1-based to use it validly in remainder operation + const currMode = Object.values(FocusModes).indexOf(space.focusMode) + 1; + const nextMode = (currMode % numModes); + setFocusMode(nextMode, space); +} + /** * "Fit" values such that they sum to `targetSum` */ diff --git a/topbar.js b/topbar.js index 39e6f4e6..48dbe8dd 100644 --- a/topbar.js +++ b/topbar.js @@ -18,6 +18,7 @@ var PopupMenu = imports.ui.popupMenu; var Clutter = imports.gi.Clutter; var Main = imports.ui.main; var Tweener = Extension.imports.utils.tweener; +var Path = imports.misc.extensionUtils.getCurrentExtension().dir.get_path(); var Tiling = Extension.imports.tiling; var Navigator = Extension.imports.navigator; @@ -108,7 +109,6 @@ if (Utils.version[1] === 32) { PopupMenuEntryHelper.call(this, text); } - activate(event) { this.label.grab_key_focus(); } @@ -190,12 +190,161 @@ class ColorEntry { } clicked() { - let space = Tiling.spaces.spaceOf(workspaceManager.get_active_workspace()); + let space = Tiling.spaces.getActiveSpace(); let color = this.entry.actor.text; space.settings.set_string('color', color); } } +/** + * FocusMode icon class. + */ +var FocusIcon = Utils.registerClass( +class FocusIcon extends St.Icon { + _init(properties = {}, tooltip_x_point=0) { + super._init(properties); + this.reactive = true; + + // allow custom x position for tooltip + this.tooltip_x_point = tooltip_x_point; + + // read in focus icons from resources folder + this.gIconDefault = Gio.icon_new_for_string(`${Path}/resources/focus-mode-default-symbolic.svg`); + this.gIconCenter = Gio.icon_new_for_string(`${Path}/resources/focus-mode-center-symbolic.svg`); + + this._initToolTip(); + this.setMode(); + + this.connect('button-press-event', () => { + if (this.clickFunction) { + this.clickFunction(); + } + }); + } + + /** + * Sets a function to be executed on click. + * @param {Function} clickFunction + * @returns + */ + setClickFunction(clickFunction) { + this.clickFunction = clickFunction; + return this; + } + + _initToolTip() { + const tt = new St.Label({style_class: 'focus-button-tooltip'}); + tt.hide(); + global.stage.add_child(tt); + this.connect('enter-event', icon => { + this._updateTooltipPosition(this.tooltip_x_point); + this._updateTooltipText(); + tt.show(); + }); + this.connect('leave-event', (icon, event) => { + if (!this.has_pointer) { + tt.hide(); + } + }); + this.tooltip = tt; + } + + /** + * Updates tooltip position relative to this button. + */ + _updateTooltipPosition(xpoint=0) { + //const offset = Tiling.spaces.getActiveSpace().width; + let point = this.apply_transform_to_point( + new Clutter.Vertex({x: xpoint, y: 0})); + this.tooltip.set_position(Math.max(0, point.x - 62), point.y + 34); + } + + _updateTooltipText() { + const markup = (color, mode) => { + this.tooltip.clutter_text + .set_markup( +` Window focus mode +Current mode: ${mode}`); + }; + if (this.mode === Tiling.FocusModes.DEFAULT) { + markup('#6be67b', 'DEFAULT'); + } + else if (this.mode === Tiling.FocusModes.CENTER) { + markup('#6be6cb', 'CENTER'); + } else { + this.tooltip.set_text(''); + } + } + + /** + * Set the mode that this icon will display. + * @param {Tiling.FocusModes} mode + */ + setMode(mode) { + mode = mode ?? Tiling.FocusModes.DEFAULT; + this.mode = mode; + if (mode === Tiling.FocusModes.DEFAULT) { + this.gicon = this.gIconDefault; + } + else if (mode === Tiling.FocusModes.CENTER) { + this.gicon = this.gIconCenter; + } + this._updateTooltipText() + return this; + } + + /** + * Sets visibility of icon. + * @param {boolean} visible + */ + setVisible(visible = true) { + this.visible = visible; + return this; + } +} +); + +var FocusButton = Utils.registerClass( +class FocusButton extends PanelMenu.Button { + _init() { + super._init(0.0, 'FocusMode'); + + this._icon = new FocusIcon({ + style_class: 'system-status-icon focus-mode-button' + }, -10); + + this.setFocusMode(); + this.add_child(this._icon); + this.connect('event', this._onClicked.bind(this)); + } + + /** + * Sets the focus mode with this button. + * @param {*} mode + */ + setFocusMode(mode) { + mode = mode ?? Tiling.FocusModes.DEFAULT; + this.focusMode = mode; + this._icon.setMode(mode); + return this; + } + + _onClicked(actor, event) { + if (Tiling.inPreview != Tiling.PreviewMode.NONE || Main.overview.visible) { + return Clutter.EVENT_PROPAGATE; + } + + if (event.type() !== Clutter.EventType.TOUCH_BEGIN && + event.type() !== Clutter.EventType.BUTTON_PRESS) { + return Clutter.EVENT_PROPAGATE; + } + + Tiling.switchToNextFocusMode(); + return Clutter.EVENT_PROPAGATE; + } +} +); + var WorkspaceMenu = Utils.registerClass( class WorkspaceMenu extends PanelMenu.Button { _init() { @@ -241,7 +390,6 @@ class WorkspaceMenu extends PanelMenu.Button { this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this._prefItem = new PopupMenu.PopupImageMenuItem('Workspace preference', 'preferences-system-symbolic'); this.menu.addMenuItem(this._prefItem); @@ -255,10 +403,8 @@ class WorkspaceMenu extends PanelMenu.Button { let temp_file = Gio.File.new_for_path(GLib.get_tmp_dir()).get_child('paperwm.workspace') temp_file.replace_contents(wi.toString(), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null) imports.misc.extensionUtils.openPrefs() - }); - // this.iconBox = new St.BoxLayout(); // this.menu.box.add(this.iconBox); @@ -350,7 +496,7 @@ class WorkspaceMenu extends PanelMenu.Button { && event.get_scroll_direction() === Clutter.ScrollDirection.SMOOTH) { let spaces = Tiling.spaces; - let active = spaces.spaceOf(workspaceManager.get_active_workspace()); + let active = spaces.getActiveSpace(); let [dx, dy] = event.get_scroll_delta(); dy *= active.height*0.05; @@ -462,7 +608,7 @@ class WorkspaceMenu extends PanelMenu.Button { if (!open) return; - let space = Tiling.spaces.spaceOf(workspaceManager.get_active_workspace()); + let space = Tiling.spaces.getActiveSpace(); this.entry.label.text = space.name; GLib.idle_add(GLib.PRIORITY_DEFAULT, this.entry.activate.bind(this.entry)); @@ -487,6 +633,7 @@ class WorkspaceMenu extends PanelMenu.Button { }); var menu; +var focusButton; var orginalActivitiesText; var screenSignals, signals; function init () { @@ -516,6 +663,15 @@ function enable () { Main.panel.addToStatusArea('WorkspaceMenu', menu, 0, 'left'); menu.show(); + focusButton = new FocusButton(); + Main.panel.addToStatusArea('FocusButton', focusButton, 1, 'left'); + signals.connect(focusButton, 'notify::allocation', () => { + const pos = focusButton.apply_relative_transform_to_point(panel, + new Clutter.Vertex({ x: 0, y: 0 })); + Tiling.spaces.setFocusIconPosition(pos.x, pos.y); + }); + fixFocusModeIcon(); + fixStyle(); screenSignals.push( @@ -549,6 +705,10 @@ function enable () { spaces.showWindowPositionBarChanged(); }); + signals.connect(Settings.settings, 'changed::show-focus-mode-icon', (settings, key) => { + fixFocusModeIcon(); + }); + signals.connect(panelBox, 'show', () => { fixTopBar(); }); @@ -574,6 +734,8 @@ function enable () { function disable() { signals.destroy(); + focusButton.destroy(); + focusButton = null; menu.destroy(); menu = null; Main.panel.statusArea.activities.actor.show(); @@ -626,6 +788,11 @@ function fixTopBar() { } } +function fixFocusModeIcon() { + prefs.show_focus_mode_icon ? focusButton.show() : focusButton.hide(); + Tiling.spaces.forEach(s => s.showFocusModeIcon()); +} + /** Override the activities label with the workspace name. let workspaceIndex = 0 @@ -635,8 +802,12 @@ function updateWorkspaceIndicator (index) { let space = spaces && spaces.spaceOf(workspaceManager.get_workspace_by_index(index)); let onMonitor = space && space.monitor === panelMonitor; let nav = Navigator.navigator - if (onMonitor || (Tiling.inPreview && nav && nav.from.monitor === panelMonitor)) + if (onMonitor || (Tiling.inPreview && nav && nav.from.monitor === panelMonitor)) { setWorkspaceName(space.name); + + // also update focus mode + focusButton.setFocusMode(space.focusMode); + } }; function setWorkspaceName (name) {