Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: chrome/browser/ui/cocoa/tabs/media_indicator_button.mm

Issue 688523002: [Cocoa] Tab audio mute control, behind a switch (off by default). (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Prevent TabStripController from unconditionally causing creation of MediaIndicatorButton. Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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)) {
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
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
OLDNEW
« no previous file with comments | « chrome/browser/ui/cocoa/tabs/media_indicator_button.h ('k') | chrome/browser/ui/cocoa/tabs/media_indicator_button_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698