From 01f5d38d721f4902bdba55232e7e6b5711ae106e Mon Sep 17 00:00:00 2001 From: ochi12 Date: Wed, 13 May 2026 22:07:36 +0800 Subject: [PATCH 01/18] Initial commit: Add symbolic icons --- .../media/tile-bottom-symbolic.svg | 23 +++++++++++++++ .../media/tile-horizontal-symbolic.svg | 20 +++++++++++++ .../media/tile-left-symbolic.svg | 23 +++++++++++++++ .../media/tile-q1-symbolic.svg | 29 +++++++++++++++++++ .../media/tile-q2-symbolic.svg | 29 +++++++++++++++++++ .../media/tile-q3-symbolic.svg | 29 +++++++++++++++++++ .../media/tile-q4-symbolic.svg | 29 +++++++++++++++++++ .../media/tile-quarter-symbolic.svg | 26 +++++++++++++++++ .../media/tile-right-symbolic.svg | 23 +++++++++++++++ .../media/tile-top-symbolic.svg | 23 +++++++++++++++ .../media/tile-vertical-symbolic.svg | 20 +++++++++++++ 11 files changed, 274 insertions(+) create mode 100644 tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg 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..73590765 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg @@ -0,0 +1,23 @@ + + + + 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..9ee212e7 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg @@ -0,0 +1,20 @@ + + + + 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..01b8df7b --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg @@ -0,0 +1,23 @@ + + + + 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..a487bb80 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg @@ -0,0 +1,29 @@ + + + + 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..692704c1 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg @@ -0,0 +1,29 @@ + + + + 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..f507b11d --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg @@ -0,0 +1,29 @@ + + + + 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..2e8ef256 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg @@ -0,0 +1,29 @@ + + + + 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..d4fd1a10 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg @@ -0,0 +1,26 @@ + + + + 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..bea76035 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg @@ -0,0 +1,23 @@ + + + + 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..bd08659d --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg @@ -0,0 +1,23 @@ + + + + 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..5d80e794 --- /dev/null +++ b/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg @@ -0,0 +1,20 @@ + + + + From c729107f9c4c17d195e136dd3e42e4f21dffb20d Mon Sep 17 00:00:00 2001 From: ochi12 Date: Wed, 13 May 2026 22:15:27 +0800 Subject: [PATCH 02/18] Add css entires for theming the tile picker --- tiling-assistant@leleat-on-github/stylesheet.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tiling-assistant@leleat-on-github/stylesheet.css b/tiling-assistant@leleat-on-github/stylesheet.css index 27878117..ffe977fe 100644 --- a/tiling-assistant@leleat-on-github/stylesheet.css +++ b/tiling-assistant@leleat-on-github/stylesheet.css @@ -6,3 +6,18 @@ .layout-shortcut .boxed-list { box-shadow: none; } + +.tiling-menu-container { + padding: 0; + padding-top: 6px; +} + +.tiling-menu-container > StBoxLayout { + spacing: 15px; + padding: 15px; + border-radius: 20px; +} + +.tiling-menu-container > StBoxLayout > StIcon { + icon-size: 64px; +} From 8520717232d0c787597bacfd7ac1368d542dd5c8 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Wed, 13 May 2026 22:16:20 +0800 Subject: [PATCH 03/18] Add File: layoutPicker.js this file exports LayoutPicker. A widget where user can hover-select a tile layout. Its current state is purely visual and is not yet integrated with moveHandler.js. --- .../src/extension/layoutPicker.js | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 tiling-assistant@leleat-on-github/src/extension/layoutPicker.js 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..97fbafcd --- /dev/null +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -0,0 +1,294 @@ +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; +} + +const LayoutPickerTileType = { + NONE: 0, + LEFT: 1, + RIGHT: 2, + TOP: 3, + BOTTOM: 4, + Q1: 5, + Q2: 6, + Q3: 7, + Q4: 8 +}; + +export const LayoutPicker = GObject.registerClass( +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.set_child(this._container); + + this._addChrome(); + + this.reactive = true; + this.visible = false; + + this._vertIcon = this._createIcon(iconPath('tile-vertical')); + this._horIcon = this._createIcon(iconPath('tile-horizontal')); + this._quartIcon = this._createIcon(iconPath('tile-quarter')); + + this._container.add_child(this._vertIcon); + this._container.add_child(this._horIcon); + this._container.add_child(this._quartIcon); + + this._tileType = LayoutPickerTileType.NONE; + + + // Defer allocation until after the actor has been laid out + this._allocationId = this.connect('notify::allocation', () => { + this.disconnect(this._allocationId); + this._allocationId = null; + this._updateAllocation(); + }); + } + + get tileType() { + return this._tileType; + } + + _setVisibility(visibility) { + this._visibility = visibility; + this._updateAllocation(); + } + + onMoving(curX, curY) { + let [mx, my] = this.get_transformed_position(); + let [w, h] = this.get_size(); + + const monitorIndex = global.display.get_current_monitor(); + const monitorArea = Main.layoutManager.monitors[monitorIndex]; + const monitorY = monitorArea ? monitorArea.y : my; // fallback to my just in case monitorArea is null; + + // 1.75 acts as an early trigger when transitioning from PEAK to SHOWN. + // This ensures the picker becomes visible before the cursor reaches it. + // The multiplier is only applied for PEAK → SHOWN, not SHOWN → PEAK. + const triggerHeight = this._visibility === LayoutPickerVisibility.PEAK ? h * 1.75 : h; + + // using monitorY instead (my) as upper bound to compensate with chromes such us the top bar height + // placing cursor above my could cause glitch. (my) and monitorY will be same for other monitor anyways. + if ( + curY >= monitorY && + curY <= my + triggerHeight && + curX >= mx && + curX <= mx + w + ) { + this._setVisibility(LayoutPickerVisibility.SHOWN); + } else { + this._setVisibility(LayoutPickerVisibility.PEAK); + } + + + this._updateLayoutPickerTileType(curX, curY); + } + + onMoveStarted() { + this._setVisibility(LayoutPickerVisibility.PEAK); + } + + onMoveFinished() { + this._setVisibility(LayoutPickerVisibility.HIDDEN); + } + + _setLayoutPickerIcon(hoveredType) { + this._clearIcons(); + switch (hoveredType) { + case LayoutPickerTileType.NONE: { + break; + } + case LayoutPickerTileType.LEFT: { + this._horIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-left')) + ); + break; + } + case LayoutPickerTileType.RIGHT: { + this._horIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-right')) + ); + break; + } + + case LayoutPickerTileType.TOP: { + this._vertIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-top')) + ); + break; + } + + case LayoutPickerTileType.BOTTOM: { + this._vertIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-bottom')) + ); + break; + } + + case LayoutPickerTileType.Q1: { + this._quartIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-q1')) + ); + break; + } + + case LayoutPickerTileType.Q2: { + this._quartIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-q2')) + ); + break; + } + + case LayoutPickerTileType.Q3: { + this._quartIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-q3')) + ); + break; + } + + case LayoutPickerTileType.Q4: { + this._quartIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-q4')) + ); + break; + } + } + } + + _createIcon(path) { + let gicon = Gio.FileIcon.new( + Gio.File.new_for_path(path) + ); + + return new St.Icon({ + gicon + }); + } + + _clearIcons() { + this._vertIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-vertical')) + ); + this._horIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-horizontal')) + ); + this._quartIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-quarter')) + ); + } + + _updateLayoutPickerTileType(curX, curY) { + let rect = icon => { + let [mx, my] = icon.get_transformed_position(); + let [mw, mh] = icon.get_size(); + return [mx, my, mw, mh]; + }; + let contains = (icon, x, y) => { + let [mx, my, mw, mh] = rect(icon); + return x >= mx && x <= mx + mw && + y >= my && y <= my + mh; + }; + + if (contains(this._horIcon, curX, curY)) { + let [mx, my_, mw, mh_] = rect(this._horIcon); + let mid = mx + (mw * 0.5); + + if (curX <= mid) + this._tileType = LayoutPickerTileType.LEFT; + else + this._tileType = LayoutPickerTileType.RIGHT; + } else if (contains(this._vertIcon, curX, curY)) { + let [mx_, my, mw_, mh] = rect(this._vertIcon); + let mid = my + (mh * 0.5); + + if (curY <= mid) + this._tileType = LayoutPickerTileType.TOP; + else + this._tileType = LayoutPickerTileType.BOTTOM; + } else if (contains(this._quartIcon, curX, curY)) { + let [mx, my, mw, mh] = rect(this._quartIcon); + let midX = mx + (mw * 0.5); + let midY = my + (mh * 0.5); + + if (curX >= midX && curY <= midY) + this._tileType = LayoutPickerTileType.Q1; + else if (curX < midX && curY <= midY) + this._tileType = LayoutPickerTileType.Q2; + else if (curX < midX && curY > midY) + this._tileType = LayoutPickerTileType.Q3; + else + this._tileType = LayoutPickerTileType.Q4; + } + else { + this._tileType = LayoutPickerTileType.NONE; + } + + this._setLayoutPickerIcon(this._tileType); + } + + _updateAllocation() { + const activeWs = global.workspace_manager.get_active_workspace(); + const monitorIndex = global.display.get_current_monitor(); + const workArea = activeWs.get_work_area_for_monitor(monitorIndex); + + if (!workArea) + return; + + this.x = (workArea.x + (workArea.width * 0.5)) - this.width * 0.5; + + this.visible = true; + + if (this._visibility === LayoutPickerVisibility.HIDDEN) { + this.y = workArea.y - this.height; + this.visible = false; + } else if (this._visibility === LayoutPickerVisibility.PEAK) { + this.y = workArea.y - this.height + this._container.get_theme_node().get_padding(St.Side.Bottom); + } else if (this._visibility === LayoutPickerVisibility.SHOWN) { + this.y = workArea.y; + } + + this.set_clip(0, Math.abs(this.y - workArea.y), workArea.width, workArea.height); + + } + + _addChrome() { + Main.layoutManager.addChrome(this); + } + + _untrackChrome() { + Main.layoutManager.untrackChrome(this); + } + + destroy() { + this._untrackChrome(); + + this._container?.destroy(); + this._container = null; + + super.destroy(); + } +}); + From 4f83e4645c9a19ac9e90c19295ae2e23853e9fd5 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Wed, 13 May 2026 22:19:33 +0800 Subject: [PATCH 04/18] Add initial integration with moveHandler.js. layout picker is instantiated in TilingMoveHandler class. This commit does not fully integrate layout picker yet but window drag movement is already integrated in this commit. --- .../src/extension/moveHandler.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js index 53034a4b..d7203288 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 } from './layoutPicker.js'; /** * This class gets to handle the move events (grab & monitor change) of windows. @@ -93,6 +94,9 @@ export default class TilingMoveHandler { this ); handleWindowActionKeyConflict(); + + this._layoutPicker = new LayoutPicker(); + } destroy() { @@ -103,6 +107,8 @@ export default class TilingMoveHandler { global.stage.disconnectObject(this); this._tilePreview.destroy(); + + this._layoutPicker.destroy(); if (this._latestMonitorLockTimerId) { GLib.Source.remove(this._latestMonitorLockTimerId); @@ -143,6 +149,8 @@ export default class TilingMoveHandler { if (window.is_skip_taskbar()) return; + 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. @@ -237,6 +245,9 @@ export default class TilingMoveHandler { } _onMoveFinished(window) { + + this._layoutPicker.onMoveFinished(); + try { window.assertExistence(); @@ -304,6 +315,8 @@ export default class TilingMoveHandler { const [x, y] = this.getDragCoords(); const currPointerPos = { x, y }; + this._layoutPicker.onMoving(x, y) + if (lowPerfMode) { if (!this._isGrabOp) { this._movingTimerId = null; From e2f92caae845e19f17c37efaefc0206cc04cf4f5 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Thu, 14 May 2026 01:43:27 +0800 Subject: [PATCH 05/18] Integrate layout picker with tiling preview In this commit window snap now works from layout picker. This commit also includes blocking some snapping behavior when currently picking from the layout picker. --- .../src/extension/layoutPicker.js | 65 ++++++++++--------- .../src/extension/moveHandler.js | 53 +++++++++------ 2 files changed, 68 insertions(+), 50 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index 97fbafcd..b2696d5f 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -16,7 +16,7 @@ function iconPath(name) { return path; } -const LayoutPickerTileType = { +export const LayoutPickerTileType = { NONE: 0, LEFT: 1, RIGHT: 2, @@ -66,39 +66,44 @@ class LayoutPicker extends St.Bin { this._allocationId = null; this._updateAllocation(); }); + + this._dragging = false; } get tileType() { return this._tileType; } + get picking() { + return this._tileType !== LayoutPickerTileType.NONE; + } + _setVisibility(visibility) { this._visibility = visibility; + console.log(this._visibility); this._updateAllocation(); } onMoving(curX, curY) { + if (this._dragging === false) + return; + let [mx, my] = this.get_transformed_position(); let [w, h] = this.get_size(); - - const monitorIndex = global.display.get_current_monitor(); + + const monitorIndex = global.display.get_current_monitor(); const monitorArea = Main.layoutManager.monitors[monitorIndex]; - const monitorY = monitorArea ? monitorArea.y : my; // fallback to my just in case monitorArea is null; - - // 1.75 acts as an early trigger when transitioning from PEAK to SHOWN. - // This ensures the picker becomes visible before the cursor reaches it. - // The multiplier is only applied for PEAK → SHOWN, not SHOWN → PEAK. - const triggerHeight = this._visibility === LayoutPickerVisibility.PEAK ? h * 1.75 : h; - - // using monitorY instead (my) as upper bound to compensate with chromes such us the top bar height - // placing cursor above my could cause glitch. (my) and monitorY will be same for other monitor anyways. - if ( - curY >= monitorY && - curY <= my + triggerHeight && - curX >= mx && - curX <= mx + w - ) { - this._setVisibility(LayoutPickerVisibility.SHOWN); + const monitorY = monitorArea ? monitorArea.y : my; // fallback to my just in case monitorArea is null; + + // 1.75 acts as an early trigger when transitioning from PEAK to SHOWN. + // This ensures the picker becomes visible before the cursor reaches it. + // The multiplier is only applied for PEAK → SHOWN, not SHOWN → PEAK. + const triggerHeight = this._visibility === LayoutPickerVisibility.PEAK ? h * 1.75 : h; + + // using monitorY instead (my) as upper bound to compensate with chromes such us the top bar height + // placing cursor above my could cause glitch. (my) and monitorY will be same for other monitor anyways. + if (curY >= monitorY && curY <= my + triggerHeight && curX >= mx && curX <= mx + w) { + this._setVisibility(LayoutPickerVisibility.SHOWN); } else { this._setVisibility(LayoutPickerVisibility.PEAK); } @@ -108,10 +113,12 @@ class LayoutPicker extends St.Bin { } onMoveStarted() { + this._dragging = true; this._setVisibility(LayoutPickerVisibility.PEAK); } onMoveFinished() { + this._dragging = false; this._setVisibility(LayoutPickerVisibility.HIDDEN); } @@ -208,8 +215,7 @@ class LayoutPicker extends St.Bin { }; let contains = (icon, x, y) => { let [mx, my, mw, mh] = rect(icon); - return x >= mx && x <= mx + mw && - y >= my && y <= my + mh; + return x >= mx && x <= mx + mw && y >= my && y <= my + mh; }; if (contains(this._horIcon, curX, curY)) { @@ -218,7 +224,7 @@ class LayoutPicker extends St.Bin { if (curX <= mid) this._tileType = LayoutPickerTileType.LEFT; - else + else this._tileType = LayoutPickerTileType.RIGHT; } else if (contains(this._vertIcon, curX, curY)) { let [mx_, my, mw_, mh] = rect(this._vertIcon); @@ -226,7 +232,7 @@ class LayoutPicker extends St.Bin { if (curY <= mid) this._tileType = LayoutPickerTileType.TOP; - else + else this._tileType = LayoutPickerTileType.BOTTOM; } else if (contains(this._quartIcon, curX, curY)) { let [mx, my, mw, mh] = rect(this._quartIcon); @@ -235,11 +241,11 @@ class LayoutPicker extends St.Bin { if (curX >= midX && curY <= midY) this._tileType = LayoutPickerTileType.Q1; - else if (curX < midX && curY <= midY) + else if (curX < midX && curY <= midY) this._tileType = LayoutPickerTileType.Q2; - else if (curX < midX && curY > midY) + else if (curX < midX && curY > midY) this._tileType = LayoutPickerTileType.Q3; - else + else this._tileType = LayoutPickerTileType.Q4; } else { @@ -269,17 +275,16 @@ class LayoutPicker extends St.Bin { } else if (this._visibility === LayoutPickerVisibility.SHOWN) { this.y = workArea.y; } - + this.set_clip(0, Math.abs(this.y - workArea.y), workArea.width, workArea.height); - } _addChrome() { - Main.layoutManager.addChrome(this); + Main.layoutManager.addChrome(this); } _untrackChrome() { - Main.layoutManager.untrackChrome(this); + Main.layoutManager.untrackChrome(this); } destroy() { diff --git a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js index d7203288..6b40254b 100644 --- a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js +++ b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js @@ -5,7 +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 } from './layoutPicker.js'; +import { LayoutPicker, LayoutPickerTileType } from './layoutPicker.js'; /** * This class gets to handle the move events (grab & monitor change) of windows. @@ -95,8 +95,7 @@ export default class TilingMoveHandler { ); handleWindowActionKeyConflict(); - this._layoutPicker = new LayoutPicker(); - + this._layoutPicker = new LayoutPicker(); } destroy() { @@ -107,8 +106,8 @@ export default class TilingMoveHandler { global.stage.disconnectObject(this); this._tilePreview.destroy(); - - this._layoutPicker.destroy(); + + this._layoutPicker.destroy(); if (this._latestMonitorLockTimerId) { GLib.Source.remove(this._latestMonitorLockTimerId); @@ -148,7 +147,7 @@ export default class TilingMoveHandler { _onMoveStarted(window, grabOp) { if (window.is_skip_taskbar()) return; - + this._layoutPicker.onMoveStarted(); // Also work with a window, which was maximized by GNOME natively @@ -245,8 +244,7 @@ export default class TilingMoveHandler { } _onMoveFinished(window) { - - this._layoutPicker.onMoveFinished(); + this._layoutPicker.onMoveFinished(); try { window.assertExistence(); @@ -315,7 +313,7 @@ export default class TilingMoveHandler { const [x, y] = this.getDragCoords(); const currPointerPos = { x, y }; - this._layoutPicker.onMoving(x, y) + this._layoutPicker.onMoving(x, y); if (lowPerfMode) { if (!this._isGrabOp) { @@ -509,23 +507,38 @@ 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 vDetectionSize = Settings.getInt('vertical-preview-area'); - const pointerAtTopEdge = this._lastPointerPos.y <= workArea.y + vDetectionSize; - const pointerAtBottomEdge = this._lastPointerPos.y >= workArea.y2 - vDetectionSize; + let pointerAtTopEdge = this._lastPointerPos.y <= workArea.y + vDetectionSize || + layoutPickerTileType === LayoutPickerTileType.TOP; + let 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; + let pointerAtLeftEdge = this._lastPointerPos.x <= workArea.x + hDetectionSize || + layoutPickerTileType === LayoutPickerTileType.LEFT; + let 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 // the top/bottom corners. 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); + const windowAtTopEdge = titleBarGrabbed && wRect.y === workArea.y && !this._layoutPicker.picking; + const windowAtBottomEdge = wRect.y >= workArea.y2 - 75 && !this._layoutPicker.picking; + const tileTopLeftQuarter = pointerAtLeftEdge && (pointerAtTopEdge || windowAtTopEdge) || + layoutPickerTileType === LayoutPickerTileType.Q2; + const tileTopRightQuarter = pointerAtRightEdge && (pointerAtTopEdge || windowAtTopEdge) || + layoutPickerTileType === LayoutPickerTileType.Q1; + const tileBottomLeftQuarter = pointerAtLeftEdge && (pointerAtBottomEdge || windowAtBottomEdge)|| + layoutPickerTileType === LayoutPickerTileType.Q3; + const tileBottomRightQuarter = pointerAtRightEdge && (pointerAtBottomEdge || windowAtBottomEdge) || + layoutPickerTileType === LayoutPickerTileType.Q4; + + // we cannot just for example do this: + // const tileTopLeftQuarter = pointerAtLeftEdge && (pointerAtTopEdge || windowAtTopEdge) || layoutPickerTileType == LayoutPickerTileType.Q2; + // this can be buggy when both are true like triggering top right preview even when in LayoutPickerTileType.RIGHT + // so reassigning the value is a must if (tileTopLeftQuarter) { this._tileRect = Twm.getTileFor('tile-topleft-quarter', workArea, this._monitorNr); @@ -546,7 +559,7 @@ 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 tileRect = shouldMaximize && !this._layoutPicker.picking ? workArea : Twm.getTileFor('tile-top-half', workArea, this._monitorNr); const holdTileRect = shouldMaximize From a05d991f47fc77032b81d3794b85475db8e6eed5 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sat, 16 May 2026 11:06:30 +0800 Subject: [PATCH 06/18] Add reveal animation this commit also includes problems fixed which are not shown when animation is not yet implemented, such as layoutPicker shown in random place initially and incorrect visibility trigger bounds. animation focused on translation_y instead of moving the object itself to a new position. --- .../src/extension/layoutPicker.js | 122 +++++++++++------- .../src/extension/moveHandler.js | 1 + 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index b2696d5f..c39c196e 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -40,15 +40,14 @@ class LayoutPicker extends St.Bin { orientation: Clutter.Orientation.HORIZONTAL, style_class: 'popup-menu-content' }); + this._visibility = LayoutPickerVisibility.HIDDEN; + this._dragging = false; this.set_child(this._container); this._addChrome(); - this.reactive = true; - this.visible = false; - this._vertIcon = this._createIcon(iconPath('tile-vertical')); this._horIcon = this._createIcon(iconPath('tile-horizontal')); this._quartIcon = this._createIcon(iconPath('tile-quarter')); @@ -59,15 +58,17 @@ class LayoutPicker extends St.Bin { this._tileType = LayoutPickerTileType.NONE; - - // Defer allocation until after the actor has been laid out - this._allocationId = this.connect('notify::allocation', () => { - this.disconnect(this._allocationId); - this._allocationId = null; - this._updateAllocation(); - }); - - this._dragging = false; + // 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); } get tileType() { @@ -79,46 +80,67 @@ class LayoutPicker extends St.Bin { } _setVisibility(visibility) { + if (this._visibility === visibility) + return; + this._visibility = visibility; - console.log(this._visibility); - this._updateAllocation(); + + this.opacity = 255; + this.reactive = true; + + let positions = [ + 0, + this._container.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; + } + + }); + } + + onMonitorEntered(monitorIndex) { + this._updateAllocation(monitorIndex); } onMoving(curX, curY) { - if (this._dragging === false) - return; + if (this._dragging === false) + return; - let [mx, my] = this.get_transformed_position(); let [w, h] = this.get_size(); + let [mx, my_] = this.get_transformed_position(); const monitorIndex = global.display.get_current_monitor(); const monitorArea = Main.layoutManager.monitors[monitorIndex]; - const monitorY = monitorArea ? monitorArea.y : my; // fallback to my just in case monitorArea is null; - - // 1.75 acts as an early trigger when transitioning from PEAK to SHOWN. - // This ensures the picker becomes visible before the cursor reaches it. - // The multiplier is only applied for PEAK → SHOWN, not SHOWN → PEAK. - const triggerHeight = this._visibility === LayoutPickerVisibility.PEAK ? h * 1.75 : h; - - // using monitorY instead (my) as upper bound to compensate with chromes such us the top bar height - // placing cursor above my could cause glitch. (my) and monitorY will be same for other monitor anyways. - if (curY >= monitorY && curY <= my + triggerHeight && curX >= mx && curX <= mx + w) { - this._setVisibility(LayoutPickerVisibility.SHOWN); - } else { - this._setVisibility(LayoutPickerVisibility.PEAK); - } + 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 && curX >= mx && curX <= mx + w) + this._setVisibility(LayoutPickerVisibility.SHOWN); + else + this._setVisibility(LayoutPickerVisibility.PEAK); this._updateLayoutPickerTileType(curX, curY); } onMoveStarted() { - this._dragging = true; + this._dragging = true; this._setVisibility(LayoutPickerVisibility.PEAK); } onMoveFinished() { - this._dragging = false; + this._dragging = false; this._setVisibility(LayoutPickerVisibility.HIDDEN); } @@ -255,32 +277,34 @@ class LayoutPicker extends St.Bin { this._setLayoutPickerIcon(this._tileType); } - _updateAllocation() { + _updateAllocation(monitorIndex) { const activeWs = global.workspace_manager.get_active_workspace(); - const monitorIndex = global.display.get_current_monitor(); const workArea = activeWs.get_work_area_for_monitor(monitorIndex); - if (!workArea) - return; + if (workArea === null) + return; - this.x = (workArea.x + (workArea.width * 0.5)) - this.width * 0.5; + const [, natWidth] = this.get_preferred_width(-1); + const [, natHeight] = this.get_preferred_height(-1); - this.visible = true; + this.set_position( + Math.round(workArea.x + (workArea.width - natWidth) / 2), + Math.round(workArea.y - natHeight) + ); - if (this._visibility === LayoutPickerVisibility.HIDDEN) { - this.y = workArea.y - this.height; - this.visible = false; - } else if (this._visibility === LayoutPickerVisibility.PEAK) { - this.y = workArea.y - this.height + this._container.get_theme_node().get_padding(St.Side.Bottom); - } else if (this._visibility === LayoutPickerVisibility.SHOWN) { - this.y = workArea.y; - } + this.remove_all_transitions(); - this.set_clip(0, Math.abs(this.y - workArea.y), workArea.width, workArea.height); + this._visibility = LayoutPickerVisibility.HIDDEN; + this.translation_y = 0; + this.opacity = 0; + this.reactive = false; } _addChrome() { - Main.layoutManager.addChrome(this); + Main.layoutManager.addChrome(this, { + affectsStruts: false, + trackFullscreen: false + }); } _untrackChrome() { diff --git a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js index 6b40254b..8d74e7f3 100644 --- a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js +++ b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js @@ -135,6 +135,7 @@ export default class TilingMoveHandler { // Reset preview mode: // Currently only needed to grab the favorite layout for the new monitor. this._preparePreviewModeChange(this._currPreviewMode, window); + this._layoutPicker.onMonitorEntered(monitorNr); } getDragCoords() { From 152d5ba3fc97827c8453c7a7f4b524e69cdc4b9b Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sun, 17 May 2026 00:10:33 +0800 Subject: [PATCH 07/18] Update tile type after picker is fully shown --- .../src/extension/layoutPicker.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index c39c196e..e2608a74 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -101,8 +101,16 @@ class LayoutPicker extends St.Bin { duration: 250, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { - this.opacity = this._visibility !== LayoutPickerVisibility.HIDDEN ? 255 : 0; - this.reactive = this._visibility !== LayoutPickerVisibility.HIDDEN; + 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); + } } }); From 57b6e651e23517f9a478f0efc29e0edd65480df9 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sun, 17 May 2026 10:36:49 +0800 Subject: [PATCH 08/18] Increase top padding from 6px to 20px --- tiling-assistant@leleat-on-github/stylesheet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiling-assistant@leleat-on-github/stylesheet.css b/tiling-assistant@leleat-on-github/stylesheet.css index ffe977fe..7cc54b1d 100644 --- a/tiling-assistant@leleat-on-github/stylesheet.css +++ b/tiling-assistant@leleat-on-github/stylesheet.css @@ -9,7 +9,7 @@ .tiling-menu-container { padding: 0; - padding-top: 6px; + padding-top: 20px; } .tiling-menu-container > StBoxLayout { From 87e9a5411537a31b8abfa699cf4071b90a9cd003 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sun, 17 May 2026 11:12:35 +0800 Subject: [PATCH 09/18] Add maximize picker tile --- .../media/tile-base-symbolic.svg | 16 ++++++++++++++++ .../media/tile-maximize-symbolic.svg | 19 +++++++++++++++++++ .../src/extension/layoutPicker.js | 17 ++++++++++++++++- .../src/extension/moveHandler.js | 7 ++++--- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg create mode 100644 tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg 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..c91ecfba --- /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-maximize-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg new file mode 100644 index 00000000..8f0226b2 --- /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/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index e2608a74..33544578 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -25,7 +25,8 @@ export const LayoutPickerTileType = { Q1: 5, Q2: 6, Q3: 7, - Q4: 8 + Q4: 8, + MAXIMIZE: 9 }; export const LayoutPicker = GObject.registerClass( @@ -51,10 +52,12 @@ class LayoutPicker extends St.Bin { this._vertIcon = this._createIcon(iconPath('tile-vertical')); this._horIcon = this._createIcon(iconPath('tile-horizontal')); this._quartIcon = this._createIcon(iconPath('tile-quarter')); + this._maxIcon = this._createIcon(iconPath('tile-base')); this._container.add_child(this._vertIcon); this._container.add_child(this._horIcon); this._container.add_child(this._quartIcon); + this._container.add_child(this._maxIcon); this._tileType = LayoutPickerTileType.NONE; @@ -212,6 +215,13 @@ class LayoutPicker extends St.Bin { ); break; } + + case LayoutPickerTileType.MAXIMIZE: { + this._maxIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-maximize')) + ); + break; + } } } @@ -235,6 +245,9 @@ class LayoutPicker extends St.Bin { this._quartIcon.gicon = Gio.FileIcon.new( Gio.File.new_for_path(iconPath('tile-quarter')) ); + this._maxIcon.gicon = Gio.FileIcon.new( + Gio.File.new_for_path(iconPath('tile-base')) + ); } _updateLayoutPickerTileType(curX, curY) { @@ -277,6 +290,8 @@ class LayoutPicker extends St.Bin { this._tileType = LayoutPickerTileType.Q3; else this._tileType = LayoutPickerTileType.Q4; + } else if (contains(this._maxIcon, curX, curY)) { + this._tileType = LayoutPickerTileType.MAXIMIZE; } else { this._tileType = LayoutPickerTileType.NONE; diff --git a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js index 8d74e7f3..34e001cd 100644 --- a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js +++ b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js @@ -512,7 +512,8 @@ export default class TilingMoveHandler { const vDetectionSize = Settings.getInt('vertical-preview-area'); let pointerAtTopEdge = this._lastPointerPos.y <= workArea.y + vDetectionSize || - layoutPickerTileType === LayoutPickerTileType.TOP; + layoutPickerTileType === LayoutPickerTileType.TOP || + layoutPickerTileType === LayoutPickerTileType.MAXIMIZE; let pointerAtBottomEdge = this._lastPointerPos.y >= workArea.y2 - vDetectionSize || layoutPickerTileType === LayoutPickerTileType.BOTTOM; const hDetectionSize = Settings.getInt('horizontal-preview-area'); @@ -560,10 +561,10 @@ 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 && !this._layoutPicker.picking + const tileRect = shouldMaximize && !this._layoutPicker.picking || layoutPickerTileType === LayoutPickerTileType.MAXIMIZE ? workArea : Twm.getTileFor('tile-top-half', workArea, this._monitorNr); - const holdTileRect = shouldMaximize + const holdTileRect = shouldMaximize && !this._layoutPicker.picking || 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 From d970e059783aa52929c15f57bc7783c9075ebc3c Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sun, 17 May 2026 13:24:16 +0800 Subject: [PATCH 10/18] Fix shadow clipping --- .../src/extension/layoutPicker.js | 7 +++++-- tiling-assistant@leleat-on-github/stylesheet.css | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index 33544578..4aea4a17 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -91,9 +91,11 @@ class LayoutPicker extends St.Bin { this.opacity = 255; this.reactive = true; + let positions = [ 0, - this._container.get_theme_node().get_padding(St.Side.Bottom), + this._container.get_theme_node().get_padding(St.Side.Bottom) + + this.get_theme_node().get_padding(St.Side.BOTTOM), this.height ]; @@ -128,6 +130,7 @@ class LayoutPicker extends St.Bin { return; let [w, h] = this.get_size(); + let bottomPadding = this.get_theme_node().get_padding(St.Side.BOTTOM); let [mx, my_] = this.get_transformed_position(); const monitorIndex = global.display.get_current_monitor(); @@ -137,7 +140,7 @@ class LayoutPicker extends St.Bin { // 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 && curX >= mx && curX <= mx + w) + if (curY >= monitorArea.y && curY <= workArea.y + h - bottomPadding && curX >= mx && curX <= mx + w) this._setVisibility(LayoutPickerVisibility.SHOWN); else this._setVisibility(LayoutPickerVisibility.PEAK); diff --git a/tiling-assistant@leleat-on-github/stylesheet.css b/tiling-assistant@leleat-on-github/stylesheet.css index 7cc54b1d..eb7f28b0 100644 --- a/tiling-assistant@leleat-on-github/stylesheet.css +++ b/tiling-assistant@leleat-on-github/stylesheet.css @@ -8,8 +8,7 @@ } .tiling-menu-container { - padding: 0; - padding-top: 20px; + padding: 20px; } .tiling-menu-container > StBoxLayout { From 073223ffc4baf3b5236cf23b531b77cc8415f777 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sun, 17 May 2026 13:57:35 +0800 Subject: [PATCH 11/18] Ensure moveFinised function for layout picker is triggered --- tiling-assistant@leleat-on-github/src/extension/moveHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js index 34e001cd..e7b9b372 100644 --- a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js +++ b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js @@ -161,6 +161,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, () => { From 78a487f045a0ea2c1c65d2b5ad11a8f61cc71e6a Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sun, 17 May 2026 19:29:25 +0800 Subject: [PATCH 12/18] Redesign icons to fit vanilla gnome aesthetics --- .../media/tile-base-symbolic.svg | 6 ++-- .../media/tile-bottom-symbolic.svg | 21 ++++++------ .../media/tile-horizontal-symbolic.svg | 15 ++++----- .../media/tile-left-symbolic.svg | 21 ++++++------ .../media/tile-maximize-symbolic.svg | 10 +++--- .../media/tile-q1-symbolic.svg | 33 +++++++++---------- .../media/tile-q2-symbolic.svg | 33 +++++++++---------- .../media/tile-q3-symbolic.svg | 33 +++++++++---------- .../media/tile-q4-symbolic.svg | 33 +++++++++---------- .../media/tile-quarter-symbolic.svg | 27 ++++++++------- .../media/tile-right-symbolic.svg | 21 ++++++------ .../media/tile-top-symbolic.svg | 21 ++++++------ .../media/tile-vertical-symbolic.svg | 15 ++++----- 13 files changed, 139 insertions(+), 150 deletions(-) diff --git a/tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg index c91ecfba..126bc597 100644 --- a/tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-base-symbolic.svg @@ -11,6 +11,6 @@ xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + d="M 8,0 C 3.568,0 0,3.568 0,8 v 48 c 0,4.432 3.568,8 8,8 h 48 c 4.432,0 8,-3.568 8,-8 V 8 C 64,3.568 60.432,0 56,0 Z m 0,3 h 48 c 2.77,0 5,2.23 5,5 v 48 c 0,2.77 -2.23,5 -5,5 H 8 C 5.23,61 3,58.77 3,56 V 8 C 3,5.23 5.23,3 8,3 Z" + style="fill:#2e3436;fill-opacity:0.35;fill-rule:evenodd" + id="path5" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg index 73590765..67c176a7 100644 --- a/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-bottom-symbolic.svg @@ -10,14 +10,13 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg index 9ee212e7..a5bead91 100644 --- a/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-horizontal-symbolic.svg @@ -10,11 +10,10 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg index 01b8df7b..b05a1efa 100644 --- a/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-left-symbolic.svg @@ -10,14 +10,13 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg index 8f0226b2..0fdeecbf 100644 --- a/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-maximize-symbolic.svg @@ -11,9 +11,9 @@ xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + d="M 8,5 C 6.3034055,5 5,6.3034055 5,8 v 48 c 0,1.696594 1.3034055,3 3,3 h 48 c 1.696594,0 3,-1.303406 3,-3 V 8 C 59,6.3034055 57.696594,5 56,5 Z" + style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#2e3436;fill-opacity:1;fill-rule:evenodd;enable-background:accumulate;stop-color:#000000;stop-opacity:1" + id="path9" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg index a487bb80..62bb30f8 100644 --- a/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-q1-symbolic.svg @@ -10,20 +10,19 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg index 692704c1..9e951c38 100644 --- a/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-q2-symbolic.svg @@ -10,20 +10,19 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg index f507b11d..492a17a3 100644 --- a/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-q3-symbolic.svg @@ -10,20 +10,19 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg index 2e8ef256..c38b541b 100644 --- a/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-q4-symbolic.svg @@ -10,20 +10,19 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg index d4fd1a10..c2e1e192 100644 --- a/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-quarter-symbolic.svg @@ -10,17 +10,16 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg index bea76035..a333b058 100644 --- a/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-right-symbolic.svg @@ -10,14 +10,13 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg index bd08659d..f6346e72 100644 --- a/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-top-symbolic.svg @@ -10,14 +10,13 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> diff --git a/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg b/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg index 5d80e794..3ee05ea9 100644 --- a/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg +++ b/tiling-assistant@leleat-on-github/media/tile-vertical-symbolic.svg @@ -10,11 +10,10 @@ xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> From 69ea1650462d81f2bd174433a7d7a92437dee82a Mon Sep 17 00:00:00 2001 From: ochi12 Date: Sun, 17 May 2026 19:31:02 +0800 Subject: [PATCH 13/18] Add support for themed icon --- .../src/extension/layoutPicker.js | 87 +++++++++---------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index 4aea4a17..4ce6b72f 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -49,10 +49,10 @@ class LayoutPicker extends St.Bin { this._addChrome(); - this._vertIcon = this._createIcon(iconPath('tile-vertical')); - this._horIcon = this._createIcon(iconPath('tile-horizontal')); - this._quartIcon = this._createIcon(iconPath('tile-quarter')); - this._maxIcon = this._createIcon(iconPath('tile-base')); + this._vertIcon = this._createIcon('tile-vertical'); + this._horIcon = this._createIcon('tile-horizontal'); + this._quartIcon = this._createIcon('tile-quarter'); + this._maxIcon = this._createIcon('tile-base'); this._container.add_child(this._vertIcon); this._container.add_child(this._horIcon); @@ -165,92 +165,85 @@ class LayoutPicker extends St.Bin { break; } case LayoutPickerTileType.LEFT: { - this._horIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-left')) - ); + this._updateIcon(this._horIcon, 'tile-left'); break; } case LayoutPickerTileType.RIGHT: { - this._horIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-right')) - ); + this._updateIcon(this._horIcon, 'tile-right'); break; } case LayoutPickerTileType.TOP: { - this._vertIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-top')) - ); + this._updateIcon(this._vertIcon, 'tile-top'); break; } case LayoutPickerTileType.BOTTOM: { - this._vertIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-bottom')) - ); + this._updateIcon(this._vertIcon, 'tile-bottom'); break; } case LayoutPickerTileType.Q1: { - this._quartIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-q1')) - ); + this._updateIcon(this._quartIcon, 'tile-q1'); break; } case LayoutPickerTileType.Q2: { - this._quartIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-q2')) - ); + this._updateIcon(this._quartIcon, 'tile-q2'); break; } case LayoutPickerTileType.Q3: { - this._quartIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-q3')) - ); + this._updateIcon(this._quartIcon, 'tile-q3'); break; } case LayoutPickerTileType.Q4: { - this._quartIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-q4')) - ); + this._updateIcon(this._quartIcon, 'tile-q4'); break; } case LayoutPickerTileType.MAXIMIZE: { - this._maxIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-maximize')) - ); + this._updateIcon(this._maxIcon, 'tile-maximize'); break; } } } - _createIcon(path) { - let gicon = Gio.FileIcon.new( - Gio.File.new_for_path(path) + _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 + gicon, + fallback_gicon }); } - _clearIcons() { - this._vertIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-vertical')) - ); - this._horIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-horizontal')) - ); - this._quartIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-quarter')) - ); - this._maxIcon.gicon = Gio.FileIcon.new( - Gio.File.new_for_path(iconPath('tile-base')) + _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() { + this._updateIcon(this._vertIcon, 'tile-vertical'); + this._updateIcon(this._horIcon, 'tile-horizontal'); + this._updateIcon(this._quartIcon, 'tile-quarter'); + this._updateIcon(this._maxIcon, 'tile-base'); } _updateLayoutPickerTileType(curX, curY) { From b1647f1584a271b028b2b89865244f1d9d50fc0b Mon Sep 17 00:00:00 2001 From: ochi12 Date: Mon, 18 May 2026 00:27:48 +0800 Subject: [PATCH 14/18] Add additional signals that ensures layout picker correct position --- .../src/extension/layoutPicker.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index 4ce6b72f..b65f17ea 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -61,6 +61,15 @@ class LayoutPicker extends St.Bin { this._tileType = LayoutPickerTileType.NONE; + // e.g ubuntu 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); + // just in case extension is enabled and disable this._updateAllocation(global.display.get_current_monitor()); @@ -332,6 +341,9 @@ class LayoutPicker extends St.Bin { destroy() { this._untrackChrome(); + + global.display.disconnectObject(this); + Main.layoutManager.disconnectObject(this); this._container?.destroy(); this._container = null; From 614a3292f462fd99233a92108c3604b95dd6fe6a Mon Sep 17 00:00:00 2001 From: ochi12 Date: Mon, 18 May 2026 14:57:23 +0800 Subject: [PATCH 15/18] Compensate for left and right padding for triggering visibility --- .../src/extension/layoutPicker.js | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index b65f17ea..100917bd 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -61,14 +61,14 @@ class LayoutPicker extends St.Bin { this._tileType = LayoutPickerTileType.NONE; - // e.g ubuntu dock is enabled and or disabled - global.display.connectObject('workareas-changed', () => { + // e.g ubuntu dock is enabled and or disabled + global.display.connectObject('workareas-changed', () => { this._updateAllocation(global.display.get_current_monitor()); - }, this); + }, this); - Main.layoutManager.connectObject('monitors-changed', () => { + Main.layoutManager.connectObject('monitors-changed', () => { this._updateAllocation(global.display.get_current_monitor()); - }, this); + }, this); // just in case extension is enabled and disable this._updateAllocation(global.display.get_current_monitor()); @@ -139,9 +139,13 @@ class LayoutPicker extends St.Bin { return; let [w, h] = this.get_size(); - let bottomPadding = this.get_theme_node().get_padding(St.Side.BOTTOM); 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 monitorIndex = global.display.get_current_monitor(); const monitorArea = Main.layoutManager.monitors[monitorIndex]; const activeWs = global.workspace_manager.get_active_workspace(); @@ -149,7 +153,12 @@ class LayoutPicker extends St.Bin { // 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 - bottomPadding && curX >= mx && curX <= mx + w) + 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); @@ -341,9 +350,9 @@ class LayoutPicker extends St.Bin { destroy() { this._untrackChrome(); - - global.display.disconnectObject(this); - Main.layoutManager.disconnectObject(this); + + global.display.disconnectObject(this); + Main.layoutManager.disconnectObject(this); this._container?.destroy(); this._container = null; From 7491de3c2934846c3d0e6d084b8e25a66683f647 Mon Sep 17 00:00:00 2001 From: ochi12 Date: Mon, 18 May 2026 17:16:03 +0800 Subject: [PATCH 16/18] Fix unreliable signal triggering update allocation Initially, the main signal triggering update allocation was window-entered-monitor. It was initially thought to be reliable, but this was overlooked. When windows are opened on the same monitor and one is dragged into another monitor, it actually updates the picker position to the other monitor. When I dragged the window left on the first monitor, the picker was still actually placed in the second monitor area. --- .../src/extension/layoutPicker.js | 17 +++++++++-------- .../src/extension/moveHandler.js | 1 - 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index 100917bd..45e829a7 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -130,14 +130,12 @@ class LayoutPicker extends St.Bin { }); } - onMonitorEntered(monitorIndex) { - this._updateAllocation(monitorIndex); - } - onMoving(curX, curY) { if (this._dragging === false) return; + this._updateAllocation(global.display.get_current_monitor()); + let [w, h] = this.get_size(); let [mx, my_] = this.get_transformed_position(); @@ -324,10 +322,13 @@ class LayoutPicker extends St.Bin { const [, natWidth] = this.get_preferred_width(-1); const [, natHeight] = this.get_preferred_height(-1); - this.set_position( - Math.round(workArea.x + (workArea.width - natWidth) / 2), - Math.round(workArea.y - natHeight) - ); + 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(); diff --git a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js index e7b9b372..64a171b4 100644 --- a/tiling-assistant@leleat-on-github/src/extension/moveHandler.js +++ b/tiling-assistant@leleat-on-github/src/extension/moveHandler.js @@ -135,7 +135,6 @@ export default class TilingMoveHandler { // Reset preview mode: // Currently only needed to grab the favorite layout for the new monitor. this._preparePreviewModeChange(this._currPreviewMode, window); - this._layoutPicker.onMonitorEntered(monitorNr); } getDragCoords() { From 34d0b5bfe4b7d13979238550d4a1d50f0a79abed Mon Sep 17 00:00:00 2001 From: ochi12 Date: Mon, 18 May 2026 23:06:01 +0800 Subject: [PATCH 17/18] Refactor: centralize layout picker icon configuration --- .../src/extension/layoutPicker.js | 136 +++++++++--------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index 45e829a7..a3775362 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -29,6 +29,46 @@ export const LayoutPickerTileType = { MAXIMIZE: 9 }; +// ommit '-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( class LayoutPicker extends St.Bin { _init() { @@ -49,15 +89,17 @@ class LayoutPicker extends St.Bin { this._addChrome(); - this._vertIcon = this._createIcon('tile-vertical'); - this._horIcon = this._createIcon('tile-horizontal'); - this._quartIcon = this._createIcon('tile-quarter'); - this._maxIcon = this._createIcon('tile-base'); + this._icons = {}; - this._container.add_child(this._vertIcon); - this._container.add_child(this._horIcon); - this._container.add_child(this._quartIcon); - this._container.add_child(this._maxIcon); + 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); + } this._tileType = LayoutPickerTileType.NONE; @@ -174,55 +216,17 @@ class LayoutPicker extends St.Bin { this._setVisibility(LayoutPickerVisibility.HIDDEN); } - _setLayoutPickerIcon(hoveredType) { + _setLayoutPickerIcon(tileType) { this._clearIcons(); - switch (hoveredType) { - case LayoutPickerTileType.NONE: { - break; - } - case LayoutPickerTileType.LEFT: { - this._updateIcon(this._horIcon, 'tile-left'); - break; - } - case LayoutPickerTileType.RIGHT: { - this._updateIcon(this._horIcon, 'tile-right'); - break; - } - - case LayoutPickerTileType.TOP: { - this._updateIcon(this._vertIcon, 'tile-top'); - break; - } - - case LayoutPickerTileType.BOTTOM: { - this._updateIcon(this._vertIcon, 'tile-bottom'); - break; - } - - case LayoutPickerTileType.Q1: { - this._updateIcon(this._quartIcon, 'tile-q1'); - break; - } - - case LayoutPickerTileType.Q2: { - this._updateIcon(this._quartIcon, 'tile-q2'); - break; - } - - case LayoutPickerTileType.Q3: { - this._updateIcon(this._quartIcon, 'tile-q3'); - break; - } - - case LayoutPickerTileType.Q4: { - this._updateIcon(this._quartIcon, 'tile-q4'); - break; - } - - case LayoutPickerTileType.MAXIMIZE: { - this._updateIcon(this._maxIcon, 'tile-maximize'); - break; - } + + for (const config of Object.values(ICONS)) { + const iconName = config.states[tileType]; + + if (!iconName) + continue; + + this._updateIcon(config.icon, iconName); + break; } } @@ -256,10 +260,8 @@ class LayoutPicker extends St.Bin { } _clearIcons() { - this._updateIcon(this._vertIcon, 'tile-vertical'); - this._updateIcon(this._horIcon, 'tile-horizontal'); - this._updateIcon(this._quartIcon, 'tile-quarter'); - this._updateIcon(this._maxIcon, 'tile-base'); + for (const config of Object.values(ICONS)) + this._updateIcon(config.icon, config.none); } _updateLayoutPickerTileType(curX, curY) { @@ -273,24 +275,24 @@ class LayoutPicker extends St.Bin { return x >= mx && x <= mx + mw && y >= my && y <= my + mh; }; - if (contains(this._horIcon, curX, curY)) { - let [mx, my_, mw, mh_] = rect(this._horIcon); + if (contains(this._icons.horizontal, curX, curY)) { + let [mx, my_, mw, mh_] = rect(this._icons.horizontal); let mid = mx + (mw * 0.5); if (curX <= mid) this._tileType = LayoutPickerTileType.LEFT; else this._tileType = LayoutPickerTileType.RIGHT; - } else if (contains(this._vertIcon, curX, curY)) { - let [mx_, my, mw_, mh] = rect(this._vertIcon); + } else if (contains(this._icons.vertical, curX, curY)) { + let [mx_, my, mw_, mh] = rect(this._icons.vertical); let mid = my + (mh * 0.5); if (curY <= mid) this._tileType = LayoutPickerTileType.TOP; else this._tileType = LayoutPickerTileType.BOTTOM; - } else if (contains(this._quartIcon, curX, curY)) { - let [mx, my, mw, mh] = rect(this._quartIcon); + } else if (contains(this._icons.quarter, curX, curY)) { + let [mx, my, mw, mh] = rect(this._icons.quarter); let midX = mx + (mw * 0.5); let midY = my + (mh * 0.5); @@ -302,7 +304,7 @@ class LayoutPicker extends St.Bin { this._tileType = LayoutPickerTileType.Q3; else this._tileType = LayoutPickerTileType.Q4; - } else if (contains(this._maxIcon, curX, curY)) { + } else if (contains(this._icons.maximize, curX, curY)) { this._tileType = LayoutPickerTileType.MAXIMIZE; } else { From 5312233683c5b65009f64272ab5f3ad75068cc7b Mon Sep 17 00:00:00 2001 From: ochi12 Date: Mon, 18 May 2026 23:46:08 +0800 Subject: [PATCH 18/18] Refactor: simplify layout picker tile update logic --- .../src/extension/layoutPicker.js | 96 +++++++++++-------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js index a3775362..60bf85ea 100644 --- a/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js +++ b/tiling-assistant@leleat-on-github/src/extension/layoutPicker.js @@ -266,51 +266,67 @@ class LayoutPicker extends St.Bin { _updateLayoutPickerTileType(curX, curY) { let rect = icon => { - let [mx, my] = icon.get_transformed_position(); - let [mw, mh] = icon.get_size(); - return [mx, my, mw, mh]; - }; - let contains = (icon, x, y) => { - let [mx, my, mw, mh] = rect(icon); - return x >= mx && x <= mx + mw && y >= my && y <= my + mh; + let [x, y] = icon.get_transformed_position(); + let [w, h] = icon.get_size(); + return { x, y, w, h }; }; - if (contains(this._icons.horizontal, curX, curY)) { - let [mx, my_, mw, mh_] = rect(this._icons.horizontal); - let mid = mx + (mw * 0.5); - - if (curX <= mid) - this._tileType = LayoutPickerTileType.LEFT; - else - this._tileType = LayoutPickerTileType.RIGHT; - } else if (contains(this._icons.vertical, curX, curY)) { - let [mx_, my, mw_, mh] = rect(this._icons.vertical); - let mid = my + (mh * 0.5); - - if (curY <= mid) - this._tileType = LayoutPickerTileType.TOP; - else - this._tileType = LayoutPickerTileType.BOTTOM; - } else if (contains(this._icons.quarter, curX, curY)) { - let [mx, my, mw, mh] = rect(this._icons.quarter); - let midX = mx + (mw * 0.5); - let midY = my + (mh * 0.5); - - if (curX >= midX && curY <= midY) - this._tileType = LayoutPickerTileType.Q1; - else if (curX < midX && curY <= midY) - this._tileType = LayoutPickerTileType.Q2; - else if (curX < midX && curY > midY) - this._tileType = LayoutPickerTileType.Q3; - else - this._tileType = LayoutPickerTileType.Q4; - } else if (contains(this._icons.maximize, curX, curY)) { - this._tileType = LayoutPickerTileType.MAXIMIZE; + 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; } - else { - this._tileType = LayoutPickerTileType.NONE; + + const vertical = rect(this._icons.vertical); + + if (contains(vertical)) { + const topPortion = curY <= horizontal.y + horizontal.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); }