OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #import "chrome/browser/ui/cocoa/tabs/media_indicator_button_cocoa.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/mac/foundation_util.h" | |
9 #include "base/macros.h" | |
10 #include "base/thread_task_runner_handle.h" | |
11 #import "chrome/browser/ui/cocoa/tabs/tab_view.h" | |
12 #include "content/public/browser/user_metrics.h" | |
13 #include "ui/gfx/animation/animation.h" | |
14 #include "ui/gfx/animation/animation_delegate.h" | |
15 #include "ui/gfx/image/image.h" | |
16 | |
17 namespace { | |
18 | |
19 // The minimum required click-to-select area of an inactive tab before allowing | |
20 // the click-to-mute functionality to be enabled. This value is in terms of | |
21 // some percentage of the MediaIndicatorButton's width. See comments in the | |
22 // updateEnabledForMuteToggle method. | |
23 const int kMinMouseSelectableAreaPercent = 250; | |
24 | |
25 } // namespace | |
26 | |
27 @implementation MediaIndicatorButton | |
28 | |
29 class FadeAnimationDelegate : public gfx::AnimationDelegate { | |
30 public: | |
31 explicit FadeAnimationDelegate(MediaIndicatorButton* button) | |
32 : button_(button) {} | |
33 ~FadeAnimationDelegate() override {} | |
34 | |
35 private: | |
36 // gfx::AnimationDelegate implementation. | |
37 void AnimationProgressed(const gfx::Animation* animation) override { | |
38 [button_ setNeedsDisplay:YES]; | |
39 } | |
40 | |
41 void AnimationCanceled(const gfx::Animation* animation) override { | |
42 AnimationEnded(animation); | |
43 } | |
44 | |
45 void AnimationEnded(const gfx::Animation* animation) override { | |
46 button_->showingMediaState_ = button_->mediaState_; | |
47 [button_ setNeedsDisplay:YES]; | |
48 [button_->animationDoneTarget_ | |
49 performSelector:button_->animationDoneAction_]; | |
50 } | |
51 | |
52 MediaIndicatorButton* const button_; | |
53 | |
54 DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate); | |
55 }; | |
56 | |
57 @synthesize showingMediaState = showingMediaState_; | |
58 | |
59 - (id)init { | |
60 if ((self = [super initWithFrame:NSZeroRect])) { | |
61 mediaState_ = TAB_MEDIA_STATE_NONE; | |
62 showingMediaState_ = TAB_MEDIA_STATE_NONE; | |
63 [self setEnabled:NO]; | |
64 [super setTarget:self]; | |
65 [super setAction:@selector(handleClick:)]; | |
66 } | |
67 return self; | |
68 } | |
69 | |
70 - (void)removeFromSuperview { | |
71 fadeAnimation_.reset(); | |
72 [super removeFromSuperview]; | |
73 } | |
74 | |
75 - (void)transitionToMediaState:(TabMediaState)nextState { | |
76 if (nextState == mediaState_) | |
77 return; | |
78 | |
79 if (nextState != TAB_MEDIA_STATE_NONE) { | |
80 [self | |
81 setImage:chrome::GetTabMediaIndicatorImage(nextState, 0).ToNSImage()]; | |
82 affordanceImage_.reset( | |
83 [chrome::GetTabMediaIndicatorAffordanceImage(nextState, 0) | |
84 .ToNSImage() retain]); | |
85 } | |
86 | |
87 if ((mediaState_ == TAB_MEDIA_STATE_AUDIO_PLAYING && | |
88 nextState == TAB_MEDIA_STATE_AUDIO_MUTING) || | |
89 (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING && | |
90 nextState == TAB_MEDIA_STATE_AUDIO_PLAYING) || | |
91 (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING && | |
92 nextState == TAB_MEDIA_STATE_NONE)) { | |
93 // Instant user feedback: No fade animation. | |
94 showingMediaState_ = nextState; | |
95 fadeAnimation_.reset(); | |
96 } else { | |
97 if (nextState == TAB_MEDIA_STATE_NONE) | |
98 showingMediaState_ = mediaState_; // Fading-out indicator. | |
99 else | |
100 showingMediaState_ = nextState; // Fading-in to next indicator. | |
101 // gfx::Animation requires a task runner is available for the current | |
102 // thread. Generally, only certain unit tests would not instantiate a task | |
103 // runner. | |
104 if (base::ThreadTaskRunnerHandle::IsSet()) { | |
105 fadeAnimation_ = chrome::CreateTabMediaIndicatorFadeAnimation(nextState); | |
106 if (!fadeAnimationDelegate_) | |
107 fadeAnimationDelegate_.reset(new FadeAnimationDelegate(self)); | |
108 fadeAnimation_->set_delegate(fadeAnimationDelegate_.get()); | |
109 fadeAnimation_->Start(); | |
110 } | |
111 } | |
112 | |
113 mediaState_ = nextState; | |
114 | |
115 [self updateEnabledForMuteToggle]; | |
116 | |
117 // An indicator state change should be made visible immediately, instead of | |
118 // the user being surprised when their mouse leaves the button. | |
119 if ([self hoverState] == kHoverStateMouseOver) | |
120 [self setHoverState:kHoverStateNone]; | |
121 | |
122 [self setNeedsDisplay:YES]; | |
123 } | |
124 | |
125 - (void)setTarget:(id)aTarget { | |
126 NOTREACHED(); // See class-level comments. | |
127 } | |
128 | |
129 - (void)setAction:(SEL)anAction { | |
130 NOTREACHED(); // See class-level comments. | |
131 } | |
132 | |
133 - (void)setAnimationDoneTarget:(id)target withAction:(SEL)action { | |
134 animationDoneTarget_ = target; | |
135 animationDoneAction_ = action; | |
136 } | |
137 | |
138 - (void)setClickTarget:(id)target withAction:(SEL)action { | |
139 clickTarget_ = target; | |
140 clickAction_ = action; | |
141 } | |
142 | |
143 - (void)mouseDown:(NSEvent*)theEvent { | |
144 // Do not handle this left-button mouse event if any modifier keys are being | |
145 // held down. Instead, the Tab should react (e.g., selection or drag start). | |
146 if ([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) { | |
147 [self setHoverState:kHoverStateNone]; // Turn off hover. | |
148 [[self nextResponder] mouseDown:theEvent]; | |
149 return; | |
150 } | |
151 [super mouseDown:theEvent]; | |
152 } | |
153 | |
154 - (void)mouseEntered:(NSEvent*)theEvent { | |
155 // If any modifier keys are being held down, do not turn on hover. | |
156 if ([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) { | |
157 [self setHoverState:kHoverStateNone]; | |
158 return; | |
159 } | |
160 [super mouseEntered:theEvent]; | |
161 } | |
162 | |
163 - (void)mouseMoved:(NSEvent*)theEvent { | |
164 // If any modifier keys are being held down, turn off hover. | |
165 if ([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask) { | |
166 [self setHoverState:kHoverStateNone]; | |
167 return; | |
168 } | |
169 [super mouseMoved:theEvent]; | |
170 } | |
171 | |
172 - (void)rightMouseDown:(NSEvent*)theEvent { | |
173 // All right-button mouse events should be handled by the Tab. | |
174 [self setHoverState:kHoverStateNone]; // Turn off hover. | |
175 [[self nextResponder] rightMouseDown:theEvent]; | |
176 } | |
177 | |
178 - (void)drawRect:(NSRect)dirtyRect { | |
179 NSImage* image = ([self hoverState] == kHoverStateNone || ![self isEnabled]) ? | |
180 [self image] : affordanceImage_.get(); | |
181 if (!image) | |
182 return; | |
183 NSRect imageRect = NSZeroRect; | |
184 imageRect.size = [image size]; | |
185 NSRect destRect = [self bounds]; | |
186 destRect.origin.y = | |
187 floor((NSHeight(destRect) / 2) - (NSHeight(imageRect) / 2)); | |
188 destRect.size = imageRect.size; | |
189 double opaqueness = | |
190 fadeAnimation_ ? fadeAnimation_->GetCurrentValue() : 1.0; | |
191 if (mediaState_ == TAB_MEDIA_STATE_NONE) | |
192 opaqueness = 1.0 - opaqueness; // Fading out, not in. | |
193 [image drawInRect:destRect | |
194 fromRect:imageRect | |
195 operation:NSCompositeSourceOver | |
196 fraction:opaqueness | |
197 respectFlipped:YES | |
198 hints:nil]; | |
199 } | |
200 | |
201 // When disabled, the superview should receive all mouse events. | |
202 - (NSView*)hitTest:(NSPoint)aPoint { | |
203 if ([self isEnabled] && ![self isHidden]) | |
204 return [super hitTest:aPoint]; | |
205 else | |
206 return nil; | |
207 } | |
208 | |
209 - (void)handleClick:(id)sender { | |
210 using base::UserMetricsAction; | |
211 | |
212 if (mediaState_ == TAB_MEDIA_STATE_AUDIO_PLAYING) | |
213 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Mute")); | |
214 else if (mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING) | |
215 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Unmute")); | |
216 else | |
217 NOTREACHED(); | |
218 | |
219 [clickTarget_ performSelector:clickAction_ withObject:self]; | |
220 } | |
221 | |
222 - (void)updateEnabledForMuteToggle { | |
223 BOOL enable = chrome::AreExperimentalMuteControlsEnabled() && | |
224 (mediaState_ == TAB_MEDIA_STATE_AUDIO_PLAYING || | |
225 mediaState_ == TAB_MEDIA_STATE_AUDIO_MUTING); | |
226 | |
227 // If the tab is not the currently-active tab, make sure it is wide enough | |
228 // before enabling click-to-mute. This ensures that there is enough click | |
229 // area for the user to activate a tab rather than unintentionally muting it. | |
230 TabView* const tabView = base::mac::ObjCCast<TabView>([self superview]); | |
231 if (enable && tabView && ([tabView state] != NSOnState)) { | |
232 const int requiredWidth = | |
233 NSWidth([self frame]) * kMinMouseSelectableAreaPercent / 100; | |
234 enable = ([tabView widthOfLargestSelectableRegion] >= requiredWidth); | |
235 } | |
236 | |
237 [self setEnabled:enable]; | |
238 } | |
239 | |
240 @end | |
OLD | NEW |