Oreon-Lime-R2/dash-to-panel/dash-to-panel-44/taskbar.js

1557 lines
60 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This file is part of the Dash-To-Panel extension for Gnome 3
*
* This program 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 2 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 <http://www.gnu.org/licenses/>.
*
*
* Credits:
* This file is based on code from the Dash to Dock extension by micheleg
* and code from the Taskbar extension by Zorin OS
* Some code was also adapted from the upstream Gnome Shell source code.
*/
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;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const SearchController = imports.ui.main.overview._overview._controls._searchController;
const AppDisplay = imports.ui.main.overview._overview._controls.appDisplay;
const AppFavorites = imports.ui.appFavorites;
const Dash = imports.ui.dash;
const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Workspace = imports.ui.workspace;
const Me = imports.misc.extensionUtils.getCurrentExtension();
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;
var DASH_ANIMATION_TIME = Dash.DASH_ANIMATION_TIME / (Dash.DASH_ANIMATION_TIME > 1 ? 1000 : 1);
var DASH_ITEM_HOVER_TIMEOUT = Dash.DASH_ITEM_HOVER_TIMEOUT;
var MIN_ICON_SIZE = 4;
/**
* Extend DashItemContainer
*
* - set label position based on taskbar orientation
*
* I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973.
* thus use this ugly pattern.
*/
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:
* - modified chldBox calculations for when 'show-apps-at-top' option is checked
* - handle horizontal dash
*/
var taskbarActor = Utils.defineClass({
Name: 'DashToPanel-TaskbarActor',
Extends: St.Widget,
_init: function(delegate) {
this._delegate = delegate;
this._currentBackgroundColor = 0;
this.callParent('_init', { name: 'dashtopanelTaskbar',
layout_manager: new Clutter.BoxLayout({ orientation: Clutter.Orientation[delegate.dtpPanel.getOrientation().toUpperCase()] }),
clip_to_allocation: true });
},
vfunc_allocate: function(box, flags)  {
Utils.setAllocation(this, box, flags);
let panel = this._delegate.dtpPanel;
let availFixedSize = box[panel.fixedCoord.c2] - box[panel.fixedCoord.c1];
let availVarSize = box[panel.varCoord.c2] - box[panel.varCoord.c1];
let [dummy, scrollview, leftFade, rightFade] = this.get_children();
let [, natSize] = this[panel.sizeFunc](availFixedSize);
let childBox = new Clutter.ActorBox();
let orientation = panel.getOrientation();
Utils.allocate(dummy, childBox, flags);
childBox[panel.varCoord.c1] = box[panel.varCoord.c1];
childBox[panel.varCoord.c2] = Math.min(availVarSize, natSize);
childBox[panel.fixedCoord.c1] = box[panel.fixedCoord.c1];
childBox[panel.fixedCoord.c2] = box[panel.fixedCoord.c2];
Utils.allocate(scrollview, childBox, flags);
let [value, , upper, , , pageSize] = scrollview[orientation[0] + 'scroll'].adjustment.get_values();
upper = Math.floor(upper);
scrollview._dtpFadeSize = upper > pageSize ? this._delegate.iconSize : 0;
if (this._currentBackgroundColor !== panel.dynamicTransparency.currentBackgroundColor) {
this._currentBackgroundColor = panel.dynamicTransparency.currentBackgroundColor;
let gradientStyle = 'background-gradient-start: ' + this._currentBackgroundColor +
'background-gradient-direction: ' + orientation;
leftFade.set_style(gradientStyle);
rightFade.set_style(gradientStyle);
}
childBox[panel.varCoord.c2] = childBox[panel.varCoord.c1] + (value > 0 ? scrollview._dtpFadeSize : 0);
Utils.allocate(leftFade, childBox, flags);
childBox[panel.varCoord.c1] = box[panel.varCoord.c2] - (value + pageSize < upper ? scrollview._dtpFadeSize : 0);
childBox[panel.varCoord.c2] = box[panel.varCoord.c2];
Utils.allocate(rightFade, childBox, flags);
},
// We want to request the natural size of all our children
// as our natural width, so we chain up to StWidget (which
// then calls BoxLayout)
vfunc_get_preferred_width: function(forHeight) {
let [, natWidth] = St.Widget.prototype.vfunc_get_preferred_width.call(this, forHeight);
return [0, natWidth];
},
vfunc_get_preferred_height: function(forWidth) {
let [, natHeight] = St.Widget.prototype.vfunc_get_preferred_height.call(this, forWidth);
return [0, natHeight];
},
});
/* This class is a fork of the upstream dash class (ui.dash.js)
*
* Summary of changes:
* - disconnect global signals adding a destroy method;
* - play animations even when not in overview mode
* - set a maximum icon size
* - show running and/or favorite applications
* - emit a custom signal when an app icon is added
* - Add scrollview
* Ensure actor is visible on keyfocus inside the scrollview
* - add 128px icon size, might be useful for hidpi display
* - Sync minimization application target position.
*/
var taskbar = Utils.defineClass({
Name: 'DashToPanel.Taskbar',
_init : function(panel) {
this.dtpPanel = panel;
// start at smallest size due to running indicator drawing area expanding but not shrinking
this.iconSize = 16;
this._shownInitially = false;
this._signalsHandler = new Utils.GlobalSignalsHandler();
this._showLabelTimeoutId = 0;
this._resetHoverTimeoutId = 0;
this._ensureAppIconVisibilityTimeoutId = 0;
this._labelShowing = false;
this.fullScrollView = 0;
let isVertical = panel.checkIfVertical();
this._box = new St.BoxLayout({ vertical: isVertical,
clip_to_allocation: false,
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.START });
this._container = new taskbarActor(this);
this._scrollView = new St.ScrollView({ name: 'dashtopanelScrollview',
hscrollbar_policy: Gtk.PolicyType.NEVER,
vscrollbar_policy: Gtk.PolicyType.NEVER,
enable_mouse_scrolling: true });
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;
this._showAppsIconWrapper.connect('menu-state-changed', Lang.bind(this, function(showAppsIconWrapper, opened) {
this._itemMenuStateChanged(showAppsIconWrapper, opened);
}));
// an instance of the showAppsIcon class is encapsulated in the wrapper
this._showAppsIcon = this._showAppsIconWrapper.realShowAppsIcon;
this.showAppsButton = this._showAppsIcon.toggleButton;
if (isVertical) {
this.showAppsButton.set_width(panel.geom.w);
}
this.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled));
this.showAppsButton.checked = (SearchController._showAppsButton) ? SearchController._showAppsButton.checked : false;
this._showAppsIcon.childScale = 1;
this._showAppsIcon.childOpacity = 255;
this._showAppsIcon.icon.setIconSize(this.iconSize);
this._hookUpLabel(this._showAppsIcon, this._showAppsIconWrapper);
this._container.add_child(new St.Widget({ width: 0, reactive: false }));
this._container.add_actor(this._scrollView);
let orientation = panel.getOrientation();
let fadeStyle = 'background-gradient-direction:' + orientation;
let fade1 = new St.Widget({ style_class: 'scrollview-fade', reactive: false });
let fade2 = new St.Widget({ style_class: 'scrollview-fade',
reactive: false,
pivot_point: Utils.getPoint({ x: .5, y: .5 }),
rotation_angle_z: 180 });
fade1.set_style(fadeStyle);
fade2.set_style(fadeStyle);
this._container.add_actor(fade1);
this._container.add_actor(fade2);
this.previewMenu = new WindowPreview.PreviewMenu(panel);
this.previewMenu.enable();
let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
this.actor = new St.Bin({ child: this._container,
y_align: St.Align.START, x_align:rtl?St.Align.END:St.Align.START
});
let adjustment = this._scrollView[orientation[0] + 'scroll'].adjustment;
this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay));
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
this._appSystem = Shell.AppSystem.get_default();
this.iconAnimator = new PanelManager.IconAnimator(this.dtpPanel.panel.actor);
this._signalsHandler.add(
[
this.dtpPanel.panel.actor,
'notify::height',
() => this._queueRedisplay()
],
[
this.dtpPanel.panel.actor,
'notify::width',
() => this._queueRedisplay()
],
[
this._appSystem,
'installed-changed',
Lang.bind(this, function() {
AppFavorites.getAppFavorites().reload();
this._queueRedisplay();
})
],
[
this._appSystem,
'app-state-changed',
Lang.bind(this, this._queueRedisplay)
],
[
AppFavorites.getAppFavorites(),
'changed',
Lang.bind(this, this._queueRedisplay)
],
[
global.window_manager,
'switch-workspace',
() => this._connectWorkspaceSignals()
],
[
Utils.DisplayWrapper.getScreen(),
[
'window-entered-monitor',
'window-left-monitor'
],
() => {
if (Me.settings.get_boolean('isolate-monitors')) {
this._queueRedisplay();
}
}
],
[
Main.overview,
'item-drag-begin',
Lang.bind(this, this._onDragBegin)
],
[
Main.overview,
'item-drag-end',
Lang.bind(this, this._onDragEnd)
],
[
Main.overview,
'item-drag-cancelled',
Lang.bind(this, this._onDragCancelled)
],
[
// Ensure the ShowAppsButton status is kept in sync
SearchController._showAppsButton,
'notify::checked',
Lang.bind(this, this._syncShowAppsButtonToggled)
],
[
Me.settings,
[
'changed::dot-size',
'changed::show-favorites',
'changed::show-running-apps',
'changed::show-favorites-all-monitors'
],
Lang.bind(this, this._redisplay)
],
[
Me.settings,
'changed::group-apps',
Lang.bind(this, function() {
this.isGroupApps = Me.settings.get_boolean('group-apps');
this._connectWorkspaceSignals();
})
],
[
Me.settings,
[
'changed::group-apps-use-launchers',
'changed::taskbar-locked'
],
() => this.resetAppIcons()
],
[
adjustment,
[
'notify::upper',
'notify::pageSize'
],
() => this._onScrollSizeChange(adjustment)
]
);
this.isGroupApps = Me.settings.get_boolean('group-apps');
this._onScrollSizeChange(adjustment);
this._connectWorkspaceSignals();
},
destroy: function() {
this.iconAnimator.destroy();
this._signalsHandler.destroy();
this._signalsHandler = 0;
this._container.destroy();
this.previewMenu.disable();
this.previewMenu.destroy();
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();
// reset timeout to avid conflicts with the mousehover event
if (this._ensureAppIconVisibilityTimeoutId>0) {
Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId);
this._ensureAppIconVisibilityTimeoutId = 0;
}
// Skip to avoid double events mouse
if (event.is_pointer_emulated())
return Clutter.EVENT_STOP;
let adjustment, delta;
adjustment = this._scrollView[orientation[0] + 'scroll'].get_adjustment();
let increment = adjustment.step_increment;
switch ( event.get_scroll_direction() ) {
case Clutter.ScrollDirection.UP:
case Clutter.ScrollDirection.LEFT:
delta = -increment;
break;
case Clutter.ScrollDirection.DOWN:
case Clutter.ScrollDirection.RIGHT:
delta = +increment;
break;
case Clutter.ScrollDirection.SMOOTH:
let [dx, dy] = event.get_scroll_delta();
delta = dy*increment;
delta += dx*increment;
break;
}
adjustment.set_value(adjustment.get_value() + delta);
return Clutter.EVENT_STOP;
},
_onScrollSizeChange: function(adjustment) {
// Update minimization animation target position on scrollview change.
this._updateAppIcons();
// When applications are ungrouped and there is some empty space on the horizontal taskbar,
// force a fixed label width to prevent the icons from "wiggling" when an animation runs
// (adding or removing an icon). When the taskbar is full, revert to a dynamic label width
// to allow them to resize and make room for new icons.
if (!this.dtpPanel.checkIfVertical() && !this.isGroupApps) {
let initial = this.fullScrollView;
if (!this.fullScrollView && Math.floor(adjustment.upper) > adjustment.page_size) {
this.fullScrollView = adjustment.page_size;
} else if (adjustment.page_size < this.fullScrollView) {
this.fullScrollView = 0;
}
if (initial != this.fullScrollView) {
this._getAppIcons().forEach(a => a.updateTitleStyle());
}
}
},
_onDragBegin: function() {
this._dragCancelled = false;
this._dragMonitor = {
dragMotion: Lang.bind(this, this._onDragMotion)
};
DND.addDragMonitor(this._dragMonitor);
if (this._box.get_n_children() == 0) {
this._emptyDropTarget = new Dash.EmptyDropTargetItem();
this._box.insert_child_at_index(this._emptyDropTarget, 0);
this._emptyDropTarget.show(true);
}
this._toggleFavortieHighlight(true);
},
_onDragCancelled: function() {
this._dragCancelled = true;
if (this._dragInfo) {
this._box.set_child_at_index(this._dragInfo[1]._dashItemContainer, this._dragInfo[0]);
}
this._endDrag();
},
_onDragEnd: function() {
if (this._dragCancelled)
return;
this._endDrag();
},
_endDrag: function() {
if (this._dragInfo && this._dragInfo[1]._dashItemContainer instanceof DragPlaceholderItem) {
this._box.remove_child(this._dragInfo[1]._dashItemContainer);
this._dragInfo[1]._dashItemContainer.destroy();
delete this._dragInfo[1]._dashItemContainer;
}
this._dragInfo = null;
this._clearEmptyDropTarget();
this._showAppsIcon.setDragApp(null);
DND.removeDragMonitor(this._dragMonitor);
this._toggleFavortieHighlight();
},
_onDragMotion: function(dragEvent) {
let app = Dash.getAppFromSource(dragEvent.source);
if (app == null)
return DND.DragMotionResult.CONTINUE;
let showAppsHovered = this._showAppsIcon.contains(dragEvent.targetActor);
if (showAppsHovered)
this._showAppsIcon.setDragApp(app);
else
this._showAppsIcon.setDragApp(null);
return DND.DragMotionResult.CONTINUE;
},
_toggleFavortieHighlight: function(show) {
let appFavorites = AppFavorites.getAppFavorites();
let cssFuncName = (show ? 'add' : 'remove') + '_style_class_name';
this._getAppIcons().filter(appIcon => appFavorites.isFavorite(appIcon.app.get_id()))
.forEach(fav => fav._container[cssFuncName]('favorite'));
},
handleIsolatedWorkspaceSwitch: function() {
this._shownInitially = this.isGroupApps;
this._queueRedisplay();
},
_connectWorkspaceSignals: function() {
this._disconnectWorkspaceSignals();
this._lastWorkspace = Utils.DisplayWrapper.getWorkspaceManager().get_active_workspace();
this._workspaceWindowAddedId = this._lastWorkspace.connect('window-added', () => this._queueRedisplay());
this._workspaceWindowRemovedId = this._lastWorkspace.connect('window-removed', () => this._queueRedisplay());
},
_disconnectWorkspaceSignals: function() {
if (this._lastWorkspace) {
this._lastWorkspace.disconnect(this._workspaceWindowAddedId);
this._lastWorkspace.disconnect(this._workspaceWindowRemovedId);
this._lastWorkspace = null;
}
},
_queueRedisplay: function () {
Main.queueDeferredWork(this._workId);
},
_hookUpLabel: function(item, syncHandler) {
item.child.connect('notify::hover', Lang.bind(this, function() {
this._syncLabel(item, syncHandler);
}));
syncHandler.connect('sync-tooltip', Lang.bind(this, function() {
this._syncLabel(item, syncHandler);
}));
},
_createAppItem: function(app, window, isLauncher) {
let appIcon = new AppIcons.taskbarAppIcon(
{
app: app,
window: window,
isLauncher: isLauncher
},
this.dtpPanel,
{
setSizeManually: true,
showLabel: false,
isDraggable: !Me.settings.get_boolean('taskbar-locked'),
},
this.previewMenu,
this.iconAnimator
);
if (appIcon._draggable) {
appIcon._draggable.connect('drag-begin',
Lang.bind(this, function() {
appIcon.actor.opacity = 0;
appIcon.isDragged = 1;
this._dropIconAnimations();
}));
appIcon._draggable.connect('drag-end',
Lang.bind(this, function() {
appIcon.actor.opacity = 255;
delete appIcon.isDragged;
this._updateAppIcons();
}));
}
appIcon.connect('menu-state-changed',
Lang.bind(this, function(appIcon, opened) {
this._itemMenuStateChanged(item, opened);
}));
let item = new TaskbarItemContainer();
item._dtpPanel = this.dtpPanel
extendDashItemContainer(item);
item.setChild(appIcon.actor);
appIcon._dashItemContainer = item;
appIcon.actor.connect('notify::hover', Lang.bind(this, function() {
if (appIcon.actor.hover){
this._ensureAppIconVisibilityTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function(){
Utils.ensureActorVisibleInScrollView(this._scrollView, appIcon.actor, this._scrollView._dtpFadeSize);
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);
}
}));
appIcon.actor.connect('clicked',
Lang.bind(this, function(actor) {
Utils.ensureActorVisibleInScrollView(this._scrollView, actor, this._scrollView._dtpFadeSize);
}));
appIcon.actor.connect('key-focus-in', Lang.bind(this, function(actor) {
let [x_shift, y_shift] = Utils.ensureActorVisibleInScrollView(this._scrollView, actor, this._scrollView._dtpFadeSize);
// This signal is triggered also by mouse click. The popup menu is opened at the original
// coordinates. Thus correct for the shift which is going to be applied to the scrollview.
if (appIcon._menu) {
appIcon._menu._boxPointer.xOffset = -x_shift;
appIcon._menu._boxPointer.yOffset = -y_shift;
}
}));
// Override default AppIcon label_actor, now the
// accessible_name is set at DashItemContainer.setLabelText
appIcon.actor.label_actor = null;
item.setLabelText(app.get_name());
appIcon.icon.setIconSize(this.iconSize);
this._hookUpLabel(item, appIcon);
return item;
},
// Return an array with the "proper" appIcons currently in the taskbar
_getAppIcons: function() {
// Only consider children which are "proper" icons and which are not
// animating out (which means they will be destroyed at the end of
// the animation)
return this._getTaskbarIcons().map(function(actor){
return actor.child._delegate;
});
},
_getTaskbarIcons: function(includeAnimated) {
return this._box.get_children().filter(function(actor) {
return actor.child &&
actor.child._delegate &&
actor.child._delegate.icon &&
(includeAnimated || !actor.animatingOut);
});
},
_updateAppIcons: function() {
let appIcons = this._getAppIcons();
appIcons.filter(icon => icon.constructor === AppIcons.taskbarAppIcon).forEach(icon => {
icon.updateIcon();
});
},
_itemMenuStateChanged: function(item, opened) {
// When the menu closes, it calls sync_hover, which means
// that the notify::hover handler does everything we need to.
if (opened) {
if (this._showLabelTimeoutId > 0) {
Mainloop.source_remove(this._showLabelTimeoutId);
this._showLabelTimeoutId = 0;
}
item.hideLabel();
} else {
// I want to listen from outside when a menu is closed. I used to
// 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();
}
}
},
_syncLabel: function (item, syncHandler) {
let shouldShow = syncHandler ? syncHandler.shouldShowTooltip() : item.child.get_hover();
if (shouldShow) {
if (this._showLabelTimeoutId == 0) {
let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
Lang.bind(this, function() {
this._labelShowing = true;
item.showLabel();
this._showLabelTimeoutId = 0;
return GLib.SOURCE_REMOVE;
}));
GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel');
if (this._resetHoverTimeoutId > 0) {
Mainloop.source_remove(this._resetHoverTimeoutId);
this._resetHoverTimeoutId = 0;
}
}
} else {
if (this._showLabelTimeoutId > 0)
Mainloop.source_remove(this._showLabelTimeoutId);
this._showLabelTimeoutId = 0;
item.hideLabel();
if (this._labelShowing) {
this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT,
Lang.bind(this, function() {
this._labelShowing = false;
this._resetHoverTimeoutId = 0;
return GLib.SOURCE_REMOVE;
}));
GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing');
}
}
},
_adjustIconSize: function() {
const thisMonitorIndex = this.dtpPanel.monitor.index;
let panelSize = PanelSettings.getPanelSize(Me.settings, thisMonitorIndex);
let availSize = panelSize - Me.settings.get_int('appicon-padding') * 2;
let minIconSize = MIN_ICON_SIZE + panelSize % 2;
if (availSize == this.iconSize)
return;
if (availSize < minIconSize) {
availSize = minIconSize;
}
// For the icon size, we only consider children which are "proper"
// icons and which are not animating out (which means they will be
// destroyed at the end of the animation)
let iconChildren = this._getTaskbarIcons().concat([this._showAppsIcon]);
let scale = this.iconSize / availSize;
this.iconSize = availSize;
for (let i = 0; i < iconChildren.length; i++) {
let icon = iconChildren[i].child._delegate.icon;
// Set the new size immediately, to keep the icons' sizes
// in sync with this.iconSize
icon.setIconSize(this.iconSize);
// Don't animate the icon size change when the overview
// is transitioning, or when initially filling
// the taskbar
if (Main.overview.animationInProgress ||
!this._shownInitially)
continue;
let [targetWidth, targetHeight] = icon.icon.get_size();
// Scale the icon's texture to the previous size and
// tween to the new size
icon.icon.set_size(icon.icon.width * scale, icon.icon.height * scale);
Utils.animate(icon.icon,
{ width: targetWidth,
height: targetHeight,
time: DASH_ANIMATION_TIME,
transition: 'easeOutQuad',
});
}
},
sortAppsCompareFunction: function(appA, appB) {
return getAppStableSequence(appA, this.dtpPanel.monitor) -
getAppStableSequence(appB, this.dtpPanel.monitor);
},
getAppInfos: function() {
//get the user's favorite apps
let favoriteApps = this._checkIfShowingFavorites() ? AppFavorites.getAppFavorites().getFavorites() : [];
//find the apps that should be in the taskbar: the favorites first, then add the running apps
// When using isolation, we filter out apps that have no windows in
// the current workspace (this check is done in AppIcons.getInterestingWindows)
let runningApps = this._checkIfShowingRunningApps() ? this._getRunningApps().sort(this.sortAppsCompareFunction.bind(this)) : [];
if (!this.isGroupApps && Me.settings.get_boolean('group-apps-use-launchers')) {
return this._createAppInfos(favoriteApps, [], true)
.concat(this._createAppInfos(runningApps)
.filter(appInfo => appInfo.windows.length));
} else {
return this._createAppInfos(favoriteApps.concat(runningApps.filter(app => favoriteApps.indexOf(app) < 0)))
.filter(appInfo => appInfo.windows.length || favoriteApps.indexOf(appInfo.app) >= 0);
}
},
_redisplay: function () {
if (!this._signalsHandler) {
return;
}
//get the currently displayed appIcons
let currentAppIcons = this._getTaskbarIcons();
let expectedAppInfos = this.getAppInfos();
//remove the appIcons which are not in the expected apps list
for (let i = currentAppIcons.length - 1; i > -1; --i) {
let appIcon = currentAppIcons[i].child._delegate;
let appIndex = Utils.findIndex(expectedAppInfos, appInfo => appInfo.app == appIcon.app &&
appInfo.isLauncher == appIcon.isLauncher);
if (appIndex < 0 ||
(appIcon.window && (this.isGroupApps || expectedAppInfos[appIndex].windows.indexOf(appIcon.window) < 0)) ||
(!appIcon.window && !appIcon.isLauncher &&
!this.isGroupApps && expectedAppInfos[appIndex].windows.length)) {
currentAppIcons[i][this._shownInitially ? 'animateOutAndDestroy' : 'destroy']();
currentAppIcons.splice(i, 1);
}
}
//if needed, reorder the existing appIcons and create the missing ones
let currentPosition = 0;
for (let i = 0, l = expectedAppInfos.length; i < l; ++i) {
let neededAppIcons = this.isGroupApps || !expectedAppInfos[i].windows.length ?
[{ app: expectedAppInfos[i].app, window: null, isLauncher: expectedAppInfos[i].isLauncher }] :
expectedAppInfos[i].windows.map(window => ({ app: expectedAppInfos[i].app, window: window, isLauncher: false }));
for (let j = 0, ll = neededAppIcons.length; j < ll; ++j) {
//check if the icon already exists
let matchingAppIconIndex = Utils.findIndex(currentAppIcons, appIcon => appIcon.child._delegate.app == neededAppIcons[j].app &&
appIcon.child._delegate.window == neededAppIcons[j].window);
if (matchingAppIconIndex > 0 && matchingAppIconIndex != currentPosition) {
//moved icon, reposition it
this._box.remove_child(currentAppIcons[matchingAppIconIndex]);
this._box.insert_child_at_index(currentAppIcons[matchingAppIconIndex], currentPosition);
} else if (matchingAppIconIndex < 0) {
//the icon doesn't exist yet, create a new one
let newAppIcon = this._createAppItem(neededAppIcons[j].app, neededAppIcons[j].window, neededAppIcons[j].isLauncher);
this._box.insert_child_at_index(newAppIcon, currentPosition);
currentAppIcons.splice(currentPosition, 0, newAppIcon);
// Skip animations on first run when adding the initial set
// of items, to avoid all items zooming in at once
newAppIcon.show(this._shownInitially);
}
++currentPosition;
}
}
this._adjustIconSize();
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
// Without it, StBoxLayout may use a stale size cache
this._box.queue_relayout();
// This is required for icon reordering when the scrollview is used.
this._updateAppIcons();
// This will update the size, and the corresponding number for each icon on the primary panel
if (this.dtpPanel.isPrimary) {
this._updateNumberOverlay();
}
this._shownInitially = true;
},
_checkIfShowingRunningApps: function() {
return Me.settings.get_boolean('show-running-apps');
},
_checkIfShowingFavorites: function() {
return Me.settings.get_boolean('show-favorites') &&
(this.dtpPanel.isPrimary || Me.settings.get_boolean('show-favorites-all-monitors'));
},
_getRunningApps: function() {
let tracker = Shell.WindowTracker.get_default();
let windows = global.get_window_actors();
let apps = [];
for (let i = 0, l = windows.length; i < l; ++i) {
let app = tracker.get_window_app(windows[i].metaWindow);
if (app && apps.indexOf(app) < 0) {
apps.push(app);
}
}
return apps;
},
_createAppInfos: function(apps, defaultWindows, defaultIsLauncher) {
return apps.map(app => ({
app: app,
isLauncher: defaultIsLauncher || false,
windows: defaultWindows || AppIcons.getInterestingWindows(app, this.dtpPanel.monitor)
.sort(sortWindowsCompareFunction)
}));
},
// Reset the displayed apps icon to mantain the correct order
resetAppIcons : function(geometryChange) {
let children = this._getTaskbarIcons(true);
for (let i = 0; i < children.length; i++) {
let item = children[i];
item.destroy();
}
// to avoid ugly animations, just suppress them like when taskbar is first loaded.
this._shownInitially = false;
this._redisplay();
if (geometryChange && this.dtpPanel.checkIfVertical()) {
this.previewMenu._updateClip();
}
},
_updateNumberOverlay: function() {
let seenApps = {};
let counter = 0;
this._getAppIcons().forEach(function(icon) {
if (!seenApps[icon.app]) {
seenApps[icon.app] = 1;
counter++;
}
if (counter <= 10) {
icon.setNumberOverlay(counter == 10 ? 0 : counter);
} else {
// No overlay after 10
icon.setNumberOverlay(-1);
}
icon.updateHotkeyNumberOverlay();
});
if (Me.settings.get_boolean('hot-keys') &&
Me.settings.get_string('hotkeys-overlay-combo') === 'ALWAYS')
this.toggleNumberOverlay(true);
},
toggleNumberOverlay: function(activate) {
let appIcons = this._getAppIcons();
appIcons.forEach(function(icon) {
icon.toggleNumberOverlay(activate);
});
},
_clearEmptyDropTarget: function() {
if (this._emptyDropTarget) {
this._emptyDropTarget.animateOutAndDestroy();
this._emptyDropTarget = null;
}
},
handleDragOver: function(source, actor, x, y, time) {
if (source == Main.xdndHandler)
return DND.DragMotionResult.CONTINUE;
// Don't allow favoriting of transient apps
if (source.app == null || source.app.is_window_backed())
return DND.DragMotionResult.NO_DROP;
if (!this._settings.is_writable('favorite-apps'))
return DND.DragMotionResult.NO_DROP;
let sourceActor = source instanceof St.Widget ? source : source.actor;
let isVertical = this.dtpPanel.checkIfVertical();
if (!this._box.contains(sourceActor) && !source._dashItemContainer) {
//not an appIcon of the taskbar, probably from the applications view
source._dashItemContainer = new DragPlaceholderItem(source, this.iconSize, isVertical);
this._box.insert_child_above(source._dashItemContainer, null);
}
let sizeProp = isVertical ? 'height' : 'width';
let posProp = isVertical ? 'y' : 'x';
let pos = isVertical ? y : x;
let currentAppIcons = this._getAppIcons();
let sourceIndex = currentAppIcons.indexOf(source);
let hoveredIndex = Utils.findIndex(currentAppIcons,
appIcon => pos >= appIcon._dashItemContainer[posProp] &&
pos <= (appIcon._dashItemContainer[posProp] + appIcon._dashItemContainer[sizeProp]));
if (!this._dragInfo) {
this._dragInfo = [sourceIndex, source];
}
if (hoveredIndex >= 0) {
let isLeft = pos < currentAppIcons[hoveredIndex]._dashItemContainer[posProp] + currentAppIcons[hoveredIndex]._dashItemContainer[sizeProp] * .5;
// Don't allow positioning before or after self and between icons of same app
if (!(hoveredIndex === sourceIndex ||
(isLeft && hoveredIndex - 1 == sourceIndex) ||
(isLeft && hoveredIndex - 1 >= 0 && source.app != currentAppIcons[hoveredIndex - 1].app &&
currentAppIcons[hoveredIndex - 1].app == currentAppIcons[hoveredIndex].app) ||
(!isLeft && hoveredIndex + 1 == sourceIndex) ||
(!isLeft && hoveredIndex + 1 < currentAppIcons.length && source.app != currentAppIcons[hoveredIndex + 1].app &&
currentAppIcons[hoveredIndex + 1].app == currentAppIcons[hoveredIndex].app))) {
this._box.set_child_at_index(source._dashItemContainer, hoveredIndex);
// Ensure the next and previous icon are visible when moving the icon
// (I assume there's room for both of them)
if (hoveredIndex > 1)
Utils.ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[hoveredIndex-1], this._scrollView._dtpFadeSize);
if (hoveredIndex < this._box.get_children().length-1)
Utils.ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[hoveredIndex+1], this._scrollView._dtpFadeSize);
}
}
return this._dragInfo[0] !== sourceIndex ? DND.DragMotionResult.MOVE_DROP : DND.DragMotionResult.CONTINUE;
},
// Draggable target interface
acceptDrop : function(source, actor, x, y, time) {
// Don't allow favoriting of transient apps
if (!source.app || source.app.is_window_backed() || !this._settings.is_writable('favorite-apps')) {
return false;
}
let appIcons = this._getAppIcons();
let sourceIndex = appIcons.indexOf(source);
let usingLaunchers = !this.isGroupApps && Me.settings.get_boolean('group-apps-use-launchers');
// dragging the icon to its original position
if (this._dragInfo[0] === sourceIndex) {
return true;
}
let appFavorites = AppFavorites.getAppFavorites();
let sourceAppId = source.app.get_id();
let appIsFavorite = appFavorites.isFavorite(sourceAppId);
let replacingIndex = sourceIndex + (sourceIndex > this._dragInfo[0] ? -1 : 1);
let favoriteIndex = replacingIndex >= 0 ? appFavorites.getFavorites().indexOf(appIcons[replacingIndex].app) : 0;
let sameApps = appIcons.filter(a => a != source && a.app == source.app);
let showingFavorites = this._checkIfShowingFavorites();
let favoritesCount = 0;
let position = 0;
let interestingWindows = {};
let getAppWindows = app => {
if (!interestingWindows[app]) {
interestingWindows[app] = AppIcons.getInterestingWindows(app, this.dtpPanel.monitor);
}
let appWindows = interestingWindows[app]; //prevents "reference to undefined property Symbol.toPrimitive" warning
return appWindows;
};
if (sameApps.length &&
((!appIcons[sourceIndex - 1] || appIcons[sourceIndex - 1].app !== source.app) &&
(!appIcons[sourceIndex + 1] || appIcons[sourceIndex + 1].app !== source.app))) {
appIcons.splice(appIcons.indexOf(sameApps[0]), sameApps.length);
Array.prototype.splice.apply(appIcons, [sourceIndex + 1, 0].concat(sameApps));
}
for (let i = 0, l = appIcons.length; i < l; ++i) {
let windows = [];
if (!usingLaunchers || (!source.isLauncher && !appIcons[i].isLauncher)) {
windows = appIcons[i].window ? [appIcons[i].window] : getAppWindows(appIcons[i].app);
}
windows.forEach(w => w._dtpPosition = position++);
if (showingFavorites &&
((usingLaunchers && appIcons[i].isLauncher) ||
(!usingLaunchers && appFavorites.isFavorite(appIcons[i].app.get_id())))) {
++favoritesCount;
}
}
if (sourceIndex < favoritesCount) {
if (appIsFavorite) {
appFavorites.moveFavoriteToPos(sourceAppId, favoriteIndex);
} else {
appFavorites.addFavoriteAtPos(sourceAppId, favoriteIndex);
}
} else if (appIsFavorite && showingFavorites && (!usingLaunchers || source.isLauncher)) {
appFavorites.removeFavorite(sourceAppId);
}
appFavorites.emit('changed');
return true;
},
_onShowAppsButtonToggled: function() {
// Sync the status of the default appButtons. Only if the two statuses are
// different, that means the user interacted with the extension provided
// application button, cutomize the behaviour. Otherwise the shell has changed the
// status (due to the _syncShowAppsButtonToggled function below) and it
// has already performed the desired action.
let selector = SearchController;
if (selector._showAppsButton &&
selector._showAppsButton.checked !== this.showAppsButton.checked) {
// find visible view
if (this.showAppsButton.checked) {
if (Me.settings.get_boolean('show-apps-override-escape')) {
//override escape key to return to the desktop when entering the overview using the showapps button
SearchController._onStageKeyPress = function(actor, event) {
if (Main.modalCount == 1 && event.get_key_symbol() === Clutter.KEY_Escape) {
this._searchActive ? this.reset() : Main.overview.hide();
return Clutter.EVENT_STOP;
}
return this.__proto__._onStageKeyPress.call(this, actor, event);
};
}
// force spring animation triggering.By default the animation only
// runs if we are already inside the overview.
if (!Main.overview._shown) {
this.forcedOverview = true;
let grid = AppDisplay._grid;
let onShownCb;
let overviewSignal = Config.PACKAGE_VERSION > '3.38.1' ? 'showing' : 'shown';
let overviewShowingId = Main.overview.connect(overviewSignal, () => {
Main.overview.disconnect(overviewShowingId);
onShownCb();
});
onShownCb = () => grid.emit('animation-done');
}
//temporarily use as primary the monitor on which the showapps btn was clicked, this is
//restored by the panel when exiting the overview
this.dtpPanel.panelManager.setFocusedMonitor(this.dtpPanel.monitor);
let overviewHiddenId = Main.overview.connect('hidden', () => {
Main.overview.disconnect(overviewHiddenId);
delete SearchController._onStageKeyPress;
});
// Finally show the overview
selector._showAppsButton.checked = true;
Main.overview.show(2 /*APP_GRID*/);
}
else {
if (this.forcedOverview) {
// force exiting overview if needed
Main.overview.hide();
this.forcedOverview = false;
}
else {
selector._showAppsButton.checked = false;
this.forcedOverview = false;
}
}
}
},
_syncShowAppsButtonToggled: function() {
let status = SearchController._showAppsButton.checked;
if (this.showAppsButton.checked !== status)
this.showAppsButton.checked = status;
},
showShowAppsButton: function() {
this.showAppsButton.visible = true;
this.showAppsButton.set_width(-1);
this.showAppsButton.set_height(-1);
},
popupFocusedAppSecondaryMenu: function() {
let appIcons = this._getAppIcons();
let tracker = Shell.WindowTracker.get_default();
for(let i in appIcons) {
if(appIcons[i].app == tracker.focus_app) {
let appIcon = appIcons[i];
if(appIcon._menu && appIcon._menu.isOpen)
appIcon._menu.close();
else
appIcon.popupMenu();
appIcon.sync_hover();
break;
}
}
},
});
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);
},
});
var TaskbarItemContainer = Utils.defineClass({
Name: 'DashToPanel-TaskbarItemContainer',
Extends: Dash.DashItemContainer,
vfunc_allocate: function(box, flags) {
if (this.child == null)
return;
Utils.setAllocation(this, box, flags);
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
let [minChildWidth, minChildHeight, natChildWidth, natChildHeight] = this.child.get_preferred_size();
let [childScaleX, childScaleY] = this.child.get_scale();
let childWidth = Math.min(natChildWidth * childScaleX, availWidth);
let childHeight = Math.min(natChildHeight * childScaleY, availHeight);
let childBox = new Clutter.ActorBox();
childBox.x1 = (availWidth - childWidth) / 2;
childBox.y1 = (availHeight - childHeight) / 2;
childBox.x2 = childBox.x1 + childWidth;
childBox.y2 = childBox.y1 + childHeight;
Utils.allocate(this.child, childBox, flags);
},
// 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,
_init: function(appIcon, iconSize, isVertical) {
this.callParent('_init', { style: AppIcons.getIconContainerStyle(isVertical), layout_manager: new Clutter.BinLayout() });
this.child = { _delegate: appIcon };
this._clone = new Clutter.Clone({
source: appIcon.icon._iconBin,
width: iconSize,
height: iconSize
});
this.add_actor(this._clone);
},
destroy: function() {
this._clone.destroy();
this.callParent('destroy');
},
});
function getAppStableSequence(app, monitor) {
let windows = AppIcons.getInterestingWindows(app, monitor);
return windows.reduce((prevWindow, window) => {
return Math.min(prevWindow, getWindowStableSequence(window));
}, Infinity);
}
function sortWindowsCompareFunction(windowA, windowB) {
return getWindowStableSequence(windowA) - getWindowStableSequence(windowB);
}
function getWindowStableSequence(window) {
return ('_dtpPosition' in window ? window._dtpPosition : window.get_stable_sequence());
}