diff --git a/data/theme/gnome-shell-sass/widgets/_app-grid.scss b/data/theme/gnome-shell-sass/widgets/_app-grid.scss index 91cf023f1989c71fa5475a52bb5c9161704281ae..49d3086a4fb51a5eabd2ab3e4a8e6eae9c61e8a1 100644 --- a/data/theme/gnome-shell-sass/widgets/_app-grid.scss +++ b/data/theme/gnome-shell-sass/widgets/_app-grid.scss @@ -124,32 +124,16 @@ $app_icon_size: 96px; icon-size: $app_icon_size * 0.5; } -.page-navigation-hint { - width: 300px; - - &.dnd { - background: rgba(255, 255, 255, 0.1); - } - - &.next:ltr, - &.previous:rtl { - background-gradient-start: rgba(255, 255, 255, 0.05); - background-gradient-end: transparent; - background-gradient-direction: horizontal; - border-radius: $modal_radius*1.5 0px 0px $modal_radius*1.5; - } - - &.previous:ltr, - &.next:rtl { - background-gradient-start: transparent; - background-gradient-end: rgba(255, 255, 255, 0.05); - background-gradient-direction: horizontal; - border-radius: 0px $modal_radius*1.5 $modal_radius*1.5 0px; +.page-navigation-arrow { + & > StIcon { + margin: 6px; + padding: 18px; + width: 24px; + height: 24px; + border-radius: 99px; } -} -.page-navigation-arrow { - margin: 6px; - width: 24px; - height: 24px; + &:insensitive > StIcon { @include button(undecorated, $osd_fg_color, transparentize($osd_bg_color, 0.5));} + &:hover > StIcon { @include button(hover, $osd_fg_color, transparentize($osd_bg_color, 0.5));} + &:active > StIcon { @include button(active, $osd_fg_color, transparentize($osd_bg_color, 0.5));} } diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 0768dee34dee61bdeb716694ccb7ea52bfebfaa6..92ed05b72f15f0362dab493ed9dcc09fb5c8b0f3 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -40,14 +40,11 @@ var APP_ICON_TITLE_COLLAPSE_TIME = 100; const FOLDER_DIALOG_ANIMATION_TIME = 200; const PAGE_PREVIEW_ANIMATION_TIME = 150; -const PAGE_PREVIEW_ANIMATION_START_OFFSET = 100; -const PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET = 300; -const PAGE_PREVIEW_MAX_ARROW_OFFSET = 80; const PAGE_INDICATOR_FADE_TIME = 200; -const MAX_PAGE_PADDING = 200; +const PAGE_PREVIEW_RATIO = 0.20; const OVERSHOOT_THRESHOLD = 20; -const OVERSHOOT_TIMEOUT = 1000; +const OVERSHOOT_TIMEOUT = 300; const DELAYED_MOVE_TIMEOUT = 200; @@ -83,13 +80,6 @@ const DEFAULT_FOLDERS = { }, }; -var SidePages = { - NONE: 0, - PREVIOUS: 1 << 0, - NEXT: 1 << 1, - DND: 1 << 2, -}; - function _getCategories(info) { let categoriesStr = info.get_categories(); if (!categoriesStr) @@ -157,6 +147,335 @@ function _findBestFolderName(apps) { return null; } +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(); + } +}); + +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; + this._pageWidth = 0; + } + + _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; + + if (previousIndicatorsVisible) + this._previousPageIndicator.show(); + + this._previousPageIndicator.ease({ + opacity: previousIndicatorsVisible ? 255 : 0, + duration: animate ? PAGE_INDICATOR_FADE_TIME : 0, + onComplete: () => { + if (!previousIndicatorsVisible) + this._previousPageIndicator.hide(); + }, + }); + + const previousArrowVisible = + this._currentPage > 0 && !previousIndicatorsVisible; + + if (previousArrowVisible) + this._previousPageArrow.show(); + + this._previousPageArrow.ease({ + opacity: previousArrowVisible ? 255 : 0, + duration: animate ? PAGE_INDICATOR_FADE_TIME : 0, + onComplete: () => { + if (!previousArrowVisible) + this._previousPageArrow.hide(); + }, + }); + + // 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); + + if (nextIndicatorsVisible) + this._nextPageIndicator.show(); + + this._nextPageIndicator.ease({ + opacity: nextIndicatorsVisible ? 255 : 0, + duration: animate ? PAGE_INDICATOR_FADE_TIME : 0, + onComplete: () => { + if (!nextIndicatorsVisible) + this._nextPageIndicator.hide(); + }, + }); + + const nextArrowVisible = + this._currentPage < nPages - 1 && + !nextIndicatorsVisible; + + if (nextArrowVisible) + this._nextPageArrow.show(); + + this._nextPageArrow.ease({ + opacity: nextArrowVisible ? 255 : 0, + duration: animate ? PAGE_INDICATOR_FADE_TIME : 0, + onComplete: () => { + if (!nextArrowVisible) + this._nextPageArrow.hide(); + }, + }); + } + + _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; + } + + _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; + const leftIndicatorOffset = -left * (1 - value); + const rightIndicatorOffset = right * (1 - value); + + this._previousPageIndicator.translationX = + ltr ? leftIndicatorOffset : rightIndicatorOffset; + this._nextPageIndicator.translationX = + ltr ? rightIndicatorOffset : leftIndicatorOffset; + + const leftArrowOffset = -left * value; + const rightArrowOffset = right * value; + + this._previousPageArrow.translationX = + ltr ? leftArrowOffset : rightArrowOffset; + this._nextPageArrow.translationX = + ltr ? rightArrowOffset : leftArrowOffset; + + // 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; + }); + } + } + + 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); + this._previousPageArrow.allocate_align_fill(ltr ? leftBox : rightBox, + 0.5, 0.5, false, false); + this._nextPageIndicator.allocate(ltr ? rightBox : leftBox); + this._nextPageArrow.allocate_align_fill(ltr ? rightBox : leftBox, + 0.5, 0.5, false, false); + + this._pageWidth = box.get_width(); + } + + 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(); + } +}); + var BaseAppView = GObject.registerClass({ GTypeFlags: GObject.TypeFlags.ABSTRACT, Properties: { @@ -192,38 +511,18 @@ var BaseAppView = GObject.registerClass({ enable_mouse_scrolling: false, }); this._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER); - this._scrollView._delegate = this; this._canScroll = true; // limiting scrolling speed this._scrollTimeoutId = 0; 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); const scroll = this._scrollView.hscroll; this._adjustment = scroll.adjustment; this._adjustment.connect('notify::value', adj => { - this._updateFade(); const value = adj.value / adj.page_size; this._pageIndicators.setCurrentPosition(value); - - const distanceToPage = Math.abs(Math.round(value) - value); - if (distanceToPage < 0.001) { - this._hintContainer.opacity = 255; - this._hintContainer.translationX = 0; - } else { - this._hintContainer.remove_transition('opacity'); - let opacity = Math.clamp( - 255 * (1 - (distanceToPage * 2)), - 0, 255); - - this._hintContainer.translationX = (Math.round(value) - value) * adj.page_size; - this._hintContainer.opacity = opacity; - } }); // Page Indicators @@ -244,10 +543,10 @@ var BaseAppView = GObject.registerClass({ style_class: 'page-navigation-hint next', opacity: 0, visible: false, - reactive: false, + reactive: true, x_expand: true, y_expand: true, - x_align: Clutter.ActorAlign.END, + x_align: Clutter.ActorAlign.FILL, y_align: Clutter.ActorAlign.FILL, }); @@ -255,55 +554,57 @@ var BaseAppView = GObject.registerClass({ style_class: 'page-navigation-hint previous', opacity: 0, visible: false, - reactive: false, + reactive: true, x_expand: true, y_expand: true, - x_align: Clutter.ActorAlign.START, + x_align: Clutter.ActorAlign.FILL, y_align: Clutter.ActorAlign.FILL, }); // Next/prev page arrows const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; - this._nextPageArrow = new St.Icon({ + this._nextPageArrow = new St.Button({ style_class: 'page-navigation-arrow', icon_name: rtl ? 'carousel-arrow-previous-symbolic' : 'carousel-arrow-next-symbolic', - opacity: 0, - reactive: false, - visible: false, - x_expand: true, - x_align: Clutter.ActorAlign.END, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, }); - this._prevPageArrow = new St.Icon({ + this._nextPageArrow.connect('clicked', + () => this.goToPage(this._grid.currentPage + 1)); + + this._prevPageArrow = new St.Button({ style_class: 'page-navigation-arrow', icon_name: rtl ? 'carousel-arrow-next-symbolic' : 'carousel-arrow-previous-symbolic', opacity: 0, - reactive: false, visible: false, - x_expand: true, - x_align: Clutter.ActorAlign.START, - }); - - this._hintContainer = new St.Widget({ - layout_manager: new Clutter.BinLayout(), - x_expand: true, - y_expand: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, }); - this._hintContainer.add_child(this._prevPageIndicator); - this._hintContainer.add_child(this._nextPageIndicator); + this._prevPageArrow.connect('clicked', + () => this.goToPage(this._grid.currentPage - 1)); const scrollContainer = new St.Widget({ - layout_manager: new Clutter.BinLayout(), clip_to_allocation: true, y_expand: true, }); - scrollContainer.add_child(this._hintContainer); scrollContainer.add_child(this._scrollView); + scrollContainer.add_child(this._prevPageIndicator); + scrollContainer.add_child(this._nextPageIndicator); scrollContainer.add_child(this._nextPageArrow); scrollContainer.add_child(this._prevPageArrow); + scrollContainer.layoutManager = new BaseAppViewGridLayout( + this._grid, + this._scrollView, + this._nextPageIndicator, + this._nextPageArrow, + this._prevPageIndicator, + this._prevPageArrow); + this._appGridLayout = scrollContainer.layoutManager; + scrollContainer._delegate = this; this._box = new St.BoxLayout({ vertical: true, @@ -321,8 +622,6 @@ var BaseAppView = GObject.registerClass({ this._swipeTracker.connect('update', this._swipeUpdate.bind(this)); this._swipeTracker.connect('end', this._swipeEnd.bind(this)); - this._availWidth = 0; - this._availHeight = 0; this._orientation = Clutter.Orientation.HORIZONTAL; this._items = new Map(); @@ -339,8 +638,7 @@ var BaseAppView = GObject.registerClass({ () => this._redisplay(), this); // Drag n' Drop - this._lastOvershoot = -1; - this._lastOvershootTimeoutId = 0; + this._overshootTimeoutId = 0; this._delayedMoveData = null; this._dragBeginId = 0; @@ -362,66 +660,8 @@ var BaseAppView = GObject.registerClass({ this._disconnectDnD(); } - _updateFadeForNavigation() { - const fadeMargin = new Clutter.Margin(); - const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; - const showingNextPage = this._pagesShown & SidePages.NEXT; - const showingPrevPage = this._pagesShown & SidePages.PREVIOUS; - - if ((showingNextPage && !rtl) || (showingPrevPage && rtl)) { - fadeMargin.right = Math.max( - -PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET, - -(this._availWidth - this._grid.layout_manager.pageWidth) / 2); - } - - if ((showingPrevPage && !rtl) || (showingNextPage && rtl)) { - fadeMargin.left = Math.max( - -PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET, - -(this._availWidth - this._grid.layout_manager.pageWidth) / 2); - } - - this._scrollView.update_fade_effect(fadeMargin); - const effect = this._scrollView.get_effect('fade'); - if (effect) - effect.extend_fade_area = true; - } - - _updateFade() { - const { pagePadding } = this._grid.layout_manager; - - if (this._pagesShown) - return; - - if (pagePadding.top === 0 && - pagePadding.right === 0 && - pagePadding.bottom === 0 && - pagePadding.left === 0) - return; - - let hOffset = 0; - let vOffset = 0; - - if ((this._adjustment.value % this._adjustment.page_size) !== 0.0) { - const vertical = this._orientation === Clutter.Orientation.VERTICAL; - - hOffset = vertical ? 0 : Math.max(pagePadding.left, pagePadding.right); - vOffset = vertical ? Math.max(pagePadding.top, pagePadding.bottom) : 0; - - if (hOffset === 0 && vOffset === 0) - return; - } - - this._scrollView.update_fade_effect( - new Clutter.Margin({ - left: hOffset, - right: hOffset, - top: vOffset, - bottom: vOffset, - })); - } - _createGrid() { - return new IconGrid.IconGrid({ allow_incomplete_pages: true }); + return new AppGrid({allow_incomplete_pages: true}); } _onScroll(actor, event) { @@ -473,41 +713,6 @@ var BaseAppView = GObject.registerClass({ 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) { if (monitor !== Main.layoutManager.primaryIndex) return; @@ -537,8 +742,6 @@ var BaseAppView = GObject.registerClass({ const adjustment = this._adjustment; const value = endProgress * adjustment.page_size; - this._syncPageHints(endProgress); - adjustment.ease(value, { mode: Clutter.AnimationMode.EASE_OUT_CUBIC, duration, @@ -636,69 +839,77 @@ var BaseAppView = GObject.registerClass({ } _resetOvershoot() { - if (this._lastOvershootTimeoutId) - GLib.source_remove(this._lastOvershootTimeoutId); - this._lastOvershootTimeoutId = 0; - this._lastOvershoot = -1; + if (this._overshootTimeoutId) + GLib.source_remove(this._overshootTimeoutId); + this._overshootTimeoutId = 0; } - _handleDragOvershoot(dragEvent) { - const [gridX, gridY] = this.get_transformed_position(); - const [gridWidth, gridHeight] = this.get_transformed_size(); + _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(); - const vertical = this._orientation === Clutter.Orientation.VERTICAL; - const gridStart = vertical ? gridY : gridX; - const gridEnd = vertical - ? gridY + gridHeight - OVERSHOOT_THRESHOLD - : gridX + gridWidth - OVERSHOOT_THRESHOLD; + 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); + } + + _handleDragOvershoot(dragEvent) { // Already animating if (this._adjustment.get_transition('value') !== null) return; - // Within the grid boundaries - const dragPosition = vertical ? dragEvent.y : dragEvent.x; - if (dragPosition > gridStart && dragPosition < gridEnd) { - // Check whether we moved out the area of the last switch - if (Math.abs(this._lastOvershoot - dragPosition) > OVERSHOOT_THRESHOLD) - this._resetOvershoot(); + const {targetActor} = dragEvent; + if (targetActor !== this._prevPageIndicator && + targetActor !== this._nextPageIndicator) { + this._resetOvershoot(); return; } - // Still in the area of the previous page switch - if (this._lastOvershoot >= 0) + if (this._overshootTimeoutId > 0) return; - const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; - if (dragPosition <= gridStart) - this.goToPage(this._grid.currentPage + (rtl ? 1 : -1)); - else if (dragPosition >= gridEnd) - this.goToPage(this._grid.currentPage + (rtl ? -1 : 1)); + let targetPage; + if (dragEvent.targetActor === this._prevPageIndicator) + targetPage = this._grid.currentPage - 1; else - return; // don't go beyond first/last page + targetPage = this._grid.currentPage + 1; - this._lastOvershoot = dragPosition; + if (targetPage < 0 || targetPage >= this._grid.nPages) + return; // don't go beyond first/last page - if (this._lastOvershootTimeoutId > 0) - GLib.source_remove(this._lastOvershootTimeoutId); + // If dragging over the drag overshoot threshold region, immediately + // switch pages + if (this._dragWithinOvershootRegion(dragEvent)) { + this._resetOvershoot(); + this.goToPage(targetPage); + } - this._lastOvershootTimeoutId = + this._overshootTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, OVERSHOOT_TIMEOUT, () => { this._resetOvershoot(); - this._handleDragOvershoot(dragEvent); + this.goToPage(targetPage); return GLib.SOURCE_REMOVE; }); - GLib.Source.set_name_by_id(this._lastOvershootTimeoutId, - '[gnome-shell] this._lastOvershootTimeoutId'); + GLib.Source.set_name_by_id(this._overshootTimeoutId, + '[gnome-shell] this._overshootTimeoutId'); } _onDragBegin() { this._dragMonitor = { dragMotion: this._onDragMotion.bind(this), + dragDrop: this._onDragDrop.bind(this), }; DND.addDragMonitor(this._dragMonitor); - this._slideSidePages(SidePages.PREVIOUS | SidePages.NEXT | SidePages.DND); + this._appGridLayout.showPageIndicators(); this._dragFocus = null; this._swipeTracker.enabled = false; } @@ -709,14 +920,6 @@ var BaseAppView = GObject.registerClass({ const appIcon = dragEvent.source; - this._dropPage = this._pageForCoords(dragEvent.x, dragEvent.y); - if (this._dropPage && - this._dropPage === SidePages.PREVIOUS && - this._grid.currentPage === 0) { - delete this._dropPage; - return DND.DragMotionResult.NO_DROP; - } - // Handle the drag overshoot. When dragging to above the // icon grid, move to the page above; when dragging below, // move to the page below. @@ -728,6 +931,13 @@ var BaseAppView = GObject.registerClass({ return DND.DragMotionResult.CONTINUE; } + _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; + } + _onDragEnd() { if (this._dragMonitor) { DND.removeDragMonitor(this._dragMonitor); @@ -735,8 +945,7 @@ var BaseAppView = GObject.registerClass({ } this._resetOvershoot(); - this._slideSidePages(SidePages.NONE); - delete this._dropPage; + this._appGridLayout.hidePageIndicators(); this._swipeTracker.enabled = true; } @@ -744,7 +953,7 @@ var BaseAppView = GObject.registerClass({ // At this point, the positions aren't stored yet, thus _redisplay() // will move all items to their original positions this._redisplay(); - this._slideSidePages(SidePages.NONE); + this._appGridLayout.hidePageIndicators(); this._swipeTracker.enabled = true; } @@ -760,11 +969,15 @@ var BaseAppView = GObject.registerClass({ } acceptDrop(source) { + const dropTarget = this._dropTarget; + delete this._dropTarget; + if (!this._canAccept(source)) return false; - if (this._dropPage) { - const increment = this._dropPage === SidePages.NEXT ? 1 : -1; + if (dropTarget === this._prevPageIndicator || + dropTarget === this._nextPageIndicator) { + const increment = dropTarget === this._prevPageIndicator ? -1 : 1; const { currentPage, nPages } = this._grid; const page = Math.min(currentPage + increment, nPages); const position = page < nPages ? -1 : 0; @@ -981,15 +1194,6 @@ var BaseAppView = GObject.registerClass({ this._grid.moveItem(item, newPage, newPosition); } - 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; this._connectDnD(); @@ -1023,276 +1227,16 @@ var BaseAppView = GObject.registerClass({ this._grid.ease(params); } - _syncPageHints(pageNumber, animate = true) { - const showingNextPage = this._pagesShown & SidePages.NEXT; - const showingPrevPage = this._pagesShown & SidePages.PREVIOUS; - const dnd = this._pagesShown & SidePages.DND; - 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 (!dnd) { - this._prevPageArrow.visible = true; - this._prevPageArrow.ease({ - opacity, - duration, - }); - } - } - - if (showingNextPage) { - const opacity = pageNumber === this._grid.nPages - 1 ? 0 : 255; - this._nextPageIndicator.visible = true; - this._nextPageIndicator.ease({ - opacity, - duration, - }); - - if (!dnd) { - this._nextPageArrow.visible = true; - this._nextPageArrow.ease({ - opacity, - duration, - }); - } - } - } - goToPage(pageNumber, animate = true) { pageNumber = Math.clamp(pageNumber, 0, this._grid.nPages - 1); if (this._grid.currentPage === pageNumber) return; - this._syncPageHints(pageNumber, animate); + this._appGridLayout.goToPage(pageNumber, animate); this._grid.goToPage(pageNumber, animate); } - adaptToSize(width, height) { - let box = new Clutter.ActorBox({ - x2: width, - 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); - - const availWidth = box.get_width(); - const availHeight = box.get_height(); - - const gridRatio = this._grid.layout_manager.columnsPerPage / - this._grid.layout_manager.rowsPerPage; - const spaceRatio = availWidth / availHeight; - let pageWidth, pageHeight; - - if (spaceRatio > gridRatio * 1.1) { - // Enough room for some preview - pageHeight = availHeight; - pageWidth = Math.ceil(availHeight * gridRatio); - - if (spaceRatio > gridRatio * 1.5) { - // Ultra-wide layout, give some extra space for - // the page area, but up to an extent. - const extraPageSpace = Math.min( - Math.floor((availWidth - pageWidth) / 2), MAX_PAGE_PADDING); - pageWidth += extraPageSpace; - this._grid.layout_manager.pagePadding.left = - Math.floor(extraPageSpace / 2); - this._grid.layout_manager.pagePadding.right = - Math.ceil(extraPageSpace / 2); - } - } else { - // Not enough room, needs to shrink horizontally - pageWidth = Math.ceil(availWidth * 0.8); - pageHeight = availHeight; - this._grid.layout_manager.pagePadding.left = - Math.floor(availWidth * 0.02); - this._grid.layout_manager.pagePadding.right = - Math.ceil(availWidth * 0.02); - } - - this._grid.adaptToSize(pageWidth, pageHeight); - - const leftPadding = Math.floor( - (availWidth - this._grid.layout_manager.pageWidth) / 2); - const rightPadding = Math.ceil( - (availWidth - this._grid.layout_manager.pageWidth) / 2); - const topPadding = Math.floor( - (availHeight - this._grid.layout_manager.pageHeight) / 2); - const bottomPadding = Math.ceil( - (availHeight - this._grid.layout_manager.pageHeight) / 2); - - this._scrollView.content_padding = new Clutter.Margin({ - left: leftPadding, - right: rightPadding, - top: topPadding, - bottom: bottomPadding, - }); - - this._availWidth = availWidth; - this._availHeight = availHeight; - - this._pageIndicatorOffset = leftPadding; - this._pageArrowOffset = Math.max( - leftPadding - PAGE_PREVIEW_MAX_ARROW_OFFSET, 0); - } - - _getIndicatorOffset(page, progress, baseOffset) { - const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; - const translationX = - (1 - progress) * PAGE_PREVIEW_ANIMATION_START_OFFSET; - - page = rtl ? -page : page; - - return (translationX - baseOffset) * page; - } - - _syncClip() { - const nextPageAdjustment = this._previewedPages.get(1); - const prevPageAdjustment = this._previewedPages.get(-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); - - const adjustment = new St.Adjustment({ - actor: this, - lower: 0, - upper: 1, - }); - - const indicator = page > 0 - ? this._nextPageIndicator : this._prevPageIndicator; - - adjustment.connectObject('notify::value', () => { - const nextPage = this._grid.currentPage + page; - const hasFollowingPage = nextPage >= 0 && - nextPage < this._grid.nPages; - - if (hasFollowingPage) { - const items = this._grid.getItemsAtPage(nextPage); - items.forEach(item => { - item.translation_x = - this._getIndicatorOffset(page, adjustment.value, 0); - }); - - if (!(state & SidePages.DND)) { - const pageArrow = page > 0 - ? this._nextPageArrow - : this._prevPageArrow; - pageArrow.set({ - visible: true, - opacity: adjustment.value * 255, - translation_x: this._getIndicatorOffset( - page, adjustment.value, - this._pageArrowOffset), - }); - } - } - if (hasFollowingPage || - (page > 0 && - this._grid.layout_manager.allow_incomplete_pages && - (state & SidePages.DND) !== 0)) { - indicator.set({ - visible: true, - opacity: adjustment.value * 255, - translation_x: this._getIndicatorOffset( - page, adjustment.value, - this._pageIndicatorOffset - indicator.width), - }); - } - this._syncClip(); - }, this); - - this._previewedPages.set(page, adjustment); - - return adjustment; - } - - _teardownPagePreview(page) { - const adjustment = this._previewedPages.get(page); - if (!adjustment) - return; - - adjustment.value = 1; - adjustment.disconnectObject(this); - this._previewedPages.delete(page); - } - - _slideSidePages(state) { - if (this._pagesShown === state) - return; - this._pagesShown = state; - const showingNextPage = state & SidePages.NEXT; - const showingPrevPage = state & SidePages.PREVIOUS; - const dnd = state & SidePages.DND; - let adjustment; - - if (dnd) { - this._nextPageIndicator.add_style_class_name('dnd'); - this._prevPageIndicator.add_style_class_name('dnd'); - } else { - this._nextPageIndicator.remove_style_class_name('dnd'); - this._prevPageIndicator.remove_style_class_name('dnd'); - } - - adjustment = this._previewedPages.get(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._nextPageArrow.visible = false; - this._nextPageIndicator.visible = false; - this._updateFadeForNavigation(); - }, - }); - } - - adjustment = this._previewedPages.get(-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._prevPageArrow.visible = false; - this._prevPageIndicator.visible = false; - this._updateFadeForNavigation(); - }, - }); - } - } - updateDragFocus(dragFocus) { this._dragFocus = dragFocus; } @@ -1371,13 +1315,7 @@ class AppDisplay extends BaseAppView { this._pageManager = new PageManager(); this._pageManager.connect('layout-changed', () => this._redisplay()); - this._stack = new St.Widget({ - layout_manager: new Clutter.BinLayout(), - x_expand: true, - y_expand: true, - }); - this.add_actor(this._stack); - this._stack.add_child(this._box); + this.add_child(this._box); this._folderIcons = []; @@ -1432,14 +1370,6 @@ class AppDisplay extends BaseAppView { super._redisplay(); } - adaptToSize(width, height) { - const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1); - height -= indicatorHeight; - - this._grid.findBestModeForSize(width, height); - super.adaptToSize(width, height); - } - _savePages() { const pages = []; @@ -2124,7 +2054,7 @@ class AppViewItem extends St.Button { }); var FolderGrid = GObject.registerClass( -class FolderGrid extends IconGrid.IconGrid { +class FolderGrid extends AppGrid { _init() { super._init({ allow_incomplete_pages: false, @@ -2133,10 +2063,13 @@ class FolderGrid extends IconGrid.IconGrid { page_halign: Clutter.ActorAlign.CENTER, page_valign: Clutter.ActorAlign.CENTER, }); - } - adaptToSize(width, height) { - this.layout_manager.adaptToSize(width, height); + this.setGridModes([ + { + rows: 3, + columns: 3, + }, + ]); } }); @@ -2270,13 +2203,6 @@ class FolderView extends BaseAppView { return false; } - adaptToSize(width, height) { - const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1); - height -= indicatorHeight; - - super.adaptToSize(width, height); - } - _loadApps() { let apps = []; let appSys = Shell.AppSystem.get_default(); diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js index 599374fa35922daa040f33818630c97bc6575491..ac8d3ecaafc8c26aed1cb7bb8fc688c16b88664b 100644 --- a/js/ui/iconGrid.js +++ b/js/ui/iconGrid.js @@ -1180,7 +1180,7 @@ var IconGrid = GObject.registerClass({ } } - findBestModeForSize(width, height) { + _findBestModeForSize(width, height) { const { pagePadding } = this.layout_manager; width -= pagePadding.left + pagePadding.right; height -= pagePadding.top + pagePadding.bottom; @@ -1207,6 +1207,13 @@ var IconGrid = GObject.registerClass({ delete child._iconGridKeyFocusInId; } + vfunc_allocate(box) { + const [width, height] = box.get_size(); + this._findBestModeForSize(width, height); + this.layout_manager.adaptToSize(width, height); + super.vfunc_allocate(box); + } + vfunc_style_changed() { super.vfunc_style_changed(); @@ -1391,10 +1398,6 @@ var IconGrid = GObject.registerClass({ return this.layout_manager.nPages; } - adaptToSize(width, height) { - this.layout_manager.adaptToSize(width, height); - } - setGridModes(modes) { this._gridModes = modes ? modes : defaultGridModes; this.queue_relayout(); diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c index 4cf45af1f20334817fdad9962dc68be7dc6cede8..77b1d0b12083038f7978120f94d164f4d58054d0 100644 --- a/src/st/st-scroll-view-fade.c +++ b/src/st/st-scroll-view-fade.c @@ -99,7 +99,6 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, gboolean h_scroll_visible, v_scroll_visible, rtl; ClutterActorBox allocation, content_box, paint_box; - ClutterMargin *content_padding; float fade_area_topleft[2]; float fade_area_bottomright[2]; @@ -111,13 +110,6 @@ st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect, clutter_actor_get_allocation_box (self->actor, &allocation); st_theme_node_get_content_box (st_widget_get_theme_node (ST_WIDGET (self->actor)), (const ClutterActorBox *)&allocation, &content_box); - g_object_get (self->actor, "content-padding", &content_padding, NULL); - - content_box.x1 += content_padding->left; - content_box.x2 -= content_padding->right; - content_box.y1 += content_padding->top; - content_box.y2 -= content_padding->bottom; - clutter_margin_free (content_padding); /* * The FBO is based on the paint_volume's size which can be larger then the actual diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c index cf13ccee0fac9bacc0a27d5d89e5e2bcdc79c340..50de481944c5f0110143ded33078923c006c272f 100644 --- a/src/st/st-scroll-view.c +++ b/src/st/st-scroll-view.c @@ -84,8 +84,6 @@ struct _StScrollViewPrivate StAdjustment *vadjustment; ClutterActor *vscroll; - ClutterMargin content_padding; - StPolicyType hscrollbar_policy; StPolicyType vscrollbar_policy; @@ -118,7 +116,6 @@ enum { PROP_VSCROLLBAR_VISIBLE, PROP_MOUSE_SCROLL, PROP_OVERLAY_SCROLLBARS, - PROP_CONTENT_PADDING, N_PROPS }; @@ -159,9 +156,6 @@ st_scroll_view_get_property (GObject *object, case PROP_OVERLAY_SCROLLBARS: g_value_set_boolean (value, priv->overlay_scrollbars); break; - case PROP_CONTENT_PADDING: - g_value_set_boxed (value, &priv->content_padding); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -208,23 +202,6 @@ st_scroll_view_update_fade_effect (StScrollView *scroll, } } -static void -st_scroll_view_set_content_padding (StScrollView *scroll, - ClutterMargin *content_padding) -{ - StScrollViewPrivate *priv = ST_SCROLL_VIEW (scroll)->priv; - - if (priv->content_padding.left == content_padding->left && - priv->content_padding.right == content_padding->right && - priv->content_padding.top == content_padding->top && - priv->content_padding.bottom == content_padding->bottom) - return; - - priv->content_padding = *content_padding; - - g_object_notify_by_pspec (G_OBJECT (scroll), props[PROP_CONTENT_PADDING]); -} - static void st_scroll_view_set_property (GObject *object, guint property_id, @@ -254,10 +231,6 @@ st_scroll_view_set_property (GObject *object, priv->hscrollbar_policy, g_value_get_enum (value)); break; - case PROP_CONTENT_PADDING: - st_scroll_view_set_content_padding (self, - (ClutterMargin *)g_value_get_boxed (value)); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -570,11 +543,6 @@ st_scroll_view_allocate (ClutterActor *actor, st_theme_node_get_content_box (theme_node, box, &content_box); - content_box.x1 += priv->content_padding.left; - content_box.x2 -= priv->content_padding.right; - content_box.y1 += priv->content_padding.top; - content_box.y2 += priv->content_padding.bottom; - avail_width = content_box.x2 - content_box.x1; avail_height = content_box.y2 - content_box.y1; @@ -966,13 +934,6 @@ st_scroll_view_class_init (StScrollViewClass *klass) FALSE, ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - props[PROP_CONTENT_PADDING] = - g_param_spec_boxed ("content-padding", - "Content padding", - "Content padding", - CLUTTER_TYPE_MARGIN, - ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - g_object_class_install_properties (object_class, N_PROPS, props); }