diff options
Diffstat (limited to 'js/ui/osdWindow.js')
-rw-r--r-- | js/ui/osdWindow.js | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/js/ui/osdWindow.js b/js/ui/osdWindow.js new file mode 100644 index 0000000..bae4b86 --- /dev/null +++ b/js/ui/osdWindow.js @@ -0,0 +1,250 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +/* exported OsdWindowManager */ + +const { Clutter, GLib, GObject, Meta, St } = imports.gi; + +const BarLevel = imports.ui.barLevel; +const Layout = imports.ui.layout; +const Main = imports.ui.main; + +var HIDE_TIMEOUT = 1500; +var FADE_TIME = 100; +var LEVEL_ANIMATION_TIME = 100; + +var OsdWindowConstraint = GObject.registerClass( +class OsdWindowConstraint extends Clutter.Constraint { + _init(props) { + this._minSize = 0; + super._init(props); + } + + set minSize(v) { + this._minSize = v; + if (this.actor) + this.actor.queue_relayout(); + } + + vfunc_update_allocation(actor, actorBox) { + // Clutter will adjust the allocation for margins, + // so add it to our minimum size + let minSize = this._minSize + actor.margin_top + actor.margin_bottom; + let [width, height] = actorBox.get_size(); + + // Enforce a ratio of 1 + let size = Math.ceil(Math.max(minSize, height)); + actorBox.set_size(size, size); + + // Recenter + let [x, y] = actorBox.get_origin(); + actorBox.set_origin(Math.ceil(x + width / 2 - size / 2), + Math.ceil(y + height / 2 - size / 2)); + } +}); + +var OsdWindow = GObject.registerClass( +class OsdWindow extends St.Widget { + _init(monitorIndex) { + super._init({ + x_expand: true, + y_expand: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + }); + + this._monitorIndex = monitorIndex; + let constraint = new Layout.MonitorConstraint({ index: monitorIndex }); + this.add_constraint(constraint); + + this._boxConstraint = new OsdWindowConstraint(); + this._box = new St.BoxLayout({ style_class: 'osd-window', + vertical: true }); + this._box.add_constraint(this._boxConstraint); + this.add_actor(this._box); + + this._icon = new St.Icon({ y_expand: true }); + this._box.add_child(this._icon); + + this._label = new St.Label(); + this._box.add(this._label); + + this._level = new BarLevel.BarLevel({ + style_class: 'level', + value: 0, + }); + this._box.add(this._level); + + this._hideTimeoutId = 0; + this._reset(); + + this.connect('destroy', this._onDestroy.bind(this)); + + this._monitorsChangedId = + Main.layoutManager.connect('monitors-changed', + this._relayout.bind(this)); + let themeContext = St.ThemeContext.get_for_stage(global.stage); + this._scaleChangedId = + themeContext.connect('notify::scale-factor', + this._relayout.bind(this)); + this._relayout(); + Main.uiGroup.add_child(this); + } + + _onDestroy() { + if (this._monitorsChangedId) + Main.layoutManager.disconnect(this._monitorsChangedId); + this._monitorsChangedId = 0; + + let themeContext = St.ThemeContext.get_for_stage(global.stage); + if (this._scaleChangedId) + themeContext.disconnect(this._scaleChangedId); + this._scaleChangedId = 0; + } + + setIcon(icon) { + this._icon.gicon = icon; + } + + setLabel(label) { + this._label.visible = label != undefined; + if (label) + this._label.text = label; + } + + setLevel(value) { + this._level.visible = value != undefined; + if (value != undefined) { + if (this.visible) { + this._level.ease_property('value', value, { + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + duration: LEVEL_ANIMATION_TIME, + }); + } else { + this._level.value = value; + } + } + } + + setMaxLevel(maxLevel = 1) { + this._level.maximum_value = maxLevel; + } + + show() { + if (!this._icon.gicon) + return; + + if (!this.visible) { + Meta.disable_unredirect_for_display(global.display); + super.show(); + this.opacity = 0; + this.get_parent().set_child_above_sibling(this, null); + + this.ease({ + opacity: 255, + duration: FADE_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + + if (this._hideTimeoutId) + GLib.source_remove(this._hideTimeoutId); + this._hideTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, HIDE_TIMEOUT, this._hide.bind(this)); + GLib.Source.set_name_by_id(this._hideTimeoutId, '[gnome-shell] this._hide'); + } + + cancel() { + if (!this._hideTimeoutId) + return; + + GLib.source_remove(this._hideTimeoutId); + this._hide(); + } + + _hide() { + this._hideTimeoutId = 0; + this.ease({ + opacity: 0, + duration: FADE_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._reset(); + Meta.enable_unredirect_for_display(global.display); + }, + }); + return GLib.SOURCE_REMOVE; + } + + _reset() { + super.hide(); + this.setLabel(null); + this.setMaxLevel(null); + this.setLevel(null); + } + + _relayout() { + /* assume 110x110 on a 640x480 display and scale from there */ + let monitor = Main.layoutManager.monitors[this._monitorIndex]; + if (!monitor) + return; // we are about to be removed + + let scalew = monitor.width / 640.0; + let scaleh = monitor.height / 480.0; + let scale = Math.min(scalew, scaleh); + let popupSize = 110 * Math.max(1, scale); + + let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; + this._icon.icon_size = popupSize / (2 * scaleFactor); + this._box.translation_y = Math.round(monitor.height / 4); + this._boxConstraint.minSize = popupSize; + } +}); + +var OsdWindowManager = class { + constructor() { + this._osdWindows = []; + Main.layoutManager.connect('monitors-changed', + this._monitorsChanged.bind(this)); + this._monitorsChanged(); + } + + _monitorsChanged() { + for (let i = 0; i < Main.layoutManager.monitors.length; i++) { + if (this._osdWindows[i] == undefined) + this._osdWindows[i] = new OsdWindow(i); + } + + for (let i = Main.layoutManager.monitors.length; i < this._osdWindows.length; i++) { + this._osdWindows[i].destroy(); + this._osdWindows[i] = null; + } + + this._osdWindows.length = Main.layoutManager.monitors.length; + } + + _showOsdWindow(monitorIndex, icon, label, level, maxLevel) { + this._osdWindows[monitorIndex].setIcon(icon); + this._osdWindows[monitorIndex].setLabel(label); + this._osdWindows[monitorIndex].setMaxLevel(maxLevel); + this._osdWindows[monitorIndex].setLevel(level); + this._osdWindows[monitorIndex].show(); + } + + show(monitorIndex, icon, label, level, maxLevel) { + if (monitorIndex != -1) { + for (let i = 0; i < this._osdWindows.length; i++) { + if (i == monitorIndex) + this._showOsdWindow(i, icon, label, level, maxLevel); + else + this._osdWindows[i].cancel(); + } + } else { + for (let i = 0; i < this._osdWindows.length; i++) + this._showOsdWindow(i, icon, label, level, maxLevel); + } + } + + hideAll() { + for (let i = 0; i < this._osdWindows.length; i++) + this._osdWindows[i].cancel(); + } +}; |