OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "chrome/browser/ui/cocoa/tabs/media_indicator_view.h" | 5 #import "chrome/browser/ui/cocoa/tabs/media_indicator_button.h" |
6 | 6 |
| 7 #include "base/logging.h" |
| 8 #include "base/thread_task_runner_handle.h" |
| 9 #include "content/public/browser/user_metrics.h" |
7 #include "ui/gfx/animation/animation.h" | 10 #include "ui/gfx/animation/animation.h" |
8 #include "ui/gfx/animation/animation_delegate.h" | 11 #include "ui/gfx/animation/animation_delegate.h" |
9 #include "ui/gfx/image/image.h" | 12 #include "ui/gfx/image/image.h" |
10 | 13 |
11 class MediaIndicatorViewAnimationDelegate : public gfx::AnimationDelegate { | 14 @implementation MediaIndicatorButton |
| 15 |
| 16 class FadeAnimationDelegate : public gfx::AnimationDelegate { |
12 public: | 17 public: |
13 MediaIndicatorViewAnimationDelegate(NSView* view, | 18 explicit FadeAnimationDelegate(MediaIndicatorButton* button) |
14 TabMediaState* mediaState, | 19 : button_(button) {} |
15 TabMediaState* animatingMediaState) | 20 ~FadeAnimationDelegate() override {} |
16 : view_(view), mediaState_(mediaState), | |
17 animatingMediaState_(animatingMediaState), | |
18 doneCallbackObject_(nil), doneCallbackSelector_(nil) {} | |
19 ~MediaIndicatorViewAnimationDelegate() override {} | |
20 | 21 |
21 void SetAnimationDoneCallback(id anObject, SEL selector) { | 22 private: |
22 doneCallbackObject_ = anObject; | 23 // gfx::AnimationDelegate implementation. |
23 doneCallbackSelector_ = selector; | 24 void AnimationProgressed(const gfx::Animation* animation) override { |
| 25 [button_ setNeedsDisplay:YES]; |
24 } | 26 } |
25 | 27 |
26 void AnimationEnded(const gfx::Animation* animation) override { | |
27 *animatingMediaState_ = *mediaState_; | |
28 [view_ setNeedsDisplay:YES]; | |
29 [doneCallbackObject_ performSelector:doneCallbackSelector_]; | |
30 } | |
31 void AnimationProgressed(const gfx::Animation* animation) override { | |
32 [view_ setNeedsDisplay:YES]; | |
33 } | |
34 void AnimationCanceled(const gfx::Animation* animation) override { | 28 void AnimationCanceled(const gfx::Animation* animation) override { |
35 AnimationEnded(animation); | 29 AnimationEnded(animation); |
36 } | 30 } |
37 | 31 |
38 private: | 32 void AnimationEnded(const gfx::Animation* animation) override { |
39 NSView* const view_; | 33 button_->showingMediaState_ = button_->mediaState_; |
40 TabMediaState* const mediaState_; | 34 [button_ setNeedsDisplay:YES]; |
41 TabMediaState* const animatingMediaState_; | 35 [button_->animationDoneTarget_ |
| 36 performSelector:button_->animationDoneAction_]; |
| 37 } |
42 | 38 |
43 id doneCallbackObject_; | 39 MediaIndicatorButton* const button_; |
44 SEL doneCallbackSelector_; | 40 |
| 41 DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate); |
45 }; | 42 }; |
46 | 43 |
47 @implementation MediaIndicatorView | 44 @synthesize showingMediaState = showingMediaState_; |
48 | |
49 @synthesize mediaState = mediaState_; | |
50 @synthesize animatingMediaState = animatingMediaState_; | |
51 | 45 |
52 - (id)init { | 46 - (id)init { |
53 if ((self = [super initWithFrame:NSZeroRect])) { | 47 if ((self = [super initWithFrame:NSZeroRect])) { |
54 mediaState_ = animatingMediaState_ = TAB_MEDIA_STATE_NONE; | 48 mediaState_ = TAB_MEDIA_STATE_NONE; |
55 delegate_.reset(new MediaIndicatorViewAnimationDelegate( | 49 showingMediaState_ = TAB_MEDIA_STATE_NONE; |
56 self, &mediaState_, &animatingMediaState_)); | 50 [self setEnabled:NO]; |
| 51 [super setTarget:self]; |
| 52 [super setAction:@selector(handleClick:)]; |
57 } | 53 } |
58 return self; | 54 return self; |
59 } | 55 } |
60 | 56 |
61 - (void)updateIndicator:(TabMediaState)mediaState { | 57 - (void)removeFromSuperview { |
62 if (mediaState == mediaState_) | 58 fadeAnimation_.reset(); |
| 59 [super removeFromSuperview]; |
| 60 } |
| 61 |
| 62 - (void)transitionToMediaState:(TabMediaState)nextState { |
| 63 if (nextState == mediaState_) |
63 return; | 64 return; |
64 | 65 |
65 mediaState_ = mediaState; | 66 if (nextState != TAB_MEDIA_STATE_NONE) { |
66 animation_.reset(); | 67 [self setImage:chrome::GetTabMediaIndicatorImage(nextState).ToNSImage()]; |
67 | 68 affordanceImage_.reset( |
68 // Prepare this view if the new TabMediaState is an active one. | 69 [chrome::GetTabMediaIndicatorAffordanceImage(nextState).ToNSImage() |
69 if (mediaState_ != TAB_MEDIA_STATE_NONE) { | 70 retain]); |
70 animatingMediaState_ = mediaState_; | |
71 NSImage* const image = | |
72 chrome::GetTabMediaIndicatorImage(mediaState_).ToNSImage(); | |
73 NSRect frame = [self frame]; | |
74 frame.size = [image size]; | |
75 [self setFrame:frame]; | |
76 [self setImage:image]; | |
77 } | 71 } |
78 | 72 |
79 // If the animation delegate is missing, that means animations were disabled | 73 if ((mediaState_ == TAB_MEDIA_STATE_AUDIO_PLAYING && |
80 // for testing; so, go directly to animating completion state. | 74 nextState == TAB_MEDIA_STATE_AUDIO_MUTING) || |
81 if (!delegate_) { | 75 (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING && |
82 animatingMediaState_ = mediaState_; | 76 nextState == TAB_MEDIA_STATE_AUDIO_PLAYING) || |
83 return; | 77 (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING && |
| 78 nextState == TAB_MEDIA_STATE_NONE)) { |
| 79 // Instant user feedback: No fade animation. |
| 80 showingMediaState_ = nextState; |
| 81 fadeAnimation_.reset(); |
| 82 } else { |
| 83 if (nextState == TAB_MEDIA_STATE_NONE) |
| 84 showingMediaState_ = mediaState_; // Fading-out indicator. |
| 85 else |
| 86 showingMediaState_ = nextState; // Fading-in to next indicator. |
| 87 // gfx::Animation requires a task runner is available for the current |
| 88 // thread. Generally, only certain unit tests would not instantiate a task |
| 89 // runner. |
| 90 if (base::ThreadTaskRunnerHandle::IsSet()) { |
| 91 fadeAnimation_ = chrome::CreateTabMediaIndicatorFadeAnimation(nextState); |
| 92 if (!fadeAnimationDelegate_) |
| 93 fadeAnimationDelegate_.reset(new FadeAnimationDelegate(self)); |
| 94 fadeAnimation_->set_delegate(fadeAnimationDelegate_.get()); |
| 95 fadeAnimation_->Start(); |
| 96 } |
84 } | 97 } |
85 | 98 |
86 animation_ = chrome::CreateTabMediaIndicatorFadeAnimation(mediaState_); | 99 [self setEnabled:(chrome::IsTabAudioMutingFeatureEnabled() && |
87 animation_->set_delegate(delegate_.get()); | 100 (nextState == TAB_MEDIA_STATE_AUDIO_PLAYING || |
88 animation_->Start(); | 101 nextState == TAB_MEDIA_STATE_AUDIO_MUTING))]; |
| 102 |
| 103 // An indicator state change should be made visible immediately, instead of |
| 104 // the user being surprised when their mouse leaves the button. |
| 105 if ([self hoverState] == kHoverStateMouseOver) |
| 106 [self setHoverState:kHoverStateNone]; |
| 107 |
| 108 mediaState_ = nextState; |
| 109 |
| 110 [self setNeedsDisplay:YES]; |
89 } | 111 } |
90 | 112 |
91 - (void)setAnimationDoneCallbackObject:(id)anObject withSelector:(SEL)selector { | 113 - (void)setTarget:(id)aTarget { |
92 if (delegate_) | 114 NOTREACHED(); // See class-level comments. |
93 delegate_->SetAnimationDoneCallback(anObject, selector); | |
94 } | 115 } |
95 | 116 |
96 - (void)drawRect:(NSRect)rect { | 117 - (void)setAction:(SEL)anAction { |
97 if (!animation_) | 118 NOTREACHED(); // See class-level comments. |
| 119 } |
| 120 |
| 121 - (void)setAnimationDoneTarget:(id)target withAction:(SEL)action { |
| 122 animationDoneTarget_ = target; |
| 123 animationDoneAction_ = action; |
| 124 } |
| 125 |
| 126 - (void)setClickTarget:(id)target withAction:(SEL)action { |
| 127 clickTarget_ = target; |
| 128 clickAction_ = action; |
| 129 } |
| 130 |
| 131 - (void)drawRect:(NSRect)dirtyRect { |
| 132 NSImage* image = ([self hoverState] == kHoverStateNone || ![self isEnabled]) ? |
| 133 [self image] : affordanceImage_.get(); |
| 134 if (!image) |
98 return; | 135 return; |
99 | 136 NSRect imageRect = NSZeroRect; |
100 double opaqueness = animation_->GetCurrentValue(); | 137 imageRect.size = [image size]; |
| 138 NSRect destRect = [self bounds]; |
| 139 destRect.origin.y = |
| 140 floor((NSHeight(destRect) / 2) - (NSHeight(imageRect) / 2)); |
| 141 destRect.size = imageRect.size; |
| 142 double opaqueness = |
| 143 fadeAnimation_ ? fadeAnimation_->GetCurrentValue() : 1.0; |
101 if (mediaState_ == TAB_MEDIA_STATE_NONE) | 144 if (mediaState_ == TAB_MEDIA_STATE_NONE) |
102 opaqueness = 1.0 - opaqueness; // Fading out, not in. | 145 opaqueness = 1.0 - opaqueness; // Fading out, not in. |
103 | 146 [image drawInRect:destRect |
104 [[self image] drawInRect:[self bounds] | 147 fromRect:imageRect |
105 fromRect:NSZeroRect | 148 operation:NSCompositeSourceOver |
106 operation:NSCompositeSourceOver | 149 fraction:opaqueness |
107 fraction:opaqueness]; | 150 respectFlipped:YES |
| 151 hints:nil]; |
108 } | 152 } |
109 | 153 |
110 - (void)disableAnimations { | 154 // When disabled, the superview should receive all mouse events. |
111 delegate_.reset(); | 155 - (NSView*)hitTest:(NSPoint)aPoint { |
| 156 if ([self isEnabled] && ![self isHidden]) |
| 157 return [super hitTest:aPoint]; |
| 158 else |
| 159 return nil; |
| 160 } |
| 161 |
| 162 - (void)handleClick:(id)sender { |
| 163 using base::UserMetricsAction; |
| 164 |
| 165 if (mediaState_ == TAB_MEDIA_STATE_AUDIO_PLAYING) |
| 166 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Mute")); |
| 167 else if (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING) |
| 168 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Unmute")); |
| 169 else |
| 170 NOTREACHED(); |
| 171 |
| 172 [clickTarget_ performSelector:clickAction_ withObject:self]; |
112 } | 173 } |
113 | 174 |
114 @end | 175 @end |
OLD | NEW |