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
This commit is contained in:
Georges Basile Stavracas Neto 2020-05-22 13:20:40 -03:00
parent 45d8e11123
commit cff0752bcc

View File

@ -117,13 +117,65 @@ var BaseAppView = GObject.registerClass({
'view-loaded': {}, 'view-loaded': {},
}, },
}, class BaseAppView extends St.Widget { }, class BaseAppView extends St.Widget {
_init(params = {}) { _init(params = {}, orientation = Clutter.Orientation.VERTICAL) {
super._init(params); super._init(params);
this._grid = this._createGrid(); this._grid = this._createGrid();
// Standard hack for ClutterBinLayout // Standard hack for ClutterBinLayout
this._grid.x_expand = true; this._grid.x_expand = true;
const vertical = orientation === Clutter.Orientation.VERTICAL;
// Scroll View
this._scrollView = new St.ScrollView({
clip_to_allocation: true,
x_expand: true,
y_expand: true,
reactive: true,
});
this._scrollView.set_policy(
vertical ? St.PolicyType.NEVER : St.PolicyType.EXTERNAL,
vertical ? St.PolicyType.EXTERNAL : St.PolicyType.NEVER);
this._canScroll = true; // limiting scrolling speed
this._scrollTimeoutId = 0;
this._scrollView.connect('scroll-event', this._onScroll.bind(this));
this._scrollView.add_actor(this._grid);
const scroll = vertical ? this._scrollView.vscroll : this._scrollView.hscroll;
this._adjustment = scroll.adjustment;
this._adjustment.connect('notify::value', adj => {
this._pageIndicators.setCurrentPosition(adj.value / adj.page_size);
});
// Page Indicators
if (vertical)
this._pageIndicators = new PageIndicators.AnimatedPageIndicators();
else
this._pageIndicators = new PageIndicators.PageIndicators(orientation);
this._pageIndicators.y_expand = vertical;
this._pageIndicators.connect('page-activated',
(indicators, pageIndex) => {
this.goToPage(pageIndex);
});
this._pageIndicators.connect('scroll-event', (actor, event) => {
this._scrollView.event(event, false);
});
// Swipe
this._swipeTracker = new SwipeTracker.SwipeTracker(this._scrollView,
Shell.ActionMode.OVERVIEW | Shell.ActionMode.POPUP);
this._swipeTracker.orientation = orientation;
this._swipeTracker.connect('begin', this._swipeBegin.bind(this));
this._swipeTracker.connect('update', this._swipeUpdate.bind(this));
this._swipeTracker.connect('end', this._swipeEnd.bind(this));
this._availWidth = 0;
this._availHeight = 0;
this._orientation = orientation;
this._items = new Map(); this._items = new Map();
this._orderedItems = []; this._orderedItems = [];
@ -142,6 +194,86 @@ var BaseAppView = GObject.registerClass({
return new IconGrid.IconGrid(); return new IconGrid.IconGrid();
} }
_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;
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
? this._scrollView.height : this._scrollView.width;
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),
});
}
_redisplay() { _redisplay() {
let oldApps = this._orderedItems.slice(); let oldApps = this._orderedItems.slice();
let oldAppIds = oldApps.map(icon => icon.id); let oldAppIds = oldApps.map(icon => icon.id);
@ -272,7 +404,22 @@ var BaseAppView = GObject.registerClass({
} }
} }
vfunc_allocate(box) {
const width = box.get_width();
const height = box.get_height();
this.adaptToSize(width, height);
super.vfunc_allocate(box);
}
vfunc_map() {
this._swipeTracker.enabled = true;
super.vfunc_map();
}
vfunc_unmap() { vfunc_unmap() {
this._swipeTracker.enabled = false;
this._clearAnimateLater(); this._clearAnimateLater();
super.vfunc_unmap(); super.vfunc_unmap();
} }
@ -298,8 +445,43 @@ var BaseAppView = GObject.registerClass({
this._grid.ease(params); this._grid.ease(params);
} }
adaptToSize(_width, _height) { goToPage(pageNumber, animate = true) {
throw new GObject.NotImplementedError('adaptToSize in %s'.format(this.constructor.name)); pageNumber = Math.clamp(pageNumber, 0, this._grid.nPages - 1);
if (this._grid.currentPage === pageNumber)
return;
this._grid.goToPage(pageNumber, animate);
}
adaptToSize(width, height) {
let box = new Clutter.ActorBox({
x2: width,
y2: height,
});
box = this._scrollView.get_theme_node().get_content_box(box);
box = this._grid.get_theme_node().get_content_box(box);
const availWidth = box.get_width();
const availHeight = box.get_height();
const oldNPages = this._grid.nPages;
this._grid.adaptToSize(availWidth, availHeight);
if (this._availWidth !== availWidth ||
this._availHeight !== availHeight ||
oldNPages !== this._grid.nPages) {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._adjustment.value = 0;
this._grid.currentPage = 0;
this._pageIndicators.setNPages(this._grid.nPages);
this._pageIndicators.setCurrentPosition(0);
return GLib.SOURCE_REMOVE;
});
}
this._availWidth = availWidth;
this._availHeight = availHeight;
} }
}); });
@ -314,59 +496,24 @@ class AppDisplay extends BaseAppView {
this._grid._delegate = this; this._grid._delegate = this;
this._scrollView.add_style_class_name('all-apps');
this._stack = new St.Widget({ this._stack = new St.Widget({
layout_manager: new Clutter.BinLayout(), layout_manager: new Clutter.BinLayout(),
x_expand: true, x_expand: true,
y_expand: true, y_expand: true,
}); });
this.add_actor(this._stack); this.add_actor(this._stack);
this._scrollView = new St.ScrollView({
style_class: 'all-apps',
x_expand: true,
y_expand: true,
reactive: true,
});
this._scrollView.add_actor(this._grid);
this._stack.add_actor(this._scrollView); this._stack.add_actor(this._scrollView);
this._scrollView.set_policy(St.PolicyType.NEVER,
St.PolicyType.EXTERNAL);
this._adjustment = this._scrollView.vscroll.adjustment;
this._adjustment.connect('notify::value', adj => {
this._pageIndicators.setCurrentPosition(adj.value / adj.page_size);
});
this._pageIndicators = new PageIndicators.AnimatedPageIndicators();
this._pageIndicators.connect('page-activated',
(indicators, pageIndex) => {
this.goToPage(pageIndex);
});
this._pageIndicators.connect('scroll-event', (actor, event) => {
this._scrollView.event(event, false);
});
this.add_actor(this._pageIndicators); this.add_actor(this._pageIndicators);
this._folderIcons = []; this._folderIcons = [];
this._scrollView.connect('scroll-event', this._onScroll.bind(this));
this._swipeTracker = new SwipeTracker.SwipeTracker(
this._scrollView, Shell.ActionMode.OVERVIEW);
this._swipeTracker.connect('begin', this._swipeBegin.bind(this));
this._swipeTracker.connect('update', this._swipeUpdate.bind(this));
this._swipeTracker.connect('end', this._swipeEnd.bind(this));
this._currentDialog = null; this._currentDialog = null;
this._displayingDialog = false; this._displayingDialog = false;
this._currentDialogDestroyId = 0; this._currentDialogDestroyId = 0;
this._canScroll = true; // limiting scrolling speed
this._scrollTimeoutId = 0;
this._availWidth = 0;
this._availHeight = 0;
this._lastOvershootY = -1; this._lastOvershootY = -1;
this._lastOvershootTimeoutId = 0; this._lastOvershootTimeoutId = 0;
@ -404,15 +551,6 @@ class AppDisplay extends BaseAppView {
} }
} }
vfunc_allocate(box) {
box = this.get_theme_node().get_content_box(box);
let availWidth = box.get_width();
let availHeight = box.get_height();
this.adaptToSize(availWidth, availHeight);
super.vfunc_allocate(box);
}
_onDestroy() { _onDestroy() {
if (this._scrollTimeoutId !== 0) { if (this._scrollTimeoutId !== 0) {
GLib.source_remove(this._scrollTimeoutId); GLib.source_remove(this._scrollTimeoutId);
@ -424,7 +562,6 @@ class AppDisplay extends BaseAppView {
this._keyPressEventId = this._keyPressEventId =
global.stage.connect('key-press-event', global.stage.connect('key-press-event',
this._onKeyPressEvent.bind(this)); this._onKeyPressEvent.bind(this));
this._swipeTracker.enabled = true;
super.vfunc_map(); super.vfunc_map();
} }
@ -433,7 +570,6 @@ class AppDisplay extends BaseAppView {
global.stage.disconnect(this._keyPressEventId); global.stage.disconnect(this._keyPressEventId);
this._keyPressEventId = 0; this._keyPressEventId = 0;
} }
this._swipeTracker.enabled = false;
super.vfunc_unmap(); super.vfunc_unmap();
} }
@ -581,68 +717,14 @@ class AppDisplay extends BaseAppView {
if (this._displayingDialog && this._currentDialog) if (this._displayingDialog && this._currentDialog)
this._currentDialog.popdown(); this._currentDialog.popdown();
this._grid.goToPage(pageNumber, animate); super.goToPage(pageNumber, animate);
} }
_onScroll(actor, event) { _onScroll(actor, event) {
if (this._displayingDialog || !this._scrollView.reactive) if (this._displayingDialog || !this._scrollView.reactive)
return Clutter.EVENT_STOP; return Clutter.EVENT_STOP;
if (this._swipeTracker.canHandleScrollEvent(event)) return super._onScroll(actor, event);
return Clutter.EVENT_PROPAGATE;
if (!this._canScroll)
return Clutter.EVENT_STOP;
let direction = event.get_scroll_direction();
if (direction == Clutter.ScrollDirection.UP)
this.goToPage(this._grid.currentPage - 1);
else if (direction == Clutter.ScrollDirection.DOWN)
this.goToPage(this._grid.currentPage + 1);
else
return Clutter.EVENT_STOP;
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;
let adjustment = this._adjustment;
adjustment.remove_transition('value');
let progress = adjustment.value / adjustment.page_size;
let points = Array.from({ length: this._grid.nPages }, (v, i) => i);
tracker.confirmSwipe(this._scrollView.height,
points, progress, Math.round(progress));
}
_swipeUpdate(tracker, progress) {
let adjustment = this._adjustment;
adjustment.value = progress * adjustment.page_size;
}
_swipeEnd(tracker, duration, endProgress) {
let adjustment = this._adjustment;
let value = endProgress * adjustment.page_size;
adjustment.ease(value, {
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
duration,
onComplete: () => {
this.goToPage(endProgress, false);
},
});
} }
_onKeyPressEvent(actor, event) { _onKeyPressEvent(actor, event) {
@ -682,36 +764,6 @@ class AppDisplay extends BaseAppView {
} }
// Called before allocation to calculate dynamic spacing
adaptToSize(width, height) {
let box = new Clutter.ActorBox();
box.x1 = 0;
box.x2 = width;
box.y1 = 0;
box.y2 = height;
box = this.get_theme_node().get_content_box(box);
box = this._scrollView.get_theme_node().get_content_box(box);
box = this._grid.get_theme_node().get_content_box(box);
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
let oldNPages = this._grid.nPages;
this._grid.adaptToSize(availWidth, availHeight);
if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != this._grid.nPages) {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._adjustment.value = 0;
this._grid.currentPage = 0;
this._pageIndicators.setNPages(this._grid.nPages);
this._pageIndicators.setCurrentPosition(0);
return GLib.SOURCE_REMOVE;
});
}
this._availWidth = availWidth;
this._availHeight = availHeight;
}
_resetOvershoot() { _resetOvershoot() {
if (this._lastOvershootTimeoutId) if (this._lastOvershootTimeoutId)
GLib.source_remove(this._lastOvershootTimeoutId); GLib.source_remove(this._lastOvershootTimeoutId);
@ -974,7 +1026,7 @@ class FolderView extends BaseAppView {
layout_manager: new Clutter.BinLayout(), layout_manager: new Clutter.BinLayout(),
x_expand: true, x_expand: true,
y_expand: true, y_expand: true,
}); }, Clutter.Orientation.HORIZONTAL);
// If it not expand, the parent doesn't take into account its preferred_width when allocating // 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" // the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
@ -984,14 +1036,6 @@ class FolderView extends BaseAppView {
this._parentView = parentView; this._parentView = parentView;
this._grid._delegate = this; this._grid._delegate = this;
this._scrollView = new St.ScrollView({
overlay_scrollbars: true,
x_expand: true,
y_expand: true,
});
this._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER);
this._scrollView.add_actor(this._grid);
const box = new St.BoxLayout({ const box = new St.BoxLayout({
vertical: true, vertical: true,
reactive: true, reactive: true,
@ -999,28 +1043,9 @@ class FolderView extends BaseAppView {
y_expand: true, y_expand: true,
}); });
box.add_child(this._scrollView); box.add_child(this._scrollView);
// Page Dots
this._adjustment = this._scrollView.hscroll.adjustment;
this._adjustment.connect('notify::value', adj => {
this._pageIndicators.setCurrentPosition(adj.value / adj.page_size);
});
this._pageIndicators = new PageIndicators.PageIndicators(Clutter.Orientation.HORIZONTAL);
this._pageIndicators.y_expand = false;
this._pageIndicators.connect('page-activated',
(indicators, pageIndex) => {
this._grid.goToPage(pageIndex);
});
this._pageIndicators.connect('scroll-event', (actor, event) => {
this._scrollView.event(event, false);
});
box.add_child(this._pageIndicators); box.add_child(this._pageIndicators);
this.add_child(box); this.add_child(box);
this._availWidth = 0;
this._availHeight = 0;
let action = new Clutter.PanAction({ interpolate: true }); let action = new Clutter.PanAction({ interpolate: true });
action.connect('pan', this._onPan.bind(this)); action.connect('pan', this._onPan.bind(this));
this._scrollView.add_action(action); this._scrollView.add_action(action);
@ -1032,18 +1057,6 @@ class FolderView extends BaseAppView {
return new FolderGrid(); return new FolderGrid();
} }
vfunc_allocate(box) {
const node = this.get_theme_node();
const contentBox = node.get_content_box(box);
const [width, height] = contentBox.get_size();
this.adaptToSize(width, height);
this._grid.topPadding = 0;
super.vfunc_allocate(box);
}
// Overridden from BaseAppView // Overridden from BaseAppView
animate(animationDirection) { animate(animationDirection) {
this._grid.animatePulse(animationDirection); this._grid.animatePulse(animationDirection);
@ -1080,26 +1093,10 @@ class FolderView extends BaseAppView {
} }
adaptToSize(width, height) { adaptToSize(width, height) {
const oldNPages = this._grid.nPages;
const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1); const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1);
height -= indicatorHeight; height -= indicatorHeight;
this._grid.adaptToSize(width, height); super.adaptToSize(width, height);
if (this._availWidth !== width ||
this._availHeight !== height ||
oldNPages !== this._grid.nPages) {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._adjustment.value = 0;
this._grid.currentPage = 0;
this._pageIndicators.setNPages(this._grid.nPages);
this._pageIndicators.setCurrentPosition(0);
return GLib.SOURCE_REMOVE;
});
}
this._availWidth = width;
this._availHeight = height;
} }
_loadApps() { _loadApps() {