#include "ui/views/controls/button/menu_button_controller.h"
#include <utility>
#include "base/functional/bind.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/events/event_constants.h"
#include "ui/events/types/event_type.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/button_controller_delegate.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/mouse_constants.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
using base::TimeTicks;
namespace views {
namespace {
ui::EventType NotifyActionToMouseEventType(
ButtonController::NotifyAction notify_action) {
switch (notify_action) {
case ButtonController::NotifyAction::kOnPress:
return ui::EventType::kMousePressed;
case ButtonController::NotifyAction::kOnRelease:
return ui::EventType::kMouseReleased;
}
}
}
MenuButtonController::PressedLock::PressedLock(
MenuButtonController* menu_button_controller)
: PressedLock(menu_button_controller, false, nullptr) {}
MenuButtonController::PressedLock::PressedLock(
MenuButtonController* menu_button_controller,
bool is_sibling_menu_show,
const ui::LocatedEvent* event)
: menu_button_controller_(
menu_button_controller->weak_factory_.GetWeakPtr()) {
menu_button_controller_->IncrementPressedLocked(is_sibling_menu_show, event);
}
std::unique_ptr<MenuButtonController::PressedLock>
MenuButtonController::TakeLock() {
return TakeLock(false, nullptr);
}
std::unique_ptr<MenuButtonController::PressedLock>
MenuButtonController::TakeLock(bool is_sibling_menu_show,
const ui::LocatedEvent* event) {
return std::make_unique<MenuButtonController::PressedLock>(
this, is_sibling_menu_show, event);
}
MenuButtonController::PressedLock::~PressedLock() {
if (menu_button_controller_) {
menu_button_controller_->DecrementPressedLocked();
}
}
MenuButtonController::MenuButtonController(
Button* button,
Button::PressedCallback callback,
std::unique_ptr<ButtonControllerDelegate> delegate)
: ButtonController(button, std::move(delegate)),
callback_(std::move(callback)) {
set_notify_action(ButtonController::NotifyAction::kOnPress);
button->GetViewAccessibility().SetRole(ax::mojom::Role::kPopUpButton);
button->GetViewAccessibility().SetHasPopup(ax::mojom::HasPopup::kMenu);
}
MenuButtonController::~MenuButtonController() = default;
bool MenuButtonController::OnMousePressed(const ui::MouseEvent& event) {
is_intentional_menu_trigger_ =
(TimeTicks::Now() - menu_closed_time_) >= kMinimumTimeBetweenButtonClicks;
if (button()->GetRequestFocusOnPress()) {
button()->RequestFocus();
}
if (button()->GetState() != Button::STATE_DISABLED &&
button()->HitTestPoint(event.location()) && IsTriggerableEvent(event)) {
return Activate(&event);
}
if (!is_intentional_menu_trigger_) {
InkDrop::Get(button())->AnimateToState(InkDropState::HIDDEN, &event);
}
return true;
}
void MenuButtonController::OnMouseReleased(const ui::MouseEvent& event) {
if (button()->GetState() != Button::STATE_DISABLED &&
delegate()->IsTriggerableEvent(event) &&
button()->HitTestPoint(event.location()) && !delegate()->InDrag()) {
Activate(&event);
} else {
if (button()->GetHideInkDropWhenShowingContextMenu()) {
InkDrop::Get(button())->AnimateToState(InkDropState::HIDDEN, &event);
}
ButtonController::OnMouseReleased(event);
}
}
void MenuButtonController::OnMouseMoved(const ui::MouseEvent& event) {
if (pressed_lock_count_ == 0) {
ButtonController::OnMouseMoved(event);
}
}
void MenuButtonController::OnMouseEntered(const ui::MouseEvent& event) {
if (pressed_lock_count_ == 0) {
ButtonController::OnMouseEntered(event);
}
}
void MenuButtonController::OnMouseExited(const ui::MouseEvent& event) {
if (pressed_lock_count_ == 0) {
ButtonController::OnMouseExited(event);
}
}
bool MenuButtonController::OnKeyPressed(const ui::KeyEvent& event) {
if (event.key_code() == ui::VKEY_SPACE && event.IsAltDown()) {
return false;
}
if (event.key_code() == ui::VKEY_RETURN &&
!PlatformStyle::kReturnClicksFocusedControl) {
return false;
}
switch (event.key_code()) {
case ui::VKEY_SPACE:
case ui::VKEY_RETURN:
case ui::VKEY_UP:
case ui::VKEY_DOWN: {
Activate(&event);
return true;
}
default:
break;
}
return false;
}
bool MenuButtonController::OnKeyReleased(const ui::KeyEvent& event) {
return false;
}
void MenuButtonController::UpdateButtonAccessibleDefaultActionVerb() {
if (button()->GetEnabled()) {
button()->GetViewAccessibility().SetDefaultActionVerb(
ax::mojom::DefaultActionVerb::kOpen);
} else {
button()->GetViewAccessibility().RemoveDefaultActionVerb();
}
}
bool MenuButtonController::IsTriggerableEvent(const ui::Event& event) {
return ButtonController::IsTriggerableEvent(event) &&
IsTriggerableEventType(event) && is_intentional_menu_trigger_;
}
void MenuButtonController::OnGestureEvent(ui::GestureEvent* event) {
if (button()->GetState() != Button::STATE_DISABLED) {
auto ref = weak_factory_.GetWeakPtr();
if (delegate()->IsTriggerableEvent(*event) && !Activate(event)) {
if (ref && button()->GetState() == Button::STATE_HOVERED) {
button()->SetState(Button::STATE_NORMAL);
}
return;
}
if (event->type() == ui::EventType::kGestureTapDown) {
event->SetHandled();
if (pressed_lock_count_ == 0) {
button()->SetState(Button::STATE_HOVERED);
}
} else if (button()->GetState() == Button::STATE_HOVERED &&
(event->type() == ui::EventType::kGestureTapCancel ||
event->type() == ui::EventType::kGestureEnd) &&
pressed_lock_count_ == 0) {
button()->SetState(Button::STATE_NORMAL);
}
}
ButtonController::OnGestureEvent(event);
}
bool MenuButtonController::Activate(const ui::Event* event) {
if (callback_) {
static_cast<internal::RootView*>(button()->GetWidget()->GetRootView())
->SetMouseAndGestureHandler(nullptr);
DCHECK(increment_pressed_lock_called_ == nullptr);
bool increment_pressed_lock_called = false;
increment_pressed_lock_called_ = &increment_pressed_lock_called;
const ui::ElementIdentifier id =
button()->GetProperty(views::kElementIdentifierKey);
if (id) {
views::ElementTrackerViews::GetInstance()->NotifyViewActivated(id,
button());
}
auto ref = weak_factory_.GetWeakPtr();
ui::KeyEvent fake_event(ui::EventType::kKeyPressed, ui::VKEY_SPACE,
ui::EF_IS_SYNTHESIZED);
if (!event) {
event = &fake_event;
}
callback_.Run(*event);
if (!ref) {
return false;
}
increment_pressed_lock_called_ = nullptr;
if (!increment_pressed_lock_called && pressed_lock_count_ == 0) {
InkDrop::Get(button())->AnimateToState(
InkDropState::ACTION_TRIGGERED, ui::LocatedEvent::FromIfValid(event));
}
return false;
}
InkDrop::Get(button())->AnimateToState(InkDropState::HIDDEN,
ui::LocatedEvent::FromIfValid(event));
return true;
}
bool MenuButtonController::IsTriggerableEventType(const ui::Event& event) {
if (event.IsMouseEvent()) {
const auto* mouse_event = event.AsMouseEvent();
if (!(mouse_event->button_flags() & button()->GetTriggerableEventFlags())) {
return false;
}
ui::EventType active_on =
delegate()->GetDragOperations(mouse_event->location()) ==
ui::DragDropTypes::DRAG_NONE
? NotifyActionToMouseEventType(notify_action())
: ui::EventType::kMouseReleased;
return event.type() == active_on;
}
return event.type() == ui::EventType::kGestureTap;
}
void MenuButtonController::NotifyClick() {
ButtonController::NotifyClick();
Activate(nullptr);
}
void MenuButtonController::IncrementPressedLocked(
bool snap_ink_drop_to_activated,
const ui::LocatedEvent* event) {
++pressed_lock_count_;
if (increment_pressed_lock_called_) {
*increment_pressed_lock_called_ = true;
}
if (!state_changed_subscription_) {
state_changed_subscription_ =
button()->AddStateChangedCallback(base::BindRepeating(
&MenuButtonController::OnButtonStateChangedWhilePressedLocked,
base::Unretained(this)));
}
should_disable_after_press_ = button()->GetState() == Button::STATE_DISABLED;
if (button()->GetState() != Button::STATE_PRESSED) {
if (snap_ink_drop_to_activated) {
delegate()->GetInkDrop()->SnapToActivated();
} else {
InkDrop::Get(button())->AnimateToState(InkDropState::ACTIVATED, event);
}
}
button()->SetState(Button::STATE_PRESSED);
delegate()->GetInkDrop()->SetHovered(false);
}
void MenuButtonController::DecrementPressedLocked() {
--pressed_lock_count_;
DCHECK_GE(pressed_lock_count_, 0);
if (pressed_lock_count_ == 0) {
menu_closed_time_ = TimeTicks::Now();
state_changed_subscription_ = {};
LabelButton::ButtonState desired_state = Button::STATE_NORMAL;
if (should_disable_after_press_) {
desired_state = Button::STATE_DISABLED;
should_disable_after_press_ = false;
} else if (button()->GetWidget() &&
!button()->GetWidget()->dragged_view() &&
delegate()->ShouldEnterHoveredState()) {
desired_state = Button::STATE_HOVERED;
delegate()->GetInkDrop()->SetHovered(true);
}
button()->SetState(desired_state);
if (button()->GetWidget() &&
button()->GetState() != Button::STATE_PRESSED) {
InkDrop::Get(button())->AnimateToState(InkDropState::DEACTIVATED,
nullptr );
}
}
}
void MenuButtonController::OnButtonStateChangedWhilePressedLocked() {
if (button()->GetState() == Button::STATE_NORMAL) {
should_disable_after_press_ = false;
} else if (button()->GetState() == Button::STATE_DISABLED) {
should_disable_after_press_ = true;
}
}
}