diff --git a/tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg new file mode 100644 index 00000000..126bc597 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg @@ -0,0 +1,16 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg new file mode 100644 index 00000000..67c176a7 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg @@ -0,0 +1,22 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg new file mode 100644 index 00000000..a5bead91 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg @@ -0,0 +1,19 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg new file mode 100644 index 00000000..b05a1efa --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg @@ -0,0 +1,22 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg new file mode 100644 index 00000000..0fdeecbf --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg @@ -0,0 +1,19 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg new file mode 100644 index 00000000..62bb30f8 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg @@ -0,0 +1,28 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg new file mode 100644 index 00000000..9e951c38 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg @@ -0,0 +1,28 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg new file mode 100644 index 00000000..492a17a3 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg @@ -0,0 +1,28 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg new file mode 100644 index 00000000..c38b541b --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg @@ -0,0 +1,28 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg new file mode 100644 index 00000000..c2e1e192 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg @@ -0,0 +1,25 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg new file mode 100644 index 00000000..a333b058 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg @@ -0,0 +1,22 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg new file mode 100644 index 00000000..f6346e72 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg @@ -0,0 +1,22 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg new file mode 100644 index 00000000..3ee05ea9 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg @@ -0,0 +1,19 @@ + + + + diff --git a/tiling-assistant@leleat-on-github/prefs.js b/tiling-assistant@leleat-on-github/prefs.js index 2769cccc..5b63d72d 100644 --- a/tiling-assistant@leleat-on-github/prefs.js +++ b/tiling-assistant@leleat-on-github/prefs.js @@ -74,6 +74,7 @@ export default class Prefs extends ExtensionPreferences { 'enable-advanced-experimental-features', 'disable-tile-groups', 'low-performance-move-mode', + 'enable-layout-picker', 'monitor-switch-grace-period', 'adapt-edge-tiling-to-favorite-layout', 'enable-tile-animations', diff --git a/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml b/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml index 88c07c63..38f477ff 100644 --- a/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml +++ b/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml @@ -239,6 +239,9 @@ false + + true + false diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js new file mode 100644 index 00000000..1ec4a072 --- /dev/null +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -0,0 +1,402 @@ +import { Clutter, GObject, St, Gio } from '../dependencies/gi.js'; +import { Main, Extension } from '../dependencies/shell.js'; + +const LayoutPickerVisibility = { + HIDDEN: 0, + PEAK: 1, + SHOWN: 2 +}; + +function iconPath(name) { + const path = Extension.lookupByURL(import.meta.url) + .dir + .get_child(`media/${name}-symbolic.svg`) + .get_path(); + + return path; +} + +export const LayoutPickerTileType = Object.freeze({ + NONE: 0, + LEFT: 1, + RIGHT: 2, + TOP: 3, + BOTTOM: 4, + Q1: 5, + Q2: 6, + Q3: 7, + Q4: 8, + MAXIMIZE: 9 +}); + +// omit '-symbolic.svg' as it is managed by iconPath function already +const ICONS = { + vertical: { + none: 'tile-vertical', + + states: { + [LayoutPickerTileType.TOP]: 'tile-top', + [LayoutPickerTileType.BOTTOM]: 'tile-bottom' + } + }, + + horizontal: { + none: 'tile-horizontal', + + states: { + [LayoutPickerTileType.LEFT]: 'tile-left', + [LayoutPickerTileType.RIGHT]: 'tile-right' + } + }, + + quarter: { + none: 'tile-quarter', + + states: { + [LayoutPickerTileType.Q1]: 'tile-q1', + [LayoutPickerTileType.Q2]: 'tile-q2', + [LayoutPickerTileType.Q3]: 'tile-q3', + [LayoutPickerTileType.Q4]: 'tile-q4' + } + }, + + maximize: { + none: 'tile-base', + + states: { + [LayoutPickerTileType.MAXIMIZE]: 'tile-maximize' + } + } +}; + +export const LayoutPicker = GObject.registerClass({ + Properties: { + 'tile-type': GObject.ParamSpec.int( + 'tile-type', 'tile-type', 'tile-type', + GObject.ParamFlags.READWRITE, + LayoutPickerTileType.NONE, + LayoutPickerTileType.MAXIMIZE, + LayoutPickerTileType.NONE + ) + } +}, class LayoutPicker extends St.Bin { + _init() { + super._init({ + style_class: 'tiling-menu-container' + }); + + this._container = new St.BoxLayout({ + x_expand: true, + orientation: Clutter.Orientation.HORIZONTAL, + style_class: 'popup-menu-content' + }); + + this._visibility = LayoutPickerVisibility.HIDDEN; + this._dragging = false; + + this.set_child(this._container); + + this._addChrome(); + + this._icons = {}; + + for (const [group, config] of Object.entries(ICONS)) { + // start with icons that appear to be not hovered + const icon = this._createIcon(config.none); + + config.icon = icon; + this._icons[group] = icon; + + this._container.add_child(icon); + } + + // e.g a dock is enabled and or disabled + global.display.connectObject('workareas-changed', () => { + this._updateAllocation(global.display.get_current_monitor()); + }, this); + + Main.layoutManager.connectObject('monitors-changed', () => { + this._updateAllocation(global.display.get_current_monitor()); + }, this); + + // GNOME Shell doesn't disable extensions when shutting down, so stop + // reacting to monitor and work-area changes as soon as it shuts down. + global.connectObject('shutdown', () => { + global.display.disconnectObject(this); + Main.layoutManager.disconnectObject(this); + }, this); + + // The picker only needs to know when the move it reacts to is over, + // so it can hide itself without the move handler having to track it. + global.display.connectObject('grab-op-end', () => this.onMoveFinished(), this); + + // just in case extension is enabled and disable + this._updateAllocation(global.display.get_current_monitor()); + + this.connectObject('notify::translation-y', () => { + this.set_clip( + 0, + this.height - this.translation_y, + this.width, + this.height + ); + }, this); + } + + _setVisibility(visibility) { + if (this._visibility === visibility) + return; + + this._visibility = visibility; + + this.opacity = 255; + this.reactive = true; + + + let positions = [ + 0, + this._container.get_theme_node().get_padding(St.Side.Bottom) + + this.get_theme_node().get_padding(St.Side.BOTTOM), + this.height + ]; + + this.remove_all_transitions(); + + this.ease({ + translation_y: positions[visibility], + duration: 250, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this.opacity = this._visibility !== LayoutPickerVisibility.HIDDEN ? 255 : 0; + this.reactive = this._visibility !== LayoutPickerVisibility.HIDDEN; + + // once picker is fully shown onMoving might not be able to update tile type + // for situations like no cursor update after picker is fully shown. + // this could causes incorrect tile type shown. + if (this._visibility === LayoutPickerVisibility.SHOWN) { + let [curX, curY] = global.get_pointer(); + this._updateLayoutPickerTileType(curX, curY); + } + } + + }); + } + + onMoving(curX, curY, monitorIndex = global.display.get_current_monitor()) { + if (this._dragging === false) + return; + + this._updateAllocation(monitorIndex); + + let [w, h] = this.get_size(); + let [mx, my_] = this.get_transformed_position(); + + const themeNode = this.get_theme_node(); + let paddingLeft = themeNode.get_padding(St.Side.LEFT); + let paddingRight = themeNode.get_padding(St.Side.RIGHT); + let paddingBottom = this.get_theme_node().get_padding(St.Side.BOTTOM); + + const monitorArea = Main.layoutManager.monitors[monitorIndex]; + const activeWs = global.workspace_manager.get_active_workspace(); + const workArea = activeWs.get_work_area_for_monitor(monitorIndex); + + // using monitorArea.y instead of workArea.y as upper bound to compensate with chromes such us the top bar height + // placing cursor above workArea.y causes visibility glitch. workArea.y and monitorArea.y will be same for other monitor anyways. + if ( + curY >= monitorArea.y && + curY <= workArea.y + h - paddingBottom && + curX >= mx + paddingLeft && + curX <= mx + w - paddingRight + ) + this._setVisibility(LayoutPickerVisibility.SHOWN); + else + this._setVisibility(LayoutPickerVisibility.PEAK); + + if (this._visibility === LayoutPickerVisibility.SHOWN) + this._updateLayoutPickerTileType(curX, curY); + else + this._resetTileType(); + } + + onMoveStarted() { + this._dragging = true; + this._resetTileType(); + this._setVisibility(LayoutPickerVisibility.PEAK); + } + + onMoveFinished() { + this._dragging = false; + this._resetTileType(); + this._setVisibility(LayoutPickerVisibility.HIDDEN); + } + + _resetTileType() { + this.tileType = LayoutPickerTileType.NONE; + this._clearIcons(); + } + + _setLayoutPickerIcon(tileType) { + this._clearIcons(); + + for (const config of Object.values(ICONS)) { + const iconName = config.states[tileType]; + + if (!iconName) + continue; + + this._updateIcon(config.icon, iconName); + break; + } + } + + _createIcon(name) { + let fallback_gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath(name)) + ); + + let gicon = new Gio.ThemedIcon({ + name: `${name}-symbolic` + }); + + return new St.Icon({ + gicon, + fallback_gicon + }); + } + + _updateIcon(icon, name) { + let fallback_gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath(name)) + ); + + let gicon = new Gio.ThemedIcon({ + name: `${name}-symbolic` + }); + icon.set({ + gicon, + fallback_gicon + }); + } + + _clearIcons() { + for (const config of Object.values(ICONS)) + this._updateIcon(config.icon, config.none); + } + + _updateLayoutPickerTileType(curX, curY) { + let rect = icon => { + let [x, y] = icon.get_transformed_position(); + let [w, h] = icon.get_size(); + return { x, y, w, h }; + }; + + let contains = ({ x, y, w, h }) => + curX >= x && + curX <= x + w && + curY >= y && + curY <= y + h; + const horizontal = rect(this._icons.horizontal); + + if (contains(horizontal)) { + const leftPortion = curX < horizontal.x + horizontal.w / 2; + + this.tileType = leftPortion + ? LayoutPickerTileType.LEFT + : LayoutPickerTileType.RIGHT; + + this._setLayoutPickerIcon(this.tileType); + return; + } + + const vertical = rect(this._icons.vertical); + + if (contains(vertical)) { + const topPortion = curY <= vertical.y + vertical.h / 2; + + this.tileType = topPortion + ? LayoutPickerTileType.TOP + : LayoutPickerTileType.BOTTOM; + + this._setLayoutPickerIcon(this.tileType); + return; + } + + const quarter = rect(this._icons.quarter); + + if (contains(quarter)) { + const leftPortion = curX < quarter.x + quarter.w / 2; + const topPortion = curY <= quarter.y + quarter.h / 2; + + if (topPortion) + { this.tileType = leftPortion + ? LayoutPickerTileType.Q2 + : LayoutPickerTileType.Q1; } + else + { this.tileType = leftPortion + ? LayoutPickerTileType.Q3 + : LayoutPickerTileType.Q4; } + + this._setLayoutPickerIcon(this.tileType); + return; + } + + this.tileType = contains(rect(this._icons.maximize)) + ? LayoutPickerTileType.MAXIMIZE + : LayoutPickerTileType.NONE; + + this._setLayoutPickerIcon(this.tileType); + } + + _updateAllocation(monitorIndex) { + // The current monitor can already be gone (e.g. when the last monitor + // is removed during shutdown), so make sure it still exists before + // asking Mutter for its work area. + if (!Main.layoutManager.monitors[monitorIndex]) + return; + + const activeWs = global.workspace_manager.get_active_workspace(); + const workArea = activeWs.get_work_area_for_monitor(monitorIndex); + + if (workArea === null) + return; + + const [, natWidth] = this.get_preferred_width(-1); + const [, natHeight] = this.get_preferred_height(-1); + + const targetX = Math.round(workArea.x + (workArea.width - natWidth) / 2); + const targetY = Math.round(workArea.y - natHeight); + + if (targetX === this.x && targetY === this.y) + return; + + this.set_position(targetX, targetY); + + this.remove_all_transitions(); + + this._visibility = LayoutPickerVisibility.HIDDEN; + this.translation_y = 0; + this.opacity = 0; + this.reactive = false; + } + + _addChrome() { + Main.layoutManager.addChrome(this, { + affectsStruts: false, + trackFullscreen: false + }); + } + + _untrackChrome() { + Main.layoutManager.untrackChrome(this); + } + + destroy() { + this._untrackChrome(); + + global.display.disconnectObject(this); + Main.layoutManager.disconnectObject(this); + + super.destroy(); + } +}); + diff --git a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js index d224221f..56cd4b8a 100644 --- a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js +++ b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js @@ -5,6 +5,7 @@ import { WINDOW_ANIMATION_TIME } from '../dependencies/unexported/windowManager. import { MoveModes, Orientation, Settings } from '../common.js'; import { Rect, Util } from './utility.js'; import { TilingWindowManager as Twm } from './tilingWindowManager.js'; +import { LayoutPicker, LayoutPickerTileType } from './layoutPicker.js'; const [MajorShellVersion] = Util.getShellVersion(); @@ -108,6 +109,8 @@ export default class TilingMoveHandler { this ); handleWindowActionKeyConflict(); + + this._layoutPicker = new LayoutPicker(); } destroy() { @@ -119,6 +122,8 @@ export default class TilingMoveHandler { this._tilePreview.destroy(); + this._layoutPicker.destroy(); + if (this._latestMonitorLockTimerId) { GLib.Source.remove(this._latestMonitorLockTimerId); this._latestMonitorLockTimerId = null; @@ -126,7 +131,7 @@ export default class TilingMoveHandler { if (this._latestPreviewTimerId) { GLib.Source.remove(this._latestPreviewTimerId); - this._latestPreviewTimerId = null; + this._latestPreviewTimerId = 0; } if (this._restoreSizeTimerId) { @@ -158,6 +163,12 @@ export default class TilingMoveHandler { if (window.is_skip_taskbar()) return; + // The picker is driven by the pointer, so it is useless for a + // keyboard-driven move. + if (grabOp !== Meta.GrabOp.KEYBOARD_MOVING && + Settings.getBoolean('enable-layout-picker')) + this._layoutPicker.onMoveStarted(); + // Also work with a window, which was maximized by GNOME natively // because it may have been tiled with this extension before being // maximized so we need to restore its size to pre-tiling. @@ -172,6 +183,8 @@ export default class TilingMoveHandler { // Try to restore the window size if (window.tiledRect || this._wasMaximizedOnStart) { + this._layoutPicker.onMoveFinished(); + let counter = 0; this._restoreSizeTimerId && GLib.Source.remove(this._restoreSizeTimerId); this._restoreSizeTimerId = GLib.timeout_add(GLib.PRIORITY_HIGH_IDLE, 10, () => { @@ -334,6 +347,8 @@ export default class TilingMoveHandler { const [x, y] = this.getDragCoords(); const currPointerPos = { x, y }; + this._layoutPicker.onMoving(x, y, this._monitorNr); + if (lowPerfMode) { if (!this._isGrabOp) { this._movingTimerId = null; @@ -527,12 +542,20 @@ export default class TilingMoveHandler { const wRect = window.get_frame_rect(); const workArea = new Rect(window.get_work_area_for_monitor(this._monitorNr)); + const layoutPickerTileType = this._layoutPicker.tileType; + const isPicking = layoutPickerTileType !== LayoutPickerTileType.NONE; + const vDetectionSize = Settings.getInt('vertical-preview-area'); - const pointerAtTopEdge = this._lastPointerPos.y <= workArea.y + vDetectionSize; - const pointerAtBottomEdge = this._lastPointerPos.y >= workArea.y2 - vDetectionSize; + const pointerAtTopEdge = this._lastPointerPos.y <= workArea.y + vDetectionSize || + layoutPickerTileType === LayoutPickerTileType.TOP || + layoutPickerTileType === LayoutPickerTileType.MAXIMIZE; + const pointerAtBottomEdge = this._lastPointerPos.y >= workArea.y2 - vDetectionSize || + layoutPickerTileType === LayoutPickerTileType.BOTTOM; const hDetectionSize = Settings.getInt('horizontal-preview-area'); - const pointerAtLeftEdge = this._lastPointerPos.x <= workArea.x + hDetectionSize; - const pointerAtRightEdge = this._lastPointerPos.x >= workArea.x2 - hDetectionSize; + const pointerAtLeftEdge = this._lastPointerPos.x <= workArea.x + hDetectionSize || + layoutPickerTileType === LayoutPickerTileType.LEFT; + const pointerAtRightEdge = this._lastPointerPos.x >= workArea.x2 - hDetectionSize || + layoutPickerTileType === LayoutPickerTileType.RIGHT; // Also use window's pos for top and bottom area detection for quarters // because global.get_pointer's y isn't accurate (no idea why...) when // grabbing the titlebar & slowly going from the left/right sides to @@ -540,10 +563,17 @@ export default class TilingMoveHandler { const titleBarGrabbed = this._lastPointerPos.y - wRect.y < 50; const windowAtTopEdge = titleBarGrabbed && wRect.y === workArea.y; const windowAtBottomEdge = wRect.y >= workArea.y2 - 75; - const tileTopLeftQuarter = pointerAtLeftEdge && (pointerAtTopEdge || windowAtTopEdge); - const tileTopRightQuarter = pointerAtRightEdge && (pointerAtTopEdge || windowAtTopEdge); - const tileBottomLeftQuarter = pointerAtLeftEdge && (pointerAtBottomEdge || windowAtBottomEdge); - const tileBottomRightQuarter = pointerAtRightEdge && (pointerAtBottomEdge || windowAtBottomEdge); + // An explicit picker selection always takes precedence over the edge + // detection, otherwise a physically detected edge could override it + // (e.g. show a quarter while the picker selected a half). + const tileTopLeftQuarter = layoutPickerTileType === LayoutPickerTileType.Q2 || + !isPicking && pointerAtLeftEdge && (pointerAtTopEdge || windowAtTopEdge); + const tileTopRightQuarter = layoutPickerTileType === LayoutPickerTileType.Q1 || + !isPicking && pointerAtRightEdge && (pointerAtTopEdge || windowAtTopEdge); + const tileBottomLeftQuarter = layoutPickerTileType === LayoutPickerTileType.Q3 || + !isPicking && pointerAtLeftEdge && (pointerAtBottomEdge || windowAtBottomEdge); + const tileBottomRightQuarter = layoutPickerTileType === LayoutPickerTileType.Q4 || + !isPicking && pointerAtRightEdge && (pointerAtBottomEdge || windowAtBottomEdge); if (tileTopLeftQuarter) { this._tileRect = Twm.getTileFor('tile-topleft-quarter', workArea, this._monitorNr); @@ -564,10 +594,15 @@ export default class TilingMoveHandler { const shouldMaximize = isLandscape && !Settings.getBoolean('enable-hold-maximize-inverse-landscape') || !isLandscape && !Settings.getBoolean('enable-hold-maximize-inverse-portrait'); - const tileRect = shouldMaximize + const onlyMaximize = !isPicking && + Settings.getBoolean('enable-layout-picker'); + const tileRect = onlyMaximize || shouldMaximize && !isPicking || + layoutPickerTileType === LayoutPickerTileType.MAXIMIZE ? workArea : Twm.getTileFor('tile-top-half', workArea, this._monitorNr); - const holdTileRect = shouldMaximize + const holdTileRect = !onlyMaximize && + (shouldMaximize && !isPicking || + layoutPickerTileType === LayoutPickerTileType.TOP) ? Twm.getTileFor('tile-top-half', workArea, this._monitorNr) : workArea; // Dont open preview / start new timer if preview was already one for the top @@ -579,6 +614,14 @@ export default class TilingMoveHandler { this._tileRect = tileRect; this._tilePreview.open(window, this._tileRect.meta, this._monitorNr); + if (tileRect.equal(holdTileRect)) { + if (this._latestPreviewTimerId) { + GLib.Source.remove(this._latestPreviewTimerId); + this._latestPreviewTimerId = 0; + } + return; + } + let timerId = 0; this._latestPreviewTimerId && GLib.Source.remove(this._latestPreviewTimerId); this._latestPreviewTimerId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, @@ -592,7 +635,7 @@ export default class TilingMoveHandler { this._tilePreview.open(window, this._tileRect.meta, this._monitorNr); } - this._latestPreviewTimerId = null; + this._latestPreviewTimerId = 0; return GLib.SOURCE_REMOVE; }); timerId = this._latestPreviewTimerId; diff --git a/tiling-assistant@leleat-on-github/src/ui/prefs.ui b/tiling-assistant@leleat-on-github/src/ui/prefs.ui index a9b5a6cd..627afa2b 100644 --- a/tiling-assistant@leleat-on-github/src/ui/prefs.ui +++ b/tiling-assistant@leleat-on-github/src/ui/prefs.ui @@ -40,6 +40,20 @@ General view-app-grid-symbolic + + + + + + Layout Picker + + + Show Layout Picker + When moving a window, reveal a menu at the top of the screen to pick a tile (halves, quarters or maximize) with the pointer. + + + + diff --git a/tiling-assistant@leleat-on-github/stylesheet.css b/tiling-assistant@leleat-on-github/stylesheet.css index 27878117..d500907f 100644 --- a/tiling-assistant@leleat-on-github/stylesheet.css +++ b/tiling-assistant@leleat-on-github/stylesheet.css @@ -6,3 +6,17 @@ .layout-shortcut .boxed-list { box-shadow: none; } + +.tiling-menu-container { + padding: 20px; +} + +.tiling-menu-container > StBoxLayout { + spacing: 15px; + padding: 15px; + border-radius: 20px; +} + +.tiling-menu-container > StBoxLayout > StIcon { + icon-size: 64px; +}