• 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/base/accessibility/accessible_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::AccessibilityTypes::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     NotifyClick(synthetic_event);
204   } else {
205     return false;
206   }
207   return true;
208 }
209 
OnKeyReleased(const ui::KeyEvent & event)210 bool CustomButton::OnKeyReleased(const ui::KeyEvent& event) {
211   if ((state_ == STATE_DISABLED) || (event.key_code() != ui::VKEY_SPACE))
212     return false;
213 
214   SetState(STATE_NORMAL);
215   // TODO(beng): remove once NotifyClick takes ui::Event.
216   ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
217                                  gfx::Point(),
218                                  gfx::Point(),
219                                  ui::EF_LEFT_MOUSE_BUTTON);
220   NotifyClick(synthetic_event);
221   return true;
222 }
223 
OnGestureEvent(ui::GestureEvent * event)224 void CustomButton::OnGestureEvent(ui::GestureEvent* event) {
225   if (state_ == STATE_DISABLED) {
226     Button::OnGestureEvent(event);
227     return;
228   }
229 
230   if (event->type() == ui::ET_GESTURE_TAP && IsTriggerableEvent(*event)) {
231     // Set the button state to hot and start the animation fully faded in. The
232     // GESTURE_END event issued immediately after will set the state to
233     // STATE_NORMAL beginning the fade out animation. See
234     // http://crbug.com/131184.
235     SetState(STATE_HOVERED);
236     hover_animation_->Reset(1.0);
237     NotifyClick(*event);
238     event->StopPropagation();
239   } else if (event->type() == ui::ET_GESTURE_TAP_DOWN &&
240              ShouldEnterPushedState(*event)) {
241     SetState(STATE_PRESSED);
242     if (request_focus_on_press_)
243       RequestFocus();
244     event->StopPropagation();
245   } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL ||
246              event->type() == ui::ET_GESTURE_END) {
247     SetState(STATE_NORMAL);
248   }
249   if (!event->handled())
250     Button::OnGestureEvent(event);
251 }
252 
AcceleratorPressed(const ui::Accelerator & accelerator)253 bool CustomButton::AcceleratorPressed(const ui::Accelerator& accelerator) {
254   SetState(STATE_NORMAL);
255   /*
256   ui::KeyEvent key_event(ui::ET_KEY_RELEASED, accelerator.key_code(),
257                          accelerator.modifiers());
258                          */
259   // TODO(beng): remove once NotifyClick takes ui::Event.
260   ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
261                                  gfx::Point(),
262                                  gfx::Point(),
263                                  ui::EF_LEFT_MOUSE_BUTTON);
264   NotifyClick(synthetic_event);
265   return true;
266 }
267 
ShowContextMenu(const gfx::Point & p,ui::MenuSourceType source_type)268 void CustomButton::ShowContextMenu(const gfx::Point& p,
269                                    ui::MenuSourceType source_type) {
270   if (!context_menu_controller())
271     return;
272 
273   // We're about to show the context menu. Showing the context menu likely means
274   // we won't get a mouse exited and reset state. Reset it now to be sure.
275   if (state_ != STATE_DISABLED)
276     SetState(STATE_NORMAL);
277   View::ShowContextMenu(p, source_type);
278 }
279 
OnDragDone()280 void CustomButton::OnDragDone() {
281   SetState(STATE_NORMAL);
282 }
283 
GetAccessibleState(ui::AccessibleViewState * state)284 void CustomButton::GetAccessibleState(ui::AccessibleViewState* state) {
285   Button::GetAccessibleState(state);
286   switch (state_) {
287     case STATE_HOVERED:
288       state->state = ui::AccessibilityTypes::STATE_HOTTRACKED;
289       break;
290     case STATE_PRESSED:
291       state->state = ui::AccessibilityTypes::STATE_PRESSED;
292       break;
293     case STATE_DISABLED:
294       state->state = ui::AccessibilityTypes::STATE_UNAVAILABLE;
295       break;
296     case STATE_NORMAL:
297     case STATE_COUNT:
298       // No additional accessibility state set for this button state.
299       break;
300   }
301 }
302 
VisibilityChanged(View * starting_from,bool visible)303 void CustomButton::VisibilityChanged(View* starting_from, bool visible) {
304   if (state_ == STATE_DISABLED)
305     return;
306   SetState(visible && IsMouseHovered() ? STATE_HOVERED : STATE_NORMAL);
307 }
308 
309 ////////////////////////////////////////////////////////////////////////////////
310 // CustomButton, gfx::AnimationDelegate implementation:
311 
AnimationProgressed(const gfx::Animation * animation)312 void CustomButton::AnimationProgressed(const gfx::Animation* animation) {
313   SchedulePaint();
314 }
315 
316 ////////////////////////////////////////////////////////////////////////////////
317 // CustomButton, protected:
318 
CustomButton(ButtonListener * listener)319 CustomButton::CustomButton(ButtonListener* listener)
320     : Button(listener),
321       state_(STATE_NORMAL),
322       animate_on_state_change_(true),
323       is_throbbing_(false),
324       triggerable_event_flags_(ui::EF_LEFT_MOUSE_BUTTON),
325       request_focus_on_press_(true) {
326   hover_animation_.reset(new gfx::ThrobAnimation(this));
327   hover_animation_->SetSlideDuration(kHoverFadeDurationMs);
328 }
329 
StateChanged()330 void CustomButton::StateChanged() {
331 }
332 
IsTriggerableEvent(const ui::Event & event)333 bool CustomButton::IsTriggerableEvent(const ui::Event& event) {
334   return event.type() == ui::ET_GESTURE_TAP_DOWN ||
335          event.type() == ui::ET_GESTURE_TAP ||
336          (event.IsMouseEvent() &&
337              (triggerable_event_flags_ & event.flags()) != 0);
338 }
339 
ShouldEnterPushedState(const ui::Event & event)340 bool CustomButton::ShouldEnterPushedState(const ui::Event& event) {
341   return IsTriggerableEvent(event);
342 }
343 
344 ////////////////////////////////////////////////////////////////////////////////
345 // CustomButton, View overrides (protected):
346 
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)347 void CustomButton::ViewHierarchyChanged(
348     const ViewHierarchyChangedDetails& details) {
349   if (!details.is_add && state_ != STATE_DISABLED)
350     SetState(STATE_NORMAL);
351 }
352 
OnBlur()353 void CustomButton::OnBlur() {
354   if (IsHotTracked())
355     SetState(STATE_NORMAL);
356 }
357 
358 }  // namespace views
359