[gnome-shell/gbsneto/custom-icon-positions: 22/40] 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: 22/40] iconGrid: Add item nudge API
- Date: Wed, 27 May 2020 22:42:57 +0000 (UTC)
commit a856bfcfe54fdf59867de270f36b2c216f82e86e
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 20cbd480b7..b004493710 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 4347cafc64..f0da69b508 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 itemsPerPage() {
const layoutManager = this.layout_manager;
return layoutManager.rows_per_page * layoutManager.columns_per_page;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]