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