2019-01-22 06:10:51 -05:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
/* exported PageIndicators, AnimatedPageIndicators */
|
2019-01-22 06:10:51 -05:00
|
|
|
|
2019-11-21 17:24:46 -05:00
|
|
|
const { Clutter, GLib, Graphene, GObject, Meta, St } = imports.gi;
|
2019-01-22 06:10:51 -05:00
|
|
|
|
|
|
|
const { ANIMATION_TIME_OUT, ANIMATION_MAX_DELAY_OUT_FOR_ITEM, AnimationDirection } = imports.ui.iconGrid;
|
|
|
|
|
2019-11-21 17:24:46 -05:00
|
|
|
const INDICATOR_INACTIVE_OPACITY = 128;
|
|
|
|
const INDICATOR_INACTIVE_OPACITY_HOVER = 255;
|
|
|
|
const INDICATOR_INACTIVE_SCALE = 2 / 3;
|
|
|
|
const INDICATOR_INACTIVE_SCALE_PRESSED = 0.5;
|
|
|
|
|
2019-08-01 19:13:10 -04:00
|
|
|
var INDICATORS_BASE_TIME = 250;
|
|
|
|
var INDICATORS_BASE_TIME_OUT = 125;
|
|
|
|
var INDICATORS_ANIMATION_DELAY = 125;
|
|
|
|
var INDICATORS_ANIMATION_DELAY_OUT = 62.5;
|
|
|
|
var INDICATORS_ANIMATION_MAX_TIME = 750;
|
|
|
|
var SWITCH_TIME = 400;
|
2019-01-22 06:10:51 -05:00
|
|
|
var INDICATORS_ANIMATION_MAX_TIME_OUT =
|
2019-08-19 13:55:49 -04:00
|
|
|
Math.min(SWITCH_TIME,
|
|
|
|
ANIMATION_TIME_OUT + ANIMATION_MAX_DELAY_OUT_FOR_ITEM);
|
2019-01-22 06:10:51 -05:00
|
|
|
|
2019-08-01 19:13:10 -04:00
|
|
|
var ANIMATION_DELAY = 100;
|
2019-01-22 06:10:51 -05:00
|
|
|
|
|
|
|
var PageIndicators = GObject.registerClass({
|
2019-08-20 17:43:54 -04:00
|
|
|
Signals: { 'page-activated': { param_types: [GObject.TYPE_INT] } },
|
2019-01-22 06:10:51 -05:00
|
|
|
}, class PageIndicators extends St.BoxLayout {
|
2019-09-10 01:36:58 -04:00
|
|
|
_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,
|
2019-08-20 17:43:54 -04:00
|
|
|
clip_to_allocation: true,
|
2019-09-10 01:36:58 -04:00
|
|
|
});
|
2019-01-22 06:10:51 -05:00
|
|
|
this._nPages = 0;
|
2019-11-21 17:24:46 -05:00
|
|
|
this._currentPosition = 0;
|
2019-01-22 06:10:51 -05:00
|
|
|
this._reactive = true;
|
|
|
|
this._reactive = true;
|
2020-12-02 16:33:26 -05:00
|
|
|
this._orientation = orientation;
|
2019-01-22 06:10:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2019-11-21 17:24:46 -05:00
|
|
|
reactive: this._reactive });
|
|
|
|
indicator.child = new St.Widget({
|
|
|
|
style_class: 'page-indicator-icon',
|
|
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
|
|
|
|
});
|
2019-01-22 06:10:51 -05:00
|
|
|
indicator.connect('clicked', () => {
|
|
|
|
this.emit('page-activated', pageIndex);
|
|
|
|
});
|
2019-11-21 17:24:46 -05:00
|
|
|
indicator.connect('notify::hover', () => {
|
|
|
|
this._updateIndicator(indicator, pageIndex);
|
|
|
|
});
|
|
|
|
indicator.connect('notify::pressed', () => {
|
|
|
|
this._updateIndicator(indicator, pageIndex);
|
|
|
|
});
|
|
|
|
this._updateIndicator(indicator, pageIndex);
|
2019-01-22 06:10:51 -05:00
|
|
|
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;
|
2019-08-19 15:38:51 -04:00
|
|
|
this.visible = this._nPages > 1;
|
2019-01-22 06:10:51 -05:00
|
|
|
}
|
|
|
|
|
2019-11-21 17:24:46 -05:00
|
|
|
_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;
|
2019-01-22 06:10:51 -05:00
|
|
|
|
|
|
|
let children = this.get_children();
|
|
|
|
for (let i = 0; i < children.length; i++)
|
2019-11-21 17:24:46 -05:00
|
|
|
this._updateIndicator(children[i], i);
|
2019-01-22 06:10:51 -05:00
|
|
|
}
|
2020-05-25 15:51:26 -04:00
|
|
|
|
|
|
|
get nPages() {
|
|
|
|
return this._nPages;
|
|
|
|
}
|
2019-01-22 06:10:51 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
var AnimatedPageIndicators = GObject.registerClass(
|
|
|
|
class AnimatedPageIndicators extends PageIndicators {
|
2020-12-02 16:33:26 -05:00
|
|
|
_init(orientation = Clutter.Orientation.VERTICAL) {
|
|
|
|
super._init(orientation);
|
2019-09-10 01:42:48 -04:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
_onDestroy() {
|
|
|
|
if (this.animateLater) {
|
|
|
|
Meta.later_remove(this.animateLater);
|
|
|
|
this.animateLater = 0;
|
|
|
|
}
|
|
|
|
}
|
2019-01-22 06:10:51 -05:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_map() {
|
|
|
|
super.vfunc_map();
|
2019-08-04 19:02:38 -04:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
// Implicit animations are skipped for unmapped actors, and our
|
|
|
|
// children aren't mapped yet, so defer to a later handler
|
|
|
|
this.animateLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
|
|
|
this.animateLater = 0;
|
|
|
|
this.animateIndicators(AnimationDirection.IN);
|
|
|
|
return GLib.SOURCE_REMOVE;
|
2019-01-22 06:10:51 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
animateIndicators(animationDirection) {
|
|
|
|
if (!this.mapped)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let children = this.get_children();
|
|
|
|
if (children.length == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (let i = 0; i < this._nPages; i++)
|
2018-07-20 15:46:19 -04:00
|
|
|
children[i].remove_all_transitions();
|
2019-01-22 06:10:51 -05:00
|
|
|
|
2020-12-02 16:33:26 -05:00
|
|
|
const vertical = this._orientation === Clutter.Orientation.VERTICAL;
|
2019-01-22 06:10:51 -05:00
|
|
|
let offset;
|
|
|
|
if (this.get_text_direction() == Clutter.TextDirection.RTL)
|
2020-12-02 16:33:26 -05:00
|
|
|
offset = vertical ? -children[0].width : -children[0].height;
|
2019-01-22 06:10:51 -05:00
|
|
|
else
|
2020-12-02 16:33:26 -05:00
|
|
|
offset = vertical ? children[0].width : children[0].height;
|
2019-01-22 06:10:51 -05:00
|
|
|
|
|
|
|
let isAnimationIn = animationDirection == AnimationDirection.IN;
|
2019-08-19 15:33:15 -04:00
|
|
|
let delay = isAnimationIn
|
|
|
|
? INDICATORS_ANIMATION_DELAY
|
|
|
|
: INDICATORS_ANIMATION_DELAY_OUT;
|
2019-01-22 06:10:51 -05:00
|
|
|
let baseTime = isAnimationIn ? INDICATORS_BASE_TIME : INDICATORS_BASE_TIME_OUT;
|
|
|
|
let totalAnimationTime = baseTime + delay * this._nPages;
|
2019-08-19 15:33:15 -04:00
|
|
|
let maxTime = isAnimationIn
|
|
|
|
? INDICATORS_ANIMATION_MAX_TIME
|
|
|
|
: INDICATORS_ANIMATION_MAX_TIME_OUT;
|
2019-01-22 06:10:51 -05:00
|
|
|
if (totalAnimationTime > maxTime)
|
|
|
|
delay -= (totalAnimationTime - maxTime) / this._nPages;
|
|
|
|
|
|
|
|
for (let i = 0; i < this._nPages; i++) {
|
2020-12-02 16:33:26 -05:00
|
|
|
const params = {
|
2018-07-20 15:46:19 -04:00
|
|
|
duration: baseTime + delay * i,
|
|
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
delay: isAnimationIn ? ANIMATION_DELAY : 0,
|
2020-12-02 16:33:26 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
if (vertical) {
|
|
|
|
children[i].translation_x = isAnimationIn ? offset : 0;
|
|
|
|
params.translation_x = isAnimationIn ? 0 : offset;
|
|
|
|
} else {
|
|
|
|
children[i].translation_y = isAnimationIn ? offset : 0;
|
|
|
|
params.translation_y = isAnimationIn ? 0 : offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
children[i].ease(params);
|
2019-01-22 06:10:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|