summaryrefslogtreecommitdiff
path: root/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js')
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js1177
1 files changed, 1177 insertions, 0 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js
new file mode 100644
index 0000000..956bbeb
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js
@@ -0,0 +1,1177 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+import {
+ Clutter,
+ Gio,
+ GLib,
+ GObject,
+ Shell,
+ St,
+} from './dependencies/gi.js';
+
+import {
+ AppFavorites,
+ Dash,
+ DND,
+ Main,
+} from './dependencies/shell/ui.js';
+
+import {
+ Util,
+} from './dependencies/shell/misc.js';
+
+import {
+ AppIcons,
+ Docking,
+ Theming,
+ Utils,
+} from './imports.js';
+
+// module "Dash" did not export DASH_ANIMATION_TIME in old versions
+// so we just define it like it is defined in Dash;
+// taken from https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/dash.js
+const DASH_ANIMATION_TIME = Dash.DASH_ANIMATION_TIME ?? 200;
+const DASH_VISIBILITY_TIMEOUT = 3;
+
+const Labels = Object.freeze({
+ SHOW_MOUNTS: Symbol('show-mounts'),
+ FIRST_LAST_CHILD_WORKAROUND: Symbol('first-last-child-workaround'),
+});
+
+/**
+ * Extend DashItemContainer
+ *
+ * - set label position based on dash orientation
+ *
+ */
+const DockDashItemContainer = GObject.registerClass(
+class DockDashItemContainer extends Dash.DashItemContainer {
+ _init(position) {
+ super._init();
+
+ this.label?.add_style_class_name(Theming.PositionStyleClass[position]);
+ if (Docking.DockManager.settings.customThemeShrink)
+ this.label?.add_style_class_name('shrink');
+ }
+
+ showLabel() {
+ return AppIcons.itemShowLabel.call(this);
+ }
+
+ // we override the method show taken from:
+ // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/dash.js
+ // in order to apply a little modification at the end of the animation
+ // which makes sure that the icon background is not blurry
+ show(animate) {
+ if (this.child == null)
+ return;
+
+ this.ease({
+ scale_x: 1,
+ scale_y: 1,
+ opacity: 255,
+ duration: animate ? DASH_ANIMATION_TIME : 0,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ onComplete: () => {
+ // when the animation is ended, we simulate
+ // a hover to gain back focus and unblur the
+ // background
+ this.set_hover(true);
+ },
+ });
+ }
+});
+
+const DockDashIconsVerticalLayout = GObject.registerClass(
+ class DockDashIconsVerticalLayout extends Clutter.BoxLayout {
+ _init() {
+ super._init({
+ orientation: Clutter.Orientation.VERTICAL,
+ });
+ }
+
+ vfunc_get_preferred_height(container, forWidth) {
+ const [natHeight] = super.vfunc_get_preferred_height(container, forWidth);
+ return [natHeight, 0];
+ }
+ });
+
+
+const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128];
+
+/**
+ * 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
+ * - hide showApps label when the custom menu is shown.
+ * - add scrollview
+ * ensure actor is visible on keyfocus inseid the scrollview
+ * - add 128px icon size, might be useful for hidpi display
+ * - sync minimization application target position.
+ * - keep running apps ordered.
+ */
+export const DockDash = GObject.registerClass({
+ Properties: {
+ 'requires-visibility': GObject.ParamSpec.boolean(
+ 'requires-visibility', 'requires-visibility', 'requires-visibility',
+ GObject.ParamFlags.READWRITE,
+ false),
+ },
+ Signals: {
+ 'menu-opened': {},
+ 'menu-closed': {},
+ 'icon-size-changed': {},
+ },
+}, class DockDash extends St.Widget {
+ _init(monitorIndex) {
+ // Initialize icon variables and size
+ super._init({
+ name: 'dash',
+ offscreen_redirect: Clutter.OffscreenRedirect.ALWAYS,
+ layout_manager: new Clutter.BinLayout(),
+ });
+
+ this._maxWidth = -1;
+ this._maxHeight = -1;
+ this.iconSize = Docking.DockManager.settings.dashMaxIconSize;
+ this._availableIconSizes = baseIconSizes;
+ this._shownInitially = false;
+ this._initializeIconSize(this.iconSize);
+ this._signalsHandler = new Utils.GlobalSignalsHandler(this);
+
+ this._separator = null;
+
+ this._monitorIndex = monitorIndex;
+ this._position = Utils.getPosition();
+ this._isHorizontal = (this._position === St.Side.TOP) ||
+ (this._position === St.Side.BOTTOM);
+
+ this._dragPlaceholder = null;
+ this._dragPlaceholderPos = -1;
+ this._animatingPlaceholdersCount = 0;
+ this._showLabelTimeoutId = 0;
+ this._resetHoverTimeoutId = 0;
+ this._labelShowing = false;
+
+ this._dashContainer = new St.BoxLayout({
+ name: 'dashtodockDashContainer',
+ x_align: Clutter.ActorAlign.CENTER,
+ y_align: Clutter.ActorAlign.CENTER,
+ vertical: !this._isHorizontal,
+ y_expand: this._isHorizontal,
+ x_expand: !this._isHorizontal,
+ });
+
+ this._scrollView = new St.ScrollView({
+ name: 'dashtodockDashScrollview',
+ hscrollbar_policy: this._isHorizontal ? St.PolicyType.EXTERNAL : St.PolicyType.NEVER,
+ vscrollbar_policy: this._isHorizontal ? St.PolicyType.NEVER : St.PolicyType.EXTERNAL,
+ x_expand: this._isHorizontal,
+ y_expand: !this._isHorizontal,
+ enable_mouse_scrolling: false,
+ });
+
+ this._scrollView.connect('scroll-event', this._onScrollEvent.bind(this));
+
+ this._boxContainer = new St.BoxLayout({
+ name: 'dashtodockBoxContainer',
+ x_align: Clutter.ActorAlign.FILL,
+ y_align: Clutter.ActorAlign.FILL,
+ vertical: !this._isHorizontal,
+ });
+ this._boxContainer.add_style_class_name(Theming.PositionStyleClass[this._position]);
+
+ const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
+ this._box = new St.BoxLayout({
+ vertical: !this._isHorizontal,
+ clip_to_allocation: false,
+ ...!this._isHorizontal ? {layout_manager: new DockDashIconsVerticalLayout()} : {},
+ x_align: rtl ? Clutter.ActorAlign.END : Clutter.ActorAlign.START,
+ y_align: this._isHorizontal ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START,
+ y_expand: !this._isHorizontal,
+ x_expand: this._isHorizontal,
+ });
+ this._box._delegate = this;
+ this._boxContainer.add_child(this._box);
+ Utils.addActor(this._scrollView, this._boxContainer);
+ this._dashContainer.add_child(this._scrollView);
+
+ this._showAppsIcon = new AppIcons.DockShowAppsIcon(this._position);
+ this._showAppsIcon.show(false);
+ this._showAppsIcon.icon.setIconSize(this.iconSize);
+ this._showAppsIcon.x_expand = false;
+ this._showAppsIcon.y_expand = false;
+ this.showAppsButton.connect('notify::hover', a => {
+ if (this._showAppsIcon.get_parent() === this._boxContainer)
+ this._ensureItemVisibility(a);
+ });
+ if (!this._isHorizontal)
+ this._showAppsIcon.y_align = Clutter.ActorAlign.START;
+ this._hookUpLabel(this._showAppsIcon);
+ this._showAppsIcon.connect('menu-state-changed', (_icon, opened) => {
+ this._itemMenuStateChanged(this._showAppsIcon, opened);
+ });
+ this.updateShowAppsButton();
+
+ this._background = new St.Widget({
+ style_class: 'dash-background',
+ y_expand: this._isHorizontal,
+ x_expand: !this._isHorizontal,
+ });
+
+ const sizerBox = new Clutter.Actor();
+ sizerBox.add_constraint(new Clutter.BindConstraint({
+ source: this._isHorizontal ? this._showAppsIcon.icon : this._dashContainer,
+ coordinate: Clutter.BindCoordinate.HEIGHT,
+ }));
+ sizerBox.add_constraint(new Clutter.BindConstraint({
+ source: this._isHorizontal ? this._dashContainer : this._showAppsIcon.icon,
+ coordinate: Clutter.BindCoordinate.WIDTH,
+ }));
+ this._background.add_child(sizerBox);
+
+ this.add_child(this._background);
+ this.add_child(this._dashContainer);
+
+ this._workId = Main.initializeDeferredWork(this._box, this._redisplay.bind(this));
+
+ this._shellSettings = new Gio.Settings({
+ schema_id: 'org.gnome.shell',
+ });
+
+ this._appSystem = Shell.AppSystem.get_default();
+
+ this.iconAnimator = new Docking.IconAnimator(this);
+
+ this._signalsHandler.add([
+ this._appSystem,
+ 'installed-changed',
+ () => {
+ AppFavorites.getAppFavorites().reload();
+ this._queueRedisplay();
+ },
+ ], [
+ AppFavorites.getAppFavorites(),
+ 'changed',
+ this._queueRedisplay.bind(this),
+ ], [
+ this._appSystem,
+ 'app-state-changed',
+ this._queueRedisplay.bind(this),
+ ], [
+ Main.overview,
+ 'item-drag-begin',
+ this._onItemDragBegin.bind(this),
+ ], [
+ Main.overview,
+ 'item-drag-end',
+ this._onItemDragEnd.bind(this),
+ ], [
+ Main.overview,
+ 'item-drag-cancelled',
+ this._onItemDragCancelled.bind(this),
+ ], [
+ Main.overview,
+ 'window-drag-begin',
+ this._onWindowDragBegin.bind(this),
+ ], [
+ Main.overview,
+ 'window-drag-cancelled',
+ this._onWindowDragEnd.bind(this),
+ ], [
+ Main.overview,
+ 'window-drag-end',
+ this._onWindowDragEnd.bind(this),
+ ]);
+
+ this.connect('destroy', this._onDestroy.bind(this));
+ }
+
+ vfunc_get_preferred_height(forWidth) {
+ const [minHeight, natHeight] = super.vfunc_get_preferred_height.call(this, forWidth);
+ if (!this._isHorizontal && this._maxHeight !== -1 && natHeight > this._maxHeight)
+ return [minHeight, this._maxHeight];
+ else
+ return [minHeight, natHeight];
+ }
+
+ vfunc_get_preferred_width(forHeight) {
+ const [minWidth, natWidth] = super.vfunc_get_preferred_width.call(this, forHeight);
+ if (this._isHorizontal && this._maxWidth !== -1 && natWidth > this._maxWidth)
+ return [minWidth, this._maxWidth];
+ else
+ return [minWidth, natWidth];
+ }
+
+ get _container() {
+ return this._dashContainer;
+ }
+
+ _onDestroy() {
+ this.iconAnimator.destroy();
+
+ if (this._requiresVisibilityTimeout) {
+ GLib.source_remove(this._requiresVisibilityTimeout);
+ delete this._requiresVisibilityTimeout;
+ }
+
+ if (this._ensureActorVisibilityTimeoutId) {
+ GLib.source_remove(this._ensureActorVisibilityTimeoutId);
+ delete this._ensureActorVisibilityTimeoutId;
+ }
+ }
+
+
+ _onItemDragBegin(...args) {
+ return Dash.Dash.prototype._onItemDragBegin.call(this, ...args);
+ }
+
+ _onItemDragCancelled(...args) {
+ return Dash.Dash.prototype._onItemDragCancelled.call(this, ...args);
+ }
+
+ _onItemDragEnd(...args) {
+ return Dash.Dash.prototype._onItemDragEnd.call(this, ...args);
+ }
+
+ _endItemDrag(...args) {
+ return Dash.Dash.prototype._endItemDrag.call(this, ...args);
+ }
+
+ _onItemDragMotion(...args) {
+ return Dash.Dash.prototype._onItemDragMotion.call(this, ...args);
+ }
+
+ _appIdListToHash(...args) {
+ return Dash.Dash.prototype._appIdListToHash.call(this, ...args);
+ }
+
+ _queueRedisplay(...args) {
+ return Dash.Dash.prototype._queueRedisplay.call(this, ...args);
+ }
+
+ _hookUpLabel(...args) {
+ return Dash.Dash.prototype._hookUpLabel.call(this, ...args);
+ }
+
+ _syncLabel(...args) {
+ return Dash.Dash.prototype._syncLabel.call(this, ...args);
+ }
+
+ _clearDragPlaceholder(...args) {
+ return Dash.Dash.prototype._clearDragPlaceholder.call(this, ...args);
+ }
+
+ _clearEmptyDropTarget(...args) {
+ return Dash.Dash.prototype._clearEmptyDropTarget.call(this, ...args);
+ }
+
+ handleDragOver(source, actor, x, y, time) {
+ let ret;
+ if (this._isHorizontal) {
+ ret = Dash.Dash.prototype.handleDragOver.call(this, source, actor, x, y, time);
+
+ if (ret === DND.DragMotionResult.CONTINUE)
+ return ret;
+ } else {
+ const propertyInjections = new Utils.PropertyInjectionsHandler();
+ propertyInjections.add(this._box, 'width', {
+ get: () => this._box.get_children().reduce((a, c) => a + c.height, 0),
+ });
+
+ if (this._dragPlaceholder) {
+ propertyInjections.add(this._dragPlaceholder, 'width', {
+ get: () => this._dragPlaceholder.height,
+ });
+ }
+
+ ret = Dash.Dash.prototype.handleDragOver.call(this, source, actor, y, x, time);
+ propertyInjections.destroy();
+
+ if (ret === DND.DragMotionResult.CONTINUE)
+ return ret;
+
+ if (this._dragPlaceholder) {
+ this._dragPlaceholder.child.set_width(this.iconSize / 2);
+ this._dragPlaceholder.child.set_height(this.iconSize);
+
+ let pos = this._dragPlaceholderPos;
+ if (this._isHorizontal &&
+ Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
+ pos = this._box.get_children() - 1 - pos;
+
+ if (pos !== this._dragPlaceholderPos) {
+ this._dragPlaceholderPos = pos;
+ this._box.set_child_at_index(this._dragPlaceholder,
+ this._dragPlaceholderPos);
+ }
+ }
+ }
+
+ if (this._dragPlaceholder) {
+ // Ensure the next and previous icon are visible when moving the
+ // placeholder (we're assuming there's room for both of them)
+ const children = this._box.get_children();
+ if (this._dragPlaceholderPos > 0) {
+ ensureActorVisibleInScrollView(this._scrollView,
+ children[this._dragPlaceholderPos - 1]);
+ }
+
+ if (this._dragPlaceholderPos >= -1 &&
+ this._dragPlaceholderPos < children.length - 1) {
+ ensureActorVisibleInScrollView(this._scrollView,
+ children[this._dragPlaceholderPos + 1]);
+ }
+ }
+
+ return ret;
+ }
+
+ acceptDrop(...args) {
+ return Dash.Dash.prototype.acceptDrop.call(this, ...args);
+ }
+
+ _onWindowDragBegin(...args) {
+ return Dash.Dash.prototype._onWindowDragBegin.call(this, ...args);
+ }
+
+ _onWindowDragEnd(...args) {
+ return Dash.Dash.prototype._onWindowDragEnd.call(this, ...args);
+ }
+
+ _onScrollEvent(actor, event) {
+ // If scroll is not used because the icon is resized, let the scroll event propagate.
+ if (!Docking.DockManager.settings.iconSizeFixed)
+ return Clutter.EVENT_PROPAGATE;
+
+ // reset timeout to avid conflicts with the mousehover event
+ this._ensureItemVisibility(null);
+
+ // Skip to avoid double events mouse
+ if (event.get_scroll_direction() !== Clutter.ScrollDirection.SMOOTH)
+ return Clutter.EVENT_STOP;
+
+
+ let adjustment, delta = 0;
+
+ if (this._isHorizontal) {
+ adjustment = this._scrollView.get_hadjustment
+ ? this._scrollView.get_hadjustment()
+ : this._scrollView.get_hscroll_bar().get_adjustment();
+ } else {
+ adjustment = this._scrollView.get_vadjustment
+ ? this._scrollView.get_vadjustment()
+ : this._scrollView.get_vscroll_bar().get_adjustment();
+ }
+
+ const increment = adjustment.step_increment;
+ const [dx, dy] = event.get_scroll_delta();
+
+ if (this._isHorizontal)
+ delta = (Math.abs(dx) > Math.abs(dy) ? dx : dy) * increment;
+ else
+ delta = dy * increment;
+
+ const value = adjustment.get_value();
+
+ // TODO: Remove this if possible.
+ if (Number.isNaN(value))
+ adjustment.set_value(delta);
+ else
+ adjustment.set_value(value + delta);
+
+ return Clutter.EVENT_STOP;
+ }
+
+ _ensureItemVisibility(actor) {
+ if (actor?.hover) {
+ const destroyId =
+ actor.connect('destroy', () => this._ensureItemVisibility(null));
+ this._ensureActorVisibilityTimeoutId = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT, 100, () => {
+ actor.disconnect(destroyId);
+ ensureActorVisibleInScrollView(this._scrollView, actor);
+ this._ensureActorVisibilityTimeoutId = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+ } else if (this._ensureActorVisibilityTimeoutId) {
+ GLib.source_remove(this._ensureActorVisibilityTimeoutId);
+ this._ensureActorVisibilityTimeoutId = 0;
+ }
+ }
+
+ _createAppItem(app) {
+ const appIcon = new AppIcons.makeAppIcon(app, this._monitorIndex, this.iconAnimator);
+
+ if (appIcon._draggable) {
+ appIcon._draggable.connect('drag-begin', () => {
+ appIcon.opacity = 50;
+ });
+ appIcon._draggable.connect('drag-end', () => {
+ appIcon.opacity = 255;
+ });
+ }
+
+ appIcon.connectObject('menu-state-changed', (_, opened) => {
+ this._itemMenuStateChanged(item, opened);
+ }, this);
+
+ const item = new DockDashItemContainer(this._position);
+ item.setChild(appIcon);
+
+ appIcon.connectObject('notify::hover', a => this._ensureItemVisibility(a), this);
+ appIcon.connectObject('clicked', actor => {
+ ensureActorVisibleInScrollView(this._scrollView, actor);
+ }, this);
+
+ appIcon.connectObject('key-focus-in', actor => {
+ const [xShift, yShift] = ensureActorVisibleInScrollView(this._scrollView, actor);
+
+ // 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 = -xShift;
+ appIcon._menu._boxPointer.yOffset = -yShift;
+ }
+ }, this);
+
+ appIcon.connectObject('notify::focused', () => {
+ const {settings} = Docking.DockManager;
+ if (appIcon.focused && settings.scrollToFocusedApplication)
+ ensureActorVisibleInScrollView(this._scrollView, item);
+ }, this);
+
+ appIcon.connectObject('notify::urgent', () => {
+ if (appIcon.urgent) {
+ ensureActorVisibleInScrollView(this._scrollView, item);
+ if (Docking.DockManager.settings.showDockUrgentNotify)
+ this._requireVisibility();
+ }
+ }, this);
+
+ // Override default AppIcon label_actor, now the
+ // accessible_name is set at DashItemContainer.setLabelText
+ appIcon.label_actor = null;
+ item.setLabelText(app.get_name());
+
+ appIcon.icon.setIconSize(this.iconSize);
+ this._hookUpLabel(item, appIcon);
+
+ item.connectObject('notify::position', () => appIcon.updateIconGeometry(), appIcon);
+ item.connectObject('notify::size', () => appIcon.updateIconGeometry(), appIcon);
+
+ return item;
+ }
+
+ _requireVisibility() {
+ this.requiresVisibility = true;
+
+ if (this._requiresVisibilityTimeout)
+ GLib.source_remove(this._requiresVisibilityTimeout);
+
+ this._requiresVisibilityTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
+ DASH_VISIBILITY_TIMEOUT, () => {
+ this._requiresVisibilityTimeout = 0;
+ this.requiresVisibility = false;
+ });
+ }
+
+ /**
+ * Return an array with the "proper" appIcons currently in the dash
+ */
+ getAppIcons() {
+ // Only consider children which are "proper"
+ // icons (i.e. ignoring drag placeholders) and which are not
+ // animating out (which means they will be destroyed at the end of
+ // the animation)
+ const iconChildren = this._box.get_children().filter(actor => {
+ return actor.child &&
+ !!actor.child.icon &&
+ !actor.animatingOut;
+ });
+
+ const appIcons = iconChildren.map(actor => {
+ return actor.child;
+ });
+
+ return appIcons;
+ }
+
+ _itemMenuStateChanged(item, opened) {
+ Dash.Dash.prototype._itemMenuStateChanged.call(this, item, opened);
+
+ if (opened) {
+ this.emit('menu-opened');
+ } 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');
+ }
+ }
+
+ _adjustIconSize() {
+ // For the icon size, we only consider children which are "proper"
+ // icons (i.e. ignoring drag placeholders) and which are not
+ // animating out (which means they will be destroyed at the end of
+ // the animation)
+ const iconChildren = this._box.get_children().filter(actor => {
+ return actor.child &&
+ actor.child._delegate &&
+ actor.child._delegate.icon &&
+ !actor.animatingOut;
+ });
+
+ iconChildren.push(this._showAppsIcon);
+
+ if (this._maxWidth === -1 && this._maxHeight === -1)
+ return;
+
+ // Check if the container is present in the stage. This avoids critical
+ // errors when unlocking the screen
+ if (!this._container.get_stage())
+ return;
+
+ const themeNode = this._dashContainer.get_theme_node();
+ const maxAllocation = new Clutter.ActorBox({
+ x1: 0,
+ y1: 0,
+ x2: this._isHorizontal ? this._maxWidth : 42 /* whatever */,
+ y2: this._isHorizontal ? 42 : this._maxHeight,
+ });
+ const maxContent = themeNode.get_content_box(maxAllocation);
+ let availSpace;
+ if (this._isHorizontal)
+ availSpace = maxContent.get_width();
+ else
+ availSpace = maxContent.get_height();
+
+ const spacing = themeNode.get_length('spacing');
+
+ const [{child: firstButton}] = iconChildren;
+ const {child: firstIcon} = firstButton?.icon ?? {child: null};
+
+ // if no icons there's nothing to adjust
+ if (!firstIcon)
+ return;
+
+ // Enforce valid spacings during the size request
+ firstIcon.ensure_style();
+ const [, , iconWidth, iconHeight] = firstIcon.get_preferred_size();
+ const [, , buttonWidth, buttonHeight] = firstButton.get_preferred_size();
+
+ if (this._isHorizontal) {
+ // Subtract icon padding and box spacing from the available width
+ availSpace -= iconChildren.length * (buttonWidth - iconWidth) +
+ (iconChildren.length - 1) * spacing;
+
+ if (this._separator) {
+ const [, , separatorWidth] = this._separator.get_preferred_size();
+ availSpace -= separatorWidth + spacing;
+ }
+ } else {
+ // Subtract icon padding and box spacing from the available height
+ availSpace -= iconChildren.length * (buttonHeight - iconHeight) +
+ (iconChildren.length - 1) * spacing;
+
+ if (this._separator) {
+ const [, , , separatorHeight] = this._separator.get_preferred_size();
+ availSpace -= separatorHeight + spacing;
+ }
+ }
+
+ const maxIconSize = availSpace / iconChildren.length;
+ const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
+ const iconSizes = this._availableIconSizes.map(s => s * scaleFactor);
+
+ let [newIconSize] = this._availableIconSizes;
+ for (let i = 0; i < iconSizes.length; i++) {
+ if (iconSizes[i] <= maxIconSize)
+ newIconSize = this._availableIconSizes[i];
+ }
+
+ if (newIconSize === this.iconSize)
+ return;
+
+ const oldIconSize = this.iconSize;
+ this.iconSize = newIconSize;
+ this.emit('icon-size-changed');
+
+ const scale = oldIconSize / newIconSize;
+ for (let i = 0; i < iconChildren.length; i++) {
+ const {icon} = iconChildren[i].child._delegate;
+
+ // 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, not visible or when initially filling
+ // the dash
+ if (!Main.overview.visible || Main.overview.animationInProgress ||
+ !this._shownInitially)
+ continue;
+
+ const [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);
+
+ icon.icon.ease({
+ width: targetWidth,
+ height: targetHeight,
+ duration: DASH_ANIMATION_TIME,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+ }
+
+ if (this._separator) {
+ const animateProperties = this._isHorizontal
+ ? {height: this.iconSize} : {width: this.iconSize};
+
+ this._separator.ease({
+ ...animateProperties,
+ duration: DASH_ANIMATION_TIME,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+ }
+ }
+
+ _redisplay() {
+ const favorites = AppFavorites.getAppFavorites().getFavoriteMap();
+
+ let running = this._appSystem.get_running();
+ const dockManager = Docking.DockManager.getDefault();
+ const {settings} = dockManager;
+
+ this._scrollView.set({
+ xAlign: Clutter.ActorAlign.FILL,
+ yAlign: Clutter.ActorAlign.FILL,
+ });
+ if (dockManager.settings.dockExtended) {
+ if (!this._isHorizontal) {
+ this._scrollView.yAlign = dockManager.settings.alwaysCenterIcons
+ ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START;
+ } else {
+ this._scrollView.xAlign = dockManager.settings.alwaysCenterIcons
+ ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START;
+ }
+ }
+
+ if (settings.isolateWorkspaces ||
+ settings.isolateMonitors) {
+ // When using isolation, we filter out apps that have no windows in
+ // the current workspace
+ const monitorIndex = this._monitorIndex;
+ running = running.filter(app =>
+ AppIcons.getInterestingWindows(app.get_windows(), monitorIndex).length);
+ }
+
+ const children = this._box.get_children().filter(actor => {
+ return actor.child &&
+ actor.child._delegate &&
+ actor.child._delegate.app;
+ });
+ // Apps currently in the dash
+ let oldApps = children.map(actor => actor.child._delegate.app);
+ // Apps supposed to be in the dash
+ const newApps = [];
+
+ const {showFavorites} = settings;
+ if (showFavorites)
+ newApps.push(...Object.values(favorites));
+
+ if (settings.showRunning) {
+ // We reorder the running apps so that they don't change position on the
+ // dash with every redisplay() call
+
+ // First: add the apps from the oldApps list that are still running
+ oldApps.forEach(oldApp => {
+ const index = running.indexOf(oldApp);
+ if (index > -1) {
+ const [app] = running.splice(index, 1);
+ if (!showFavorites || !(app.get_id() in favorites))
+ newApps.push(app);
+ }
+ });
+
+ // Second: add the new apps
+ running.forEach(app => {
+ if (!showFavorites || !(app.get_id() in favorites))
+ newApps.push(app);
+ });
+ }
+
+ this._signalsHandler.removeWithLabel(Labels.SHOW_MOUNTS);
+ if (dockManager.removables) {
+ this._signalsHandler.addWithLabel(Labels.SHOW_MOUNTS,
+ dockManager.removables, 'changed', this._queueRedisplay.bind(this));
+ dockManager.removables.getApps().forEach(removable => {
+ if (!newApps.includes(removable))
+ newApps.push(removable);
+ });
+ } else {
+ oldApps = oldApps.filter(app => !app.location || app.isTrash);
+ }
+
+ if (dockManager.trash) {
+ const trashApp = dockManager.trash.getApp();
+ if (!newApps.includes(trashApp))
+ newApps.push(trashApp);
+ } else {
+ oldApps = oldApps.filter(app => !app.isTrash);
+ }
+
+ // Temporary remove the separator so that we don't compute to position icons
+ const oldSeparatorPos = this._box.get_children().indexOf(this._separator);
+ if (this._separator)
+ this._box.remove_child(this._separator);
+
+ // Figure out the actual changes to the list of items; we iterate
+ // over both the list of items currently in the dash and the list
+ // of items expected there, and collect additions and removals.
+ // Moves are both an addition and a removal, where the order of
+ // the operations depends on whether we encounter the position
+ // where the item has been added first or the one from where it
+ // was removed.
+ // There is an assumption that only one item is moved at a given
+ // time; when moving several items at once, everything will still
+ // end up at the right position, but there might be additional
+ // additions/removals (e.g. it might remove all the launchers
+ // and add them back in the new order even if a smaller set of
+ // additions and removals is possible).
+ // If above assumptions turns out to be a problem, we might need
+ // to use a more sophisticated algorithm, e.g. Longest Common
+ // Subsequence as used by diff.
+
+ const addedItems = [];
+ const removedActors = [];
+
+ let newIndex = 0;
+ let oldIndex = 0;
+ while (newIndex < newApps.length || oldIndex < oldApps.length) {
+ const oldApp = oldApps.length > oldIndex ? oldApps[oldIndex] : null;
+ const newApp = newApps.length > newIndex ? newApps[newIndex] : null;
+
+ // No change at oldIndex/newIndex
+ if (oldApp === newApp) {
+ oldIndex++;
+ newIndex++;
+ continue;
+ }
+
+ // App removed at oldIndex
+ if (oldApp && !newApps.includes(oldApp)) {
+ removedActors.push(children[oldIndex]);
+ oldIndex++;
+ continue;
+ }
+
+ // App added at newIndex
+ if (newApp && !oldApps.includes(newApp)) {
+ addedItems.push({
+ app: newApp,
+ item: this._createAppItem(newApp),
+ pos: newIndex,
+ });
+ newIndex++;
+ continue;
+ }
+
+ // App moved
+ const nextApp = newApps.length > newIndex + 1
+ ? newApps[newIndex + 1] : null;
+ const insertHere = nextApp && nextApp === oldApp;
+ const alreadyRemoved = removedActors.reduce((result, actor) => {
+ const removedApp = actor.child._delegate.app;
+ return result || removedApp === newApp;
+ }, false);
+
+ if (insertHere || alreadyRemoved) {
+ const newItem = this._createAppItem(newApp);
+ addedItems.push({
+ app: newApp,
+ item: newItem,
+ pos: newIndex + removedActors.length,
+ });
+ newIndex++;
+ } else {
+ removedActors.push(children[oldIndex]);
+ oldIndex++;
+ }
+ }
+
+ for (let i = 0; i < addedItems.length; i++) {
+ this._box.insert_child_at_index(addedItems[i].item,
+ addedItems[i].pos);
+ }
+
+ for (let i = 0; i < removedActors.length; i++) {
+ const item = removedActors[i];
+
+ // Don't animate item removal when the overview is transitioning
+ // or hidden
+ if (!Main.overview.animationInProgress)
+ item.animateOutAndDestroy();
+ else
+ item.destroy();
+ }
+
+ // Update separator
+ const nFavorites = Object.keys(favorites).length;
+ const nIcons = children.length + addedItems.length - removedActors.length;
+ if (nFavorites > 0 && nFavorites < nIcons) {
+ if (!this._separator) {
+ this._separator = new St.Widget({
+ style_class: 'dash-separator',
+ x_align: this._isHorizontal
+ ? Clutter.ActorAlign.FILL : Clutter.ActorAlign.CENTER,
+ y_align: this._isHorizontal
+ ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.FILL,
+ width: this._isHorizontal ? -1 : this.iconSize,
+ height: this._isHorizontal ? this.iconSize : -1,
+ reactive: true,
+ track_hover: true,
+ });
+ this._separator.connect('notify::hover', a => this._ensureItemVisibility(a));
+ }
+ let pos = nFavorites + this._animatingPlaceholdersCount;
+ if (this._dragPlaceholder)
+ pos++;
+ const removedFavorites = removedActors.filter(a =>
+ children.indexOf(a) < oldSeparatorPos);
+ pos += removedFavorites.length;
+ this._box.insert_child_at_index(this._separator, pos);
+ } else if (this._separator) {
+ this._separator.destroy();
+ this._separator = null;
+ }
+
+ this._adjustIconSize();
+
+ // Skip animations on first run when adding the initial set
+ // of items, to avoid all items zooming in at once
+ const animate = this._shownInitially &&
+ !Main.layoutManager._startingUp;
+
+ if (!this._shownInitially)
+ this._shownInitially = true;
+
+ addedItems.forEach(({item}) => item.show(animate));
+
+ // 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 will update the size, and the corresponding number for each icon
+ this._updateNumberOverlay();
+
+ this.updateShowAppsButton();
+ }
+
+ _updateNumberOverlay() {
+ const appIcons = this.getAppIcons();
+ let counter = 1;
+ appIcons.forEach(icon => {
+ if (counter < 10) {
+ icon.setNumberOverlay(counter);
+ counter++;
+ } else if (counter === 10) {
+ icon.setNumberOverlay(0);
+ counter++;
+ } else {
+ // No overlay after 10
+ icon.setNumberOverlay(-1);
+ }
+ icon.updateNumberOverlay();
+ });
+ }
+
+ toggleNumberOverlay(activate) {
+ const appIcons = this.getAppIcons();
+ appIcons.forEach(icon => {
+ icon.toggleNumberOverlay(activate);
+ });
+ }
+
+ _initializeIconSize(maxSize) {
+ const maxAllowed = baseIconSizes[baseIconSizes.length - 1];
+ maxSize = Math.min(maxSize, maxAllowed);
+
+ if (Docking.DockManager.settings.iconSizeFixed) {
+ this._availableIconSizes = [maxSize];
+ } else {
+ this._availableIconSizes = baseIconSizes.filter(val => {
+ return val < maxSize;
+ });
+ this._availableIconSizes.push(maxSize);
+ }
+ }
+
+ setIconSize(maxSize, doNotAnimate) {
+ this._initializeIconSize(maxSize);
+
+ if (doNotAnimate)
+ this._shownInitially = false;
+
+ this._queueRedisplay();
+ }
+
+ /**
+ * Reset the displayed apps icon to maintain the correct order when changing
+ * show favorites/show running settings
+ */
+ resetAppIcons() {
+ const children = this._box.get_children().filter(actor => {
+ return actor.child &&
+ !!actor.child.icon;
+ });
+ for (let i = 0; i < children.length; i++) {
+ const item = children[i];
+ item.destroy();
+ }
+
+ // to avoid ugly animations, just suppress them like when dash is first loaded.
+ this._shownInitially = false;
+ this._redisplay();
+ }
+
+ get showAppsButton() {
+ return this._showAppsIcon.toggleButton;
+ }
+
+ showShowAppsButton() {
+ this._showAppsIcon.visible = true;
+ this._showAppsIcon.show(true);
+ this.updateShowAppsButton();
+ }
+
+ hideShowAppsButton() {
+ this._showAppsIcon.visible = false;
+ }
+
+ setMaxSize(maxWidth, maxHeight) {
+ if (this._maxWidth === maxWidth &&
+ this._maxHeight === maxHeight)
+ return;
+
+ this._maxWidth = maxWidth;
+ this._maxHeight = maxHeight;
+ this._queueRedisplay();
+ }
+
+ updateShowAppsButton() {
+ if (this._showAppsIcon.get_parent() && !this._showAppsIcon.visible)
+ return;
+
+ const {settings} = Docking.DockManager;
+ const notifiedProperties = [];
+ const showAppsContainer = settings.showAppsAlwaysInTheEdge || !settings.dockExtended
+ ? this._dashContainer : this._boxContainer;
+
+ this._signalsHandler.addWithLabel(Labels.FIRST_LAST_CHILD_WORKAROUND,
+ showAppsContainer, 'notify',
+ (_obj, pspec) => notifiedProperties.push(pspec.name));
+
+ if (this._showAppsIcon.get_parent() !== showAppsContainer) {
+ this._showAppsIcon.get_parent()?.remove_child(this._showAppsIcon);
+
+ if (Docking.DockManager.settings.showAppsAtTop)
+ showAppsContainer.insert_child_below(this._showAppsIcon, null);
+ else
+ showAppsContainer.insert_child_above(this._showAppsIcon, null);
+ } else if (settings.showAppsAtTop) {
+ showAppsContainer.set_child_below_sibling(this._showAppsIcon, null);
+ } else {
+ showAppsContainer.set_child_above_sibling(this._showAppsIcon, null);
+ }
+
+ this._signalsHandler.removeWithLabel(Labels.FIRST_LAST_CHILD_WORKAROUND);
+
+ // This is indeed ugly, but we need to ensure that the last and first
+ // visible widgets are re-computed by St, that is buggy because of a
+ // mutter issue that is being fixed:
+ // https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2047
+ if (!notifiedProperties.includes('first-child'))
+ showAppsContainer.notify('first-child');
+ if (!notifiedProperties.includes('last-child'))
+ showAppsContainer.notify('last-child');
+ }
+});
+
+
+/**
+ * This is a copy of the same function in utils.js, but also adjust horizontal scrolling
+ * and perform few further checks on the current value to avoid changing the values when
+ * it would be clamp to the current one in any case.
+ * Return the amount of shift applied
+ *
+ * @param scrollView
+ * @param actor
+ */
+function ensureActorVisibleInScrollView(scrollView, actor) {
+ // access to scrollView.[hv]scroll was deprecated in gnome 46
+ // instead, adjustment can be accessed directly
+ // keep old way for backwards compatibility (gnome <= 45)
+ const vAdjustment = scrollView.vadjustment ?? scrollView.vscroll.adjustment;
+ const hAdjustment = scrollView.hadjustment ?? scrollView.hscroll.adjustment;
+ const {value: vValue0, pageSize: vPageSize, upper: vUpper} = vAdjustment;
+ const {value: hValue0, pageSize: hPageSize, upper: hUpper} = hAdjustment;
+ let [hValue, vValue] = [hValue0, vValue0];
+ let vOffset = 0;
+ let hOffset = 0;
+
+ const fade = scrollView.get_effect('fade');
+ if (fade) {
+ vOffset = fade.fade_margins.top;
+ hOffset = fade.fade_margins.left;
+ }
+
+ const box = actor.get_allocation_box();
+ let {y1} = box, {y2} = box, {x1} = box, {x2} = box;
+
+ let parent = actor.get_parent();
+ while (parent !== scrollView) {
+ if (!parent)
+ throw new Error('Actor not in scroll view');
+
+ const parentBox = parent.get_allocation_box();
+ y1 += parentBox.y1;
+ y2 += parentBox.y1;
+ x1 += parentBox.x1;
+ x2 += parentBox.x1;
+ parent = parent.get_parent();
+ }
+
+ if (y1 < vValue + vOffset)
+ vValue = Math.max(0, y1 - vOffset);
+ else if (vValue < vUpper - vPageSize && y2 > vValue + vPageSize - vOffset)
+ vValue = Math.min(vUpper - vPageSize, y2 + vOffset - vPageSize);
+
+ if (x1 < hValue + hOffset)
+ hValue = Math.max(0, x1 - hOffset);
+ else if (hValue < hUpper - hPageSize && x2 > hValue + hPageSize - hOffset)
+ hValue = Math.min(hUpper - hPageSize, x2 + hOffset - hPageSize);
+
+ if (vValue !== vValue0) {
+ vAdjustment.ease(vValue, {
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ duration: Util.SCROLL_TIME,
+ });
+ }
+
+ if (hValue !== hValue0) {
+ hAdjustment.ease(hValue, {
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ duration: Util.SCROLL_TIME,
+ });
+ }
+
+ return [hValue - hValue0, vValue - vValue0];
+}