MacViews: Use Mac's "Constrained Window Button" style for Button::STYLE_BUTTON LabelB...
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / media_indicator_button.cc
blobe9e8d0a4849047d8d8c437e3915804c2de2f21c4
1 // Copyright 2014 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 #include "chrome/browser/ui/views/tabs/media_indicator_button.h"
7 #include "chrome/browser/ui/views/tabs/tab.h"
8 #include "chrome/browser/ui/views/tabs/tab_controller.h"
9 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
10 #include "content/public/browser/user_metrics.h"
11 #include "ui/gfx/animation/animation_delegate.h"
12 #include "ui/gfx/canvas.h"
13 #include "ui/gfx/image/image.h"
15 using base::UserMetricsAction;
17 namespace {
19 // The minimum required click-to-select area of an inactive Tab before allowing
20 // the click-to-mute functionality to be enabled. These values are in terms of
21 // some percentage of the MediaIndicatorButton's width. See comments in
22 // UpdateEnabledForMuteToggle().
23 const int kMinMouseSelectableAreaPercent = 250;
24 const int kMinGestureSelectableAreaPercent = 400;
26 } // namespace
28 const char MediaIndicatorButton::kViewClassName[] = "MediaIndicatorButton";
30 class MediaIndicatorButton::FadeAnimationDelegate
31 : public gfx::AnimationDelegate {
32 public:
33 explicit FadeAnimationDelegate(MediaIndicatorButton* button)
34 : button_(button) {}
35 ~FadeAnimationDelegate() override {}
37 private:
38 // gfx::AnimationDelegate
39 void AnimationProgressed(const gfx::Animation* animation) override {
40 button_->SchedulePaint();
43 void AnimationCanceled(const gfx::Animation* animation) override {
44 button_->showing_media_state_ = button_->media_state_;
45 button_->SchedulePaint();
48 void AnimationEnded(const gfx::Animation* animation) override {
49 button_->showing_media_state_ = button_->media_state_;
50 button_->SchedulePaint();
53 MediaIndicatorButton* const button_;
55 DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate);
58 MediaIndicatorButton::MediaIndicatorButton(Tab* parent_tab)
59 : views::ImageButton(NULL),
60 parent_tab_(parent_tab),
61 media_state_(TAB_MEDIA_STATE_NONE),
62 showing_media_state_(TAB_MEDIA_STATE_NONE) {
63 DCHECK(parent_tab_);
64 SetEventTargeter(
65 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
68 MediaIndicatorButton::~MediaIndicatorButton() {}
70 void MediaIndicatorButton::TransitionToMediaState(TabMediaState next_state) {
71 if (next_state == media_state_)
72 return;
74 if (next_state != TAB_MEDIA_STATE_NONE) {
75 const gfx::ImageSkia* const indicator_image =
76 chrome::GetTabMediaIndicatorImage(next_state).ToImageSkia();
77 SetImage(views::CustomButton::STATE_NORMAL, indicator_image);
78 SetImage(views::CustomButton::STATE_DISABLED, indicator_image);
79 const gfx::ImageSkia* const affordance_image =
80 chrome::GetTabMediaIndicatorAffordanceImage(next_state).ToImageSkia();
81 SetImage(views::CustomButton::STATE_HOVERED, affordance_image);
82 SetImage(views::CustomButton::STATE_PRESSED, affordance_image);
85 if ((media_state_ == TAB_MEDIA_STATE_AUDIO_PLAYING &&
86 next_state == TAB_MEDIA_STATE_AUDIO_MUTING) ||
87 (media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING &&
88 next_state == TAB_MEDIA_STATE_AUDIO_PLAYING) ||
89 (media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING &&
90 next_state == TAB_MEDIA_STATE_NONE)) {
91 // Instant user feedback: No fade animation.
92 showing_media_state_ = next_state;
93 fade_animation_.reset();
94 } else {
95 if (next_state == TAB_MEDIA_STATE_NONE)
96 showing_media_state_ = media_state_; // Fading-out indicator.
97 else
98 showing_media_state_ = next_state; // Fading-in to next indicator.
99 fade_animation_ = chrome::CreateTabMediaIndicatorFadeAnimation(next_state);
100 if (!fade_animation_delegate_)
101 fade_animation_delegate_.reset(new FadeAnimationDelegate(this));
102 fade_animation_->set_delegate(fade_animation_delegate_.get());
103 fade_animation_->Start();
106 media_state_ = next_state;
108 UpdateEnabledForMuteToggle();
110 // An indicator state change should be made visible immediately, instead of
111 // the user being surprised when their mouse leaves the button.
112 if (state() == views::CustomButton::STATE_HOVERED) {
113 SetState(enabled() ? views::CustomButton::STATE_NORMAL :
114 views::CustomButton::STATE_DISABLED);
117 // Note: The calls to SetImage(), SetEnabled(), and SetState() above will call
118 // SchedulePaint() if necessary.
121 void MediaIndicatorButton::UpdateEnabledForMuteToggle() {
122 bool enable = chrome::IsTabAudioMutingFeatureEnabled() &&
123 (media_state_ == TAB_MEDIA_STATE_AUDIO_PLAYING ||
124 media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING);
126 // If the tab is not the currently-active tab, make sure it is wide enough
127 // before enabling click-to-mute. This ensures that there is enough click
128 // area for the user to activate a tab rather than unintentionally muting it.
129 // Note that IsTriggerableEvent() is also overridden to provide an even wider
130 // requirement for tap gestures.
131 if (enable && !GetTab()->IsActive()) {
132 const int required_width = width() * kMinMouseSelectableAreaPercent / 100;
133 enable = (GetTab()->GetWidthOfLargestSelectableRegion() >= required_width);
136 SetEnabled(enable);
139 const char* MediaIndicatorButton::GetClassName() const {
140 return kViewClassName;
143 views::View* MediaIndicatorButton::GetTooltipHandlerForPoint(
144 const gfx::Point& point) {
145 return NULL; // Tab (the parent View) provides the tooltip.
148 bool MediaIndicatorButton::OnMousePressed(const ui::MouseEvent& event) {
149 // Do not handle this mouse event when anything but the left mouse button is
150 // pressed or when any modifier keys are being held down. Instead, the Tab
151 // should react (e.g., middle-click for close, right-click for context menu).
152 if (event.flags() != ui::EF_LEFT_MOUSE_BUTTON) {
153 if (state_ != views::CustomButton::STATE_DISABLED)
154 SetState(views::CustomButton::STATE_NORMAL); // Turn off hover.
155 return false; // Event to be handled by Tab.
157 return ImageButton::OnMousePressed(event);
160 bool MediaIndicatorButton::OnMouseDragged(const ui::MouseEvent& event) {
161 const ButtonState previous_state = state();
162 const bool ret = ImageButton::OnMouseDragged(event);
163 if (previous_state != views::CustomButton::STATE_NORMAL &&
164 state() == views::CustomButton::STATE_NORMAL)
165 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Dragged"));
166 return ret;
169 void MediaIndicatorButton::OnMouseEntered(const ui::MouseEvent& event) {
170 // If any modifier keys are being held down, do not turn on hover.
171 if (state_ != views::CustomButton::STATE_DISABLED &&
172 event.flags() != ui::EF_NONE) {
173 SetState(views::CustomButton::STATE_NORMAL);
174 return;
176 ImageButton::OnMouseEntered(event);
179 void MediaIndicatorButton::OnMouseMoved(const ui::MouseEvent& event) {
180 // If any modifier keys are being held down, turn off hover.
181 if (state_ != views::CustomButton::STATE_DISABLED &&
182 event.flags() != ui::EF_NONE) {
183 SetState(views::CustomButton::STATE_NORMAL);
184 return;
186 ImageButton::OnMouseMoved(event);
189 void MediaIndicatorButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
190 UpdateEnabledForMuteToggle();
193 void MediaIndicatorButton::OnPaint(gfx::Canvas* canvas) {
194 double opaqueness =
195 fade_animation_ ? fade_animation_->GetCurrentValue() : 1.0;
196 if (media_state_ == TAB_MEDIA_STATE_NONE)
197 opaqueness = 1.0 - opaqueness; // Fading out, not in.
198 if (opaqueness < 1.0)
199 canvas->SaveLayerAlpha(opaqueness * SK_AlphaOPAQUE);
200 ImageButton::OnPaint(canvas);
201 if (opaqueness < 1.0)
202 canvas->Restore();
205 bool MediaIndicatorButton::DoesIntersectRect(const views::View* target,
206 const gfx::Rect& rect) const {
207 // If this button is not enabled, Tab (the parent View) handles all mouse
208 // events.
209 return enabled() &&
210 views::ViewTargeterDelegate::DoesIntersectRect(target, rect);
213 void MediaIndicatorButton::NotifyClick(const ui::Event& event) {
214 if (media_state_ == TAB_MEDIA_STATE_AUDIO_PLAYING)
215 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Mute"));
216 else if (media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING)
217 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Unmute"));
218 else
219 NOTREACHED();
221 GetTab()->controller()->ToggleTabAudioMute(GetTab());
224 bool MediaIndicatorButton::IsTriggerableEvent(const ui::Event& event) {
225 // For mouse events, only trigger on the left mouse button and when no
226 // modifier keys are being held down.
227 if (event.IsMouseEvent() && event.flags() != ui::EF_LEFT_MOUSE_BUTTON)
228 return false;
230 // For gesture events on an inactive tab, require an even wider tab before
231 // click-to-mute can be triggered. See comments in
232 // UpdateEnabledForMuteToggle().
233 if (event.IsGestureEvent() && !GetTab()->IsActive()) {
234 const int required_width = width() * kMinGestureSelectableAreaPercent / 100;
235 if (GetTab()->GetWidthOfLargestSelectableRegion() < required_width)
236 return false;
239 return views::ImageButton::IsTriggerableEvent(event);
242 Tab* MediaIndicatorButton::GetTab() const {
243 DCHECK_EQ(static_cast<views::View*>(parent_tab_), parent());
244 return parent_tab_;