830 lines
29 KiB
JavaScript
830 lines
29 KiB
JavaScript
|
/**
|
||
|
* Vertical Workspaces
|
||
|
* dash.js
|
||
|
*
|
||
|
* @author GdH <G-dH@github.com>
|
||
|
* @copyright 2022-2023
|
||
|
* @license GPL-3.0
|
||
|
* modified dash module of https://github.com/RensAlthuis/vertical-overview extension
|
||
|
*/
|
||
|
|
||
|
const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi;
|
||
|
const AppDisplay = imports.ui.appDisplay;
|
||
|
const AppFavorites = imports.ui.appFavorites;
|
||
|
const DND = imports.ui.dnd;
|
||
|
const IconGrid = imports.ui.iconGrid;
|
||
|
const Main = imports.ui.main;
|
||
|
const Overview = imports.ui.overview;
|
||
|
const Dash = imports.ui.dash;
|
||
|
const { DashIcon, DashItemContainer, getAppFromSource, DragPlaceholderItem } = imports.ui.dash;
|
||
|
|
||
|
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||
|
const Util = Me.imports.util;
|
||
|
const _ = Me.imports.settings._;
|
||
|
|
||
|
let verticalOverrides = {};
|
||
|
let _origWorkId;
|
||
|
let _newWorkId;
|
||
|
let _showAppsIconBtnPressId;
|
||
|
|
||
|
// added values to achieve better ability to scale down according the available space
|
||
|
var BaseIconSizes = [16, 24, 32, 40, 44, 48, 56, 64, 72, 80, 96, 112, 128];
|
||
|
|
||
|
const RecentFilesSearchProviderPrefix = Me.imports.recentFilesSearchProvider.prefix;
|
||
|
const WindowSearchProviderPrefix = Me.imports.windowSearchProvider.prefix;
|
||
|
|
||
|
let _overrides;
|
||
|
|
||
|
const DASH_ITEM_LABEL_SHOW_TIME = 150;
|
||
|
|
||
|
let opt;
|
||
|
|
||
|
function update(reset = false) {
|
||
|
if (_overrides) {
|
||
|
_overrides.removeAll();
|
||
|
}
|
||
|
|
||
|
opt = Me.imports.settings.opt;
|
||
|
const dash = Main.overview._overview._controls.layoutManager._dash;
|
||
|
|
||
|
setToHorizontal();
|
||
|
|
||
|
dash.remove_style_class_name("vertical-overview");
|
||
|
dash.remove_style_class_name("vertical-overview-left");
|
||
|
dash.remove_style_class_name("vertical-overview-right");
|
||
|
|
||
|
if (reset) {
|
||
|
_moveDashAppGridIcon(reset);
|
||
|
_connectShowAppsIcon(reset);
|
||
|
_updateSearchWindowsIcon(false);
|
||
|
_updateRecentFilesIcon(false);
|
||
|
dash.visible = true;
|
||
|
dash._background.opacity = 255;
|
||
|
_overrides = null;
|
||
|
opt = null;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
_overrides = new Util.Overrides();
|
||
|
|
||
|
_overrides.addOverride('DashItemContainer', Dash.DashItemContainer.prototype, DashItemContainerOverride);
|
||
|
_overrides.addOverride('DashCommon', Dash.Dash.prototype, DashCommonOverride);
|
||
|
|
||
|
if (opt.DASH_VERTICAL) {
|
||
|
_overrides.addOverride('Dash', Dash.Dash.prototype, DashOverride);
|
||
|
setToVertical();
|
||
|
dash.add_style_class_name("vertical-overview");
|
||
|
|
||
|
if (!_newWorkId) {
|
||
|
_origWorkId = dash._workId;
|
||
|
dash._workId = Main.initializeDeferredWork(dash._box, dash._redisplay.bind(dash));
|
||
|
_newWorkId = dash._workId;
|
||
|
} else {
|
||
|
dash._workId = _newWorkId;
|
||
|
}
|
||
|
} else {
|
||
|
setToHorizontal();
|
||
|
if (_origWorkId)
|
||
|
dash._workId = _origWorkId;
|
||
|
}
|
||
|
|
||
|
_updateSearchWindowsIcon();
|
||
|
_updateRecentFilesIcon();
|
||
|
_moveDashAppGridIcon();
|
||
|
_connectShowAppsIcon();
|
||
|
|
||
|
if (dash._showWindowsIcon && !dash._showWindowsIconClickedId) {
|
||
|
dash._showWindowsIconClickedId = dash._showWindowsIcon.toggleButton.connect('clicked', (a, c) => c && Util.activateSearchProvider(WindowSearchProviderPrefix));
|
||
|
}
|
||
|
if (dash._recentFilesIcon && !dash._recentFilesIconClickedId) {
|
||
|
dash._recentFilesIconClickedId = dash._recentFilesIcon.toggleButton.connect('clicked', (a, c) => c && Util.activateSearchProvider(RecentFilesSearchProviderPrefix));
|
||
|
}
|
||
|
Main.overview.dash._redisplay();
|
||
|
Main.overview._overview._controls.layoutManager._dash.visible = opt.DASH_VISIBLE;
|
||
|
}
|
||
|
|
||
|
function setToVertical() {
|
||
|
let dash = Main.overview._overview._controls.layoutManager._dash;
|
||
|
|
||
|
dash._box.layout_manager.orientation = Clutter.Orientation.VERTICAL;
|
||
|
dash._dashContainer.layout_manager.orientation = Clutter.Orientation.VERTICAL;
|
||
|
dash._dashContainer.y_expand = false;
|
||
|
dash._dashContainer.x_expand = true;
|
||
|
dash.x_align = Clutter.ActorAlign.START;
|
||
|
dash.y_align = Clutter.ActorAlign.CENTER;
|
||
|
|
||
|
let sizerBox = dash._background.get_children()[0];
|
||
|
sizerBox.clear_constraints();
|
||
|
sizerBox.add_constraint(new Clutter.BindConstraint({
|
||
|
source: dash._showAppsIcon.icon,
|
||
|
coordinate: Clutter.BindCoordinate.WIDTH,
|
||
|
}));
|
||
|
sizerBox.add_constraint(new Clutter.BindConstraint({
|
||
|
source: dash._dashContainer,
|
||
|
coordinate: Clutter.BindCoordinate.HEIGHT,
|
||
|
}));
|
||
|
dash._box.remove_all_children();
|
||
|
dash._separator = null;
|
||
|
dash._queueRedisplay();
|
||
|
dash._adjustIconSize();
|
||
|
|
||
|
dash.add_style_class_name(opt.DASH_LEFT ? 'vertical-overview-left' : 'vertical-overview-right');
|
||
|
}
|
||
|
|
||
|
function setToHorizontal() {
|
||
|
let dash = Main.overview._overview._controls.layoutManager._dash;
|
||
|
if (_origWorkId)
|
||
|
dash._workId = _origWorkId; //pretty sure this is a leak, but there no provided way to disconnect these...
|
||
|
dash._box.layout_manager.orientation = Clutter.Orientation.HORIZONTAL;
|
||
|
dash._dashContainer.layout_manager.orientation = Clutter.Orientation.HORIZONTAL;
|
||
|
dash._dashContainer.y_expand = true;
|
||
|
dash._dashContainer.x_expand = false;
|
||
|
dash.x_align = Clutter.ActorAlign.CENTER;
|
||
|
dash.y_align = 0;
|
||
|
|
||
|
let sizerBox = dash._background.get_children()[0];
|
||
|
sizerBox.clear_constraints();
|
||
|
sizerBox.add_constraint(new Clutter.BindConstraint({
|
||
|
source: dash._showAppsIcon.icon,
|
||
|
coordinate: Clutter.BindCoordinate.HEIGHT,
|
||
|
}));
|
||
|
sizerBox.add_constraint(new Clutter.BindConstraint({
|
||
|
source: dash._dashContainer,
|
||
|
coordinate: Clutter.BindCoordinate.WIDTH,
|
||
|
}));
|
||
|
|
||
|
dash._box.remove_all_children();
|
||
|
dash._separator = null;
|
||
|
dash._queueRedisplay();
|
||
|
dash._adjustIconSize();
|
||
|
}
|
||
|
|
||
|
function _moveDashAppGridIcon(reset = false) {
|
||
|
// move dash app grid icon to the front
|
||
|
const dash = Main.overview._overview._controls.layoutManager._dash;
|
||
|
|
||
|
const appIconPosition = opt.get('showAppsIconPosition', true);
|
||
|
dash._showAppsIcon.remove_style_class_name('show-apps-icon-vertical-hide');
|
||
|
dash._showAppsIcon.remove_style_class_name('show-apps-icon-horizontal-hide');
|
||
|
dash._showAppsIcon.opacity = 255;
|
||
|
if (!reset && appIconPosition === 0) // 0 - start
|
||
|
dash._dashContainer.set_child_at_index(dash._showAppsIcon, 0);
|
||
|
if (reset || appIconPosition === 1) { // 1 - end
|
||
|
const index = dash._dashContainer.get_children().length - 1;
|
||
|
dash._dashContainer.set_child_at_index(dash._showAppsIcon, index);
|
||
|
}
|
||
|
if (!reset && appIconPosition === 2) {// 2 - hide
|
||
|
const style = opt.DASH_VERTICAL ? 'show-apps-icon-vertical-hide' : 'show-apps-icon-horizontal-hide';
|
||
|
dash._showAppsIcon.add_style_class_name(style);
|
||
|
// for some reason even if the icon height in vertical mode should be set to 0 by the style, it stays visible in full size returning height 1px
|
||
|
dash._showAppsIcon.opacity = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _connectShowAppsIcon(reset = false) {
|
||
|
if (!reset) {
|
||
|
if (_showAppsIconBtnPressId || Util.dashIsDashToDock()) {
|
||
|
// button is already connected || dash is Dash to Dock
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Main.overview.dash._showAppsIcon.reactive = true;
|
||
|
_showAppsIconBtnPressId = Main.overview.dash._showAppsIcon.connect('button-press-event', (actor, event) => {
|
||
|
const button = event.get_button();
|
||
|
if (button === Clutter.BUTTON_MIDDLE) {
|
||
|
Util.openPreferences();
|
||
|
} else if (button === Clutter.BUTTON_SECONDARY) {
|
||
|
Util.activateSearchProvider(WindowSearchProviderPrefix);
|
||
|
} else {
|
||
|
return Clutter.EVENT_PROPAGATE;
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
if (_showAppsIconBtnPressId) {
|
||
|
Main.overview.dash._showAppsIcon.disconnect(_showAppsIconBtnPressId);
|
||
|
_showAppsIconBtnPressId = 0;
|
||
|
Main.overview.dash._showAppsIcon.reactive = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var DashOverride = {
|
||
|
handleDragOver: function (source, actor, _x, y, _time) {
|
||
|
let app = getAppFromSource(source);
|
||
|
|
||
|
// Don't allow favoriting of transient apps
|
||
|
if (app == null || app.is_window_backed())
|
||
|
return DND.DragMotionResult.NO_DROP;
|
||
|
|
||
|
if (!global.settings.is_writable('favorite-apps'))
|
||
|
return DND.DragMotionResult.NO_DROP;
|
||
|
|
||
|
let favorites = AppFavorites.getAppFavorites().getFavorites();
|
||
|
let numFavorites = favorites.length;
|
||
|
|
||
|
let favPos = favorites.indexOf(app);
|
||
|
|
||
|
let children = this._box.get_children();
|
||
|
let numChildren = children.length;
|
||
|
let boxHeight = this._box.height;
|
||
|
|
||
|
// Keep the placeholder out of the index calculation; assuming that
|
||
|
// the remove target has the same size as "normal" items, we don't
|
||
|
// need to do the same adjustment there.
|
||
|
if (this._dragPlaceholder) {
|
||
|
boxHeight -= this._dragPlaceholder.height;
|
||
|
numChildren--;
|
||
|
}
|
||
|
|
||
|
// Same with the separator
|
||
|
if (this._separator) {
|
||
|
boxHeight -= this._separator.height;
|
||
|
numChildren--;
|
||
|
}
|
||
|
|
||
|
let pos;
|
||
|
if (!this._emptyDropTarget)
|
||
|
pos = Math.floor(y * numChildren / boxHeight);
|
||
|
else
|
||
|
pos = 0; // always insert at the top when dash is empty
|
||
|
|
||
|
// Put the placeholder after the last favorite if we are not
|
||
|
// in the favorites zone
|
||
|
if (pos > numFavorites)
|
||
|
pos = numFavorites;
|
||
|
|
||
|
if (pos !== this._dragPlaceholderPos && this._animatingPlaceholdersCount === 0) {
|
||
|
this._dragPlaceholderPos = pos;
|
||
|
|
||
|
// Don't allow positioning before or after self
|
||
|
if (favPos != -1 && (pos == favPos || pos == favPos + 1)) {
|
||
|
this._clearDragPlaceholder();
|
||
|
return DND.DragMotionResult.CONTINUE;
|
||
|
}
|
||
|
|
||
|
// If the placeholder already exists, we just move
|
||
|
// it, but if we are adding it, expand its size in
|
||
|
// an animation
|
||
|
let fadeIn;
|
||
|
if (this._dragPlaceholder) {
|
||
|
this._dragPlaceholder.destroy();
|
||
|
fadeIn = false;
|
||
|
} else {
|
||
|
fadeIn = true;
|
||
|
}
|
||
|
|
||
|
this._dragPlaceholder = new DragPlaceholderItem();
|
||
|
this._dragPlaceholder.child.set_width(this.iconSize / 2);
|
||
|
this._dragPlaceholder.child.set_height(this.iconSize);
|
||
|
this._box.insert_child_at_index(this._dragPlaceholder,
|
||
|
this._dragPlaceholderPos);
|
||
|
this._dragPlaceholder.show(fadeIn);
|
||
|
}
|
||
|
|
||
|
if (!this._dragPlaceholder)
|
||
|
return DND.DragMotionResult.NO_DROP;
|
||
|
|
||
|
let srcIsFavorite = favPos != -1;
|
||
|
|
||
|
if (srcIsFavorite)
|
||
|
return DND.DragMotionResult.MOVE_DROP;
|
||
|
|
||
|
return DND.DragMotionResult.COPY_DROP;
|
||
|
},
|
||
|
|
||
|
_redisplay: function () {
|
||
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
||
|
|
||
|
let running = this._appSystem.get_running();
|
||
|
|
||
|
let 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
|
||
|
let newApps = [];
|
||
|
|
||
|
for (let id in favorites)
|
||
|
newApps.push(favorites[id]);
|
||
|
|
||
|
for (let i = 0; i < running.length; i++) {
|
||
|
let app = running[i];
|
||
|
if (app.get_id() in favorites)
|
||
|
continue;
|
||
|
newApps.push(app);
|
||
|
}
|
||
|
|
||
|
// 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.
|
||
|
let addedItems = [];
|
||
|
let removedActors = [];
|
||
|
|
||
|
let newIndex = 0;
|
||
|
let oldIndex = 0;
|
||
|
while (newIndex < newApps.length || oldIndex < oldApps.length) {
|
||
|
let oldApp = oldApps.length > oldIndex ? oldApps[oldIndex] : null;
|
||
|
let 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
|
||
|
let nextApp = newApps.length > newIndex + 1
|
||
|
? newApps[newIndex + 1] : null;
|
||
|
let insertHere = nextApp && nextApp == oldApp;
|
||
|
let alreadyRemoved = removedActors.reduce((result, actor) => {
|
||
|
let removedApp = actor.child._delegate.app;
|
||
|
return result || removedApp == newApp;
|
||
|
}, false);
|
||
|
|
||
|
if (insertHere || alreadyRemoved) {
|
||
|
let 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++) {
|
||
|
let item = removedActors[i];
|
||
|
|
||
|
// Don't animate item removal when the overview is transitioning
|
||
|
// or hidden
|
||
|
if (Main.overview.visible && !Main.overview.animationInProgress)
|
||
|
item.animateOutAndDestroy();
|
||
|
else
|
||
|
item.destroy();
|
||
|
}
|
||
|
|
||
|
this._adjustIconSize();
|
||
|
|
||
|
// Skip animations on first run when adding the initial set
|
||
|
// of items, to avoid all items zooming in at once
|
||
|
|
||
|
let animate = this._shownInitially && Main.overview.visible &&
|
||
|
!Main.overview.animationInProgress;
|
||
|
|
||
|
if (!this._shownInitially)
|
||
|
this._shownInitially = true;
|
||
|
|
||
|
for (let i = 0; i < addedItems.length; i++)
|
||
|
addedItems[i].item.show(animate);
|
||
|
|
||
|
// Update separator
|
||
|
const nFavorites = Object.keys(favorites).length;
|
||
|
const nIcons = children.length + addedItems.length - removedActors.length;
|
||
|
if (nFavorites > 0 && nFavorites < nIcons) {
|
||
|
// destroy the horizontal separator if it exists.
|
||
|
// this is incredibly janky, but I can't think of a better way atm.
|
||
|
if (this._separator && this._separator.height !== 1) {
|
||
|
this._separator.destroy();
|
||
|
this._separator = null;
|
||
|
}
|
||
|
|
||
|
if (!this._separator) {
|
||
|
this._separator = new St.Widget({
|
||
|
style_class: 'dash-separator',
|
||
|
x_align: Clutter.ActorAlign.CENTER,
|
||
|
y_align: Clutter.ActorAlign.CENTER,
|
||
|
width: this.iconSize,
|
||
|
height: 1,
|
||
|
});
|
||
|
this._box.add_child(this._separator)
|
||
|
}
|
||
|
|
||
|
//FIXME: separator placement is broken (also in original dash)
|
||
|
let pos = nFavorites;
|
||
|
if (this._dragPlaceholder)
|
||
|
pos++;
|
||
|
this._box.set_child_at_index(this._separator, pos);
|
||
|
} else if (this._separator) {
|
||
|
this._separator.destroy();
|
||
|
this._separator = null;
|
||
|
}
|
||
|
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
|
||
|
// Without it, StBoxLayout may use a stale size cache
|
||
|
this._box.queue_relayout();
|
||
|
},
|
||
|
|
||
|
_createAppItem: function (app) {
|
||
|
let appIcon = new DashIcon(app);
|
||
|
|
||
|
let indicator = appIcon._dot;
|
||
|
indicator.x_align = opt.DASH_LEFT ? Clutter.ActorAlign.START : Clutter.ActorAlign.END;
|
||
|
indicator.y_align = Clutter.ActorAlign.CENTER;
|
||
|
|
||
|
appIcon.connect('menu-state-changed',
|
||
|
(o, opened) => {
|
||
|
this._itemMenuStateChanged(item, opened);
|
||
|
});
|
||
|
|
||
|
let item = new DashItemContainer();
|
||
|
item.setChild(appIcon);
|
||
|
|
||
|
// 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);
|
||
|
|
||
|
return item;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var DashItemContainerOverride = {
|
||
|
// move labels according dash position
|
||
|
showLabel: function() {
|
||
|
if (!this._labelText)
|
||
|
return;
|
||
|
|
||
|
this.label.set_text(this._labelText);
|
||
|
this.label.opacity = 0;
|
||
|
this.label.show();
|
||
|
|
||
|
let [stageX, stageY] = this.get_transformed_position();
|
||
|
|
||
|
const itemWidth = this.allocation.get_width();
|
||
|
const itemHeight = this.allocation.get_height();
|
||
|
|
||
|
const labelWidth = this.label.get_width();
|
||
|
const labelHeight = this.label.get_height();
|
||
|
const xOffset = Math.floor((itemWidth - labelWidth) / 2);
|
||
|
let x = Math.clamp(stageX + xOffset, 0, global.stage.width - labelWidth);
|
||
|
|
||
|
let node = this.label.get_theme_node();
|
||
|
let y;
|
||
|
|
||
|
if (opt.DASH_TOP) {
|
||
|
const yOffset = itemHeight - labelHeight + 3 * node.get_length('-y-offset');
|
||
|
y = stageY + yOffset;
|
||
|
|
||
|
} else if (opt.DASH_BOTTOM) {
|
||
|
const yOffset = node.get_length('-y-offset');
|
||
|
y = stageY - this.label.height - yOffset;
|
||
|
|
||
|
} else if (opt.DASH_RIGHT) {
|
||
|
const yOffset = Math.floor((itemHeight - labelHeight) / 2);
|
||
|
|
||
|
const xOffset = 4;
|
||
|
|
||
|
x = stageX - xOffset - this.label.width;
|
||
|
y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight);
|
||
|
|
||
|
} if (opt.DASH_LEFT) {
|
||
|
const yOffset = Math.floor((itemHeight - labelHeight) / 2);
|
||
|
|
||
|
const xOffset = 4;
|
||
|
|
||
|
x = stageX + this.width + xOffset;
|
||
|
y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight);
|
||
|
}
|
||
|
|
||
|
this.label.set_position(x, y);
|
||
|
this.label.ease({
|
||
|
opacity: 255,
|
||
|
duration: DASH_ITEM_LABEL_SHOW_TIME,
|
||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||
|
});
|
||
|
|
||
|
this.label.set_position(x, y);
|
||
|
this.label.ease({
|
||
|
opacity: 255,
|
||
|
duration: DASH_ITEM_LABEL_SHOW_TIME,
|
||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var DashCommonOverride = {
|
||
|
_adjustIconSize: function () {
|
||
|
// 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)
|
||
|
let iconChildren = this._box.get_children().filter(actor => {
|
||
|
return actor.child &&
|
||
|
actor.child._delegate &&
|
||
|
actor.child._delegate.icon &&
|
||
|
!actor.animatingOut;
|
||
|
});
|
||
|
|
||
|
// add new custom icons into the calculation
|
||
|
if (this._showAppsIcon.visible) {
|
||
|
iconChildren.push(this._showAppsIcon);
|
||
|
}
|
||
|
if (this._showWindowsIcon) {
|
||
|
iconChildren.push(this._showWindowsIcon);
|
||
|
}
|
||
|
if (this._recentFilesIcon) {
|
||
|
iconChildren.push(this._recentFilesIcon);
|
||
|
}
|
||
|
|
||
|
if (!iconChildren.length) return;
|
||
|
|
||
|
if (this._maxWidth === -1 || this._maxHeight === -1)
|
||
|
return;
|
||
|
|
||
|
const dashHorizontal = !opt.DASH_VERTICAL;
|
||
|
|
||
|
const themeNode = this.get_theme_node();
|
||
|
const maxAllocation = new Clutter.ActorBox({
|
||
|
x1: 0,
|
||
|
y1: 0,
|
||
|
x2: dashHorizontal ? this._maxWidth : 42, // not whatever
|
||
|
y2: dashHorizontal ? 42 : this._maxHeight,
|
||
|
});
|
||
|
|
||
|
let maxContent = themeNode.get_content_box(maxAllocation);
|
||
|
|
||
|
let spacing = themeNode.get_length('spacing');
|
||
|
|
||
|
let firstButton = iconChildren[0].child;
|
||
|
let firstIcon = firstButton._delegate.icon;
|
||
|
|
||
|
if (!firstIcon.icon) return;
|
||
|
|
||
|
// Enforce valid spacings during the size request
|
||
|
firstIcon.icon.ensure_style();
|
||
|
const [, , iconWidth, iconHeight] = firstIcon.icon.get_preferred_size();
|
||
|
const [, , buttonWidth, buttonHeight] = firstButton.get_preferred_size();
|
||
|
|
||
|
let availWidth, availHeight, maxIconSize;
|
||
|
if (dashHorizontal) {
|
||
|
availWidth = maxContent.x2 - maxContent.x1;
|
||
|
// Subtract icon padding and box spacing from the available width
|
||
|
availWidth -= iconChildren.length * (buttonWidth - iconWidth) +
|
||
|
(iconChildren.length - 1) * spacing +
|
||
|
2 * this._background.get_theme_node().get_horizontal_padding();
|
||
|
|
||
|
availHeight = this._maxHeight;
|
||
|
availHeight -= this.margin_top + this.margin_bottom;
|
||
|
availHeight -= this._background.get_theme_node().get_vertical_padding();
|
||
|
availHeight -= themeNode.get_vertical_padding();
|
||
|
availHeight -= buttonHeight - iconHeight;
|
||
|
|
||
|
maxIconSize = Math.min(availWidth / iconChildren.length, availHeight, opt.MAX_ICON_SIZE);
|
||
|
} else {
|
||
|
availWidth = this._maxWidth;
|
||
|
availWidth -= this._background.get_theme_node().get_horizontal_padding();
|
||
|
availWidth -= themeNode.get_horizontal_padding();
|
||
|
availWidth -= buttonWidth - iconWidth;
|
||
|
|
||
|
availHeight = maxContent.y2 - maxContent.y1;
|
||
|
availHeight -= iconChildren.length * (buttonHeight - iconHeight) +
|
||
|
(iconChildren.length - 1) * spacing +
|
||
|
2 * this._background.get_theme_node().get_vertical_padding();
|
||
|
|
||
|
maxIconSize = Math.min(availWidth, availHeight / iconChildren.length, opt.MAX_ICON_SIZE);
|
||
|
}
|
||
|
|
||
|
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||
|
let iconSizes = BaseIconSizes.map(s => s * scaleFactor);
|
||
|
|
||
|
let newIconSize = BaseIconSizes[0];
|
||
|
for (let i = 0; i < iconSizes.length; i++) {
|
||
|
if (iconSizes[i] <= maxIconSize)
|
||
|
newIconSize = BaseIconSizes[i];
|
||
|
}
|
||
|
|
||
|
/*if (newIconSize == this.iconSize)
|
||
|
return;*/
|
||
|
|
||
|
let oldIconSize = this.iconSize;
|
||
|
this.iconSize = newIconSize;
|
||
|
this.emit('icon-size-changed');
|
||
|
|
||
|
let scale = oldIconSize / newIconSize;
|
||
|
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, not visible or when initially filling
|
||
|
// the dash
|
||
|
if (!Main.overview.visible || 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);
|
||
|
|
||
|
icon.icon.ease({
|
||
|
width: targetWidth,
|
||
|
height: targetHeight,
|
||
|
duration: Dash.DASH_ANIMATION_TIME,
|
||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (this._separator) {
|
||
|
this._separator.ease({
|
||
|
width: dashHorizontal ? 1 : this.iconSize,
|
||
|
height: dashHorizontal ? this.iconSize : 1,
|
||
|
duration: Dash.DASH_ANIMATION_TIME,
|
||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
}
|
||
|
|
||
|
function _updateSearchWindowsIcon(show = opt.SHOW_WINDOWS_ICON) {
|
||
|
|
||
|
const dash = Main.overview._overview._controls.layoutManager._dash;
|
||
|
const dashContainer = dash._dashContainer;
|
||
|
|
||
|
if (dash._showWindowsIcon) {
|
||
|
dashContainer.remove_child(dash._showWindowsIcon);
|
||
|
dash._showWindowsIconClickedId && dash._showWindowsIcon.toggleButton.disconnect(dash._showWindowsIconClickedId);
|
||
|
dash._showWindowsIconClickedId = undefined;
|
||
|
dash._showWindowsIcon && dash._showWindowsIcon.destroy();
|
||
|
dash._showWindowsIcon = undefined;
|
||
|
}
|
||
|
|
||
|
if (!show || !opt.WINDOW_SEARCH_PROVIDER_ENABLED) return;
|
||
|
|
||
|
if (!dash._showWindowsIcon) {
|
||
|
dash._showWindowsIcon = new ShowWindowsIcon();
|
||
|
dash._showWindowsIcon.show(false);
|
||
|
dashContainer.add_child(dash._showWindowsIcon);
|
||
|
dash._hookUpLabel(dash._showWindowsIcon);
|
||
|
}
|
||
|
|
||
|
dash._showWindowsIcon.icon.setIconSize(opt.MAX_ICON_SIZE);
|
||
|
if (opt.SHOW_WINDOWS_ICON === 1) {
|
||
|
dashContainer.set_child_at_index(dash._showWindowsIcon, 0);
|
||
|
} else if (opt.SHOW_WINDOWS_ICON === 2) {
|
||
|
index = dashContainer.get_children().length - 1;
|
||
|
dashContainer.set_child_at_index(dash._showWindowsIcon, index);
|
||
|
}
|
||
|
|
||
|
Main.overview._overview._controls.layoutManager._dash._adjustIconSize();
|
||
|
}
|
||
|
|
||
|
var ShowWindowsIcon = GObject.registerClass(
|
||
|
class ShowWindowsIcon extends Dash.DashItemContainer {
|
||
|
_init() {
|
||
|
super._init();
|
||
|
|
||
|
this._labelText = _('Search Open Windows (Hotkey: Space)');
|
||
|
this.toggleButton = new St.Button({
|
||
|
style_class: 'show-apps',
|
||
|
track_hover: true,
|
||
|
can_focus: true,
|
||
|
toggle_mode: false,
|
||
|
});
|
||
|
|
||
|
this._iconActor = null;
|
||
|
this.icon = new IconGrid.BaseIcon(this.labelText, {
|
||
|
setSizeManually: true,
|
||
|
showLabel: false,
|
||
|
createIcon: this._createIcon.bind(this),
|
||
|
});
|
||
|
this.icon.y_align = Clutter.ActorAlign.CENTER;
|
||
|
|
||
|
this.toggleButton.add_actor(this.icon);
|
||
|
this.toggleButton._delegate = this;
|
||
|
|
||
|
this.setChild(this.toggleButton);
|
||
|
}
|
||
|
|
||
|
_createIcon(size) {
|
||
|
this._iconActor = new St.Icon({
|
||
|
icon_name: 'focus-windows-symbolic',
|
||
|
icon_size: size,
|
||
|
style_class: 'show-apps-icon',
|
||
|
track_hover: true,
|
||
|
});
|
||
|
return this._iconActor;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function _updateRecentFilesIcon(show = opt.SHOW_RECENT_FILES_ICON) {
|
||
|
|
||
|
const dash = Main.overview._overview._controls.layoutManager._dash;
|
||
|
const dashContainer = dash._dashContainer;
|
||
|
|
||
|
if (dash._recentFilesIcon) {
|
||
|
dashContainer.remove_child(dash._recentFilesIcon);
|
||
|
dash._recentFilesIconClickedId && dash._recentFilesIcon.toggleButton.disconnect(dash._recentFilesIconClickedId);
|
||
|
dash._recentFilesIconClickedId = undefined;
|
||
|
dash._recentFilesIcon && dash._recentFilesIcon.destroy();
|
||
|
dash._recentFilesIcon = undefined;
|
||
|
}
|
||
|
|
||
|
if (!show || !opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED) return;
|
||
|
|
||
|
if (!dash._recentFilesIcon) {
|
||
|
dash._recentFilesIcon = new ShowRecentFilesIcon();
|
||
|
dash._recentFilesIcon.show(false);
|
||
|
dashContainer.add_child(dash._recentFilesIcon);
|
||
|
dash._hookUpLabel(dash._recentFilesIcon);
|
||
|
}
|
||
|
|
||
|
dash._recentFilesIcon.icon.setIconSize(opt.MAX_ICON_SIZE);
|
||
|
if (opt.SHOW_RECENT_FILES_ICON === 1) {
|
||
|
dashContainer.set_child_at_index(dash._recentFilesIcon, 0);
|
||
|
} else if (opt.SHOW_RECENT_FILES_ICON === 2) {
|
||
|
index = dashContainer.get_children().length - 1;
|
||
|
dashContainer.set_child_at_index(dash._recentFilesIcon, index);
|
||
|
}
|
||
|
|
||
|
Main.overview._overview._controls.layoutManager._dash._adjustIconSize();
|
||
|
}
|
||
|
|
||
|
var ShowRecentFilesIcon = GObject.registerClass(
|
||
|
class ShowRecentFilesIcon extends Dash.DashItemContainer {
|
||
|
_init() {
|
||
|
super._init();
|
||
|
|
||
|
this._labelText = _('Search Recent Files (Hotkey: Ctrl + Space)');
|
||
|
this.toggleButton = new St.Button({
|
||
|
style_class: 'show-apps',
|
||
|
track_hover: true,
|
||
|
can_focus: true,
|
||
|
toggle_mode: false,
|
||
|
});
|
||
|
|
||
|
this._iconActor = null;
|
||
|
this.icon = new IconGrid.BaseIcon(this.labelText, {
|
||
|
setSizeManually: true,
|
||
|
showLabel: false,
|
||
|
createIcon: this._createIcon.bind(this),
|
||
|
});
|
||
|
this.icon.y_align = Clutter.ActorAlign.CENTER;
|
||
|
|
||
|
this.toggleButton.add_actor(this.icon);
|
||
|
this.toggleButton._delegate = this;
|
||
|
|
||
|
this.setChild(this.toggleButton);
|
||
|
}
|
||
|
|
||
|
_createIcon(size) {
|
||
|
this._iconActor = new St.Icon({
|
||
|
icon_name: 'document-open-recent-symbolic',
|
||
|
icon_size: size,
|
||
|
style_class: 'show-apps-icon',
|
||
|
track_hover: true,
|
||
|
});
|
||
|
return this._iconActor;
|
||
|
}
|
||
|
});
|