[gnome-shell/wip/fmuellner/notification-redux+sass: 131/141] calendar: Add support for action area and expanded bodies



commit 5463b20f22fe8f63a7bdb6d12c7e5910ef457d26
Author: Florian Müllner <fmuellner gnome org>
Date:   Tue Feb 17 01:12:44 2015 +0100

    calendar: Add support for action area and expanded bodies
    
    Notifications in the message list cannot be expanded, however we will
    soon use NotificationMessage to re-implement notification banners, where
    we still want actions and expanded content.
    While this functionality logically belongs to the future banner subclass,
    it is cleaner and easier to have the basic support in the base class.
    This also leaves the door open for expanded notifications in the summary,
    should that become a thing again.

 js/ui/calendar.js |  196 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 194 insertions(+), 2 deletions(-)
---
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index 240b692..84a0646 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -4,6 +4,7 @@ const Atk = imports.gi.Atk;
 const Clutter = imports.gi.Clutter;
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
 const Gtk = imports.gi.Gtk;
 const Lang = imports.lang;
 const St = imports.gi.St;
@@ -878,17 +879,127 @@ const ScaleLayout = new Lang.Class({
     }
 });
 
+const LabelExpanderLayout = new Lang.Class({
+    Name: 'LabelExpanderLayout',
+    Extends: Clutter.LayoutManager,
+    Properties: { 'expansion': GObject.ParamSpec.double('expansion',
+                                                        'Expansion',
+                                                        'Expansion of the layout, between 0 (collapsed) ' +
+                                                        'and 1 (fully expanded',
+                                                         GObject.ParamFlags.READABLE | 
GObject.ParamFlags.WRITABLE,
+                                                         0, 1, 0)},
+
+    _init: function(params) {
+        this._expansion = 0;
+        this.expandLines = 6;
+
+        this.parent(params);
+    },
+
+    get expansion() {
+        return this._expansion;
+    },
+
+    set expansion(v) {
+        if (v == this._expansion)
+            return;
+        this._expansion = v;
+        this.notify('expansion');
+
+        let visibleIndex = this._expansion > 0 ? 1 : 0;
+        for (let i = 0; this._container && i < this._container.get_n_children(); i++)
+            this._container.get_child_at_index(i).visible = (i == visibleIndex);
+
+        this.layout_changed();
+    },
+
+    vfunc_set_container: function(container) {
+        this._container = container;
+    },
+
+    vfunc_get_preferred_width: function(container, forHeight) {
+        let [min, nat] = [0, 0];
+
+        for (let i = 0; i < container.get_n_children(); i++) {
+            if (i > 1)
+                break; // we support one unexpanded + one expanded child
+
+            let child = container.get_child_at_index(i);
+            let [childMin, childNat] = child.get_preferred_width(forHeight);
+            [min, nat] = [Math.max(min, childMin), Math.max(nat, childNat)];
+        }
+
+        return [min, nat];
+    },
+
+    vfunc_get_preferred_height: function(container, forWidth) {
+        let [min, nat] = [0, 0];
+
+        let children = container.get_children();
+        if (children[0])
+            [min, nat] = children[0].get_preferred_height(forWidth);
+
+        if (children[1]) {
+            let [min2, nat2] = children[1].get_preferred_height(forWidth);
+            let [expMin, expNat] = [Math.min(min2, min * this.expandLines),
+                                    Math.min(nat2, nat * this.expandLines)];
+            [min, nat] = [min + this._expansion * (expMin - min),
+                          nat + this._expansion * (expNat - nat)];
+        }
+
+        return [min, nat];
+    },
+
+    vfunc_allocate: function(container, box, flags) {
+        for (let i = 0; i < container.get_n_children(); i++) {
+            let child = container.get_child_at_index(i);
+
+            if (!child.visible)
+                continue;
+
+            let xAlign = 0;
+            let xFill = false;
+            if (child.needs_expand(Clutter.Orientation.HORIZONTAL)) {
+                switch(child.get_x_align()) {
+                    case Clutter.ActorAlign.FILL:
+                        xFill = true;
+                        break;
+                    case Clutter.ActorAlign.START:
+                        break;
+                    case Clutter.ActorAlign.CENTER:
+                        xAlign = 0.5;
+                        break;
+                    case Clutter.ActorAlign.END:
+                        xAlign = 1;
+                        break;
+                }
+            }
+            child.allocate_align_fill(box, xAlign, 0, xFill, false, flags);
+        }
+
+    }
+});
+
 const Message = new Lang.Class({
     Name: 'Message',
 
     _init: function(title, body) {
+        this.expanded = false;
+
         this.actor = new St.Button({ style_class: 'message',
                                      accessible_role: Atk.Role.NOTIFICATION,
                                      can_focus: true,
                                      x_expand: true, x_fill: true });
 
+        let vbox = new St.BoxLayout({ vertical: true });
+        this.actor.set_child(vbox);
+
         let hbox = new St.BoxLayout();
-        this.actor.set_child(hbox);
+        vbox.add_actor(hbox);
+
+        this._actionBin = new St.Widget({ layout_manager: new ScaleLayout(),
+                                          visible: false });
+        vbox.add_actor(this._actionBin);
 
         this._iconBin = new St.Bin({ style_class: 'message-icon-bin',
                                      y_expand: true,
@@ -917,9 +1028,14 @@ const Message = new Lang.Class({
         this._closeButton = new St.Button({ child: closeIcon, visible: false });
         titleBox.add_actor(this._closeButton);
 
+        this._bodyStack = new St.Widget({ x_expand: true,
+                                          x_align: Clutter.ActorAlign.START });
+        this._bodyStack.layout_manager = new LabelExpanderLayout();
+        contentBox.add_actor(this._bodyStack);
+
         this.bodyLabel = new URLHighlighter(body, false, this._useBodyMarkup);
         this.bodyLabel.actor.add_style_class_name('message-body');
-        contentBox.add_actor(this.bodyLabel.actor);
+        this._bodyStack.add_actor(this.bodyLabel.actor);
 
         this._closeButton.connect('clicked', Lang.bind(this,
             function() {
@@ -957,6 +1073,82 @@ const Message = new Lang.Class({
             this.setBody(this.bodyLabel.actor.text);
     },
 
+    setActionArea: function(actor) {
+        if (actor == null) {
+            if (this._actionBin.get_n_children() > 0)
+                this._actionBin.get_child_at_index(0).destroy();
+            return;
+        }
+
+        if (this._actionBin.get_n_children() > 0)
+            throw new Error('Message already has an action area');
+
+        this._actionBin.add_actor(actor);
+        this._actionBin.visible = this.expanded;
+    },
+
+    setExpandedBody: function(actor) {
+        if (actor == null) {
+            if (this._bodyStack.get_n_children() > 1)
+                this._bodyStack.get_child_at_index(1).destroy();
+            return;
+        }
+
+        if (this._bodyStack.get_n_children() > 1)
+            throw new Error('Message already has an expanded body actor');
+
+        this._bodyStack.insert_child_at_index(actor, 1);
+    },
+
+    expand: function(animate) {
+        this.expanded = true;
+
+        this._actionBin.visible = (this._actionBin.get_n_children() > 0);
+
+        if (this._bodyStack.get_n_children() < 2) {
+            let expandedLabel = new URLHighlighter(this.bodyLabel.actor.text,
+                                                   true, this._useBodyMarkup);
+            this.setExpandedBody(expandedLabel.actor);
+        }
+
+        if (animate) {
+            Tweener.addTween(this._bodyStack.layout_manager,
+                             { expansion: 1,
+                               time: MessageTray.ANIMATION_TIME,
+                               transition: 'easeOutQuad' });
+            this._actionBin.scale_y = 0;
+            Tweener.addTween(this._actionBin,
+                             { scale_y: 1,
+                               time: MessageTray.ANIMATION_TIME,
+                               transition: 'easeOutQuad' });
+        } else {
+            this._bodyStack.layout_manager.expansion = 1;
+            this._actionBin.scale_y = 1;
+        }
+    },
+
+    unexpand: function(animate) {
+        if (animate) {
+            Tweener.addTween(this._bodyStack.layout_manager,
+                             { expansion: 0,
+                               time: MessageTray.ANIMATION_TIME,
+                               transition: 'easeOutQuad' });
+            Tweener.addTween(this._actionBin,
+                             { scale_y: 0,
+                               time: MessageTray.ANIMATION_TIME,
+                               transition: 'easeOutQuad',
+                               onCompleteScope: this,
+                               onComplete: function() {
+                                   this._actionBin.hide();
+                                   this.expanded = false;
+                               }});
+        } else {
+            this._bodyStack.layout_manager.expansion = 0;
+            this._actionBin.scale_y = 0;
+            this.expanded = false;
+        }
+    },
+
     canClear: function() {
         return true;
     },


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