iconGrid: Add item nudge API
Nudging an item can be done via one side, or both. This is essentially a set of helpers for Drag n' Drop to use. The x and y values are relative to the icon grid itself. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/603
This commit is contained in:
parent
0dd430f2f4
commit
a9e7b7853a
@ -255,6 +255,18 @@ class BaseAppView {
|
|||||||
|
|
||||||
Tweener.addTween(this._grid, params);
|
Tweener.addTween(this._grid, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canDropAt(x, y) {
|
||||||
|
return this._grid.canDropAt(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
nudgeItemsAtIndex(index, dragLocation) {
|
||||||
|
this._grid.nudgeItemsAtIndex(index, dragLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeNudges() {
|
||||||
|
this._grid.removeNudges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Signals.addSignalMethods(BaseAppView.prototype);
|
Signals.addSignalMethods(BaseAppView.prototype);
|
||||||
|
|
||||||
|
@ -29,6 +29,25 @@ var AnimationDirection = {
|
|||||||
var APPICON_ANIMATION_OUT_SCALE = 3;
|
var APPICON_ANIMATION_OUT_SCALE = 3;
|
||||||
var APPICON_ANIMATION_OUT_TIME = 0.25;
|
var APPICON_ANIMATION_OUT_TIME = 0.25;
|
||||||
|
|
||||||
|
const LEFT_DIVIDER_LEEWAY = 30;
|
||||||
|
const RIGHT_DIVIDER_LEEWAY = 30;
|
||||||
|
|
||||||
|
const NUDGE_ANIMATION_TYPE = Clutter.AnimationMode.EASE_OUT_ELASTIC;
|
||||||
|
const NUDGE_DURATION = 800;
|
||||||
|
|
||||||
|
const NUDGE_RETURN_ANIMATION_TYPE = Clutter.AnimationMode.EASE_OUT_QUINT;
|
||||||
|
const NUDGE_RETURN_DURATION = 300;
|
||||||
|
|
||||||
|
const NUDGE_FACTOR = 0.33;
|
||||||
|
|
||||||
|
var DragLocation = {
|
||||||
|
DEFAULT: 0,
|
||||||
|
ON_ICON: 1,
|
||||||
|
START_EDGE: 2,
|
||||||
|
END_EDGE: 3,
|
||||||
|
EMPTY_AREA: 4,
|
||||||
|
}
|
||||||
|
|
||||||
var BaseIcon = GObject.registerClass(
|
var BaseIcon = GObject.registerClass(
|
||||||
class BaseIcon extends St.Bin {
|
class BaseIcon extends St.Bin {
|
||||||
_init(label, params) {
|
_init(label, params) {
|
||||||
@ -807,6 +826,223 @@ var IconGrid = GObject.registerClass({
|
|||||||
}
|
}
|
||||||
return GLib.SOURCE_REMOVE;
|
return GLib.SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drag n' Drop methods
|
||||||
|
|
||||||
|
nudgeItemsAtIndex(index, dragLocation) {
|
||||||
|
// No nudging when the cursor is in an empty area
|
||||||
|
if (dragLocation == DragLocation.EMPTY_AREA || dragLocation == DragLocation.ON_ICON)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let children = this.get_children().filter(c => c.is_visible());
|
||||||
|
let nudgeIndex = index;
|
||||||
|
let rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
|
||||||
|
|
||||||
|
if (dragLocation != DragLocation.START_EDGE) {
|
||||||
|
let leftItem = children[nudgeIndex - 1];
|
||||||
|
let offset = rtl ? Math.floor(this._hItemSize * NUDGE_FACTOR) : Math.floor(-this._hItemSize * NUDGE_FACTOR);
|
||||||
|
this._animateNudge(leftItem, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nudge the icon to the right if we are the first item or not at the
|
||||||
|
// end of row
|
||||||
|
if (dragLocation != DragLocation.END_EDGE) {
|
||||||
|
let rightItem = children[nudgeIndex];
|
||||||
|
let offset = rtl ? Math.floor(-this._hItemSize * NUDGE_FACTOR) : Math.floor(this._hItemSize * NUDGE_FACTOR);
|
||||||
|
this._animateNudge(rightItem, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeNudges() {
|
||||||
|
let children = this.get_children().filter(c => c.is_visible());
|
||||||
|
for (let index = 0; index < children.length; index++) {
|
||||||
|
this._animateNudge(children[index],
|
||||||
|
NUDGE_RETURN_ANIMATION_TYPE,
|
||||||
|
NUDGE_RETURN_DURATION,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_animateNudge(item, animationType, duration, offset) {
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.save_easing_state();
|
||||||
|
item.set_easing_mode(animationType);
|
||||||
|
item.set_easing_duration(duration);
|
||||||
|
item.translation_x = offset;
|
||||||
|
item.restore_easing_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is overriden by the PaginatedIconGrid subclass so we can
|
||||||
|
// take into account the extra space when dragging from a folder
|
||||||
|
_calculateDndRow(y) {
|
||||||
|
let rowHeight = this._getVItemSize() + this._getSpacing();
|
||||||
|
return Math.floor(y / rowHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the drop point index or -1 if we can't drop there
|
||||||
|
canDropAt(x, y) {
|
||||||
|
// This is an complex calculation, but in essence, we divide the grid
|
||||||
|
// as:
|
||||||
|
//
|
||||||
|
// left empty space
|
||||||
|
// | left padding right padding
|
||||||
|
// | | width without padding |
|
||||||
|
// +--------+---+---------------------------------------+-----+
|
||||||
|
// | | | | | | | |
|
||||||
|
// | | | | | | | |
|
||||||
|
// | | |--------+-----------+----------+-------| |
|
||||||
|
// | | | | | | | |
|
||||||
|
// | | | | | | | |
|
||||||
|
// | | |--------+-----------+----------+-------| |
|
||||||
|
// | | | | | | | |
|
||||||
|
// | | | | | | | |
|
||||||
|
// | | |--------+-----------+----------+-------| |
|
||||||
|
// | | | | | | | |
|
||||||
|
// | | | | | | | |
|
||||||
|
// +--------+---+---------------------------------------+-----+
|
||||||
|
//
|
||||||
|
// The left empty space is immediately discarded, and ignored in all
|
||||||
|
// calculations.
|
||||||
|
//
|
||||||
|
// The width (with paddings) is used to determine if we're dragging
|
||||||
|
// over the left or right padding, and which column is being dragged
|
||||||
|
// on.
|
||||||
|
//
|
||||||
|
// Finally, the width without padding is used to figure out where in
|
||||||
|
// the icon (start edge, end edge, on it, etc) the cursor is.
|
||||||
|
|
||||||
|
let [nColumns, usedWidth] = this._computeLayout(this.width);
|
||||||
|
|
||||||
|
let leftEmptySpace;
|
||||||
|
switch (this._xAlign) {
|
||||||
|
case St.Align.START:
|
||||||
|
leftEmptySpace = 0;
|
||||||
|
break;
|
||||||
|
case St.Align.MIDDLE:
|
||||||
|
leftEmptySpace = Math.floor((this.width - usedWidth) / 2);
|
||||||
|
break;
|
||||||
|
case St.Align.END:
|
||||||
|
leftEmptySpace = availWidth - usedWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
x -= leftEmptySpace;
|
||||||
|
y -= this.topPadding;
|
||||||
|
|
||||||
|
let row = this._calculateDndRow(y);
|
||||||
|
|
||||||
|
// Correct sx to handle the left padding to correctly calculate
|
||||||
|
// the column
|
||||||
|
let rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
|
||||||
|
let gridX = x - this.leftPadding;
|
||||||
|
|
||||||
|
let widthWithoutPadding = usedWidth - this.leftPadding - this.rightPadding;
|
||||||
|
let columnWidth = widthWithoutPadding / nColumns;
|
||||||
|
|
||||||
|
let column;
|
||||||
|
if (x < this.leftPadding)
|
||||||
|
column = 0;
|
||||||
|
else if (x > usedWidth - this.rightPadding)
|
||||||
|
column = nColumns - 1;
|
||||||
|
else
|
||||||
|
column = Math.floor(gridX / columnWidth);
|
||||||
|
|
||||||
|
let isFirstIcon = column == 0;
|
||||||
|
let isLastIcon = column == nColumns - 1;
|
||||||
|
|
||||||
|
// If we're outside of the grid, we are in an invalid drop location
|
||||||
|
if (x < 0 || x > usedWidth)
|
||||||
|
return [-1, DragLocation.DEFAULT];
|
||||||
|
|
||||||
|
let children = this.get_children().filter(c => c.is_visible());
|
||||||
|
let childIndex = Math.min((row * nColumns) + column, children.length);
|
||||||
|
|
||||||
|
// If we're above the grid vertically, we are in an invalid
|
||||||
|
// drop location
|
||||||
|
if (childIndex < 0)
|
||||||
|
return [-1, DragLocation.DEFAULT];
|
||||||
|
|
||||||
|
// If we're past the last visible element in the grid,
|
||||||
|
// we might be allowed to drop there.
|
||||||
|
if (childIndex >= children.length)
|
||||||
|
return [children.length, DragLocation.EMPTY_AREA];
|
||||||
|
|
||||||
|
let child = children[childIndex];
|
||||||
|
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight] = child.get_preferred_size();
|
||||||
|
|
||||||
|
// This is the width of the cell that contains the icon
|
||||||
|
// (excluding spacing between cells)
|
||||||
|
let childIconWidth = Math.max(this._getHItemSize(), childNaturalWidth);
|
||||||
|
|
||||||
|
// Calculate the original position of the child icon (prior to nudging)
|
||||||
|
let childX;
|
||||||
|
if (rtl)
|
||||||
|
childX = widthWithoutPadding - (column * columnWidth) - childIconWidth;
|
||||||
|
else
|
||||||
|
childX = column * columnWidth;
|
||||||
|
|
||||||
|
let iconLeftX = childX + LEFT_DIVIDER_LEEWAY;
|
||||||
|
let iconRightX = childX + childIconWidth - RIGHT_DIVIDER_LEEWAY
|
||||||
|
|
||||||
|
let dropIndex;
|
||||||
|
let dragLocation;
|
||||||
|
|
||||||
|
x -= this.leftPadding;
|
||||||
|
|
||||||
|
if (x < iconLeftX) {
|
||||||
|
// We are to the left of the icon target
|
||||||
|
if (isFirstIcon || x < 0) {
|
||||||
|
// We are before the leftmost icon on the grid
|
||||||
|
if (rtl) {
|
||||||
|
dropIndex = childIndex + 1;
|
||||||
|
dragLocation = DragLocation.END_EDGE;
|
||||||
|
} else {
|
||||||
|
dropIndex = childIndex;
|
||||||
|
dragLocation = DragLocation.START_EDGE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We are between the previous icon (next in RTL) and this one
|
||||||
|
if (rtl)
|
||||||
|
dropIndex = childIndex + 1;
|
||||||
|
else
|
||||||
|
dropIndex = childIndex;
|
||||||
|
|
||||||
|
dragLocation = DragLocation.DEFAULT;
|
||||||
|
}
|
||||||
|
} else if (x >= iconRightX) {
|
||||||
|
// We are to the right of the icon target
|
||||||
|
if (childIndex >= children.length) {
|
||||||
|
// We are beyond the last valid icon
|
||||||
|
// (to the right of the app store / trash can, if present)
|
||||||
|
dropIndex = -1;
|
||||||
|
dragLocation = DragLocation.DEFAULT;
|
||||||
|
} else if (isLastIcon || x >= widthWithoutPadding) {
|
||||||
|
// We are beyond the rightmost icon on the grid
|
||||||
|
if (rtl) {
|
||||||
|
dropIndex = childIndex;
|
||||||
|
dragLocation = DragLocation.START_EDGE;
|
||||||
|
} else {
|
||||||
|
dropIndex = childIndex + 1;
|
||||||
|
dragLocation = DragLocation.END_EDGE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We are between this icon and the next one (previous in RTL)
|
||||||
|
if (rtl)
|
||||||
|
dropIndex = childIndex;
|
||||||
|
else
|
||||||
|
dropIndex = childIndex + 1;
|
||||||
|
|
||||||
|
dragLocation = DragLocation.DEFAULT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We are over the icon target area
|
||||||
|
dropIndex = childIndex;
|
||||||
|
dragLocation = DragLocation.ON_ICON;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [dropIndex, dragLocation];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var PaginatedIconGrid = GObject.registerClass({
|
var PaginatedIconGrid = GObject.registerClass({
|
||||||
@ -881,6 +1117,23 @@ var PaginatedIconGrid = GObject.registerClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Overridden from IconGrid
|
// Overridden from IconGrid
|
||||||
|
_calculateDndRow(y) {
|
||||||
|
let row = super._calculateDndRow(y);
|
||||||
|
|
||||||
|
// If there's no extra space, just return the current value and maintain
|
||||||
|
// the same behavior when without a folder opened.
|
||||||
|
if (!this._extraSpaceData)
|
||||||
|
return row;
|
||||||
|
|
||||||
|
let [ baseRow, nRowsUp, nRowsDown ] = this._extraSpaceData;
|
||||||
|
let newRow = row + nRowsUp;
|
||||||
|
|
||||||
|
if (row > baseRow)
|
||||||
|
newRow -= nRowsDown;
|
||||||
|
|
||||||
|
return newRow;
|
||||||
|
}
|
||||||
|
|
||||||
_getChildrenToAnimate() {
|
_getChildrenToAnimate() {
|
||||||
let children = this._getVisibleChildren();
|
let children = this._getVisibleChildren();
|
||||||
let firstIndex = this._childrenPerPage * this.currentPage;
|
let firstIndex = this._childrenPerPage * this.currentPage;
|
||||||
|
Loading…
Reference in New Issue
Block a user