diff --git a/Settings.ui b/Settings.ui index eeba2be..ca7bb30 100644 --- a/Settings.ui +++ b/Settings.ui @@ -2,11 +2,315 @@ + + True + False + vertical + + + True + False + none + + + True + True + False + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + False + True + Animation type + 0 + + + False + True + 0 + + + + + True + False + center + + Simple + Ripple + Plank + + + + False + False + end + 1 + + + + + + + + + True + True + False + + + True + False + 12 + 12 + 12 + 12 + 24 + 32 + + + True + False + 12 + Duration + True + 0 + end + + + 0 + 0 + + + + + True + True + end + True + animate_appicon_hover_options_duration_adjustment + 0 + 0 + right + + + + 1 + 0 + + + + + True + False + Rotation + 0 + end + + + 0 + 1 + + + + + True + True + end + True + animate_appicon_hover_options_rotation_adjustment + 0 + 0 + right + + + + 1 + 1 + + + + + True + False + Travel + 0 + end + + + 0 + 2 + + + + + True + True + end + True + animate_appicon_hover_options_travel_adjustment + 0 + 0 + right + + + + 1 + 2 + + + + + True + False + Zoom + 0 + end + + + 0 + 3 + + + + + True + True + end + True + animate_appicon_hover_options_zoom_adjustment + 0 + 0 + right + + + + 1 + 3 + + + + + True + False + Convexity + 0 + end + + + 0 + 4 + + + + + True + True + end + True + animate_appicon_hover_options_convexity_adjustment + 1 + 1 + right + + + + 1 + 4 + + + + + True + False + Extent + 0 + end + + + 0 + 5 + + + + + True + True + end + True + animate_appicon_hover_options_extent_adjustment + 0 + 0 + right + + + + 1 + 5 + + + + + + + + + False + True + 0 + + + 100 1 10 + + 0 + 300 + 1 + 5 + + + -30 + 30 + 1 + 5 + + + 0 + 100 + 1 + 5 + + + 100 + 250 + 1 + 5 + + + 0 + 3 + 0.1 + 1 + + + 1 + 10 + 0.1 + 1 + 0.33 1 @@ -7283,6 +7587,66 @@ 1 + + + True + False + True + Animate hovering app icons + 0 + + + 0 + 2 + + + + + True + False + 6 + + + True + True + True + center + + + True + False + emblem-system-symbolic + + + + + + False + True + 0 + + + + + True + True + end + center + + + False + True + 1 + + + + + 1 + 2 + + diff --git a/appIcons.js b/appIcons.js index 9ea5ce8..5cfb135 100644 --- a/appIcons.js +++ b/appIcons.js @@ -194,6 +194,7 @@ var taskbarAppIcon = Utils.defineClass({ this._stateChangedId = 0; } + this._onAnimateAppiconHoverChanged(); this._setAppIconPadding(); this._showDots(); @@ -230,6 +231,7 @@ var taskbarAppIcon = Utils.defineClass({ this._hoverChangeId = this.actor.connect('notify::hover', () => this._onAppIconHoverChanged()); this._dtpSettingsSignalIds = [ + Me.settings.connect('changed::animate-appicon-hover', Lang.bind(this, this._onAnimateAppiconHoverChanged)), Me.settings.connect('changed::dot-position', Lang.bind(this, this._settingsChangeRefresh)), Me.settings.connect('changed::dot-size', Lang.bind(this, this._settingsChangeRefresh)), Me.settings.connect('changed::dot-style-focused', Lang.bind(this, this._settingsChangeRefresh)), @@ -269,6 +271,29 @@ var taskbarAppIcon = Utils.defineClass({ return this.app.create_icon_texture(this.dtpPanel.taskbar.iconSize); }, + // Used by TaskbarItemContainer to animate appIcons on hover + getCloneButton: function() { + // The source of the clone is this._container, + // using this.actor directly would break DnD style. + let clone = new Clutter.Clone({ + source: this.actor.child, + x: this.actor.child.x, y: this.actor.child.y, + width: this.actor.child.width, height: this.actor.child.height, + pivot_point: new Utils.getPoint({ x: 0.5, y: 0.5 }), + opacity: 255, + reactive: false, + x_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER, + }); + + // "clone" of this.actor + return new St.Button({ + child: clone, + x: this.actor.x, y: this.actor.y, + width: this.actor.width, height: this.actor.height, + reactive: false, + }); + }, + shouldShowTooltip: function() { if (!Me.settings.get_boolean('show-tooltip') || (!this.isLauncher && Me.settings.get_boolean("show-window-previews") && @@ -378,6 +403,36 @@ var taskbarAppIcon = Utils.defineClass({ }); }, + _onAnimateAppiconHoverChanged: function() { + if (Me.settings.get_boolean('animate-appicon-hover')) { + this._container.add_style_class_name('animate-appicon-hover'); + + // Workaround to prevent scaled icon from being ugly when it is animated on hover. + // It increases the "resolution" of the icon without changing the icon size. + this.icon.createIcon = (iconSize) => this.app.create_icon_texture(2 * iconSize); + this._iconIconBinActorAddedId = this.icon._iconBin.connect('actor-added', () => { + if (this.icon._iconBin.child.mapped) { + this.icon._iconBin.child.set_size(this.icon.iconSize, this.icon.iconSize); + } else { + let iconMappedId = this.icon._iconBin.child.connect('notify::mapped', () => { + this.icon._iconBin.child.set_size(this.icon.iconSize, this.icon.iconSize); + this.icon._iconBin.child.disconnect(iconMappedId); + }); + } + }); + if (this.icon._iconBin.child) + this.icon._createIconTexture(this.icon.iconSize); + } else { + this._container.remove_style_class_name('animate-appicon-hover'); + + if (this._iconIconBinActorAddedId) { + this.icon._iconBin.disconnect(this._iconIconBinActorAddedId); + this._iconIconBinActorAddedId = 0; + this.icon.createIcon = Lang.bind(this, this._createIcon); + } + } + }, + _onMouseScroll: function(actor, event) { let scrollAction = Me.settings.get_string('scroll-icon-action'); @@ -1553,6 +1608,10 @@ function ItemShowLabel() { let position = this._dtpPanel.getPosition(); let labelOffset = node.get_length('-x-offset'); + // From TaskbarItemContainer + if (this._getIconAnimationOffset) + labelOffset += this._getIconAnimationOffset(); + let xOffset = Math.floor((itemWidth - labelWidth) / 2); let x = stageX + xOffset let y = stageY + (itemHeight - labelHeight) * .5; diff --git a/prefs.js b/prefs.js index 76b3149..2579fe2 100644 --- a/prefs.js +++ b/prefs.js @@ -1988,14 +1988,104 @@ const Settings = new Lang.Class({ } this._settings.bind('animate-app-switch', - this._builder.get_object('animate_app_switch_switch'), - 'active', - Gio.SettingsBindFlags.DEFAULT); + this._builder.get_object('animate_app_switch_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); this._settings.bind('animate-window-launch', - this._builder.get_object('animate_window_launch_switch'), - 'active', - Gio.SettingsBindFlags.DEFAULT); + this._builder.get_object('animate_window_launch_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('animate-appicon-hover', + this._builder.get_object('animate_appicon_hover_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('animate-appicon-hover', + this._builder.get_object('animate_appicon_hover_button'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + + { + this._settings.bind('animate-appicon-hover-animation-type', + this._builder.get_object('animate_appicon_hover_options_type_combo'), + 'active-id', + Gio.SettingsBindFlags.DEFAULT); + + let scales = [ + ['animate_appicon_hover_options_duration_scale', 'animate-appicon-hover-animation-duration', 1], + ['animate_appicon_hover_options_rotation_scale', 'animate-appicon-hover-animation-rotation', 1], + ['animate_appicon_hover_options_travel_scale', 'animate-appicon-hover-animation-travel', 100], + ['animate_appicon_hover_options_zoom_scale', 'animate-appicon-hover-animation-zoom', 100], + ['animate_appicon_hover_options_convexity_scale', 'animate-appicon-hover-animation-convexity', 1], + ['animate_appicon_hover_options_extent_scale', 'animate-appicon-hover-animation-extent', 1], + ]; + + let updateScale = scale => { + let [id, key, factor] = scale; + let type = this._settings.get_string('animate-appicon-hover-animation-type'); + let value = this._settings.get_value(key).deep_unpack()[type]; + let defaultValue = this._settings.get_default_value(key).deep_unpack()[type]; + this._builder.get_object(id).sensitive = defaultValue !== undefined; + this._builder.get_object(id).set_value(value * factor || 0); + this._builder.get_object(id).clear_marks(); + this._builder.get_object(id).add_mark(defaultValue * factor, Gtk.PositionType.TOP, + defaultValue !== undefined ? (defaultValue * factor).toString() : ' '); + }; + + scales.forEach(scale => { + let [id, key, factor] = scale; + this._settings.connect('changed::' + key, () => updateScale(scale)); + this._builder.get_object(id).connect('value-changed', widget => { + let type = this._settings.get_string('animate-appicon-hover-animation-type'); + let variant = this._settings.get_value(key); + let unpacked = variant.deep_unpack(); + if (unpacked[type] != widget.get_value() / factor) { + unpacked[type] = widget.get_value() / factor; + this._settings.set_value(key, new GLib.Variant(variant.get_type_string(), unpacked)); + } + }); + }); + + this._settings.connect('changed::animate-appicon-hover-animation-type', () => scales.forEach(updateScale)); + scales.forEach(updateScale); + } + + this._builder.get_object('animate_appicon_hover_button').connect('clicked', Lang.bind(this, function() { + let dialog = new Gtk.Dialog({ title: _('App icon animation options'), + transient_for: this.widget.get_toplevel(), + use_header_bar: true, + modal: true }); + + // GTK+ leaves positive values for application-defined response ids. + // Use +1 for the reset action + dialog.add_button(_('Reset to defaults'), 1); + + let box = this._builder.get_object('animate_appicon_hover_options'); + dialog.get_content_area().add(box); + + dialog.connect('response', Lang.bind(this, function(dialog, id) { + if (id == 1) { + // restore default settings + this._settings.set_value('animate-appicon-hover-animation-type', this._settings.get_default_value('animate-appicon-hover-animation-type')); + this._settings.set_value('animate-appicon-hover-animation-duration', this._settings.get_default_value('animate-appicon-hover-animation-duration')); + this._settings.set_value('animate-appicon-hover-animation-rotation', this._settings.get_default_value('animate-appicon-hover-animation-rotation')); + this._settings.set_value('animate-appicon-hover-animation-travel', this._settings.get_default_value('animate-appicon-hover-animation-travel')); + this._settings.set_value('animate-appicon-hover-animation-zoom', this._settings.get_default_value('animate-appicon-hover-animation-zoom')); + this._settings.set_value('animate-appicon-hover-animation-convexity', this._settings.get_default_value('animate-appicon-hover-animation-convexity')); + this._settings.set_value('animate-appicon-hover-animation-extent', this._settings.get_default_value('animate-appicon-hover-animation-extent')); + } else { + // remove the settings box so it doesn't get destroyed; + dialog.get_content_area().remove(box); + dialog.destroy(); + } + return; + })); + + dialog.show_all(); + + })); this._settings.bind('stockgs-keep-dash', this._builder.get_object('stockgs_dash_switch'), @@ -2124,7 +2214,30 @@ const Settings = new Lang.Class({ * Object containing all signals defined in the glade file */ _SignalHandler: { - + animate_appicon_hover_options_duration_scale_format_value_cb: function(scale, value) { + return _("%d ms").format(value); + }, + + animate_appicon_hover_options_rotation_scale_format_value_cb: function(scale, value) { + return _("%d °").format(value); + }, + + animate_appicon_hover_options_travel_scale_format_value_cb: function(scale, value) { + return _("%d %%").format(value); + }, + + animate_appicon_hover_options_zoom_scale_format_value_cb: function(scale, value) { + return _("%d %%").format(value); + }, + + animate_appicon_hover_options_convexity_scale_format_value_cb: function(scale, value) { + return _("%.1f").format(value); + }, + + animate_appicon_hover_options_extent_scale_format_value_cb: function(scale, value) { + return Gettext.ngettext("%d icon", "%d icons", value).format(value); + }, + position_bottom_button_clicked_cb: function(button) { if (!this._ignorePositionRadios && button.get_active()) this._setPanelPosition(Pos.BOTTOM); }, diff --git a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml index 1c83b1b..6e269db 100644 --- a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml +++ b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml @@ -58,6 +58,11 @@ + + + + + false @@ -763,6 +768,38 @@ true Animate when new window launched + + false + Animate app icon on hover + + + 'SIMPLE' + App icon hover animation type + + + {'RIPPLE':2,'PLANK':1} + App icon hover animation curve convexity (1 is linear, more is convex, less is concave) + + + {'SIMPLE':160,'RIPPLE':130,'PLANK':100} + App icon hover animation duration in milliseconds + + + {'RIPPLE':4,'PLANK':4} + App icon hover animation extent (maximum number of animated icons) + + + {'SIMPLE':0,'RIPPLE':10,'PLANK':0} + App icon hover animation rotation in degrees + + + {'SIMPLE':0.30,'RIPPLE':0.40,'PLANK':0} + App icon hover animation travel translation in relation to the app icon size + + + {'SIMPLE':1,'RIPPLE':1.25,'PLANK':2} + App icon hover animation zoom scale in relation to the app icon size + true Integrate items from the gnome appmenu into the right click menu diff --git a/stylesheet.css b/stylesheet.css index 84f664a..6917e24 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -50,6 +50,10 @@ background-color: rgba(238, 238, 236, 0.1); } +#dashtopanelScrollview .app-well-app:hover .dtp-container.animate-appicon-hover { + background: none; +} + #dashtopanelScrollview .app-well-app:active .dtp-container { background-color: rgba(238, 238, 236, 0.18); } diff --git a/taskbar.js b/taskbar.js index 5626585..400a7b2 100644 --- a/taskbar.js +++ b/taskbar.js @@ -26,6 +26,7 @@ const Clutter = imports.gi.Clutter; const Config = imports.misc.config; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Signals = imports.signals; const Lang = imports.lang; @@ -48,6 +49,7 @@ const AppIcons = Me.imports.appIcons; const Panel = Me.imports.panel; const PanelManager = Me.imports.panelManager; const PanelSettings = Me.imports.panelSettings; +const Pos = Me.imports.panelPositions; const Utils = Me.imports.utils; const WindowPreview = Me.imports.windowPreview; @@ -68,6 +70,44 @@ function extendDashItemContainer(dashItemContainer) { dashItemContainer.showLabel = AppIcons.ItemShowLabel; }; +const iconAnimationSettings = { + _getDictValue: function(key) { + let type = Me.settings.get_string('animate-appicon-hover-animation-type'); + return Me.settings.get_value(key).deep_unpack()[type] || 0; + }, + + get type() { + if (!Me.settings.get_boolean('animate-appicon-hover')) + return ""; + + return Me.settings.get_string('animate-appicon-hover-animation-type'); + }, + + get convexity() { + return Math.max(0, this._getDictValue('animate-appicon-hover-animation-convexity')); + }, + + get duration() { + return this._getDictValue('animate-appicon-hover-animation-duration'); + }, + + get extent() { + return Math.max(1, this._getDictValue('animate-appicon-hover-animation-extent')); + }, + + get rotation() { + return this._getDictValue('animate-appicon-hover-animation-rotation'); + }, + + get travel() { + return Math.max(0, this._getDictValue('animate-appicon-hover-animation-travel')); + }, + + get zoom() { + return Math.max(1, this._getDictValue('animate-appicon-hover-animation-zoom')); + }, +}; + /* This class is a fork of the upstream DashActor class (ui.dash.js) * * Summary of changes: @@ -189,7 +229,9 @@ var taskbar = Utils.defineClass({ vscrollbar_policy: Gtk.PolicyType.NEVER, enable_mouse_scrolling: true }); - this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent )); + this._scrollView.connect('leave-event', Lang.bind(this, this._onLeaveEvent)); + this._scrollView.connect('motion-event', Lang.bind(this, this._onMotionEvent)); + this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); this._scrollView.add_actor(this._box); this._showAppsIconWrapper = panel.showAppsIconWrapper; @@ -370,6 +412,74 @@ var taskbar = Utils.defineClass({ this._disconnectWorkspaceSignals(); }, + _dropIconAnimations: function() { + this._getTaskbarIcons().forEach(item => { + item.raise(0); + item.stretch(0); + }); + }, + + _updateIconAnimations: function(pointerX, pointerY) { + this._iconAnimationTimestamp = Date.now(); + let type = iconAnimationSettings.type; + + if (!pointerX || !pointerY) + [pointerX, pointerY] = global.get_pointer(); + + this._getTaskbarIcons().forEach(item => { + let [x, y] = item.get_transformed_position(); + let [width, height] = item.get_transformed_size(); + let [centerX, centerY] = [x + width / 2, y + height / 2]; + let size = this._box.vertical ? height : width; + let difference = this._box.vertical ? pointerY - centerY : pointerX - centerX; + let distance = Math.abs(difference); + let maxDistance = (iconAnimationSettings.extent / 2) * size; + + if (type == 'PLANK') { + // Make the position stable for items that are far from the pointer. + let translation = distance <= maxDistance ? + distance / (2 + 8 * distance / maxDistance) : + // the previous expression with distance = maxDistance + maxDistance / 10; + + if (difference > 0) + translation *= -1; + + item.stretch(translation); + } + + if (distance <= maxDistance) { + let level = (maxDistance - distance) / maxDistance; + level = Math.pow(level, iconAnimationSettings.convexity); + item.raise(level); + } else { + item.raise(0); + } + }); + }, + + _onLeaveEvent: function(actor) { + let [stageX, stageY] = global.get_pointer(); + let [success, x, y] = actor.transform_stage_point(stageX, stageY); + if (success && !actor.allocation.contains(x, y) && (iconAnimationSettings.type == 'RIPPLE' || iconAnimationSettings.type == 'PLANK')) + this._dropIconAnimations(); + + return Clutter.EVENT_PROPAGATE; + }, + + _onMotionEvent: function(actor_, event) { + if (iconAnimationSettings.type == 'RIPPLE' || iconAnimationSettings.type == 'PLANK') { + let timestamp = Date.now(); + if (!this._iconAnimationTimestamp || + (timestamp - this._iconAnimationTimestamp >= iconAnimationSettings.duration / 2)) { + let [pointerX, pointerY] = event.get_coords(); + this._updateIconAnimations(pointerX, pointerY); + } + } + + return Clutter.EVENT_PROPAGATE; + }, + _onScrollEvent: function(actor, event) { let orientation = this.dtpPanel.getOrientation(); @@ -566,6 +676,7 @@ var taskbar = Utils.defineClass({ Lang.bind(this, function() { appIcon.actor.opacity = 50; appIcon.isDragged = 1; + this._dropIconAnimations(); })); appIcon._draggable.connect('drag-end', Lang.bind(this, function() { @@ -580,7 +691,7 @@ var taskbar = Utils.defineClass({ this._itemMenuStateChanged(item, opened); })); - let item = new Dash.DashItemContainer(); + let item = new TaskbarItemContainer(); item._dtpPanel = this.dtpPanel extendDashItemContainer(item); @@ -595,11 +706,19 @@ var taskbar = Utils.defineClass({ this._ensureAppIconVisibilityTimeoutId = 0; return GLib.SOURCE_REMOVE; })); + + if (!appIcon.isDragged && iconAnimationSettings.type == 'SIMPLE') + appIcon.actor.get_parent().raise(1); + else if (!appIcon.isDragged && (iconAnimationSettings.type == 'RIPPLE' || iconAnimationSettings.type == 'PLANK')) + this._updateIconAnimations(); } else { if (this._ensureAppIconVisibilityTimeoutId>0) { Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); this._ensureAppIconVisibilityTimeoutId = 0; } + + if (!appIcon.isDragged && iconAnimationSettings.type == 'SIMPLE') + appIcon.actor.get_parent().raise(0); } })); @@ -672,6 +791,14 @@ var taskbar = Utils.defineClass({ // add a custom signal to the appIcon, since gnome 3.8 the signal // calling this callback was added upstream. this.emit('menu-closed'); + + // The icon menu grabs the events and, once it is closed, the pointer is maybe + // no longer over the taskbar and the animations are not dropped. + if (iconAnimationSettings.type == 'RIPPLE' || iconAnimationSettings.type == 'PLANK') { + this._scrollView.sync_hover(); + if (!this._scrollView.hover) + this._dropIconAnimations(); + } } }, @@ -1230,6 +1357,187 @@ var taskbar = Utils.defineClass({ Signals.addSignalMethods(taskbar.prototype); +const CloneContainerConstraint = Utils.defineClass({ + Name: 'DashToPanel-CloneContainerConstraint', + Extends: Clutter.BindConstraint, + + vfunc_update_allocation: function(actor, actorBox) { + if (!this.source) + return; + + let [stageX, stageY] = this.source.get_transformed_position(); + let [width, height] = this.source.get_transformed_size(); + + actorBox.set_origin(stageX, stageY); + actorBox.set_size(width, height); + }, +}); + +const TaskbarItemContainer = Utils.defineClass({ + Name: 'DashToPanel-TaskbarItemContainer', + Extends: Dash.DashItemContainer, + + // In case appIcon is removed from the taskbar while it is hovered, + // restore opacity before dashItemContainer.animateOutAndDestroy does the destroy animation. + animateOutAndDestroy: function() { + if (this._raisedClone) { + this._raisedClone.source.opacity = 255; + this._raisedClone.destroy(); + } + + this.callParent('animateOutAndDestroy'); + }, + + // For ItemShowLabel + _getIconAnimationOffset: function() { + if (!Me.settings.get_boolean('animate-appicon-hover')) + return 0; + + let travel = iconAnimationSettings.travel; + let zoom = iconAnimationSettings.zoom; + return this._dtpPanel.dtpSize * (travel + (zoom - 1) / 2); + }, + + _updateCloneContainerPosition: function(cloneContainer) { + let [stageX, stageY] = this.get_transformed_position(); + + if (Config.PACKAGE_VERSION >= '3.36') + cloneContainer.set_position(stageX - this.translation_x, stageY - this.translation_y); + else + cloneContainer.set_position(stageX, stageY); + }, + + _createRaisedClone: function() { + let [width, height] = this.get_transformed_size(); + + // "clone" of this child (appIcon actor) + let cloneButton = this.child._delegate.getCloneButton(); + + // "clone" of this (taskbarItemContainer) + let cloneContainer = new St.Bin({ + child: cloneButton, + width: width, height: height, + reactive: false, + }); + + this._updateCloneContainerPosition(cloneContainer); + + // For the stretch animation + if (Config.PACKAGE_VERSION >= '3.36') { + let boundProperty = this._dtpPanel.checkIfVertical() ? 'translation_y' : 'translation_x'; + this.bind_property(boundProperty, cloneContainer, boundProperty, GObject.BindingFlags.SYNC_CREATE); + } else { + let constraint = new CloneContainerConstraint({ source: this }); + cloneContainer.add_constraint(constraint); + } + + // The clone follows its source when the taskbar is scrolled. + let taskbarScrollView = this.get_parent().get_parent(); + let adjustment = this._dtpPanel.checkIfVertical() ? taskbarScrollView.vscroll.get_adjustment() : taskbarScrollView.hscroll.get_adjustment(); + let adjustmentChangedId = adjustment.connect('notify::value', () => this._updateCloneContainerPosition(cloneContainer)); + + // Update clone position when an item is added to / removed from the taskbar. + let taskbarBox = this.get_parent(); + let taskbarBoxAllocationChangedId = taskbarBox.connect('notify::allocation', () => this._updateCloneContainerPosition(cloneContainer)); + + // The clone itself + this._raisedClone = cloneButton.child; + this._raisedClone.connect('destroy', () => { + adjustment.disconnect(adjustmentChangedId); + taskbarBox.disconnect(taskbarBoxAllocationChangedId); + Mainloop.idle_add(() => cloneContainer.destroy()); + delete this._raisedClone; + }); + + this._raisedClone.source.opacity = 0; + Main.uiGroup.add_actor(cloneContainer); + }, + + // Animate the clone. + // AppIcon actors cannot go outside the taskbar so the animation is done with a clone. + // If level is zero, the clone is dropped and destroyed. + raise: function(level) { + if (this._raisedClone) + Utils.stopAnimations(this._raisedClone); + else if (level) + this._createRaisedClone(); + else + return; + + let panelPosition = this._dtpPanel.getPosition(); + let panelElementPositions = this._dtpPanel.panelManager.panelsElementPositions[this._dtpPanel.monitor.index] || Pos.defaults; + let taskbarPosition = panelElementPositions.filter(pos => pos.element == 'taskbar')[0].position; + + let vertical = panelPosition == St.Side.LEFT || panelPosition == St.Side.RIGHT; + let translationDirection = panelPosition == St.Side.TOP || panelPosition == St.Side.LEFT ? 1 : -1; + let rotationDirection; + if (panelPosition == St.Side.LEFT || taskbarPosition == Pos.STACKED_TL) + rotationDirection = -1; + else if (panelPosition == St.Side.RIGHT || taskbarPosition == Pos.STACKED_BR) + rotationDirection = 1; + else { + let items = this.get_parent().get_children(); + let index = items.indexOf(this); + rotationDirection = (index - (items.length - 1) / 2) / ((items.length - 1) / 2); + } + + let duration = iconAnimationSettings.duration / 1000; + let rotation = iconAnimationSettings.rotation; + let travel = iconAnimationSettings.travel; + let zoom = iconAnimationSettings.zoom; + + // level is about 1 for the icon that is hovered, less for others. + // time depends on the translation to do. + let [width, height] = this._raisedClone.source.get_transformed_size(); + let translationMax = (vertical ? width : height) * (travel + (zoom - 1) / 2); + let translationEnd = translationMax * level; + let translationDone = vertical ? this._raisedClone.translation_x : this._raisedClone.translation_y; + let translationTodo = Math.abs(translationEnd - translationDone); + let scale = 1 + (zoom - 1) * level; + let rotationAngleZ = rotationDirection * rotation * level; + let time = duration * translationTodo / translationMax; + + let options = { + scale_x: scale, scale_y: scale, + rotation_angle_z: rotationAngleZ, + time: time, + transition: 'easeOutQuad', + onComplete: () => { + if (!level) { + this._raisedClone.source.opacity = 255; + this._raisedClone.destroy(); + delete this._raisedClone; + } + }, + }; + options[vertical ? 'translation_x' : 'translation_y'] = translationDirection * translationEnd; + + Utils.animate(this._raisedClone, options); + }, + + // Animate this and cloneContainer, since cloneContainer translation is bound to this. + stretch: function(translation) { + let duration = iconAnimationSettings.duration / 1000; + let zoom = iconAnimationSettings.zoom; + let animatedProperty = this._dtpPanel.checkIfVertical() ? 'translation_y' : 'translation_x'; + let isShowing = this.opacity != 255 || this.child.opacity != 255; + + if (isShowing) { + // Do no stop the animation initiated in DashItemContainer.show. + this[animatedProperty] = zoom * translation; + } else { + let options = { + time: duration, + transition: 'easeOutQuad', + }; + options[animatedProperty] = zoom * translation; + + Utils.stopAnimations(this); + Utils.animate(this, options); + } + }, +}); + var DragPlaceholderItem = Utils.defineClass({ Name: 'DashToPanel-DragPlaceholderItem', Extends: St.Widget,