[gnome-shell/wip/new-notifications: 1/8] messageTray: Add a new message tray indicator



commit f583fc2df630c342e353f6817f86f01a3bd62031
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Wed Dec 4 21:09:23 2013 -0500

    messageTray: Add a new message tray indicator

 data/theme/gnome-shell.css |   22 ++++++
 js/ui/layout.js            |  165 +++++++++++++++++++++-----------------------
 js/ui/messageTray.js       |  125 +++++++++++++++++++++++++++++++++
 js/ui/overview.js          |    2 -
 js/ui/overviewControls.js  |   84 ----------------------
 5 files changed, 226 insertions(+), 172 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index d3e8a75..fa00cbb 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1450,6 +1450,28 @@ StScrollBar StButton#vhandle:active {
     color: #999999;
 }
 
+.message-tray-indicator {
+    spacing: 4px;
+}
+
+.message-tray-indicator-count {
+    font-size: 1.2em;
+    font-weight: bold;
+
+    color: black;
+    background-color: rgba(255, 255, 255, 0.7);
+    border-radius: 1em;
+    padding: 1em;
+    text-align: center;
+}
+
+.message-tray-indicator-glow {
+    height: 4px;
+    background-gradient-start: rgba(255, 255, 255, 0);
+    background-gradient-end: rgba(255, 255, 255, 1);
+    background-gradient-direction: vertical;
+}
+
 .notification {
     border-radius: 10px 10px 0px 0px;
     background: rgba(0,0,0,0.9);
diff --git a/js/ui/layout.js b/js/ui/layout.js
index c74033b..9e7749d 100644
--- a/js/ui/layout.js
+++ b/js/ui/layout.js
@@ -23,11 +23,6 @@ const KEYBOARD_ANIMATION_TIME = 0.15;
 const BACKGROUND_FADE_ANIMATION_TIME = 1.0;
 const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
 
-// The message tray takes this much pressure
-// in the pressure barrier at once to release it.
-const MESSAGE_TRAY_PRESSURE_THRESHOLD = 250; // pixels
-const MESSAGE_TRAY_PRESSURE_TIMEOUT = 1000; // ms
-
 const HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels
 const HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms
 
@@ -151,7 +146,6 @@ const LayoutManager = new Lang.Class({
 
         this._keyboardIndex = -1;
         this._rightPanelBarrier = null;
-        this._trayBarrier = null;
 
         this._inOverview = false;
         this._updateRegionIdle = 0;
@@ -211,7 +205,6 @@ const LayoutManager = new Lang.Class({
         this.trayBox = new St.Widget({ name: 'trayBox',
                                        layout_manager: new Clutter.BinLayout() }); 
         this.addChrome(this.trayBox);
-        this._setupTrayPressure();
 
         this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox',
                                               reactive: true,
@@ -441,50 +434,9 @@ const LayoutManager = new Lang.Class({
         }
     },
 
-    _setupTrayPressure: function() {
-        this._trayPressure = new PressureBarrier(MESSAGE_TRAY_PRESSURE_THRESHOLD,
-                                                 MESSAGE_TRAY_PRESSURE_TIMEOUT,
-                                                 Shell.KeyBindingMode.NORMAL |
-                                                 Shell.KeyBindingMode.OVERVIEW);
-        this._trayPressure.setEventFilter(this._trayBarrierEventFilter);
-        this._trayPressure.connect('trigger', function(barrier) {
-            if (Main.layoutManager.bottomMonitor.inFullscreen)
-                return;
-
-            Main.messageTray.openTray();
-        });
-    },
-
-    _updateTrayBarrier: function() {
-        let monitor = this.bottomMonitor;
-
-        if (this._trayBarrier) {
-            this._trayPressure.removeBarrier(this._trayBarrier);
-            this._trayBarrier.destroy();
-            this._trayBarrier = null;
-        }
-
-        this._trayBarrier = new Meta.Barrier({ display: global.display,
-                                               x1: monitor.x, x2: monitor.x + monitor.width,
-                                               y1: monitor.y + monitor.height, y2: monitor.y + 
monitor.height,
-                                               directions: Meta.BarrierDirection.NEGATIVE_Y });
-        this._trayPressure.addBarrier(this._trayBarrier);
-    },
-
-    _trayBarrierEventFilter: function(event) {
-        // Throw out all events where the pointer was grabbed by another
-        // client, as the client that grabbed the pointer expects to have
-        // complete control over it
-        if (event.grabbed && Main.modalCount == 0)
-            return true;
-
-        return false;
-    },
-
     _monitorsChanged: function() {
         this._updateMonitors();
         this._updateBoxes();
-        this._updateTrayBarrier();
         this._updateHotCorners();
         this._updateBackgrounds();
         this._updateFullscreen();
@@ -1066,10 +1018,10 @@ const HotCorner = new Lang.Class({
 
         this._setupFallbackCornerIfNeeded(layoutManager);
 
-        this._pressureBarrier = new PressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD,
-                                                    HOT_CORNER_PRESSURE_TIMEOUT,
-                                                    Shell.KeyBindingMode.NORMAL |
-                                                    Shell.KeyBindingMode.OVERVIEW);
+        this._pressureBarrier = new TriggerablePressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD,
+                                                               HOT_CORNER_PRESSURE_TIMEOUT,
+                                                               Shell.KeyBindingMode.NORMAL |
+                                                               Shell.KeyBindingMode.OVERVIEW);
         this._pressureBarrier.connect('trigger', Lang.bind(this, this._toggleOverview));
 
         // Cache the three ripples instead of dynamically creating and destroying them.
@@ -1247,14 +1199,12 @@ const PressureBarrier = new Lang.Class({
     Name: 'PressureBarrier',
 
     _init: function(threshold, timeout, keybindingMode) {
-        this._threshold = threshold;
-        this._timeout = timeout;
+        this.threshold = threshold;
+        this.timeout = timeout;
         this._keybindingMode = keybindingMode;
         this._barriers = [];
         this._eventFilter = null;
-
-        this._isTriggered = false;
-        this._reset();
+        this.reset();
     },
 
     addBarrier: function(barrier) {
@@ -1283,10 +1233,10 @@ const PressureBarrier = new Lang.Class({
         this._eventFilter = filter;
     },
 
-    _reset: function() {
+    reset: function() {
         this._barrierEvents = [];
-        this._currentPressure = 0;
         this._lastTime = 0;
+        this.currentPressure = 0;
     },
 
     _isHorizontal: function(barrier) {
@@ -1307,12 +1257,21 @@ const PressureBarrier = new Lang.Class({
             return Math.abs(event.dy);
     },
 
+    get currentPressure() {
+        return this._currentPressure;
+    },
+
+    set currentPressure(value) {
+        this._currentPressure = value;
+        this.emit('pressure-changed');
+    },
+
     _trimBarrierEvents: function() {
         // Events are guaranteed to be sorted in time order from
         // oldest to newest, so just look for the first old event,
         // and then chop events after that off.
         let i = 0;
-        let threshold = this._lastTime - this._timeout;
+        let threshold = this._lastTime - this.timeout;
 
         while (i < this._barrierEvents.length) {
             let [time, distance] = this._barrierEvents[i];
@@ -1325,49 +1284,38 @@ const PressureBarrier = new Lang.Class({
 
         for (i = 0; i < firstNewEvent; i++) {
             let [time, distance] = this._barrierEvents[i];
-            this._currentPressure -= distance;
+            this.currentPressure = distance;
         }
 
         this._barrierEvents = this._barrierEvents.slice(firstNewEvent);
     },
 
     _onBarrierLeft: function(barrier, event) {
-        this._reset();
-        this._isTriggered = false;
-    },
-
-    _trigger: function() {
-        this._isTriggered = true;
-        this.emit('trigger');
-        this._reset();
+        this.reset();
     },
 
-    _onBarrierHit: function(barrier, event) {
-        // If we've triggered the barrier, wait until the pointer has the
-        // left the barrier hitbox until we trigger it again.
-        if (this._isTriggered)
-            return;
-
+    _shouldUseEvent: function(barrier, event) {
         if (this._eventFilter && this._eventFilter(event))
-            return;
+            return false;
 
         // Throw out all events not in the proper keybinding mode
         if (!(this._keybindingMode & Main.keybindingMode))
-            return;
+            return false;
 
         let slide = this._getDistanceAlongBarrier(barrier, event);
         let distance = this._getDistanceAcrossBarrier(barrier, event);
 
-        if (distance >= this._threshold) {
-            this._trigger();
-            return;
-        }
-
         // Throw out events where the cursor is move more
         // along the axis of the barrier than moving with
         // the barrier.
         if (slide > distance)
-            return;
+            return false;
+
+        return true;
+    },
+
+    _appendEvent: function(barrier, event) {
+        let distance = this._getDistanceAcrossBarrier(barrier, event);
 
         this._lastTime = event.time;
 
@@ -1375,10 +1323,55 @@ const PressureBarrier = new Lang.Class({
         distance = Math.min(15, distance);
 
         this._barrierEvents.push([event.time, distance]);
-        this._currentPressure += distance;
+        this.currentPressure += distance;
+    },
 
-        if (this._currentPressure >= this._threshold)
-            this._trigger();
+    _onBarrierHit: function(barrier, event) {
+        if (!this._shouldUseEvent(barrier, event))
+            return;
+
+        this._appendEvent(barrier, event);
     }
 });
 Signals.addSignalMethods(PressureBarrier.prototype);
+
+const TriggerablePressureBarrier = new Lang.Class({
+    Name: 'TriggerablePressureBarrier',
+    Extends: PressureBarrier,
+
+    _init: function(threshold, timeout, keybindingMode) {
+        this.parent(threshold, timeout, keybindingMode);
+        this._isTriggered = false;
+    },
+
+    _trigger: function() {
+        this._isTriggered = true;
+        this.emit('trigger');
+        this.reset();
+    },
+
+    _onBarrierLeft: function() {
+        this.parent();
+        this._isTriggered = false;
+    },
+
+    _onBarrierHit: function(barrier, event) {
+        // If we've triggered the barrier, wait until the pointer has the
+        // left the barrier hitbox until we trigger it again.
+        if (this._isTriggered)
+            return;
+
+        if (!this._shouldUseEvent(barrier, event))
+            return;
+
+        if (distance >= this.threshold) {
+            this._trigger();
+            return;
+        }
+
+        this._appendEvent(barrier, event);
+
+        if (this.currentPressure >= this.threshold)
+            this._trigger();
+    },
+});
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index dc630b3..9f6eba3 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -20,6 +20,7 @@ const CtrlAltTab = imports.ui.ctrlAltTab;
 const GnomeSession = imports.misc.gnomeSession;
 const GrabHelper = imports.ui.grabHelper;
 const Hash = imports.misc.hash;
+const Layout = imports.ui.layout;
 const Lightbox = imports.ui.lightbox;
 const Main = imports.ui.main;
 const PointerWatcher = imports.ui.pointerWatcher;
@@ -49,6 +50,9 @@ const TRAY_DWELL_CHECK_INTERVAL = 100; // ms
 
 const IDLE_TIME = 1000;
 
+const MESSAGE_TRAY_PRESSURE_THRESHOLD = 250; // pixels
+const MESSAGE_TRAY_PRESSURE_TIMEOUT = 1000; // ms
+
 const State = {
     HIDDEN:  0,
     SHOWING: 1,
@@ -1723,6 +1727,118 @@ const MessageTrayMenuButton = new Lang.Class({
     },
 });
 
+const MessageTrayIndicator = new Lang.Class({
+    Name: 'MessageTrayIndicator',
+
+    _init: function(tray) {
+        this._tray = tray;
+
+        this.actor = new St.BoxLayout({ style_class: 'message-tray-indicator',
+                                        reactive: true,
+                                        track_hover: true,
+                                        vertical: true,
+                                        x_expand: true,
+                                        y_expand: true,
+                                        y_align: Clutter.ActorAlign.START });
+        this.actor.connect('notify::height', Lang.bind(this, function() {
+            this.actor.translation_y = -this.actor.height;
+        }));
+        this.actor.connect('button-press-event', Lang.bind(this, function() {
+            this._tray.openTray();
+            this._pressureBarrier.reset();
+        }));
+
+        this._count = new St.Label({ style_class: 'message-tray-indicator-count',
+                                     x_expand: true,
+                                     x_align: Clutter.ActorAlign.CENTER });
+        this.actor.add_child(this._count);
+
+        this._tray.connect('indicator-count-updated', Lang.bind(this, this._syncCount));
+        this._syncCount();
+
+        this._glow = new St.Widget({ style_class: 'message-tray-indicator-glow',
+                                     x_expand: true });
+        this.actor.add_child(this._glow);
+
+        this._pressureBarrier = new Layout.PressureBarrier(MESSAGE_TRAY_PRESSURE_THRESHOLD,
+                                                           MESSAGE_TRAY_PRESSURE_TIMEOUT,
+                                                           Shell.KeyBindingMode.NORMAL |
+                                                           Shell.KeyBindingMode.OVERVIEW);
+        this._pressureBarrier.setEventFilter(this._barrierEventFilter);
+        Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateBarrier));
+        this._updateBarrier();
+
+        this._pressureBarrier.connect('pressure-changed', Lang.bind(this, this._updatePressure));
+        this._pressureValue = 0;
+        this._syncGlow();
+    },
+
+    _updateBarrier: function() {
+        let monitor = Main.layoutManager.bottomMonitor;
+
+        if (this._barrier) {
+            this._pressureBarrier.removeBarrier(this._trayBarrier);
+            this._barrier.destroy();
+            this._barrier = null;
+        }
+
+        this._barrier = new Meta.Barrier({ display: global.display,
+                                           x1: monitor.x, x2: monitor.x + monitor.width,
+                                           y1: monitor.y + monitor.height, y2: monitor.y + monitor.height,
+                                           directions: Meta.BarrierDirection.NEGATIVE_Y });
+        this._pressureBarrier.addBarrier(this._barrier);
+    },
+
+    _trayBarrierEventFilter: function(event) {
+        // Throw out all events where the pointer was grabbed by another
+        // client, as the client that grabbed the pointer expects to have
+        // complete control over it
+        if (event.grabbed && Main.modalCount == 0)
+            return true;
+
+        if (this._tray.hasVisibleNotification())
+            return true;
+
+        return false;
+    },
+
+    _syncCount: function() {
+        let count = this._tray.indicatorCount;
+        this._count.visible = (count > 0);
+        this._count.text = '' + count;
+    },
+
+    _syncGlow: function() {
+        let value = this._pressureValue;
+        let percent = value / this._pressureBarrier.threshold;
+        this.actor.opacity = Math.min(percent * 255, 255);
+        this.actor.visible = (value > 0);
+    },
+
+    get pressureValue() {
+        return this._pressureValue;
+    },
+
+    set pressureValue(value) {
+        this._pressureValue = value;
+        this._syncGlow();
+   },
+
+    _updatePressure: function() {
+        let value = this._pressureBarrier.currentPressure;
+        this.pressureValue = value;
+        if (value > 0) {
+            Tweener.removeTweens(this);
+            Tweener.addTween(this, { time: this._pressureBarrier.timeout / 1000,
+                                     pressureValue: 0 });
+        }
+    },
+
+    destroy: function() {
+        this.actor.destroy();
+    },
+});
+
 const MessageTray = new Lang.Class({
     Name: 'MessageTray',
 
@@ -1900,6 +2016,11 @@ const MessageTray = new Lang.Class({
 
         this._messageTrayMenuButton = new MessageTrayMenuButton(this);
         this.actor.add_actor(this._messageTrayMenuButton.actor);
+
+        this._indicator = new MessageTrayIndicator(this);
+        Main.layoutManager.trayBox.add_child(this._indicator.actor);
+        Main.layoutManager.trackChrome(this._indicator.actor);
+        this._grabHelper.addActor(this._indicator.actor);
     },
 
     close: function() {
@@ -2351,6 +2472,10 @@ const MessageTray = new Lang.Class({
         this._updateState();
     },
 
+    hasVisibleNotification: function() {
+        return this._notificationState != State.HIDDEN;
+    },
+
     // All of the logic for what happens when occurs here; the various
     // event handlers merely update variables such as
     // 'this._pointerInNotification', 'this._traySummoned', etc, and
diff --git a/js/ui/overview.js b/js/ui/overview.js
index 1809fe4..a8bcd48 100644
--- a/js/ui/overview.js
+++ b/js/ui/overview.js
@@ -269,8 +269,6 @@ const Overview = new Lang.Class({
         this._overview.add(this._controls.actor, { y_fill: true, expand: true });
         this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
 
-        this._stack.add_actor(this._controls.indicatorActor);
-
         // TODO - recalculate everything when desktop size changes
         this.dashIconSize = this._dash.iconSize;
         this._dash.connect('icon-size-changed',
diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js
index de6142d..0f12e8b 100644
--- a/js/ui/overviewControls.js
+++ b/js/ui/overviewControls.js
@@ -391,87 +391,6 @@ const DashSpacer = new Lang.Class({
     }
 });
 
-const MessagesIndicator = new Lang.Class({
-    Name: 'MessagesIndicator',
-
-    _init: function(viewSelector) {
-        this._count = 0;
-        this._sources = [];
-        this._viewSelector = viewSelector;
-
-        this._container = new St.BoxLayout({ style_class: 'messages-indicator-contents',
-                                             reactive: true,
-                                             track_hover: true,
-                                             x_expand: true,
-                                             y_expand: true,
-                                             x_align: Clutter.ActorAlign.CENTER });
-
-        this._icon = new St.Icon({ icon_name: 'user-idle-symbolic',
-                                   icon_size: 16 });
-        this._container.add_actor(this._icon);
-
-        this._label = new St.Label();
-        this._container.add_actor(this._label);
-
-        this._highlight = new St.Widget({ style_class: 'messages-indicator-highlight',
-                                          x_expand: true,
-                                          y_expand: true,
-                                          y_align: Clutter.ActorAlign.END,
-                                          visible: false });
-
-        this._container.connect('notify::hover', Lang.bind(this,
-            function() {
-                this._highlight.visible = this._container.hover;
-            }));
-
-        let clickAction = new Clutter.ClickAction();
-        this._container.add_action(clickAction);
-        clickAction.connect('clicked', Lang.bind(this,
-            function() {
-                Main.messageTray.openTray();
-            }));
-
-        Main.messageTray.connect('showing', Lang.bind(this,
-            function() {
-                this._highlight.visible = false;
-                this._container.hover = false;
-            }));
-
-        let layout = new Clutter.BinLayout();
-        this.actor = new St.Widget({ layout_manager: layout,
-                                     style_class: 'messages-indicator',
-                                     y_expand: true,
-                                     y_align: Clutter.ActorAlign.END,
-                                     visible: false });
-        this.actor.add_actor(this._container);
-        this.actor.add_actor(this._highlight);
-
-        Main.messageTray.connect('indicator-count-updated', Lang.bind(this, this._sync));
-        this._sync();
-
-        this._viewSelector.connect('page-changed', Lang.bind(this, this._updateVisibility));
-        Main.overview.connect('showing', Lang.bind(this, this._updateVisibility));
-    },
-
-    _sync: function() {
-        let count = Main.messageTray.indicatorCount;
-        this._count = count;
-        this._label.text = ngettext("%d new message",
-                                    "%d new messages",
-                                   count).format(count);
-
-        this._icon.visible = Main.messageTray.hasChatSources;
-        this._updateVisibility();
-    },
-
-    _updateVisibility: function() {
-        let activePage = this._viewSelector.getActivePage();
-        let visible = ((this._count > 0) && (activePage == ViewSelector.ViewPage.WINDOWS));
-
-        this.actor.visible = visible;
-    }
-});
-
 const ControlsLayout = new Lang.Class({
     Name: 'ControlsLayout',
     Extends: Clutter.BinLayout,
@@ -500,9 +419,6 @@ const ControlsManager = new Lang.Class({
         this.viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
         this.viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
 
-        this._indicator = new MessagesIndicator(this.viewSelector);
-        this.indicatorActor = this._indicator.actor;
-
         let layout = new ControlsLayout();
         this.actor = new St.Widget({ layout_manager: layout,
                                      reactive: true,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]