From edfa50f254170d816bcb784113f6222eab4de19e Mon Sep 17 00:00:00 2001 From: Julian Schacher Date: Sun, 4 Jul 2021 05:16:32 +0200 Subject: [PATCH] Update: Scroll preferences window content on Drag-and-Drop Scroll the content of the preferences window on Drag-and-Drop, when the mouse is in the upper or lower part of the content of the preferences window. This helps a user, who has e.g. a lot of items, but the preferences window with a low height, to easily move any item to any position. --- src/prefs.js | 24 +++++- src/prefsModules/PrefsBoxOrderItemRow.js | 6 +- src/prefsModules/ScrollManager.js | 99 ++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/prefsModules/ScrollManager.js diff --git a/src/prefs.js b/src/prefs.js index b3016ba..187a6f1 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -27,6 +27,7 @@ const Me = ExtensionUtils.getCurrentExtension(); const PrefsBoxOrderListEmptyPlaceholder = Me.imports.prefsModules.PrefsBoxOrderListEmptyPlaceholder; const PrefsBoxOrderItemRow = Me.imports.prefsModules.PrefsBoxOrderItemRow; +const ScrollManager = Me.imports.prefsModules.ScrollManager; var PrefsWidget = GObject.registerClass({ GTypeName: "PrefsWidget", @@ -63,12 +64,33 @@ var PrefsWidget = GObject.registerClass({ window.default_height = 750; }); + // Scroll up or down, when a Drag-and-Drop operation is in progress and + // the user has their cursor either in the upper or lower 10% of this + // widget respectively. + this._scrollManager = new ScrollManager.ScrollManager(this); + let controller = new Gtk.DropControllerMotion(); + controller.connect("motion", (_, x, y) => { + // If the pointer is currently in the upper ten percent of this + // widget, then scroll up. + if (y <= this.get_allocated_height() * 0.1) this._scrollManager.startScrollUp(); + // If the pointer is currently in the lower ten percent of this + // widget, then scroll down. + else if (y >= this.get_allocated_height() * 0.9) this._scrollManager.startScrollDown(); + // Otherwise stop scrolling. + else this._scrollManager.stopScrollAll(); + }); + controller.connect("leave", () => { + // Stop scrolling on leave. + this._scrollManager.stopScrollAll(); + }); + this.add_controller(controller); + // Initialize the given `gtkListBox`. const initializeGtkListBox = (boxOrder, gtkListBox) => { // Add the items of the given configured box order as // GtkListBoxRows. for (const item of boxOrder) { - const listBoxRow = new PrefsBoxOrderItemRow.PrefsBoxOrderItemRow(); + const listBoxRow = new PrefsBoxOrderItemRow.PrefsBoxOrderItemRow({}, this._scrollManager); listBoxRow.item = item; if (item.startsWith("appindicator-kstatusnotifieritem-")) { diff --git a/src/prefsModules/PrefsBoxOrderItemRow.js b/src/prefsModules/PrefsBoxOrderItemRow.js index 9914f79..3ca63ae 100644 --- a/src/prefsModules/PrefsBoxOrderItemRow.js +++ b/src/prefsModules/PrefsBoxOrderItemRow.js @@ -31,7 +31,7 @@ var PrefsBoxOrderItemRow = GObject.registerClass({ Template: Me.dir.get_child("prefs-box-order-item-row.ui").get_uri(), Children: ["item-name-display-label"] }, class PrefsBoxOrderItemRow extends Gtk.ListBoxRow { - _init(params = {}) { + _init(params = {}, scrollManager) { super._init(params); // Make `this` draggable by creating a drag source and adding it to @@ -41,6 +41,10 @@ var PrefsBoxOrderItemRow = GObject.registerClass({ dragSource.connect("prepare", () => { return Gdk.ContentProvider.new_for_value(this); }); + // Stop all scrolling, which is due to this DND operation. + dragSource.connect("drag-end", () => { + scrollManager.stopScrollAll(); + }); this.add_controller(dragSource); /// Make `this` accept drops by creating a drop target and adding it to diff --git a/src/prefsModules/ScrollManager.js b/src/prefsModules/ScrollManager.js new file mode 100644 index 0000000..322d4f0 --- /dev/null +++ b/src/prefsModules/ScrollManager.js @@ -0,0 +1,99 @@ +"use strict"; +/* + * This file is part of Top-Bar-Organizer (a Gnome Shell Extension for + * organizing your Gnome Shell top bar). + * Copyright (C) 2021 Julian Schacher + * + * Top-Bar-Organizer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/* exported ScrollManager */ +const GLib = imports.gi.GLib; + +var ScrollManager = class ScrollManager { + /** + * @param {Gtk.ScrolledWindow} gtkScrolledWindow + */ + constructor(gtkScrolledWindow) { + this._gtkScrolledWindow = gtkScrolledWindow; + + this._scrollUp = false; + this._scrollDown = false; + } + + startScrollUp() { + // If the scroll up is already started, don't do anything. + if (this._scrollUp) return; + + // Make sure scroll down is stopped. + this.stopScrollDown(); + + this._scrollUp = true; + + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => { + // Set the new vadjustment value to either the current value minus a + // step increment or to 0. + const newVAdjustementValue = Math.max(this._gtkScrolledWindow.vadjustment.get_value() - this._gtkScrolledWindow.vadjustment.get_step_increment(), 0); + + // If the new value is the old one, return and stop this interval. + if (newVAdjustementValue === this._gtkScrolledWindow.vadjustment.get_value()) { + this._scrollUp = false; + return this._scrollUp; + } + // Otherwise, update the value. + this._gtkScrolledWindow.vadjustment.set_value(newVAdjustementValue); + return this._scrollUp; + }); + } + + startScrollDown() { + // If the scroll down is already started, don't do anything. + if (this._scrollDown) return; + + // Make sure scroll up is stopped. + this.stopScrollUp(); + + this._scrollDown = true; + + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => { + // Set the new vadjusment value either to the curent value plus a + // step increment or to the upper value minus the page size. + const newVAdjustementValue = Math.min( + this._gtkScrolledWindow.vadjustment.get_value() + this._gtkScrolledWindow.vadjustment.get_step_increment(), + this._gtkScrolledWindow.vadjustment.get_upper() - this._gtkScrolledWindow.vadjustment.get_page_size() + ); + + // If the new value is the old one, return and stop this interval. + if (newVAdjustementValue === this._gtkScrolledWindow.vadjustment.get_value()) { + this._scrollDown = false; + return this._scrollDown; + } + // Otherwise, update the value. + this._gtkScrolledWindow.vadjustment.set_value(newVAdjustementValue); + return this._scrollDown; + }); + } + + stopScrollUp() { + this._scrollUp = false; + } + + stopScrollDown() { + this._scrollDown = false; + } + + stopScrollAll() { + this.stopScrollUp(); + this.stopScrollDown(); + } +};