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
;
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;
28 const char MediaIndicatorButton::kViewClassName
[] = "MediaIndicatorButton";
30 class MediaIndicatorButton::FadeAnimationDelegate
31 : public gfx::AnimationDelegate
{
33 explicit FadeAnimationDelegate(MediaIndicatorButton
* button
)
35 ~FadeAnimationDelegate() override
{}
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
) {
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_
)
74 if (next_state
!= TAB_MEDIA_STATE_NONE
) {
75 ui::ThemeProvider
* tp
= GetThemeProvider();
76 gfx::ImageSkia indicator_image
=
77 chrome::GetTabMediaIndicatorImage(next_state
, tp
).AsImageSkia();
78 SetImage(views::CustomButton::STATE_NORMAL
, &indicator_image
);
79 SetImage(views::CustomButton::STATE_DISABLED
, &indicator_image
);
80 gfx::ImageSkia affordance_image
=
81 chrome::GetTabMediaIndicatorAffordanceImage(next_state
, tp
)
83 SetImage(views::CustomButton::STATE_HOVERED
, &affordance_image
);
84 SetImage(views::CustomButton::STATE_PRESSED
, &affordance_image
);
87 if ((media_state_
== TAB_MEDIA_STATE_AUDIO_PLAYING
&&
88 next_state
== TAB_MEDIA_STATE_AUDIO_MUTING
) ||
89 (media_state_
== TAB_MEDIA_STATE_AUDIO_MUTING
&&
90 next_state
== TAB_MEDIA_STATE_AUDIO_PLAYING
) ||
91 (media_state_
== TAB_MEDIA_STATE_AUDIO_MUTING
&&
92 next_state
== TAB_MEDIA_STATE_NONE
)) {
93 // Instant user feedback: No fade animation.
94 showing_media_state_
= next_state
;
95 fade_animation_
.reset();
97 if (next_state
== TAB_MEDIA_STATE_NONE
)
98 showing_media_state_
= media_state_
; // Fading-out indicator.
100 showing_media_state_
= next_state
; // Fading-in to next indicator.
101 fade_animation_
= chrome::CreateTabMediaIndicatorFadeAnimation(next_state
);
102 if (!fade_animation_delegate_
)
103 fade_animation_delegate_
.reset(new FadeAnimationDelegate(this));
104 fade_animation_
->set_delegate(fade_animation_delegate_
.get());
105 fade_animation_
->Start();
108 media_state_
= next_state
;
110 UpdateEnabledForMuteToggle();
112 // An indicator state change should be made visible immediately, instead of
113 // the user being surprised when their mouse leaves the button.
114 if (state() == views::CustomButton::STATE_HOVERED
) {
115 SetState(enabled() ? views::CustomButton::STATE_NORMAL
:
116 views::CustomButton::STATE_DISABLED
);
119 // Note: The calls to SetImage(), SetEnabled(), and SetState() above will call
120 // SchedulePaint() if necessary.
123 void MediaIndicatorButton::UpdateEnabledForMuteToggle() {
124 bool enable
= chrome::AreExperimentalMuteControlsEnabled() &&
125 (media_state_
== TAB_MEDIA_STATE_AUDIO_PLAYING
||
126 media_state_
== TAB_MEDIA_STATE_AUDIO_MUTING
);
128 // If the tab is not the currently-active tab, make sure it is wide enough
129 // before enabling click-to-mute. This ensures that there is enough click
130 // area for the user to activate a tab rather than unintentionally muting it.
131 // Note that IsTriggerableEvent() is also overridden to provide an even wider
132 // requirement for tap gestures.
133 if (enable
&& !GetTab()->IsActive()) {
134 const int required_width
= width() * kMinMouseSelectableAreaPercent
/ 100;
135 enable
= (GetTab()->GetWidthOfLargestSelectableRegion() >= required_width
);
141 const char* MediaIndicatorButton::GetClassName() const {
142 return kViewClassName
;
145 views::View
* MediaIndicatorButton::GetTooltipHandlerForPoint(
146 const gfx::Point
& point
) {
147 return NULL
; // Tab (the parent View) provides the tooltip.
150 bool MediaIndicatorButton::OnMousePressed(const ui::MouseEvent
& event
) {
151 // Do not handle this mouse event when anything but the left mouse button is
152 // pressed or when any modifier keys are being held down. Instead, the Tab
153 // should react (e.g., middle-click for close, right-click for context menu).
154 if (event
.flags() != ui::EF_LEFT_MOUSE_BUTTON
) {
155 if (state_
!= views::CustomButton::STATE_DISABLED
)
156 SetState(views::CustomButton::STATE_NORMAL
); // Turn off hover.
157 return false; // Event to be handled by Tab.
159 return ImageButton::OnMousePressed(event
);
162 bool MediaIndicatorButton::OnMouseDragged(const ui::MouseEvent
& event
) {
163 const ButtonState previous_state
= state();
164 const bool ret
= ImageButton::OnMouseDragged(event
);
165 if (previous_state
!= views::CustomButton::STATE_NORMAL
&&
166 state() == views::CustomButton::STATE_NORMAL
)
167 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Dragged"));
171 void MediaIndicatorButton::OnMouseEntered(const ui::MouseEvent
& event
) {
172 // If any modifier keys are being held down, do not turn on hover.
173 if (state_
!= views::CustomButton::STATE_DISABLED
&&
174 event
.flags() != ui::EF_NONE
) {
175 SetState(views::CustomButton::STATE_NORMAL
);
178 ImageButton::OnMouseEntered(event
);
181 void MediaIndicatorButton::OnMouseMoved(const ui::MouseEvent
& event
) {
182 // If any modifier keys are being held down, turn off hover.
183 if (state_
!= views::CustomButton::STATE_DISABLED
&&
184 event
.flags() != ui::EF_NONE
) {
185 SetState(views::CustomButton::STATE_NORMAL
);
188 ImageButton::OnMouseMoved(event
);
191 void MediaIndicatorButton::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
192 UpdateEnabledForMuteToggle();
195 void MediaIndicatorButton::OnPaint(gfx::Canvas
* canvas
) {
197 fade_animation_
? fade_animation_
->GetCurrentValue() : 1.0;
198 if (media_state_
== TAB_MEDIA_STATE_NONE
)
199 opaqueness
= 1.0 - opaqueness
; // Fading out, not in.
200 if (opaqueness
< 1.0)
201 canvas
->SaveLayerAlpha(opaqueness
* SK_AlphaOPAQUE
);
202 ImageButton::OnPaint(canvas
);
203 if (opaqueness
< 1.0)
207 bool MediaIndicatorButton::DoesIntersectRect(const views::View
* target
,
208 const gfx::Rect
& rect
) const {
209 // If this button is not enabled, Tab (the parent View) handles all mouse
212 views::ViewTargeterDelegate::DoesIntersectRect(target
, rect
);
215 void MediaIndicatorButton::NotifyClick(const ui::Event
& event
) {
216 if (media_state_
== TAB_MEDIA_STATE_AUDIO_PLAYING
)
217 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Mute"));
218 else if (media_state_
== TAB_MEDIA_STATE_AUDIO_MUTING
)
219 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Unmute"));
223 GetTab()->controller()->ToggleTabAudioMute(GetTab());
226 bool MediaIndicatorButton::IsTriggerableEvent(const ui::Event
& event
) {
227 // For mouse events, only trigger on the left mouse button and when no
228 // modifier keys are being held down.
229 if (event
.IsMouseEvent() && event
.flags() != ui::EF_LEFT_MOUSE_BUTTON
)
232 // For gesture events on an inactive tab, require an even wider tab before
233 // click-to-mute can be triggered. See comments in
234 // UpdateEnabledForMuteToggle().
235 if (event
.IsGestureEvent() && !GetTab()->IsActive()) {
236 const int required_width
= width() * kMinGestureSelectableAreaPercent
/ 100;
237 if (GetTab()->GetWidthOfLargestSelectableRegion() < required_width
)
241 return views::ImageButton::IsTriggerableEvent(event
);
244 Tab
* MediaIndicatorButton::GetTab() const {
245 DCHECK_EQ(static_cast<views::View
*>(parent_tab_
), parent());