OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/ui/views/tabs/media_indicator_button.h" | |
6 | |
7 #include "base/macros.h" | |
8 #include "chrome/browser/ui/views/tabs/tab.h" | |
9 #include "chrome/browser/ui/views/tabs/tab_controller.h" | |
10 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h" | |
11 #include "content/public/browser/user_metrics.h" | |
12 #include "ui/gfx/animation/animation_delegate.h" | |
13 #include "ui/gfx/canvas.h" | |
14 #include "ui/gfx/image/image.h" | |
15 | |
16 using base::UserMetricsAction; | |
17 | |
18 namespace { | |
19 | |
20 // The minimum required click-to-select area of an inactive Tab before allowing | |
21 // the click-to-mute functionality to be enabled. These values are in terms of | |
22 // some percentage of the MediaIndicatorButton's width. See comments in | |
23 // UpdateEnabledForMuteToggle(). | |
24 const int kMinMouseSelectableAreaPercent = 250; | |
25 const int kMinGestureSelectableAreaPercent = 400; | |
26 | |
27 // Returns true if either Shift or Control are being held down. In this case, | |
28 // mouse events are delegated to the Tab, to perform tab selection in the tab | |
29 // strip instead. | |
30 bool IsShiftOrControlDown(const ui::Event& event) { | |
31 return (event.flags() & (ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)) != 0; | |
32 } | |
33 | |
34 } // namespace | |
35 | |
36 const char MediaIndicatorButton::kViewClassName[] = "MediaIndicatorButton"; | |
37 | |
38 class MediaIndicatorButton::FadeAnimationDelegate | |
39 : public gfx::AnimationDelegate { | |
40 public: | |
41 explicit FadeAnimationDelegate(MediaIndicatorButton* button) | |
42 : button_(button) {} | |
43 ~FadeAnimationDelegate() override {} | |
44 | |
45 private: | |
46 // gfx::AnimationDelegate | |
47 void AnimationProgressed(const gfx::Animation* animation) override { | |
48 button_->SchedulePaint(); | |
49 } | |
50 | |
51 void AnimationCanceled(const gfx::Animation* animation) override { | |
52 AnimationEnded(animation); | |
53 } | |
54 | |
55 void AnimationEnded(const gfx::Animation* animation) override { | |
56 button_->showing_media_state_ = button_->media_state_; | |
57 button_->parent_tab_->MediaStateChanged(); | |
58 } | |
59 | |
60 MediaIndicatorButton* const button_; | |
61 | |
62 DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate); | |
63 }; | |
64 | |
65 MediaIndicatorButton::MediaIndicatorButton(Tab* parent_tab) | |
66 : views::ImageButton(NULL), | |
67 parent_tab_(parent_tab), | |
68 media_state_(TAB_MEDIA_STATE_NONE), | |
69 showing_media_state_(TAB_MEDIA_STATE_NONE) { | |
70 DCHECK(parent_tab_); | |
71 SetEventTargeter( | |
72 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); | |
73 } | |
74 | |
75 MediaIndicatorButton::~MediaIndicatorButton() {} | |
76 | |
77 void MediaIndicatorButton::TransitionToMediaState(TabMediaState next_state) { | |
78 if (next_state == media_state_) | |
79 return; | |
80 | |
81 TabMediaState previous_media_showing_state = showing_media_state_; | |
82 | |
83 if (next_state != TAB_MEDIA_STATE_NONE) | |
84 ResetImages(next_state); | |
85 | |
86 if ((media_state_ == TAB_MEDIA_STATE_AUDIO_PLAYING && | |
87 next_state == TAB_MEDIA_STATE_AUDIO_MUTING) || | |
88 (media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING && | |
89 next_state == TAB_MEDIA_STATE_AUDIO_PLAYING) || | |
90 (media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING && | |
91 next_state == TAB_MEDIA_STATE_NONE)) { | |
92 // Instant user feedback: No fade animation. | |
93 showing_media_state_ = next_state; | |
94 fade_animation_.reset(); | |
95 } else { | |
96 if (next_state == TAB_MEDIA_STATE_NONE) | |
97 showing_media_state_ = media_state_; // Fading-out indicator. | |
98 else | |
99 showing_media_state_ = next_state; // Fading-in to next indicator. | |
100 fade_animation_ = chrome::CreateTabMediaIndicatorFadeAnimation(next_state); | |
101 if (!fade_animation_delegate_) | |
102 fade_animation_delegate_.reset(new FadeAnimationDelegate(this)); | |
103 fade_animation_->set_delegate(fade_animation_delegate_.get()); | |
104 fade_animation_->Start(); | |
105 } | |
106 | |
107 media_state_ = next_state; | |
108 | |
109 if (previous_media_showing_state != showing_media_state_) | |
110 parent_tab_->MediaStateChanged(); | |
111 | |
112 UpdateEnabledForMuteToggle(); | |
113 | |
114 // An indicator state change should be made visible immediately, instead of | |
115 // the user being surprised when their mouse leaves the button. | |
116 if (state() == views::CustomButton::STATE_HOVERED) { | |
117 SetState(enabled() ? views::CustomButton::STATE_NORMAL : | |
118 views::CustomButton::STATE_DISABLED); | |
119 } | |
120 | |
121 // Note: The calls to SetImage(), SetEnabled(), and SetState() above will call | |
122 // SchedulePaint() if necessary. | |
123 } | |
124 | |
125 void MediaIndicatorButton::UpdateEnabledForMuteToggle() { | |
126 bool enable = chrome::AreExperimentalMuteControlsEnabled() && | |
127 (media_state_ == TAB_MEDIA_STATE_AUDIO_PLAYING || | |
128 media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING); | |
129 | |
130 // If the tab is not the currently-active tab, make sure it is wide enough | |
131 // before enabling click-to-mute. This ensures that there is enough click | |
132 // area for the user to activate a tab rather than unintentionally muting it. | |
133 // Note that IsTriggerableEvent() is also overridden to provide an even wider | |
134 // requirement for tap gestures. | |
135 if (enable && !GetTab()->IsActive()) { | |
136 const int required_width = width() * kMinMouseSelectableAreaPercent / 100; | |
137 enable = (GetTab()->GetWidthOfLargestSelectableRegion() >= required_width); | |
138 } | |
139 | |
140 SetEnabled(enable); | |
141 } | |
142 | |
143 void MediaIndicatorButton::OnParentTabButtonColorChanged() { | |
144 if (media_state_ == TAB_MEDIA_STATE_AUDIO_PLAYING || | |
145 media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING) | |
146 ResetImages(media_state_); | |
147 } | |
148 | |
149 const char* MediaIndicatorButton::GetClassName() const { | |
150 return kViewClassName; | |
151 } | |
152 | |
153 views::View* MediaIndicatorButton::GetTooltipHandlerForPoint( | |
154 const gfx::Point& point) { | |
155 return NULL; // Tab (the parent View) provides the tooltip. | |
156 } | |
157 | |
158 bool MediaIndicatorButton::OnMousePressed(const ui::MouseEvent& event) { | |
159 // Do not handle this mouse event when anything but the left mouse button is | |
160 // pressed or when any modifier keys are being held down. Instead, the Tab | |
161 // should react (e.g., middle-click for close, right-click for context menu). | |
162 if (!event.IsOnlyLeftMouseButton() || IsShiftOrControlDown(event)) { | |
163 if (state() != views::CustomButton::STATE_DISABLED) | |
164 SetState(views::CustomButton::STATE_NORMAL); // Turn off hover. | |
165 return false; // Event to be handled by Tab. | |
166 } | |
167 return ImageButton::OnMousePressed(event); | |
168 } | |
169 | |
170 bool MediaIndicatorButton::OnMouseDragged(const ui::MouseEvent& event) { | |
171 const ButtonState previous_state = state(); | |
172 const bool ret = ImageButton::OnMouseDragged(event); | |
173 if (previous_state != views::CustomButton::STATE_NORMAL && | |
174 state() == views::CustomButton::STATE_NORMAL) | |
175 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Dragged")); | |
176 return ret; | |
177 } | |
178 | |
179 void MediaIndicatorButton::OnMouseEntered(const ui::MouseEvent& event) { | |
180 // If any modifier keys are being held down, do not turn on hover. | |
181 if (state() != views::CustomButton::STATE_DISABLED && | |
182 IsShiftOrControlDown(event)) { | |
183 SetState(views::CustomButton::STATE_NORMAL); | |
184 return; | |
185 } | |
186 ImageButton::OnMouseEntered(event); | |
187 } | |
188 | |
189 void MediaIndicatorButton::OnMouseMoved(const ui::MouseEvent& event) { | |
190 // If any modifier keys are being held down, turn off hover. | |
191 if (state() != views::CustomButton::STATE_DISABLED && | |
192 IsShiftOrControlDown(event)) { | |
193 SetState(views::CustomButton::STATE_NORMAL); | |
194 return; | |
195 } | |
196 ImageButton::OnMouseMoved(event); | |
197 } | |
198 | |
199 void MediaIndicatorButton::OnBoundsChanged(const gfx::Rect& previous_bounds) { | |
200 UpdateEnabledForMuteToggle(); | |
201 } | |
202 | |
203 void MediaIndicatorButton::OnPaint(gfx::Canvas* canvas) { | |
204 double opaqueness = | |
205 fade_animation_ ? fade_animation_->GetCurrentValue() : 1.0; | |
206 if (media_state_ == TAB_MEDIA_STATE_NONE) | |
207 opaqueness = 1.0 - opaqueness; // Fading out, not in. | |
208 if (opaqueness < 1.0) | |
209 canvas->SaveLayerAlpha(opaqueness * SK_AlphaOPAQUE); | |
210 ImageButton::OnPaint(canvas); | |
211 if (opaqueness < 1.0) | |
212 canvas->Restore(); | |
213 } | |
214 | |
215 bool MediaIndicatorButton::DoesIntersectRect(const views::View* target, | |
216 const gfx::Rect& rect) const { | |
217 // If this button is not enabled, Tab (the parent View) handles all mouse | |
218 // events. | |
219 return enabled() && | |
220 views::ViewTargeterDelegate::DoesIntersectRect(target, rect); | |
221 } | |
222 | |
223 void MediaIndicatorButton::NotifyClick(const ui::Event& event) { | |
224 if (media_state_ == TAB_MEDIA_STATE_AUDIO_PLAYING) | |
225 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Mute")); | |
226 else if (media_state_ == TAB_MEDIA_STATE_AUDIO_MUTING) | |
227 content::RecordAction(UserMetricsAction("MediaIndicatorButton_Unmute")); | |
228 else | |
229 NOTREACHED(); | |
230 | |
231 GetTab()->controller()->ToggleTabAudioMute(GetTab()); | |
232 } | |
233 | |
234 bool MediaIndicatorButton::IsTriggerableEvent(const ui::Event& event) { | |
235 // For mouse events, only trigger on the left mouse button and when no | |
236 // modifier keys are being held down. | |
237 if (event.IsMouseEvent() && | |
238 (!static_cast<const ui::MouseEvent*>(&event)->IsOnlyLeftMouseButton() || | |
239 IsShiftOrControlDown(event))) | |
240 return false; | |
241 | |
242 // For gesture events on an inactive tab, require an even wider tab before | |
243 // click-to-mute can be triggered. See comments in | |
244 // UpdateEnabledForMuteToggle(). | |
245 if (event.IsGestureEvent() && !GetTab()->IsActive()) { | |
246 const int required_width = width() * kMinGestureSelectableAreaPercent / 100; | |
247 if (GetTab()->GetWidthOfLargestSelectableRegion() < required_width) | |
248 return false; | |
249 } | |
250 | |
251 return views::ImageButton::IsTriggerableEvent(event); | |
252 } | |
253 | |
254 Tab* MediaIndicatorButton::GetTab() const { | |
255 DCHECK_EQ(static_cast<views::View*>(parent_tab_), parent()); | |
256 return parent_tab_; | |
257 } | |
258 | |
259 void MediaIndicatorButton::ResetImages(TabMediaState state) { | |
260 SkColor color = parent_tab_->button_color(); | |
261 gfx::ImageSkia indicator_image = | |
262 chrome::GetTabMediaIndicatorImage(state, color).AsImageSkia(); | |
263 SetImage(views::CustomButton::STATE_NORMAL, &indicator_image); | |
264 SetImage(views::CustomButton::STATE_DISABLED, &indicator_image); | |
265 gfx::ImageSkia affordance_image = | |
266 chrome::GetTabMediaIndicatorAffordanceImage(state, color).AsImageSkia(); | |
267 SetImage(views::CustomButton::STATE_HOVERED, &affordance_image); | |
268 SetImage(views::CustomButton::STATE_PRESSED, &affordance_image); | |
269 } | |
OLD | NEW |