summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorviettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-16 22:26:56 +0000
committerviettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-16 22:26:56 +0000
commit812742f88a7b3b6457f5d5cfcef88ee7848ba764 (patch)
treec76f4535f41da799ff23ce0737935cc653b9de25 /chrome/browser
parent33e1fe807e303f9bd31abce0c6e7033a084a269c (diff)
downloadchromium_src-812742f88a7b3b6457f5d5cfcef88ee7848ba764.zip
chromium_src-812742f88a7b3b6457f5d5cfcef88ee7848ba764.tar.gz
chromium_src-812742f88a7b3b6457f5d5cfcef88ee7848ba764.tar.bz2
Mac: make unselected pinned tabs glow when their title changes.
When an unselected pinned tab changes its title, that tab should briefly "glow" to indicate this (see what Win/Chrome does). BUG=29261,28154 TEST=Go to <http://www.surflocal.net/Awards/submit/animatedtitle.html>, pin it, and select another tab; the pinned tab should glow (pulsate in this case) nicely. Also make sure that hover effects work as normal. Try this on a number of themes, with multiple tabs, etc. Review URL: http://codereview.chromium.org/455042 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34762 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/cocoa/tab_controller.mm7
-rw-r--r--chrome/browser/cocoa/tab_view.h41
-rw-r--r--chrome/browser/cocoa/tab_view.mm186
-rw-r--r--chrome/browser/cocoa/tab_view_unittest.mm28
4 files changed, 215 insertions, 47 deletions
diff --git a/chrome/browser/cocoa/tab_controller.mm b/chrome/browser/cocoa/tab_controller.mm
index 49ce170..c9469e0 100644
--- a/chrome/browser/cocoa/tab_controller.mm
+++ b/chrome/browser/cocoa/tab_controller.mm
@@ -105,7 +105,9 @@ class MenuDelegate : public menus::SimpleMenuModel::Delegate {
- (void)internalSetSelected:(BOOL)selected {
selected_ = selected;
TabView* tabView = static_cast<TabView*>([self view]);
+ DCHECK([tabView isKindOfClass:[TabView class]]);
[tabView setState:selected];
+ [tabView cancelAlert];
[self updateVisibility];
[self updateTitleColor];
}
@@ -149,6 +151,11 @@ class MenuDelegate : public menus::SimpleMenuModel::Delegate {
- (void)setTitle:(NSString*)title {
[[self view] setToolTip:title];
+ if ([self pinned] && ![self selected]) {
+ TabView* tabView = static_cast<TabView*>([self view]);
+ DCHECK([tabView isKindOfClass:[TabView class]]);
+ [tabView startAlert];
+ }
[super setTitle:title];
}
diff --git a/chrome/browser/cocoa/tab_view.h b/chrome/browser/cocoa/tab_view.h
index 58383af..b43053e 100644
--- a/chrome/browser/cocoa/tab_view.h
+++ b/chrome/browser/cocoa/tab_view.h
@@ -11,6 +11,26 @@
#import "chrome/browser/cocoa/background_gradient_view.h"
#import "chrome/browser/cocoa/hover_close_button.h"
+namespace tabs {
+
+// Nomenclature:
+// Tabs _glow_ under two different circumstances, when they are _hovered_ (by
+// the mouse) and when they are _alerted_ (to show that the tab's title has
+// changed).
+
+// The state of alerting (to show a title change on an unselected, pinned tab).
+// This is more complicated than a simple on/off since we want to allow the
+// alert glow to go through a full rise-hold-fall cycle to avoid flickering (or
+// always holding).
+enum AlertState {
+ kAlertNone = 0, // Obj-C initializes to this.
+ kAlertRising,
+ kAlertHolding,
+ kAlertFalling
+};
+
+} // namespace tabs
+
@class TabController, TabWindowController;
// A view that handles the event tracking (clicking and dragging) for a tab
@@ -29,8 +49,16 @@
scoped_nsobject<NSTrackingArea> closeTrackingArea_;
BOOL isMouseInside_; // Is the mouse hovering over?
- CGFloat hoverAlpha_; // How strong the mouse hover state is.
- NSTimeInterval lastHoverUpdate_; // Time the hover value was last updated.
+ tabs::AlertState alertState_;
+
+ CGFloat hoverAlpha_; // How strong the hover glow is.
+ NSTimeInterval hoverHoldEndTime_; // When the hover glow will begin dimming.
+
+ CGFloat alertAlpha_; // How strong the alert glow is.
+ NSTimeInterval alertHoldEndTime_; // When the hover glow will begin dimming.
+
+ NSTimeInterval lastGlowUpdate_; // Time either glow was last updated.
+
NSPoint hoverPoint_; // Current location of hover in view coords.
// All following variables are valid for the duration of a drag.
@@ -60,6 +88,7 @@
@property(assign, nonatomic) NSCellStateValue state;
@property(assign, nonatomic) CGFloat hoverAlpha;
+@property(assign, nonatomic) CGFloat alertAlpha;
// Determines if the tab is in the process of animating closed. It may still
// be visible on-screen, but should not respond to/initiate any events. Upon
@@ -70,6 +99,14 @@
// Enables/Disables tracking regions for the tab.
- (void)setTrackingEnabled:(BOOL)enabled;
+// Begin showing an "alert" glow (shown to call attention to an unselected
+// pinned tab whose title changed).
+- (void)startAlert;
+
+// Stop showing the "alert" glow; this won't immediately wipe out any glow, but
+// will make it fade away.
+- (void)cancelAlert;
+
@end
#endif // CHROME_BROWSER_COCOA_TAB_VIEW_H_
diff --git a/chrome/browser/cocoa/tab_view.mm b/chrome/browser/cocoa/tab_view.mm
index 28dca36..1f6c29a 100644
--- a/chrome/browser/cocoa/tab_view.mm
+++ b/chrome/browser/cocoa/tab_view.mm
@@ -16,8 +16,18 @@ const CGFloat kInsetMultiplier = 2.0/3.0;
const CGFloat kControlPoint1Multiplier = 1.0/3.0;
const CGFloat kControlPoint2Multiplier = 3.0/8.0;
-const NSTimeInterval kAnimationShowDuration = 0.2;
-const NSTimeInterval kAnimationHideDuration = 0.4;
+// The amount of time in seconds during which each type of glow increases, holds
+// steady, and decreases, respectively.
+const NSTimeInterval kHoverShowDuration = 0.2;
+const NSTimeInterval kHoverHoldDuration = 0.02;
+const NSTimeInterval kHoverHideDuration = 0.4;
+const NSTimeInterval kAlertShowDuration = 0.4;
+const NSTimeInterval kAlertHoldDuration = 0.4;
+const NSTimeInterval kAlertHideDuration = 0.4;
+
+// The default time interval in seconds between glow updates (when
+// increasing/decreasing).
+const NSTimeInterval kGlowUpdateInterval = 0.025;
const CGFloat kTearDistance = 36.0;
const NSTimeInterval kTearDuration = 0.333;
@@ -28,10 +38,19 @@ const CGFloat kRapidCloseDist = 2.5;
} // namespace
+@interface TabView(Private)
+
+- (void)resetLastGlowUpdateTime;
+- (NSTimeInterval)timeElapsedSinceLastGlowUpdate;
+- (void)adjustGlowValue;
+
+@end // TabView(Private)
+
@implementation TabView
@synthesize state = state_;
@synthesize hoverAlpha = hoverAlpha_;
+@synthesize alertAlpha = alertAlpha_;
@synthesize closing = closing_;
- (id)initWithFrame:(NSRect)frame {
@@ -67,36 +86,10 @@ const CGFloat kRapidCloseDist = 2.5;
return YES;
}
-- (void)adjustHoverValue {
- NSTimeInterval thisUpdate = [NSDate timeIntervalSinceReferenceDate];
-
- NSTimeInterval elapsed = thisUpdate - lastHoverUpdate_;
-
- CGFloat opacity = [self hoverAlpha];
- if (isMouseInside_) {
- opacity += elapsed / kAnimationShowDuration;
- } else {
- opacity -= elapsed / kAnimationHideDuration;
- }
-
- if (!isMouseInside_ && opacity < 0) {
- opacity = 0;
- } else if (isMouseInside_ && opacity > 1) {
- opacity = 1;
- } else {
- [self performSelector:_cmd withObject:nil afterDelay:0.02];
- }
- lastHoverUpdate_ = thisUpdate;
- [self setHoverAlpha:opacity];
-
- [self setNeedsDisplay:YES];
-}
-
- (void)mouseEntered:(NSEvent*)theEvent {
- lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
isMouseInside_ = YES;
- [self adjustHoverValue];
- [self setNeedsDisplay:YES];
+ [self resetLastGlowUpdateTime];
+ [self adjustGlowValue];
}
- (void)mouseMoved:(NSEvent*)theEvent {
@@ -106,10 +99,11 @@ const CGFloat kRapidCloseDist = 2.5;
}
- (void)mouseExited:(NSEvent*)theEvent {
- lastHoverUpdate_ = [NSDate timeIntervalSinceReferenceDate];
isMouseInside_ = NO;
- [self adjustHoverValue];
- [self setNeedsDisplay:YES];
+ hoverHoldEndTime_ =
+ [NSDate timeIntervalSinceReferenceDate] + kHoverHoldDuration;
+ [self resetLastGlowUpdateTime];
+ [self adjustGlowValue];
}
- (void)setTrackingEnabled:(BOOL)enabled {
@@ -678,9 +672,14 @@ const CGFloat kRapidCloseDist = 2.5;
[context saveGraphicsState];
[path addClip];
- if (selected || hoverAlpha_ > 0) {
+ // Use the same overlay for both hover and alert glows by combining their
+ // opacities. Note that h + a - h*a = h + a*(1-h) = a + h*(1-a).
+ CGFloat hoverAlpha = [self hoverAlpha];
+ CGFloat alertAlpha = [self alertAlpha];
+ CGFloat glowAlpha = hoverAlpha + alertAlpha - hoverAlpha * alertAlpha;
+ if (selected || glowAlpha > 0) {
// Draw the background.
- CGFloat backgroundAlpha = hoverAlpha_ * 0.5;
+ CGFloat backgroundAlpha = glowAlpha * 0.5;
[context saveGraphicsState];
CGContextRef cgContext =
(CGContextRef)([context graphicsPort]);
@@ -693,13 +692,12 @@ const CGFloat kRapidCloseDist = 2.5;
[context restoreGraphicsState];
// Draw a mouse hover gradient for the default themes
- if (!selected) {
+ if (!selected && hoverAlpha > 0) {
if (![theme backgroundImageForStyle:GTMThemeStyleTabBarDeselected
state:GTMThemeStateActiveWindow]) {
scoped_nsobject<NSGradient> glow([NSGradient alloc]);
[glow initWithStartingColor:[NSColor colorWithCalibratedWhite:1.0
- alpha:1.0 *
- hoverAlpha_]
+ alpha:1.0 * hoverAlpha]
endingColor:[NSColor colorWithCalibratedWhite:1.0
alpha:0.0]];
@@ -724,7 +722,7 @@ const CGFloat kRapidCloseDist = 2.5;
[highlightTransform translateXBy:1 yBy:-1];
scoped_nsobject<NSBezierPath> highlightPath([path copy]);
[highlightPath transformUsingAffineTransform:highlightTransform];
- [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2 + 0.3 * hoverAlpha_]
+ [[NSColor colorWithCalibratedWhite:1.0 alpha:0.2 + 0.3 * glowAlpha]
setStroke];
[highlightPath stroke];
@@ -777,4 +775,114 @@ const CGFloat kRapidCloseDist = 2.5;
}
}
+- (void)startAlert {
+ // Do not start a new alert while already alerting or while in a decay cycle.
+ if (alertState_ == tabs::kAlertNone) {
+ alertState_ = tabs::kAlertRising;
+ [self resetLastGlowUpdateTime];
+ [self adjustGlowValue];
+ }
+}
+
+- (void)cancelAlert {
+ if (alertState_ != tabs::kAlertNone) {
+ alertState_ = tabs::kAlertFalling;
+ alertHoldEndTime_ =
+ [NSDate timeIntervalSinceReferenceDate] + kGlowUpdateInterval;
+ [self resetLastGlowUpdateTime];
+ [self adjustGlowValue];
+ }
+}
+
@end // @implementation TabView
+
+@implementation TabView(Private)
+
+- (void)resetLastGlowUpdateTime {
+ lastGlowUpdate_ = [NSDate timeIntervalSinceReferenceDate];
+}
+
+- (NSTimeInterval)timeElapsedSinceLastGlowUpdate {
+ return [NSDate timeIntervalSinceReferenceDate] - lastGlowUpdate_;
+}
+
+- (void)adjustGlowValue {
+ // A time interval long enough to represent no update.
+ const NSTimeInterval kNoUpdate = 1000000;
+
+ // Time until next update for either glow.
+ NSTimeInterval nextUpdate = kNoUpdate;
+
+ NSTimeInterval elapsed = [self timeElapsedSinceLastGlowUpdate];
+ NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
+
+ // TODO(viettrungluu): <http://crbug.com/30617> -- split off the stuff below
+ // into a pure function and add a unit test.
+
+ CGFloat hoverAlpha = [self hoverAlpha];
+ if (isMouseInside_) {
+ // Increase hover glow until it's 1.
+ if (hoverAlpha < 1) {
+ hoverAlpha = MIN(hoverAlpha + elapsed / kHoverShowDuration, 1);
+ [self setHoverAlpha:hoverAlpha];
+ nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
+ } // Else already 1 (no update needed).
+ } else {
+ if (currentTime >= hoverHoldEndTime_) {
+ // No longer holding, so decrease hover glow until it's 0.
+ if (hoverAlpha > 0) {
+ hoverAlpha = MAX(hoverAlpha - elapsed / kHoverHideDuration, 0);
+ [self setHoverAlpha:hoverAlpha];
+ nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
+ } // Else already 0 (no update needed).
+ } else {
+ // Schedule update for end of hold time.
+ nextUpdate = MIN(hoverHoldEndTime_ - currentTime, nextUpdate);
+ }
+ }
+
+ CGFloat alertAlpha = [self alertAlpha];
+ if (alertState_ == tabs::kAlertRising) {
+ // Increase alert glow until it's 1 ...
+ alertAlpha = MIN(alertAlpha + elapsed / kAlertShowDuration, 1);
+ [self setAlertAlpha:alertAlpha];
+
+ // ... and having reached 1, switch to holding.
+ if (alertAlpha >= 1) {
+ alertState_ = tabs::kAlertHolding;
+ alertHoldEndTime_ = currentTime + kAlertHoldDuration;
+ nextUpdate = MIN(kAlertHoldDuration, nextUpdate);
+ } else {
+ nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
+ }
+ } else if (alertState_ != tabs::kAlertNone) {
+ if (alertAlpha > 0) {
+ if (currentTime >= alertHoldEndTime_) {
+ // Stop holding, then decrease alert glow (until it's 0).
+ if (alertState_ == tabs::kAlertHolding) {
+ alertState_ = tabs::kAlertFalling;
+ nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
+ } else {
+ DCHECK_EQ(tabs::kAlertFalling, alertState_);
+ alertAlpha = MAX(alertAlpha - elapsed / kAlertHideDuration, 0);
+ [self setAlertAlpha:alertAlpha];
+ nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
+ }
+ } else {
+ // Schedule update for end of hold time.
+ nextUpdate = MIN(alertHoldEndTime_ - currentTime, nextUpdate);
+ }
+ } else {
+ // Done the alert decay cycle.
+ alertState_ = tabs::kAlertNone;
+ }
+ }
+
+ if (nextUpdate < kNoUpdate)
+ [self performSelector:_cmd withObject:nil afterDelay:nextUpdate];
+
+ [self resetLastGlowUpdateTime];
+ [self setNeedsDisplay:YES];
+}
+
+@end // @implementation TabView(Private)
diff --git a/chrome/browser/cocoa/tab_view_unittest.mm b/chrome/browser/cocoa/tab_view_unittest.mm
index 0c4d768..128274b 100644
--- a/chrome/browser/cocoa/tab_view_unittest.mm
+++ b/chrome/browser/cocoa/tab_view_unittest.mm
@@ -28,12 +28,13 @@ TEST_VIEW(TabViewTest, view_)
// Test drawing, mostly to ensure nothing leaks or crashes.
TEST_F(TabViewTest, Display) {
- [view_ setHoverAlpha:0.0];
- [view_ display];
- [view_ setHoverAlpha:0.5];
- [view_ display];
- [view_ setHoverAlpha:1.0];
- [view_ display];
+ for (int i = 0; i < 5; i++) {
+ for (int j = 0; j < 5; j++) {
+ [view_ setHoverAlpha:i*0.2];
+ [view_ setAlertAlpha:j*0.2];
+ [view_ display];
+ }
+ }
}
// Test dragging and mouse tracking.
@@ -46,4 +47,19 @@ TEST_F(TabViewTest, Menu) {
EXPECT_FALSE([view_ menu]);
}
+TEST_F(TabViewTest, Glow) {
+ // TODO(viettrungluu): Figure out how to test this, which is timing-sensitive
+ // and which moreover uses |-performSelector:withObject:afterDelay:|.
+
+ // Call |-startAlert|/|-cancelAlert| and make sure it doesn't crash.
+ for (int i = 0; i < 5; i++) {
+ [view_ startAlert];
+ [view_ cancelAlert];
+ }
+ [view_ startAlert];
+ [view_ startAlert];
+ [view_ cancelAlert];
+ [view_ cancelAlert];
+}
+
} // namespace