js/appDisplay: Implement navigation of pages by hovering/clicking edges
Add the necessary animations to slide in the icons in the previous/next pages, also needing to 1) drop the viewport clipping, and 2) extend scrollview fade effects to let see the pages in the navigated direction(s). The animation is driven via 2 adjustments, one for each side, so they can animate independently. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1630>
This commit is contained in:
parent
f31c49c40e
commit
d75ed55ed8
@ -136,3 +136,17 @@ $app_grid_fg_color: #fff;
|
|||||||
border-radius: 99px;
|
border-radius: 99px;
|
||||||
icon-size: $app_icon_size * 0.5;
|
icon-size: $app_icon_size * 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-navigation-hint {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
width: 88px;
|
||||||
|
|
||||||
|
&.next {
|
||||||
|
&:ltr { border-radius: 15px 0px 0px 15px; }
|
||||||
|
&:rtl { border-radius: 0px 15px 15px 0px; }
|
||||||
|
}
|
||||||
|
&.previous {
|
||||||
|
&:ltr { border-radius: 0px 15px 15px 0px; }
|
||||||
|
&:rtl { border-radius: 15px 0px 0px 15px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -38,6 +38,10 @@ var APP_ICON_TITLE_COLLAPSE_TIME = 100;
|
|||||||
|
|
||||||
const FOLDER_DIALOG_ANIMATION_TIME = 200;
|
const FOLDER_DIALOG_ANIMATION_TIME = 200;
|
||||||
|
|
||||||
|
const PAGE_PREVIEW_ANIMATION_TIME = 150;
|
||||||
|
const PAGE_PREVIEW_FADE_EFFECT_OFFSET = 160;
|
||||||
|
const PAGE_INDICATOR_FADE_TIME = 200;
|
||||||
|
|
||||||
const OVERSHOOT_THRESHOLD = 20;
|
const OVERSHOOT_THRESHOLD = 20;
|
||||||
const OVERSHOOT_TIMEOUT = 1000;
|
const OVERSHOOT_TIMEOUT = 1000;
|
||||||
|
|
||||||
@ -48,6 +52,12 @@ const DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000055);
|
|||||||
|
|
||||||
let discreteGpuAvailable = false;
|
let discreteGpuAvailable = false;
|
||||||
|
|
||||||
|
var SidePages = {
|
||||||
|
NONE: 0,
|
||||||
|
PREVIOUS: 1 << 0,
|
||||||
|
NEXT: 1 << 1,
|
||||||
|
};
|
||||||
|
|
||||||
function _getCategories(info) {
|
function _getCategories(info) {
|
||||||
let categoriesStr = info.get_categories();
|
let categoriesStr = info.get_categories();
|
||||||
if (!categoriesStr)
|
if (!categoriesStr)
|
||||||
@ -149,6 +159,10 @@ var BaseAppView = GObject.registerClass({
|
|||||||
this._canScroll = true; // limiting scrolling speed
|
this._canScroll = true; // limiting scrolling speed
|
||||||
this._scrollTimeoutId = 0;
|
this._scrollTimeoutId = 0;
|
||||||
this._scrollView.connect('scroll-event', this._onScroll.bind(this));
|
this._scrollView.connect('scroll-event', this._onScroll.bind(this));
|
||||||
|
this._scrollView.connect('motion-event', this._onMotion.bind(this));
|
||||||
|
this._scrollView.connect('enter-event', this._onMotion.bind(this));
|
||||||
|
this._scrollView.connect('leave-event', this._onLeave.bind(this));
|
||||||
|
this._scrollView.connect('button-press-event', this._onButtonPress.bind(this));
|
||||||
|
|
||||||
this._scrollView.add_actor(this._grid);
|
this._scrollView.add_actor(this._grid);
|
||||||
|
|
||||||
@ -172,12 +186,44 @@ var BaseAppView = GObject.registerClass({
|
|||||||
this._scrollView.event(event, false);
|
this._scrollView.event(event, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Navigation indicators
|
||||||
|
this._nextPageIndicator = new St.Widget({
|
||||||
|
style_class: 'page-navigation-hint next',
|
||||||
|
opacity: 0,
|
||||||
|
visible: false,
|
||||||
|
reactive: false,
|
||||||
|
x_expand: true,
|
||||||
|
y_expand: true,
|
||||||
|
x_align: Clutter.ActorAlign.END,
|
||||||
|
y_align: Clutter.ActorAlign.FILL,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._prevPageIndicator = new St.Widget({
|
||||||
|
style_class: 'page-navigation-hint previous',
|
||||||
|
opacity: 0,
|
||||||
|
visible: false,
|
||||||
|
reactive: false,
|
||||||
|
x_expand: true,
|
||||||
|
y_expand: true,
|
||||||
|
x_align: Clutter.ActorAlign.START,
|
||||||
|
y_align: Clutter.ActorAlign.FILL,
|
||||||
|
});
|
||||||
|
|
||||||
|
const scrollContainer = new St.Widget({
|
||||||
|
layout_manager: new Clutter.BinLayout(),
|
||||||
|
clip_to_allocation: true,
|
||||||
|
y_expand: true,
|
||||||
|
});
|
||||||
|
scrollContainer.add_child(this._prevPageIndicator);
|
||||||
|
scrollContainer.add_child(this._nextPageIndicator);
|
||||||
|
scrollContainer.add_child(this._scrollView);
|
||||||
|
|
||||||
this._box = new St.BoxLayout({
|
this._box = new St.BoxLayout({
|
||||||
vertical: true,
|
vertical: true,
|
||||||
x_expand: true,
|
x_expand: true,
|
||||||
y_expand: true,
|
y_expand: true,
|
||||||
});
|
});
|
||||||
this._box.add_child(this._scrollView);
|
this._box.add_child(scrollContainer);
|
||||||
this._box.add_child(this._pageIndicators);
|
this._box.add_child(this._pageIndicators);
|
||||||
|
|
||||||
// Swipe
|
// Swipe
|
||||||
@ -221,6 +267,8 @@ var BaseAppView = GObject.registerClass({
|
|||||||
this._dragCancelledId = 0;
|
this._dragCancelledId = 0;
|
||||||
|
|
||||||
this.connect('destroy', this._onDestroy.bind(this));
|
this.connect('destroy', this._onDestroy.bind(this));
|
||||||
|
|
||||||
|
this._previewedPages = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDestroy() {
|
_onDestroy() {
|
||||||
@ -243,9 +291,21 @@ var BaseAppView = GObject.registerClass({
|
|||||||
this._disconnectDnD();
|
this._disconnectDnD();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateFadeForNavigation() {
|
||||||
|
const fadeMargin = new Clutter.Margin();
|
||||||
|
fadeMargin.right = (this._pagesShown & SidePages.NEXT) !== 0
|
||||||
|
? -PAGE_PREVIEW_FADE_EFFECT_OFFSET : 0;
|
||||||
|
fadeMargin.left = (this._pagesShown & SidePages.PREVIOUS) !== 0
|
||||||
|
? -PAGE_PREVIEW_FADE_EFFECT_OFFSET : 0;
|
||||||
|
this._scrollView.update_fade_effect(fadeMargin);
|
||||||
|
}
|
||||||
|
|
||||||
_updateFade() {
|
_updateFade() {
|
||||||
const { pagePadding } = this._grid.layout_manager;
|
const { pagePadding } = this._grid.layout_manager;
|
||||||
|
|
||||||
|
if (this._pagesShown)
|
||||||
|
return;
|
||||||
|
|
||||||
if (pagePadding.top === 0 &&
|
if (pagePadding.top === 0 &&
|
||||||
pagePadding.right === 0 &&
|
pagePadding.right === 0 &&
|
||||||
pagePadding.bottom === 0 &&
|
pagePadding.bottom === 0 &&
|
||||||
@ -327,6 +387,41 @@ var BaseAppView = GObject.registerClass({
|
|||||||
return Clutter.EVENT_STOP;
|
return Clutter.EVENT_STOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pageForCoords(x, y) {
|
||||||
|
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
|
||||||
|
const { allocation } = this._grid;
|
||||||
|
|
||||||
|
const [success, pointerX] = this._scrollView.transform_stage_point(x, y);
|
||||||
|
if (!success)
|
||||||
|
return SidePages.NONE;
|
||||||
|
|
||||||
|
if (pointerX < allocation.x1)
|
||||||
|
return rtl ? SidePages.NEXT : SidePages.PREVIOUS;
|
||||||
|
else if (pointerX > allocation.x2)
|
||||||
|
return rtl ? SidePages.PREVIOUS : SidePages.NEXT;
|
||||||
|
|
||||||
|
return SidePages.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMotion(actor, event) {
|
||||||
|
const page = this._pageForCoords(...event.get_coords());
|
||||||
|
this._slideSidePages(page);
|
||||||
|
|
||||||
|
return Clutter.EVENT_PROPAGATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onButtonPress(actor, event) {
|
||||||
|
const page = this._pageForCoords(...event.get_coords());
|
||||||
|
if (page === SidePages.NEXT)
|
||||||
|
this.goToPage(this._grid.currentPage + 1);
|
||||||
|
else if (page === SidePages.PREVIOUS)
|
||||||
|
this.goToPage(this._grid.currentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onLeave() {
|
||||||
|
this._slideSidePages(SidePages.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
_swipeBegin(tracker, monitor) {
|
_swipeBegin(tracker, monitor) {
|
||||||
if (monitor !== Main.layoutManager.primaryIndex)
|
if (monitor !== Main.layoutManager.primaryIndex)
|
||||||
return;
|
return;
|
||||||
@ -351,6 +446,8 @@ var BaseAppView = GObject.registerClass({
|
|||||||
const adjustment = this._adjustment;
|
const adjustment = this._adjustment;
|
||||||
const value = endProgress * adjustment.page_size;
|
const value = endProgress * adjustment.page_size;
|
||||||
|
|
||||||
|
this._syncPageHints(endProgress);
|
||||||
|
|
||||||
adjustment.ease(value, {
|
adjustment.ease(value, {
|
||||||
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
|
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
|
||||||
duration,
|
duration,
|
||||||
@ -868,12 +965,37 @@ var BaseAppView = GObject.registerClass({
|
|||||||
this._grid.ease(params);
|
this._grid.ease(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_syncPageHints(pageNumber, animate = true) {
|
||||||
|
const showingNextPage = this._pagesShown & SidePages.NEXT;
|
||||||
|
const showingPrevPage = this._pagesShown & SidePages.PREVIOUS;
|
||||||
|
const duration = animate ? PAGE_INDICATOR_FADE_TIME : 0;
|
||||||
|
|
||||||
|
if (showingPrevPage) {
|
||||||
|
const opacity = pageNumber === 0 ? 0 : 255;
|
||||||
|
this._prevPageIndicator.visible = true;
|
||||||
|
this._prevPageIndicator.ease({
|
||||||
|
opacity,
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showingNextPage) {
|
||||||
|
const opacity = pageNumber === this._grid.nPages - 1 ? 0 : 255;
|
||||||
|
this._nextPageIndicator.visible = true;
|
||||||
|
this._nextPageIndicator.ease({
|
||||||
|
opacity,
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
goToPage(pageNumber, animate = true) {
|
goToPage(pageNumber, animate = true) {
|
||||||
pageNumber = Math.clamp(pageNumber, 0, this._grid.nPages - 1);
|
pageNumber = Math.clamp(pageNumber, 0, this._grid.nPages - 1);
|
||||||
|
|
||||||
if (this._grid.currentPage === pageNumber)
|
if (this._grid.currentPage === pageNumber)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this._syncPageHints(pageNumber, animate);
|
||||||
this._grid.goToPage(pageNumber, animate);
|
this._grid.goToPage(pageNumber, animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -894,6 +1016,121 @@ var BaseAppView = GObject.registerClass({
|
|||||||
this._availWidth = availWidth;
|
this._availWidth = availWidth;
|
||||||
this._availHeight = availHeight;
|
this._availHeight = availHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getPagePreviewAdjustment(page) {
|
||||||
|
const previewedPage = this._previewedPages.get(page);
|
||||||
|
return previewedPage?.adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncClip() {
|
||||||
|
const nextPageAdjustment = this._getPagePreviewAdjustment(1);
|
||||||
|
const prevPageAdjustment = this._getPagePreviewAdjustment(-1);
|
||||||
|
this._grid.clip_to_view =
|
||||||
|
(!prevPageAdjustment || prevPageAdjustment.value === 0) &&
|
||||||
|
(!nextPageAdjustment || nextPageAdjustment.value === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupPagePreview(page, state) {
|
||||||
|
if (this._previewedPages.has(page))
|
||||||
|
return this._previewedPages.get(page).adjustment;
|
||||||
|
|
||||||
|
const adjustment = new St.Adjustment({
|
||||||
|
actor: this,
|
||||||
|
lower: 0,
|
||||||
|
upper: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const indicator = page > 0
|
||||||
|
? this._nextPageIndicator : this._prevPageIndicator;
|
||||||
|
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
|
||||||
|
|
||||||
|
const notifyId = adjustment.connect('notify::value', () => {
|
||||||
|
let translationX = (1 - adjustment.value) * 100 * page;
|
||||||
|
translationX = rtl ? -translationX : translationX;
|
||||||
|
const nextPage = this._grid.currentPage + page;
|
||||||
|
if (nextPage >= 0 &&
|
||||||
|
nextPage < this._grid.nPages - 1) {
|
||||||
|
const items = this._grid.getItemsAtPage(nextPage);
|
||||||
|
items.forEach(item => (item.translation_x = translationX));
|
||||||
|
indicator.set({
|
||||||
|
visible: true,
|
||||||
|
opacity: adjustment.value * 255,
|
||||||
|
translationX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._syncClip();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._previewedPages.set(page, {
|
||||||
|
adjustment,
|
||||||
|
notifyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
_teardownPagePreview(page) {
|
||||||
|
const previewedPage = this._previewedPages.get(page);
|
||||||
|
if (!previewedPage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
previewedPage.adjustment.value = 1;
|
||||||
|
previewedPage.adjustment.disconnect(previewedPage.notifyId);
|
||||||
|
this._previewedPages.delete(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
_slideSidePages(state) {
|
||||||
|
if (this._pagesShown === state)
|
||||||
|
return;
|
||||||
|
this._pagesShown = state;
|
||||||
|
const showingNextPage = state & SidePages.NEXT;
|
||||||
|
const showingPrevPage = state & SidePages.PREVIOUS;
|
||||||
|
let adjustment;
|
||||||
|
|
||||||
|
adjustment = this._getPagePreviewAdjustment(1);
|
||||||
|
if (showingNextPage) {
|
||||||
|
adjustment = this._setupPagePreview(1, state);
|
||||||
|
|
||||||
|
adjustment.ease(1, {
|
||||||
|
duration: PAGE_PREVIEW_ANIMATION_TIME,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
});
|
||||||
|
this._updateFadeForNavigation();
|
||||||
|
} else if (adjustment) {
|
||||||
|
adjustment.ease(0, {
|
||||||
|
duration: PAGE_PREVIEW_ANIMATION_TIME,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete: () => {
|
||||||
|
this._teardownPagePreview(1);
|
||||||
|
this._syncClip();
|
||||||
|
this._nextPageIndicator.visible = false;
|
||||||
|
this._updateFadeForNavigation();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustment = this._getPagePreviewAdjustment(-1);
|
||||||
|
if (showingPrevPage) {
|
||||||
|
adjustment = this._setupPagePreview(-1, state);
|
||||||
|
|
||||||
|
adjustment.ease(1, {
|
||||||
|
duration: PAGE_PREVIEW_ANIMATION_TIME,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
});
|
||||||
|
this._updateFadeForNavigation();
|
||||||
|
} else if (adjustment) {
|
||||||
|
adjustment.ease(0, {
|
||||||
|
duration: PAGE_PREVIEW_ANIMATION_TIME,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete: () => {
|
||||||
|
this._teardownPagePreview(-1);
|
||||||
|
this._syncClip();
|
||||||
|
this._prevPageIndicator.visible = false;
|
||||||
|
this._updateFadeForNavigation();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var PageManager = GObject.registerClass({
|
var PageManager = GObject.registerClass({
|
||||||
|
Loading…
Reference in New Issue
Block a user