appDisplay: Move DnD code to BaseAppView

This code will be shared with FolderView in the next commit, so
avoid duplication already and move the to-be-shared code into the
base class.

Because BaseAppView can handle vertical and horizontal orientations,
adapt the drag overshoot code to also handle horizontal overshoot.

https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1284
This commit is contained in:
Georges Basile Stavracas Neto 2020-06-24 14:53:12 -03:00 committed by Florian Müllner
parent 60311aa4d1
commit 704e08dc08

View File

@ -122,6 +122,7 @@ var BaseAppView = GObject.registerClass({
super._init(params); super._init(params);
this._grid = this._createGrid(); this._grid = this._createGrid();
this._grid._delegate = this;
// Standard hack for ClutterBinLayout // Standard hack for ClutterBinLayout
this._grid.x_expand = true; this._grid.x_expand = true;
@ -189,6 +190,46 @@ var BaseAppView = GObject.registerClass({
this._parentalControlsManager.connect('app-filter-changed', () => { this._parentalControlsManager.connect('app-filter-changed', () => {
this._redisplay(); this._redisplay();
}); });
// Drag n' Drop
this._lastOvershoot = -1;
this._lastOvershootTimeoutId = 0;
this._delayedMoveId = 0;
this._targetDropPosition = null;
this._dragBeginId =
Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
this._dragEndId =
Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
this._dragCancelledId =
Main.overview.connect('item-drag-cancelled', this._onDragCancelled.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
}
_onDestroy() {
this._removeDelayedMove();
if (this._dragBeginId > 0) {
Main.overview.disconnect(this._dragBeginId);
this._dragBeginId = 0;
}
if (this._dragEndId > 0) {
Main.overview.disconnect(this._dragEndId);
this._dragEndId = 0;
}
if (this._dragCancelledId > 0) {
Main.overview.disconnect(this._dragCancelledId);
this._dragCancelledId = 0;
}
if (this._dragMonitor) {
DND.removeDragMonitor(this._dragMonitor);
this._dragMonitor = null;
}
} }
_createGrid() { _createGrid() {
@ -275,6 +316,178 @@ var BaseAppView = GObject.registerClass({
}); });
} }
_maybeMoveItem(dragEvent) {
const [success, x, y] =
this._grid.transform_stage_point(dragEvent.x, dragEvent.y);
if (!success)
return;
const { source } = dragEvent;
const [page, position, dragLocation] =
this._getDropTarget(x, y, source);
const item = position !== -1
? this._grid.getItemAt(page, position) : null;
// Dragging over invalid parts of the grid cancels the timeout
if (item === source ||
dragLocation === IconGrid.DragLocation.INVALID ||
dragLocation === IconGrid.DragLocation.ON_ICON) {
this._removeDelayedMove();
return;
}
if (!this._targetDropPosition ||
this._targetDropPosition.page !== page ||
this._targetDropPosition.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;
});
}
}
_removeDelayedMove() {
if (this._delayedMoveId > 0) {
GLib.source_remove(this._delayedMoveId);
this._delayedMoveId = 0;
}
this._targetDropPosition = null;
}
_resetOvershoot() {
if (this._lastOvershootTimeoutId)
GLib.source_remove(this._lastOvershootTimeoutId);
this._lastOvershootTimeoutId = 0;
this._lastOvershoot = -1;
}
_handleDragOvershoot(dragEvent) {
const [gridX, gridY] = this.get_transformed_position();
const [gridWidth, gridHeight] = this.get_transformed_size();
const vertical = this._orientation === Clutter.Orientation.VERTICAL;
const gridStart = vertical ? gridY : gridX;
const gridEnd = vertical
? gridY + gridHeight - OVERSHOOT_THRESHOLD
: gridX + gridWidth - OVERSHOOT_THRESHOLD;
// Already animating
if (this._adjustment.get_transition('value') !== null)
return;
// Within the grid boundaries
const dragPosition = vertical ? dragEvent.y : dragEvent.x;
if (dragPosition > gridStart && dragPosition < gridEnd) {
// Check whether we moved out the area of the last switch
if (Math.abs(this._lastOvershoot - dragPosition) > OVERSHOOT_THRESHOLD)
this._resetOvershoot();
return;
}
// Still in the area of the previous page switch
if (this._lastOvershoot >= 0)
return;
const currentPosition = this._adjustment.value;
const maxPosition = this._adjustment.upper - this._adjustment.page_size;
if (dragPosition <= gridStart && currentPosition > 0)
this.goToPage(this._grid.currentPage - 1);
else if (dragPosition >= gridEnd && currentPosition < maxPosition)
this.goToPage(this._grid.currentPage + 1);
else
return; // don't go beyond first/last page
this._lastOvershoot = dragPosition;
if (this._lastOvershootTimeoutId > 0)
GLib.source_remove(this._lastOvershootTimeoutId);
this._lastOvershootTimeoutId =
GLib.timeout_add(GLib.PRIORITY_DEFAULT, OVERSHOOT_TIMEOUT, () => {
this._resetOvershoot();
this._handleDragOvershoot(dragEvent);
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._lastOvershootTimeoutId,
'[gnome-shell] this._lastOvershootTimeoutId');
}
_onDragBegin() {
this._dragMonitor = {
dragMotion: this._onDragMotion.bind(this),
};
DND.addDragMonitor(this._dragMonitor);
}
_onDragMotion(dragEvent) {
if (!(dragEvent.source instanceof AppViewItem))
return DND.DragMotionResult.CONTINUE;
const appIcon = dragEvent.source;
// Handle the drag overshoot. When dragging to above the
// icon grid, move to the page above; when dragging below,
// move to the page below.
if (appIcon instanceof AppViewItem)
this._handleDragOvershoot(dragEvent);
this._maybeMoveItem(dragEvent);
return DND.DragMotionResult.CONTINUE;
}
_onDragEnd() {
if (this._dragMonitor) {
DND.removeDragMonitor(this._dragMonitor);
this._dragMonitor = null;
}
this._resetOvershoot();
}
_onDragCancelled() {
// At this point, the positions aren't stored yet, thus _redisplay()
// will move all items to their original positions
this._redisplay();
}
_canAccept(source) {
return source instanceof AppViewItem;
}
handleDragOver(source) {
if (!this._canAccept(source))
return DND.DragMotionResult.NO_DROP;
return DND.DragMotionResult.MOVE_DROP;
}
acceptDrop(source) {
if (!this._canAccept(source))
return false;
// Dropped before the icon was moved
if (this._targetDropPosition) {
const { page, position } = this._targetDropPosition;
this._moveItem(source, page, position);
this._removeDelayedMove();
}
return true;
}
_addItem(item, page, position) { _addItem(item, page, position) {
let itemIndex = 0; let itemIndex = 0;
@ -646,7 +859,6 @@ class AppDisplay extends BaseAppView {
y_expand: true, y_expand: true,
}); });
this._grid._delegate = this;
this._pageManager = new PageManager(); this._pageManager = new PageManager();
this._scrollView.add_style_class_name('all-apps'); this._scrollView.add_style_class_name('all-apps');
@ -667,10 +879,6 @@ class AppDisplay extends BaseAppView {
this._displayingDialog = false; this._displayingDialog = false;
this._currentDialogDestroyId = 0; this._currentDialogDestroyId = 0;
this._lastOvershootY = -1;
this._lastOvershootTimeoutId = 0;
this._delayedMoveId = 0;
this._targetDropPosition = null;
this._placeholder = null; this._placeholder = null;
Main.overview.connect('hidden', () => this.goToPage(0)); Main.overview.connect('hidden', () => this.goToPage(0));
@ -687,12 +895,6 @@ class AppDisplay extends BaseAppView {
Main.queueDeferredWork(this._redisplayWorkId); Main.queueDeferredWork(this._redisplayWorkId);
}); });
Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
Main.overview.connect('item-drag-cancelled', this._onDragCancelled.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
this._switcherooNotifyId = global.connect('notify::switcheroo-control', this._switcherooNotifyId = global.connect('notify::switcheroo-control',
() => this._updateDiscreteGpuAvailable()); () => this._updateDiscreteGpuAvailable());
this._updateDiscreteGpuAvailable(); this._updateDiscreteGpuAvailable();
@ -709,7 +911,7 @@ class AppDisplay extends BaseAppView {
} }
_onDestroy() { _onDestroy() {
this._removeDelayedMove(); super._onDestroy();
if (this._scrollTimeoutId !== 0) { if (this._scrollTimeoutId !== 0) {
GLib.source_remove(this._scrollTimeoutId); GLib.source_remove(this._scrollTimeoutId);
@ -975,112 +1177,17 @@ class AppDisplay extends BaseAppView {
}); });
} }
_removeDelayedMove() {
if (this._delayedMoveId > 0) {
GLib.source_remove(this._delayedMoveId);
this._delayedMoveId = 0;
}
this._targetDropPosition = null;
}
_maybeMoveItem(dragEvent) { _maybeMoveItem(dragEvent) {
const [success, x, y] = const clonedEvent = {
this._grid.transform_stage_point(dragEvent.x, dragEvent.y); ...dragEvent,
source: this._placeholder ? this._placeholder : dragEvent.source,
if (!success)
return;
const source = this._placeholder ? this._placeholder : dragEvent.source;
const [page, position, dragLocation] =
this._getDropTarget(x, y, source);
const item = position !== -1
? this._grid.getItemAt(page, position) : null;
// Dragging over invalid parts of the grid cancels the timeout
if (item === source ||
dragLocation === IconGrid.DragLocation.INVALID ||
dragLocation === IconGrid.DragLocation.ON_ICON) {
this._removeDelayedMove();
return;
}
if (!this._targetDropPosition ||
this._targetDropPosition.page !== page ||
this._targetDropPosition.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;
});
}
}
_resetOvershoot() {
if (this._lastOvershootTimeoutId)
GLib.source_remove(this._lastOvershootTimeoutId);
this._lastOvershootTimeoutId = 0;
this._lastOvershootY = -1;
}
_handleDragOvershoot(dragEvent) {
let [, gridY] = this.get_transformed_position();
let [, gridHeight] = this.get_transformed_size();
const gridBottom = gridY + gridHeight - OVERSHOOT_THRESHOLD;
// Already animating
if (this._adjustment.get_transition('value') !== null)
return;
// Within the grid boundaries
if (dragEvent.y > gridY && dragEvent.y < gridBottom) {
// Check whether we moved out the area of the last switch
if (Math.abs(this._lastOvershootY - dragEvent.y) > OVERSHOOT_THRESHOLD)
this._resetOvershoot();
return;
}
// Still in the area of the previous page switch
if (this._lastOvershootY >= 0)
return;
let currentY = this._adjustment.value;
let maxY = this._adjustment.upper - this._adjustment.page_size;
if (dragEvent.y <= gridY && currentY > 0)
this.goToPage(this._grid.currentPage - 1);
else if (dragEvent.y >= gridBottom && currentY < maxY)
this.goToPage(this._grid.currentPage + 1);
else
return; // don't go beyond first/last page
this._lastOvershootY = dragEvent.y;
if (this._lastOvershootTimeoutId > 0)
GLib.source_remove(this._lastOvershootTimeoutId);
this._lastOvershootTimeoutId =
GLib.timeout_add(GLib.PRIORITY_DEFAULT, OVERSHOOT_TIMEOUT, () => {
this._resetOvershoot();
this._handleDragOvershoot(dragEvent);
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._lastOvershootTimeoutId,
'[gnome-shell] this._lastOvershootTimeoutId');
}
_onDragBegin(_overview, source) {
this._dragMonitor = {
dragMotion: this._onDragMotion.bind(this),
}; };
DND.addDragMonitor(this._dragMonitor);
super._maybeMoveItem(clonedEvent);
}
_onDragBegin(overview, source) {
super._onDragBegin(overview, source);
// When dragging from a folder dialog, the dragged app icon doesn't // When dragging from a folder dialog, the dragged app icon doesn't
// exist in AppDisplay. We work around that by adding a placeholder // exist in AppDisplay. We work around that by adding a placeholder
@ -1091,66 +1198,30 @@ class AppDisplay extends BaseAppView {
} }
_onDragMotion(dragEvent) { _onDragMotion(dragEvent) {
if (!(dragEvent.source instanceof AppViewItem)) if (this._currentDialog)
return DND.DragMotionResult.CONTINUE; return DND.DragMotionResult.CONTINUE;
let appIcon = dragEvent.source; return super._onDragMotion(dragEvent);
// Handle the drag overshoot. When dragging to above the
// icon grid, move to the page above; when dragging below,
// move to the page below.
if (appIcon instanceof AppViewItem)
this._handleDragOvershoot(dragEvent);
this._maybeMoveItem(dragEvent);
return DND.DragMotionResult.CONTINUE;
} }
_onDragEnd() { _onDragEnd() {
if (this._dragMonitor) { super._onDragEnd();
DND.removeDragMonitor(this._dragMonitor);
this._dragMonitor = null;
}
this._resetOvershoot();
this._removePlaceholder(); this._removePlaceholder();
} }
_onDragCancelled(_overview, source) { _onDragCancelled(overview, source) {
const view = _getViewFromIcon(source); const view = _getViewFromIcon(source);
if (view instanceof FolderView) if (view instanceof FolderView)
return; return;
// At this point, the positions aren't stored yet, thus _redisplay() super._onDragCancelled(overview, source);
// will move all items to their original positions
this._redisplay();
}
_canAccept(source) {
return source instanceof AppViewItem;
}
handleDragOver(source) {
if (!this._canAccept(source))
return DND.DragMotionResult.NO_DROP;
return DND.DragMotionResult.MOVE_DROP;
} }
acceptDrop(source) { acceptDrop(source) {
if (!this._canAccept(source)) if (!super.acceptDrop(source))
return false; return false;
// Dropped before the icon was moved
if (this._targetDropPosition) {
const { page, position } = this._targetDropPosition;
this._moveItem(source, page, position);
this._removeDelayedMove();
}
this._savePages(); this._savePages();
let view = _getViewFromIcon(source); let view = _getViewFromIcon(source);