appDisplay: Transform folder into a dialog

Make the AppFolderPopup behave much more like a dialog than a
popup itself. To do that, remove the BoxPointer and replace it
by a StBoxLayout. The dialog is is also bind-constrained to the
view selector.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/896
This commit is contained in:
Georges Basile Stavracas Neto 2019-12-12 15:54:18 -03:00
parent e573d739af
commit f46d10c4f9
2 changed files with 126 additions and 118 deletions

View File

@ -86,14 +86,16 @@ $app_grid_fg_color: #fff;
} }
// expanded folder // expanded folder
.app-folder-popup { .app-folder-dialog {
-arrow-border-radius: 8px; border-radius: 8px;
-arrow-background-color: transparentize(darken($osd_bg_color,10%), 0.5); spacing: 24px;
-arrow-base: 24px; background-color: transparentize(darken($osd_bg_color,10%), 0.05);
-arrow-rise: 11px; }
.app-folder-dialog-container {
padding: 12px;
width: 800px;
height: 600px;
} }
.app-folder-popup-bin { padding: $base_padding - 1px; }
.app-folder-icon { .app-folder-icon {
padding: $base_padding; padding: $base_padding;

View File

@ -5,7 +5,6 @@ const { Clutter, Gio, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi;
const Signals = imports.signals; const Signals = imports.signals;
const AppFavorites = imports.ui.appFavorites; const AppFavorites = imports.ui.appFavorites;
const BoxPointer = imports.ui.boxpointer;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const GrabHelper = imports.ui.grabHelper; const GrabHelper = imports.ui.grabHelper;
const IconGrid = imports.ui.iconGrid; const IconGrid = imports.ui.iconGrid;
@ -41,6 +40,8 @@ var PAGE_SWITCH_TIME = 250;
var APP_ICON_SCALE_IN_TIME = 500; var APP_ICON_SCALE_IN_TIME = 500;
var APP_ICON_SCALE_IN_DELAY = 700; var APP_ICON_SCALE_IN_DELAY = 700;
const FOLDER_DIALOG_ANIMATION_TIME = 200;
const OVERSHOOT_THRESHOLD = 20; const OVERSHOOT_THRESHOLD = 20;
const OVERSHOOT_TIMEOUT = 1000; const OVERSHOOT_TIMEOUT = 1000;
@ -1404,10 +1405,10 @@ class FolderView extends BaseAppView {
this._scrollView.update_fade_effect(fadeOffset, 0); this._scrollView.update_fade_effect(fadeOffset, 0);
// Set extra padding to avoid popup or close button being cut off // Set extra padding to avoid popup or close button being cut off
this._grid.topPadding = Math.max(this._grid.topPadding - this._offsetForEachSide, 0); this._grid.topPadding = Math.max(this._grid.topPadding, 0);
this._grid.bottomPadding = Math.max(this._grid.bottomPadding - this._offsetForEachSide, 0); this._grid.bottomPadding = Math.max(this._grid.bottomPadding, 0);
this._grid.leftPadding = Math.max(this._grid.leftPadding - this._offsetForEachSide, 0); this._grid.leftPadding = Math.max(this._grid.leftPadding, 0);
this._grid.rightPadding = Math.max(this._grid.rightPadding - this._offsetForEachSide, 0); this._grid.rightPadding = Math.max(this._grid.rightPadding, 0);
this.set_width(this.usedWidth()); this.set_width(this.usedWidth());
this.set_height(this.usedHeight()); this.set_height(this.usedHeight());
@ -1422,7 +1423,7 @@ class FolderView extends BaseAppView {
let contentBox = this.get_theme_node().get_content_box(pageBox); let contentBox = this.get_theme_node().get_content_box(pageBox);
// We only can show icons inside the collection view boxPointer // We only can show icons inside the collection view boxPointer
// so we have to subtract the required padding etc of the boxpointer // so we have to subtract the required padding etc of the boxpointer
return [(contentBox.x2 - contentBox.x1) - 2 * this._offsetForEachSide, (contentBox.y2 - contentBox.y1) - 2 * this._offsetForEachSide]; return [contentBox.x2 - contentBox.x1, contentBox.y2 - contentBox.y1];
} }
usedWidth() { usedWidth() {
@ -1440,10 +1441,6 @@ class FolderView extends BaseAppView {
return Math.min(this._grid.nRows(availWidthPerPage), maxRows); return Math.min(this._grid.nRows(availWidthPerPage), maxRows);
} }
setPaddingOffsets(offset) {
this._offsetForEachSide = offset;
}
_loadApps() { _loadApps() {
let apps = []; let apps = [];
let excludedApps = this._folder.get_strv('excluded-apps'); let excludedApps = this._folder.get_strv('excluded-apps');
@ -1599,7 +1596,7 @@ var FolderIcon = GObject.registerClass({
this._removeMenuTimeout(); this._removeMenuTimeout();
this._ensurePopup(); this._ensurePopup();
this.view._scrollView.vscroll.adjustment.value = 0; this.view._scrollView.vscroll.adjustment.value = 0;
this._openSpaceForPopup(); this._popup.popup();
} }
getAppIds() { getAppIds() {
@ -1694,65 +1691,21 @@ var FolderIcon = GObject.registerClass({
} }
_popupHeight() { _popupHeight() {
let usedHeight = this.view.usedHeight() + this._popup.getOffset(St.Side.TOP) + this._popup.getOffset(St.Side.BOTTOM); let usedHeight = this.view.usedHeight();
return usedHeight; return usedHeight;
} }
_openSpaceForPopup() {
this._spaceReadySignalId = this._parentView.connect('space-ready', () => {
this._parentView.disconnect(this._spaceReadySignalId);
this._spaceReadySignalId = 0;
this._popup.popup();
this._updatePopupPosition();
});
this._parentView.openSpaceForPopup(this, this._boxPointerArrowside, this.view.nRowsDisplayedAtOnce());
}
_calculateBoxPointerArrowSide() {
let spaceTop = this.y - this._parentView.getCurrentPageY();
let spaceBottom = this._parentView.height - (spaceTop + this.height);
return spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
}
_updatePopupSize() {
// StWidget delays style calculation until needed, make sure we use the correct values
this.view._grid.ensure_style();
let offsetForEachSide = Math.ceil((this._popup.getOffset(St.Side.TOP) +
this._popup.getOffset(St.Side.BOTTOM) -
this._popup.getCloseButtonOverlap()) / 2);
// Add extra padding to prevent boxpointer decorations and close button being cut off
this.view.setPaddingOffsets(offsetForEachSide);
this.view.adaptToSize(this._parentAvailableWidth, this._parentAvailableHeight);
}
_updatePopupPosition() {
if (!this._popup)
return;
if (this._boxPointerArrowside == St.Side.BOTTOM)
this._popup.y = this.allocation.y1 + this.translation_y - this._popupHeight();
else
this._popup.y = this.allocation.y1 + this.translation_y + this.height;
}
_ensurePopup() { _ensurePopup() {
if (this._popup && !this._popupInvalidated) if (this._popup && !this._popupInvalidated)
return; return;
this._boxPointerArrowside = this._calculateBoxPointerArrowSide();
if (!this._popup) { if (!this._popup) {
this._popup = new AppFolderPopup(this, this._boxPointerArrowside); this._popup = new AppFolderPopup(this);
this._parentView.addFolderPopup(this._popup); this._parentView.addFolderPopup(this._popup);
this._popup.connect('open-state-changed', (popup, isOpen) => { this._popup.connect('open-state-changed', (popup, isOpen) => {
if (!isOpen) if (!isOpen)
this.checked = false; this.checked = false;
}); });
} else {
this._popup.updateArrowSide(this._boxPointerArrowside);
} }
this._updatePopupSize();
this._updatePopupPosition();
this._popupInvalidated = false; this._popupInvalidated = false;
} }
@ -1943,43 +1896,36 @@ var AppFolderPopup = GObject.registerClass({
'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] }, 'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
}, },
}, class AppFolderPopup extends St.Widget { }, class AppFolderPopup extends St.Widget {
_init(source, side) { _init(source) {
super._init({ super._init({
layout_manager: new Clutter.BinLayout(), layout_manager: new Clutter.BinLayout(),
style_class: 'app-folder-dialog-container',
visible: false, visible: false,
// We don't want to expand really, but look
// at the layout manager of our parent...
//
// DOUBLE HACK: if you set one, you automatically
// get the effect for the other direction too, so
// we need to set the y_align
x_expand: true, x_expand: true,
y_expand: true, y_expand: true,
x_align: Clutter.ActorAlign.CENTER, x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.START, y_align: Clutter.ActorAlign.CENTER,
}); });
this.add_constraint(new Clutter.BindConstraint({
source: Main.overview.viewSelector,
coordinate: Clutter.BindCoordinate.ALL,
}));
this._source = source; this._source = source;
this._view = source.view; this._view = source.view;
this._arrowSide = side;
this._isOpen = false; this._isOpen = false;
this.parentOffset = 0; this.parentOffset = 0;
this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide, { this._viewBox = new St.BoxLayout({
style_class: 'app-folder-popup-bin', style_class: 'app-folder-dialog',
x_expand: true, x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.FILL,
y_align: Clutter.ActorAlign.FILL,
}); });
this.add_child(this._viewBox);
this._boxPointer.style_class = 'app-folder-popup'; this._viewBox.add_child(this._view);
this.add_actor(this._boxPointer);
this._boxPointer.bin.set_child(this._view);
this.closeButton = Util.makeCloseButton(this._boxPointer);
this.closeButton.connect('clicked', this.popdown.bind(this));
this.add_actor(this.closeButton);
this._boxPointer.bind_property('opacity', this.closeButton, 'opacity',
GObject.BindingFlags.SYNC_CREATE);
global.focus_manager.add_group(this); global.focus_manager.add_group(this);
@ -1988,6 +1934,78 @@ var AppFolderPopup = GObject.registerClass({
}); });
this._grabHelper.addActor(Main.layoutManager.overviewGroup); this._grabHelper.addActor(Main.layoutManager.overviewGroup);
this.connect('destroy', this._onDestroy.bind(this)); this.connect('destroy', this._onDestroy.bind(this));
this._sourceMappedId = 0;
this._needsZoomAndFade = false;
}
_zoomAndFadeIn() {
let [sourceX, sourceY] =
this._source.get_transformed_position();
let [dialogX, dialogY] =
this.get_transformed_position();
this.set({
translation_x: sourceX - dialogX,
translation_y: sourceY - dialogY,
scale_x: this._source.width / this.width,
scale_y: this._source.height / this.height,
opacity: 0,
});
this.ease({
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] =
this.get_transformed_position();
this.ease({
translation_x: sourceX - dialogX,
translation_y: sourceY - dialogY,
scale_x: this._source.width / this.width,
scale_y: this._source.height / this.height,
opacity: 0,
duration: FOLDER_DIALOG_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this.set({
translation_x: 0,
translation_y: 0,
scale_x: 1,
scale_y: 1,
opacity: 255,
});
this.hide();
},
});
this._needsZoomAndFade = false;
} }
_onDestroy() { _onDestroy() {
@ -1996,6 +2014,22 @@ var AppFolderPopup = GObject.registerClass({
this._grabHelper.ungrab({ actor: this }); this._grabHelper.ungrab({ actor: this });
this._grabHelper = null; this._grabHelper = null;
} }
if (this._sourceMappedId) {
this._source.disconnect(this._sourceMappedId);
this._sourceMappedId = 0;
}
}
vfunc_allocate(box, flags) {
let contentBox = this.get_theme_node().get_content_box(box);
this._view.adaptToSize(contentBox.get_width(), contentBox.get_height());
super.vfunc_allocate(box, flags);
// We can only start zooming after receiving an allocation
if (this._needsZoomAndFade)
this._zoomAndFadeIn();
} }
vfunc_key_press_event(keyEvent) { vfunc_key_press_event(keyEvent) {
@ -2061,20 +2095,9 @@ var AppFolderPopup = GObject.registerClass({
if (!this._isOpen) if (!this._isOpen)
return; return;
this._needsZoomAndFade = true;
this.show(); this.show();
this._boxPointer.setArrowActor(this._source);
// We need to hide the icons of the view until the boxpointer animation
// is completed so we can animate the icons after as we like without
// showing them while boxpointer is animating.
this._view.opacity = 0;
this._boxPointer.open(BoxPointer.PopupAnimation.FADE |
BoxPointer.PopupAnimation.SLIDE,
() => {
this._view.opacity = 255;
this._view.animate(IconGrid.AnimationDirection.IN);
});
this.emit('open-state-changed', true); this.emit('open-state-changed', true);
} }
@ -2082,29 +2105,12 @@ var AppFolderPopup = GObject.registerClass({
if (!this._isOpen) if (!this._isOpen)
return; return;
this._grabHelper.ungrab({ actor: this }); this._zoomAndFadeOut();
this._boxPointer.close(BoxPointer.PopupAnimation.FADE | this._grabHelper.ungrab({ actor: this });
BoxPointer.PopupAnimation.SLIDE);
this._isOpen = false; this._isOpen = false;
this.emit('open-state-changed', false); this.emit('open-state-changed', false);
} }
getCloseButtonOverlap() {
return this.closeButton.get_theme_node().get_length('-shell-close-overlap-y');
}
getOffset(side) {
let offset = this._boxPointer.getPadding(side);
if (this._arrowSide == side)
offset += this._boxPointer.getArrowHeight();
return offset;
}
updateArrowSide(side) {
this._arrowSide = side;
this._boxPointer.updateArrowSide(side);
}
}); });
var AppIcon = GObject.registerClass({ var AppIcon = GObject.registerClass({