[gnome-shell] MessageTray: fix wobbling during summaryitem animation
- From: Dan Winship <danw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] MessageTray: fix wobbling during summaryitem animation
- Date: Mon, 11 Oct 2010 15:52:12 +0000 (UTC)
commit 0215a449adec81c5668d5a270983e1a05f3eab18
Author: Dan Winship <danw gnome org>
Date: Fri Sep 24 16:04:56 2010 -0400
MessageTray: fix wobbling during summaryitem animation
Redo the way that the summary item expand/collapse animation works so
that the items all resize in unison so that when moving from one to
another, the summary area as a whole stays a constant width rather
than wobbling slightly.
(Also rename all references to the "minimum" summary item title width,
since it's not a minimum, it's just the width.)
https://bugzilla.gnome.org/show_bug.cgi?id=630546
data/theme/gnome-shell.css | 6 +-
js/ui/messageTray.js | 251 ++++++++++++++++++++++----------------------
2 files changed, 130 insertions(+), 127 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 893c75d..c14709c 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -959,6 +959,10 @@ StTooltip {
* icons, because then the summary would be 0x0 when there were no
* icons in it, and so you wouldn't be able to hover over it to
* activate it.
+ *
+ * Also, the spacing between a summary-source's icon and title is
+ * actually specified as padding-left in source-title, because we
+ * want the spacing to collapse along with the title.
*/
#summary-mode {
padding: 2px 0px 0px 4px;
@@ -966,7 +970,6 @@ StTooltip {
}
.summary-source {
- spacing: 4px;
}
.summary-source-button {
@@ -982,6 +985,7 @@ StTooltip {
font: 12px sans-serif;
font-weight: bold;
color: white;
+ padding-left: 4px;
}
.calendar-calendarweek {
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index e45bef8..d2eb30e 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -700,141 +700,56 @@ Source.prototype = {
};
Signals.addSignalMethods(Source.prototype);
-function SummaryItem(source, minTitleWidth) {
- this._init(source, minTitleWidth);
+function SummaryItem(source) {
+ this._init(source);
}
SummaryItem.prototype = {
- _init: function(source, minTitleWidth) {
+ _init: function(source) {
this.source = source;
- // The message tray items should all be the same width when expanded. Because the only variation is introduced by the width of the title,
- // we pass in the desired minimum title width, which is the maximum title width of the items which are currently in the tray. If the width
- // of the title of this item is greater (up to MAX_SOURCE_TITLE_WIDTH), then that width will be used, and the width of all the other items
- // in the message tray will be readjusted.
- this._minTitleWidth = minTitleWidth;
this.actor = new St.Button({ style_class: 'summary-source-button',
reactive: true,
track_hover: true });
- this._sourceBox = new Shell.GenericContainer({ style_class: 'summary-source',
- reactive: true });
- this._sourceBox.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
- this._sourceBox.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
- this._sourceBox.connect('allocate', Lang.bind(this, this._allocate));
+ this._sourceBox = new St.BoxLayout({ style_class: 'summary-source' });
this._sourceIcon = source.getSummaryIcon();
- this._sourceTitleBin = new St.Bin({ y_align: St.Align.MIDDLE, x_fill: true });
+ this._sourceTitleBin = new St.Bin({ y_align: St.Align.MIDDLE,
+ x_fill: true,
+ clip_to_allocation: true });
this._sourceTitle = new St.Label({ style_class: 'source-title',
text: source.title });
this._sourceTitle.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._sourceTitleBin.child = this._sourceTitle;
+ this._sourceTitleBin.width = 0;
this._sourceBox.add_actor(this._sourceIcon);
- this._sourceBox.add_actor(this._sourceTitleBin);
- this._widthFraction = 0;
+ this._sourceBox.add_actor(this._sourceTitleBin, { expand: true });
this.actor.child = this._sourceBox;
},
- getTitleNaturalWidth: function() {
- let [sourceTitleBinMinWidth, sourceTitleBinNaturalWidth] =
- this._sourceTitleBin.get_preferred_width(-1);
- return Math.min(sourceTitleBinNaturalWidth, MAX_SOURCE_TITLE_WIDTH);
- },
-
- setMinTitleWidth: function(minTitleWidth) {
- this._minTitleWidth = minTitleWidth;
- },
-
- _getPreferredWidth: function(actor, forHeight, alloc) {
- let [found, spacing] = this._sourceBox.get_theme_node().get_length('spacing', false);
- if (!found)
- spacing = 0;
- let [sourceIconMinWidth, sourceIconNaturalWidth] = this._sourceIcon.get_preferred_width(forHeight);
- let [sourceTitleBinMinWidth, sourceTitleBinNaturalWidth] =
- this._sourceTitleBin.get_preferred_width(forHeight);
- let minWidth = sourceIconNaturalWidth +
- (this._widthFraction > 0 ? spacing : 0) +
- this._widthFraction * Math.min(Math.max(sourceTitleBinNaturalWidth, this._minTitleWidth),
- MAX_SOURCE_TITLE_WIDTH);
- alloc.min_size = minWidth;
- alloc.natural_size = minWidth;
- },
-
- _getPreferredHeight: function(actor, forWidth, alloc) {
- let [sourceIconMinHeight, sourceIconNaturalHeight] = this._sourceIcon.get_preferred_height(forWidth);
- alloc.min_size = sourceIconNaturalHeight;
- alloc.natural_size = sourceIconNaturalHeight;
- },
+ // getTitleNaturalWidth, getTitleWidth, and setTitleWidth include
+ // the spacing between the icon and title (which is actually
+ // _sourceTitle's padding-left) as part of the width.
- _allocate: function (actor, box, flags) {
- let width = box.x2 - box.x1;
- let height = box.y2 - box.y1;
-
- let [sourceIconMinWidth, sourceIconNaturalWidth] = this._sourceIcon.get_preferred_width(-1);
- let [sourceIconMinHeight, sourceIconNaturalHeight] = this._sourceIcon.get_preferred_height(-1);
-
- let iconBox = new Clutter.ActorBox();
- iconBox.x1 = 0;
- iconBox.y1 = 0;
- iconBox.x2 = sourceIconNaturalWidth;
- iconBox.y2 = sourceIconNaturalHeight;
-
- this._sourceIcon.allocate(iconBox, flags);
-
- let [found, spacing] = this._sourceBox.get_theme_node().get_length('spacing', false);
- if (!found)
- spacing = 0;
-
- let titleBox = new Clutter.ActorBox();
- if (width > sourceIconNaturalWidth + spacing) {
- titleBox.x1 = iconBox.x2 + spacing;
- titleBox.x2 = width;
- } else {
- titleBox.x1 = iconBox.x2;
- titleBox.x2 = iconBox.x2;
- }
- titleBox.y1 = 0;
- titleBox.y2 = height;
-
- this._sourceTitleBin.allocate(titleBox, flags);
-
- this._sourceTitleBin.set_clip(0, 0, titleBox.x2 - titleBox.x1, height);
- },
-
- expand: function() {
- // this._adjustEllipsization replaces some text with the dots at the end of the animation,
- // and then we replace the dots with the text before we begin the animation to collapse
- // the title. These changes are not noticeable at the speed with which we do the animation,
- // while animating in the ellipsized mode does not look good.
- Tweener.addTween(this,
- { widthFraction: 1,
- time: ANIMATION_TIME,
- transition: 'linear',
- onComplete: this._adjustEllipsization,
- onCompleteScope: this });
- },
+ getTitleNaturalWidth: function() {
+ let [minWidth, naturalWidth] = this._sourceTitle.get_preferred_width(-1);
- collapse: function() {
- this._sourceTitle.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
- Tweener.addTween(this,
- { widthFraction: 0,
- time: ANIMATION_TIME,
- transition: 'linear' });
+ return Math.min(naturalWidth, MAX_SOURCE_TITLE_WIDTH);
},
- _adjustEllipsization: function() {
- let [sourceTitleBinMinWidth, sourceTitleBinNaturalWidth] = this._sourceTitleBin.get_preferred_width(-1);
- if (sourceTitleBinNaturalWidth > MAX_SOURCE_TITLE_WIDTH)
- this._sourceTitle.clutter_text.ellipsize = Pango.EllipsizeMode.END;
+ getTitleWidth: function() {
+ return this._sourceTitleBin.width;
},
- set widthFraction(widthFraction) {
- this._widthFraction = widthFraction;
- this._sourceBox.queue_relayout();
+ setTitleWidth: function(width) {
+ width = Math.round(width);
+ if (width != this._sourceTitleBin.width)
+ this._sourceTitleBin.width = width;
},
- get widthFraction() {
- return this._widthFraction;
+ setEllipsization: function(mode) {
+ this._sourceTitle.clutter_text.ellipsize = mode;
}
};
@@ -873,6 +788,14 @@ MessageTray.prototype = {
this._summaryNotificationBin.hide();
this._summaryNotification = null;
this._clickedSummaryItem = null;
+ this._expandedSummaryItem = null;
+ this._summaryItemTitleWidth = 0;
+
+ // To simplify the summary item animation code, we pretend
+ // that there's an invisible SummaryItem to the left of the
+ // leftmost real summary item, and that it's expanded when all
+ // of the other items are collapsed.
+ this._imaginarySummaryItemTitleWidth = 0;
this._trayState = State.HIDDEN;
this._locked = false;
@@ -958,17 +881,15 @@ MessageTray.prototype = {
return;
}
- let minTitleWidth = (this._longestSummaryItem ? this._longestSummaryItem.getTitleNaturalWidth() : 0);
- let summaryItem = new SummaryItem(source, minTitleWidth);
+ let summaryItem = new SummaryItem(source);
this._summary.insert_actor(summaryItem.actor, 0);
- let newItemTitleWidth = summaryItem.getTitleNaturalWidth();
- if (newItemTitleWidth > minTitleWidth) {
- for (let i = 0; i < this._summaryItems.length; i++) {
- this._summaryItems[i].setMinTitleWidth(newItemTitleWidth);
- }
- summaryItem.setMinTitleWidth(newItemTitleWidth);
+ let titleWidth = summaryItem.getTitleNaturalWidth();
+ if (titleWidth > this._summaryItemTitleWidth) {
+ this._summaryItemTitleWidth = titleWidth;
+ if (!this._expandedSummaryItem)
+ this._imaginarySummaryItemTitleWidth = titleWidth;
this._longestSummaryItem = summaryItem;
}
@@ -1020,19 +941,20 @@ MessageTray.prototype = {
this._summaryItems.splice(index, 1);
if (this._longestSummaryItem.source == source) {
-
- let maxTitleWidth = 0;
+ let newTitleWidth = 0;
this._longestSummaryItem = null;
for (let i = 0; i < this._summaryItems.length; i++) {
let summaryItem = this._summaryItems[i];
- if (summaryItem.getTitleNaturalWidth() > maxTitleWidth) {
- maxTitleWidth = summaryItem.getTitleNaturalWidth();
+ let titleWidth = summaryItem.getTitleNaturalWidth();
+ if (titleWidth > newTitleWidth) {
+ newTitleWidth = titleWidth;
this._longestSummaryItem = summaryItem;
}
}
- for (let i = 0; i < this._summaryItems.length; i++) {
- this._summaryItems[i].setMinTitleWidth(maxTitleWidth);
- }
+
+ this._summaryItemTitleWidth = newTitleWidth;
+ if (!this._expandedSummaryItem)
+ this._imaginarySummaryItemTitleWidth = newTitleWidth;
}
let needUpdate = false;
@@ -1108,10 +1030,87 @@ MessageTray.prototype = {
},
_onSummaryItemHoverChanged: function(summaryItem) {
- if (summaryItem.actor.hover)
- summaryItem.expand();
+ // We can't just animate individual summary items as the
+ // pointer moves in and out of them, because if they don't
+ // move in sync you get weird-looking wobbling. So whenever
+ // there's a change, we have to re-tween the entire summary
+ // area.
+
+ if (summaryItem.actor.hover) {
+ if (summaryItem == this._expandedSummaryItem)
+ return;
+
+ this._expandedSummaryItem = summaryItem;
+ } else {
+ if (summaryItem != this._expandedSummaryItem)
+ return;
+
+ this._expandedSummaryItem = null;
+
+ // Turn off ellipsization while collapsing; it looks better
+ summaryItem.setEllipsization(Pango.EllipsizeMode.NONE);
+ }
+
+ // We tween on a "_expandedSummaryItemTitleWidth" pseudo-property
+ // that represents the current title width of the
+ // expanded/expanding item, or the width of the imaginary
+ // invisible item if we're collapsing everything.
+ Tweener.addTween(this,
+ { _expandedSummaryItemTitleWidth: this._summaryItemTitleWidth,
+ time: ANIMATION_TIME,
+ transition: 'easeOutQuad',
+ onComplete: this._expandSummaryItemCompleted,
+ onCompleteScope: this });
+ },
+
+ get _expandedSummaryItemTitleWidth() {
+ if (this._expandedSummaryItem)
+ return this._expandedSummaryItem.getTitleWidth();
+ else
+ return this._imaginarySummaryItemTitleWidth;
+ },
+
+ set _expandedSummaryItemTitleWidth(expansion) {
+ // Expand the expanding item to its new width
+ if (this._expandedSummaryItem)
+ this._expandedSummaryItem.setTitleWidth(expansion);
else
- summaryItem.collapse();
+ this._imaginarySummaryItemTitleWidth = expansion;
+
+ // Figure out how much space the other items are currently
+ // using, and how much they need to be shrunk to keep the
+ // total width (including the width of the imaginary item)
+ // constant.
+ let excess = this._summaryItemTitleWidth - expansion;
+ let oldExcess = 0, shrinkage;
+ if (excess) {
+ for (let i = 0; i < this._summaryItems.length; i++) {
+ if (this._summaryItems[i] != this._expandedSummaryItem)
+ oldExcess += this._summaryItems[i].getTitleWidth();
+ }
+ if (this._expandedSummaryItem)
+ oldExcess += this._imaginarySummaryItemTitleWidth;
+ }
+ if (excess && oldExcess)
+ shrinkage = excess / oldExcess;
+ else
+ shrinkage = 0;
+
+ // Now shrink each one proportionately
+ for (let i = 0; i < this._summaryItems.length; i++) {
+ if (this._summaryItems[i] == this._expandedSummaryItem)
+ continue;
+
+ let width = this._summaryItems[i].getTitleWidth();
+ this._summaryItems[i].setTitleWidth(width * shrinkage);
+ }
+ if (this._expandedSummaryItem)
+ this._imaginarySummaryItemTitleWidth *= shrinkage;
+ },
+
+ _expandSummaryItemCompleted: function() {
+ if (this._expandedSummaryItem)
+ this._expandedSummaryItem.setEllipsization(Pango.EllipsizeMode.END);
},
_onSummaryItemClicked: function(summaryItem) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]