diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 9e5c46d96..1d9201853 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -1608,12 +1608,26 @@ StScrollBar StButton#vhandle:active { -shell-counter-overlap-y: 13px; } +/* OSD */ +.osd-window { + text-align: center; + font-weight: bold; + spacing: 1em; +} + +.osd-window .level { + height: 0.6em; + border-radius: 0.3em; + background-color: rgba(190,190,190,0.2); +} + /* App Switcher */ .switcher-popup { padding: 8px; spacing: 16px; } +.osd-window, .switcher-list { background: rgba(0,0,0,0.8); border: 1px solid rgba(128,128,128,0.40); diff --git a/js/Makefile.am b/js/Makefile.am index 908d515a2..62c8323ad 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -69,6 +69,7 @@ nobase_dist_js_DATA = \ ui/shellEntry.js \ ui/shellMountOperation.js \ ui/notificationDaemon.js \ + ui/osdWindow.js \ ui/overview.js \ ui/overviewControls.js \ ui/panel.js \ diff --git a/js/ui/main.js b/js/ui/main.js index 57b5a7aeb..3c4cba5bb 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -18,6 +18,7 @@ const ExtensionSystem = imports.ui.extensionSystem; const ExtensionDownloader = imports.ui.extensionDownloader; const Keyboard = imports.ui.keyboard; const MessageTray = imports.ui.messageTray; +const OsdWindow = imports.ui.osdWindow; const Overview = imports.ui.overview; const Panel = imports.ui.panel; const Params = imports.misc.params; @@ -51,6 +52,7 @@ let screenShield = null; let notificationDaemon = null; let windowAttentionHandler = null; let ctrlAltTabManager = null; +let osdWindow = null; let sessionMode = null; let shellDBusService = null; let shellMountOpDBusService = null; @@ -137,6 +139,7 @@ function startSession() { xdndHandler = new XdndHandler.XdndHandler(); ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); + osdWindow = new OsdWindow.OsdWindow(); overview = new Overview.Overview(); wm = new WindowManager.WindowManager(); magnifier = new Magnifier.Magnifier(); diff --git a/js/ui/osdWindow.js b/js/ui/osdWindow.js new file mode 100644 index 000000000..fc90ed288 --- /dev/null +++ b/js/ui/osdWindow.js @@ -0,0 +1,170 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Clutter = imports.gi.Clutter; +const St = imports.gi.St; + +const Lang = imports.lang; +const Layout = imports.ui.layout; +const Main = imports.ui.main; +const Mainloop = imports.mainloop; +const Tweener = imports.ui.tweener; + +const HIDE_TIMEOUT = 1500; +const FADE_TIME = 0.1; +const LEVEL_ANIMATION_TIME = 0.1; + +const LevelBar = new Lang.Class({ + Name: 'LevelBar', + + _init: function() { + this._level = 0; + + this.actor = new St.Bin({ style_class: 'level', + x_fill: true, y_fill: true }); + this._bar = new St.DrawingArea(); + this._bar.connect('repaint', Lang.bind(this, this._repaint)); + + this.actor.set_child(this._bar); + }, + + get level() { + return this._level; + }, + + set level(value) { + let newValue = Math.max(0, Math.min(value, 100)); + if (newValue == this._level) + return; + this._level = newValue; + this._bar.queue_repaint(); + }, + + _repaint: function() { + let cr = this._bar.get_context(); + + let node = this.actor.get_theme_node(); + let radius = node.get_border_radius(0); // assume same radius for all corners + Clutter.cairo_set_source_color(cr, node.get_foreground_color()); + + let [w, h] = this._bar.get_surface_size(); + w *= (this._level / 100.); + + if (w == 0) + return; + + cr.moveTo(radius, 0); + if (w >= radius) + cr.arc(w - radius, radius, radius, 1.5 * Math.PI, 2. * Math.PI); + else + cr.lineTo(w, 0); + if (w >= radius) + cr.arc(w - radius, h - radius, radius, 0, 0.5 * Math.PI); + else + cr.lineTo(w, h); + cr.arc(radius, h - radius, radius, 0.5 * Math.PI, Math.PI); + cr.arc(radius, radius, radius, Math.PI, 1.5 * Math.PI); + cr.fill(); + } +}); + +const OsdWindow = new Lang.Class({ + Name: 'OsdWindow', + + _init: function() { + this.actor = new St.Widget({ x_expand: true, + y_expand: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER }); + this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true })); + this._box = new St.BoxLayout({ style_class: 'osd-window', + vertical: true }); + this.actor.add_actor(this._box); + + this._icon = new St.Icon(); + this._box.add(this._icon, { expand: true }); + + this._label = new St.Label(); + this._box.add(this._label); + + this._level = new LevelBar(); + this._box.add(this._level.actor); + + this._hideTimeoutId = 0; + this._reset(); + + Main.layoutManager.connect('monitors-changed', + Lang.bind(this, this._monitorsChanged)); + this._monitorsChanged(); + + Main.layoutManager.addChrome(this.actor, { affectsInputRegion: false }); + }, + + setIcon: function(icon) { + this._icon.gicon = icon; + }, + + setLabel: function(label) { + this._label.visible = (label != undefined); + if (label) + this._label.text = label; + }, + + setLevel: function(level) { + this._level.actor.visible = (level != undefined); + if (this.actor.visible) + Tweener.addTween(this._level, + { level: level, + time: LEVEL_ANIMATION_TIME, + transition: 'easeOutQuad' }); + else + this._level.level = level; + }, + + show: function() { + if (!this._icon.gicon) + return; + + if (!this.actor.visible) { + this.actor.show(); + this.actor.opacity = 0; + + Tweener.addTween(this.actor, + { opacity: 255, + time: FADE_TIME, + transition: 'easeOutQuad' }); + } + + if (this._hideTimeoutId) + Mainloop.source_remove(this._hideTimeoutId); + this._hideTimeoutId = Mainloop.timeout_add(HIDE_TIMEOUT, + Lang.bind(this, this._hide)); + }, + + _hide: function() { + Tweener.addTween(this.actor, + { opacity: 0, + time: FADE_TIME, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, this._reset) }); + }, + + _reset: function() { + this.actor.hide(); + this.setLabel(null); + this.setLevel(null); + }, + + _monitorsChanged: function() { + /* assume 130x130 on a 640x480 display and scale from there */ + let monitor = Main.layoutManager.primaryMonitor; + let scalew = monitor.width / 640.0; + let scaleh = monitor.height / 480.0; + let scale = Math.min(scalew, scaleh); + let size = 130 * Math.max(1, scale); + + this._box.set_size(size, size); + this._box.translation_y = monitor.height / 4; + + this._icon.icon_size = size / 2; + } +});