[gnome-shell/gbsneto/custom-icon-positions: 20/35] iconGrid: Add item nudge API
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/gbsneto/custom-icon-positions: 20/35] iconGrid: Add item nudge API
- Date: Wed, 27 May 2020 01:45:14 +0000 (UTC)
commit 6470040bfac6af0d5eea27a6fce1dcd86128a09e
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date: Mon May 25 15:58:39 2020 -0300
iconGrid: Add item nudge API
Add a new item nudging API, and the acompanying drop target
API. The bulk of it is implemented by IconGridLayout, since
it's this layout manager that knows where each icon is placed
at.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1284
js/ui/appDisplay.js | 85 ++++++++++++++++++++++++++
js/ui/iconGrid.js | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 252 insertions(+)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 05f580fdfd..da0af64649 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -180,6 +180,8 @@ var BaseAppView = GObject.registerClass({
this._availWidth = 0;
this._availHeight = 0;
+ this._nudgedItem = null;
+ this._nudgedLocation = -1;
this._items = new Map();
this._orderedItems = [];
@@ -350,6 +352,47 @@ var BaseAppView = GObject.registerClass({
this._grid.opacity = 255;
}
+ _getRealDropPosition(item, dragLocation) {
+ const itemsPerPage = this._grid.itemsPerPage;
+ let [page, position] = this._grid.getItemPosition(item);
+
+ if (dragLocation === IconGrid.DragLocation.END_EDGE)
+ position += 1;
+
+ if (position >= itemsPerPage) {
+ position %= itemsPerPage;
+ page++;
+ } else if (position < 0) {
+ page--;
+ if (page >= 0) {
+ const pageItems =
+ this._grid.getItemsAtPage(page).filter(c => c.visible);
+ position = pageItems.length - 1;
+ } else {
+ position = 0;
+ page = 0;
+ }
+ }
+
+ return [page, position];
+ }
+
+ _nudgedItemReallyChanged(item, dragLocation) {
+ if (!this._nudgedItem)
+ return true;
+
+ if (dragLocation === IconGrid.DragLocation.ON_ICON ||
+ this._nudgedLocation === IconGrid.DragLocation.ON_ICON)
+ return true;
+
+ const [realPage, realPosition] =
+ this._getRealDropPosition(item, dragLocation);
+ const [oldPage, oldPosition] =
+ this._getRealDropPosition(this._nudgedItem, this._nudgedLocation);
+
+ return realPage !== oldPage || realPosition !== oldPosition;
+ }
+
animate(animationDirection, onComplete) {
if (onComplete) {
let animationDoneId = this._grid.connect('animation-done', () => {
@@ -435,6 +478,48 @@ var BaseAppView = GObject.registerClass({
this._grid.goToPage(pageNumber, animate);
}
+ getDropTarget(x, y) {
+ let [item, dragLocation] = this._grid.getDropTarget(x, y);
+
+ // Append to the page if dragging over empty area
+ if (dragLocation === IconGrid.DragLocation.EMPTY_SPACE) {
+ const currentPage = this._grid.currentPage;
+ const pageItems =
+ this._grid.getItemsAtPage(currentPage).filter(c => c.visible);
+
+ item = pageItems[pageItems.length - 1];
+ dragLocation = IconGrid.DragLocation.END_EDGE;
+ }
+
+ return [item, dragLocation];
+ }
+
+ nudgeItem(item, dragLocation) {
+ if (this._nudgedItem === item && this._nudgedLocation === dragLocation)
+ return;
+
+ // Did the nudged item actually change?
+ if (!this._nudgedItemReallyChanged(item, dragLocation))
+ return;
+
+ this.removeNudges();
+
+ this._nudgedItem = item;
+ this._nudgedLocation = dragLocation;
+
+ this._grid.nudgeItem(item, dragLocation);
+ }
+
+ removeNudges() {
+ if (!this._nudgedItem)
+ return;
+
+ this._nudgedItem = null;
+ this._nudgedLocation = -1;
+
+ this._grid.removeNudges();
+ }
+
adaptToSize(width, height) {
let box = new Clutter.ActorBox();
box.x1 = 0;
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 50429fb362..e18f5f0823 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -52,6 +52,25 @@ const gridModes = [
},
];
+var LEFT_DIVIDER_LEEWAY = 20;
+var RIGHT_DIVIDER_LEEWAY = 20;
+
+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 = {
+ INVALID: 0,
+ START_EDGE: 1,
+ ON_ICON: 2,
+ END_EDGE: 3,
+ EMPTY_SPACE: 4,
+};
+
var BaseIcon = GObject.registerClass(
class BaseIcon extends St.Bin {
_init(label, params) {
@@ -899,6 +918,97 @@ var IconGridLayout = GObject.registerClass({
return itemData.pageIndex;
}
+ /**
+ * getDropTarget:
+ * @param {int} x: position of the horizontal axis
+ * @param {int} y: position of the vertical axis
+ * @param {int} page: page, or -1 for the current page
+ *
+ * Retrieves the item located at (@x, @y), as well as the position.
+ * Both @x and @y are relative to the current page.
+ *
+ * @returns {[Clutter.Actor, DragLocation]} the item and drag location
+ * under (@x, @y)
+ */
+ getDropTarget(x, y) {
+ const childSize = this._getChildrenMaxSize();
+ const [leftEmptySpace, topEmptySpace, hSpacing, vSpacing] =
+ this._calculateSpacing(childSize);
+
+ const isRtl =
+ Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
+
+ let page = this._orientation === Clutter.Orientation.VERTICAL
+ ? Math.floor(y / this._pageHeight)
+ : Math.floor(x / this._pageWidth);
+
+ if (isRtl && this._orientation === Clutter.Orientation.HORIZONTAL)
+ page = swap(page, this._pages.length);
+
+ // Page-relative coordinates from now on
+ x %= this._pageWidth;
+ y %= this._pageHeight;
+
+ if (x < leftEmptySpace || y < topEmptySpace)
+ return [null, DragLocation.INVALID];
+
+ const gridWidth =
+ childSize * this._columnsPerPage +
+ hSpacing * (this._columnsPerPage - 1);
+ const gridHeight =
+ childSize * this._rowsPerPage +
+ vSpacing * (this._rowsPerPage - 1);
+
+ if (x > leftEmptySpace + gridWidth || y > topEmptySpace + gridHeight)
+ return [null, DragLocation.INVALID];
+
+ const halfHSpacing = hSpacing / 2;
+ const halfVSpacing = vSpacing / 2;
+ const visibleItems = this._getVisibleChildrenForPage(page);
+
+ for (let itemIndex in visibleItems) {
+ const item = visibleItems[itemIndex];
+ const childBox = item.get_allocation_box().copy();
+
+ // Page offset
+ switch (this._orientation) {
+ case Clutter.Orientation.HORIZONTAL:
+ childBox.set_origin(childBox.x1 - page * this._pageWidth, childBox.y1);
+ break;
+ case Clutter.Orientation.VERTICAL:
+ childBox.set_origin(childBox.x1, childBox.y1 - page * this._pageHeight);
+ break;
+ }
+
+ // Outside the icon boundaries
+ if (x < childBox.x1 - halfHSpacing ||
+ x > childBox.x2 + halfHSpacing ||
+ y < childBox.y1 - halfVSpacing ||
+ y > childBox.y2 + halfVSpacing)
+ continue;
+
+ let dragLocation;
+
+ if (x < childBox.x1 + LEFT_DIVIDER_LEEWAY)
+ dragLocation = DragLocation.START_EDGE;
+ else if (x > childBox.x2 - RIGHT_DIVIDER_LEEWAY)
+ dragLocation = DragLocation.END_EDGE;
+ else
+ dragLocation = DragLocation.ON_ICON;
+
+ if (isRtl) {
+ if (dragLocation === DragLocation.START_EDGE)
+ dragLocation = DragLocation.END_EDGE;
+ else if (dragLocation === DragLocation.END_EDGE)
+ dragLocation = DragLocation.START_EDGE;
+ }
+
+ return [item, dragLocation];
+ }
+
+ return [null, DragLocation.EMPTY_SPACE];
+ }
+
// eslint-disable-next-line camelcase
get allow_incomplete_pages() {
return this._allowIncompletePages;
@@ -1525,6 +1635,63 @@ var IconGrid = GObject.registerClass({
});
}
+ nudgeItem(item, dragLocation) {
+ if (dragLocation === DragLocation.INVALID ||
+ dragLocation === DragLocation.EMPTY_AREA ||
+ dragLocation === DragLocation.ON_ICON)
+ return;
+
+ const layoutManager = this.layout_manager;
+ const columnsPerPage = layoutManager.columns_per_page;
+ const itemPage = layoutManager.getItemPage(item);
+ const children = layoutManager.getChildrenAtPage(itemPage).filter(c => c.visible);
+ const nudgeIndex = children.indexOf(item);
+ const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
+
+ if (dragLocation === DragLocation.START_EDGE) {
+ const offset = rtl
+ ? Math.floor(item.width * NUDGE_FACTOR)
+ : Math.floor(-item.width * NUDGE_FACTOR);
+
+ const leftItem = children[nudgeIndex - 1];
+ if (nudgeIndex % columnsPerPage > 0)
+ this._animateNudge(leftItem, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, offset);
+
+ this._animateNudge(item, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, -offset);
+ }
+
+ if (dragLocation === DragLocation.END_EDGE) {
+ const offset = rtl
+ ? Math.floor(-item.width * NUDGE_FACTOR)
+ : Math.floor(item.width * NUDGE_FACTOR);
+
+ this._animateNudge(item, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, -offset);
+
+ const rightItem = children[nudgeIndex + 1];
+ if (nudgeIndex < children.length - 1 &&
+ nudgeIndex % columnsPerPage < columnsPerPage - 1)
+ this._animateNudge(rightItem, NUDGE_ANIMATION_TYPE, NUDGE_DURATION, offset);
+ }
+ }
+
+ removeNudges() {
+ const children = this.get_children().filter(c => c.visible);
+ for (let child of children)
+ this._animateNudge(child, NUDGE_RETURN_ANIMATION_TYPE, NUDGE_RETURN_DURATION, 0);
+ }
+
+ _animateNudge(item, animationType, duration, offset) {
+ item.ease({
+ translation_x: offset,
+ mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
+ });
+ }
+
+ getDropTarget(x, y) {
+ const layoutManager = this.layout_manager;
+ return layoutManager.getDropTarget(x, y, this._currentPage);
+ }
+
get appsPerPage() {
const layoutManager = this.layout_manager;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]