diff options
Diffstat (limited to 'js/ui/pageIndicators.js')
-rw-r--r-- | js/ui/pageIndicators.js | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/js/ui/pageIndicators.js b/js/ui/pageIndicators.js new file mode 100644 index 0000000..18a376c --- /dev/null +++ b/js/ui/pageIndicators.js @@ -0,0 +1,116 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +/* exported PageIndicators */ + +const { Clutter, Graphene, GObject, St } = imports.gi; + +const INDICATOR_INACTIVE_OPACITY = 128; +const INDICATOR_INACTIVE_OPACITY_HOVER = 255; +const INDICATOR_INACTIVE_SCALE = 2 / 3; +const INDICATOR_INACTIVE_SCALE_PRESSED = 0.5; + +var PageIndicators = GObject.registerClass({ + Signals: { 'page-activated': { param_types: [GObject.TYPE_INT] } }, +}, class PageIndicators extends St.BoxLayout { + _init(orientation = Clutter.Orientation.VERTICAL) { + let vertical = orientation == Clutter.Orientation.VERTICAL; + 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._currentPosition = 0; + this._reactive = true; + this._reactive = true; + this._orientation = orientation; + } + + 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; + const indicator = new St.Button({ + style_class: 'page-indicator', + button_mask: St.ButtonMask.ONE | + St.ButtonMask.TWO | + St.ButtonMask.THREE, + reactive: this._reactive, + }); + indicator.child = new St.Widget({ + style_class: 'page-indicator-icon', + pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), + }); + indicator.connect('clicked', () => { + this.emit('page-activated', pageIndex); + }); + indicator.connect('notify::hover', () => { + this._updateIndicator(indicator, pageIndex); + }); + indicator.connect('notify::pressed', () => { + this._updateIndicator(indicator, pageIndex); + }); + this._updateIndicator(indicator, 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; + } + + _updateIndicator(indicator, pageIndex) { + let progress = + Math.max(1 - Math.abs(this._currentPosition - pageIndex), 0); + + let inactiveScale = indicator.pressed + ? INDICATOR_INACTIVE_SCALE_PRESSED : INDICATOR_INACTIVE_SCALE; + let inactiveOpacity = indicator.hover + ? INDICATOR_INACTIVE_OPACITY_HOVER : INDICATOR_INACTIVE_OPACITY; + + let scale = inactiveScale + (1 - inactiveScale) * progress; + let opacity = inactiveOpacity + (255 - inactiveOpacity) * progress; + + indicator.child.set_scale(scale, scale); + indicator.child.opacity = opacity; + } + + setCurrentPosition(currentPosition) { + this._currentPosition = currentPosition; + + let children = this.get_children(); + for (let i = 0; i < children.length; i++) + this._updateIndicator(children[i], i); + } + + get nPages() { + return this._nPages; + } +}); |