1 // Copyright (c) 2011 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/button.h"
6 
7 #include <utility>
8 
9 #include "base/strings/utf_string_conversions.h"
10 #include "ui/accessibility/ax_enums.mojom.h"
11 #include "ui/accessibility/ax_node_data.h"
12 #include "ui/base/class_property.h"
13 #include "ui/events/event.h"
14 #include "ui/events/event_utils.h"
15 #include "ui/events/keycodes/keyboard_codes.h"
16 #include "ui/gfx/animation/throb_animation.h"
17 #include "ui/gfx/color_palette.h"
18 #include "ui/native_theme/native_theme.h"
19 #include "ui/views/animation/ink_drop_highlight.h"
20 #include "ui/views/animation/ink_drop_impl.h"
21 #include "ui/views/controls/button/button_controller.h"
22 #include "ui/views/controls/button/button_controller_delegate.h"
23 #include "ui/views/controls/button/button_observer.h"
24 #include "ui/views/controls/button/checkbox.h"
25 #include "ui/views/controls/button/image_button.h"
26 #include "ui/views/controls/button/label_button.h"
27 #include "ui/views/controls/button/menu_button.h"
28 #include "ui/views/controls/button/radio_button.h"
29 #include "ui/views/controls/button/toggle_button.h"
30 #include "ui/views/controls/focus_ring.h"
31 #include "ui/views/painter.h"
32 #include "ui/views/style/platform_style.h"
33 #include "ui/views/widget/widget.h"
34 
35 #if defined(USE_AURA)
36 #include "ui/aura/client/capture_client.h"
37 #include "ui/aura/window.h"
38 #endif
39 
40 namespace views {
41 
42 namespace {
43 
44 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsButtonProperty, false)
45 
46 }  // namespace
47 
48 ////////////////////////////////////////////////////////////////////////////////
49 // WidgetObserverButtonBridge:
WidgetObserverButtonBridge(Button * button)50 Button::WidgetObserverButtonBridge::WidgetObserverButtonBridge(Button* button)
51     : owner_(button) {
52   DCHECK(button->GetWidget());
53   button->GetWidget()->AddObserver(this);
54 }
55 
~WidgetObserverButtonBridge()56 Button::WidgetObserverButtonBridge::~WidgetObserverButtonBridge() {
57   if (owner_)
58     owner_->GetWidget()->RemoveObserver(this);
59 }
60 
OnWidgetPaintAsActiveChanged(Widget * widget,bool paint_as_active)61 void Button::WidgetObserverButtonBridge::OnWidgetPaintAsActiveChanged(
62     Widget* widget,
63     bool paint_as_active) {
64   owner_->WidgetPaintAsActiveChanged(widget, paint_as_active);
65 }
66 
OnWidgetDestroying(Widget * widget)67 void Button::WidgetObserverButtonBridge::OnWidgetDestroying(Widget* widget) {
68   widget->RemoveObserver(this);
69   owner_ = nullptr;
70 }
71 
72 ////////////////////////////////////////////////////////////////////////////////
73 // ButtonControllerDelegate:
DefaultButtonControllerDelegate(Button * button)74 Button::DefaultButtonControllerDelegate::DefaultButtonControllerDelegate(
75     Button* button)
76     : ButtonControllerDelegate(button) {}
77 
78 Button::DefaultButtonControllerDelegate::~DefaultButtonControllerDelegate() =
79     default;
80 
RequestFocusFromEvent()81 void Button::DefaultButtonControllerDelegate::RequestFocusFromEvent() {
82   button()->RequestFocusFromEvent();
83 }
84 
NotifyClick(const ui::Event & event)85 void Button::DefaultButtonControllerDelegate::NotifyClick(
86     const ui::Event& event) {
87   button()->NotifyClick(event);
88 }
89 
OnClickCanceled(const ui::Event & event)90 void Button::DefaultButtonControllerDelegate::OnClickCanceled(
91     const ui::Event& event) {
92   button()->OnClickCanceled(event);
93 }
94 
IsTriggerableEvent(const ui::Event & event)95 bool Button::DefaultButtonControllerDelegate::IsTriggerableEvent(
96     const ui::Event& event) {
97   return button()->IsTriggerableEvent(event);
98 }
99 
ShouldEnterPushedState(const ui::Event & event)100 bool Button::DefaultButtonControllerDelegate::ShouldEnterPushedState(
101     const ui::Event& event) {
102   return button()->ShouldEnterPushedState(event);
103 }
104 
ShouldEnterHoveredState()105 bool Button::DefaultButtonControllerDelegate::ShouldEnterHoveredState() {
106   return button()->ShouldEnterHoveredState();
107 }
108 
GetInkDrop()109 InkDrop* Button::DefaultButtonControllerDelegate::GetInkDrop() {
110   return button()->GetInkDrop();
111 }
112 
GetDragOperations(const gfx::Point & press_pt)113 int Button::DefaultButtonControllerDelegate::GetDragOperations(
114     const gfx::Point& press_pt) {
115   return button()->GetDragOperations(press_pt);
116 }
117 
InDrag()118 bool Button::DefaultButtonControllerDelegate::InDrag() {
119   return button()->InDrag();
120 }
121 
122 ////////////////////////////////////////////////////////////////////////////////
123 
124 // static
125 constexpr Button::ButtonState Button::kButtonStates[STATE_COUNT];
126 
127 // static
AsButton(const views::View * view)128 const Button* Button::AsButton(const views::View* view) {
129   return AsButton(const_cast<View*>(view));
130 }
131 
132 // static
AsButton(views::View * view)133 Button* Button::AsButton(views::View* view) {
134   if (view && view->GetProperty(kIsButtonProperty))
135     return static_cast<Button*>(view);
136   return nullptr;
137 }
138 
139 // static
GetButtonStateFrom(ui::NativeTheme::State state)140 Button::ButtonState Button::GetButtonStateFrom(ui::NativeTheme::State state) {
141   switch (state) {
142     case ui::NativeTheme::kDisabled:
143       return Button::STATE_DISABLED;
144     case ui::NativeTheme::kHovered:
145       return Button::STATE_HOVERED;
146     case ui::NativeTheme::kNormal:
147       return Button::STATE_NORMAL;
148     case ui::NativeTheme::kPressed:
149       return Button::STATE_PRESSED;
150     case ui::NativeTheme::kNumStates:
151       NOTREACHED();
152   }
153   return Button::STATE_NORMAL;
154 }
155 
156 ////////////////////////////////////////////////////////////////////////////////
157 // Button, public:
158 
159 Button::~Button() = default;
160 
SetFocusForPlatform()161 void Button::SetFocusForPlatform() {
162 #if defined(OS_MACOSX)
163   // On Mac, buttons are focusable only in full keyboard access mode.
164   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
165 #else
166   SetFocusBehavior(FocusBehavior::ALWAYS);
167 #endif
168 }
169 
SetTooltipText(const base::string16 & tooltip_text)170 void Button::SetTooltipText(const base::string16& tooltip_text) {
171   if (tooltip_text == tooltip_text_)
172     return;
173   tooltip_text_ = tooltip_text;
174   OnSetTooltipText(tooltip_text);
175   TooltipTextChanged();
176 }
177 
SetAccessibleName(const base::string16 & name)178 void Button::SetAccessibleName(const base::string16& name) {
179   accessible_name_ = name;
180   NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
181 }
182 
GetAccessibleName() const183 const base::string16& Button::GetAccessibleName() const {
184   return accessible_name_.empty() ? tooltip_text_ : accessible_name_;
185 }
186 
SetState(ButtonState state)187 void Button::SetState(ButtonState state) {
188   if (state == state_)
189     return;
190 
191   if (animate_on_state_change_ &&
192       (!is_throbbing_ || !hover_animation_.is_animating())) {
193     is_throbbing_ = false;
194     if ((state_ == STATE_HOVERED) && (state == STATE_NORMAL)) {
195       // For HOVERED -> NORMAL, animate from hovered (1) to not hovered (0).
196       hover_animation_.Hide();
197     } else if (state != STATE_HOVERED) {
198       // For HOVERED -> PRESSED/DISABLED, or any transition not involving
199       // HOVERED at all, simply set the state to not hovered (0).
200       hover_animation_.Reset();
201     } else if (state_ == STATE_NORMAL) {
202       // For NORMAL -> HOVERED, animate from not hovered (0) to hovered (1).
203       hover_animation_.Show();
204     } else {
205       // For PRESSED/DISABLED -> HOVERED, simply set the state to hovered (1).
206       hover_animation_.Reset(1);
207     }
208   }
209 
210   ButtonState old_state = state_;
211   state_ = state;
212   StateChanged(old_state);
213   SchedulePaint();
214 }
215 
GetVisualState() const216 Button::ButtonState Button::GetVisualState() const {
217   if (PlatformStyle::kInactiveWidgetControlsAppearDisabled && GetWidget() &&
218       !GetWidget()->ShouldPaintAsActive()) {
219     return STATE_DISABLED;
220   }
221   return state();
222 }
223 
StartThrobbing(int cycles_til_stop)224 void Button::StartThrobbing(int cycles_til_stop) {
225   if (!animate_on_state_change_)
226     return;
227   is_throbbing_ = true;
228   hover_animation_.StartThrobbing(cycles_til_stop);
229 }
230 
StopThrobbing()231 void Button::StopThrobbing() {
232   if (hover_animation_.is_animating()) {
233     hover_animation_.Stop();
234     SchedulePaint();
235   }
236 }
237 
SetAnimationDuration(base::TimeDelta duration)238 void Button::SetAnimationDuration(base::TimeDelta duration) {
239   hover_animation_.SetSlideDuration(duration);
240 }
241 
SetInstallFocusRingOnFocus(bool install)242 void Button::SetInstallFocusRingOnFocus(bool install) {
243   if (install)
244     focus_ring_ = FocusRing::Install(this);
245   else
246     focus_ring_.reset();
247 }
248 
SetHotTracked(bool is_hot_tracked)249 void Button::SetHotTracked(bool is_hot_tracked) {
250   if (state_ != STATE_DISABLED) {
251     SetState(is_hot_tracked ? STATE_HOVERED : STATE_NORMAL);
252     if (show_ink_drop_when_hot_tracked_) {
253       AnimateInkDrop(is_hot_tracked ? views::InkDropState::ACTIVATED
254                                     : views::InkDropState::HIDDEN,
255                      nullptr);
256     }
257   }
258 
259   if (is_hot_tracked)
260     NotifyAccessibilityEvent(ax::mojom::Event::kHover, true);
261 }
262 
IsHotTracked() const263 bool Button::IsHotTracked() const {
264   return state_ == STATE_HOVERED;
265 }
266 
SetFocusPainter(std::unique_ptr<Painter> focus_painter)267 void Button::SetFocusPainter(std::unique_ptr<Painter> focus_painter) {
268   focus_painter_ = std::move(focus_painter);
269 }
270 
SetHighlighted(bool bubble_visible)271 void Button::SetHighlighted(bool bubble_visible) {
272   AnimateInkDrop(bubble_visible ? views::InkDropState::ACTIVATED
273                                 : views::InkDropState::DEACTIVATED,
274                  nullptr);
275   for (ButtonObserver& observer : button_observers_)
276     observer.OnHighlightChanged(this, bubble_visible);
277 }
278 
AddButtonObserver(ButtonObserver * observer)279 void Button::AddButtonObserver(ButtonObserver* observer) {
280   button_observers_.AddObserver(observer);
281 }
282 
RemoveButtonObserver(ButtonObserver * observer)283 void Button::RemoveButtonObserver(ButtonObserver* observer) {
284   button_observers_.RemoveObserver(observer);
285 }
286 
GetKeyClickActionForEvent(const ui::KeyEvent & event)287 Button::KeyClickAction Button::GetKeyClickActionForEvent(
288     const ui::KeyEvent& event) {
289   if (event.key_code() == ui::VKEY_SPACE)
290     return PlatformStyle::kKeyClickActionOnSpace;
291   if (event.key_code() == ui::VKEY_RETURN &&
292       PlatformStyle::kReturnClicksFocusedControl)
293     return KeyClickAction::kOnKeyPress;
294   return KeyClickAction::kNone;
295 }
296 
SetButtonController(std::unique_ptr<ButtonController> button_controller)297 void Button::SetButtonController(
298     std::unique_ptr<ButtonController> button_controller) {
299   button_controller_ = std::move(button_controller);
300 }
301 
GetMenuPosition() const302 gfx::Point Button::GetMenuPosition() const {
303   gfx::Rect lb = GetLocalBounds();
304 
305   // Offset of the associated menu position.
306   constexpr gfx::Vector2d kMenuOffset{-2, -4};
307 
308   // The position of the menu depends on whether or not the locale is
309   // right-to-left.
310   gfx::Point menu_position(lb.right(), lb.bottom());
311   if (base::i18n::IsRTL())
312     menu_position.set_x(lb.x());
313 
314   View::ConvertPointToScreen(this, &menu_position);
315   if (base::i18n::IsRTL())
316     menu_position.Offset(-kMenuOffset.x(), kMenuOffset.y());
317   else
318     menu_position += kMenuOffset;
319 
320   DCHECK(GetWidget());
321   const int max_x_coordinate =
322       GetWidget()->GetWorkAreaBoundsInScreen().right() - 1;
323   if (max_x_coordinate && max_x_coordinate <= menu_position.x())
324     menu_position.set_x(max_x_coordinate - 1);
325   return menu_position;
326 }
327 
328 ////////////////////////////////////////////////////////////////////////////////
329 // Button, View overrides:
330 
OnMousePressed(const ui::MouseEvent & event)331 bool Button::OnMousePressed(const ui::MouseEvent& event) {
332   return button_controller_->OnMousePressed(event);
333 }
334 
OnMouseDragged(const ui::MouseEvent & event)335 bool Button::OnMouseDragged(const ui::MouseEvent& event) {
336   if (state_ != STATE_DISABLED) {
337     const bool should_enter_pushed = ShouldEnterPushedState(event);
338     const bool should_show_pending =
339         should_enter_pushed &&
340         button_controller_->notify_action() ==
341             ButtonController::NotifyAction::kOnRelease &&
342         !InDrag();
343     if (HitTestPoint(event.location())) {
344       SetState(should_enter_pushed ? STATE_PRESSED : STATE_HOVERED);
345       if (should_show_pending && GetInkDrop()->GetTargetInkDropState() ==
346                                      views::InkDropState::HIDDEN) {
347         AnimateInkDrop(views::InkDropState::ACTION_PENDING, &event);
348       }
349     } else {
350       SetState(STATE_NORMAL);
351       if (should_show_pending && GetInkDrop()->GetTargetInkDropState() ==
352                                      views::InkDropState::ACTION_PENDING) {
353         AnimateInkDrop(views::InkDropState::HIDDEN, &event);
354       }
355     }
356   }
357   return true;
358 }
359 
OnMouseReleased(const ui::MouseEvent & event)360 void Button::OnMouseReleased(const ui::MouseEvent& event) {
361   button_controller_->OnMouseReleased(event);
362 }
363 
OnMouseCaptureLost()364 void Button::OnMouseCaptureLost() {
365   // Starting a drag results in a MouseCaptureLost. Reset button state.
366   // TODO(varkha): Reset the state even while in drag. The same logic may
367   // applies everywhere so gather any feedback and update.
368   if (state_ != STATE_DISABLED)
369     SetState(STATE_NORMAL);
370   AnimateInkDrop(views::InkDropState::HIDDEN, nullptr /* event */);
371   GetInkDrop()->SetHovered(false);
372   InkDropHostView::OnMouseCaptureLost();
373 }
374 
OnMouseEntered(const ui::MouseEvent & event)375 void Button::OnMouseEntered(const ui::MouseEvent& event) {
376   button_controller_->OnMouseEntered(event);
377 }
378 
OnMouseExited(const ui::MouseEvent & event)379 void Button::OnMouseExited(const ui::MouseEvent& event) {
380   button_controller_->OnMouseExited(event);
381 }
382 
OnMouseMoved(const ui::MouseEvent & event)383 void Button::OnMouseMoved(const ui::MouseEvent& event) {
384   button_controller_->OnMouseMoved(event);
385 }
386 
OnKeyPressed(const ui::KeyEvent & event)387 bool Button::OnKeyPressed(const ui::KeyEvent& event) {
388   return button_controller_->OnKeyPressed(event);
389 }
390 
OnKeyReleased(const ui::KeyEvent & event)391 bool Button::OnKeyReleased(const ui::KeyEvent& event) {
392   return button_controller_->OnKeyReleased(event);
393 }
394 
OnGestureEvent(ui::GestureEvent * event)395 void Button::OnGestureEvent(ui::GestureEvent* event) {
396   button_controller_->OnGestureEvent(event);
397 }
398 
AcceleratorPressed(const ui::Accelerator & accelerator)399 bool Button::AcceleratorPressed(const ui::Accelerator& accelerator) {
400   SetState(STATE_NORMAL);
401   NotifyClick(accelerator.ToKeyEvent());
402   return true;
403 }
404 
SkipDefaultKeyEventProcessing(const ui::KeyEvent & event)405 bool Button::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
406   // If this button is focused and the user presses space or enter, don't let
407   // that be treated as an accelerator if there is a key click action
408   // corresponding to it.
409   return GetKeyClickActionForEvent(event) != KeyClickAction::kNone;
410 }
411 
GetTooltipText(const gfx::Point & p) const412 base::string16 Button::GetTooltipText(const gfx::Point& p) const {
413   return tooltip_text_;
414 }
415 
ShowContextMenu(const gfx::Point & p,ui::MenuSourceType source_type)416 void Button::ShowContextMenu(const gfx::Point& p,
417                              ui::MenuSourceType source_type) {
418   if (!context_menu_controller())
419     return;
420 
421   // We're about to show the context menu. Showing the context menu likely means
422   // we won't get a mouse exited and reset state. Reset it now to be sure.
423   if (state_ != STATE_DISABLED)
424     SetState(STATE_NORMAL);
425   if (hide_ink_drop_when_showing_context_menu_) {
426     GetInkDrop()->SetHovered(false);
427     AnimateInkDrop(InkDropState::HIDDEN, nullptr /* event */);
428   }
429   InkDropHostView::ShowContextMenu(p, source_type);
430 }
431 
OnDragDone()432 void Button::OnDragDone() {
433   // Only reset the state to normal if the button isn't currently disabled
434   // (since disabled buttons may still be able to be dragged).
435   if (state_ != STATE_DISABLED)
436     SetState(STATE_NORMAL);
437   AnimateInkDrop(InkDropState::HIDDEN, nullptr /* event */);
438 }
439 
OnPaint(gfx::Canvas * canvas)440 void Button::OnPaint(gfx::Canvas* canvas) {
441   InkDropHostView::OnPaint(canvas);
442   PaintButtonContents(canvas);
443   Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
444 }
445 
GetAccessibleNodeData(ui::AXNodeData * node_data)446 void Button::GetAccessibleNodeData(ui::AXNodeData* node_data) {
447   node_data->role = ax::mojom::Role::kButton;
448   node_data->SetName(GetAccessibleName());
449   if (!GetEnabled())
450     node_data->SetRestriction(ax::mojom::Restriction::kDisabled);
451 
452   switch (state_) {
453     case STATE_HOVERED:
454       node_data->AddState(ax::mojom::State::kHovered);
455       break;
456     case STATE_PRESSED:
457       node_data->SetCheckedState(ax::mojom::CheckedState::kTrue);
458       break;
459     case STATE_DISABLED:
460       node_data->SetRestriction(ax::mojom::Restriction::kDisabled);
461       break;
462     case STATE_NORMAL:
463     case STATE_COUNT:
464       // No additional accessibility node_data set for this button node_data.
465       break;
466   }
467   if (GetEnabled())
468     node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kPress);
469 
470   button_controller_->UpdateAccessibleNodeData(node_data);
471 }
472 
VisibilityChanged(View * starting_from,bool visible)473 void Button::VisibilityChanged(View* starting_from, bool visible) {
474   InkDropHostView::VisibilityChanged(starting_from, visible);
475   if (state_ == STATE_DISABLED)
476     return;
477   SetState(visible && ShouldEnterHoveredState() ? STATE_HOVERED : STATE_NORMAL);
478 }
479 
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)480 void Button::ViewHierarchyChanged(const ViewHierarchyChangedDetails& details) {
481   if (!details.is_add && state_ != STATE_DISABLED && details.child == this)
482     SetState(STATE_NORMAL);
483   InkDropHostView::ViewHierarchyChanged(details);
484 }
485 
OnFocus()486 void Button::OnFocus() {
487   InkDropHostView::OnFocus();
488   if (focus_painter_)
489     SchedulePaint();
490 }
491 
OnBlur()492 void Button::OnBlur() {
493   InkDropHostView::OnBlur();
494   if (IsHotTracked() || state_ == STATE_PRESSED) {
495     SetState(STATE_NORMAL);
496     if (GetInkDrop()->GetTargetInkDropState() != views::InkDropState::HIDDEN)
497       AnimateInkDrop(views::InkDropState::HIDDEN, nullptr /* event */);
498     // TODO(bruthig) : Fix Buttons to work well when multiple input
499     // methods are interacting with a button. e.g. By animating to HIDDEN here
500     // it is possible for a Mouse Release to trigger an action however there
501     // would be no visual cue to the user that this will occur.
502   }
503   if (focus_painter_)
504     SchedulePaint();
505 }
506 
AddedToWidget()507 void Button::AddedToWidget() {
508   if (PlatformStyle::kInactiveWidgetControlsAppearDisabled)
509     widget_observer_ = std::make_unique<WidgetObserverButtonBridge>(this);
510 }
511 
RemovedFromWidget()512 void Button::RemovedFromWidget() {
513   widget_observer_.reset();
514 }
515 
CreateInkDrop()516 std::unique_ptr<InkDrop> Button::CreateInkDrop() {
517   std::unique_ptr<InkDrop> ink_drop = InkDropHostView::CreateInkDrop();
518   ink_drop->SetShowHighlightOnFocus(!focus_ring_);
519   return ink_drop;
520 }
521 
GetInkDropBaseColor() const522 SkColor Button::GetInkDropBaseColor() const {
523   return ink_drop_base_color_;
524 }
525 
526 ////////////////////////////////////////////////////////////////////////////////
527 // Button, gfx::AnimationDelegate implementation:
528 
AnimationProgressed(const gfx::Animation * animation)529 void Button::AnimationProgressed(const gfx::Animation* animation) {
530   SchedulePaint();
531 }
532 
533 ////////////////////////////////////////////////////////////////////////////////
534 // Button, protected:
535 
Button(ButtonListener * listener)536 Button::Button(ButtonListener* listener)
537     : AnimationDelegateViews(this),
538       listener_(listener),
539       ink_drop_base_color_(gfx::kPlaceholderColor) {
540   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
541   SetProperty(kIsButtonProperty, true);
542   hover_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(150));
543   SetInstallFocusRingOnFocus(PlatformStyle::kPreferFocusRings);
544   button_controller_ = std::make_unique<ButtonController>(
545       this, std::make_unique<DefaultButtonControllerDelegate>(this));
546 }
547 
RequestFocusFromEvent()548 void Button::RequestFocusFromEvent() {
549   if (request_focus_on_press_)
550     RequestFocus();
551 }
552 
NotifyClick(const ui::Event & event)553 void Button::NotifyClick(const ui::Event& event) {
554   if (has_ink_drop_action_on_click_) {
555     AnimateInkDrop(InkDropState::ACTION_TRIGGERED,
556                    ui::LocatedEvent::FromIfValid(&event));
557   }
558 
559   // We can be called when there is no listener, in cases like double clicks on
560   // menu buttons etc.
561   if (listener_)
562     listener_->ButtonPressed(this, event);
563 }
564 
OnClickCanceled(const ui::Event & event)565 void Button::OnClickCanceled(const ui::Event& event) {
566   if (ShouldUpdateInkDropOnClickCanceled()) {
567     if (GetInkDrop()->GetTargetInkDropState() ==
568             views::InkDropState::ACTION_PENDING ||
569         GetInkDrop()->GetTargetInkDropState() ==
570             views::InkDropState::ALTERNATE_ACTION_PENDING) {
571       AnimateInkDrop(views::InkDropState::HIDDEN,
572                      ui::LocatedEvent::FromIfValid(&event));
573     }
574   }
575 }
576 
OnSetTooltipText(const base::string16 & tooltip_text)577 void Button::OnSetTooltipText(const base::string16& tooltip_text) {}
578 
StateChanged(ButtonState old_state)579 void Button::StateChanged(ButtonState old_state) {
580   button_controller_->OnStateChanged(old_state);
581   for (ButtonObserver& observer : button_observers_)
582     observer.OnStateChanged(this, old_state);
583 }
584 
IsTriggerableEvent(const ui::Event & event)585 bool Button::IsTriggerableEvent(const ui::Event& event) {
586   return button_controller_->IsTriggerableEvent(event);
587 }
588 
ShouldUpdateInkDropOnClickCanceled() const589 bool Button::ShouldUpdateInkDropOnClickCanceled() const {
590   return true;
591 }
592 
ShouldEnterPushedState(const ui::Event & event)593 bool Button::ShouldEnterPushedState(const ui::Event& event) {
594   return IsTriggerableEvent(event);
595 }
596 
PaintButtonContents(gfx::Canvas * canvas)597 void Button::PaintButtonContents(gfx::Canvas* canvas) {}
598 
ShouldEnterHoveredState()599 bool Button::ShouldEnterHoveredState() {
600   if (!GetVisible())
601     return false;
602 
603   bool check_mouse_position = true;
604 #if defined(USE_AURA)
605   // If another window has capture, we shouldn't check the current mouse
606   // position because the button won't receive any mouse events - so if the
607   // mouse was hovered, the button would be stuck in a hovered state (since it
608   // would never receive OnMouseExited).
609   const Widget* widget = GetWidget();
610   if (widget && widget->GetNativeWindow()) {
611     aura::Window* root_window = widget->GetNativeWindow()->GetRootWindow();
612     aura::client::CaptureClient* capture_client =
613         aura::client::GetCaptureClient(root_window);
614     aura::Window* capture_window =
615         capture_client ? capture_client->GetGlobalCaptureWindow() : nullptr;
616     check_mouse_position = !capture_window || capture_window == root_window;
617   }
618 #endif
619 
620   return check_mouse_position && IsMouseHovered();
621 }
622 
OnEnabledChanged()623 void Button::OnEnabledChanged() {
624   if (GetEnabled() ? (state_ != STATE_DISABLED) : (state_ == STATE_DISABLED))
625     return;
626 
627   if (GetEnabled()) {
628     bool should_enter_hover_state = ShouldEnterHoveredState();
629     SetState(should_enter_hover_state ? STATE_HOVERED : STATE_NORMAL);
630     GetInkDrop()->SetHovered(should_enter_hover_state);
631   } else {
632     SetState(STATE_DISABLED);
633     GetInkDrop()->SetHovered(false);
634   }
635 }
636 
WidgetPaintAsActiveChanged(Widget * widget,bool paint_as_active)637 void Button::WidgetPaintAsActiveChanged(Widget* widget, bool paint_as_active) {
638   StateChanged(state());
639 }
640 
641 BEGIN_METADATA(Button)
642 METADATA_PARENT_CLASS(InkDropHostView)
643 END_METADATA()
644 
645 }  // namespace views
646