summaryrefslogtreecommitdiffstats
path: root/js/ui/pageIndicators.js
blob: 18a376cba0389c9bc5bfa19d8fb5aaf975daaaea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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;
    }
});