Chromium Code Reviews| Index: chrome/browser/ui/cocoa/tabs/media_indicator_button.mm |
| diff --git a/chrome/browser/ui/cocoa/tabs/media_indicator_view.mm b/chrome/browser/ui/cocoa/tabs/media_indicator_button.mm |
| similarity index 14% |
| rename from chrome/browser/ui/cocoa/tabs/media_indicator_view.mm |
| rename to chrome/browser/ui/cocoa/tabs/media_indicator_button.mm |
| index e3e71cdbcb1d09675e34b21602bb938ffe22a097..87ced8c075eda8dfe07e0a9d841219d81a14eb13 100644 |
| --- a/chrome/browser/ui/cocoa/tabs/media_indicator_view.mm |
| +++ b/chrome/browser/ui/cocoa/tabs/media_indicator_button.mm |
| @@ -2,113 +2,174 @@ |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| -#import "chrome/browser/ui/cocoa/tabs/media_indicator_view.h" |
| +#import "chrome/browser/ui/cocoa/tabs/media_indicator_button.h" |
| +#include "base/logging.h" |
| +#include "base/thread_task_runner_handle.h" |
| +#include "content/public/browser/user_metrics.h" |
| #include "ui/gfx/animation/animation.h" |
| #include "ui/gfx/animation/animation_delegate.h" |
| #include "ui/gfx/image/image.h" |
| -class MediaIndicatorViewAnimationDelegate : public gfx::AnimationDelegate { |
| +@implementation MediaIndicatorButton |
| + |
| +class FadeAnimationDelegate : public gfx::AnimationDelegate { |
| public: |
| - MediaIndicatorViewAnimationDelegate(NSView* view, |
| - TabMediaState* mediaState, |
| - TabMediaState* animatingMediaState) |
| - : view_(view), mediaState_(mediaState), |
| - animatingMediaState_(animatingMediaState), |
| - doneCallbackObject_(nil), doneCallbackSelector_(nil) {} |
| - ~MediaIndicatorViewAnimationDelegate() override {} |
| - |
| - void SetAnimationDoneCallback(id anObject, SEL selector) { |
| - doneCallbackObject_ = anObject; |
| - doneCallbackSelector_ = selector; |
| - } |
| + explicit FadeAnimationDelegate(MediaIndicatorButton* button) |
| + : button_(button) {} |
| + ~FadeAnimationDelegate() override {} |
| - void AnimationEnded(const gfx::Animation* animation) override { |
| - *animatingMediaState_ = *mediaState_; |
| - [view_ setNeedsDisplay:YES]; |
| - [doneCallbackObject_ performSelector:doneCallbackSelector_]; |
| - } |
| + private: |
| + // gfx::AnimationDelegate implementation. |
| void AnimationProgressed(const gfx::Animation* animation) override { |
| - [view_ setNeedsDisplay:YES]; |
| + [button_ setNeedsDisplay:YES]; |
| } |
| + |
| void AnimationCanceled(const gfx::Animation* animation) override { |
| AnimationEnded(animation); |
| } |
| - private: |
| - NSView* const view_; |
| - TabMediaState* const mediaState_; |
| - TabMediaState* const animatingMediaState_; |
| + void AnimationEnded(const gfx::Animation* animation) override { |
| + button_->showingMediaState_ = button_->mediaState_; |
| + [button_ setNeedsDisplay:YES]; |
| + [button_->animationDoneTarget_ |
| + performSelector:button_->animationDoneAction_]; |
| + } |
| - id doneCallbackObject_; |
| - SEL doneCallbackSelector_; |
| -}; |
| + MediaIndicatorButton* const button_; |
| -@implementation MediaIndicatorView |
| + DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate); |
| +}; |
| -@synthesize mediaState = mediaState_; |
| -@synthesize animatingMediaState = animatingMediaState_; |
| +@synthesize showingMediaState = showingMediaState_; |
| - (id)init { |
| if ((self = [super initWithFrame:NSZeroRect])) { |
| - mediaState_ = animatingMediaState_ = TAB_MEDIA_STATE_NONE; |
| - delegate_.reset(new MediaIndicatorViewAnimationDelegate( |
| - self, &mediaState_, &animatingMediaState_)); |
| + mediaState_ = TAB_MEDIA_STATE_NONE; |
| + showingMediaState_ = TAB_MEDIA_STATE_NONE; |
| + [self setEnabled:NO]; |
| + [super setTarget:self]; |
| + [super setAction:@selector(handleClick:)]; |
| } |
| return self; |
| } |
| -- (void)updateIndicator:(TabMediaState)mediaState { |
| - if (mediaState == mediaState_) |
| +- (void)removeFromSuperview { |
| + fadeAnimation_.reset(); |
| + [super removeFromSuperview]; |
| +} |
| + |
| +- (void)transitionToMediaState:(TabMediaState)nextState { |
| + if (nextState == mediaState_) |
| return; |
| - mediaState_ = mediaState; |
| - animation_.reset(); |
| - |
| - // Prepare this view if the new TabMediaState is an active one. |
| - if (mediaState_ != TAB_MEDIA_STATE_NONE) { |
| - animatingMediaState_ = mediaState_; |
| - NSImage* const image = |
| - chrome::GetTabMediaIndicatorImage(mediaState_).ToNSImage(); |
| - NSRect frame = [self frame]; |
| - frame.size = [image size]; |
| - [self setFrame:frame]; |
| - [self setImage:image]; |
| + if (nextState != TAB_MEDIA_STATE_NONE) { |
| + [self setImage:chrome::GetTabMediaIndicatorImage(nextState).ToNSImage()]; |
| + affordanceImage_.reset( |
| + [chrome::GetTabMediaIndicatorAffordanceImage(nextState).ToNSImage() |
| + retain]); |
| } |
| - // If the animation delegate is missing, that means animations were disabled |
| - // for testing; so, go directly to animating completion state. |
| - if (!delegate_) { |
| - animatingMediaState_ = mediaState_; |
| - return; |
| + if ((mediaState_ == TAB_MEDIA_STATE_AUDIO_PLAYING && |
| + nextState == TAB_MEDIA_STATE_AUDIO_MUTING) || |
| + (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING && |
| + nextState == TAB_MEDIA_STATE_AUDIO_PLAYING) || |
| + (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING && |
| + nextState == TAB_MEDIA_STATE_NONE)) { |
|
Nico
2014/10/31 21:55:00
nit: this looks like model code that should be sha
miu
2014/11/01 00:43:38
Yep. Planning on some cleanup w.r.t. the animatio
|
| + // Instant user feedback: No fade animation. |
| + showingMediaState_ = nextState; |
| + fadeAnimation_.reset(); |
| + } else { |
| + if (nextState == TAB_MEDIA_STATE_NONE) |
| + showingMediaState_ = mediaState_; // Fading-out indicator. |
| + else |
| + showingMediaState_ = nextState; // Fading-in to next indicator. |
| + // gfx::Animation requires a task runner is available for the current |
| + // thread. Generally, only certain unit tests would not instantiate a task |
| + // runner. |
| + if (base::ThreadTaskRunnerHandle::IsSet()) { |
| + fadeAnimation_ = chrome::CreateTabMediaIndicatorFadeAnimation(nextState); |
| + if (!fadeAnimationDelegate_) |
| + fadeAnimationDelegate_.reset(new FadeAnimationDelegate(self)); |
| + fadeAnimation_->set_delegate(fadeAnimationDelegate_.get()); |
| + fadeAnimation_->Start(); |
| + } |
| } |
| - animation_ = chrome::CreateTabMediaIndicatorFadeAnimation(mediaState_); |
| - animation_->set_delegate(delegate_.get()); |
| - animation_->Start(); |
| + [self setEnabled:(chrome::IsTabAudioMutingFeatureEnabled() && |
| + (nextState == TAB_MEDIA_STATE_AUDIO_PLAYING || |
| + nextState == TAB_MEDIA_STATE_AUDIO_MUTING))]; |
| + |
| + // An indicator state change should be made visible immediately, instead of |
| + // the user being surprised when their mouse leaves the button. |
| + if ([self hoverState] == kHoverStateMouseOver) |
| + [self setHoverState:kHoverStateNone]; |
| + |
| + mediaState_ = nextState; |
| + |
| + [self setNeedsDisplay:YES]; |
| } |
| -- (void)setAnimationDoneCallbackObject:(id)anObject withSelector:(SEL)selector { |
| - if (delegate_) |
| - delegate_->SetAnimationDoneCallback(anObject, selector); |
| +- (void)setTarget:(id)aTarget { |
| + NOTREACHED(); // See class-level comments. |
| } |
| -- (void)drawRect:(NSRect)rect { |
| - if (!animation_) |
| - return; |
| +- (void)setAction:(SEL)anAction { |
| + NOTREACHED(); // See class-level comments. |
| +} |
| + |
| +- (void)setAnimationDoneTarget:(id)target withAction:(SEL)action { |
| + animationDoneTarget_ = target; |
| + animationDoneAction_ = action; |
| +} |
| - double opaqueness = animation_->GetCurrentValue(); |
| +- (void)setClickTarget:(id)target withAction:(SEL)action { |
| + clickTarget_ = target; |
| + clickAction_ = action; |
| +} |
| + |
| +- (void)drawRect:(NSRect)dirtyRect { |
| + NSImage* image = ([self hoverState] == kHoverStateNone || ![self isEnabled]) ? |
| + [self image] : affordanceImage_.get(); |
| + if (!image) |
| + return; |
| + NSRect imageRect = NSZeroRect; |
| + imageRect.size = [image size]; |
| + NSRect destRect = [self bounds]; |
| + destRect.origin.y = |
| + floor((NSHeight(destRect) / 2) - (NSHeight(imageRect) / 2)); |
| + destRect.size = imageRect.size; |
| + double opaqueness = |
| + fadeAnimation_ ? fadeAnimation_->GetCurrentValue() : 1.0; |
| if (mediaState_ == TAB_MEDIA_STATE_NONE) |
| opaqueness = 1.0 - opaqueness; // Fading out, not in. |
| + [image drawInRect:destRect |
| + fromRect:imageRect |
| + operation:NSCompositeSourceOver |
| + fraction:opaqueness |
| + respectFlipped:YES |
| + hints:nil]; |
| +} |
| - [[self image] drawInRect:[self bounds] |
| - fromRect:NSZeroRect |
| - operation:NSCompositeSourceOver |
| - fraction:opaqueness]; |
| +// When disabled, the superview should receive all mouse events. |
| +- (NSView*)hitTest:(NSPoint)aPoint { |
| + if ([self isEnabled] && ![self isHidden]) |
| + return [super hitTest:aPoint]; |
| + else |
| + return nil; |
| } |
| -- (void)disableAnimations { |
| - delegate_.reset(); |
| +- (void)handleClick:(id)sender { |
| + using base::UserMetricsAction; |
| + |
| + if (mediaState_ == TAB_MEDIA_STATE_AUDIO_PLAYING) |
| + content::RecordAction(UserMetricsAction("MediaIndicatorButton_Mute")); |
| + else if (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING) |
| + content::RecordAction(UserMetricsAction("MediaIndicatorButton_Unmute")); |
| + else |
| + NOTREACHED(); |
| + |
| + [clickTarget_ performSelector:clickAction_ withObject:self]; |
| } |
| @end |