diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index a3dd2be08..8532aaf45 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -78,6 +78,7 @@ ui/overview.js ui/overviewControls.js ui/padOsd.js + ui/pageIndicators.js ui/panel.js ui/panelMenu.js ui/pointerWatcher.js diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 1a3533cab..23ec4df70 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -19,6 +19,7 @@ const IconGrid = imports.ui.iconGrid; const Main = imports.ui.main; const Overview = imports.ui.overview; const OverviewControls = imports.ui.overviewControls; +const PageIndicators = imports.ui.pageIndicators; const PopupMenu = imports.ui.popupMenu; const Tweener = imports.ui.tweener; const Workspace = imports.ui.workspace; @@ -245,116 +246,6 @@ class BaseAppView { }; Signals.addSignalMethods(BaseAppView.prototype); -var PageIndicatorsActor = GObject.registerClass( -class PageIndicatorsActor extends St.BoxLayout { - _init() { - super._init({ style_class: 'page-indicators', - vertical: true, - x_expand: true, y_expand: true, - x_align: Clutter.ActorAlign.END, - y_align: Clutter.ActorAlign.CENTER, - reactive: true, - clip_to_allocation: true }); - } - - vfunc_get_preferred_height(forWidth) { - // We want to request the natural height of all our children as our - // natural height, so we chain up to St.BoxLayout, but we only request 0 - // as minimum height, since it's not that important if some indicators - // are not shown - let [, natHeight] = super.vfunc_get_preferred_height(forWidth); - return [0, natHeight]; - } -}); - -class PageIndicators { - constructor() { - this.actor = new PageIndicatorsActor(); - this._nPages = 0; - this._currentPage = undefined; - - this.actor.connect('notify::mapped', () => { - this.animateIndicators(IconGrid.AnimationDirection.IN); - }); - } - - setNPages(nPages) { - if (this._nPages == nPages) - return; - - let diff = nPages - this._nPages; - if (diff > 0) { - for (let i = 0; i < diff; i++) { - let pageIndex = this._nPages + i; - let indicator = new St.Button({ style_class: 'page-indicator', - button_mask: St.ButtonMask.ONE | - St.ButtonMask.TWO | - St.ButtonMask.THREE, - toggle_mode: true, - checked: pageIndex == this._currentPage }); - indicator.child = new St.Widget({ style_class: 'page-indicator-icon' }); - indicator.connect('clicked', () => { - this.emit('page-activated', pageIndex); - }); - this.actor.add_actor(indicator); - } - } else { - let children = this.actor.get_children().splice(diff); - for (let i = 0; i < children.length; i++) - children[i].destroy(); - } - this._nPages = nPages; - this.actor.visible = (this._nPages > 1); - } - - setCurrentPage(currentPage) { - this._currentPage = currentPage; - - let children = this.actor.get_children(); - for (let i = 0; i < children.length; i++) - children[i].set_checked(i == this._currentPage); - } - - animateIndicators(animationDirection) { - if (!this.actor.mapped) - return; - - let children = this.actor.get_children(); - if (children.length == 0) - return; - - for (let i = 0; i < this._nPages; i++) - Tweener.removeTweens(children[i]); - - let offset; - if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) - offset = -children[0].width; - else - offset = children[0].width; - - let isAnimationIn = animationDirection == IconGrid.AnimationDirection.IN; - let delay = isAnimationIn ? INDICATORS_ANIMATION_DELAY : - INDICATORS_ANIMATION_DELAY_OUT; - let baseTime = isAnimationIn ? INDICATORS_BASE_TIME : INDICATORS_BASE_TIME_OUT; - let totalAnimationTime = baseTime + delay * this._nPages; - let maxTime = isAnimationIn ? INDICATORS_ANIMATION_MAX_TIME : - INDICATORS_ANIMATION_MAX_TIME_OUT; - if (totalAnimationTime > maxTime) - delay -= (totalAnimationTime - maxTime) / this._nPages; - - for (let i = 0; i < this._nPages; i++) { - children[i].translation_x = isAnimationIn ? offset : 0; - Tweener.addTween(children[i], - { translation_x: isAnimationIn ? 0 : offset, - time: baseTime + delay * i, - transition: 'easeInOutQuad', - delay: isAnimationIn ? VIEWS_SWITCH_ANIMATION_DELAY : 0 - }); - } - } -}; -Signals.addSignalMethods(PageIndicators.prototype); - var AllView = class AllView extends BaseAppView { constructor() { super({ usePagination: true }, null); @@ -373,13 +264,13 @@ var AllView = class AllView extends BaseAppView { St.PolicyType.EXTERNAL); this._adjustment = this._scrollView.vscroll.adjustment; - this._pageIndicators = new PageIndicators(); + this._pageIndicators = new PageIndicators.AnimatedPageIndicators(); this._pageIndicators.connect('page-activated', (indicators, pageIndex) => { this.goToPage(pageIndex); }); - this._pageIndicators.actor.connect('scroll-event', this._onScroll.bind(this)); - this.actor.add_actor(this._pageIndicators.actor); + this._pageIndicators.connect('scroll-event', this._onScroll.bind(this)); + this.actor.add_actor(this._pageIndicators); this.folderIcons = []; diff --git a/js/ui/pageIndicators.js b/js/ui/pageIndicators.js new file mode 100644 index 000000000..43e6fcf4b --- /dev/null +++ b/js/ui/pageIndicators.js @@ -0,0 +1,142 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Clutter = imports.gi.Clutter; +const GObject = imports.gi.GObject; +const St = imports.gi.St; + +const Tweener = imports.ui.tweener; +const { ANIMATION_TIME_OUT, ANIMATION_MAX_DELAY_OUT_FOR_ITEM, AnimationDirection } = imports.ui.iconGrid; + +var INDICATORS_BASE_TIME = 0.25; +var INDICATORS_BASE_TIME_OUT = 0.125; +var INDICATORS_ANIMATION_DELAY = 0.125; +var INDICATORS_ANIMATION_DELAY_OUT = 0.0625; +var INDICATORS_ANIMATION_MAX_TIME = 0.75; +var SWITCH_TIME = 0.4; +var INDICATORS_ANIMATION_MAX_TIME_OUT = + Math.min (SWITCH_TIME, + ANIMATION_TIME_OUT + ANIMATION_MAX_DELAY_OUT_FOR_ITEM); + +var ANIMATION_DELAY = 0.1; + +var PageIndicators = GObject.registerClass({ + Signals: { 'page-activated': { param_types: [GObject.TYPE_INT] } } +}, class PageIndicators extends St.BoxLayout { + _init(vertical = true) { + super._init({ style_class: 'page-indicators', + vertical, + x_expand: true, y_expand: true, + x_align: vertical ? Clutter.ActorAlign.END : Clutter.ActorAlign.CENTER, + y_align: vertical ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.END, + reactive: true, + clip_to_allocation: true }); + this._nPages = 0; + this._currentPage = undefined; + this._reactive = true; + this._reactive = true; + } + + vfunc_get_preferred_height(forWidth) { + // We want to request the natural height of all our children as our + // natural height, so we chain up to St.BoxLayout, but we only request 0 + // as minimum height, since it's not that important if some indicators + // are not shown + let [, natHeight] = super.vfunc_get_preferred_height(forWidth); + return [0, natHeight]; + } + + setReactive(reactive) { + let children = this.get_children(); + for (let i = 0; i < children.length; i++) + children[i].reactive = reactive; + + this._reactive = reactive; + } + + setNPages(nPages) { + if (this._nPages == nPages) + return; + + let diff = nPages - this._nPages; + if (diff > 0) { + for (let i = 0; i < diff; i++) { + let pageIndex = this._nPages + i; + let indicator = new St.Button({ style_class: 'page-indicator', + button_mask: St.ButtonMask.ONE | + St.ButtonMask.TWO | + St.ButtonMask.THREE, + toggle_mode: true, + reactive: this._reactive, + checked: pageIndex == this._currentPage }); + indicator.child = new St.Widget({ style_class: 'page-indicator-icon' }); + indicator.connect('clicked', () => { + this.emit('page-activated', pageIndex); + }); + this.add_actor(indicator); + } + } else { + let children = this.get_children().splice(diff); + for (let i = 0; i < children.length; i++) + children[i].destroy(); + } + this._nPages = nPages; + this.visible = (this._nPages > 1); + } + + setCurrentPage(currentPage) { + this._currentPage = currentPage; + + let children = this.get_children(); + for (let i = 0; i < children.length; i++) + children[i].set_checked(i == this._currentPage); + } +}); + +var AnimatedPageIndicators = GObject.registerClass( +class AnimatedPageIndicators extends PageIndicators { + _init() { + super._init(true); + + this.connect('notify::mapped', () => { + this.animateIndicators(AnimationDirection.IN); + }); + } + + animateIndicators(animationDirection) { + if (!this.mapped) + return; + + let children = this.get_children(); + if (children.length == 0) + return; + + for (let i = 0; i < this._nPages; i++) + Tweener.removeTweens(children[i]); + + let offset; + if (this.get_text_direction() == Clutter.TextDirection.RTL) + offset = -children[0].width; + else + offset = children[0].width; + + let isAnimationIn = animationDirection == AnimationDirection.IN; + let delay = isAnimationIn ? INDICATORS_ANIMATION_DELAY : + INDICATORS_ANIMATION_DELAY_OUT; + let baseTime = isAnimationIn ? INDICATORS_BASE_TIME : INDICATORS_BASE_TIME_OUT; + let totalAnimationTime = baseTime + delay * this._nPages; + let maxTime = isAnimationIn ? INDICATORS_ANIMATION_MAX_TIME : + INDICATORS_ANIMATION_MAX_TIME_OUT; + if (totalAnimationTime > maxTime) + delay -= (totalAnimationTime - maxTime) / this._nPages; + + for (let i = 0; i < this._nPages; i++) { + children[i].translation_x = isAnimationIn ? offset : 0; + Tweener.addTween(children[i], { + translation_x: isAnimationIn ? 0 : offset, + time: baseTime + delay * i, + transition: 'easeInOutQuad', + delay: isAnimationIn ? ANIMATION_DELAY : 0 + }); + } + } +});