• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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 "ui/views/controls/button/custom_button.h"
6 
7 #include "ui/accessibility/ax_view_state.h"
8 #include "ui/events/event.h"
9 #include "ui/events/keycodes/keyboard_codes.h"
10 #include "ui/gfx/animation/throb_animation.h"
11 #include "ui/gfx/screen.h"
12 #include "ui/views/controls/button/blue_button.h"
13 #include "ui/views/controls/button/checkbox.h"
14 #include "ui/views/controls/button/image_button.h"
15 #include "ui/views/controls/button/label_button.h"
16 #include "ui/views/controls/button/menu_button.h"
17 #include "ui/views/controls/button/radio_button.h"
18 #include "ui/views/controls/button/text_button.h"
19 #include "ui/views/widget/widget.h"
20 
21 namespace views {
22 
23 // How long the hover animation takes if uninterrupted.
24 static const int kHoverFadeDurationMs = 150;
25 
26 // static
27 const char CustomButton::kViewClassName[] = "CustomButton";
28 
29 ////////////////////////////////////////////////////////////////////////////////
30 // CustomButton, public:
31 
32 // static
AsCustomButton(const views::View * view)33 const CustomButton* CustomButton::AsCustomButton(const views::View* view) {
34   return AsCustomButton(const_cast<views::View*>(view));
35 }
36 
AsCustomButton(views::View * view)37 CustomButton* CustomButton::AsCustomButton(views::View* view) {
38   if (view) {
39     const char* classname = view->GetClassName();
40     if (!strcmp(classname, Checkbox::kViewClassName) ||
41         !strcmp(classname, CustomButton::kViewClassName) ||
42         !strcmp(classname, ImageButton::kViewClassName) ||
43         !strcmp(classname, LabelButton::kViewClassName) ||
44         !strcmp(classname, RadioButton::kViewClassName) ||
45         !strcmp(classname, MenuButton::kViewClassName) ||
46         !strcmp(classname, TextButton::kViewClassName)) {
47       return static_cast<CustomButton*>(view);
48     }
49   }
50   return NULL;
51 }
52 
~CustomButton()53 CustomButton::~CustomButton() {
54 }
55 
SetState(ButtonState state)56 void CustomButton::SetState(ButtonState state) {
57   if (state == state_)
58     return;
59 
60   if (animate_on_state_change_ &&
61       (!is_throbbing_ || !hover_animation_->is_animating())) {
62     is_throbbing_ = false;
63     if (state_ == STATE_NORMAL && state == STATE_HOVERED) {
64       // Button is hovered from a normal state, start hover animation.
65       hover_animation_->Show();
66     } else if ((state_ == STATE_HOVERED || state_ == STATE_PRESSED)
67           && state == STATE_NORMAL) {
68       // Button is returning to a normal state from hover, start hover
69       // fade animation.
70       hover_animation_->Hide();
71     } else {
72       hover_animation_->Stop();
73     }
74   }
75 
76   state_ = state;
77   StateChanged();
78   if (state_changed_delegate_.get())
79     state_changed_delegate_->StateChanged(state_);
80   SchedulePaint();
81 }
82 
StartThrobbing(int cycles_til_stop)83 void CustomButton::StartThrobbing(int cycles_til_stop) {
84   is_throbbing_ = true;
85   hover_animation_->StartThrobbing(cycles_til_stop);
86 }
87 
StopThrobbing()88 void CustomButton::StopThrobbing() {
89   if (hover_animation_->is_animating()) {
90     hover_animation_->Stop();
91     SchedulePaint();
92   }
93 }
94 
SetAnimationDuration(int duration)95 void CustomButton::SetAnimationDuration(int duration) {
96   hover_animation_->SetSlideDuration(duration);
97 }
98 
SetHotTracked(bool is_hot_tracked)99 void CustomButton::SetHotTracked(bool is_hot_tracked) {
100   if (state_ != STATE_DISABLED)
101     SetState(is_hot_tracked ? STATE_HOVERED : STATE_NORMAL);
102 
103   if (is_hot_tracked)
104     NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, true);
105 }
106 
IsHotTracked() const107 bool CustomButton::IsHotTracked() const {
108   return state_ == STATE_HOVERED;
109 }
110 
111 ////////////////////////////////////////////////////////////////////////////////
112 // CustomButton, View overrides:
113 
OnEnabledChanged()114 void CustomButton::OnEnabledChanged() {
115   if (enabled() ? (state_ != STATE_DISABLED) : (state_ == STATE_DISABLED))
116     return;
117 
118   if (enabled())
119     SetState(IsMouseHovered() ? STATE_HOVERED : STATE_NORMAL);
120   else
121     SetState(STATE_DISABLED);
122 }
123 
GetClassName() const124 const char* CustomButton::GetClassName() const {
125   return kViewClassName;
126 }
127 
OnMousePressed(const ui::MouseEvent & event)128 bool CustomButton::OnMousePressed(const ui::MouseEvent& event) {
129   if (state_ != STATE_DISABLED) {
130     if (ShouldEnterPushedState(event) && HitTestPoint(event.location()))
131       SetState(STATE_PRESSED);
132     if (request_focus_on_press_)
133       RequestFocus();
134   }
135   return true;
136 }
137 
OnMouseDragged(const ui::MouseEvent & event)138 bool CustomButton::OnMouseDragged(const ui::MouseEvent& event) {
139   if (state_ != STATE_DISABLED) {
140     if (HitTestPoint(event.location()))
141       SetState(ShouldEnterPushedState(event) ? STATE_PRESSED : STATE_HOVERED);
142     else
143       SetState(STATE_NORMAL);
144   }
145   return true;
146 }
147 
OnMouseReleased(const ui::MouseEvent & event)148 void CustomButton::OnMouseReleased(const ui::MouseEvent& event) {
149   if (state_ == STATE_DISABLED)
150     return;
151 
152   if (!HitTestPoint(event.location())) {
153     SetState(STATE_NORMAL);
154     return;
155   }
156 
157   SetState(STATE_HOVERED);
158   if (IsTriggerableEvent(event)) {
159     NotifyClick(event);
160     // NOTE: We may be deleted at this point (by the listener's notification
161     // handler).
162   }
163 }
164 
OnMouseCaptureLost()165 void CustomButton::OnMouseCaptureLost() {
166   // Starting a drag results in a MouseCaptureLost, we need to ignore it.
167   if (state_ != STATE_DISABLED && !InDrag())
168     SetState(STATE_NORMAL);
169 }
170 
OnMouseEntered(const ui::MouseEvent & event)171 void CustomButton::OnMouseEntered(const ui::MouseEvent& event) {
172   if (state_ != STATE_DISABLED)
173     SetState(STATE_HOVERED);
174 }
175 
OnMouseExited(const ui::MouseEvent & event)176 void CustomButton::OnMouseExited(const ui::MouseEvent& event) {
177   // Starting a drag results in a MouseExited, we need to ignore it.
178   if (state_ != STATE_DISABLED && !InDrag())
179     SetState(STATE_NORMAL);
180 }
181 
OnMouseMoved(const ui::MouseEvent & event)182 void CustomButton::OnMouseMoved(const ui::MouseEvent& event) {
183   if (state_ != STATE_DISABLED)
184     SetState(HitTestPoint(event.location()) ? STATE_HOVERED : STATE_NORMAL);
185 }
186 
OnKeyPressed(const ui::KeyEvent & event)187 bool CustomButton::OnKeyPressed(const ui::KeyEvent& event) {
188   if (state_ == STATE_DISABLED)
189     return false;
190 
191   // Space sets button state to pushed. Enter clicks the button. This matches
192   // the Windows native behavior of buttons, where Space clicks the button on
193   // KeyRelease and Enter clicks the button on KeyPressed.
194   if (event.key_code() == ui::VKEY_SPACE) {
195     SetState(STATE_PRESSED);
196   } else if (event.key_code() == ui::VKEY_RETURN) {
197     SetState(STATE_NORMAL);
198     // TODO(beng): remove once NotifyClick takes ui::Event.
199     ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
200                                    gfx::Point(),
201                                    gfx::Point(),
202                                    ui::EF_LEFT_MOUSE_BUTTON,
203                                    ui::EF_LEFT_MOUSE_BUTTON);
204     NotifyClick(synthetic_event);
205   } else {
206     return false;
207   }
208   return true;
209 }
210 
OnKeyReleased(const ui::KeyEvent & event)211 bool CustomButton::OnKeyReleased(const ui::KeyEvent& event) {
212   if ((state_ == STATE_DISABLED) || (event.key_code() != ui::VKEY_SPACE))
213     return false;
214 
215   SetState(STATE_NORMAL);
216   // TODO(beng): remove once NotifyClick takes ui::Event.
217   ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
218                                  gfx::Point(),
219                                  gfx::Point(),
220                                  ui::EF_LEFT_MOUSE_BUTTON,
221                                  ui::EF_LEFT_MOUSE_BUTTON);
222   NotifyClick(synthetic_event);
223   return true;
224 }
225 
OnGestureEvent(ui::GestureEvent * event)226 void CustomButton::OnGestureEvent(ui::GestureEvent* event) {
227   if (state_ == STATE_DISABLED) {
228     Button::OnGestureEvent(event);
229     return;
230   }
231 
232   if (event->type() == ui::ET_GESTURE_TAP && IsTriggerableEvent(*event)) {
233     // Set the button state to hot and start the animation fully faded in. The
234     // GESTURE_END event issued immediately after will set the state to
235     // STATE_NORMAL beginning the fade out animation. See
236     // http://crbug.com/131184.
237     SetState(STATE_HOVERED);
238     hover_animation_->Reset(1.0);
239     NotifyClick(*event);
240     event->StopPropagation();
241   } else if (event->type() == ui::ET_GESTURE_TAP_DOWN &&
242              ShouldEnterPushedState(*event)) {
243     SetState(STATE_PRESSED);
244     if (request_focus_on_press_)
245       RequestFocus();
246     event->StopPropagation();
247   } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL ||
248              event->type() == ui::ET_GESTURE_END) {
249     SetState(STATE_NORMAL);
250   }
251   if (!event->handled())
252     Button::OnGestureEvent(event);
253 }
254 
AcceleratorPressed(const ui::Accelerator & accelerator)255 bool CustomButton::AcceleratorPressed(const ui::Accelerator& accelerator) {
256   SetState(STATE_NORMAL);
257   /*
258   ui::KeyEvent key_event(ui::ET_KEY_RELEASED, accelerator.key_code(),
259                          accelerator.modifiers());
260                          */
261   // TODO(beng): remove once NotifyClick takes ui::Event.
262   ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
263                                  gfx::Point(),
264                                  gfx::Point(),
265                                  ui::EF_LEFT_MOUSE_BUTTON,
266                                  ui::EF_LEFT_MOUSE_BUTTON);
267   NotifyClick(synthetic_event);
268   return true;
269 }
270 
ShowContextMenu(const gfx::Point & p,ui::MenuSourceType source_type)271 void CustomButton::ShowContextMenu(const gfx::Point& p,
272                                    ui::MenuSourceType source_type) {
273   if (!context_menu_controller())
274     return;
275 
276   // We're about to show the context menu. Showing the context menu likely means
277   // we won't get a mouse exited and reset state. Reset it now to be sure.
278   if (state_ != STATE_DISABLED)
279     SetState(STATE_NORMAL);
280   View::ShowContextMenu(p, source_type);
281 }
282 
OnDragDone()283 void CustomButton::OnDragDone() {
284   SetState(STATE_NORMAL);
285 }
286 
GetAccessibleState(ui::AXViewState * state)287 void CustomButton::GetAccessibleState(ui::AXViewState* state) {
288   Button::GetAccessibleState(state);
289   switch (state_) {
290     case STATE_HOVERED:
291       state->AddStateFlag(ui::AX_STATE_HOVERED);
292       break;
293     case STATE_PRESSED:
294       state->AddStateFlag(ui::AX_STATE_PRESSED);
295       break;
296     case STATE_DISABLED:
297       state->AddStateFlag(ui::AX_STATE_DISABLED);
298       break;
299     case STATE_NORMAL:
300     case STATE_COUNT:
301       // No additional accessibility state set for this button state.
302       break;
303   }
304 }
305 
VisibilityChanged(View * starting_from,bool visible)306 void CustomButton::VisibilityChanged(View* starting_from, bool visible) {
307   if (state_ == STATE_DISABLED)
308     return;
309   SetState(visible && IsMouseHovered() ? STATE_HOVERED : STATE_NORMAL);
310 }
311 
312 ////////////////////////////////////////////////////////////////////////////////
313 // CustomButton, gfx::AnimationDelegate implementation:
314 
AnimationProgressed(const gfx::Animation * animation)315 void CustomButton::AnimationProgressed(const gfx::Animation* animation) {
316   SchedulePaint();
317 }
318 
319 ////////////////////////////////////////////////////////////////////////////////
320 // CustomButton, protected:
321 
CustomButton(ButtonListener * listener)322 CustomButton::CustomButton(ButtonListener* listener)
323     : Button(listener),
324       state_(STATE_NORMAL),
325       animate_on_state_change_(true),
326       is_throbbing_(false),
327       triggerable_event_flags_(ui::EF_LEFT_MOUSE_BUTTON),
328       request_focus_on_press_(true) {
329   hover_animation_.reset(new gfx::ThrobAnimation(this));
330   hover_animation_->SetSlideDuration(kHoverFadeDurationMs);
331 }
332 
StateChanged()333 void CustomButton::StateChanged() {
334 }
335 
IsTriggerableEvent(const ui::Event & event)336 bool CustomButton::IsTriggerableEvent(const ui::Event& event) {
337   return event.type() == ui::ET_GESTURE_TAP_DOWN ||
338          event.type() == ui::ET_GESTURE_TAP ||
339          (event.IsMouseEvent() &&
340              (triggerable_event_flags_ & event.flags()) != 0);
341 }
342 
ShouldEnterPushedState(const ui::Event & event)343 bool CustomButton::ShouldEnterPushedState(const ui::Event& event) {
344   return IsTriggerableEvent(event);
345 }
346 
347 ////////////////////////////////////////////////////////////////////////////////
348 // CustomButton, View overrides (protected):
349 
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)350 void CustomButton::ViewHierarchyChanged(
351     const ViewHierarchyChangedDetails& details) {
352   if (!details.is_add && state_ != STATE_DISABLED)
353     SetState(STATE_NORMAL);
354 }
355 
OnBlur()356 void CustomButton::OnBlur() {
357   if (IsHotTracked())
358     SetState(STATE_NORMAL);
359 }
360 
361 }  // namespace views
362