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