2011-09-28 09:16:26 -04:00
|
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
|
/* exported AppDisplay, AppSearchProvider */
|
2008-11-20 19:53:11 -05:00
|
|
|
|
|
2022-01-01 13:12:19 -05:00
|
|
|
|
const {
|
|
|
|
|
Clutter, Gio, GLib, GObject, Graphene, Pango, Shell, St,
|
|
|
|
|
} = imports.gi;
|
2008-11-20 19:53:11 -05:00
|
|
|
|
|
2009-10-15 19:28:29 -04:00
|
|
|
|
const AppFavorites = imports.ui.appFavorites;
|
2021-08-11 20:02:12 -04:00
|
|
|
|
const { AppMenu } = imports.ui.appMenu;
|
2021-01-23 14:44:11 -05:00
|
|
|
|
const BoxPointer = imports.ui.boxpointer;
|
2009-06-30 16:35:39 -04:00
|
|
|
|
const DND = imports.ui.dnd;
|
2014-06-11 07:13:12 -04:00
|
|
|
|
const GrabHelper = imports.ui.grabHelper;
|
2010-07-20 22:22:19 -04:00
|
|
|
|
const IconGrid = imports.ui.iconGrid;
|
2020-06-04 00:37:48 -04:00
|
|
|
|
const Layout = imports.ui.layout;
|
2009-07-31 17:20:26 -04:00
|
|
|
|
const Main = imports.ui.main;
|
2019-01-22 06:10:51 -05:00
|
|
|
|
const PageIndicators = imports.ui.pageIndicators;
|
2019-04-24 05:37:10 -04:00
|
|
|
|
const ParentalControlsManager = imports.misc.parentalControlsManager;
|
2010-05-20 11:18:46 -04:00
|
|
|
|
const PopupMenu = imports.ui.popupMenu;
|
2017-08-21 09:20:25 -04:00
|
|
|
|
const Search = imports.ui.search;
|
2019-06-30 08:15:37 -04:00
|
|
|
|
const SwipeTracker = imports.ui.swipeTracker;
|
2010-06-22 12:39:14 -04:00
|
|
|
|
const Params = imports.misc.params;
|
2017-08-21 09:20:25 -04:00
|
|
|
|
const SystemActions = imports.misc.systemActions;
|
2009-04-01 15:51:17 -04:00
|
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
|
var MENU_POPUP_TIMEOUT = 600;
|
2020-09-24 10:59:35 -04:00
|
|
|
|
var POPDOWN_DIALOG_TIMEOUT = 500;
|
2009-04-23 10:41:24 -04:00
|
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
|
var FOLDER_SUBICON_FRACTION = .4;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2019-08-01 19:13:10 -04:00
|
|
|
|
var VIEWS_SWITCH_TIME = 400;
|
|
|
|
|
var VIEWS_SWITCH_ANIMATION_DELAY = 100;
|
2017-07-18 13:23:12 -04:00
|
|
|
|
|
2020-05-09 10:26:37 -04:00
|
|
|
|
var SCROLL_TIMEOUT_TIME = 150;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2019-06-29 13:09:32 -04:00
|
|
|
|
var APP_ICON_SCALE_IN_TIME = 500;
|
|
|
|
|
var APP_ICON_SCALE_IN_DELAY = 700;
|
|
|
|
|
|
2020-11-13 12:33:32 -05:00
|
|
|
|
var APP_ICON_TITLE_EXPAND_TIME = 200;
|
|
|
|
|
var APP_ICON_TITLE_COLLAPSE_TIME = 100;
|
2020-10-21 13:33:23 -04:00
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
|
const FOLDER_DIALOG_ANIMATION_TIME = 200;
|
|
|
|
|
|
2021-02-03 06:46:07 -05:00
|
|
|
|
const PAGE_PREVIEW_ANIMATION_TIME = 150;
|
|
|
|
|
const PAGE_INDICATOR_FADE_TIME = 200;
|
2022-06-10 17:30:26 -04:00
|
|
|
|
const PAGE_PREVIEW_RATIO = 0.20;
|
2021-02-03 06:46:07 -05:00
|
|
|
|
|
2022-08-09 18:39:41 -04:00
|
|
|
|
const OVERSHOOT_THRESHOLD = 20;
|
2022-08-20 17:33:31 -04:00
|
|
|
|
const OVERSHOOT_TIMEOUT = 1000;
|
2019-11-23 09:06:14 -05:00
|
|
|
|
|
2020-06-22 17:22:13 -04:00
|
|
|
|
const DELAYED_MOVE_TIMEOUT = 200;
|
|
|
|
|
|
2020-09-24 09:17:53 -04:00
|
|
|
|
const DIALOG_SHADE_NORMAL = Clutter.Color.from_pixel(0x000000cc);
|
|
|
|
|
const DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000055);
|
|
|
|
|
|
2022-03-02 08:33:11 -05:00
|
|
|
|
const DEFAULT_FOLDERS = {
|
|
|
|
|
'Utilities': {
|
|
|
|
|
name: 'X-GNOME-Utilities.directory',
|
|
|
|
|
categories: ['X-GNOME-Utilities'],
|
|
|
|
|
apps: [
|
|
|
|
|
'gnome-abrt.desktop',
|
|
|
|
|
'gnome-system-log.desktop',
|
|
|
|
|
'nm-connection-editor.desktop',
|
|
|
|
|
'org.gnome.baobab.desktop',
|
|
|
|
|
'org.gnome.Connections.desktop',
|
|
|
|
|
'org.gnome.DejaDup.desktop',
|
|
|
|
|
'org.gnome.Dictionary.desktop',
|
|
|
|
|
'org.gnome.DiskUtility.desktop',
|
|
|
|
|
'org.gnome.eog.desktop',
|
|
|
|
|
'org.gnome.Evince.desktop',
|
|
|
|
|
'org.gnome.FileRoller.desktop',
|
|
|
|
|
'org.gnome.fonts.desktop',
|
|
|
|
|
'org.gnome.seahorse.Application.desktop',
|
|
|
|
|
'org.gnome.tweaks.desktop',
|
|
|
|
|
'org.gnome.Usage.desktop',
|
|
|
|
|
'vinagre.desktop',
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
'YaST': {
|
|
|
|
|
name: 'suse-yast.directory',
|
2022-03-09 21:13:40 -05:00
|
|
|
|
categories: ['X-SuSE-YaST'],
|
2022-03-02 08:33:11 -05:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2014-02-14 08:00:17 -05:00
|
|
|
|
function _getCategories(info) {
|
|
|
|
|
let categoriesStr = info.get_categories();
|
|
|
|
|
if (!categoriesStr)
|
|
|
|
|
return [];
|
|
|
|
|
return categoriesStr.split(';');
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-28 12:06:11 -05:00
|
|
|
|
function _listsIntersect(a, b) {
|
2019-08-19 20:51:42 -04:00
|
|
|
|
for (let itemA of a) {
|
2018-07-14 16:56:22 -04:00
|
|
|
|
if (b.includes(itemA))
|
2014-01-28 12:06:11 -05:00
|
|
|
|
return true;
|
2019-08-19 20:51:42 -04:00
|
|
|
|
}
|
2014-01-28 12:06:11 -05:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 22:24:25 -05:00
|
|
|
|
function _getFolderName(folder) {
|
|
|
|
|
let name = folder.get_string('name');
|
|
|
|
|
|
|
|
|
|
if (folder.get_boolean('translate')) {
|
2020-02-27 22:36:14 -05:00
|
|
|
|
let translated = Shell.util_get_translated_folder_name(name);
|
|
|
|
|
if (translated !== null)
|
|
|
|
|
return translated;
|
2013-01-31 09:16:25 -05:00
|
|
|
|
}
|
2013-12-15 22:24:25 -05:00
|
|
|
|
|
|
|
|
|
return name;
|
2014-01-13 11:39:30 -05:00
|
|
|
|
}
|
2013-01-31 09:16:25 -05:00
|
|
|
|
|
2019-06-28 18:47:32 -04:00
|
|
|
|
function _getViewFromIcon(icon) {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
for (let parent = icon.get_parent(); parent; parent = parent.get_parent()) {
|
|
|
|
|
if (parent instanceof BaseAppView)
|
|
|
|
|
return parent;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
function _findBestFolderName(apps) {
|
|
|
|
|
let appInfos = apps.map(app => app.get_app_info());
|
|
|
|
|
|
|
|
|
|
let categoryCounter = {};
|
|
|
|
|
let commonCategories = [];
|
|
|
|
|
|
|
|
|
|
appInfos.reduce((categories, appInfo) => {
|
2019-10-31 19:19:38 -04:00
|
|
|
|
for (let category of _getCategories(appInfo)) {
|
2019-07-16 16:51:25 -04:00
|
|
|
|
if (!(category in categoryCounter))
|
|
|
|
|
categoryCounter[category] = 0;
|
|
|
|
|
|
|
|
|
|
categoryCounter[category] += 1;
|
|
|
|
|
|
|
|
|
|
// If a category is present in all apps, its counter will
|
|
|
|
|
// reach appInfos.length
|
|
|
|
|
if (category.length > 0 &&
|
2019-08-19 20:51:42 -04:00
|
|
|
|
categoryCounter[category] == appInfos.length)
|
2019-07-16 16:51:25 -04:00
|
|
|
|
categories.push(category);
|
|
|
|
|
}
|
|
|
|
|
return categories;
|
|
|
|
|
}, commonCategories);
|
|
|
|
|
|
|
|
|
|
for (let category of commonCategories) {
|
2022-02-07 09:14:06 -05:00
|
|
|
|
const directory = `${category}.directory`;
|
2020-04-12 11:15:48 -04:00
|
|
|
|
const translated = Shell.util_get_translated_folder_name(directory);
|
2020-02-27 22:36:14 -05:00
|
|
|
|
if (translated !== null)
|
|
|
|
|
return translated;
|
2019-07-16 16:51:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 14:25:05 -04:00
|
|
|
|
const AppGrid = GObject.registerClass({
|
|
|
|
|
Properties: {
|
|
|
|
|
'indicators-padding': GObject.ParamSpec.boxed('indicators-padding',
|
|
|
|
|
'Indicators padding', 'Indicators padding',
|
|
|
|
|
GObject.ParamFlags.READWRITE,
|
|
|
|
|
Clutter.Margin.$gtype),
|
|
|
|
|
},
|
|
|
|
|
}, class AppGrid extends IconGrid.IconGrid {
|
|
|
|
|
_init(layoutParams) {
|
|
|
|
|
super._init(layoutParams);
|
|
|
|
|
|
|
|
|
|
this._indicatorsPadding = new Clutter.Margin();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updatePadding() {
|
|
|
|
|
const node = this.get_theme_node();
|
|
|
|
|
const {rowSpacing, columnSpacing} = this.layoutManager;
|
|
|
|
|
|
|
|
|
|
const padding = this._indicatorsPadding.copy();
|
|
|
|
|
padding.left += rowSpacing;
|
|
|
|
|
padding.right += rowSpacing;
|
|
|
|
|
padding.top += columnSpacing;
|
|
|
|
|
padding.bottom += columnSpacing;
|
|
|
|
|
['top', 'right', 'bottom', 'left'].forEach(side => {
|
|
|
|
|
padding[side] += node.get_length(`page-padding-${side}`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.layoutManager.pagePadding = padding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_style_changed() {
|
|
|
|
|
super.vfunc_style_changed();
|
|
|
|
|
this._updatePadding();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get indicatorsPadding() {
|
|
|
|
|
return this._indicatorsPadding;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set indicatorsPadding(v) {
|
|
|
|
|
if (this._indicatorsPadding === v)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._indicatorsPadding = v ? v : new Clutter.Margin();
|
|
|
|
|
this._updatePadding();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2022-06-10 17:30:26 -04:00
|
|
|
|
const BaseAppViewGridLayout = GObject.registerClass(
|
|
|
|
|
class BaseAppViewGridLayout extends Clutter.BinLayout {
|
|
|
|
|
_init(grid, scrollView, nextPageIndicator, nextPageArrow,
|
|
|
|
|
previousPageIndicator, previousPageArrow) {
|
|
|
|
|
if (!(grid instanceof AppGrid))
|
|
|
|
|
throw new Error('Grid must be an AppGrid subclass');
|
|
|
|
|
|
|
|
|
|
super._init();
|
|
|
|
|
|
|
|
|
|
this._grid = grid;
|
|
|
|
|
this._scrollView = scrollView;
|
|
|
|
|
this._previousPageIndicator = previousPageIndicator;
|
|
|
|
|
this._previousPageArrow = previousPageArrow;
|
|
|
|
|
this._nextPageIndicator = nextPageIndicator;
|
|
|
|
|
this._nextPageArrow = nextPageArrow;
|
|
|
|
|
|
|
|
|
|
grid.connect('pages-changed', () => this._syncPageIndicatorsVisibility());
|
|
|
|
|
|
|
|
|
|
this._pageIndicatorsAdjustment = new St.Adjustment({
|
|
|
|
|
lower: 0,
|
|
|
|
|
upper: 1,
|
|
|
|
|
});
|
|
|
|
|
this._pageIndicatorsAdjustment.connect(
|
|
|
|
|
'notify::value', () => this._syncPageIndicators());
|
|
|
|
|
|
|
|
|
|
this._showIndicators = false;
|
|
|
|
|
this._currentPage = 0;
|
2022-06-23 15:41:42 -04:00
|
|
|
|
this._pageWidth = 0;
|
2022-06-10 17:30:26 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_getIndicatorsWidth(box) {
|
|
|
|
|
const [width, height] = box.get_size();
|
|
|
|
|
const arrows = [
|
|
|
|
|
this._nextPageArrow,
|
|
|
|
|
this._previousPageArrow,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const minArrowsWidth = arrows.reduce(
|
|
|
|
|
(previousWidth, accessory) => {
|
|
|
|
|
const [min] = accessory.get_preferred_width(height);
|
|
|
|
|
return Math.max(previousWidth, min);
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
const idealIndicatorWidth = (width * PAGE_PREVIEW_RATIO) / 2;
|
|
|
|
|
|
|
|
|
|
return Math.max(idealIndicatorWidth, minArrowsWidth);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_syncPageIndicatorsVisibility(animate = true) {
|
|
|
|
|
const previousIndicatorsVisible =
|
|
|
|
|
this._currentPage > 0 && this._showIndicators;
|
|
|
|
|
|
2022-06-14 16:40:30 -04:00
|
|
|
|
if (previousIndicatorsVisible)
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._previousPageIndicator.show();
|
2022-06-14 16:40:30 -04:00
|
|
|
|
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._previousPageIndicator.ease({
|
|
|
|
|
opacity: previousIndicatorsVisible ? 255 : 0,
|
|
|
|
|
duration: animate ? PAGE_INDICATOR_FADE_TIME : 0,
|
|
|
|
|
onComplete: () => {
|
2022-06-14 16:40:30 -04:00
|
|
|
|
if (!previousIndicatorsVisible)
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._previousPageIndicator.hide();
|
|
|
|
|
},
|
|
|
|
|
});
|
2022-06-14 16:40:30 -04:00
|
|
|
|
|
|
|
|
|
const previousArrowVisible =
|
|
|
|
|
this._currentPage > 0 && !previousIndicatorsVisible;
|
|
|
|
|
|
|
|
|
|
if (previousArrowVisible)
|
|
|
|
|
this._previousPageArrow.show();
|
|
|
|
|
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._previousPageArrow.ease({
|
2022-06-14 16:40:30 -04:00
|
|
|
|
opacity: previousArrowVisible ? 255 : 0,
|
2022-06-10 17:30:26 -04:00
|
|
|
|
duration: animate ? PAGE_INDICATOR_FADE_TIME : 0,
|
2022-06-14 16:40:30 -04:00
|
|
|
|
onComplete: () => {
|
|
|
|
|
if (!previousArrowVisible)
|
|
|
|
|
this._previousPageArrow.hide();
|
|
|
|
|
},
|
2022-06-10 17:30:26 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Always show the next page indicator to allow dropping
|
|
|
|
|
// icons into new pages
|
|
|
|
|
const {allowIncompletePages, nPages} = this._grid.layoutManager;
|
|
|
|
|
const nextIndicatorsVisible = this._showIndicators &&
|
|
|
|
|
(allowIncompletePages ? true : this._currentPage < nPages - 1);
|
2022-06-14 16:40:30 -04:00
|
|
|
|
|
|
|
|
|
if (nextIndicatorsVisible)
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._nextPageIndicator.show();
|
2022-06-14 16:40:30 -04:00
|
|
|
|
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._nextPageIndicator.ease({
|
|
|
|
|
opacity: nextIndicatorsVisible ? 255 : 0,
|
|
|
|
|
duration: animate ? PAGE_INDICATOR_FADE_TIME : 0,
|
|
|
|
|
onComplete: () => {
|
2022-06-14 16:40:30 -04:00
|
|
|
|
if (!nextIndicatorsVisible)
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._nextPageIndicator.hide();
|
|
|
|
|
},
|
|
|
|
|
});
|
2022-06-14 16:40:30 -04:00
|
|
|
|
|
|
|
|
|
const nextArrowVisible =
|
|
|
|
|
this._currentPage < nPages - 1 &&
|
|
|
|
|
!nextIndicatorsVisible;
|
|
|
|
|
|
|
|
|
|
if (nextArrowVisible)
|
|
|
|
|
this._nextPageArrow.show();
|
|
|
|
|
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._nextPageArrow.ease({
|
2022-06-14 16:40:30 -04:00
|
|
|
|
opacity: nextArrowVisible ? 255 : 0,
|
2022-06-10 17:30:26 -04:00
|
|
|
|
duration: animate ? PAGE_INDICATOR_FADE_TIME : 0,
|
2022-06-14 16:40:30 -04:00
|
|
|
|
onComplete: () => {
|
|
|
|
|
if (!nextArrowVisible)
|
|
|
|
|
this._nextPageArrow.hide();
|
|
|
|
|
},
|
2022-06-10 17:30:26 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-23 15:41:42 -04:00
|
|
|
|
_getEndIcon(icons) {
|
|
|
|
|
const {columnsPerPage} = this._grid.layoutManager;
|
|
|
|
|
const index = Math.min(icons.length, columnsPerPage);
|
|
|
|
|
return icons[Math.max(index - 1, 0)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_translatePreviousPageIcons(value, ltr) {
|
|
|
|
|
if (this._currentPage === 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const previousPage = this._currentPage - 1;
|
|
|
|
|
const icons = this._grid.getItemsAtPage(previousPage).filter(i => i.visible);
|
|
|
|
|
if (icons.length === 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const {left, right} = this._grid.indicatorsPadding;
|
|
|
|
|
const {columnSpacing} = this._grid.layoutManager;
|
|
|
|
|
const endIcon = this._getEndIcon(icons);
|
|
|
|
|
let iconOffset;
|
|
|
|
|
|
|
|
|
|
if (ltr) {
|
|
|
|
|
const currentPageOffset = this._pageWidth * this._currentPage;
|
|
|
|
|
iconOffset = currentPageOffset - endIcon.allocation.x2 + left - columnSpacing;
|
|
|
|
|
} else {
|
|
|
|
|
const rtlPage = this._grid.nPages - previousPage - 1;
|
|
|
|
|
const pageOffset = this._pageWidth * rtlPage;
|
|
|
|
|
iconOffset = pageOffset - endIcon.allocation.x1 - right + columnSpacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const icon of icons)
|
|
|
|
|
icon.translationX = iconOffset * value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_translateNextPageIcons(value, ltr) {
|
|
|
|
|
if (this._currentPage >= this._grid.nPages - 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const nextPage = this._currentPage + 1;
|
|
|
|
|
const icons = this._grid.getItemsAtPage(nextPage).filter(i => i.visible);
|
|
|
|
|
if (icons.length === 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const {left, right} = this._grid.indicatorsPadding;
|
|
|
|
|
const {columnSpacing} = this._grid.layoutManager;
|
|
|
|
|
let iconOffset;
|
|
|
|
|
|
|
|
|
|
if (ltr) {
|
|
|
|
|
const pageOffset = this._pageWidth * nextPage;
|
|
|
|
|
iconOffset = pageOffset - icons[0].allocation.x1 - right + columnSpacing;
|
|
|
|
|
} else {
|
|
|
|
|
const rtlPage = this._grid.nPages - this._currentPage - 1;
|
|
|
|
|
const currentPageOffset = this._pageWidth * rtlPage;
|
|
|
|
|
iconOffset = currentPageOffset - icons[0].allocation.x2 + left - columnSpacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const icon of icons)
|
|
|
|
|
icon.translationX = iconOffset * value;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 17:30:26 -04:00
|
|
|
|
_syncPageIndicators() {
|
|
|
|
|
if (!this._container)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const {value} = this._pageIndicatorsAdjustment;
|
|
|
|
|
|
|
|
|
|
const ltr = this._container.get_text_direction() !== Clutter.TextDirection.RTL;
|
|
|
|
|
const {left, right} = this._grid.indicatorsPadding;
|
2022-06-14 16:40:30 -04:00
|
|
|
|
const leftIndicatorOffset = -left * (1 - value);
|
|
|
|
|
const rightIndicatorOffset = right * (1 - value);
|
|
|
|
|
|
|
|
|
|
this._previousPageIndicator.translationX =
|
|
|
|
|
ltr ? leftIndicatorOffset : rightIndicatorOffset;
|
|
|
|
|
this._nextPageIndicator.translationX =
|
|
|
|
|
ltr ? rightIndicatorOffset : leftIndicatorOffset;
|
2022-06-10 17:30:26 -04:00
|
|
|
|
|
|
|
|
|
const leftArrowOffset = -left * value;
|
|
|
|
|
const rightArrowOffset = right * value;
|
|
|
|
|
|
|
|
|
|
this._previousPageArrow.translationX =
|
|
|
|
|
ltr ? leftArrowOffset : rightArrowOffset;
|
|
|
|
|
this._nextPageArrow.translationX =
|
|
|
|
|
ltr ? rightArrowOffset : leftArrowOffset;
|
2022-06-23 15:41:42 -04:00
|
|
|
|
|
|
|
|
|
// Page icons
|
|
|
|
|
this._translatePreviousPageIcons(value, ltr);
|
|
|
|
|
this._translateNextPageIcons(value, ltr);
|
|
|
|
|
|
|
|
|
|
if (this._grid.nPages > 0) {
|
|
|
|
|
this._grid.getItemsAtPage(this._currentPage).forEach(icon => {
|
|
|
|
|
icon.translationX = 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-06-10 17:30:26 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_set_container(container) {
|
|
|
|
|
this._container = container;
|
|
|
|
|
this._pageIndicatorsAdjustment.actor = container;
|
|
|
|
|
this._syncPageIndicators();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_allocate(container, box) {
|
|
|
|
|
const ltr = container.get_text_direction() !== Clutter.TextDirection.RTL;
|
|
|
|
|
const indicatorsWidth = this._getIndicatorsWidth(box);
|
|
|
|
|
|
|
|
|
|
this._grid.indicatorsPadding = new Clutter.Margin({
|
|
|
|
|
left: indicatorsWidth,
|
|
|
|
|
right: indicatorsWidth,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._scrollView.allocate(box);
|
|
|
|
|
|
|
|
|
|
const leftBox = box.copy();
|
|
|
|
|
leftBox.x2 = leftBox.x1 + indicatorsWidth;
|
|
|
|
|
|
|
|
|
|
const rightBox = box.copy();
|
|
|
|
|
rightBox.x1 = rightBox.x2 - indicatorsWidth;
|
|
|
|
|
|
|
|
|
|
this._previousPageIndicator.allocate(ltr ? leftBox : rightBox);
|
2022-08-09 13:26:24 -04:00
|
|
|
|
this._previousPageArrow.allocate_align_fill(ltr ? leftBox : rightBox,
|
|
|
|
|
0.5, 0.5, false, false);
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._nextPageIndicator.allocate(ltr ? rightBox : leftBox);
|
2022-08-09 13:26:24 -04:00
|
|
|
|
this._nextPageArrow.allocate_align_fill(ltr ? rightBox : leftBox,
|
|
|
|
|
0.5, 0.5, false, false);
|
2022-06-23 15:41:42 -04:00
|
|
|
|
|
|
|
|
|
this._pageWidth = box.get_width();
|
2022-06-10 17:30:26 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goToPage(page, animate = true) {
|
|
|
|
|
if (this._currentPage === page)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._currentPage = page;
|
|
|
|
|
this._syncPageIndicatorsVisibility(animate);
|
|
|
|
|
this._syncPageIndicators();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showPageIndicators() {
|
|
|
|
|
if (this._showIndicators)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._pageIndicatorsAdjustment.ease(1, {
|
|
|
|
|
duration: PAGE_PREVIEW_ANIMATION_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._grid.clipToView = false;
|
|
|
|
|
this._showIndicators = true;
|
|
|
|
|
this._syncPageIndicatorsVisibility();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hidePageIndicators() {
|
|
|
|
|
if (!this._showIndicators)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._pageIndicatorsAdjustment.ease(0, {
|
|
|
|
|
duration: PAGE_PREVIEW_ANIMATION_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
|
|
|
|
|
onComplete: () => {
|
|
|
|
|
this._grid.clipToView = true;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._showIndicators = false;
|
|
|
|
|
this._syncPageIndicatorsVisibility();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
var BaseAppView = GObject.registerClass({
|
|
|
|
|
GTypeFlags: GObject.TypeFlags.ABSTRACT,
|
2021-05-11 10:44:29 -04:00
|
|
|
|
Properties: {
|
|
|
|
|
'gesture-modes': GObject.ParamSpec.flags(
|
|
|
|
|
'gesture-modes', 'gesture-modes', 'gesture-modes',
|
|
|
|
|
GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
|
|
|
|
|
Shell.ActionMode, Shell.ActionMode.OVERVIEW),
|
|
|
|
|
},
|
2019-07-16 05:24:13 -04:00
|
|
|
|
Signals: {
|
|
|
|
|
'view-loaded': {},
|
2019-08-20 17:43:54 -04:00
|
|
|
|
},
|
2019-07-16 05:24:13 -04:00
|
|
|
|
}, class BaseAppView extends St.Widget {
|
2020-12-29 17:18:14 -05:00
|
|
|
|
_init(params = {}) {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
super._init(params);
|
|
|
|
|
|
2020-05-20 18:20:15 -04:00
|
|
|
|
this._grid = this._createGrid();
|
2020-06-24 13:53:12 -04:00
|
|
|
|
this._grid._delegate = this;
|
2013-02-19 18:38:11 -05:00
|
|
|
|
// Standard hack for ClutterBinLayout
|
2018-07-02 10:03:07 -04:00
|
|
|
|
this._grid.x_expand = true;
|
2020-12-02 13:58:49 -05:00
|
|
|
|
this._grid.connect('pages-changed', () => {
|
|
|
|
|
this.goToPage(this._grid.currentPage);
|
|
|
|
|
this._pageIndicators.setNPages(this._grid.nPages);
|
|
|
|
|
this._pageIndicators.setCurrentPosition(this._grid.currentPage);
|
|
|
|
|
});
|
2013-02-19 18:38:11 -05:00
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
// Scroll View
|
|
|
|
|
this._scrollView = new St.ScrollView({
|
2021-02-03 06:39:26 -05:00
|
|
|
|
style_class: 'apps-scroll-view',
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
clip_to_allocation: true,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
|
|
|
|
reactive: true,
|
2020-06-12 18:08:50 -04:00
|
|
|
|
enable_mouse_scrolling: false,
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
});
|
2020-12-29 17:18:14 -05:00
|
|
|
|
this._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER);
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
|
|
|
|
|
this._canScroll = true; // limiting scrolling speed
|
|
|
|
|
this._scrollTimeoutId = 0;
|
|
|
|
|
this._scrollView.connect('scroll-event', this._onScroll.bind(this));
|
|
|
|
|
|
|
|
|
|
this._scrollView.add_actor(this._grid);
|
|
|
|
|
|
2020-12-29 17:18:14 -05:00
|
|
|
|
const scroll = this._scrollView.hscroll;
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
this._adjustment = scroll.adjustment;
|
|
|
|
|
this._adjustment.connect('notify::value', adj => {
|
2021-02-23 11:44:23 -05:00
|
|
|
|
const value = adj.value / adj.page_size;
|
|
|
|
|
this._pageIndicators.setCurrentPosition(value);
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Page Indicators
|
2021-01-08 16:23:51 -05:00
|
|
|
|
this._pageIndicators =
|
|
|
|
|
new PageIndicators.PageIndicators(Clutter.Orientation.HORIZONTAL);
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
|
2020-12-29 17:18:14 -05:00
|
|
|
|
this._pageIndicators.y_expand = false;
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
this._pageIndicators.connect('page-activated',
|
|
|
|
|
(indicators, pageIndex) => {
|
|
|
|
|
this.goToPage(pageIndex);
|
|
|
|
|
});
|
|
|
|
|
this._pageIndicators.connect('scroll-event', (actor, event) => {
|
|
|
|
|
this._scrollView.event(event, false);
|
|
|
|
|
});
|
|
|
|
|
|
2021-02-03 06:46:07 -05:00
|
|
|
|
// Navigation indicators
|
|
|
|
|
this._nextPageIndicator = new St.Widget({
|
|
|
|
|
style_class: 'page-navigation-hint next',
|
|
|
|
|
opacity: 0,
|
|
|
|
|
visible: false,
|
2022-06-28 18:47:06 -04:00
|
|
|
|
reactive: true,
|
2021-02-03 06:46:07 -05:00
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
2022-06-14 15:58:19 -04:00
|
|
|
|
x_align: Clutter.ActorAlign.FILL,
|
2021-02-03 06:46:07 -05:00
|
|
|
|
y_align: Clutter.ActorAlign.FILL,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._prevPageIndicator = new St.Widget({
|
|
|
|
|
style_class: 'page-navigation-hint previous',
|
|
|
|
|
opacity: 0,
|
|
|
|
|
visible: false,
|
2022-06-28 18:47:06 -04:00
|
|
|
|
reactive: true,
|
2021-02-03 06:46:07 -05:00
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
2022-06-14 15:58:19 -04:00
|
|
|
|
x_align: Clutter.ActorAlign.FILL,
|
2021-02-03 06:46:07 -05:00
|
|
|
|
y_align: Clutter.ActorAlign.FILL,
|
|
|
|
|
});
|
|
|
|
|
|
2021-02-13 13:54:14 -05:00
|
|
|
|
// Next/prev page arrows
|
|
|
|
|
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
|
2022-06-28 11:39:50 -04:00
|
|
|
|
this._nextPageArrow = new St.Button({
|
2021-02-13 13:54:14 -05:00
|
|
|
|
style_class: 'page-navigation-arrow',
|
|
|
|
|
icon_name: rtl
|
2022-01-31 10:10:58 -05:00
|
|
|
|
? 'carousel-arrow-previous-symbolic'
|
|
|
|
|
: 'carousel-arrow-next-symbolic',
|
2022-08-09 13:26:24 -04:00
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
2021-02-13 13:54:14 -05:00
|
|
|
|
});
|
2022-06-28 11:39:50 -04:00
|
|
|
|
this._nextPageArrow.connect('clicked',
|
|
|
|
|
() => this.goToPage(this._grid.currentPage + 1));
|
|
|
|
|
|
|
|
|
|
this._prevPageArrow = new St.Button({
|
2021-02-13 13:54:14 -05:00
|
|
|
|
style_class: 'page-navigation-arrow',
|
|
|
|
|
icon_name: rtl
|
2022-01-31 10:10:58 -05:00
|
|
|
|
? 'carousel-arrow-next-symbolic'
|
|
|
|
|
: 'carousel-arrow-previous-symbolic',
|
2021-02-13 13:54:14 -05:00
|
|
|
|
opacity: 0,
|
|
|
|
|
visible: false,
|
2022-08-09 13:26:24 -04:00
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
2021-02-13 13:54:14 -05:00
|
|
|
|
});
|
2022-06-28 11:39:50 -04:00
|
|
|
|
this._prevPageArrow.connect('clicked',
|
|
|
|
|
() => this.goToPage(this._grid.currentPage - 1));
|
2021-02-13 13:54:14 -05:00
|
|
|
|
|
2021-02-03 06:46:07 -05:00
|
|
|
|
const scrollContainer = new St.Widget({
|
|
|
|
|
clip_to_allocation: true,
|
|
|
|
|
y_expand: true,
|
|
|
|
|
});
|
|
|
|
|
scrollContainer.add_child(this._scrollView);
|
2022-06-10 17:30:26 -04:00
|
|
|
|
scrollContainer.add_child(this._prevPageIndicator);
|
|
|
|
|
scrollContainer.add_child(this._nextPageIndicator);
|
2021-02-13 13:54:14 -05:00
|
|
|
|
scrollContainer.add_child(this._nextPageArrow);
|
|
|
|
|
scrollContainer.add_child(this._prevPageArrow);
|
2022-06-10 17:30:26 -04:00
|
|
|
|
scrollContainer.layoutManager = new BaseAppViewGridLayout(
|
|
|
|
|
this._grid,
|
|
|
|
|
this._scrollView,
|
|
|
|
|
this._nextPageIndicator,
|
|
|
|
|
this._nextPageArrow,
|
|
|
|
|
this._prevPageIndicator,
|
|
|
|
|
this._prevPageArrow);
|
|
|
|
|
this._appGridLayout = scrollContainer.layoutManager;
|
|
|
|
|
scrollContainer._delegate = this;
|
2021-02-03 06:46:07 -05:00
|
|
|
|
|
2020-12-29 17:18:14 -05:00
|
|
|
|
this._box = new St.BoxLayout({
|
|
|
|
|
vertical: true,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
|
|
|
|
});
|
2021-02-03 06:46:07 -05:00
|
|
|
|
this._box.add_child(scrollContainer);
|
2020-12-29 17:18:14 -05:00
|
|
|
|
this._box.add_child(this._pageIndicators);
|
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
// Swipe
|
|
|
|
|
this._swipeTracker = new SwipeTracker.SwipeTracker(this._scrollView,
|
2021-05-11 10:44:29 -04:00
|
|
|
|
Clutter.Orientation.HORIZONTAL, this.gestureModes);
|
2020-12-29 17:18:14 -05:00
|
|
|
|
this._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL;
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
this._swipeTracker.connect('begin', this._swipeBegin.bind(this));
|
|
|
|
|
this._swipeTracker.connect('update', this._swipeUpdate.bind(this));
|
|
|
|
|
this._swipeTracker.connect('end', this._swipeEnd.bind(this));
|
|
|
|
|
|
2020-12-29 17:18:14 -05:00
|
|
|
|
this._orientation = Clutter.Orientation.HORIZONTAL;
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
|
2019-10-31 19:46:35 -04:00
|
|
|
|
this._items = new Map();
|
2019-10-31 19:44:02 -04:00
|
|
|
|
this._orderedItems = [];
|
2020-04-02 13:47:52 -04:00
|
|
|
|
|
2019-04-24 05:37:10 -04:00
|
|
|
|
// Filter the apps through the user’s parental controls.
|
|
|
|
|
this._parentalControlsManager = ParentalControlsManager.getDefault();
|
2021-08-15 18:36:59 -04:00
|
|
|
|
this._parentalControlsManager.connectObject('app-filter-changed',
|
|
|
|
|
() => this._redisplay(), this);
|
2020-06-24 13:53:12 -04:00
|
|
|
|
|
2021-01-19 19:44:21 -05:00
|
|
|
|
// Don't duplicate favorites
|
|
|
|
|
this._appFavorites = AppFavorites.getAppFavorites();
|
2021-08-15 18:36:59 -04:00
|
|
|
|
this._appFavorites.connectObject('changed',
|
|
|
|
|
() => this._redisplay(), this);
|
2021-01-19 19:44:21 -05:00
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
// Drag n' Drop
|
2022-08-09 17:31:46 -04:00
|
|
|
|
this._overshootTimeoutId = 0;
|
2020-09-24 17:57:56 -04:00
|
|
|
|
this._delayedMoveData = null;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
|
2020-06-24 13:56:32 -04:00
|
|
|
|
this._dragBeginId = 0;
|
|
|
|
|
this._dragEndId = 0;
|
|
|
|
|
this._dragCancelledId = 0;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
|
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2021-02-03 06:46:07 -05:00
|
|
|
|
|
|
|
|
|
this._previewedPages = new Map();
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDestroy() {
|
2020-11-26 13:10:11 -05:00
|
|
|
|
if (this._swipeTracker) {
|
|
|
|
|
this._swipeTracker.destroy();
|
|
|
|
|
delete this._swipeTracker;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
this._removeDelayedMove();
|
2020-06-24 13:56:32 -04:00
|
|
|
|
this._disconnectDnD();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-02-19 17:23:41 -05:00
|
|
|
|
|
2020-05-20 18:20:15 -04:00
|
|
|
|
_createGrid() {
|
2022-06-10 14:25:05 -04:00
|
|
|
|
return new AppGrid({allow_incomplete_pages: true});
|
2020-05-20 18:20:15 -04:00
|
|
|
|
}
|
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
_onScroll(actor, event) {
|
|
|
|
|
if (this._swipeTracker.canHandleScrollEvent(event))
|
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
|
|
|
|
|
if (!this._canScroll)
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
|
|
|
|
|
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
|
|
|
|
|
const vertical = this._orientation === Clutter.Orientation.VERTICAL;
|
|
|
|
|
|
|
|
|
|
let nextPage = this._grid.currentPage;
|
|
|
|
|
switch (event.get_scroll_direction()) {
|
|
|
|
|
case Clutter.ScrollDirection.UP:
|
|
|
|
|
nextPage -= 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Clutter.ScrollDirection.DOWN:
|
|
|
|
|
nextPage += 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Clutter.ScrollDirection.LEFT:
|
|
|
|
|
if (vertical)
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
nextPage += rtl ? 1 : -1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Clutter.ScrollDirection.RIGHT:
|
|
|
|
|
if (vertical)
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
nextPage += rtl ? -1 : 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.goToPage(nextPage);
|
|
|
|
|
|
|
|
|
|
this._canScroll = false;
|
|
|
|
|
this._scrollTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
|
|
|
|
SCROLL_TIMEOUT_TIME, () => {
|
|
|
|
|
this._canScroll = true;
|
|
|
|
|
this._scrollTimeoutId = 0;
|
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_swipeBegin(tracker, monitor) {
|
|
|
|
|
if (monitor !== Main.layoutManager.primaryIndex)
|
|
|
|
|
return;
|
|
|
|
|
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
if (this._dragFocus) {
|
|
|
|
|
this._dragFocus.cancelActions();
|
|
|
|
|
this._dragFocus = null;
|
|
|
|
|
}
|
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
const adjustment = this._adjustment;
|
|
|
|
|
adjustment.remove_transition('value');
|
|
|
|
|
|
|
|
|
|
const progress = adjustment.value / adjustment.page_size;
|
|
|
|
|
const points = Array.from({ length: this._grid.nPages }, (v, i) => i);
|
|
|
|
|
const size = tracker.orientation === Clutter.Orientation.VERTICAL
|
2022-04-09 18:48:39 -04:00
|
|
|
|
? this._grid.allocation.get_height() : this._grid.allocation.get_width();
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
|
|
|
|
|
tracker.confirmSwipe(size, points, progress, Math.round(progress));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_swipeUpdate(tracker, progress) {
|
|
|
|
|
const adjustment = this._adjustment;
|
|
|
|
|
adjustment.value = progress * adjustment.page_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_swipeEnd(tracker, duration, endProgress) {
|
|
|
|
|
const adjustment = this._adjustment;
|
|
|
|
|
const value = endProgress * adjustment.page_size;
|
|
|
|
|
|
|
|
|
|
adjustment.ease(value, {
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
|
|
|
|
|
duration,
|
|
|
|
|
onComplete: () => this.goToPage(endProgress, false),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 13:56:32 -04:00
|
|
|
|
_connectDnD() {
|
|
|
|
|
this._dragBeginId =
|
|
|
|
|
Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
|
|
|
|
|
this._dragEndId =
|
|
|
|
|
Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
|
|
|
|
|
this._dragCancelledId =
|
|
|
|
|
Main.overview.connect('item-drag-cancelled', this._onDragCancelled.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_disconnectDnD() {
|
|
|
|
|
if (this._dragBeginId > 0) {
|
|
|
|
|
Main.overview.disconnect(this._dragBeginId);
|
|
|
|
|
this._dragBeginId = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._dragEndId > 0) {
|
|
|
|
|
Main.overview.disconnect(this._dragEndId);
|
|
|
|
|
this._dragEndId = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._dragCancelledId > 0) {
|
|
|
|
|
Main.overview.disconnect(this._dragCancelledId);
|
|
|
|
|
this._dragCancelledId = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._dragMonitor) {
|
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
|
this._dragMonitor = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
_maybeMoveItem(dragEvent) {
|
|
|
|
|
const [success, x, y] =
|
|
|
|
|
this._grid.transform_stage_point(dragEvent.x, dragEvent.y);
|
|
|
|
|
|
|
|
|
|
if (!success)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const { source } = dragEvent;
|
|
|
|
|
const [page, position, dragLocation] =
|
|
|
|
|
this._getDropTarget(x, y, source);
|
|
|
|
|
const item = position !== -1
|
|
|
|
|
? this._grid.getItemAt(page, position) : null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Dragging over invalid parts of the grid cancels the timeout
|
|
|
|
|
if (item === source ||
|
|
|
|
|
dragLocation === IconGrid.DragLocation.INVALID ||
|
|
|
|
|
dragLocation === IconGrid.DragLocation.ON_ICON) {
|
|
|
|
|
this._removeDelayedMove();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 17:57:56 -04:00
|
|
|
|
if (!this._delayedMoveData ||
|
|
|
|
|
this._delayedMoveData.page !== page ||
|
|
|
|
|
this._delayedMoveData.position !== position) {
|
2020-06-24 13:53:12 -04:00
|
|
|
|
// Update the item with a small delay
|
|
|
|
|
this._removeDelayedMove();
|
2020-09-24 17:57:56 -04:00
|
|
|
|
this._delayedMoveData = {
|
|
|
|
|
page,
|
|
|
|
|
position,
|
|
|
|
|
source,
|
|
|
|
|
destroyId: source.connect('destroy', () => this._removeDelayedMove()),
|
|
|
|
|
timeoutId: GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
|
|
|
|
DELAYED_MOVE_TIMEOUT, () => {
|
|
|
|
|
this._moveItem(source, page, position);
|
|
|
|
|
this._delayedMoveData.timeoutId = 0;
|
|
|
|
|
this._removeDelayedMove();
|
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
|
}),
|
|
|
|
|
};
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_removeDelayedMove() {
|
2020-09-24 17:57:56 -04:00
|
|
|
|
if (!this._delayedMoveData)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const { source, destroyId, timeoutId } = this._delayedMoveData;
|
|
|
|
|
|
|
|
|
|
if (timeoutId > 0)
|
|
|
|
|
GLib.source_remove(timeoutId);
|
|
|
|
|
|
|
|
|
|
if (destroyId > 0)
|
|
|
|
|
source.disconnect(destroyId);
|
|
|
|
|
|
|
|
|
|
this._delayedMoveData = null;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_resetOvershoot() {
|
2022-08-09 17:31:46 -04:00
|
|
|
|
if (this._overshootTimeoutId)
|
|
|
|
|
GLib.source_remove(this._overshootTimeoutId);
|
|
|
|
|
this._overshootTimeoutId = 0;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-09 18:39:41 -04:00
|
|
|
|
_dragWithinOvershootRegion(dragEvent) {
|
|
|
|
|
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
|
|
|
|
|
const {x, y, targetActor: indicator} = dragEvent;
|
|
|
|
|
const [indicatorX, indicatorY] = indicator.get_transformed_position();
|
|
|
|
|
const [indicatorWidth, indicatorHeight] = indicator.get_transformed_size();
|
|
|
|
|
|
|
|
|
|
let overshootX = indicatorX;
|
|
|
|
|
if (indicator === this._nextPageIndicator || rtl)
|
|
|
|
|
overshootX += indicatorWidth - OVERSHOOT_THRESHOLD;
|
|
|
|
|
|
|
|
|
|
const overshootBox = new Clutter.ActorBox();
|
|
|
|
|
overshootBox.set_origin(overshootX, indicatorY);
|
|
|
|
|
overshootBox.set_size(OVERSHOOT_THRESHOLD, indicatorHeight);
|
|
|
|
|
|
|
|
|
|
return overshootBox.contains(x, y);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
_handleDragOvershoot(dragEvent) {
|
|
|
|
|
// Already animating
|
|
|
|
|
if (this._adjustment.get_transition('value') !== null)
|
|
|
|
|
return;
|
|
|
|
|
|
2022-06-28 18:47:06 -04:00
|
|
|
|
const {targetActor} = dragEvent;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
|
2022-06-28 18:47:06 -04:00
|
|
|
|
if (targetActor !== this._prevPageIndicator &&
|
|
|
|
|
targetActor !== this._nextPageIndicator) {
|
|
|
|
|
this._resetOvershoot();
|
2020-06-24 13:53:12 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-09 17:31:46 -04:00
|
|
|
|
if (this._overshootTimeoutId > 0)
|
2020-06-24 13:53:12 -04:00
|
|
|
|
return;
|
|
|
|
|
|
2022-06-28 18:47:06 -04:00
|
|
|
|
let targetPage;
|
|
|
|
|
if (dragEvent.targetActor === this._prevPageIndicator)
|
|
|
|
|
targetPage = this._grid.currentPage - 1;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
else
|
2022-06-28 18:47:06 -04:00
|
|
|
|
targetPage = this._grid.currentPage + 1;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
|
2022-06-28 18:47:06 -04:00
|
|
|
|
if (targetPage < 0 || targetPage >= this._grid.nPages)
|
|
|
|
|
return; // don't go beyond first/last page
|
2020-06-24 13:53:12 -04:00
|
|
|
|
|
2022-08-09 18:39:41 -04:00
|
|
|
|
// If dragging over the drag overshoot threshold region, immediately
|
|
|
|
|
// switch pages
|
|
|
|
|
if (this._dragWithinOvershootRegion(dragEvent)) {
|
|
|
|
|
this._resetOvershoot();
|
|
|
|
|
this.goToPage(targetPage);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-09 17:31:46 -04:00
|
|
|
|
this._overshootTimeoutId =
|
2020-06-24 13:53:12 -04:00
|
|
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, OVERSHOOT_TIMEOUT, () => {
|
|
|
|
|
this._resetOvershoot();
|
2022-06-28 18:47:06 -04:00
|
|
|
|
this.goToPage(targetPage);
|
2020-06-24 13:53:12 -04:00
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
|
});
|
2022-08-09 17:31:46 -04:00
|
|
|
|
GLib.Source.set_name_by_id(this._overshootTimeoutId,
|
|
|
|
|
'[gnome-shell] this._overshootTimeoutId');
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragBegin() {
|
|
|
|
|
this._dragMonitor = {
|
|
|
|
|
dragMotion: this._onDragMotion.bind(this),
|
2022-06-29 13:25:47 -04:00
|
|
|
|
dragDrop: this._onDragDrop.bind(this),
|
2020-06-24 13:53:12 -04:00
|
|
|
|
};
|
|
|
|
|
DND.addDragMonitor(this._dragMonitor);
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._appGridLayout.showPageIndicators();
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
this._dragFocus = null;
|
|
|
|
|
this._swipeTracker.enabled = false;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragMotion(dragEvent) {
|
|
|
|
|
if (!(dragEvent.source instanceof AppViewItem))
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
|
|
|
|
|
const appIcon = dragEvent.source;
|
|
|
|
|
|
|
|
|
|
// Handle the drag overshoot. When dragging to above the
|
|
|
|
|
// icon grid, move to the page above; when dragging below,
|
|
|
|
|
// move to the page below.
|
|
|
|
|
if (appIcon instanceof AppViewItem)
|
|
|
|
|
this._handleDragOvershoot(dragEvent);
|
|
|
|
|
|
|
|
|
|
this._maybeMoveItem(dragEvent);
|
|
|
|
|
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-29 13:25:47 -04:00
|
|
|
|
_onDragDrop(dropEvent) {
|
|
|
|
|
// Because acceptDrop() does not receive the target actor, store it
|
|
|
|
|
// here and use this value in the acceptDrop() implementation below.
|
|
|
|
|
this._dropTarget = dropEvent.targetActor;
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
_onDragEnd() {
|
|
|
|
|
if (this._dragMonitor) {
|
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
|
this._dragMonitor = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._resetOvershoot();
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._appGridLayout.hidePageIndicators();
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
this._swipeTracker.enabled = true;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragCancelled() {
|
|
|
|
|
// At this point, the positions aren't stored yet, thus _redisplay()
|
|
|
|
|
// will move all items to their original positions
|
|
|
|
|
this._redisplay();
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._appGridLayout.hidePageIndicators();
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
this._swipeTracker.enabled = true;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_canAccept(source) {
|
|
|
|
|
return source instanceof AppViewItem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleDragOver(source) {
|
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
|
return DND.DragMotionResult.NO_DROP;
|
|
|
|
|
|
|
|
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acceptDrop(source) {
|
2022-06-29 13:25:47 -04:00
|
|
|
|
const dropTarget = this._dropTarget;
|
|
|
|
|
delete this._dropTarget;
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
|
return false;
|
|
|
|
|
|
2022-06-29 13:25:47 -04:00
|
|
|
|
if (dropTarget === this._prevPageIndicator ||
|
|
|
|
|
dropTarget === this._nextPageIndicator) {
|
|
|
|
|
const increment = dropTarget === this._prevPageIndicator ? -1 : 1;
|
2021-02-03 06:51:17 -05:00
|
|
|
|
const { currentPage, nPages } = this._grid;
|
|
|
|
|
const page = Math.min(currentPage + increment, nPages);
|
|
|
|
|
const position = page < nPages ? -1 : 0;
|
|
|
|
|
|
|
|
|
|
this._moveItem(source, page, position);
|
|
|
|
|
this.goToPage(page);
|
|
|
|
|
} else if (this._delayedMoveData) {
|
|
|
|
|
// Dropped before the icon was moved
|
2020-09-24 17:57:56 -04:00
|
|
|
|
const { page, position } = this._delayedMoveData;
|
2020-06-24 13:53:12 -04:00
|
|
|
|
|
|
|
|
|
this._moveItem(source, page, position);
|
|
|
|
|
this._removeDelayedMove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-25 16:33:48 -04:00
|
|
|
|
_findBestPageToAppend(startPage = 1) {
|
|
|
|
|
for (let i = startPage; i < this._grid.nPages; i++) {
|
|
|
|
|
const pageItems =
|
|
|
|
|
this._grid.getItemsAtPage(i).filter(c => c.visible);
|
|
|
|
|
|
|
|
|
|
if (pageItems.length < this._grid.itemsPerPage)
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-23 15:48:43 -04:00
|
|
|
|
_getLinearPosition(page, position) {
|
2020-05-26 09:58:17 -04:00
|
|
|
|
let itemIndex = 0;
|
|
|
|
|
|
|
|
|
|
if (this._grid.nPages > 0) {
|
|
|
|
|
const realPage = page === -1 ? this._grid.nPages - 1 : page;
|
|
|
|
|
|
|
|
|
|
itemIndex = position === -1
|
|
|
|
|
? this._grid.getItemsAtPage(realPage).filter(c => c.visible).length - 1
|
|
|
|
|
: position;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < realPage; i++) {
|
|
|
|
|
const pageItems = this._grid.getItemsAtPage(i).filter(c => c.visible);
|
|
|
|
|
itemIndex += pageItems.length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-23 15:48:43 -04:00
|
|
|
|
return itemIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_addItem(item, page, position) {
|
|
|
|
|
// Append icons to the first page with empty slot, starting from
|
|
|
|
|
// the second page
|
|
|
|
|
if (this._grid.nPages > 1 && page === -1 && position === -1)
|
|
|
|
|
page = this._findBestPageToAppend();
|
|
|
|
|
|
|
|
|
|
const itemIndex = this._getLinearPosition(page, position);
|
|
|
|
|
|
2020-05-26 09:58:17 -04:00
|
|
|
|
this._orderedItems.splice(itemIndex, 0, item);
|
|
|
|
|
this._items.set(item.id, item);
|
|
|
|
|
this._grid.addItem(item, page, position);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_removeItem(item) {
|
|
|
|
|
const iconIndex = this._orderedItems.indexOf(item);
|
|
|
|
|
|
|
|
|
|
this._orderedItems.splice(iconIndex, 1);
|
|
|
|
|
this._items.delete(item.id);
|
|
|
|
|
this._grid.removeItem(item);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 00:41:43 -04:00
|
|
|
|
_getItemPosition(item) {
|
|
|
|
|
const { itemsPerPage } = this._grid;
|
|
|
|
|
|
|
|
|
|
let iconIndex = this._orderedItems.indexOf(item);
|
|
|
|
|
if (iconIndex === -1)
|
|
|
|
|
iconIndex = this._orderedItems.length - 1;
|
|
|
|
|
|
|
|
|
|
const page = Math.floor(iconIndex / itemsPerPage);
|
|
|
|
|
const position = iconIndex % itemsPerPage;
|
|
|
|
|
|
|
|
|
|
return [page, position];
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_redisplay() {
|
2019-10-31 19:44:02 -04:00
|
|
|
|
let oldApps = this._orderedItems.slice();
|
2019-07-01 22:13:07 -04:00
|
|
|
|
let oldAppIds = oldApps.map(icon => icon.id);
|
|
|
|
|
|
2020-05-26 00:41:43 -04:00
|
|
|
|
let newApps = this._loadApps().sort(this._compareItems.bind(this));
|
2019-07-01 22:13:07 -04:00
|
|
|
|
let newAppIds = newApps.map(icon => icon.id);
|
|
|
|
|
|
|
|
|
|
let addedApps = newApps.filter(icon => !oldAppIds.includes(icon.id));
|
|
|
|
|
let removedApps = oldApps.filter(icon => !newAppIds.includes(icon.id));
|
|
|
|
|
|
|
|
|
|
// Remove old app icons
|
|
|
|
|
removedApps.forEach(icon => {
|
2020-05-26 09:58:17 -04:00
|
|
|
|
this._removeItem(icon);
|
2019-11-21 16:21:44 -05:00
|
|
|
|
icon.destroy();
|
2019-07-01 22:13:07 -04:00
|
|
|
|
});
|
|
|
|
|
|
2020-06-23 11:18:56 -04:00
|
|
|
|
// Add new app icons, or move existing ones
|
|
|
|
|
newApps.forEach(icon => {
|
|
|
|
|
const [page, position] = this._getItemPosition(icon);
|
|
|
|
|
if (addedApps.includes(icon))
|
|
|
|
|
this._addItem(icon, page, position);
|
|
|
|
|
else if (page !== -1 && position !== -1)
|
|
|
|
|
this._moveItem(icon, page, position);
|
2019-07-01 22:13:07 -04:00
|
|
|
|
});
|
2019-07-01 22:01:59 -04:00
|
|
|
|
|
|
|
|
|
this.emit('view-loaded');
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-01-28 11:11:10 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
getAllItems() {
|
2019-10-31 19:44:02 -04:00
|
|
|
|
return this._orderedItems;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_compareItems(a, b) {
|
2014-01-28 11:36:57 -05:00
|
|
|
|
return a.name.localeCompare(b.name);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_selectAppInternal(id) {
|
2019-10-31 19:46:35 -04:00
|
|
|
|
if (this._items.has(id))
|
|
|
|
|
this._items.get(id).navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
2011-07-09 09:30:42 -04:00
|
|
|
|
else
|
2022-02-07 09:14:06 -05:00
|
|
|
|
log(`No such application ${id}`);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2011-07-09 09:30:42 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
selectApp(id) {
|
2019-10-31 19:46:35 -04:00
|
|
|
|
if (this._items.has(id)) {
|
|
|
|
|
let item = this._items.get(id);
|
|
|
|
|
|
|
|
|
|
if (item.mapped) {
|
|
|
|
|
this._selectAppInternal(id);
|
|
|
|
|
} else {
|
|
|
|
|
// Need to wait until the view is mapped
|
|
|
|
|
let signalId = item.connect('notify::mapped', actor => {
|
2017-10-30 20:38:18 -04:00
|
|
|
|
if (actor.mapped) {
|
|
|
|
|
actor.disconnect(signalId);
|
|
|
|
|
this._selectAppInternal(id);
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-10-31 19:46:35 -04:00
|
|
|
|
}
|
2011-07-09 09:30:42 -04:00
|
|
|
|
} else {
|
|
|
|
|
// Need to wait until the view is built
|
2017-10-30 20:38:18 -04:00
|
|
|
|
let signalId = this.connect('view-loaded', () => {
|
2011-07-09 09:30:42 -04:00
|
|
|
|
this.disconnect(signalId);
|
|
|
|
|
this.selectApp(id);
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2011-07-09 09:30:42 -04:00
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-06-17 13:10:54 -04:00
|
|
|
|
|
2020-05-25 14:58:39 -04:00
|
|
|
|
_getDropTarget(x, y, source) {
|
|
|
|
|
const { currentPage } = this._grid;
|
|
|
|
|
|
|
|
|
|
let [item, dragLocation] = this._grid.getDropTarget(x, y);
|
|
|
|
|
|
|
|
|
|
const [sourcePage, sourcePosition] = this._grid.getItemPosition(source);
|
|
|
|
|
const targetPage = currentPage;
|
|
|
|
|
let targetPosition = item
|
|
|
|
|
? this._grid.getItemPosition(item)[1] : -1;
|
|
|
|
|
|
|
|
|
|
// In case we're hovering over the edge of an item but the
|
|
|
|
|
// reflow will happen in the opposite direction (the drag
|
|
|
|
|
// can't "naturally push the item away"), we instead set the
|
|
|
|
|
// drop target to the adjacent item that can be pushed away
|
|
|
|
|
// in the reflow-direction.
|
|
|
|
|
//
|
|
|
|
|
// We must avoid doing that if we're hovering over the first
|
|
|
|
|
// or last column though, in that case there is no adjacent
|
|
|
|
|
// icon we could push away.
|
|
|
|
|
if (dragLocation === IconGrid.DragLocation.START_EDGE &&
|
|
|
|
|
targetPosition > sourcePosition &&
|
|
|
|
|
targetPage === sourcePage) {
|
|
|
|
|
const nColumns = this._grid.layout_manager.columns_per_page;
|
|
|
|
|
const targetColumn = targetPosition % nColumns;
|
|
|
|
|
|
|
|
|
|
if (targetColumn > 0) {
|
|
|
|
|
targetPosition -= 1;
|
|
|
|
|
dragLocation = IconGrid.DragLocation.END_EDGE;
|
|
|
|
|
}
|
|
|
|
|
} else if (dragLocation === IconGrid.DragLocation.END_EDGE &&
|
|
|
|
|
(targetPosition < sourcePosition ||
|
|
|
|
|
targetPage !== sourcePage)) {
|
|
|
|
|
const nColumns = this._grid.layout_manager.columns_per_page;
|
|
|
|
|
const targetColumn = targetPosition % nColumns;
|
|
|
|
|
|
|
|
|
|
if (targetColumn < nColumns - 1) {
|
|
|
|
|
targetPosition += 1;
|
|
|
|
|
dragLocation = IconGrid.DragLocation.START_EDGE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append to the page if dragging over empty area
|
|
|
|
|
if (dragLocation === IconGrid.DragLocation.EMPTY_SPACE) {
|
|
|
|
|
const pageItems =
|
|
|
|
|
this._grid.getItemsAtPage(currentPage).filter(c => c.visible);
|
|
|
|
|
|
|
|
|
|
targetPosition = pageItems.length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [targetPage, targetPosition, dragLocation];
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 09:58:56 -04:00
|
|
|
|
_moveItem(item, newPage, newPosition) {
|
|
|
|
|
const [page, position] = this._grid.getItemPosition(item);
|
|
|
|
|
if (page === newPage && position === newPosition)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-09-23 15:53:17 -04:00
|
|
|
|
// Update the _orderedItems array
|
|
|
|
|
let index = this._orderedItems.indexOf(item);
|
|
|
|
|
this._orderedItems.splice(index, 1);
|
2020-05-26 09:58:56 -04:00
|
|
|
|
|
2020-09-23 15:53:17 -04:00
|
|
|
|
index = this._getLinearPosition(newPage, newPosition);
|
|
|
|
|
this._orderedItems.splice(index, 0, item);
|
|
|
|
|
|
|
|
|
|
this._grid.moveItem(item, newPage, newPosition);
|
2020-05-26 09:58:56 -04:00
|
|
|
|
}
|
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
vfunc_map() {
|
|
|
|
|
this._swipeTracker.enabled = true;
|
2020-06-24 13:56:32 -04:00
|
|
|
|
this._connectDnD();
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
super.vfunc_map();
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-31 13:00:58 -04:00
|
|
|
|
vfunc_unmap() {
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
this._swipeTracker.enabled = false;
|
2020-06-24 13:56:32 -04:00
|
|
|
|
this._disconnectDnD();
|
2020-03-31 13:00:58 -04:00
|
|
|
|
super.vfunc_unmap();
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
animateSwitch(animationDirection) {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.remove_all_transitions();
|
2018-07-20 15:46:19 -04:00
|
|
|
|
this._grid.remove_all_transitions();
|
2014-06-17 15:31:53 -04:00
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
|
let params = {
|
|
|
|
|
duration: VIEWS_SWITCH_TIME,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2018-07-20 15:46:19 -04:00
|
|
|
|
};
|
2014-06-17 15:31:53 -04:00
|
|
|
|
if (animationDirection == IconGrid.AnimationDirection.IN) {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.show();
|
2014-06-17 15:31:53 -04:00
|
|
|
|
params.opacity = 255;
|
2018-07-20 15:46:19 -04:00
|
|
|
|
params.delay = VIEWS_SWITCH_ANIMATION_DELAY;
|
2014-06-17 15:31:53 -04:00
|
|
|
|
} else {
|
|
|
|
|
params.opacity = 0;
|
|
|
|
|
params.delay = 0;
|
2019-07-16 05:24:13 -04:00
|
|
|
|
params.onComplete = () => this.hide();
|
2014-06-17 15:31:53 -04:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
|
this._grid.ease(params);
|
2013-02-19 17:23:41 -05:00
|
|
|
|
}
|
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
goToPage(pageNumber, animate = true) {
|
|
|
|
|
pageNumber = Math.clamp(pageNumber, 0, this._grid.nPages - 1);
|
|
|
|
|
|
|
|
|
|
if (this._grid.currentPage === pageNumber)
|
|
|
|
|
return;
|
|
|
|
|
|
2022-06-10 17:30:26 -04:00
|
|
|
|
this._appGridLayout.goToPage(pageNumber, animate);
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
this._grid.goToPage(pageNumber, animate);
|
|
|
|
|
}
|
|
|
|
|
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
updateDragFocus(dragFocus) {
|
|
|
|
|
this._dragFocus = dragFocus;
|
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
|
|
|
|
|
2019-12-05 10:31:52 -05:00
|
|
|
|
var PageManager = GObject.registerClass({
|
|
|
|
|
Signals: { 'layout-changed': {} },
|
|
|
|
|
}, class PageManager extends GObject.Object {
|
|
|
|
|
_init() {
|
|
|
|
|
super._init();
|
|
|
|
|
|
2020-09-01 16:12:56 -04:00
|
|
|
|
this._updatingPages = false;
|
|
|
|
|
this._loadPages();
|
|
|
|
|
|
2019-12-05 10:31:52 -05:00
|
|
|
|
global.settings.connect('changed::app-picker-layout',
|
|
|
|
|
this._loadPages.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_loadPages() {
|
|
|
|
|
const layout = global.settings.get_value('app-picker-layout');
|
|
|
|
|
this._pages = layout.recursiveUnpack();
|
2020-09-01 14:41:44 -04:00
|
|
|
|
if (!this._updatingPages)
|
|
|
|
|
this.emit('layout-changed');
|
2019-12-05 10:31:52 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getAppPosition(appId) {
|
|
|
|
|
let position = -1;
|
|
|
|
|
let page = -1;
|
|
|
|
|
|
|
|
|
|
for (let pageIndex = 0; pageIndex < this._pages.length; pageIndex++) {
|
|
|
|
|
const pageData = this._pages[pageIndex];
|
|
|
|
|
|
2020-09-10 10:30:15 -04:00
|
|
|
|
if (appId in pageData) {
|
|
|
|
|
page = pageIndex;
|
|
|
|
|
position = pageData[appId].position;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-12-05 10:31:52 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [page, position];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set pages(p) {
|
|
|
|
|
const packedPages = [];
|
|
|
|
|
|
|
|
|
|
// Pack the icon properties as a GVariant
|
|
|
|
|
for (const page of p) {
|
|
|
|
|
const pageData = {};
|
|
|
|
|
for (const [appId, properties] of Object.entries(page))
|
|
|
|
|
pageData[appId] = new GLib.Variant('a{sv}', properties);
|
|
|
|
|
packedPages.push(pageData);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-01 14:41:44 -04:00
|
|
|
|
this._updatingPages = true;
|
|
|
|
|
|
2019-12-05 10:31:52 -05:00
|
|
|
|
const variant = new GLib.Variant('aa{sv}', packedPages);
|
|
|
|
|
global.settings.set_value('app-picker-layout', variant);
|
2020-09-01 14:41:44 -04:00
|
|
|
|
|
|
|
|
|
this._updatingPages = false;
|
2019-12-05 10:31:52 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get pages() {
|
|
|
|
|
return this._pages;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-12-04 11:43:57 -05:00
|
|
|
|
var AppDisplay = GObject.registerClass(
|
|
|
|
|
class AppDisplay extends BaseAppView {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
_init() {
|
|
|
|
|
super._init({
|
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
|
|
|
|
});
|
|
|
|
|
|
2019-12-05 10:31:52 -05:00
|
|
|
|
this._pageManager = new PageManager();
|
2020-09-01 14:49:16 -04:00
|
|
|
|
this._pageManager.connect('layout-changed', () => this._redisplay());
|
2019-12-11 13:07:16 -05:00
|
|
|
|
|
2022-06-08 10:32:58 -04:00
|
|
|
|
this.add_child(this._box);
|
2013-08-12 13:09:43 -04:00
|
|
|
|
|
2019-12-05 08:06:48 -05:00
|
|
|
|
this._folderIcons = [];
|
2013-08-30 12:50:35 -04:00
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
this._currentDialog = null;
|
|
|
|
|
this._displayingDialog = false;
|
2013-07-09 09:11:03 -04:00
|
|
|
|
|
2020-06-23 11:05:27 -04:00
|
|
|
|
this._placeholder = null;
|
2019-11-23 09:06:14 -05:00
|
|
|
|
|
2019-01-27 19:42:00 -05:00
|
|
|
|
Main.overview.connect('hidden', () => this.goToPage(0));
|
2017-10-30 20:38:18 -04:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this._redisplayWorkId = Main.initializeDeferredWork(this, this._redisplay.bind(this));
|
2014-01-28 11:11:10 -05:00
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
|
Shell.AppSystem.get_default().connect('installed-changed', () => {
|
2014-01-28 11:11:10 -05:00
|
|
|
|
Main.queueDeferredWork(this._redisplayWorkId);
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2014-06-24 15:17:09 -04:00
|
|
|
|
this._folderSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
|
2022-03-02 08:33:11 -05:00
|
|
|
|
this._ensureDefaultFolders();
|
2017-10-30 20:38:18 -04:00
|
|
|
|
this._folderSettings.connect('changed::folder-children', () => {
|
2014-01-28 11:11:10 -05:00
|
|
|
|
Main.queueDeferredWork(this._redisplayWorkId);
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2019-12-04 11:43:57 -05:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-16 03:30:14 -04:00
|
|
|
|
_onDestroy() {
|
2020-06-24 13:53:12 -04:00
|
|
|
|
super._onDestroy();
|
2020-06-22 17:22:13 -04:00
|
|
|
|
|
2019-10-16 03:30:14 -04:00
|
|
|
|
if (this._scrollTimeoutId !== 0) {
|
|
|
|
|
GLib.source_remove(this._scrollTimeoutId);
|
|
|
|
|
this._scrollTimeoutId = 0;
|
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-01-28 11:11:10 -05:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_map() {
|
|
|
|
|
this._keyPressEventId =
|
|
|
|
|
global.stage.connect('key-press-event',
|
|
|
|
|
this._onKeyPressEvent.bind(this));
|
|
|
|
|
super.vfunc_map();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_unmap() {
|
|
|
|
|
if (this._keyPressEventId) {
|
|
|
|
|
global.stage.disconnect(this._keyPressEventId);
|
|
|
|
|
this._keyPressEventId = 0;
|
|
|
|
|
}
|
|
|
|
|
super.vfunc_unmap();
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-18 11:13:27 -04:00
|
|
|
|
_redisplay() {
|
2020-02-17 10:21:58 -05:00
|
|
|
|
this._folderIcons.forEach(icon => {
|
|
|
|
|
icon.view._redisplay();
|
|
|
|
|
});
|
2020-05-19 16:32:22 -04:00
|
|
|
|
|
|
|
|
|
super._redisplay();
|
2019-07-18 11:13:27 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 00:41:17 -04:00
|
|
|
|
_savePages() {
|
|
|
|
|
const pages = [];
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < this._grid.nPages; i++) {
|
|
|
|
|
const pageItems =
|
|
|
|
|
this._grid.getItemsAtPage(i).filter(c => c.visible);
|
|
|
|
|
const pageData = {};
|
|
|
|
|
|
|
|
|
|
pageItems.forEach((item, index) => {
|
|
|
|
|
pageData[item.id] = {
|
|
|
|
|
position: GLib.Variant.new_int32(index),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
pages.push(pageData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._pageManager.pages = pages;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-02 08:33:11 -05:00
|
|
|
|
_ensureDefaultFolders() {
|
|
|
|
|
if (this._folderSettings.get_strv('folder-children').length > 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const folders = Object.keys(DEFAULT_FOLDERS);
|
|
|
|
|
this._folderSettings.set_strv('folder-children', folders);
|
|
|
|
|
|
|
|
|
|
const { path } = this._folderSettings;
|
2022-03-09 19:12:36 -05:00
|
|
|
|
for (const folder of folders) {
|
2022-03-02 08:33:11 -05:00
|
|
|
|
const { name, categories, apps } = DEFAULT_FOLDERS[folder];
|
|
|
|
|
const child = new Gio.Settings({
|
|
|
|
|
schema_id: 'org.gnome.desktop.app-folders.folder',
|
2022-03-15 16:25:16 -04:00
|
|
|
|
path: `${path}folders/${folder}/`,
|
2022-03-02 08:33:11 -05:00
|
|
|
|
});
|
|
|
|
|
child.set_string('name', name);
|
|
|
|
|
child.set_boolean('translate', true);
|
|
|
|
|
child.set_strv('categories', categories);
|
|
|
|
|
if (apps)
|
|
|
|
|
child.set_strv('apps', apps);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-23 11:05:27 -04:00
|
|
|
|
_ensurePlaceholder(source) {
|
|
|
|
|
if (this._placeholder)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const appSys = Shell.AppSystem.get_default();
|
|
|
|
|
const app = appSys.lookup_app(source.id);
|
|
|
|
|
|
|
|
|
|
const isDraggable =
|
|
|
|
|
global.settings.is_writable('favorite-apps') ||
|
|
|
|
|
global.settings.is_writable('app-picker-layout');
|
|
|
|
|
|
|
|
|
|
this._placeholder = new AppIcon(app, { isDraggable });
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
this._placeholder.connect('notify::pressed', () => {
|
|
|
|
|
if (this._placeholder.pressed)
|
|
|
|
|
this.updateDragFocus(this._placeholder);
|
|
|
|
|
});
|
2020-06-23 11:05:27 -04:00
|
|
|
|
this._placeholder.scaleAndFade();
|
|
|
|
|
this._redisplay();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_removePlaceholder() {
|
|
|
|
|
if (this._placeholder) {
|
|
|
|
|
this._placeholder.undoScaleAndFade();
|
|
|
|
|
this._placeholder = null;
|
|
|
|
|
this._redisplay();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-03 07:18:19 -05:00
|
|
|
|
getAppInfos() {
|
|
|
|
|
return this._appInfoList;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 00:41:43 -04:00
|
|
|
|
_getItemPosition(item) {
|
2020-06-25 16:33:48 -04:00
|
|
|
|
if (item === this._placeholder) {
|
|
|
|
|
let [page, position] = this._grid.getItemPosition(item);
|
|
|
|
|
|
|
|
|
|
if (page === -1)
|
|
|
|
|
page = this._findBestPageToAppend(this._grid.currentPage);
|
|
|
|
|
|
|
|
|
|
return [page, position];
|
|
|
|
|
}
|
2020-06-23 11:05:27 -04:00
|
|
|
|
|
2020-05-26 00:41:43 -04:00
|
|
|
|
return this._pageManager.getAppPosition(item.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_compareItems(a, b) {
|
2020-06-23 11:05:27 -04:00
|
|
|
|
const [aPage, aPosition] = this._getItemPosition(a);
|
|
|
|
|
const [bPage, bPosition] = this._getItemPosition(b);
|
2020-05-26 00:41:43 -04:00
|
|
|
|
|
|
|
|
|
if (aPage === -1 && bPage === -1)
|
|
|
|
|
return a.name.localeCompare(b.name);
|
|
|
|
|
else if (aPage === -1)
|
|
|
|
|
return 1;
|
|
|
|
|
else if (bPage === -1)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if (aPage !== bPage)
|
|
|
|
|
return aPage - bPage;
|
|
|
|
|
|
|
|
|
|
return aPosition - bPosition;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_loadApps() {
|
2019-11-21 16:03:34 -05:00
|
|
|
|
let appIcons = [];
|
2018-12-19 07:26:54 -05:00
|
|
|
|
this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => {
|
2015-07-30 13:55:19 -04:00
|
|
|
|
try {
|
2019-08-19 15:38:51 -04:00
|
|
|
|
appInfo.get_id(); // catch invalid file encodings
|
2019-01-28 20:26:39 -05:00
|
|
|
|
} catch (e) {
|
2015-07-30 13:55:19 -04:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-01-19 19:44:21 -05:00
|
|
|
|
return !this._appFavorites.isFavorite(appInfo.get_id()) &&
|
|
|
|
|
this._parentalControlsManager.shouldShowApp(appInfo);
|
2018-12-03 07:18:19 -05:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let apps = this._appInfoList.map(app => app.get_id());
|
2014-01-28 11:11:10 -05:00
|
|
|
|
|
|
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
|
|
2020-05-19 16:49:09 -04:00
|
|
|
|
const appsInsideFolders = new Set();
|
2019-12-05 08:06:48 -05:00
|
|
|
|
this._folderIcons = [];
|
2019-07-01 22:13:07 -04:00
|
|
|
|
|
2014-01-28 11:11:10 -05:00
|
|
|
|
let folders = this._folderSettings.get_strv('folder-children');
|
2017-10-30 20:38:18 -04:00
|
|
|
|
folders.forEach(id => {
|
2022-02-07 09:14:06 -05:00
|
|
|
|
let path = `${this._folderSettings.path}folders/${id}/`;
|
2019-10-31 19:46:35 -04:00
|
|
|
|
let icon = this._items.get(id);
|
2019-07-30 11:15:51 -04:00
|
|
|
|
if (!icon) {
|
|
|
|
|
icon = new FolderIcon(id, path, this);
|
2020-09-24 13:16:48 -04:00
|
|
|
|
icon.connect('apps-changed', () => {
|
|
|
|
|
this._redisplay();
|
|
|
|
|
this._savePages();
|
|
|
|
|
});
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
icon.connect('notify::pressed', () => {
|
|
|
|
|
if (icon.pressed)
|
|
|
|
|
this.updateDragFocus(icon);
|
|
|
|
|
});
|
2019-07-30 11:15:51 -04:00
|
|
|
|
}
|
2020-05-19 16:49:09 -04:00
|
|
|
|
|
|
|
|
|
// Don't try to display empty folders
|
|
|
|
|
if (!icon.visible) {
|
|
|
|
|
icon.destroy();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-21 16:03:34 -05:00
|
|
|
|
appIcons.push(icon);
|
2019-12-05 08:06:48 -05:00
|
|
|
|
this._folderIcons.push(icon);
|
2020-05-19 16:49:09 -04:00
|
|
|
|
|
|
|
|
|
icon.getAppIds().forEach(appId => appsInsideFolders.add(appId));
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2014-01-28 11:11:10 -05:00
|
|
|
|
|
2015-01-27 16:10:45 -05:00
|
|
|
|
// Allow dragging of the icon only if the Dash would accept a drop to
|
|
|
|
|
// change favorite-apps. There are no other possible drop targets from
|
|
|
|
|
// the app picker, so there's no other need for a drag to start,
|
|
|
|
|
// at least on single-monitor setups.
|
|
|
|
|
// This also disables drag-to-launch on multi-monitor setups,
|
|
|
|
|
// but we hope that is not used much.
|
2020-07-16 10:15:03 -04:00
|
|
|
|
const isDraggable =
|
|
|
|
|
global.settings.is_writable('favorite-apps') ||
|
|
|
|
|
global.settings.is_writable('app-picker-layout');
|
2015-01-27 16:10:45 -05:00
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
|
apps.forEach(appId => {
|
2020-05-19 16:49:09 -04:00
|
|
|
|
if (appsInsideFolders.has(appId))
|
|
|
|
|
return;
|
|
|
|
|
|
2019-10-31 19:46:35 -04:00
|
|
|
|
let icon = this._items.get(appId);
|
2019-11-21 15:57:17 -05:00
|
|
|
|
if (!icon) {
|
|
|
|
|
let app = appSys.lookup_app(appId);
|
|
|
|
|
|
2020-07-16 10:15:03 -04:00
|
|
|
|
icon = new AppIcon(app, { isDraggable });
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
icon.connect('notify::pressed', () => {
|
|
|
|
|
if (icon.pressed)
|
|
|
|
|
this.updateDragFocus(icon);
|
|
|
|
|
});
|
2019-11-21 15:57:17 -05:00
|
|
|
|
}
|
2015-01-27 16:10:45 -05:00
|
|
|
|
|
2019-11-21 16:03:34 -05:00
|
|
|
|
appIcons.push(icon);
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2019-07-01 22:13:07 -04:00
|
|
|
|
|
2020-06-23 11:05:27 -04:00
|
|
|
|
// At last, if there's a placeholder available, add it
|
|
|
|
|
if (this._placeholder)
|
|
|
|
|
appIcons.push(this._placeholder);
|
|
|
|
|
|
2019-11-21 16:03:34 -05:00
|
|
|
|
return appIcons;
|
2019-06-29 12:22:08 -04:00
|
|
|
|
}
|
2014-01-28 11:11:10 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
animateSwitch(animationDirection) {
|
2017-10-30 21:19:44 -04:00
|
|
|
|
super.animateSwitch(animationDirection);
|
2014-06-17 15:31:53 -04:00
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (this._currentDialog && this._displayingDialog &&
|
2019-08-19 20:51:42 -04:00
|
|
|
|
animationDirection == IconGrid.AnimationDirection.OUT) {
|
2019-12-12 14:00:01 -05:00
|
|
|
|
this._currentDialog.ease({
|
2018-07-20 15:46:19 -04:00
|
|
|
|
opacity: 0,
|
|
|
|
|
duration: VIEWS_SWITCH_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
onComplete: () => (this.opacity = 255),
|
2018-07-20 15:46:19 -04:00
|
|
|
|
});
|
2019-08-19 20:51:42 -04:00
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
|
goToPage(pageNumber, animate = true) {
|
2020-05-19 09:43:10 -04:00
|
|
|
|
pageNumber = Math.clamp(pageNumber, 0, this._grid.nPages - 1);
|
2014-04-27 11:05:10 -04:00
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (this._grid.currentPage === pageNumber &&
|
|
|
|
|
this._displayingDialog &&
|
|
|
|
|
this._currentDialog)
|
2013-07-09 09:11:03 -04:00
|
|
|
|
return;
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (this._displayingDialog && this._currentDialog)
|
|
|
|
|
this._currentDialog.popdown();
|
2013-07-09 09:11:03 -04:00
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
super.goToPage(pageNumber, animate);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-02-18 15:04:51 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onScroll(actor, event) {
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (this._displayingDialog || !this._scrollView.reactive)
|
2013-11-29 13:17:34 -05:00
|
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-13 12:53:02 -04:00
|
|
|
|
|
appDisplay: Factor out shared code into BaseAppView
The two BaseAppView subclasses now share a lot in terms of
widgetry: they both have a scroll view, pagination dots, swipe
management, etc.
Move this shared code into BaseAppView. Notice, however, that
BaseAppView only creates the widgetry, but it doesn't add them
to any specific layout. FolderView arranges the widgetry in a
vertical box, while AppDisplay arranges it in a ShellStack.
Add a new 'orientation' parameter to BaseAppView and use it
to determine the orientation of the pagination dots, the swipe
tracker direction, and the scroll event handling.
It is worth noticing that the scroll event is a bit more
sophisticated now: when the orientation is horizontal, it
handles all directions since mice wheels usually only generate
up/down events.
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1271
2020-05-22 12:20:40 -04:00
|
|
|
|
return super._onScroll(actor, event);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-08-12 10:36:45 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onKeyPressEvent(actor, event) {
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (this._displayingDialog)
|
2013-11-29 13:17:34 -05:00
|
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-12 11:20:07 -04:00
|
|
|
|
|
2019-11-05 14:37:28 -05:00
|
|
|
|
if (event.get_key_symbol() === Clutter.KEY_Page_Up) {
|
2014-06-17 13:10:54 -04:00
|
|
|
|
this.goToPage(this._grid.currentPage - 1);
|
2013-11-29 13:17:34 -05:00
|
|
|
|
return Clutter.EVENT_STOP;
|
2019-11-05 14:37:28 -05:00
|
|
|
|
} else if (event.get_key_symbol() === Clutter.KEY_Page_Down) {
|
2014-06-17 13:10:54 -04:00
|
|
|
|
this.goToPage(this._grid.currentPage + 1);
|
2013-11-29 13:17:34 -05:00
|
|
|
|
return Clutter.EVENT_STOP;
|
2022-03-13 04:18:11 -04:00
|
|
|
|
} else if (event.get_key_symbol() === Clutter.KEY_Home) {
|
|
|
|
|
this.goToPage(0);
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
} else if (event.get_key_symbol() === Clutter.KEY_End) {
|
|
|
|
|
this.goToPage(this._grid.nPages - 1);
|
|
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-12 11:20:07 -04:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-29 13:17:34 -05:00
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-09-12 11:20:07 -04:00
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
addFolderDialog(dialog) {
|
2020-06-04 00:37:31 -04:00
|
|
|
|
Main.layoutManager.overviewGroup.add_child(dialog);
|
2019-12-12 14:00:01 -05:00
|
|
|
|
dialog.connect('open-state-changed', (o, isOpen) => {
|
2021-08-15 18:36:59 -04:00
|
|
|
|
this._currentDialog?.disconnectObject(this);
|
2019-07-18 10:06:38 -04:00
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
this._currentDialog = null;
|
2019-07-18 10:06:38 -04:00
|
|
|
|
|
|
|
|
|
if (isOpen) {
|
2019-12-12 14:00:01 -05:00
|
|
|
|
this._currentDialog = dialog;
|
2021-08-15 18:36:59 -04:00
|
|
|
|
this._currentDialog.connectObject('destroy',
|
|
|
|
|
() => (this._currentDialog = null), this);
|
2019-07-18 10:06:38 -04:00
|
|
|
|
}
|
2020-06-04 21:37:50 -04:00
|
|
|
|
this._displayingDialog = isOpen;
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-02-18 14:19:18 -05:00
|
|
|
|
|
2020-06-22 17:22:13 -04:00
|
|
|
|
_maybeMoveItem(dragEvent) {
|
2020-06-24 13:53:12 -04:00
|
|
|
|
const clonedEvent = {
|
|
|
|
|
...dragEvent,
|
|
|
|
|
source: this._placeholder ? this._placeholder : dragEvent.source,
|
|
|
|
|
};
|
2019-11-23 16:25:00 -05:00
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
super._maybeMoveItem(clonedEvent);
|
2019-06-28 19:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
_onDragBegin(overview, source) {
|
|
|
|
|
super._onDragBegin(overview, source);
|
2020-06-23 11:05:27 -04:00
|
|
|
|
|
|
|
|
|
// When dragging from a folder dialog, the dragged app icon doesn't
|
|
|
|
|
// exist in AppDisplay. We work around that by adding a placeholder
|
|
|
|
|
// icon that is either destroyed on cancel, or becomes the effective
|
|
|
|
|
// new icon when dropped.
|
2021-01-26 05:33:54 -05:00
|
|
|
|
if (_getViewFromIcon(source) instanceof FolderView ||
|
|
|
|
|
this._appFavorites.isFavorite(source.id))
|
2020-06-23 11:05:27 -04:00
|
|
|
|
this._ensurePlaceholder(source);
|
2019-06-28 19:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragMotion(dragEvent) {
|
2020-06-24 13:53:12 -04:00
|
|
|
|
if (this._currentDialog)
|
2019-06-28 19:48:22 -04:00
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
return super._onDragMotion(dragEvent);
|
2019-06-28 19:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragEnd() {
|
2020-06-24 13:53:12 -04:00
|
|
|
|
super._onDragEnd();
|
2020-06-23 11:05:27 -04:00
|
|
|
|
this._removePlaceholder();
|
2021-03-18 19:41:16 -04:00
|
|
|
|
this._savePages();
|
2019-06-28 19:48:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
_onDragCancelled(overview, source) {
|
2020-06-23 09:33:22 -04:00
|
|
|
|
const view = _getViewFromIcon(source);
|
|
|
|
|
|
|
|
|
|
if (view instanceof FolderView)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-06-24 13:53:12 -04:00
|
|
|
|
super._onDragCancelled(overview, source);
|
2019-06-29 00:26:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acceptDrop(source) {
|
2020-06-24 13:53:12 -04:00
|
|
|
|
if (!super.acceptDrop(source))
|
2019-06-29 00:26:08 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
2020-05-26 00:41:17 -04:00
|
|
|
|
this._savePages();
|
|
|
|
|
|
2019-06-29 00:26:08 -04:00
|
|
|
|
let view = _getViewFromIcon(source);
|
2020-05-26 10:19:39 -04:00
|
|
|
|
if (view instanceof FolderView)
|
|
|
|
|
view.removeApp(source.app);
|
2019-06-29 00:26:08 -04:00
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (this._currentDialog)
|
|
|
|
|
this._currentDialog.popdown();
|
2019-06-29 00:26:08 -04:00
|
|
|
|
|
2021-01-26 05:33:54 -05:00
|
|
|
|
if (this._appFavorites.isFavorite(source.id))
|
|
|
|
|
this._appFavorites.removeFavorite(source.id);
|
|
|
|
|
|
2019-06-29 00:26:08 -04:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
createFolder(apps) {
|
|
|
|
|
let newFolderId = GLib.uuid_string_random();
|
|
|
|
|
|
|
|
|
|
let folders = this._folderSettings.get_strv('folder-children');
|
|
|
|
|
folders.push(newFolderId);
|
|
|
|
|
this._folderSettings.set_strv('folder-children', folders);
|
|
|
|
|
|
|
|
|
|
// Create the new folder
|
|
|
|
|
let newFolderPath = this._folderSettings.path.concat('folders/', newFolderId, '/');
|
2021-08-12 21:02:05 -04:00
|
|
|
|
let newFolderSettings;
|
|
|
|
|
try {
|
|
|
|
|
newFolderSettings = new Gio.Settings({
|
|
|
|
|
schema_id: 'org.gnome.desktop.app-folders.folder',
|
|
|
|
|
path: newFolderPath,
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
2019-07-16 16:51:25 -04:00
|
|
|
|
log('Error creating new folder');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 21:29:29 -04:00
|
|
|
|
// The hovered AppIcon always passes its own id as the first
|
|
|
|
|
// one, and this is where we want the folder to be created
|
2020-09-24 11:00:03 -04:00
|
|
|
|
let [folderPage, folderPosition] =
|
2020-05-26 21:29:29 -04:00
|
|
|
|
this._grid.getItemPosition(this._items.get(apps[0]));
|
|
|
|
|
|
2020-09-24 11:00:03 -04:00
|
|
|
|
// Adjust the final position
|
|
|
|
|
folderPosition -= apps.reduce((counter, appId) => {
|
|
|
|
|
const [page, position] =
|
|
|
|
|
this._grid.getItemPosition(this._items.get(appId));
|
|
|
|
|
if (page === folderPage && position < folderPosition)
|
|
|
|
|
counter++;
|
|
|
|
|
return counter;
|
|
|
|
|
}, 0);
|
|
|
|
|
|
2019-10-31 19:46:35 -04:00
|
|
|
|
let appItems = apps.map(id => this._items.get(id).app);
|
2019-07-16 16:51:25 -04:00
|
|
|
|
let folderName = _findBestFolderName(appItems);
|
|
|
|
|
if (!folderName)
|
|
|
|
|
folderName = _("Unnamed Folder");
|
|
|
|
|
|
|
|
|
|
newFolderSettings.delay();
|
|
|
|
|
newFolderSettings.set_string('name', folderName);
|
|
|
|
|
newFolderSettings.set_strv('apps', apps);
|
|
|
|
|
newFolderSettings.apply();
|
|
|
|
|
|
2020-05-26 21:29:29 -04:00
|
|
|
|
this._redisplay();
|
|
|
|
|
|
|
|
|
|
// Move the folder to where the icon target icon was
|
|
|
|
|
const folderItem = this._items.get(newFolderId);
|
|
|
|
|
this._moveItem(folderItem, folderPage, folderPosition);
|
|
|
|
|
this._savePages();
|
2019-12-04 15:09:10 -05:00
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2015-01-27 16:10:45 -05:00
|
|
|
|
|
2017-10-30 21:19:44 -04:00
|
|
|
|
var AppSearchProvider = class AppSearchProvider {
|
|
|
|
|
constructor() {
|
2009-11-29 17:45:30 -05:00
|
|
|
|
this._appSys = Shell.AppSystem.get_default();
|
2012-08-09 17:52:36 -04:00
|
|
|
|
this.id = 'applications';
|
2017-06-12 22:24:12 -04:00
|
|
|
|
this.isRemoteProvider = false;
|
|
|
|
|
this.canLaunchSearch = false;
|
2017-08-21 09:20:25 -04:00
|
|
|
|
|
|
|
|
|
this._systemActions = new SystemActions.getDefault();
|
2019-04-24 05:37:10 -04:00
|
|
|
|
|
|
|
|
|
this._parentalControlsManager = ParentalControlsManager.getDefault();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-11-29 17:45:30 -05:00
|
|
|
|
|
2022-06-23 12:13:36 -04:00
|
|
|
|
getResultMetas(apps) {
|
2021-03-21 20:50:25 -04:00
|
|
|
|
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
2012-02-17 10:39:27 -05:00
|
|
|
|
let metas = [];
|
2017-08-21 09:20:25 -04:00
|
|
|
|
for (let id of apps) {
|
|
|
|
|
if (id.endsWith('.desktop')) {
|
|
|
|
|
let app = this._appSys.lookup_app(id);
|
|
|
|
|
|
2019-02-12 09:02:09 -05:00
|
|
|
|
metas.push({
|
|
|
|
|
id: app.get_id(),
|
|
|
|
|
name: app.get_name(),
|
2020-09-02 05:11:39 -04:00
|
|
|
|
createIcon: size => app.create_icon_texture(size),
|
2017-08-21 09:20:25 -04:00
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
let name = this._systemActions.getName(id);
|
|
|
|
|
let iconName = this._systemActions.getIconName(id);
|
|
|
|
|
|
2021-02-16 08:26:12 -05:00
|
|
|
|
const createIcon = size => new St.Icon({
|
|
|
|
|
icon_name: iconName,
|
2021-03-21 20:50:25 -04:00
|
|
|
|
width: size * scaleFactor,
|
|
|
|
|
height: size * scaleFactor,
|
2021-02-16 08:26:12 -05:00
|
|
|
|
style_class: 'system-action-icon',
|
|
|
|
|
});
|
2017-08-21 09:20:25 -04:00
|
|
|
|
|
|
|
|
|
metas.push({ id, name, createIcon });
|
|
|
|
|
}
|
2012-02-17 10:39:27 -05:00
|
|
|
|
}
|
2017-08-21 09:20:25 -04:00
|
|
|
|
|
2022-06-23 12:13:36 -04:00
|
|
|
|
return new Promise(resolve => resolve(metas));
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-11-29 17:45:30 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
filterResults(results, maxNumber) {
|
2013-05-29 16:48:30 -04:00
|
|
|
|
return results.slice(0, maxNumber);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-05-29 16:48:30 -04:00
|
|
|
|
|
2022-06-23 12:13:36 -04:00
|
|
|
|
getInitialResultSet(terms, cancellable) {
|
2019-04-24 05:37:10 -04:00
|
|
|
|
// Defer until the parental controls manager is initialised, so the
|
|
|
|
|
// results can be filtered correctly.
|
|
|
|
|
if (!this._parentalControlsManager.initialized) {
|
2022-06-23 12:13:36 -04:00
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
let initializedId = this._parentalControlsManager.connect('app-filter-changed', async () => {
|
|
|
|
|
if (this._parentalControlsManager.initialized) {
|
|
|
|
|
this._parentalControlsManager.disconnect(initializedId);
|
|
|
|
|
resolve(await this.getInitialResultSet(terms, cancellable));
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-04-24 05:37:10 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-02 20:13:42 -04:00
|
|
|
|
let query = terms.join(' ');
|
2015-07-30 13:55:19 -04:00
|
|
|
|
let groups = Shell.AppSystem.search(query);
|
2013-11-02 20:13:42 -04:00
|
|
|
|
let usage = Shell.AppUsage.get_default();
|
|
|
|
|
let results = [];
|
2019-04-24 05:37:10 -04:00
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
|
groups.forEach(group => {
|
|
|
|
|
group = group.filter(appID => {
|
2020-03-14 09:45:42 -04:00
|
|
|
|
const app = this._appSys.lookup_app(appID);
|
2019-04-24 05:37:10 -04:00
|
|
|
|
return app && this._parentalControlsManager.shouldShowApp(app.app_info);
|
2013-11-02 20:13:42 -04:00
|
|
|
|
});
|
2017-10-30 20:38:18 -04:00
|
|
|
|
results = results.concat(group.sort(
|
2020-04-03 19:52:29 -04:00
|
|
|
|
(a, b) => usage.compare(a, b)));
|
2013-11-02 20:13:42 -04:00
|
|
|
|
});
|
2017-08-21 09:20:25 -04:00
|
|
|
|
|
|
|
|
|
results = results.concat(this._systemActions.getMatchingActions(terms));
|
2022-06-23 12:13:36 -04:00
|
|
|
|
return new Promise(resolve => resolve(results));
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
Kill off ShellAppInfo, move into ShellApp
This dramatically thins down and sanitizes the application code.
The ShellAppSystem changes in a number of ways:
* Preferences are special cased more explicitly; they aren't apps,
they're shortcuts for an app), and we don't have many of them, so
don't need e.g. the optimizations in ShellAppSystem for searching.
* get_app() changes to lookup_app() and returns null if an app isn't
found. The semantics where it tried to find the .desktop file
if we didn't know about it were just broken; I am pretty sure no
caller needs this, and if they do we'll fix them.
* ShellAppSystem maintains two indexes on apps (by desktop file id
and by GMenuTreeEntry), but is no longer in the business of
dealing with GMenuTree as far as hierarchy and categories go. That
is moved up into js/ui/appDisplay.js. Actually, it flattens both
apps and settings.
Also, ShellWindowTracker is now the sole reference-owner for
window-backed apps. We still do the weird "window:0x1234beef" id
for these apps, but a reference is not stored in ShellAppSystem.
The js/ui/appDisplay.js code is rewritten, and sucks a lot less.
Variable names are clearer:
_apps -> _appIcons
_filterApp -> _visibleApps
_filters -> _categoryBox
Similarly for function names. We no longer call (for every app) a
recursive lookup in GMenuTree to see if it's in a particular section
on every category switch; it's all cached.
NOTE - this intentionally reverts the incremental loading code from
commit 7813c5b93f6bcde8c4beae286e82bfc472b2b656. It's fast enough
here without that.
https://bugzilla.gnome.org/show_bug.cgi?id=648149
2011-04-21 13:35:01 -04:00
|
|
|
|
|
2022-06-23 12:13:36 -04:00
|
|
|
|
getSubsearchResultSet(previousResults, terms, cancellable) {
|
|
|
|
|
return this.getInitialResultSet(terms, cancellable);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
Kill off ShellAppInfo, move into ShellApp
This dramatically thins down and sanitizes the application code.
The ShellAppSystem changes in a number of ways:
* Preferences are special cased more explicitly; they aren't apps,
they're shortcuts for an app), and we don't have many of them, so
don't need e.g. the optimizations in ShellAppSystem for searching.
* get_app() changes to lookup_app() and returns null if an app isn't
found. The semantics where it tried to find the .desktop file
if we didn't know about it were just broken; I am pretty sure no
caller needs this, and if they do we'll fix them.
* ShellAppSystem maintains two indexes on apps (by desktop file id
and by GMenuTreeEntry), but is no longer in the business of
dealing with GMenuTree as far as hierarchy and categories go. That
is moved up into js/ui/appDisplay.js. Actually, it flattens both
apps and settings.
Also, ShellWindowTracker is now the sole reference-owner for
window-backed apps. We still do the weird "window:0x1234beef" id
for these apps, but a reference is not stored in ShellAppSystem.
The js/ui/appDisplay.js code is rewritten, and sucks a lot less.
Variable names are clearer:
_apps -> _appIcons
_filterApp -> _visibleApps
_filters -> _categoryBox
Similarly for function names. We no longer call (for every app) a
recursive lookup in GMenuTree to see if it's in a particular section
on every category switch; it's all cached.
NOTE - this intentionally reverts the incremental loading code from
commit 7813c5b93f6bcde8c4beae286e82bfc472b2b656. It's fast enough
here without that.
https://bugzilla.gnome.org/show_bug.cgi?id=648149
2011-04-21 13:35:01 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
createResultObject(resultMeta) {
|
2020-10-21 16:20:59 -04:00
|
|
|
|
if (resultMeta.id.endsWith('.desktop')) {
|
|
|
|
|
return new AppIcon(this._appSys.lookup_app(resultMeta['id']), {
|
|
|
|
|
expandTitleOnHover: false,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2017-08-21 09:20:25 -04:00
|
|
|
|
return new SystemActionIcon(this, resultMeta);
|
2020-10-21 16:20:59 -04:00
|
|
|
|
}
|
2009-11-29 17:45:30 -05:00
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
|
};
|
2013-08-20 04:49:02 -04:00
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
var AppViewItem = GObject.registerClass(
|
|
|
|
|
class AppViewItem extends St.Button {
|
2020-10-21 16:20:59 -04:00
|
|
|
|
_init(params = {}, isDraggable = true, expandTitleOnHover = true) {
|
2020-05-26 17:05:40 -04:00
|
|
|
|
super._init({
|
|
|
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
|
|
|
|
|
reactive: true,
|
|
|
|
|
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
|
|
|
|
|
can_focus: true,
|
|
|
|
|
...params,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._delegate = this;
|
|
|
|
|
|
|
|
|
|
if (isDraggable) {
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
this._draggable = DND.makeDraggable(this, { timeoutThreshold: 200 });
|
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
this._draggable.connect('drag-begin', this._onDragBegin.bind(this));
|
|
|
|
|
this._draggable.connect('drag-cancelled', this._onDragCancelled.bind(this));
|
|
|
|
|
this._draggable.connect('drag-end', this._onDragEnd.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._otherIconIsHovering = false;
|
2020-11-16 13:12:25 -05:00
|
|
|
|
this._expandTitleOnHover = expandTitleOnHover;
|
2020-05-26 17:05:40 -04:00
|
|
|
|
|
2020-10-21 16:20:59 -04:00
|
|
|
|
if (expandTitleOnHover)
|
|
|
|
|
this.connect('notify::hover', this._onHover.bind(this));
|
2020-05-26 17:05:40 -04:00
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDestroy() {
|
|
|
|
|
if (this._dragMonitor) {
|
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
|
this._dragMonitor = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._draggable) {
|
|
|
|
|
if (this._dragging)
|
|
|
|
|
Main.overview.endItemDrag(this);
|
|
|
|
|
this._draggable = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-13 12:34:22 -05:00
|
|
|
|
_updateMultiline() {
|
2020-11-16 13:12:25 -05:00
|
|
|
|
if (!this._expandTitleOnHover || !this.icon.label)
|
2020-10-21 13:33:23 -04:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const { label } = this.icon;
|
|
|
|
|
const { clutterText } = label;
|
|
|
|
|
const layout = clutterText.get_layout();
|
|
|
|
|
if (!layout.is_wrapped() && !layout.is_ellipsized())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
label.remove_transition('allocation');
|
|
|
|
|
|
|
|
|
|
const id = label.connect('notify::allocation', () => {
|
|
|
|
|
label.restore_easing_state();
|
|
|
|
|
label.disconnect(id);
|
|
|
|
|
});
|
|
|
|
|
|
2021-11-17 18:25:48 -05:00
|
|
|
|
const expand = this._forcedHighlight || this.hover || this.has_key_focus();
|
2020-10-21 13:33:23 -04:00
|
|
|
|
label.save_easing_state();
|
2020-11-13 12:35:29 -05:00
|
|
|
|
label.set_easing_duration(expand
|
2020-10-21 13:33:23 -04:00
|
|
|
|
? APP_ICON_TITLE_EXPAND_TIME
|
|
|
|
|
: APP_ICON_TITLE_COLLAPSE_TIME);
|
|
|
|
|
clutterText.set({
|
2020-11-13 12:35:29 -05:00
|
|
|
|
line_wrap: expand,
|
|
|
|
|
line_wrap_mode: expand ? Pango.WrapMode.WORD_CHAR : Pango.WrapMode.NONE,
|
|
|
|
|
ellipsize: expand ? Pango.EllipsizeMode.NONE : Pango.EllipsizeMode.END,
|
2020-10-21 13:33:23 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-13 12:34:22 -05:00
|
|
|
|
_onHover() {
|
|
|
|
|
this._updateMultiline();
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
_onDragBegin() {
|
|
|
|
|
this._dragging = true;
|
|
|
|
|
this.scaleAndFade();
|
|
|
|
|
Main.overview.beginItemDrag(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragCancelled() {
|
|
|
|
|
this._dragging = false;
|
|
|
|
|
Main.overview.cancelledItemDrag(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragEnd() {
|
|
|
|
|
this._dragging = false;
|
|
|
|
|
this.undoScaleAndFade();
|
|
|
|
|
Main.overview.endItemDrag(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scaleIn() {
|
|
|
|
|
this.scale_x = 0;
|
|
|
|
|
this.scale_y = 0;
|
|
|
|
|
|
|
|
|
|
this.ease({
|
|
|
|
|
scale_x: 1,
|
|
|
|
|
scale_y: 1,
|
|
|
|
|
duration: APP_ICON_SCALE_IN_TIME,
|
|
|
|
|
delay: APP_ICON_SCALE_IN_DELAY,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUINT,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scaleAndFade() {
|
|
|
|
|
this.reactive = false;
|
|
|
|
|
this.ease({
|
2020-06-24 11:53:36 -04:00
|
|
|
|
scale_x: 0.5,
|
|
|
|
|
scale_y: 0.5,
|
|
|
|
|
opacity: 0,
|
2020-05-26 17:05:40 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
undoScaleAndFade() {
|
|
|
|
|
this.reactive = true;
|
|
|
|
|
this.ease({
|
|
|
|
|
scale_x: 1.0,
|
|
|
|
|
scale_y: 1.0,
|
|
|
|
|
opacity: 255,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_canAccept(source) {
|
|
|
|
|
return source !== this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_setHoveringByDnd(hovering) {
|
|
|
|
|
if (this._otherIconIsHovering === hovering)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._otherIconIsHovering = hovering;
|
|
|
|
|
|
|
|
|
|
if (hovering) {
|
|
|
|
|
this._dragMonitor = {
|
|
|
|
|
dragMotion: this._onDragMotion.bind(this),
|
|
|
|
|
};
|
|
|
|
|
DND.addDragMonitor(this._dragMonitor);
|
|
|
|
|
} else {
|
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDragMotion(dragEvent) {
|
|
|
|
|
if (!this.contains(dragEvent.targetActor))
|
|
|
|
|
this._setHoveringByDnd(false);
|
|
|
|
|
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_withinLeeways(x) {
|
|
|
|
|
return x < IconGrid.LEFT_DIVIDER_LEEWAY ||
|
|
|
|
|
x > this.width - IconGrid.RIGHT_DIVIDER_LEEWAY;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-13 12:35:29 -05:00
|
|
|
|
vfunc_key_focus_in() {
|
|
|
|
|
this._updateMultiline();
|
|
|
|
|
super.vfunc_key_focus_in();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_key_focus_out() {
|
|
|
|
|
this._updateMultiline();
|
|
|
|
|
super.vfunc_key_focus_out();
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
handleDragOver(source, _actor, x) {
|
|
|
|
|
if (source === this)
|
|
|
|
|
return DND.DragMotionResult.NO_DROP;
|
|
|
|
|
|
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
|
|
|
|
|
if (this._withinLeeways(x)) {
|
|
|
|
|
this._setHoveringByDnd(false);
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._setHoveringByDnd(true);
|
|
|
|
|
|
|
|
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acceptDrop(source, _actor, x) {
|
|
|
|
|
this._setHoveringByDnd(false);
|
|
|
|
|
|
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (this._withinLeeways(x))
|
2020-08-03 15:47:42 -04:00
|
|
|
|
return false;
|
2020-05-26 17:05:40 -04:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
cancelActions() {
|
|
|
|
|
if (this._draggable)
|
|
|
|
|
this._draggable.fakeRelease();
|
|
|
|
|
this.fake_release();
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
get id() {
|
|
|
|
|
return this._id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get name() {
|
|
|
|
|
return this._name;
|
|
|
|
|
}
|
2021-11-17 18:25:48 -05:00
|
|
|
|
|
|
|
|
|
setForcedHighlight(highlighted) {
|
|
|
|
|
this._forcedHighlight = highlighted;
|
|
|
|
|
this.set({
|
|
|
|
|
track_hover: !highlighted,
|
|
|
|
|
hover: highlighted,
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-05-26 17:05:40 -04:00
|
|
|
|
});
|
|
|
|
|
|
2020-05-20 18:20:15 -04:00
|
|
|
|
var FolderGrid = GObject.registerClass(
|
2022-06-10 14:25:05 -04:00
|
|
|
|
class FolderGrid extends AppGrid {
|
2020-05-20 18:20:15 -04:00
|
|
|
|
_init() {
|
|
|
|
|
super._init({
|
|
|
|
|
allow_incomplete_pages: false,
|
|
|
|
|
columns_per_page: 3,
|
|
|
|
|
rows_per_page: 3,
|
|
|
|
|
page_halign: Clutter.ActorAlign.CENTER,
|
|
|
|
|
page_valign: Clutter.ActorAlign.CENTER,
|
|
|
|
|
});
|
2022-06-09 14:32:26 -04:00
|
|
|
|
|
|
|
|
|
this.setGridModes([
|
|
|
|
|
{
|
|
|
|
|
rows: 3,
|
|
|
|
|
columns: 3,
|
|
|
|
|
},
|
|
|
|
|
]);
|
2020-05-20 18:20:15 -04:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
var FolderView = GObject.registerClass(
|
|
|
|
|
class FolderView extends BaseAppView {
|
|
|
|
|
_init(folder, id, parentView) {
|
|
|
|
|
super._init({
|
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
|
x_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
y_expand: true,
|
2021-05-11 10:44:29 -04:00
|
|
|
|
gesture_modes: Shell.ActionMode.POPUP,
|
2020-05-27 22:29:16 -04:00
|
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
|
|
2013-08-30 12:50:35 -04:00
|
|
|
|
// If it not expand, the parent doesn't take into account its preferred_width when allocating
|
|
|
|
|
// the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
|
2018-07-02 10:03:07 -04:00
|
|
|
|
this._grid.x_expand = true;
|
2019-07-16 16:51:25 -04:00
|
|
|
|
this._id = id;
|
2019-06-29 11:58:26 -04:00
|
|
|
|
this._folder = folder;
|
|
|
|
|
this._parentView = parentView;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
this._grid._delegate = this;
|
2013-08-30 12:50:35 -04:00
|
|
|
|
|
2020-12-29 17:18:14 -05:00
|
|
|
|
this.add_child(this._box);
|
2020-05-20 18:20:15 -04:00
|
|
|
|
|
2013-08-23 14:49:27 -04:00
|
|
|
|
let action = new Clutter.PanAction({ interpolate: true });
|
2017-12-01 19:27:35 -05:00
|
|
|
|
action.connect('pan', this._onPan.bind(this));
|
2019-08-30 23:21:10 -04:00
|
|
|
|
this._scrollView.add_action(action);
|
2019-06-29 11:58:26 -04:00
|
|
|
|
|
2020-09-24 12:36:40 -04:00
|
|
|
|
this._deletingFolder = false;
|
2020-08-13 19:20:52 -04:00
|
|
|
|
this._appIds = [];
|
2019-06-29 11:58:26 -04:00
|
|
|
|
this._redisplay();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-08-20 04:49:02 -04:00
|
|
|
|
|
2020-05-20 18:20:15 -04:00
|
|
|
|
_createGrid() {
|
|
|
|
|
return new FolderGrid();
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 14:11:51 -04:00
|
|
|
|
_getFolderApps() {
|
|
|
|
|
const appIds = [];
|
|
|
|
|
const excludedApps = this._folder.get_strv('excluded-apps');
|
|
|
|
|
const appSys = Shell.AppSystem.get_default();
|
|
|
|
|
const addAppId = appId => {
|
|
|
|
|
if (excludedApps.includes(appId))
|
|
|
|
|
return;
|
|
|
|
|
|
2021-01-19 19:44:21 -05:00
|
|
|
|
if (this._appFavorites.isFavorite(appId))
|
|
|
|
|
return;
|
|
|
|
|
|
2020-06-24 14:11:51 -04:00
|
|
|
|
const app = appSys.lookup_app(appId);
|
|
|
|
|
if (!app)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!this._parentalControlsManager.shouldShowApp(app.get_app_info()))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (appIds.indexOf(appId) !== -1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
appIds.push(appId);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const folderApps = this._folder.get_strv('apps');
|
|
|
|
|
folderApps.forEach(addAppId);
|
|
|
|
|
|
|
|
|
|
const folderCategories = this._folder.get_strv('categories');
|
|
|
|
|
const appInfos = this._parentView.getAppInfos();
|
|
|
|
|
appInfos.forEach(appInfo => {
|
|
|
|
|
let appCategories = _getCategories(appInfo);
|
|
|
|
|
if (!_listsIntersect(folderCategories, appCategories))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
addAppId(appInfo.get_id());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return appIds;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 14:37:42 -04:00
|
|
|
|
_getItemPosition(item) {
|
2020-08-13 19:20:52 -04:00
|
|
|
|
const appIndex = this._appIds.indexOf(item.id);
|
2020-06-24 14:37:42 -04:00
|
|
|
|
|
|
|
|
|
if (appIndex === -1)
|
|
|
|
|
return [-1, -1];
|
|
|
|
|
|
|
|
|
|
const { itemsPerPage } = this._grid;
|
|
|
|
|
return [Math.floor(appIndex / itemsPerPage), appIndex % itemsPerPage];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_compareItems(a, b) {
|
2020-08-13 19:20:52 -04:00
|
|
|
|
const aPosition = this._appIds.indexOf(a.id);
|
|
|
|
|
const bPosition = this._appIds.indexOf(b.id);
|
2020-06-24 14:37:42 -04:00
|
|
|
|
|
|
|
|
|
if (aPosition === -1 && bPosition === -1)
|
|
|
|
|
return a.name.localeCompare(b.name);
|
|
|
|
|
else if (aPosition === -1)
|
|
|
|
|
return 1;
|
|
|
|
|
else if (bPosition === -1)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
return aPosition - bPosition;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
createFolderIcon(size) {
|
2021-01-19 20:03:49 -05:00
|
|
|
|
const layout = new Clutter.GridLayout({
|
|
|
|
|
row_homogeneous: true,
|
|
|
|
|
column_homogeneous: true,
|
|
|
|
|
});
|
2019-11-11 16:07:19 -05:00
|
|
|
|
let icon = new St.Widget({
|
|
|
|
|
layout_manager: layout,
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
2022-02-07 09:14:06 -05:00
|
|
|
|
style: `width: ${size}px; height: ${size}px;`,
|
2019-11-11 16:07:19 -05:00
|
|
|
|
});
|
2021-01-19 20:03:49 -05:00
|
|
|
|
|
2013-08-20 04:49:02 -04:00
|
|
|
|
let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
|
|
|
|
|
|
2019-10-31 19:44:02 -04:00
|
|
|
|
let numItems = this._orderedItems.length;
|
2014-06-20 11:46:22 -04:00
|
|
|
|
let rtl = icon.get_text_direction() == Clutter.TextDirection.RTL;
|
2014-03-14 10:50:18 -04:00
|
|
|
|
for (let i = 0; i < 4; i++) {
|
2022-02-07 09:14:06 -05:00
|
|
|
|
const style = `width: ${subSize}px; height: ${subSize}px;`;
|
2020-04-03 17:09:09 -04:00
|
|
|
|
let bin = new St.Bin({ style });
|
2017-08-11 05:04:37 -04:00
|
|
|
|
if (i < numItems)
|
2019-10-31 19:44:02 -04:00
|
|
|
|
bin.child = this._orderedItems[i].app.create_icon_texture(subSize);
|
2014-08-05 17:26:45 -04:00
|
|
|
|
layout.attach(bin, rtl ? (i + 1) % 2 : i % 2, Math.floor(i / 2), 1, 1);
|
2013-08-20 04:49:02 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return icon;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-08-30 12:50:35 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onPan(action) {
|
2019-01-31 09:08:00 -05:00
|
|
|
|
let [dist_, dx_, dy] = action.get_motion_delta(0);
|
2019-08-30 23:21:10 -04:00
|
|
|
|
let adjustment = this._scrollView.vscroll.adjustment;
|
|
|
|
|
adjustment.value -= (dy / this._scrollView.height) * adjustment.page_size;
|
2013-08-23 14:49:27 -04:00
|
|
|
|
return false;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-08-23 14:49:27 -04:00
|
|
|
|
|
2019-06-29 11:58:26 -04:00
|
|
|
|
_loadApps() {
|
2019-07-01 22:13:07 -04:00
|
|
|
|
let apps = [];
|
2019-06-29 11:58:26 -04:00
|
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
|
|
2020-08-13 19:20:52 -04:00
|
|
|
|
this._appIds.forEach(appId => {
|
2020-06-24 14:11:51 -04:00
|
|
|
|
const app = appSys.lookup_app(appId);
|
2019-07-30 11:15:51 -04:00
|
|
|
|
|
2019-10-31 19:46:35 -04:00
|
|
|
|
let icon = this._items.get(appId);
|
2019-11-23 15:44:55 -05:00
|
|
|
|
if (!icon)
|
|
|
|
|
icon = new AppIcon(app);
|
|
|
|
|
|
2019-07-01 22:13:07 -04:00
|
|
|
|
apps.push(icon);
|
2019-06-29 11:58:26 -04:00
|
|
|
|
});
|
2019-07-01 22:13:07 -04:00
|
|
|
|
|
|
|
|
|
return apps;
|
2019-06-29 11:58:26 -04:00
|
|
|
|
}
|
2019-06-29 00:26:08 -04:00
|
|
|
|
|
2020-08-13 19:20:52 -04:00
|
|
|
|
_redisplay() {
|
|
|
|
|
// Keep the app ids list cached
|
|
|
|
|
this._appIds = this._getFolderApps();
|
|
|
|
|
|
|
|
|
|
super._redisplay();
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 14:37:42 -04:00
|
|
|
|
acceptDrop(source) {
|
|
|
|
|
if (!super.acceptDrop(source))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const folderApps = this._orderedItems.map(item => item.id);
|
|
|
|
|
this._folder.set_strv('apps', folderApps);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-17 10:56:10 -05:00
|
|
|
|
addApp(app) {
|
|
|
|
|
let folderApps = this._folder.get_strv('apps');
|
|
|
|
|
folderApps.push(app.id);
|
|
|
|
|
|
|
|
|
|
this._folder.set_strv('apps', folderApps);
|
|
|
|
|
|
|
|
|
|
// Also remove from 'excluded-apps' if the app id is listed
|
|
|
|
|
// there. This is only possible on categories-based folders.
|
|
|
|
|
let excludedApps = this._folder.get_strv('excluded-apps');
|
|
|
|
|
let index = excludedApps.indexOf(app.id);
|
|
|
|
|
if (index >= 0) {
|
|
|
|
|
excludedApps.splice(index, 1);
|
|
|
|
|
this._folder.set_strv('excluded-apps', excludedApps);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 00:26:08 -04:00
|
|
|
|
removeApp(app) {
|
|
|
|
|
let folderApps = this._folder.get_strv('apps');
|
|
|
|
|
let index = folderApps.indexOf(app.id);
|
2019-07-16 16:51:25 -04:00
|
|
|
|
if (index >= 0)
|
2019-06-29 00:26:08 -04:00
|
|
|
|
folderApps.splice(index, 1);
|
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
// Remove the folder if this is the last app icon; otherwise,
|
|
|
|
|
// just remove the icon
|
|
|
|
|
if (folderApps.length == 0) {
|
2020-09-24 12:36:40 -04:00
|
|
|
|
this._deletingFolder = true;
|
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
// Resetting all keys deletes the relocatable schema
|
|
|
|
|
let keys = this._folder.settings_schema.list_keys();
|
2020-09-24 11:17:58 -04:00
|
|
|
|
for (const key of keys)
|
2019-07-16 16:51:25 -04:00
|
|
|
|
this._folder.reset(key);
|
2019-11-21 16:45:12 -05:00
|
|
|
|
|
|
|
|
|
let settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
|
|
|
|
|
let folders = settings.get_strv('folder-children');
|
|
|
|
|
folders.splice(folders.indexOf(this._id), 1);
|
|
|
|
|
settings.set_strv('folder-children', folders);
|
2020-09-24 12:36:40 -04:00
|
|
|
|
|
|
|
|
|
this._deletingFolder = false;
|
2019-07-16 16:51:25 -04:00
|
|
|
|
} else {
|
2020-09-24 11:41:27 -04:00
|
|
|
|
// If this is a categories-based folder, also add it to
|
|
|
|
|
// the list of excluded apps
|
|
|
|
|
const categories = this._folder.get_strv('categories');
|
|
|
|
|
if (categories.length > 0) {
|
|
|
|
|
const excludedApps = this._folder.get_strv('excluded-apps');
|
|
|
|
|
excludedApps.push(app.id);
|
|
|
|
|
this._folder.set_strv('excluded-apps', excludedApps);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
this._folder.set_strv('apps', folderApps);
|
|
|
|
|
}
|
2019-06-29 00:26:08 -04:00
|
|
|
|
}
|
2020-09-24 12:36:40 -04:00
|
|
|
|
|
|
|
|
|
get deletingFolder() {
|
|
|
|
|
return this._deletingFolder;
|
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
var FolderIcon = GObject.registerClass({
|
|
|
|
|
Signals: {
|
|
|
|
|
'apps-changed': {},
|
2019-08-20 17:43:54 -04:00
|
|
|
|
},
|
2020-05-26 17:26:52 -04:00
|
|
|
|
}, class FolderIcon extends AppViewItem {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
_init(id, path, parentView) {
|
|
|
|
|
super._init({
|
|
|
|
|
style_class: 'app-well-app app-folder',
|
|
|
|
|
button_mask: St.ButtonMask.ONE,
|
|
|
|
|
toggle_mode: true,
|
|
|
|
|
can_focus: true,
|
2020-05-26 17:26:52 -04:00
|
|
|
|
}, global.settings.is_writable('app-picker-layout'));
|
|
|
|
|
this._id = id;
|
|
|
|
|
this._name = '';
|
2013-01-31 11:13:37 -05:00
|
|
|
|
this._parentView = parentView;
|
|
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
|
this._folder = new Gio.Settings({
|
|
|
|
|
schema_id: 'org.gnome.desktop.app-folders.folder',
|
|
|
|
|
path,
|
|
|
|
|
});
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2019-06-28 22:46:16 -04:00
|
|
|
|
this.icon = new IconGrid.BaseIcon('', {
|
|
|
|
|
createIcon: this._createIcon.bind(this),
|
2019-08-20 17:43:54 -04:00
|
|
|
|
setSizeManually: true,
|
2019-06-28 22:46:16 -04:00
|
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.set_child(this.icon);
|
|
|
|
|
this.label_actor = this.icon.label;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
this.view = new FolderView(this._folder, id, parentView);
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2021-08-15 18:36:59 -04:00
|
|
|
|
this._folder.connectObject(
|
|
|
|
|
'changed', this._sync.bind(this), this);
|
2020-02-17 09:35:49 -05:00
|
|
|
|
this._sync();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
_onDestroy() {
|
2020-05-26 17:26:52 -04:00
|
|
|
|
super._onDestroy();
|
2019-11-23 13:57:02 -05:00
|
|
|
|
|
2020-05-20 15:22:36 -04:00
|
|
|
|
if (this._dialog)
|
|
|
|
|
this._dialog.destroy();
|
|
|
|
|
else
|
|
|
|
|
this.view.destroy();
|
2019-07-22 10:57:57 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_clicked() {
|
|
|
|
|
this.open();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_unmap() {
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (this._dialog)
|
|
|
|
|
this._dialog.popdown();
|
2020-02-07 04:57:19 -05:00
|
|
|
|
|
|
|
|
|
super.vfunc_unmap();
|
2019-09-10 01:42:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-18 10:19:13 -04:00
|
|
|
|
open() {
|
2019-12-12 14:00:01 -05:00
|
|
|
|
this._ensureFolderDialog();
|
2019-08-30 23:21:10 -04:00
|
|
|
|
this.view._scrollView.vscroll.adjustment.value = 0;
|
2019-12-12 14:00:01 -05:00
|
|
|
|
this._dialog.popup();
|
2019-07-18 10:19:13 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
getAppIds() {
|
2017-10-30 20:38:18 -04:00
|
|
|
|
return this.view.getAllItems().map(item => item.id);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
|
_setHoveringByDnd(hovering) {
|
2020-05-26 17:26:52 -04:00
|
|
|
|
if (this._otherIconIsHovering == hovering)
|
2019-10-29 19:06:10 -04:00
|
|
|
|
return;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
|
2020-05-26 17:26:52 -04:00
|
|
|
|
super._setHoveringByDnd(hovering);
|
2019-07-01 20:37:35 -04:00
|
|
|
|
|
2020-05-26 17:26:52 -04:00
|
|
|
|
if (hovering)
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.add_style_pseudo_class('drop');
|
2020-05-26 17:26:52 -04:00
|
|
|
|
else
|
2019-10-29 19:06:10 -04:00
|
|
|
|
this.remove_style_pseudo_class('drop');
|
2019-07-01 20:37:35 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
|
_onDragMotion(dragEvent) {
|
2020-05-26 17:26:52 -04:00
|
|
|
|
if (!this._canAccept(dragEvent.source))
|
2019-10-29 19:06:10 -04:00
|
|
|
|
this._setHoveringByDnd(false);
|
|
|
|
|
|
2020-05-26 17:26:52 -04:00
|
|
|
|
return super._onDragMotion(dragEvent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getDragActor() {
|
|
|
|
|
const iconParams = {
|
|
|
|
|
createIcon: this._createIcon.bind(this),
|
|
|
|
|
showLabel: this.icon.label !== null,
|
|
|
|
|
setSizeManually: false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const icon = new IconGrid.BaseIcon(this.name, iconParams);
|
|
|
|
|
icon.style_class = this.style_class;
|
|
|
|
|
|
|
|
|
|
return icon;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getDragActorSource() {
|
|
|
|
|
return this;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_canAccept(source) {
|
|
|
|
|
if (!(source instanceof AppIcon))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
let view = _getViewFromIcon(source);
|
2019-12-04 11:43:57 -05:00
|
|
|
|
if (!view || !(view instanceof AppDisplay))
|
2019-06-28 18:47:32 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (this._folder.get_strv('apps').includes(source.id))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acceptDrop(source) {
|
2020-05-26 17:26:52 -04:00
|
|
|
|
const accepted = super.acceptDrop(source);
|
2019-10-29 19:06:10 -04:00
|
|
|
|
|
2020-05-26 17:26:52 -04:00
|
|
|
|
if (!accepted)
|
2019-08-09 09:56:37 -04:00
|
|
|
|
return false;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
|
2020-02-17 10:56:10 -05:00
|
|
|
|
this.view.addApp(source.app);
|
2019-06-28 18:47:32 -04:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_updateName() {
|
2014-01-28 11:36:57 -05:00
|
|
|
|
let name = _getFolderName(this._folder);
|
|
|
|
|
if (this.name == name)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-05-26 17:26:52 -04:00
|
|
|
|
this._name = name;
|
2014-01-28 11:36:57 -05:00
|
|
|
|
this.icon.label.text = this.name;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
|
2020-02-17 09:35:49 -05:00
|
|
|
|
_sync() {
|
2020-09-24 12:36:40 -04:00
|
|
|
|
if (this.view.deletingFolder)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-02-17 10:21:58 -05:00
|
|
|
|
this.emit('apps-changed');
|
2014-01-28 11:36:57 -05:00
|
|
|
|
this._updateName();
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.visible = this.view.getAllItems().length > 0;
|
2019-06-28 18:49:18 -04:00
|
|
|
|
this.icon.update();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_createIcon(iconSize) {
|
2013-08-15 04:38:17 -04:00
|
|
|
|
return this.view.createFolderIcon(iconSize, this);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
_ensureFolderDialog() {
|
2019-12-12 14:07:37 -05:00
|
|
|
|
if (this._dialog)
|
2013-08-30 12:50:35 -04:00
|
|
|
|
return;
|
2019-12-12 14:00:01 -05:00
|
|
|
|
if (!this._dialog) {
|
2020-06-04 21:32:14 -04:00
|
|
|
|
this._dialog = new AppFolderDialog(this, this._folder,
|
|
|
|
|
this._parentView);
|
2019-12-12 14:00:01 -05:00
|
|
|
|
this._parentView.addFolderDialog(this._dialog);
|
|
|
|
|
this._dialog.connect('open-state-changed', (popup, isOpen) => {
|
2020-06-04 22:24:20 -04:00
|
|
|
|
const duration = FOLDER_DIALOG_ANIMATION_TIME / 2;
|
|
|
|
|
const mode = isOpen
|
|
|
|
|
? Clutter.AnimationMode.EASE_OUT_QUAD
|
|
|
|
|
: Clutter.AnimationMode.EASE_IN_QUAD;
|
|
|
|
|
|
|
|
|
|
this.ease({
|
|
|
|
|
opacity: isOpen ? 0 : 255,
|
|
|
|
|
duration,
|
|
|
|
|
mode,
|
|
|
|
|
delay: isOpen ? 0 : FOLDER_DIALOG_ANIMATION_TIME - duration,
|
|
|
|
|
});
|
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
|
if (!isOpen)
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.checked = false;
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2013-01-31 11:13:37 -05:00
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2019-09-13 21:03:15 -04:00
|
|
|
|
});
|
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
|
var AppFolderDialog = GObject.registerClass({
|
2019-07-16 05:24:13 -04:00
|
|
|
|
Signals: {
|
|
|
|
|
'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
|
2019-08-20 17:43:54 -04:00
|
|
|
|
},
|
2020-06-04 21:00:10 -04:00
|
|
|
|
}, class AppFolderDialog extends St.Bin {
|
2020-06-04 21:32:14 -04:00
|
|
|
|
_init(source, folder, appDisplay) {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
super._init({
|
|
|
|
|
visible: false,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
2020-06-04 00:43:11 -04:00
|
|
|
|
reactive: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
2021-01-25 11:08:56 -05:00
|
|
|
|
this.add_constraint(new Layout.MonitorConstraint({ primary: true }));
|
2020-06-04 00:49:14 -04:00
|
|
|
|
|
|
|
|
|
const clickAction = new Clutter.ClickAction();
|
|
|
|
|
clickAction.connect('clicked', () => {
|
|
|
|
|
const [x, y] = clickAction.get_coords();
|
|
|
|
|
const actor =
|
|
|
|
|
global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
|
|
|
|
|
|
|
|
|
if (actor === this)
|
|
|
|
|
this.popdown();
|
|
|
|
|
});
|
|
|
|
|
this.add_action(clickAction);
|
2020-06-04 00:37:48 -04:00
|
|
|
|
|
2013-01-31 11:13:37 -05:00
|
|
|
|
this._source = source;
|
2019-12-17 14:39:24 -05:00
|
|
|
|
this._folder = folder;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
this._view = source.view;
|
2020-06-04 21:32:14 -04:00
|
|
|
|
this._appDisplay = appDisplay;
|
|
|
|
|
this._delegate = this;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
|
|
|
|
this._isOpen = false;
|
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
|
this._viewBox = new St.BoxLayout({
|
|
|
|
|
style_class: 'app-folder-dialog',
|
2019-08-20 17:43:54 -04:00
|
|
|
|
x_expand: true,
|
2019-12-12 13:54:18 -05:00
|
|
|
|
y_expand: true,
|
|
|
|
|
x_align: Clutter.ActorAlign.FILL,
|
|
|
|
|
y_align: Clutter.ActorAlign.FILL,
|
2019-12-17 14:39:24 -05:00
|
|
|
|
vertical: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
});
|
2020-06-04 21:00:10 -04:00
|
|
|
|
|
|
|
|
|
this.child = new St.Bin({
|
2020-06-04 00:43:11 -04:00
|
|
|
|
style_class: 'app-folder-dialog-container',
|
|
|
|
|
child: this._viewBox,
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
2020-06-04 21:00:10 -04:00
|
|
|
|
});
|
2019-12-17 14:39:24 -05:00
|
|
|
|
|
|
|
|
|
this._addFolderNameEntry();
|
2019-12-12 13:54:18 -05:00
|
|
|
|
this._viewBox.add_child(this._view);
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
global.focus_manager.add_group(this);
|
2013-03-06 15:42:36 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this._grabHelper = new GrabHelper.GrabHelper(this, {
|
2019-08-20 17:43:54 -04:00
|
|
|
|
actionMode: Shell.ActionMode.POPUP,
|
2018-10-11 14:12:54 -04:00
|
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
2020-06-25 15:51:30 -04:00
|
|
|
|
this._dragMonitor = null;
|
2019-12-12 13:54:18 -05:00
|
|
|
|
this._sourceMappedId = 0;
|
2020-06-04 21:32:14 -04:00
|
|
|
|
this._popdownTimeoutId = 0;
|
2019-12-12 13:54:18 -05:00
|
|
|
|
this._needsZoomAndFade = false;
|
2020-09-24 15:31:00 -04:00
|
|
|
|
|
|
|
|
|
this._popdownCallbacks = [];
|
2019-12-12 13:54:18 -05:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 14:39:24 -05:00
|
|
|
|
_addFolderNameEntry() {
|
|
|
|
|
this._entryBox = new St.BoxLayout({
|
|
|
|
|
style_class: 'folder-name-container',
|
|
|
|
|
});
|
|
|
|
|
this._viewBox.add_child(this._entryBox);
|
|
|
|
|
|
|
|
|
|
// Empty actor to center the title
|
|
|
|
|
let ghostButton = new Clutter.Actor();
|
|
|
|
|
this._entryBox.add_child(ghostButton);
|
|
|
|
|
|
|
|
|
|
let stack = new Shell.Stack({
|
|
|
|
|
x_expand: true,
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
});
|
|
|
|
|
this._entryBox.add_child(stack);
|
|
|
|
|
|
|
|
|
|
// Folder name label
|
|
|
|
|
this._folderNameLabel = new St.Label({
|
|
|
|
|
style_class: 'folder-name-label',
|
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
stack.add_child(this._folderNameLabel);
|
|
|
|
|
|
|
|
|
|
// Folder name entry
|
|
|
|
|
this._entry = new St.Entry({
|
|
|
|
|
style_class: 'folder-name-entry',
|
|
|
|
|
opacity: 0,
|
|
|
|
|
reactive: false,
|
|
|
|
|
});
|
|
|
|
|
this._entry.clutter_text.set({
|
|
|
|
|
x_expand: true,
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._entry.clutter_text.connect('activate', () => {
|
|
|
|
|
this._showFolderLabel();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
stack.add_child(this._entry);
|
|
|
|
|
|
|
|
|
|
// Edit button
|
|
|
|
|
this._editButton = new St.Button({
|
|
|
|
|
style_class: 'edit-folder-button',
|
|
|
|
|
button_mask: St.ButtonMask.ONE,
|
|
|
|
|
toggle_mode: true,
|
|
|
|
|
reactive: true,
|
|
|
|
|
can_focus: true,
|
|
|
|
|
x_align: Clutter.ActorAlign.END,
|
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
2022-03-21 11:44:24 -04:00
|
|
|
|
icon_name: 'document-edit-symbolic',
|
2019-12-17 14:39:24 -05:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._editButton.connect('notify::checked', () => {
|
|
|
|
|
if (this._editButton.checked)
|
|
|
|
|
this._showFolderEntry();
|
|
|
|
|
else
|
|
|
|
|
this._showFolderLabel();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._entryBox.add_child(this._editButton);
|
|
|
|
|
|
|
|
|
|
ghostButton.add_constraint(new Clutter.BindConstraint({
|
|
|
|
|
source: this._editButton,
|
|
|
|
|
coordinate: Clutter.BindCoordinate.SIZE,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
this._folder.connect('changed::name', () => this._syncFolderName());
|
|
|
|
|
this._syncFolderName();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_syncFolderName() {
|
|
|
|
|
let newName = _getFolderName(this._folder);
|
|
|
|
|
|
|
|
|
|
this._folderNameLabel.text = newName;
|
|
|
|
|
this._entry.text = newName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_switchActor(from, to) {
|
|
|
|
|
to.reactive = true;
|
|
|
|
|
to.ease({
|
|
|
|
|
opacity: 255,
|
|
|
|
|
duration: 300,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
from.ease({
|
|
|
|
|
opacity: 0,
|
|
|
|
|
duration: 300,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
|
onComplete: () => {
|
|
|
|
|
from.reactive = false;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_showFolderLabel() {
|
|
|
|
|
if (this._editButton.checked)
|
|
|
|
|
this._editButton.checked = false;
|
|
|
|
|
|
|
|
|
|
this._maybeUpdateFolderName();
|
|
|
|
|
this._switchActor(this._entry, this._folderNameLabel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_showFolderEntry() {
|
|
|
|
|
this._switchActor(this._folderNameLabel, this._entry);
|
|
|
|
|
|
|
|
|
|
this._entry.clutter_text.set_selection(0, -1);
|
|
|
|
|
this._entry.clutter_text.grab_key_focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_maybeUpdateFolderName() {
|
|
|
|
|
let folderName = _getFolderName(this._folder);
|
|
|
|
|
let newFolderName = this._entry.text.trim();
|
|
|
|
|
|
|
|
|
|
if (newFolderName.length === 0 || newFolderName === folderName)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._folder.set_string('name', newFolderName);
|
|
|
|
|
this._folder.set_boolean('translate', false);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
|
_zoomAndFadeIn() {
|
|
|
|
|
let [sourceX, sourceY] =
|
|
|
|
|
this._source.get_transformed_position();
|
|
|
|
|
let [dialogX, dialogY] =
|
2020-06-04 21:05:11 -04:00
|
|
|
|
this.child.get_transformed_position();
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
2020-06-04 21:05:11 -04:00
|
|
|
|
this.child.set({
|
2019-12-12 13:54:18 -05:00
|
|
|
|
translation_x: sourceX - dialogX,
|
|
|
|
|
translation_y: sourceY - dialogY,
|
2020-06-04 21:05:11 -04:00
|
|
|
|
scale_x: this._source.width / this.child.width,
|
|
|
|
|
scale_y: this._source.height / this.child.height,
|
2019-12-12 13:54:18 -05:00
|
|
|
|
opacity: 0,
|
|
|
|
|
});
|
|
|
|
|
|
2020-06-04 23:31:13 -04:00
|
|
|
|
this.ease({
|
2020-09-24 09:17:53 -04:00
|
|
|
|
background_color: DIALOG_SHADE_NORMAL,
|
2020-06-04 23:31:13 -04:00
|
|
|
|
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
|
});
|
2020-06-04 21:05:11 -04:00
|
|
|
|
this.child.ease({
|
2019-12-12 13:54:18 -05:00
|
|
|
|
translation_x: 0,
|
|
|
|
|
translation_y: 0,
|
|
|
|
|
scale_x: 1,
|
|
|
|
|
scale_y: 1,
|
|
|
|
|
opacity: 255,
|
|
|
|
|
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._needsZoomAndFade = false;
|
|
|
|
|
|
|
|
|
|
if (this._sourceMappedId === 0) {
|
|
|
|
|
this._sourceMappedId = this._source.connect(
|
|
|
|
|
'notify::mapped', this._zoomAndFadeOut.bind(this));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_zoomAndFadeOut() {
|
|
|
|
|
if (!this._isOpen)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!this._source.mapped) {
|
|
|
|
|
this.hide();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let [sourceX, sourceY] =
|
|
|
|
|
this._source.get_transformed_position();
|
|
|
|
|
let [dialogX, dialogY] =
|
2020-06-04 21:05:11 -04:00
|
|
|
|
this.child.get_transformed_position();
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
2020-06-04 23:31:13 -04:00
|
|
|
|
this.ease({
|
|
|
|
|
background_color: Clutter.Color.from_pixel(0x00000000),
|
|
|
|
|
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
|
});
|
|
|
|
|
|
2020-06-04 21:05:11 -04:00
|
|
|
|
this.child.ease({
|
2019-12-12 13:54:18 -05:00
|
|
|
|
translation_x: sourceX - dialogX,
|
|
|
|
|
translation_y: sourceY - dialogY,
|
2020-06-04 21:05:11 -04:00
|
|
|
|
scale_x: this._source.width / this.child.width,
|
|
|
|
|
scale_y: this._source.height / this.child.height,
|
2019-12-12 13:54:18 -05:00
|
|
|
|
opacity: 0,
|
|
|
|
|
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
|
onComplete: () => {
|
2020-06-04 21:05:11 -04:00
|
|
|
|
this.child.set({
|
2019-12-12 13:54:18 -05:00
|
|
|
|
translation_x: 0,
|
|
|
|
|
translation_y: 0,
|
|
|
|
|
scale_x: 1,
|
|
|
|
|
scale_y: 1,
|
|
|
|
|
opacity: 255,
|
|
|
|
|
});
|
|
|
|
|
this.hide();
|
2020-09-24 15:31:00 -04:00
|
|
|
|
|
|
|
|
|
this._popdownCallbacks.forEach(func => func());
|
|
|
|
|
this._popdownCallbacks = [];
|
2019-12-12 13:54:18 -05:00
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._needsZoomAndFade = false;
|
2019-07-22 11:02:10 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-25 15:51:30 -04:00
|
|
|
|
_removeDragMonitor() {
|
|
|
|
|
if (!this._dragMonitor)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
|
this._dragMonitor = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_removePopdownTimeout() {
|
|
|
|
|
if (this._popdownTimeoutId === 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
GLib.source_remove(this._popdownTimeoutId);
|
|
|
|
|
this._popdownTimeoutId = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 11:02:10 -04:00
|
|
|
|
_onDestroy() {
|
|
|
|
|
if (this._isOpen) {
|
|
|
|
|
this._isOpen = false;
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this._grabHelper.ungrab({ actor: this });
|
2019-07-22 11:02:10 -04:00
|
|
|
|
this._grabHelper = null;
|
|
|
|
|
}
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
|
|
|
|
if (this._sourceMappedId) {
|
|
|
|
|
this._source.disconnect(this._sourceMappedId);
|
|
|
|
|
this._sourceMappedId = 0;
|
|
|
|
|
}
|
2020-06-04 21:32:14 -04:00
|
|
|
|
|
2020-06-25 15:51:30 -04:00
|
|
|
|
this._removePopdownTimeout();
|
|
|
|
|
this._removeDragMonitor();
|
2019-12-12 13:54:18 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-09 15:30:26 -04:00
|
|
|
|
vfunc_allocate(box) {
|
|
|
|
|
super.vfunc_allocate(box);
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
|
|
|
|
// We can only start zooming after receiving an allocation
|
|
|
|
|
if (this._needsZoomAndFade)
|
|
|
|
|
this._zoomAndFadeIn();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-06-11 09:27:40 -04:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_key_press_event(keyEvent) {
|
|
|
|
|
if (global.stage.get_key_focus() != this)
|
2014-06-11 09:27:40 -04:00
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
|
|
|
|
|
// Since we need to only grab focus on one item child when the user
|
|
|
|
|
// actually press a key we don't use navigate_focus when opening
|
|
|
|
|
// the popup.
|
|
|
|
|
// Instead of that, grab the focus on the AppFolderPopup actor
|
|
|
|
|
// and actually moves the focus to a child only when the user
|
|
|
|
|
// actually press a key.
|
|
|
|
|
// It should work with just grab_key_focus on the AppFolderPopup
|
|
|
|
|
// actor, but since the arrow keys are not wrapping_around the focus
|
|
|
|
|
// is not grabbed by a child when the widget that has the current focus
|
|
|
|
|
// is the same that is requesting focus, so to make it works with arrow
|
|
|
|
|
// keys we need to connect to the key-press-event and navigate_focus
|
|
|
|
|
// when that happens using TAB_FORWARD or TAB_BACKWARD instead of arrow
|
|
|
|
|
// keys
|
|
|
|
|
|
|
|
|
|
// Use TAB_FORWARD for down key and right key
|
|
|
|
|
// and TAB_BACKWARD for up key and left key on ltr
|
|
|
|
|
// languages
|
|
|
|
|
let direction;
|
|
|
|
|
let isLtr = Clutter.get_default_text_direction() == Clutter.TextDirection.LTR;
|
2019-09-10 01:42:48 -04:00
|
|
|
|
switch (keyEvent.keyval) {
|
2019-11-05 14:37:28 -05:00
|
|
|
|
case Clutter.KEY_Down:
|
2019-02-01 07:21:00 -05:00
|
|
|
|
direction = St.DirectionType.TAB_FORWARD;
|
|
|
|
|
break;
|
2019-11-05 14:37:28 -05:00
|
|
|
|
case Clutter.KEY_Right:
|
2019-09-12 18:27:56 -04:00
|
|
|
|
direction = isLtr
|
|
|
|
|
? St.DirectionType.TAB_FORWARD
|
|
|
|
|
: St.DirectionType.TAB_BACKWARD;
|
2019-02-01 07:21:00 -05:00
|
|
|
|
break;
|
2019-11-05 14:37:28 -05:00
|
|
|
|
case Clutter.KEY_Up:
|
2019-02-01 07:21:00 -05:00
|
|
|
|
direction = St.DirectionType.TAB_BACKWARD;
|
|
|
|
|
break;
|
2019-11-05 14:37:28 -05:00
|
|
|
|
case Clutter.KEY_Left:
|
2019-09-12 18:27:56 -04:00
|
|
|
|
direction = isLtr
|
|
|
|
|
? St.DirectionType.TAB_BACKWARD
|
|
|
|
|
: St.DirectionType.TAB_FORWARD;
|
2019-02-01 07:21:00 -05:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2014-06-11 09:27:40 -04:00
|
|
|
|
}
|
2019-09-10 01:42:48 -04:00
|
|
|
|
return this.navigate_focus(null, direction, false);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2020-09-24 09:17:53 -04:00
|
|
|
|
_setLighterBackground(lighter) {
|
|
|
|
|
const backgroundColor = lighter
|
|
|
|
|
? DIALOG_SHADE_HIGHLIGHT
|
|
|
|
|
: DIALOG_SHADE_NORMAL;
|
|
|
|
|
|
|
|
|
|
this.ease({
|
|
|
|
|
backgroundColor,
|
|
|
|
|
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-04 21:32:14 -04:00
|
|
|
|
_withinDialog(x, y) {
|
2020-07-29 06:50:47 -04:00
|
|
|
|
const childExtents = this.child.get_transformed_extents();
|
|
|
|
|
return childExtents.contains_point(new Graphene.Point({ x, y }));
|
2020-06-04 21:32:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-25 15:51:30 -04:00
|
|
|
|
_setupDragMonitor() {
|
|
|
|
|
if (this._dragMonitor)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._dragMonitor = {
|
|
|
|
|
dragMotion: dragEvent => {
|
2020-09-24 09:17:53 -04:00
|
|
|
|
const withinDialog =
|
|
|
|
|
this._withinDialog(dragEvent.x, dragEvent.y);
|
|
|
|
|
|
|
|
|
|
this._setLighterBackground(!withinDialog);
|
|
|
|
|
|
|
|
|
|
if (withinDialog) {
|
2020-06-25 15:51:30 -04:00
|
|
|
|
this._removePopdownTimeout();
|
|
|
|
|
this._removeDragMonitor();
|
|
|
|
|
}
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
DND.addDragMonitor(this._dragMonitor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_setupPopdownTimeout() {
|
|
|
|
|
if (this._popdownTimeoutId > 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._popdownTimeoutId =
|
|
|
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, POPDOWN_DIALOG_TIMEOUT, () => {
|
|
|
|
|
this._popdownTimeoutId = 0;
|
|
|
|
|
this.popdown();
|
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-04 21:32:14 -04:00
|
|
|
|
handleDragOver(source, actor, x, y) {
|
|
|
|
|
if (this._withinDialog(x, y)) {
|
2020-09-24 09:17:53 -04:00
|
|
|
|
this._setLighterBackground(false);
|
2020-06-25 15:51:30 -04:00
|
|
|
|
this._removePopdownTimeout();
|
|
|
|
|
this._removeDragMonitor();
|
|
|
|
|
} else {
|
|
|
|
|
this._setupPopdownTimeout();
|
|
|
|
|
this._setupDragMonitor();
|
2020-06-04 21:32:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 15:06:01 -04:00
|
|
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acceptDrop(source) {
|
|
|
|
|
const appId = source.id;
|
|
|
|
|
|
|
|
|
|
this.popdown(() => {
|
|
|
|
|
this._view.removeApp(source);
|
|
|
|
|
this._appDisplay.selectApp(appId);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return true;
|
2020-06-04 21:32:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
toggle() {
|
2013-01-31 11:13:37 -05:00
|
|
|
|
if (this._isOpen)
|
|
|
|
|
this.popdown();
|
|
|
|
|
else
|
|
|
|
|
this.popup();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
popup() {
|
2013-01-31 11:13:37 -05:00
|
|
|
|
if (this._isOpen)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-02-04 11:37:02 -05:00
|
|
|
|
this._isOpen = this._grabHelper.grab({
|
|
|
|
|
actor: this,
|
|
|
|
|
onUngrab: () => this.popdown(),
|
|
|
|
|
});
|
2014-06-11 07:13:12 -04:00
|
|
|
|
|
|
|
|
|
if (!this._isOpen)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-06-04 00:37:31 -04:00
|
|
|
|
this.get_parent().set_child_above_sibling(this, null);
|
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
|
this._needsZoomAndFade = true;
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.show();
|
2013-02-20 08:13:46 -05:00
|
|
|
|
|
2013-01-31 11:13:37 -05:00
|
|
|
|
this.emit('open-state-changed', true);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
|
2020-09-24 15:31:00 -04:00
|
|
|
|
popdown(callback) {
|
|
|
|
|
// Either call the callback right away, or wait for the zoom out
|
|
|
|
|
// animation to finish
|
|
|
|
|
if (callback) {
|
|
|
|
|
if (this.visible)
|
|
|
|
|
this._popdownCallbacks.push(callback);
|
|
|
|
|
else
|
|
|
|
|
callback();
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-31 11:13:37 -05:00
|
|
|
|
if (!this._isOpen)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
|
this._zoomAndFadeOut();
|
2019-12-17 14:39:24 -05:00
|
|
|
|
this._showFolderLabel();
|
2014-06-11 07:13:12 -04:00
|
|
|
|
|
2013-01-31 11:13:37 -05:00
|
|
|
|
this._isOpen = false;
|
2020-06-24 14:32:27 -04:00
|
|
|
|
this._grabHelper.ungrab({ actor: this });
|
2013-01-31 11:13:37 -05:00
|
|
|
|
this.emit('open-state-changed', false);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var AppIcon = GObject.registerClass({
|
|
|
|
|
Signals: {
|
|
|
|
|
'menu-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
|
|
|
|
|
'sync-tooltip': {},
|
2019-08-20 17:43:54 -04:00
|
|
|
|
},
|
2020-05-26 17:05:40 -04:00
|
|
|
|
}, class AppIcon extends AppViewItem {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
_init(app, iconParams = {}) {
|
2020-05-26 17:05:40 -04:00
|
|
|
|
// Get the isDraggable property without passing it on to the BaseIcon:
|
|
|
|
|
const appIconParams = Params.parse(iconParams, { isDraggable: true }, true);
|
|
|
|
|
const isDraggable = appIconParams['isDraggable'];
|
|
|
|
|
delete iconParams['isDraggable'];
|
2020-10-21 16:20:59 -04:00
|
|
|
|
const expandTitleOnHover = appIconParams['expandTitleOnHover'];
|
|
|
|
|
delete iconParams['expandTitleOnHover'];
|
2020-05-26 17:05:40 -04:00
|
|
|
|
|
2020-10-21 16:20:59 -04:00
|
|
|
|
super._init({ style_class: 'app-well-app' }, isDraggable, expandTitleOnHover);
|
2013-01-30 17:51:43 -05:00
|
|
|
|
|
2009-12-08 12:51:05 -05:00
|
|
|
|
this.app = app;
|
2020-05-26 17:05:40 -04:00
|
|
|
|
this._id = app.get_id();
|
|
|
|
|
this._name = app.get_name();
|
2013-12-15 22:18:17 -05:00
|
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
|
this._iconContainer = new St.Widget({
|
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
|
|
|
|
});
|
2015-02-17 12:33:37 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.set_child(this._iconContainer);
|
2015-02-17 12:33:37 -05:00
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
|
this._folderPreviewId = 0;
|
|
|
|
|
|
2017-12-01 19:27:35 -05:00
|
|
|
|
iconParams['createIcon'] = this._createIcon.bind(this);
|
2013-08-15 04:38:17 -04:00
|
|
|
|
iconParams['setSizeManually'] = true;
|
2013-01-30 17:51:43 -05:00
|
|
|
|
this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams);
|
2018-06-30 14:00:08 -04:00
|
|
|
|
this._iconContainer.add_child(this.icon);
|
2009-12-08 12:51:05 -05:00
|
|
|
|
|
2019-11-26 19:13:24 -05:00
|
|
|
|
this._dot = new St.Widget({
|
|
|
|
|
style_class: 'app-well-app-running-dot',
|
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
|
x_expand: true,
|
|
|
|
|
y_expand: true,
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
y_align: Clutter.ActorAlign.END,
|
|
|
|
|
});
|
|
|
|
|
this._iconContainer.add_child(this._dot);
|
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.label_actor = this.icon.label;
|
2011-03-08 13:33:57 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this));
|
2010-05-20 11:18:46 -04:00
|
|
|
|
|
2009-12-08 12:51:05 -05:00
|
|
|
|
this._menu = null;
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this._menuManager = new PopupMenu.PopupMenuManager(this);
|
2009-12-08 12:51:05 -05:00
|
|
|
|
|
2010-03-10 08:52:28 -05:00
|
|
|
|
this._menuTimeoutId = 0;
|
2021-08-15 18:36:59 -04:00
|
|
|
|
this.app.connectObject('notify::state',
|
|
|
|
|
() => this._updateRunningStyle(), this);
|
2014-11-01 10:24:03 -04:00
|
|
|
|
this._updateRunningStyle();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2010-01-07 00:40:21 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onDestroy() {
|
2020-05-26 17:05:40 -04:00
|
|
|
|
super._onDestroy();
|
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
|
if (this._folderPreviewId > 0) {
|
|
|
|
|
GLib.source_remove(this._folderPreviewId);
|
|
|
|
|
this._folderPreviewId = 0;
|
|
|
|
|
}
|
2019-11-21 16:50:11 -05:00
|
|
|
|
|
2010-03-10 08:52:28 -05:00
|
|
|
|
this._removeMenuTimeout();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2010-03-10 08:52:28 -05:00
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
_onDragBegin() {
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
if (this._menu)
|
|
|
|
|
this._menu.close(true);
|
2020-05-26 17:05:40 -04:00
|
|
|
|
this._removeMenuTimeout();
|
|
|
|
|
super._onDragBegin();
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_createIcon(iconSize) {
|
2020-09-02 05:11:39 -04:00
|
|
|
|
return this.app.create_icon_texture(iconSize);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-01-30 17:51:43 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_removeMenuTimeout() {
|
2010-03-10 08:52:28 -05:00
|
|
|
|
if (this._menuTimeoutId > 0) {
|
2019-08-19 14:50:33 -04:00
|
|
|
|
GLib.source_remove(this._menuTimeoutId);
|
2010-03-10 08:52:28 -05:00
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2010-01-07 00:40:21 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_updateRunningStyle() {
|
Major ShellApp API cleanup, startup notification, window focus handling
This patch combines several high level changes which are conceptually
independent but in practice rather intertwined.
* Add a "state" property to ShellApp which reflects whether it's
stopped, starting, or started. This will allow us to later clean
up all the callers that are using ".get_windows().length > 0" as
a proxy for this property
* Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
A lot of code was calling .launch, but it's signficantly clearer
if we call this ".open_new_window()", and later if we gain the ability
to call into an application's menu, we can implement this correctly rather
than trying to update all .launch callers.
* Because ShellApp now has a "starting" state, rebase panel.js on top of
this so that when we get a startup-notification sequence for an app
and transition it to starting, it becomes the focus app, and panel.js
cleanly just tracks the focus app, rather than bouncing between SN
sequences. This removes display of non-app startup sequences, which
I consider an acceptable action in light of the committed changes
to startup-notification and GTK+.
https://bugzilla.gnome.org/show_bug.cgi?id=614755
2010-04-03 14:07:44 -04:00
|
|
|
|
if (this.app.state != Shell.AppState.STOPPED)
|
2015-02-17 12:33:37 -05:00
|
|
|
|
this._dot.show();
|
Major ShellApp API cleanup, startup notification, window focus handling
This patch combines several high level changes which are conceptually
independent but in practice rather intertwined.
* Add a "state" property to ShellApp which reflects whether it's
stopped, starting, or started. This will allow us to later clean
up all the callers that are using ".get_windows().length > 0" as
a proxy for this property
* Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
A lot of code was calling .launch, but it's signficantly clearer
if we call this ".open_new_window()", and later if we gain the ability
to call into an application's menu, we can implement this correctly rather
than trying to update all .launch callers.
* Because ShellApp now has a "starting" state, rebase panel.js on top of
this so that when we get a startup-notification sequence for an app
and transition it to starting, it becomes the focus app, and panel.js
cleanly just tracks the focus app, rather than bouncing between SN
sequences. This removes display of non-app startup sequences, which
I consider an acceptable action in light of the committed changes
to startup-notification and GTK+.
https://bugzilla.gnome.org/show_bug.cgi?id=614755
2010-04-03 14:07:44 -04:00
|
|
|
|
else
|
2015-02-17 12:33:37 -05:00
|
|
|
|
this._dot.hide();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_setPopupTimeout() {
|
2014-07-22 06:40:54 -04:00
|
|
|
|
this._removeMenuTimeout();
|
2019-08-19 14:50:33 -04:00
|
|
|
|
this._menuTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, MENU_POPUP_TIMEOUT, () => {
|
2017-10-30 20:38:18 -04:00
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
|
this.popupMenu();
|
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
|
});
|
2014-07-22 06:40:54 -04:00
|
|
|
|
GLib.Source.set_name_by_id(this._menuTimeoutId, '[gnome-shell] this.popupMenu');
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-07-22 06:40:54 -04:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_leave_event(crossingEvent) {
|
2020-04-23 18:03:17 -04:00
|
|
|
|
const ret = super.vfunc_leave_event(crossingEvent);
|
2019-09-10 01:42:48 -04:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.fake_release();
|
2014-07-22 06:40:54 -04:00
|
|
|
|
this._removeMenuTimeout();
|
2019-09-10 01:42:48 -04:00
|
|
|
|
return ret;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-07-22 06:40:54 -04:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_button_press_event(buttonEvent) {
|
2020-04-23 18:04:27 -04:00
|
|
|
|
const ret = super.vfunc_button_press_event(buttonEvent);
|
2019-09-10 01:42:48 -04:00
|
|
|
|
if (buttonEvent.button == 1) {
|
2014-07-22 06:40:54 -04:00
|
|
|
|
this._setPopupTimeout();
|
2019-09-10 01:42:48 -04:00
|
|
|
|
} else if (buttonEvent.button == 3) {
|
2011-01-26 11:15:41 -05:00
|
|
|
|
this.popupMenu();
|
2013-11-29 13:17:34 -05:00
|
|
|
|
return Clutter.EVENT_STOP;
|
2010-03-10 08:52:28 -05:00
|
|
|
|
}
|
2020-04-23 18:04:27 -04:00
|
|
|
|
return ret;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_touch_event(touchEvent) {
|
2020-04-23 18:04:27 -04:00
|
|
|
|
const ret = super.vfunc_touch_event(touchEvent);
|
2019-09-10 01:42:48 -04:00
|
|
|
|
if (touchEvent.type == Clutter.EventType.TOUCH_BEGIN)
|
2014-07-22 06:40:54 -04:00
|
|
|
|
this._setPopupTimeout();
|
|
|
|
|
|
2020-04-23 18:04:27 -04:00
|
|
|
|
return ret;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-07-22 06:40:54 -04:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_clicked(button) {
|
2010-03-10 08:52:28 -05:00
|
|
|
|
this._removeMenuTimeout();
|
2014-08-20 12:39:02 -04:00
|
|
|
|
this.activate(button);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onKeyboardPopupMenu() {
|
2011-02-03 12:38:03 -05:00
|
|
|
|
this.popupMenu();
|
2018-11-27 07:58:25 -05:00
|
|
|
|
this._menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2011-02-03 12:38:03 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
getId() {
|
2010-02-15 19:50:36 -05:00
|
|
|
|
return this.app.get_id();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2010-02-15 19:50:36 -05:00
|
|
|
|
|
2021-01-07 08:05:18 -05:00
|
|
|
|
popupMenu(side = St.Side.LEFT) {
|
2021-11-17 18:25:48 -05:00
|
|
|
|
this.setForcedHighlight(true);
|
2010-03-10 08:52:28 -05:00
|
|
|
|
this._removeMenuTimeout();
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.fake_release();
|
2015-01-27 16:10:45 -05:00
|
|
|
|
|
2009-11-12 17:46:59 -05:00
|
|
|
|
if (!this._menu) {
|
2021-08-11 20:02:12 -04:00
|
|
|
|
this._menu = new AppMenu(this, side, {
|
|
|
|
|
favoritesSection: true,
|
|
|
|
|
showSingleWindows: true,
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2021-08-11 20:02:12 -04:00
|
|
|
|
this._menu.setApp(this.app);
|
2017-10-30 20:38:18 -04:00
|
|
|
|
this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
|
2011-02-12 14:03:44 -05:00
|
|
|
|
if (!isPoppedUp)
|
2009-11-12 17:46:59 -05:00
|
|
|
|
this._onMenuPoppedDown();
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2021-08-15 18:36:59 -04:00
|
|
|
|
Main.overview.connectObject('hiding',
|
|
|
|
|
() => this._menu.close(), this);
|
2010-05-20 11:18:46 -04:00
|
|
|
|
|
2021-08-11 20:02:12 -04:00
|
|
|
|
Main.uiGroup.add_actor(this._menu.actor);
|
2010-10-07 14:15:51 -04:00
|
|
|
|
this._menuManager.addMenu(this._menu);
|
2009-11-12 17:46:59 -05:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-03 16:03:33 -04:00
|
|
|
|
this.emit('menu-state-changed', true);
|
|
|
|
|
|
2021-08-11 20:02:12 -04:00
|
|
|
|
this._menu.open(BoxPointer.PopupAnimation.FULL);
|
2012-02-29 19:09:41 -05:00
|
|
|
|
this._menuManager.ignoreRelease();
|
2013-08-18 10:20:22 -04:00
|
|
|
|
this.emit('sync-tooltip');
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
|
|
|
|
return false;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onMenuPoppedDown() {
|
2021-11-17 18:25:48 -05:00
|
|
|
|
this.setForcedHighlight(false);
|
2012-11-03 16:03:33 -04:00
|
|
|
|
this.emit('menu-state-changed', false);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-12-08 12:51:05 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
activate(button) {
|
2014-08-20 12:39:02 -04:00
|
|
|
|
let event = Clutter.get_current_event();
|
|
|
|
|
let modifiers = event ? event.get_state() : 0;
|
2018-05-29 00:27:22 -04:00
|
|
|
|
let isMiddleButton = button && button == Clutter.BUTTON_MIDDLE;
|
|
|
|
|
let isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) != 0;
|
|
|
|
|
let openNewWindow = this.app.can_open_new_window() &&
|
|
|
|
|
this.app.state == Shell.AppState.RUNNING &&
|
|
|
|
|
(isCtrlPressed || isMiddleButton);
|
2009-12-08 12:51:05 -05:00
|
|
|
|
|
2014-06-17 15:31:53 -04:00
|
|
|
|
if (this.app.state == Shell.AppState.STOPPED || openNewWindow)
|
|
|
|
|
this.animateLaunch();
|
|
|
|
|
|
2014-08-20 12:39:02 -04:00
|
|
|
|
if (openNewWindow)
|
2013-02-17 23:08:41 -05:00
|
|
|
|
this.app.open_new_window(-1);
|
2014-08-20 12:39:02 -04:00
|
|
|
|
else
|
2013-02-17 23:08:41 -05:00
|
|
|
|
this.app.activate();
|
|
|
|
|
|
Major ShellApp API cleanup, startup notification, window focus handling
This patch combines several high level changes which are conceptually
independent but in practice rather intertwined.
* Add a "state" property to ShellApp which reflects whether it's
stopped, starting, or started. This will allow us to later clean
up all the callers that are using ".get_windows().length > 0" as
a proxy for this property
* Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
A lot of code was calling .launch, but it's signficantly clearer
if we call this ".open_new_window()", and later if we gain the ability
to call into an application's menu, we can implement this correctly rather
than trying to update all .launch callers.
* Because ShellApp now has a "starting" state, rebase panel.js on top of
this so that when we get a startup-notification sequence for an app
and transition it to starting, it becomes the focus app, and panel.js
cleanly just tracks the focus app, rather than bouncing between SN
sequences. This removes display of non-app startup sequences, which
I consider an acceptable action in light of the committed changes
to startup-notification and GTK+.
https://bugzilla.gnome.org/show_bug.cgi?id=614755
2010-04-03 14:07:44 -04:00
|
|
|
|
Main.overview.hide();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-09-01 14:15:29 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
animateLaunch() {
|
2014-06-17 15:31:53 -04:00
|
|
|
|
this.icon.animateZoomOut();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
|
2019-09-15 05:40:27 -04:00
|
|
|
|
animateLaunchAtPos(x, y) {
|
|
|
|
|
this.icon.animateZoomOutAtPos(x, y);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
shellWorkspaceLaunch(params) {
|
2019-08-02 06:58:34 -04:00
|
|
|
|
let { stack } = new Error();
|
2022-02-07 09:14:06 -05:00
|
|
|
|
log(`shellWorkspaceLaunch is deprecated, use app.open_new_window() instead\n${stack}`);
|
2019-08-02 06:58:34 -04:00
|
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
|
params = Params.parse(params, {
|
|
|
|
|
workspace: -1,
|
|
|
|
|
timestamp: 0,
|
|
|
|
|
});
|
2011-01-30 16:09:58 -05:00
|
|
|
|
|
2011-08-11 05:35:23 -04:00
|
|
|
|
this.app.open_new_window(params.workspace);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-08-17 20:29:54 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
getDragActor() {
|
2019-08-30 21:51:02 -04:00
|
|
|
|
return this.app.create_icon_texture(Main.overview.dash.iconSize);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2009-06-30 16:35:39 -04:00
|
|
|
|
|
2010-02-04 16:57:38 -05:00
|
|
|
|
// Returns the original actor that should align with the actor
|
|
|
|
|
// we show as the item is being dragged.
|
2017-10-30 20:03:21 -04:00
|
|
|
|
getDragActorSource() {
|
2010-07-21 19:29:02 -04:00
|
|
|
|
return this.icon.icon;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2013-08-18 10:20:22 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
shouldShowTooltip() {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
return this.hover && (!this._menu || !this._menu.isOpen);
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2019-07-04 17:39:13 -04:00
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
|
_showFolderPreview() {
|
|
|
|
|
this.icon.label.opacity = 0;
|
|
|
|
|
this.icon.icon.ease({
|
|
|
|
|
scale_x: FOLDER_SUBICON_FRACTION,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
scale_y: FOLDER_SUBICON_FRACTION,
|
2019-07-12 18:32:54 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_hideFolderPreview() {
|
|
|
|
|
this.icon.label.opacity = 255;
|
|
|
|
|
this.icon.icon.ease({
|
|
|
|
|
scale_x: 1.0,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
scale_y: 1.0,
|
2019-07-12 18:32:54 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_canAccept(source) {
|
|
|
|
|
let view = _getViewFromIcon(source);
|
|
|
|
|
|
|
|
|
|
return source != this &&
|
2019-10-29 18:47:55 -04:00
|
|
|
|
(source instanceof this.constructor) &&
|
2019-12-04 11:43:57 -05:00
|
|
|
|
(view instanceof AppDisplay);
|
2019-07-12 18:32:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_setHoveringByDnd(hovering) {
|
2019-10-29 19:06:10 -04:00
|
|
|
|
if (this._otherIconIsHovering == hovering)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
super._setHoveringByDnd(hovering);
|
2019-10-29 19:06:10 -04:00
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
|
if (hovering) {
|
|
|
|
|
if (this._folderPreviewId > 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._folderPreviewId =
|
|
|
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.add_style_pseudo_class('drop');
|
2019-07-12 18:32:54 -04:00
|
|
|
|
this._showFolderPreview();
|
|
|
|
|
this._folderPreviewId = 0;
|
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
if (this._folderPreviewId > 0) {
|
|
|
|
|
GLib.source_remove(this._folderPreviewId);
|
|
|
|
|
this._folderPreviewId = 0;
|
|
|
|
|
}
|
|
|
|
|
this._hideFolderPreview();
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.remove_style_pseudo_class('drop');
|
2019-07-12 18:32:54 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 17:05:40 -04:00
|
|
|
|
acceptDrop(source, actor, x) {
|
|
|
|
|
const accepted = super.acceptDrop(source, actor, x);
|
|
|
|
|
if (!accepted)
|
2020-05-25 15:00:23 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
|
let view = _getViewFromIcon(this);
|
|
|
|
|
let apps = [this.id, source.id];
|
|
|
|
|
|
2021-08-12 21:09:51 -04:00
|
|
|
|
return view?.createFolder(apps);
|
2019-07-12 18:32:54 -04:00
|
|
|
|
}
|
appDisplay: Improve app grid interaction for touch devices
Currently, handling of touch devices in the app grid is a bit awkward,
paging by dragging the view can only happen if started from the gaps
between icons, trying to drag from an icon will trigger DnD, and popping
up the menu takes over it all.
Instead, have the app grid actions play this game of rock-paper-scissors:
- Fast swipes on icons trigger scrolling, beats DnD and menu
- Slower press-and-drag on icons trigger DnD, beats scrolling and menu
- Long press triggers menu, beats scrolling, is beaten by DnD
This allows quick swipes to handle navigation, while still allowing the
fine grained operations. DnD, when triggered, dismisses the menu, if
shown.
This all could probably be nicer with a more stateful gesture framework,
we're not there yet though.
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3849
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1774>
2021-03-18 14:01:50 -04:00
|
|
|
|
|
|
|
|
|
cancelActions() {
|
|
|
|
|
if (this._menu)
|
|
|
|
|
this._menu.close(true);
|
|
|
|
|
this._removeMenuTimeout();
|
|
|
|
|
super.cancelActions();
|
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
var SystemActionIcon = GObject.registerClass(
|
|
|
|
|
class SystemActionIcon extends Search.GridSearchResult {
|
2017-10-30 20:03:21 -04:00
|
|
|
|
activate() {
|
2017-08-21 09:20:25 -04:00
|
|
|
|
SystemActions.getDefault().activateAction(this.metaInfo['id']);
|
|
|
|
|
Main.overview.hide();
|
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|