From 202aca7bf4eb4927658b82b627afb9dab254a551 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 10 Dec 2023 15:37:46 +0100 Subject: Merging upstream version 20231210. Signed-off-by: Daniel Baumann --- extensions/44/vertical-workspaces/lib/winTmb.js | 525 ++++++++++++++++++++++++ 1 file changed, 525 insertions(+) create mode 100644 extensions/44/vertical-workspaces/lib/winTmb.js (limited to 'extensions/44/vertical-workspaces/lib/winTmb.js') diff --git a/extensions/44/vertical-workspaces/lib/winTmb.js b/extensions/44/vertical-workspaces/lib/winTmb.js new file mode 100644 index 0000000..b18ea18 --- /dev/null +++ b/extensions/44/vertical-workspaces/lib/winTmb.js @@ -0,0 +1,525 @@ +/** + * V-Shell (Vertical Workspaces) + * WinTmb + * + * @author GdH + * @copyright 2021-2023 + * @license GPL-3.0 + */ + +'use strict'; + +const Clutter = imports.gi.Clutter; +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; +const St = imports.gi.St; + +const AltTab = imports.ui.altTab; +const DND = imports.ui.dnd; +const Main = imports.ui.main; + +let Me; +let opt; + +const SCROLL_ICON_OPACITY = 240; +const DRAG_OPACITY = 200; +const CLOSE_BTN_OPACITY = 240; + + +var WinTmbModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + + this._firstActivation = true; + this.moduleEnabled = false; + } + + cleanGlobals() { + Me = null; + opt = null; + } + + update(reset) { + this._removeTimeouts(); + + this.moduleEnabled = opt.get('windowThumbnailModule'); + + reset = reset || !this.moduleEnabled; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WinTmb - Keeping untouched'); + } + + _activateModule() { + this._timeouts = {}; + if (!this._windowThumbnails) + this._windowThumbnails = []; + + Main.overview.connectObject('hidden', () => this.showThumbnails(), this); + console.debug(' WinTmb - Activated'); + } + + _disableModule() { + Main.overview.disconnectObject(this); + this._disconnectStateAdjustment(); + this.removeAllThumbnails(); + console.debug(' WinTmb - Disabled'); + } + + _removeTimeouts() { + if (this._timeouts) { + Object.values(this._timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + this._timeouts = null; + } + } + + createThumbnail(metaWin) { + const thumbnail = new WindowThumbnail(metaWin, { + 'height': Math.floor(opt.WINDOW_THUMBNAIL_SCALE * global.display.get_monitor_geometry(global.display.get_current_monitor()).height), + 'thumbnailsOnScreen': this._windowThumbnails.length, + }); + + this._windowThumbnails.push(thumbnail); + thumbnail.connect('removed', tmb => { + this._windowThumbnails.splice(this._windowThumbnails.indexOf(tmb), 1); + tmb.destroy(); + if (!this._windowThumbnails.length) + this._disconnectStateAdjustment(); + }); + + if (!this._stateAdjustmentConId) { + this._stateAdjustmentConId = Main.overview._overview.controls._stateAdjustment.connectObject('notify::value', () => { + if (!this._thumbnailsHidden && (!opt.OVERVIEW_MODE2 || opt.WORKSPACE_MODE)) + this.hideThumbnails(); + }, this); + } + } + + hideThumbnails() { + this._windowThumbnails.forEach(tmb => { + tmb.ease({ + opacity: 0, + duration: 200, + mode: Clutter.AnimationMode.LINEAR, + onComplete: () => tmb.hide(), + }); + }); + this._thumbnailsHidden = true; + } + + showThumbnails() { + this._windowThumbnails.forEach(tmb => { + tmb.show(); + tmb.ease({ + opacity: 255, + duration: 100, + mode: Clutter.AnimationMode.LINEAR, + }); + }); + this._thumbnailsHidden = false; + } + + removeAllThumbnails() { + this._windowThumbnails.forEach(tmb => tmb.remove()); + this._windowThumbnails = []; + } + + _disconnectStateAdjustment() { + Main.overview._overview.controls._stateAdjustment.disconnectObject(this); + } +}; + +const WindowThumbnail = GObject.registerClass({ + Signals: { 'removed': {} }, +}, class WindowThumbnail extends St.Widget { + _init(metaWin, args) { + this._hoverShowsPreview = false; + this._customOpacity = 255; + this._initTmbHeight = args.height; + this._minimumHeight = Math.floor(5 / 100 * global.display.get_monitor_geometry(global.display.get_current_monitor()).height); + this._scrollTimeout = 100; + this._positionOffset = args.thumbnailsOnScreen; + this._reverseTmbWheelFunc = false; + this._click_count = 1; + this._prevBtnPressTime = 0; + this.w = metaWin; + super._init({ + layout_manager: new Clutter.BinLayout(), + visible: true, + reactive: true, + can_focus: true, + track_hover: true, + }); + this.connect('button-release-event', this._onBtnReleased.bind(this)); + this.connect('scroll-event', this._onScrollEvent.bind(this)); + // this.connect('motion-event', this._onMouseMove.bind(this)); // may be useful in the future.. + + this._delegate = this; + this._draggable = DND.makeDraggable(this, { dragActorOpacity: DRAG_OPACITY }); + this._draggable.connect('drag-end', this._end_drag.bind(this)); + this._draggable.connect('drag-cancelled', this._end_drag.bind(this)); + this._draggable._animateDragEnd = eventTime => { + this._draggable._animationInProgress = true; + this._draggable._onAnimationComplete(this._draggable._dragActor, eventTime); + this.opacity = this._customOpacity; + }; + + this.clone = new Clutter.Clone({ reactive: true }); + Main.layoutManager.addChrome(this); + + this.window = this.w.get_compositor_private(); + + this.clone.set_source(this.window); + + this.add_child(this.clone); + this._addCloseButton(); + this._addScrollModeIcon(); + + this.connect('enter-event', () => { + global.display.set_cursor(Meta.Cursor.POINTING_HAND); + this._closeButton.opacity = CLOSE_BTN_OPACITY; + this._scrollModeBin.opacity = SCROLL_ICON_OPACITY; + if (this._hoverShowsPreview && !Main.overview._shown) { + this._closeButton.opacity = 50; + this._showWindowPreview(false, true); + } + }); + + this.connect('leave-event', () => { + global.display.set_cursor(Meta.Cursor.DEFAULT); + this._closeButton.opacity = 0; + this._scrollModeBin.opacity = 0; + if (this._winPreview) + this._destroyWindowPreview(); + }); + + this._setSize(true); + this.set_position(...this._getInitialPosition()); + this.show(); + this.window_id = this.w.get_id(); + this.tmbRedrawDirection = true; + + // remove thumbnail content and hide thumbnail if its window is destroyed + this.windowConnect = this.window.connect('destroy', () => { + if (this) + this.remove(); + }); + } + + _getInitialPosition() { + const offset = 20; + let monitor = Main.layoutManager.monitors[global.display.get_current_monitor()]; + let x = Math.min(monitor.x + monitor.width - (this.window.width * this.scale) - offset); + let y = Math.min(monitor.y + monitor.height - (this.window.height * this.scale) - offset - ((this._positionOffset * this._initTmbHeight) % (monitor.height - this._initTmbHeight))); + return [x, y]; + } + + _setSize(resetScale = false) { + if (resetScale) + this.scale = Math.min(1.0, this._initTmbHeight / this.window.height); + + const width = this.window.width * this.scale; + const height = this.window.height * this.scale; + this.set_size(width, height); + if (this.icon) { + this.icon.scale_x = this.scale; + this.icon.scale_y = this.scale; + } + + // when the scale of this. actor change, this.clone resize accordingly, + // but the reactive area of the actor doesn't change until the actor is redrawn + // this updates the actor's input region area: + Main.layoutManager._queueUpdateRegions(); + } + + /* _onMouseMove(actor, event) { + let [pos_x, pos_y] = event.get_coords(); + let state = event.get_state(); + if (this._ctrlPressed(state)) { + } + }*/ + + _onBtnReleased(actor, event) { + // Clutter.Event.click_count property in no longer available, since GS42 + if ((event.get_time() - this._prevBtnPressTime) < Clutter.Settings.get_default().double_click_time) + this._click_count += 1; + else + this._click_count = 1; + + this._prevBtnPressTime = event.get_time(); + + if (this._click_count === 2 && event.get_button() === Clutter.BUTTON_PRIMARY) + this.w.activate(global.get_current_time()); + + + const button = event.get_button(); + const state = event.get_state(); + switch (button) { + case Clutter.BUTTON_PRIMARY: + if (this._ctrlPressed(state)) { + this._setSize(); + } else { + this._reverseTmbWheelFunc = !this._reverseTmbWheelFunc; + this._scrollModeBin.set_child(this._reverseTmbWheelFunc ? this._scrollModeSourceIcon : this._scrollModeResizeIcon); + } + return Clutter.EVENT_STOP; + case Clutter.BUTTON_SECONDARY: + if (this._ctrlPressed(state)) { + this.remove(); + } else { + this._hoverShowsPreview = !this._hoverShowsPreview; + this._showWindowPreview(); + } + return Clutter.EVENT_STOP; + case Clutter.BUTTON_MIDDLE: + if (this._ctrlPressed(state)) + this.w.delete(global.get_current_time()); + return Clutter.EVENT_STOP; + default: + return Clutter.EVENT_PROPAGATE; + } + } + + _onScrollEvent(actor, event) { + let direction = Me.Util.getScrollDirection(event); + + if (this._actionTimeoutActive()) + return Clutter.EVENT_PROPAGATE; + let state = event.get_state(); + switch (direction) { + case Clutter.ScrollDirection.UP: + if (this._shiftPressed(state)) { + this.opacity = Math.min(255, this.opacity + 24); + this._customOpacity = this.opacity; + } else if (this._reverseTmbWheelFunc !== this._ctrlPressed(state)) { + this._switchSourceWin(-1); + } else if (this._reverseTmbWheelFunc === this._ctrlPressed(state)) { + this.scale = Math.max(0.05, this.scale - 0.025); + } + break; + case Clutter.ScrollDirection.DOWN: + if (this._shiftPressed(state)) { + this.opacity = Math.max(48, this.opacity - 24); + this._customOpacity = this.opacity; + } else if (this._reverseTmbWheelFunc !== this._ctrlPressed(state)) { + this._switchSourceWin(+1); + } else if (this._reverseTmbWheelFunc === this._ctrlPressed(state)) { + this.scale = Math.min(1, this.scale + 0.025); + } + break; + default: + return Clutter.EVENT_PROPAGATE; + } + this._setSize(); + return Clutter.EVENT_STOP; + } + + remove() { + if (this.clone) { + this.window.disconnect(this.windowConnect); + this.clone.set_source(null); + } + if (this._winPreview) + this._destroyWindowPreview(); + + this.emit('removed'); + } + + _end_drag() { + this.set_position(this._draggable._dragOffsetX + this._draggable._dragX, this._draggable._dragOffsetY + this._draggable._dragY); + this._setSize(); + } + + _ctrlPressed(state) { + return (state & Clutter.ModifierType.CONTROL_MASK) !== 0; + } + + _shiftPressed(state) { + return (state & Clutter.ModifierType.SHIFT_MASK) !== 0; + } + + _switchSourceWin(direction) { + let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null); + windows = windows.filter(w => !(w.skip_taskbar || w.minimized)); + let idx = -1; + for (let i = 0; i < windows.length; i++) { + if (windows[i] === this.w) { + idx = i + direction; + break; + } + } + idx = idx >= windows.length ? 0 : idx; + idx = idx < 0 ? windows.length - 1 : idx; + let w = windows[idx]; + let win = w.get_compositor_private(); + this.clone.set_source(win); + this.window.disconnect(this.windowConnect); + // the new thumbnail should be the same height as the previous one + this.scale = (this.scale * this.window.height) / win.height; + this.window = win; + this.windowConnect = this.window.connect('destroy', () => { + if (this) + this.remove(); + }); + this.w = w; + + if (this._winPreview) + this._showWindowPreview(true); + } + + _actionTimeoutActive() { + const timeout = this._reverseTmbWheelFunc ? this._scrollTimeout : this._scrollTimeout / 4; + if (!this._lastActionTime || Date.now() - this._lastActionTime > timeout) { + this._lastActionTime = Date.now(); + return false; + } + return true; + } + + /* _setIcon() { + let tracker = Shell.WindowTracker.get_default(); + let app = tracker.get_window_app(this.w); + let icon = app + ? app.create_icon_texture(this.height) + : new St.Icon({ icon_name: 'icon-missing', icon_size: this.height }); + icon.x_expand = icon.y_expand = true; + if (this.icon) + this.icon.destroy(); + this.icon = icon; + }*/ + + _addCloseButton() { + const closeButton = new St.Button({ + opacity: 0, + style_class: 'window-close', + child: new St.Icon({ icon_name: 'preview-close-symbolic' }), + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.START, + x_expand: true, + y_expand: true, + }); + + closeButton.set_style(` + margin: 3px; + background-color: rgba(200, 0, 0, 0.9); + `); + + closeButton.connect('clicked', () => { + this.remove(); + return Clutter.EVENT_STOP; + }); + + this._closeButton = closeButton; + this.add_child(this._closeButton); + } + + _addScrollModeIcon() { + this._scrollModeBin = new St.Bin({ + x_expand: true, + y_expand: true, + }); + this._scrollModeResizeIcon = new St.Icon({ + icon_name: 'view-fullscreen-symbolic', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true, + opacity: SCROLL_ICON_OPACITY, + style_class: 'icon-dropshadow', + scale_x: 0.5, + scale_y: 0.5, + }); + this._scrollModeResizeIcon.set_style(` + margin: 13px; + color: rgb(255, 255, 255); + box-shadow: 0 0 40px 40px rgba(0,0,0,0.7); + `); + this._scrollModeSourceIcon = new St.Icon({ + icon_name: 'media-skip-forward-symbolic', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true, + opacity: SCROLL_ICON_OPACITY, + style_class: 'icon-dropshadow', + scale_x: 0.5, + scale_y: 0.5, + }); + this._scrollModeSourceIcon.set_style(` + margin: 13px; + color: rgb(255, 255, 255); + box-shadow: 0 0 40px 40px rgba(0,0,0,0.7); + `); + this._scrollModeBin.set_child(this._scrollModeResizeIcon); + this.add_child(this._scrollModeBin); + this._scrollModeBin.opacity = 0; + } + + _showWindowPreview(update = false, dontDestroy = false) { + if (this._winPreview && !dontDestroy) { + this._destroyWindowPreview(); + this._previewCreationTime = 0; + this._closeButton.opacity = CLOSE_BTN_OPACITY; + if (!update) + return; + } + + if (!this._winPreview) { + this._winPreview = new AltTab.CyclerHighlight(); + global.window_group.add_actor(this._winPreview); + [this._winPreview._xPointer, this._winPreview._yPointer] = global.get_pointer(); + } + + if (!update) { + this._winPreview.opacity = 0; + this._winPreview.ease({ + opacity: 255, + duration: 70, + mode: Clutter.AnimationMode.LINEAR, + /* onComplete: () => { + this._closeButton.opacity = 50; + },*/ + }); + + this.ease({ + opacity: Math.min(50, this._customOpacity), + duration: 70, + mode: Clutter.AnimationMode.LINEAR, + onComplete: () => { + }, + }); + } else { + this._winPreview.opacity = 255; + } + this._winPreview.window = this.w; + this._winPreview._window = this.w; + global.window_group.set_child_above_sibling(this._winPreview, null); + } + + _destroyWindowPreview() { + if (this._winPreview) { + this._winPreview.ease({ + opacity: 0, + duration: 100, + mode: Clutter.AnimationMode.LINEAR, + onComplete: () => { + this._winPreview.destroy(); + this._winPreview = null; + this.opacity = this._customOpacity; + }, + }); + } + } +}); -- cgit v1.2.3