[gnome-shell/wip/fmuellner/notification-redux+sass: 100/141] calendar: Add MessageList and Section/Message base types
- From: Florian Müllner <fmuellner src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/fmuellner/notification-redux+sass: 100/141] calendar: Add MessageList and Section/Message base types
- Date: Fri, 20 Feb 2015 13:05:22 +0000 (UTC)
commit b5e8f25d8057583be07077ca06487dd0711a9f2d
Author: Florian Müllner <fmuellner gnome org>
Date: Fri Dec 5 16:24:35 2014 +0100
calendar: Add MessageList and Section/Message base types
The message list is a scrollable list that will hold sections of
different types of time-related messages like notifications,
calendar events or birthday reminders. When no section displays
any content for the selected date, a placeholder is shown instead.
https://bugzilla.gnome.org/show_bug.cgi?id=744817
data/gnome-shell-theme.gresource.xml | 1 +
data/theme/_common.scss | 82 +++++++-
data/theme/gnome-shell.css | 72 ++++++-
data/theme/no-events.svg | 119 ++++++++++
js/ui/calendar.js | 421 +++++++++++++++++++++++++++++++++-
5 files changed, 687 insertions(+), 8 deletions(-)
---
diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml
index 067d324..9e270d0 100644
--- a/data/gnome-shell-theme.gresource.xml
+++ b/data/gnome-shell-theme.gresource.xml
@@ -19,6 +19,7 @@
<file>gnome-shell-high-contrast.css</file>
<file>logged-in-indicator.svg</file>
<file>more-results.svg</file>
+ <file>no-events.svg</file>
<file>noise-texture.png</file>
<file>page-indicator-active.svg</file>
<file>page-indicator-inactive.svg</file>
diff --git a/data/theme/_common.scss b/data/theme/_common.scss
index ae8b208..9575e67 100644
--- a/data/theme/_common.scss
+++ b/data/theme/_common.scss
@@ -702,7 +702,8 @@ StScrollBar {
}
.calendar,
- .datemenu-today-button {
+ .datemenu-today-button,
+ .message-list-sections {
margin: 0 1.5em;
}
@@ -711,12 +712,22 @@ StScrollBar {
padding-bottom: 3em;
}
- .datemenu-today-button {
+ .datemenu-today-button,
+ .message-list-section-title {
border-radius: 4px;
padding: .4em;
}
- .datemenu-today-button {
+ .message-list-section-list:ltr {
+ padding-left: .4em;
+ }
+
+ .message-list-section-list:rtl {
+ padding-right: .4em;
+ }
+
+ .datemenu-today-button,
+ .message-list-section-title {
&:hover,&:focus { background-color: lighten($bg_color,5%); }
&:active {
color: lighten($selected_fg_color,5%);
@@ -731,6 +742,11 @@ StScrollBar {
font-size: 1.5em;
}
+ .message-list-section-title {
+ color: darken($fg_color,40%);
+ font-weight: bold;
+ }
+
.calendar-month-label {
color: darken($fg_color,5%);
font-weight: bold;
@@ -802,6 +818,66 @@ StScrollBar {
opacity: 0.5;
}
+ /* Message list */
+ .message-list {
+ width: 340px;
+ }
+
+ .message-list-sections {
+ spacing: 1.5em;
+ }
+
+ .message-list-section,
+ .message-list-section-list {
+ spacing: 0.7em;
+ }
+
+ .message-list-section-title-box {
+ spacing: 0.4em;
+ }
+
+ .message-list-section-close > StIcon {
+ icon-size: 16px;
+ border-radius: 8px;
+ color: $bg_color;
+ background-color: darken($fg_color,60%);
+ }
+
+ /* FIXME: how do you do this in sass? */
+ .message-list-section-close:hover > StIcon,
+ .message-list-section-close:focus > StIcon {
+ background-color: darken($fg_color,40%);
+ }
+
+ .message {
+ background-color: lighten($bg_color,5%);
+ &:hover,&:focus { background-color: lighten($bg_color,15%); }
+ }
+
+ .message-icon-bin {
+ padding: 5px;
+ }
+
+ .message-icon-bin > StIcon {
+ icon-size: 48px;
+ }
+
+ .message-secondary-bin {
+ color: darken($fg_color,40%);
+ }
+
+ .message-secondary-bin > StIcon {
+ icon-size: 16px;
+ }
+
+ .message-title {
+ font-weight: bold;
+ }
+ .message-content {
+ padding: 5px;
+ spacing: 5px;
+ }
+
.events-table { //right hand side
width: 15em;
spacing-columns: 1em;
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 45d73b2..94160f7 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -592,26 +592,41 @@ StScrollBar {
margin-bottom: 1em; }
.calendar,
-.datemenu-today-button {
+.datemenu-today-button,
+.message-list-sections {
margin: 0 1.5em; }
.datemenu-calendar-column {
spacing: 0.5em;
padding-bottom: 3em; }
-.datemenu-today-button {
+.datemenu-today-button,
+.message-list-section-title {
border-radius: 4px;
padding: .4em; }
-.datemenu-today-button:hover, .datemenu-today-button:focus {
+.message-list-section-list:ltr {
+ padding-left: .4em; }
+
+.message-list-section-list:rtl {
+ padding-right: .4em; }
+
+.datemenu-today-button:hover, .datemenu-today-button:focus,
+.message-list-section-title:hover,
+.message-list-section-title:focus {
background-color: #454c4c; }
-.datemenu-today-button:active {
+.datemenu-today-button:active,
+.message-list-section-title:active {
color: white;
background-color: #215d9c; }
.datemenu-today-button .date-label {
font-size: 1.5em; }
+.message-list-section-title {
+ color: #8e8e80;
+ font-weight: bold; }
+
.calendar-month-label {
color: #e2e2df;
font-weight: bold;
@@ -681,6 +696,55 @@ StScrollBar {
color: rgba(238, 238, 236, 0.15);
opacity: 0.5; }
+/* Message list */
+.message-list {
+ width: 340px; }
+
+.message-list-sections {
+ spacing: 1.5em; }
+
+.message-list-section,
+.message-list-section-list {
+ spacing: 0.7em; }
+
+.message-list-section-title-box {
+ spacing: 0.4em; }
+
+.message-list-section-close > StIcon {
+ icon-size: 16px;
+ border-radius: 8px;
+ color: #393f3f;
+ background-color: #59594f; }
+
+/* FIXME: how do you do this in sass? */
+.message-list-section-close:hover > StIcon,
+.message-list-section-close:focus > StIcon {
+ background-color: #8e8e80; }
+
+.message {
+ background-color: #454c4c; }
+ .message:hover, .message:focus {
+ background-color: #5d6767; }
+
+.message-icon-bin {
+ padding: 5px; }
+
+.message-icon-bin > StIcon {
+ icon-size: 48px; }
+
+.message-secondary-bin {
+ color: #8e8e80; }
+
+.message-secondary-bin > StIcon {
+ icon-size: 16px; }
+
+.message-title {
+ font-weight: bold; }
+
+.message-content {
+ padding: 5px;
+ spacing: 5px; }
+
.events-table {
width: 15em;
spacing-columns: 1em;
diff --git a/data/theme/no-events.svg b/data/theme/no-events.svg
new file mode 100644
index 0000000..8ab08a9
--- /dev/null
+++ b/data/theme/no-events.svg
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="64px"
+ height="64px"
+ id="svg3471"
+ version="1.1"
+ inkscape:version="0.48.5 r10040"
+ sodipodi:docname="New document 5">
+ <defs
+ id="defs3473" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.5"
+ inkscape:cx="32"
+ inkscape:cy="32"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:document-units="px"
+ inkscape:grid-bbox="true"
+ inkscape:window-width="1461"
+ inkscape:window-height="772"
+ inkscape:window-x="37"
+ inkscape:window-y="64"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata3476">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <g
+ transform="matrix(4,0,0,4,1.9999997,2.3636364)"
+ id="g19145"
+ style="fill:#bebebe;fill-opacity:1;display:inline">
+ <g
+ id="g19147"
+ inkscape:label="status"
+ style="fill:#bebebe;fill-opacity:1;display:inline"
+ transform="translate(-541.0002,-301)" />
+ <g
+ style="fill:#bebebe;fill-opacity:1"
+ id="g19149"
+ inkscape:label="devices"
+ transform="translate(-541.0002,-301)" />
+ <g
+ style="fill:#bebebe;fill-opacity:1"
+ id="g19151"
+ inkscape:label="apps"
+ transform="translate(-541.0002,-301)" />
+ <g
+ style="fill:#bebebe;fill-opacity:1"
+ id="g19153"
+ inkscape:label="places"
+ transform="translate(-541.0002,-301)" />
+ <g
+ style="fill:#bebebe;fill-opacity:1"
+ id="g19155"
+ inkscape:label="mimetypes"
+ transform="translate(-541.0002,-301)">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 543.0002,301 c -1.05237,0 -2,0.84508 -2,1.9375 l 0,11.125 c 0,1.09242 0.94763,1.9375
2,1.9375 l 11,0 c 1.05237,0 2,-0.84508 2,-1.9375 l 0,-11.125 c 0,-1.09242 -0.94763,-1.9375 -2,-1.9375 l -11,0
z m 0,5 3.03125,0 0,2 -3.03125,0 0,-2 z m 4.03125,0 2.96875,0 0,2 -2.96875,0 0,-2 z m 3.96875,0 3,0 0,2 -3,0
0,-2 z m -8,3 3.03125,0 0,2 -3.03125,0 0,-2 z m 4.03125,0 2.96875,0 0,2 -2.96875,0 0,-2 z m 3.96875,0 3,0 0,2
-3,0 0,-2 z m -8,3 3.03125,0 0,2 -3.03125,0 0,-2 z m 4.03125,0 2.96875,0 0,2 -2.96875,0 0,-2 z m 3.96875,0
3,0 0,2 -3,0 0,-2 z"
+ id="path19157"
+
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Sans;-inkscape-font-specification:Sans"
/>
+ <rect
+ height="1.9999993"
+ id="rect19159"
+
style="opacity:0.35;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ width="2.9999993"
+ x="551.00018"
+ y="309" />
+ </g>
+ <g
+ id="g19161"
+ inkscape:label="emblems"
+ style="fill:#bebebe;fill-opacity:1;display:inline"
+ transform="translate(-541.0002,-301)" />
+ <g
+ id="g19163"
+ inkscape:label="emotes"
+ style="fill:#bebebe;fill-opacity:1;display:inline"
+ transform="translate(-541.0002,-301)" />
+ <g
+ id="g19165"
+ inkscape:label="categories"
+ style="fill:#bebebe;fill-opacity:1;display:inline"
+ transform="translate(-541.0002,-301)" />
+ <g
+ id="g19167"
+ inkscape:label="actions"
+ style="fill:#bebebe;fill-opacity:1;display:inline"
+ transform="translate(-541.0002,-301)" />
+ </g>
+ </g>
+</svg>
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index a09350a..d4046cf 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -1,8 +1,10 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+const Atk = imports.gi.Atk;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const St = imports.gi.St;
const Signals = imports.signals;
@@ -12,12 +14,16 @@ const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
const Util = imports.misc.util;
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
const SHOW_WEEKDATE_KEY = 'show-weekdate';
const ELLIPSIS_CHAR = '\u2026';
+const MESSAGE_ANIMATION_TIME = 0.1;
+
// alias to prevent xgettext from picking up strings translated in GTK+
const gtk30_ = Gettext_gtk30.gettext;
const NC_ = function(context, str) { return str; };
@@ -852,9 +858,324 @@ const Calendar = new Lang.Class({
}));
}
});
-
Signals.addSignalMethods(Calendar.prototype);
+const ScaleLayout = new Lang.Class({
+ Name: 'ScaleLayout',
+ Extends: Clutter.BinLayout,
+
+ _connectContainer: function(container) {
+ if (this._container == container)
+ return;
+
+ if (this._container)
+ for (let id of this._signals)
+ this._container.disconnect(id);
+
+ this._container = container;
+ this._signals = [];
+
+ if (this._container)
+ for (let signal of ['notify::scale-x', 'notify::scale-y']) {
+ let id = this._container.connect(signal, Lang.bind(this,
+ function() {
+ this.layout_changed();
+ }));
+ this._signals.push(id);
+ }
+ },
+
+ vfunc_get_preferred_width: function(container, forHeight) {
+ this._connectContainer(container);
+
+ let [min, nat] = this.parent(container, forHeight);
+ return [Math.floor(min * container.scale_x),
+ Math.floor(nat * container.scale_x)];
+ },
+
+ vfunc_get_preferred_height: function(container, forWidth) {
+ this._connectContainer(container);
+
+ let [min, nat] = this.parent(container, forWidth);
+ return [Math.floor(min * container.scale_y),
+ Math.floor(nat * container.scale_y)];
+ }
+});
+
+const Message = new Lang.Class({
+ Name: 'Message',
+
+ _init: function(title, body) {
+ this.actor = new St.Button({ style_class: 'message',
+ accessible_role: Atk.Role.NOTIFICATION,
+ can_focus: true,
+ x_expand: true, x_fill: true });
+
+ let hbox = new St.BoxLayout();
+ this.actor.set_child(hbox);
+
+ this._iconBin = new St.Bin({ style_class: 'message-icon-bin',
+ y_expand: true,
+ visible: false });
+ this._iconBin.set_y_align(Clutter.ActorAlign.START);
+ hbox.add_actor(this._iconBin);
+
+ let contentBox = new St.BoxLayout({ style_class: 'message-content',
+ vertical: true, x_expand: true });
+ hbox.add_actor(contentBox);
+
+ let titleBox = new St.BoxLayout();
+ contentBox.add_actor(titleBox);
+
+ this.titleLabel = new St.Label({ style_class: 'message-title',
+ x_expand: true,
+ x_align: Clutter.ActorAlign.START });
+ this.setTitle(title);
+ titleBox.add_actor(this.titleLabel);
+
+ this._secondaryBin = new St.Bin({ style_class: 'message-secondary-bin' });
+ titleBox.add_actor(this._secondaryBin);
+
+ let closeIcon = new St.Icon({ icon_name: 'window-close-symbolic',
+ icon_size: 16 });
+ this._closeButton = new St.Button({ child: closeIcon, visible: false });
+ titleBox.add_actor(this._closeButton);
+
+ this.bodyLabel = new URLHighlighter(body, false, this._useBodyMarkup);
+ this.bodyLabel.actor.add_style_class_name('message-body');
+ contentBox.add_actor(this.bodyLabel.actor);
+
+ this._closeButton.connect('clicked', Lang.bind(this,
+ function() {
+ this.emit('close');
+ }));
+ this.actor.connect('notify::hover', Lang.bind(this, this._sync));
+ this.actor.connect('clicked', Lang.bind(this, this._onClicked));
+ this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+ this._sync();
+ },
+
+ setIcon: function(actor) {
+ this._iconBin.child = actor;
+ this._iconBin.visible = (actor != null);
+ },
+
+ setSecondaryActor: function(actor) {
+ this._secondaryBin.child = actor;
+ },
+
+ setTitle: function(text) {
+ let title = text ? _fixMarkup(text.replace(/\n/g, ' '), false) : '';
+ this.titleLabel.text = title;
+ },
+
+ setBody: function(text) {
+ this.bodyLabel.setMarkup(text, this._useBodyMarkup);
+ },
+
+ setUseBodyMarkup: function(enable) {
+ if (this._useBodyMarkup === enable)
+ return;
+ this._useBodyMarkup = enable;
+ if (this.bodyLabel)
+ this.setBody(this.bodyLabel.actor.text);
+ },
+
+ canClear: function() {
+ return true;
+ },
+
+ _sync: function() {
+ let hovered = this.actor.hover;
+ this._closeButton.visible = hovered;
+ this._secondaryBin.visible = !hovered;
+ },
+
+ _onClicked: function() {
+ },
+
+ _onDestroy: function() {
+ }
+});
+Signals.addSignalMethods(Message.prototype);
+
+const MessageListSection = new Lang.Class({
+ Name: 'MessageListSection',
+
+ _init: function(title) {
+ this.actor = new St.BoxLayout({ style_class: 'message-list-section',
+ clip_to_allocation: true,
+ x_expand: true, vertical: true });
+ let titleBox = new St.BoxLayout({ style_class: 'message-list-section-title-box' });
+ this.actor.add_actor(titleBox);
+
+ this._title = new St.Button({ style_class: 'message-list-section-title',
+ label: title,
+ can_focus: true,
+ x_expand: true,
+ x_align: St.Align.START });
+ titleBox.add_actor(this._title);
+
+ this._title.connect('clicked', Lang.bind(this, this._onTitleClicked));
+ this._title.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn));
+
+ let closeIcon = new St.Icon({ icon_name: 'window-close-symbolic' });
+ this._closeButton = new St.Button({ style_class: 'message-list-section-close',
+ child: closeIcon,
+ accessible_name: _("Clear section"),
+ can_focus: true });
+ this._closeButton.set_x_align(Clutter.ActorAlign.END);
+ titleBox.add_actor(this._closeButton);
+
+ this._closeButton.connect('clicked', Lang.bind(this, this.clear));
+
+ this._list = new St.BoxLayout({ style_class: 'message-list-section-list',
+ vertical: true });
+ this.actor.add_actor(this._list);
+
+ this._list.connect('actor-added', Lang.bind(this, this._sync));
+ this._list.connect('actor-removed', Lang.bind(this, this._sync));
+
+ this._messages = new Map();
+ this._date = new Date();
+ this.empty = true;
+ this._sync();
+ },
+
+ _onTitleClicked: function() {
+ Main.overview.hide();
+ Main.panel.closeCalendar();
+ },
+
+ _onKeyFocusIn: function(actor) {
+ this.emit('key-focus-in', actor);
+ },
+
+ setDate: function(date) {
+ if (_sameDay(date, this._date))
+ return;
+ this._date = date;
+ this._sync();
+ },
+
+ addMessage: function(message, animate) {
+ this.addMessageAtIndex(message, 0, animate);
+ },
+
+ addMessageAtIndex: function(message, index, animate) {
+ let obj = {
+ container: null,
+ destroyId: 0,
+ keyFocusId: 0,
+ closeId: 0
+ };
+ let pivot = new Clutter.Point({ x: .5, y: .5 });
+ let scale = animate ? 0 : 1;
+ obj.container = new St.Widget({ layout_manager: new ScaleLayout(),
+ pivot_point: pivot,
+ scale_x: scale, scale_y: scale });
+ obj.keyFocusId = message.actor.connect('key-focus-in',
+ Lang.bind(this, this._onKeyFocusIn));
+ obj.destroyId = message.actor.connect('destroy',
+ Lang.bind(this, function() {
+ this.removeMessage(message, false);
+ }));
+ obj.closeId = message.connect('close',
+ Lang.bind(this, function() {
+ this.removeMessage(message, true);
+ }));
+
+ this._messages.set(message, obj);
+ obj.container.add_actor(message.actor);
+
+ this._list.insert_child_at_index(obj.container, index);
+
+ if (animate)
+ Tweener.addTween(obj.container, { scale_x: 1,
+ scale_y: 1,
+ time: MESSAGE_ANIMATION_TIME,
+ transition: 'easeOutQuad' });
+ },
+
+ removeMessage: function(message, animate) {
+ let obj = this._messages.get(message);
+
+ message.actor.disconnect(obj.destroyId);
+ message.actor.disconnect(obj.keyFocusId);
+ message.disconnect(obj.closeId);
+
+ this._messages.delete(message);
+
+ if (animate)
+ Tweener.addTween(obj.container, { scale_x: 0, scale_y: 0,
+ time: MESSAGE_ANIMATION_TIME,
+ transition: 'easeOutQuad',
+ onComplete: function() {
+ obj.container.destroy();
+ }});
+ else
+ obj.container.destroy();
+ },
+
+ clear: function() {
+ let messages = [...this._messages.keys()].filter(function(message) {
+ return message.canClear();
+ });
+
+ // If there are few messages, letting them all zoom out looks OK
+ if (messages.length < 2) {
+ messages.forEach(Lang.bind(this, function(message) {
+ this.removeMessage(message, true); }));
+ } else {
+ // Otherwise we slide them out one by one, and then zoom them
+ // out "off-screen" in the end to smoothly shrink the parent
+ let delay = MESSAGE_ANIMATION_TIME / Math.max(messages.length, 5);
+ for (let i = 0; i < messages.length; i++) {
+ let message = messages[i];
+ let obj = this._messages.get(message);
+ Tweener.addTween(obj.container,
+ { anchor_x: this._list.width,
+ opacity: 0,
+ time: MESSAGE_ANIMATION_TIME,
+ delay: i * delay,
+ transition: 'easeOutQuad',
+ onComplete: Lang.bind(this, function() {
+ this.removeMessage(message, true);
+ })});
+ }
+ }
+ },
+
+ _canClear: function() {
+ for (let message of this._messages.keys())
+ if (message.canClear())
+ return true;
+ return false;
+ },
+
+ _isToday: function() {
+ let today = new Date();
+ return _sameDay(this._date, today);
+ },
+
+ _syncVisible: function() {
+ this.actor.visible = !this.empty;
+ },
+
+ _sync: function() {
+ let empty = this._list.get_n_children() == 0;
+ let changed = this.empty !== empty;
+ this.empty = empty;
+
+ if (changed)
+ this.emit('empty-changed');
+
+ this._closeButton.visible = this._canClear();
+ this._syncVisible();
+ }
+});
+Signals.addSignalMethods(MessageListSection.prototype);
+
const EventsList = new Lang.Class({
Name: 'EventsList',
@@ -1030,3 +1351,101 @@ const EventsList = new Lang.Class({
}
}
});
+
+const Placeholder = new Lang.Class({
+ Name: 'Placeholder',
+
+ _init: function() {
+ this.actor = new St.BoxLayout({ style_class: 'message-list-placeholder',
+ vertical: true });
+
+ this._date = new Date();
+
+ let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/no-events.svg');
+ let gicon = new Gio.FileIcon({ file: file });
+
+ this._icon = new St.Icon({ gicon: gicon });
+ this.actor.add_actor(this._icon);
+
+ this._label = new St.Label({ text: _("No Events") });
+ this.actor.add_actor(this._label);
+ }
+});
+
+const MessageList = new Lang.Class({
+ Name: 'MessageList',
+
+ _init: function() {
+ this.actor = new St.Widget({ style_class: 'message-list',
+ layout_manager: new Clutter.BinLayout(),
+ x_expand: true, y_expand: true });
+
+ this._placeholder = new Placeholder();
+ this.actor.add_actor(this._placeholder.actor);
+
+ this._scrollView = new St.ScrollView({ style_class: 'vfade',
+ overlay_scrollbars: true,
+ x_expand: true, y_expand: true,
+ x_fill: true, y_fill: true });
+ this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ this.actor.add_actor(this._scrollView);
+
+ this._sectionList = new St.BoxLayout({ style_class: 'message-list-sections',
+ vertical: true,
+ y_expand: true,
+ y_align: Clutter.ActorAlign.START });
+ this._scrollView.add_actor(this._sectionList);
+ this._sections = new Map();
+ },
+
+ _addSection: function(section) {
+ let obj = {
+ destroyId: 0,
+ visibleId: 0,
+ emptyChangedId: 0,
+ keyFocusId: 0
+ };
+ obj.destroyId = section.actor.connect('destroy', Lang.bind(this,
+ function() {
+ this._removeSection(section);
+ }));
+ obj.visibleId = section.actor.connect('notify::visible',
+ Lang.bind(this, this._sync));
+ obj.emptyChangedId = section.connect('empty-changed',
+ Lang.bind(this, this._sync));
+ obj.keyFocusId = section.connect('key-focus-in',
+ Lang.bind(this, this._onKeyFocusIn));
+
+ this._sections.set(section, obj);
+ this._sectionList.add_actor(section.actor);
+ this._sync();
+ },
+
+ _removeSection: function(section) {
+ let obj = this._sections.get(section);
+ section.actor.disconnect(obj.destroyId);
+ section.actor.disconnect(obj.visibleId);
+ section.disconnect(obj.emptyChangedId);
+ section.disconnect(obj.keyFocusId);
+
+ this._sections.delete(section);
+ this._sectionList.remove_actor(section.actor);
+ this._sync();
+ },
+
+ _onKeyFocusIn: function(section, actor) {
+ Util.ensureActorVisibleInScrollView(this._scrollView, actor);
+ },
+
+ _sync: function() {
+ let showPlaceholder = [...this._sections.keys()].every(function(s) {
+ return s.empty || !s.actor.visible;
+ });
+ this._placeholder.actor.visible = showPlaceholder;
+ },
+
+ setDate: function(date) {
+ for (let section of this._sections.keys())
+ section.setDate(date);
+ }
+});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]