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