From bfd4d0f7aa57bce6bfe9ec5b54c1281ef06684f3 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 23 Sep 2020 16:48:43 -0300 Subject: [PATCH 01/14] appDisplay: Factor out linear position calculation When adding an item to the app grid, the item is added to a sorted array. This is calculated by adding all visible items in pages before the one being modified. Future commits will need this to move items without reparenting them, so factor this code into a separate function. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index f9e6a9b7da..9094fcf038 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -514,15 +514,10 @@ var BaseAppView = GObject.registerClass({ return -1; } - _addItem(item, page, position) { + _getLinearPosition(page, position) { let itemIndex = 0; if (this._grid.nPages > 0) { - // Append icons to the first page with empty slot, starting from - // the second page - if (this._grid.nPages > 1 && page === -1 && position === -1) - page = this._findBestPageToAppend(); - const realPage = page === -1 ? this._grid.nPages - 1 : page; itemIndex = position === -1 @@ -535,6 +530,17 @@ var BaseAppView = GObject.registerClass({ } } + return itemIndex; + } + + _addItem(item, page, position) { + // Append icons to the first page with empty slot, starting from + // the second page + if (this._grid.nPages > 1 && page === -1 && position === -1) + page = this._findBestPageToAppend(); + + const itemIndex = this._getLinearPosition(page, position); + this._orderedItems.splice(itemIndex, 0, item); this._items.set(item.id, item); this._grid.addItem(item, page, position); -- GitLab From ffdff07eafc9adfe01f05486970fb86d835ab8d9 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 23 Sep 2020 16:52:09 -0300 Subject: [PATCH 02/14] iconGrid: Add moveItem This new public API moves items without removing and readding them, which allows us to avoid some tricky behavior. Noticeably, following the original design described at 3555550d5, the new IconGridLayout.moveItem() method does not call `layout_changed`. This is done by IconGrid itself, queueing a relayout. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/iconGrid.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js index 12a4aed9bd..a6a42b615a 100644 --- a/js/ui/iconGrid.js +++ b/js/ui/iconGrid.js @@ -847,6 +847,22 @@ var IconGridLayout = GObject.registerClass({ this.addItem(item); } + /** + * moveItem: + * @param {Clutter.Actor} item: item to move + * @param {int} newPage: new page of the item + * @param {int} newPosition: new page of the item + * + * Moves @item to the grid. @item must be part of the grid. + */ + moveItem(item, newPage, newPosition) { + if (!this._items.has(item)) + throw new Error(`Item ${item} is not part of the IconGridLayout`); + + this._removeItemData(item); + this._addItemToPage(item, newPage, newPosition); + } + /** * removeItem: * @param {Clutter.Actor} item: item to remove from the grid @@ -1422,6 +1438,19 @@ var IconGrid = GObject.registerClass({ this.layout_manager.appendItem(item); } + /** + * moveItem: + * @param {Clutter.Actor} item: item to move + * @param {int} newPage: new page of the item + * @param {int} newPosition: new page of the item + * + * Moves @item to the grid. @item must be part of the grid. + */ + moveItem(item, newPage, newPosition) { + this.layout_manager.moveItem(item, newPage, newPosition); + this.queue_relayout(); + } + /** * removeItem: * @param {Clutter.Actor} item: item to remove from the grid -- GitLab From 062c014223c605ba3cdedf28ef21ecfadda58046 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 23 Sep 2020 16:53:17 -0300 Subject: [PATCH 03/14] appDisplay: Use new moveItem API to move items This uses the API added in the previous commit. The intent here is to avoid removing and readding actors when moving them around. Related: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3165 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 9094fcf038..3f2f605429 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -746,10 +746,14 @@ var BaseAppView = GObject.registerClass({ if (page === newPage && position === newPosition) return; - if (page !== -1 && position !== -1) - this._removeItem(item); + // Update the _orderedItems array + let index = this._orderedItems.indexOf(item); + this._orderedItems.splice(index, 1); - this._addItem(item, newPage, newPosition); + index = this._getLinearPosition(newPage, newPosition); + this._orderedItems.splice(index, 0, item); + + this._grid.moveItem(item, newPage, newPosition); } vfunc_allocate(box) { -- GitLab From cc3519332c38d56763c57a3fcdabb66a2e8a476f Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 10:17:53 -0300 Subject: [PATCH 04/14] appDisplay: Lighten folder dialog background when dragging out As per design feedback, make the app folder dialog background lighter when hovering it with an icon. This gives the visual feedback to show that that region is a target. Use the new DIALOG_SHADE_NORMAL variable in the other places where its color was hardcoded. Related: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3092 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 3f2f605429..4e160f621e 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -38,6 +38,9 @@ const OVERSHOOT_TIMEOUT = 1000; const DELAYED_MOVE_TIMEOUT = 200; +const DIALOG_SHADE_NORMAL = Clutter.Color.from_pixel(0x000000cc); +const DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000055); + let discreteGpuAvailable = false; function _getCategories(info) { @@ -2210,7 +2213,7 @@ var AppFolderDialog = GObject.registerClass({ }); this.ease({ - background_color: Clutter.Color.from_pixel(0x000000cc), + background_color: DIALOG_SHADE_NORMAL, duration: FOLDER_DIALOG_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, }); @@ -2361,6 +2364,18 @@ var AppFolderDialog = GObject.registerClass({ return this.navigate_focus(null, direction, false); } + _setLighterBackground(lighter) { + const backgroundColor = lighter + ? DIALOG_SHADE_HIGHLIGHT + : DIALOG_SHADE_NORMAL; + + this.ease({ + backgroundColor, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + _withinDialog(x, y) { const childExtents = this.child.get_transformed_extents(); return childExtents.contains_point(new Graphene.Point({ x, y })); @@ -2372,7 +2387,12 @@ var AppFolderDialog = GObject.registerClass({ this._dragMonitor = { dragMotion: dragEvent => { - if (this._withinDialog(dragEvent.x, dragEvent.y)) { + const withinDialog = + this._withinDialog(dragEvent.x, dragEvent.y); + + this._setLighterBackground(!withinDialog); + + if (withinDialog) { this._removePopdownTimeout(); this._removeDragMonitor(); } @@ -2396,6 +2416,7 @@ var AppFolderDialog = GObject.registerClass({ handleDragOver(source, actor, x, y) { if (this._withinDialog(x, y)) { + this._setLighterBackground(false); this._removePopdownTimeout(); this._removeDragMonitor(); } else { -- GitLab From 8d84e05a2a11f9b341b24af5d963b5491282f769 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 11:59:35 -0300 Subject: [PATCH 05/14] appDisplay: Reduce folder dialog popdown timeout 1500ms is too long of a delay, and it confuses people more than it helps with accidental drops. Reduce the timeout to 500ms as per design feedback. Related: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3092 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 4e160f621e..6011d72ee6 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -19,7 +19,7 @@ const Params = imports.misc.params; const SystemActions = imports.misc.systemActions; var MENU_POPUP_TIMEOUT = 600; -var POPDOWN_DIALOG_TIMEOUT = 1500; +var POPDOWN_DIALOG_TIMEOUT = 500; var FOLDER_SUBICON_FRACTION = .4; -- GitLab From 17095928117be1648dc0a68313251e842dd4842c Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 12:00:03 -0300 Subject: [PATCH 06/14] appDisplay: Adjust folder position If you try and drop an icon that's in the same page, but before the drop target, it'll be one position ahead of where it should be - because we just removed one icon before the target position. Adjust the final position of the to-be-created folder. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 6011d72ee6..b704a54601 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -1314,9 +1314,18 @@ class AppDisplay extends BaseAppView { // The hovered AppIcon always passes its own id as the first // one, and this is where we want the folder to be created - const [folderPage, folderPosition] = + let [folderPage, folderPosition] = this._grid.getItemPosition(this._items.get(apps[0])); + // Adjust the final position + folderPosition -= apps.reduce((counter, appId) => { + const [page, position] = + this._grid.getItemPosition(this._items.get(appId)); + if (page === folderPage && position < folderPosition) + counter++; + return counter; + }, 0); + let appItems = apps.map(id => this._items.get(id).app); let folderName = _findBestFolderName(appItems); if (!folderName) -- GitLab From 75a8697671b61bbf28926d56574c5ddcc34c2159 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 12:17:58 -0300 Subject: [PATCH 07/14] appDisplay: Trivial const-correctness fix https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index b704a54601..ed735f5404 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -1820,7 +1820,7 @@ class FolderView extends BaseAppView { if (folderApps.length == 0) { // Resetting all keys deletes the relocatable schema let keys = this._folder.settings_schema.list_keys(); - for (let key of keys) + for (const key of keys) this._folder.reset(key); let settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' }); -- GitLab From 31591ff0293dbdef7808ac6188599dde63ba1f37 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 12:41:27 -0300 Subject: [PATCH 08/14] appDisplay: Only change excluded-apps when not deleting folder It is useless to update the 'excluded-apps' list when we know we're going to delete the folder entirely. Only update 'excluded-apps' when not deleting the app folder. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index ed735f5404..66a4557bb7 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -1806,15 +1806,6 @@ class FolderView extends BaseAppView { if (index >= 0) folderApps.splice(index, 1); - // If this is a categories-based folder, also add it to - // the list of excluded apps - let categories = this._folder.get_strv('categories'); - if (categories.length > 0) { - let excludedApps = this._folder.get_strv('excluded-apps'); - excludedApps.push(app.id); - this._folder.set_strv('excluded-apps', excludedApps); - } - // Remove the folder if this is the last app icon; otherwise, // just remove the icon if (folderApps.length == 0) { @@ -1828,6 +1819,15 @@ class FolderView extends BaseAppView { folders.splice(folders.indexOf(this._id), 1); settings.set_strv('folder-children', folders); } else { + // If this is a categories-based folder, also add it to + // the list of excluded apps + const categories = this._folder.get_strv('categories'); + if (categories.length > 0) { + const excludedApps = this._folder.get_strv('excluded-apps'); + excludedApps.push(app.id); + this._folder.set_strv('excluded-apps', excludedApps); + } + this._folder.set_strv('apps', folderApps); } } -- GitLab From 7dafd25ef468e1de6f5cb2247c0225c6f8e41ada Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 13:36:40 -0300 Subject: [PATCH 09/14] appDisplay: Don't emit 'apps-changed' when deleting folder To delete a folder, FolderView needs to reset all keys under that particular folder's GSettings path. That generates 5 'changed' signals, all of which end up calling AppDisplay._redisplay(), which is costly. Don't emit 'apps-changed' when deleting a folder. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 66a4557bb7..8979d7e57c 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -1639,6 +1639,7 @@ class FolderView extends BaseAppView { action.connect('pan', this._onPan.bind(this)); this._scrollView.add_action(action); + this._deletingFolder = false; this._appIds = []; this._redisplay(); } @@ -1809,6 +1810,8 @@ class FolderView extends BaseAppView { // Remove the folder if this is the last app icon; otherwise, // just remove the icon if (folderApps.length == 0) { + this._deletingFolder = true; + // Resetting all keys deletes the relocatable schema let keys = this._folder.settings_schema.list_keys(); for (const key of keys) @@ -1818,6 +1821,8 @@ class FolderView extends BaseAppView { let folders = settings.get_strv('folder-children'); folders.splice(folders.indexOf(this._id), 1); settings.set_strv('folder-children', folders); + + this._deletingFolder = false; } else { // If this is a categories-based folder, also add it to // the list of excluded apps @@ -1831,6 +1836,10 @@ class FolderView extends BaseAppView { this._folder.set_strv('apps', folderApps); } } + + get deletingFolder() { + return this._deletingFolder; + } }); var FolderIcon = GObject.registerClass({ @@ -1972,6 +1981,9 @@ var FolderIcon = GObject.registerClass({ } _sync() { + if (this.view.deletingFolder) + return; + this.emit('apps-changed'); this._updateName(); this.visible = this.view.getAllItems().length > 0; -- GitLab From 59549e0b1357ac11ed25a068d134118ae6cb4ef3 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 14:16:48 -0300 Subject: [PATCH 10/14] appDisplay: Save pages when folder apps change After dragging an icon to inside a folder, we do not save the grid layout, leaving the icon's position stored when it actually isn't there anymore. Fix that by saving pages whenever folder apps change. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 8979d7e57c..840d4fd3e0 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -1097,7 +1097,10 @@ class AppDisplay extends BaseAppView { let icon = this._items.get(id); if (!icon) { icon = new FolderIcon(id, path, this); - icon.connect('apps-changed', this._redisplay.bind(this)); + icon.connect('apps-changed', () => { + this._redisplay(); + this._savePages(); + }); } // Don't try to display empty folders -- GitLab From 33bd038af256551430b21f9fe96711515d745fb4 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 15:57:12 -0300 Subject: [PATCH 11/14] appDisplay: Don't change opacity on destruction At the end of BaseAppView._clearAnimateLater(), the '_grid' actor's opacity is set to 255. As it turns out, _clearAnimateLater() is called, among others, by vfunc_unmap(). However, unmapping is part of the destruction process, and at the time it is called, '_grid' is already destroying, which makes GJS complain about accessing an invalid object. Don't change opacity on BaseAppView._clearAnimateLater(), and instead move it to the couple of places outside vfunc_unmap() that call it. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 840d4fd3e0..fa2782d81e 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -654,7 +654,6 @@ var BaseAppView = GObject.registerClass({ this.disconnect(this._viewLoadedHandlerId); this._viewLoadedHandlerId = 0; } - this._grid.opacity = 255; } animate(animationDirection, onComplete) { @@ -666,6 +665,7 @@ var BaseAppView = GObject.registerClass({ } this._clearAnimateLater(); + this._grid.opacity = 255; if (animationDirection == IconGrid.AnimationDirection.IN) { const doSpringAnimationLater = laterType => { @@ -684,6 +684,7 @@ var BaseAppView = GObject.registerClass({ this._viewLoadedHandlerId = this.connect('view-loaded', () => { this._clearAnimateLater(); + this._grid.opacity = 255; doSpringAnimationLater(Meta.LaterType.BEFORE_REDRAW); }); } -- GitLab From 1acbdcc9b3b01298dc52040103e6f640953c1b8f Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 16:31:00 -0300 Subject: [PATCH 12/14] appDisplay: Allow passing callback to AppFolderDialog.popdown() Next commit will allow removing icons from the folder, and we want to do that effectively after the dialog pops down. Add an optional callback to AppFolderDialog.popdown(). https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index fa2782d81e..43bab2db98 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -2093,6 +2093,8 @@ var AppFolderDialog = GObject.registerClass({ this._sourceMappedId = 0; this._popdownTimeoutId = 0; this._needsZoomAndFade = false; + + this._popdownCallbacks = []; } _addFolderNameEntry() { @@ -2297,6 +2299,9 @@ var AppFolderDialog = GObject.registerClass({ opacity: 255, }); this.hide(); + + this._popdownCallbacks.forEach(func => func()); + this._popdownCallbacks = []; }, }); @@ -2477,7 +2482,16 @@ var AppFolderDialog = GObject.registerClass({ this.emit('open-state-changed', true); } - popdown() { + popdown(callback) { + // Either call the callback right away, or wait for the zoom out + // animation to finish + if (callback) { + if (this.visible) + this._popdownCallbacks.push(callback); + else + callback(); + } + if (!this._isOpen) return; -- GitLab From aaff88a6bb06ebc6acf9b4ceaa456d679c71fdaa Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 16:06:01 -0300 Subject: [PATCH 13/14] appDisplay: Remove icon from folder when dragging out When dragging an icon outside of a folder dialog, there's a small delay before the dialog pops down. If the icon is dropped during this delay, the drag is cancelled, and the icon continues to be in the folder. However, this behavior turned out to be problematic, and it was a common point of failure that throwing icons outside folders wouldn't work. Remove the icon from the folder when dragging it to outside the dialog. Related: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3092 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 43bab2db98..ce6754c562 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -2454,7 +2454,18 @@ var AppFolderDialog = GObject.registerClass({ this._setupDragMonitor(); } - return DND.DragMotionResult.NO_DROP; + return DND.DragMotionResult.MOVE_DROP; + } + + acceptDrop(source) { + const appId = source.id; + + this.popdown(() => { + this._view.removeApp(source); + this._appDisplay.selectApp(appId); + }); + + return true; } toggle() { -- GitLab From d04daf6a1c54d716367100fb352493a05bbd9157 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 24 Sep 2020 18:57:56 -0300 Subject: [PATCH 14/14] appDisplay: Protect against source icon destruction When dragging icons out of a folder dialog, there is a very peculiar combination of steps that may break GNOME Shell: 1. Open an app folder dialog 2. Start dragging an icon to outside the grid 3. Wait until the popdown animation starts 4. Before it finishes, drop the icon 5. See the warnings / crash That's caused by the source icon being destroyed after the delayed move timer starts, and before it finishes. Protect against the source icon being destroyed before the delayed move timeout triggers by connecting to the 'destroy' signal and removing the timeout on the callback. Use a single field, called '_delayedMoveData', to store all data related to delayed moves. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1447 --- js/ui/appDisplay.js | 52 +++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index ce6754c562..56b4a58714 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -199,8 +199,7 @@ var BaseAppView = GObject.registerClass({ // Drag n' Drop this._lastOvershoot = -1; this._lastOvershootTimeoutId = 0; - this._delayedMoveId = 0; - this._targetDropPosition = null; + this._delayedMoveData = null; this._dragBeginId = 0; this._dragEndId = 0; @@ -355,29 +354,40 @@ var BaseAppView = GObject.registerClass({ return; } - if (!this._targetDropPosition || - this._targetDropPosition.page !== page || - this._targetDropPosition.position !== position) { + if (!this._delayedMoveData || + this._delayedMoveData.page !== page || + this._delayedMoveData.position !== position) { // Update the item with a small delay this._removeDelayedMove(); - this._targetDropPosition = { page, position }; - - this._delayedMoveId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, - DELAYED_MOVE_TIMEOUT, () => { - this._moveItem(source, page, position); - this._targetDropPosition = null; - this._delayedMoveId = 0; - return GLib.SOURCE_REMOVE; - }); + this._delayedMoveData = { + page, + position, + source, + destroyId: source.connect('destroy', () => this._removeDelayedMove()), + timeoutId: GLib.timeout_add(GLib.PRIORITY_DEFAULT, + DELAYED_MOVE_TIMEOUT, () => { + this._moveItem(source, page, position); + this._delayedMoveData.timeoutId = 0; + this._removeDelayedMove(); + return GLib.SOURCE_REMOVE; + }), + }; } } _removeDelayedMove() { - if (this._delayedMoveId > 0) { - GLib.source_remove(this._delayedMoveId); - this._delayedMoveId = 0; - } - this._targetDropPosition = null; + if (!this._delayedMoveData) + return; + + const { source, destroyId, timeoutId } = this._delayedMoveData; + + if (timeoutId > 0) + GLib.source_remove(timeoutId); + + if (destroyId > 0) + source.disconnect(destroyId); + + this._delayedMoveData = null; } _resetOvershoot() { @@ -495,8 +505,8 @@ var BaseAppView = GObject.registerClass({ return false; // Dropped before the icon was moved - if (this._targetDropPosition) { - const { page, position } = this._targetDropPosition; + if (this._delayedMoveData) { + const { page, position } = this._delayedMoveData; this._moveItem(source, page, position); this._removeDelayedMove(); -- GitLab