From 55eb949defc285ea7651ee0fb773cd287c5c06b2 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 5 Jul 2019 12:32:51 -0300 Subject: [PATCH] baseViewIcon: Introduce base class for view icons Right now, only AppIcon supports being dragged. In the future, however, both app and folder icons will be reorderable, and to avoid copying the same code between FolderIcon and AppIcon, add a new base class BaseViewIcon that contains the shared code between them. Adding this new base class also has the side effect that it already allows for folder icons to be dragged, although full support for that will come in next commits. Because the Dash icons are not drop targets themselves, add a tiny DashIcon class, which is an AppDisplay.AppIcon subclass, and disable all DND drop code from it. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/603 --- js/ui/appDisplay.js | 443 ++++++++++++++++++++++++++------------------ js/ui/dash.js | 30 ++- 2 files changed, 292 insertions(+), 181 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index d89a41593..6a722498b 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -886,7 +886,8 @@ var AllView = class AllView extends BaseAppView { if (index == -1) return false; - if (source.view instanceof FolderView) { + if ((source instanceof AppIcon) && + (source.view instanceof FolderView)) { source.view.removeApp(source.app); source = this._items[source.id]; @@ -1462,37 +1463,243 @@ var FolderView = class FolderView extends BaseAppView { } }; -var FolderIcon = class FolderIcon { +var BaseViewIcon = class BaseViewIcon { + constructor(params, buttonParams) { + buttonParams = Params.parse(buttonParams, { + pivot_point: new Clutter.Point({x: 0.5, y: 0.5}), + reactive: true, + can_focus: true, + x_fill: true, + y_fill: true + }, true); + + this.actor = new St.Button(buttonParams); + this.actor._delegate = this; + + // Get the isDraggable property without passing it on to the BaseIcon: + params = Params.parse(params, { + isDraggable: true, + hideWhileDragging: false + }, true); + let isDraggable = params['isDraggable']; + delete params['isDraggable']; + + this._hasDndHover = false; + + if (isDraggable) { + this._draggable = DND.makeDraggable(this.actor); + this._draggable.connect('drag-begin', () => { + this._dragging = true; + this.scaleAndFade(); + Main.overview.beginItemDrag(this); + }); + this._draggable.connect('drag-cancelled', () => { + this._dragging = false; + Main.overview.cancelledItemDrag(this); + }); + this._draggable.connect('drag-end', () => { + this._dragging = false; + this.undoScaleAndFade(); + Main.overview.endItemDrag(this); + }); + } + + Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this)); + Main.overview.connect('item-drag-end', this._onDragEnd.bind(this)); + + this.actor.connect('destroy', this._onDestroy.bind(this)); + } + + _onDestroy() { + if (this._draggable && this._dragging) { + Main.overview.endItemDrag(this); + this.draggable = null; + } + } + + _createIcon(iconSize) { + throw new GObject.NotImplementedError(`_createIcon in ${this.constructor.name}`); + } + + _canDropAt(source) { + return false; + } + + // Should be overriden by subclasses + _setHoveringByDnd(isHovering) { + if (isHovering) + this.actor.add_style_pseudo_class('drop'); + else + this.actor.remove_style_pseudo_class('drop'); + } + + _onDragBegin() { + this._dragMonitor = { + dragMotion: this._onDragMotion.bind(this), + }; + DND.addDragMonitor(this._dragMonitor); + } + + _onDragMotion(dragEvent) { + let target = dragEvent.targetActor; + let hoveringActor = target == this.actor || this.actor.contains(target); + let canDrop = this._canDropAt(dragEvent.source); + let hasDndHover = hoveringActor && canDrop; + + if (this._hasDndHover != hasDndHover) { + this._setHoveringByDnd(hasDndHover); + this._hasDndHover = hasDndHover; + } + + return DND.DragMotionResult.CONTINUE; + } + + _onDragEnd() { + this.actor.remove_style_pseudo_class('drop'); + DND.removeDragMonitor(this._dragMonitor); + } + + handleDragOver(source, actor, x, y, time) { + if (source == this) + return DND.DragMotionResult.NO_DROP; + + if (!this._canDropAt(source)) + return DND.DragMotionResult.CONTINUE; + + return DND.DragMotionResult.MOVE_DROP; + } + + acceptDrop(source, actor, x, y, time) { + source.undoScaleAndFade(); + + this._setHoveringByDnd(false); + + if (!this._canDropAt(source)) + return false; + + return true; + } + + getDragActor() { + let iconParams = { + createIcon: this._createIcon.bind(this), + showLabel: (this._icon.label != null), + setSizeManually: true + }; + + let icon = new IconGrid.BaseIcon(this.name, iconParams); + icon.setIconSize(this.icon.iconSize); + + let bin = new St.Bin({ style_class: this.actor.style_class }); + bin.set_child(icon); + + return bin; + } + + getDragActorSource() { + return this._icon.icon; + } + + _scaleIn() { + this.actor.scale_x = 0; + this.actor.scale_y = 0; + this.actor.pivot_point = new Clutter.Point({ x: 0.5, y: 0.5 }); + + Tweener.addTween(this.actor, { + scale_x: 1, + scale_y: 1, + time: APP_ICON_SCALE_IN_TIME, + delay: APP_ICON_SCALE_IN_DELAY, + transition: (t, b, c, d) => { + // Similar to easeOutElastic, but less aggressive. + t /= d; + let p = 0.5; + return b + c * (Math.pow(2, -11 * t) * Math.sin(2 * Math.PI * (t - p / 4) / p) + 1); + } + }); + } + + _unscheduleScaleIn() { + if (this._scaleInId != 0) { + this.actor.disconnect(this._scaleInId); + this._scaleInId = 0; + } + } + + scheduleScaleIn() { + if (this._scaleInId != 0) + return; + + if (this.actor.mapped) { + this._scaleIn(); + } else { + this._scaleInId = this.actor.connect('notify::mapped', () => { + this._unscheduleScaleIn(); + this._scaleIn(); + }) + } + } + + scaleAndFade() { + this.actor.save_easing_state(); + this.actor.reactive = false; + this.actor.scale_x = 0.75; + this.actor.scale_y = 0.75; + this.actor.opacity = 128; + this.actor.restore_easing_state(); + } + + undoScaleAndFade() { + this.actor.save_easing_state(); + this.actor.reactive = true; + this.actor.scale_x = 1.0; + this.actor.scale_y = 1.0; + this.actor.opacity = 255; + this.actor.restore_easing_state(); + } + + get icon() { + return this._icon; + } + + get id() { + return this._id; + } + + get name() { + return this._name; + } + + get view() { + return this._view; + } +} + +var FolderIcon = class FolderIcon extends BaseViewIcon { constructor(id, path, parentView) { - this.id = id; - this.name = ''; - this._parentView = parentView; + super({ hideWhileDragging: true }, { + style_class: 'app-well-app app-folder', + toggle_mode: true + }); + + this._id = id; + this._name = ''; + this._view = parentView; this._folder = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders.folder', path: path }); - this.actor = new St.Button({ style_class: 'app-well-app app-folder', - button_mask: St.ButtonMask.ONE, - toggle_mode: true, - can_focus: true, - x_fill: true, - y_fill: true }); - this.actor._delegate = this; + // whether we need to update arrow side, position etc. this._popupInvalidated = false; - this.icon = new IconGrid.BaseIcon('', { + this._icon = new IconGrid.BaseIcon('', { createIcon: this._createIcon.bind(this), setSizeManually: true }); this.actor.set_child(this.icon); this.actor.label_actor = this.icon.label; - this.view = new FolderView(this._folder, parentView); - - Main.overview.connect('item-drag-begin', - this._onDragBegin.bind(this)); - Main.overview.connect('item-drag-end', - this._onDragEnd.bind(this)); + this._folderView = new FolderView(this._folder, parentView); this.actor.connect('clicked', this.open.bind(this)); this.actor.connect('destroy', this.onDestroy.bind(this)); @@ -1506,10 +1713,10 @@ var FolderIcon = class FolderIcon { } onDestroy() { - this.view.actor.destroy(); + this._folderView.actor.destroy(); if (this._spaceReadySignalId) { - this._parentView.disconnect(this._spaceReadySignalId); + this.view.disconnect(this._spaceReadySignalId); this._spaceReadySignalId = 0; } @@ -1519,38 +1726,22 @@ var FolderIcon = class FolderIcon { open() { this._ensurePopup(); - this.view.actor.vscroll.adjustment.value = 0; + this._folderView.actor.vscroll.adjustment.value = 0; this._openSpaceForPopup(); } getAppIds() { - return this.view.getAllItems().map(item => item.id); + return this._folderView.getAllItems().map(item => item.id); } _onDragBegin() { - this._dragMonitor = { - dragMotion: this._onDragMotion.bind(this), - }; - DND.addDragMonitor(this._dragMonitor); - - this._parentView.inhibitEventBlocker(); - } - - _onDragMotion(dragEvent) { - let target = dragEvent.targetActor; - - if (!this.actor.contains(target) || !this._canDropAt(dragEvent.source)) - this.actor.remove_style_pseudo_class('drop'); - else - this.actor.add_style_pseudo_class('drop'); - - return DND.DragMotionResult.CONTINUE; + super._onDragBegin(); + this.view.inhibitEventBlocker(); } _onDragEnd() { - this.actor.remove_style_pseudo_class('drop'); - this._parentView.uninhibitEventBlocker(); - DND.removeDragMonitor(this._dragMonitor); + super._onDragEnd(); + this.view.uninhibitEventBlocker(); } _canDropAt(source) { @@ -1590,57 +1781,57 @@ var FolderIcon = class FolderIcon { _updateName() { let name = _getFolderName(this._folder); - if (this.name == name) + if (this._name == name) return; - this.name = name; - this.icon.label.text = this.name; + this._name = name; + this.icon.label.text = name; this.emit('name-changed'); } _redisplay() { this._updateName(); - this.actor.visible = this.view.getAllItems().length > 0; + this.actor.visible = this._folderView.getAllItems().length > 0; this.icon.update(); this.emit('apps-changed'); } _createIcon(iconSize) { - return this.view.createFolderIcon(iconSize, this); + return this._folderView.createFolderIcon(iconSize, this); } _popupHeight() { - let usedHeight = this.view.usedHeight() + this._popup.getOffset(St.Side.TOP) + this._popup.getOffset(St.Side.BOTTOM); + let usedHeight = this._folderView.usedHeight() + this._popup.getOffset(St.Side.TOP) + this._popup.getOffset(St.Side.BOTTOM); return usedHeight; } _openSpaceForPopup() { - this._spaceReadySignalId = this._parentView.connect('space-ready', () => { - this._parentView.disconnect(this._spaceReadySignalId); + this._spaceReadySignalId = this.view.connect('space-ready', () => { + this.view.disconnect(this._spaceReadySignalId); this._spaceReadySignalId = 0; this._popup.popup(); this._updatePopupPosition(); }); - this._parentView.openSpaceForPopup(this, this._boxPointerArrowside, this.view.nRowsDisplayedAtOnce()); + this.view.openSpaceForPopup(this, this._boxPointerArrowside, this._folderView.nRowsDisplayedAtOnce()); } _calculateBoxPointerArrowSide() { - let spaceTop = this.actor.y - this._parentView.getCurrentPageY(); - let spaceBottom = this._parentView.actor.height - (spaceTop + this.actor.height); + let spaceTop = this.actor.y - this.view.getCurrentPageY(); + let spaceBottom = this.view.actor.height - (spaceTop + this.actor.height); return spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP; } _updatePopupSize() { // StWidget delays style calculation until needed, make sure we use the correct values - this.view._grid.ensure_style(); + this._folderView._grid.ensure_style(); let offsetForEachSide = Math.ceil((this._popup.getOffset(St.Side.TOP) + this._popup.getOffset(St.Side.BOTTOM) - this._popup.getCloseButtonOverlap()) / 2); // Add extra padding to prevent boxpointer decorations and close button being cut off - this.view.setPaddingOffsets(offsetForEachSide); - this.view.adaptToSize(this._parentAvailableWidth, this._parentAvailableHeight); + this._folderView.setPaddingOffsets(offsetForEachSide); + this._folderView.adaptToSize(this._parentAvailableWidth, this._parentAvailableHeight); } _updatePopupPosition() { @@ -1659,7 +1850,7 @@ var FolderIcon = class FolderIcon { this._boxPointerArrowside = this._calculateBoxPointerArrowSide(); if (!this._popup) { this._popup = new AppFolderPopup(this, this._boxPointerArrowside); - this._parentView.addFolderPopup(this._popup); + this.view.addFolderPopup(this._popup); this._popup.connect('open-state-changed', (popup, isOpen) => { if (!isOpen) this.actor.checked = false; @@ -1676,7 +1867,7 @@ var FolderIcon = class FolderIcon { this._parentAvailableWidth = width; this._parentAvailableHeight = height; if (this._popup) - this.view.adaptToSize(width, height); + this._folderView.adaptToSize(width, height); this._popupInvalidated = true; } }; @@ -1685,7 +1876,7 @@ Signals.addSignalMethods(FolderIcon.prototype); var AppFolderPopup = class AppFolderPopup { constructor(source, side) { this._source = source; - this._view = source.view; + this._view = source._folderView; this._arrowSide = side; this._isOpen = false; @@ -1847,20 +2038,16 @@ var AppFolderPopup = class AppFolderPopup { }; Signals.addSignalMethods(AppFolderPopup.prototype); -var AppIcon = class AppIcon { - constructor(app, view, iconParams = {}) { +var AppIcon = class AppIcon extends BaseViewIcon { + constructor(app, parentView, iconParams = {}) { + super(iconParams, { + button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO, + style_class: 'app-well-app' + }); this.app = app; - this.id = app.get_id(); - this.name = app.get_name(); - this._view = view; - - this.actor = new St.Button({ style_class: 'app-well-app', - pivot_point: new Clutter.Point({x: 0.5, y: 0.5}), - reactive: true, - button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO, - can_focus: true, - x_fill: true, - y_fill: true }); + this._id = app.get_id(); + this._name = app.get_name(); + this._view = parentView; this._dot = new St.Widget({ style_class: 'app-well-app-running-dot', layout_manager: new Clutter.BinLayout(), @@ -1877,14 +2064,11 @@ var AppIcon = class AppIcon { this.actor._delegate = this; this._scaleInId = 0; - // Get the isDraggable property without passing it on to the BaseIcon: - let appIconParams = Params.parse(iconParams, { isDraggable: true }, true); - let isDraggable = appIconParams['isDraggable']; delete iconParams['isDraggable']; iconParams['createIcon'] = this._createIcon.bind(this); iconParams['setSizeManually'] = true; - this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams); + this._icon = new IconGrid.BaseIcon(app.get_name(), iconParams); this._iconContainer.add_child(this.icon); this.actor.label_actor = this.icon.label; @@ -1898,26 +2082,8 @@ var AppIcon = class AppIcon { this._menu = null; this._menuManager = new PopupMenu.PopupMenuManager(this.actor); - if (isDraggable) { - this._draggable = DND.makeDraggable(this.actor); - this._draggable.connect('drag-begin', () => { - this._dragging = true; - this.scaleAndFade(); - this._removeMenuTimeout(); - Main.overview.beginItemDrag(this); - }); - this._draggable.connect('drag-cancelled', () => { - this._dragging = false; - Main.overview.cancelledItemDrag(this); - }); - this._draggable.connect('drag-end', () => { - this._dragging = false; - this.undoScaleAndFade(); - Main.overview.endItemDrag(this); - }); - } - - this.actor.connect('destroy', this._onDestroy.bind(this)); + if (this._draggable) + this._draggable.connect('drag-begin', this._removeMenuTimeout.bind(this)); this._menuTimeoutId = 0; this._stateChangedId = this.app.connect('notify::state', () => { @@ -1927,12 +2093,10 @@ var AppIcon = class AppIcon { } _onDestroy() { + super._onDestroy(); + if (this._stateChangedId > 0) this.app.disconnect(this._stateChangedId); - if (this._draggable && this._dragging) { - Main.overview.endItemDrag(this); - this.draggable = null; - } this._stateChangedId = 0; this._removeMenuTimeout(); } @@ -2075,46 +2239,6 @@ var AppIcon = class AppIcon { this.icon.animateZoomOut(); } - _scaleIn() { - this.actor.scale_x = 0; - this.actor.scale_y = 0; - this.actor.pivot_point = new Clutter.Point({ x: 0.5, y: 0.5 }); - - Tweener.addTween(this.actor, { - scale_x: 1, - scale_y: 1, - time: APP_ICON_SCALE_IN_TIME, - delay: APP_ICON_SCALE_IN_DELAY, - transition: (t, b, c, d) => { - // Similar to easeOutElastic, but less aggressive. - t /= d; - let p = 0.5; - return b + c * (Math.pow(2, -11 * t) * Math.sin(2 * Math.PI * (t - p / 4) / p) + 1); - } - }); - } - - _unscheduleScaleIn() { - if (this._scaleInId != 0) { - this.actor.disconnect(this._scaleInId); - this._scaleInId = 0; - } - } - - scheduleScaleIn() { - if (this._scaleInId != 0) - return; - - if (this.actor.mapped) { - this._scaleIn(); - } else { - this._scaleInId = this.actor.connect('notify::mapped', () => { - this._unscheduleScaleIn(); - this._scaleIn(); - }) - } - } - shellWorkspaceLaunch(params) { params = Params.parse(params, { workspace: -1, timestamp: 0 }); @@ -2122,46 +2246,9 @@ var AppIcon = class AppIcon { this.app.open_new_window(params.workspace); } - getDragActor() { - let iconParams = { createIcon: this._createIcon.bind(this), - showLabel: (this.icon.label != null), - setSizeManually: true }; - let icon = new IconGrid.BaseIcon(this.name, iconParams); - icon.setIconSize(this.icon.iconSize); - return icon; - } - - // Returns the original actor that should align with the actor - // we show as the item is being dragged. - getDragActorSource() { - return this.icon.icon; - } - shouldShowTooltip() { return this.actor.hover && (!this._menu || !this._menu.isOpen); } - - scaleAndFade() { - this.actor.save_easing_state(); - this.actor.reactive = false; - this.actor.scale_x = 0.75; - this.actor.scale_y = 0.75; - this.actor.opacity = 128; - this.actor.restore_easing_state(); - } - - undoScaleAndFade() { - this.actor.save_easing_state(); - this.actor.reactive = true; - this.actor.scale_x = 1.0; - this.actor.scale_y = 1.0; - this.actor.opacity = 255; - this.actor.restore_easing_state(); - } - - get view() { - return this._view; - } }; Signals.addSignalMethods(AppIcon.prototype); diff --git a/js/ui/dash.js b/js/ui/dash.js index c22cb3e8b..136a9837e 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -25,6 +25,32 @@ function getAppFromSource(source) { } } +var DashIcon = class DashIcon extends AppDisplay.AppIcon { + constructor(app) { + super(app, null, { + setSizeManually: true, + showLabel: false + }); + + + } + + // Disable all DnD methods + _onDragBegin() { + } + + _onDragEnd() { + } + + handleDragOver() { + return DND.DragMotionResult.CONTINUE; + } + + acceptDrop() { + return false; + } +} + // A container like StBin, but taking the child's scale into account // when requesting a size var DashItemContainer = GObject.registerClass( @@ -475,9 +501,7 @@ var Dash = class Dash { } _createAppItem(app) { - let appIcon = new AppDisplay.AppIcon(app, null, - { setSizeManually: true, - showLabel: false }); + let appIcon = new DashIcon(app); appIcon.connect('menu-state-changed', (appIcon, opened) => {