%PDF- %PDF-
Direktori : /usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/extension/ |
Current File : //usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/extension/tilingPopup.js |
import { Clutter, GObject, Meta, St } from '../dependencies/gi.js'; import { Main, SwitcherPopup } from '../dependencies/shell.js'; import { Direction, Orientation } from '../common.js'; import { Util } from './utility.js'; import { TilingWindowManager as Twm } from './tilingWindowManager.js'; import * as AltTab from './altTab.js'; /** * Classes for the Tiling Popup, which opens when tiling a window * and there is free screen space to fill with other windows. * Mostly based on GNOME's altTab.js */ export const TilingSwitcherPopup = GObject.registerClass({ Signals: { // Bool indicates whether the Tiling Popup was canceled // (or if a window was tiled with this popup) 'closed': { param_types: [GObject.TYPE_BOOLEAN] } } }, class TilingSwitcherPopup extends AltTab.TilingAppSwitcherPopup { /** * @param {Meta.Windows[]} openWindows an array of Meta.Windows, which this * popup offers to tile. * @param {Rect} freeScreenRect the Rect, which the popup will tile a window * to. The popup will be centered in this rect. * @param {boolean} allowConsecutivePopup allow the popup to create another * Tiling Popup, if there is still unambiguous free screen space after * this popup tiled a window. * @param {boolean} skipAnim */ _init(openWindows, freeScreenRect, allowConsecutivePopup = true, skipAnim = false) { this._freeScreenRect = freeScreenRect; this._shadeBG = null; this._monitor = -1; SwitcherPopup.SwitcherPopup.prototype._init.call(this); this._thumbnails = null; this._thumbnailTimeoutId = 0; this._currentWindow = -1; this.thumbnailsVisible = false; // The window, which was tiled with the Tiling Popup after it's closed // or null, if the popup was closed with tiling a window this.tiledWindow = null; this._allowConsecutivePopup = allowConsecutivePopup; this._skipAnim = skipAnim; this._switcherList = new TSwitcherList(this, openWindows); this._items = this._switcherList.icons; // Destroy popup when touching outside of popup this.connect('touch-event', () => { if (Meta.is_wayland_compositor()) this.fadeAndDestroy(); return Clutter.EVENT_PROPAGATE; }); } /** * @param {Array} tileGroup an array of Meta.Windows. When the popup * appears it will shade the background. These windows will won't * be affected by that. * @returns if the popup was successfully shown. */ show(tileGroup) { this._monitor = tileGroup[0]?.get_monitor() ?? global.display.get_current_monitor(); if (!this._items.length) return false; const grab = Main.pushModal(this); // We expect at least a keyboard grab here if ((grab.get_seat_state() & Clutter.GrabState.KEYBOARD) === 0) { Main.popModal(grab); return false; } this._grab = grab; this._haveModal = true; this._switcherList.connect('item-activated', this._itemActivated.bind(this)); this._switcherList.connect('item-entered', this._itemEntered.bind(this)); this._switcherList.connect('item-removed', this._itemRemoved.bind(this)); this.add_child(this._switcherList); // Need to force an allocation so we can figure out // whether we need to scroll when selecting this.visible = true; this.get_allocation_box(); this._select(0); Main.osdWindowManager.hideAll(); this._shadeBackground(tileGroup); this.opacity = 0; this.ease({ opacity: 255, duration: 200, mode: Clutter.AnimationMode.EASE_OUT_QUAD }); return true; } _shadeBackground(tileGroup) { const tiledWindow = tileGroup[0]; const activeWs = global.workspace_manager.get_active_workspace(); const workArea = activeWs.get_work_area_for_monitor(this._monitor); this._shadeBG = new St.Widget({ style: 'background-color : black', x: workArea.x, y: workArea.y, width: workArea.width, height: workArea.height, opacity: 0 }); global.window_group.add_child(this._shadeBG); this._shadeBG.ease({ opacity: 180, duration: 200, mode: Clutter.AnimationMode.EASE_OUT_QUAD }); if (!tiledWindow) return; // Clones to correctly shade the background for consecutive tiling. for (let i = 1; i < tileGroup.length; i++) { const wActor = tileGroup[i].get_compositor_private(); const clone = new Clutter.Clone({ source: wActor, x: wActor.x, y: wActor.y }); global.window_group.add_child(clone); wActor.hide(); this.connect('destroy', () => { wActor.show(); clone.destroy(); }); } const tActor = tiledWindow.get_compositor_private(); global.window_group.set_child_above_sibling(tActor, this._shadeBG); } vfunc_allocate(box) { this.set_allocation(box); const freeScreenRect = this._freeScreenRect; const childBox = new Clutter.ActorBox(); const leftPadding = this.get_theme_node().get_padding(St.Side.LEFT); const rightPadding = this.get_theme_node().get_padding(St.Side.RIGHT); const hPadding = leftPadding + rightPadding; const [, childNaturalHeight] = this._switcherList.get_preferred_height( freeScreenRect.width - hPadding); const [, childNaturalWidth] = this._switcherList.get_preferred_width(childNaturalHeight); childBox.x1 = Math.max(freeScreenRect.x + leftPadding, freeScreenRect.x + Math.floor((freeScreenRect.width - childNaturalWidth) / 2)); childBox.x2 = Math.min(freeScreenRect.x2 - rightPadding, childBox.x1 + childNaturalWidth); childBox.y1 = freeScreenRect.y + Math.floor((freeScreenRect.height - childNaturalHeight) / 2); childBox.y2 = childBox.y1 + childNaturalHeight; this._switcherList.allocate(childBox); if (this._thumbnails) { const cbox = this._switcherList.get_allocation_box(); const monitor = global.display.get_monitor_geometry(this._monitor); const leftPadd = this.get_theme_node().get_padding(St.Side.LEFT); const rightPadd = this.get_theme_node().get_padding(St.Side.RIGHT); const bottomPadding = this.get_theme_node().get_padding(St.Side.BOTTOM); const hPadd = leftPadd + rightPadd; const icon = this._items[this._selectedIndex]; const [posX] = icon.get_transformed_position(); const thumbnailCenter = posX + icon.width / 2; const [, cNatWidth] = this._thumbnails.get_preferred_width(-1); cbox.x1 = Math.max(monitor.x + leftPadd, Math.floor(thumbnailCenter - cNatWidth / 2) ); if (cbox.x1 + cNatWidth > monitor.x + monitor.width - hPadd) { const offset = cbox.x1 + cNatWidth - monitor.width + hPadd; cbox.x1 = Math.max(monitor.x + leftPadd, cbox.x1 - offset - hPadd); } const spacing = this.get_theme_node().get_length('spacing'); cbox.x2 = cbox.x1 + cNatWidth; if (cbox.x2 > monitor.x + monitor.width - rightPadd) cbox.x2 = monitor.x + monitor.width - rightPadd; cbox.y1 = this._switcherList.allocation.y2 + spacing; this._thumbnails.addClones(monitor.y + monitor.height - bottomPadding - cbox.y1); const [, cNatHeight] = this._thumbnails.get_preferred_height(-1); cbox.y2 = cbox.y1 + cNatHeight; this._thumbnails.allocate(cbox); } } vfunc_button_press_event(buttonEvent) { const btn = buttonEvent.get_button(); if (btn === Clutter.BUTTON_MIDDLE || btn === Clutter.BUTTON_SECONDARY) { this._finish(global.get_current_time()); return Clutter.EVENT_PROPAGATE; } return super.vfunc_button_press_event(buttonEvent); } _keyPressHandler(keysym) { const moveUp = Util.isDirection(keysym, Direction.N); const moveDown = Util.isDirection(keysym, Direction.S); const moveLeft = Util.isDirection(keysym, Direction.W); const moveRight = Util.isDirection(keysym, Direction.E); if (this._thumbnailsFocused) { if (moveLeft) this._select(this._selectedIndex, this._previousWindow()); else if (moveRight) this._select(this._selectedIndex, this._nextWindow()); else if (moveUp || moveDown) this._select(this._selectedIndex, null, true); else return Clutter.EVENT_PROPAGATE; } else if (moveLeft) { this._select(this._previous()); } else if (moveRight) { this._select(this._next()); } else if (moveDown || moveUp) { this._select(this._selectedIndex, 0); } else { return Clutter.EVENT_PROPAGATE; } return Clutter.EVENT_STOP; } _windowActivated(thumbnailSwitcher, n) { const window = this._items[this._selectedIndex].cachedWindows[n]; this._tileWindow(window); this.fadeAndDestroy(); } _finish(timestamp) { const appIcon = this._items[this._selectedIndex]; const window = appIcon.cachedWindows[Math.max(0, this._currentWindow)]; this._tileWindow(window); SwitcherPopup.SwitcherPopup.prototype._finish.call(this, timestamp); } fadeAndDestroy() { if (this._alreadyDestroyed) return; this._alreadyDestroyed = true; const canceled = !this.tiledWindow; this.emit('closed', canceled); this._shadeBG?.destroy(); this._shadeBG = null; super.fadeAndDestroy(); } _tileWindow(window) { let rect = this._freeScreenRect; // Halve the tile rect. // If isShiftPressed, then put the window at the top / left side; // if isAltPressed, then put it at the bottom / right side. // The orientation depends on the available screen space. const isShiftPressed = Util.isModPressed(Clutter.ModifierType.SHIFT_MASK); const isAltPressed = Util.isModPressed(Clutter.ModifierType.MOD1_MASK); if (isShiftPressed || isAltPressed) { // Prefer vertical a bit more (because screens are usually horizontal) const vertical = rect.width >= rect.height * 1.25; const size = vertical ? 'width' : 'height'; const orientation = vertical ? Orientation.V : Orientation.H; const idx = isShiftPressed ? 0 : 1; rect = rect.getUnitAt(idx, rect[size] / 2, orientation); } this.tiledWindow = window; window.change_workspace(global.workspace_manager.get_active_workspace()); // We want to activate/focus the window after it was tiled with the // Tiling Popup. Calling activate/focus() after tile() doesn't seem to // work for GNOME Terminal if it is maximized before trying to tile it. // It won't be tiled properly in that case for some reason... Instead // activate first but clear the tiling signals before so that the old // tile group won't be accidentally raised. Twm.clearTilingProps(window.get_id()); window.activate(global.get_current_time()); Twm.tile(window, rect, { monitorNr: this._monitor, openTilingPopup: this._allowConsecutivePopup, skipAnim: this._skipAnim }); } // Dont _finish(), if no mods are pressed _resetNoModsTimeout() { } }); const TSwitcherList = GObject.registerClass( class TilingSwitcherList extends AltTab.TilingAppSwitcher { _setIconSize() { let j = 0; while (this._items.length > 1 && this._items[j].style_class !== 'item-box') j++; const themeNode = this._items[j].get_theme_node(); this._list.ensure_style(); const iconPadding = themeNode.get_horizontal_padding(); const iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT); const [, labelNaturalHeight] = this.icons[j].label.get_preferred_height(-1); const iconSpacing = labelNaturalHeight + iconPadding + iconBorder; const totalSpacing = this._list.spacing * (this._items.length - 1); const freeScreenRect = this._altTabPopup._freeScreenRect; const parentPadding = this.get_parent().get_theme_node().get_horizontal_padding(); const availWidth = freeScreenRect.width - parentPadding - this.get_theme_node().get_horizontal_padding(); const scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; const baseIconSizes = [96, 64, 48, 32, 22]; const iconSizes = baseIconSizes.map(s => s * scaleFactor); let iconSize = baseIconSizes[0]; if (this._items.length > 1) { for (let i = 0; i < baseIconSizes.length; i++) { iconSize = baseIconSizes[i]; const height = iconSizes[i] + iconSpacing; const w = height * this._items.length + totalSpacing; if (w <= availWidth) break; } } this._iconSize = iconSize; for (let i = 0; i < this.icons.length; i++) this.icons[i].set_size(iconSize); } });