// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/accessibility/autoclick/autoclick_controller.h"
#include <tuple>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/autoclick/autoclick_drag_event_rewriter.h"
#include "ash/accessibility/autoclick/autoclick_ring_handler.h"
#include "ash/accessibility/autoclick/autoclick_scroll_position_handler.h"
#include "ash/constants/ash_constants.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/accessibility/accessibility_feature_disable_dialog.h"
#include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
#include "ash/wm/fullscreen_window_finder.h"
#include "ash/wm/window_util.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/timer/timer.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_sink.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
// The ratio of how long the gesture should take to begin after a dwell to the
// total amount of time of the dwell.
const float kStartGestureDelayRatio = 1 / 6.0;
// How much distance to travel with each generated scroll event.
const int kScrollDelta = 10;
bool IsModifierKey(const ui::KeyboardCode key_code) {
return key_code == ui::VKEY_SHIFT || key_code == ui::VKEY_LSHIFT ||
key_code == ui::VKEY_CONTROL || key_code == ui::VKEY_LCONTROL ||
key_code == ui::VKEY_RCONTROL || key_code == ui::VKEY_MENU ||
key_code == ui::VKEY_LMENU || key_code == ui::VKEY_RMENU;
}
base::TimeDelta CalculateStartGestureDelay(base::TimeDelta total_delay) {
return total_delay * kStartGestureDelayRatio;
}
views::Widget::InitParams CreateAutoclickOverlayWidgetParams(
aura::Window* root_window) {
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.accept_events = false;
params.activatable = views::Widget::InitParams::Activatable::kNo;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.parent =
Shell::GetContainer(root_window, kShellWindowId_OverlayContainer);
return params;
}
} // namespace
// static.
base::TimeDelta AutoclickController::GetDefaultAutoclickDelay() {
return base::Milliseconds(int64_t{kDefaultAutoclickDelayMs});
}
AutoclickController::AutoclickController()
: delay_(GetDefaultAutoclickDelay()),
autoclick_ring_handler_(std::make_unique<AutoclickRingHandler>()),
drag_event_rewriter_(std::make_unique<AutoclickDragEventRewriter>()) {
Shell::GetPrimaryRootWindow()->GetHost()->GetEventSource()->AddEventRewriter(
drag_event_rewriter_.get());
Shell::Get()->cursor_manager()->AddObserver(this);
InitClickTimers();
UpdateRingSize();
}
AutoclickController::~AutoclickController() {
// Clean up UI.
HideScrollPosition();
menu_bubble_controller_ = nullptr;
CancelAutoclickAction();
// This may be called during shutdown in which case some of the
// ash objects may already be destroyed.
auto* shell = Shell::Get();
if (!shell) {
return;
}
auto* cursor_manager = shell->cursor_manager();
if (cursor_manager) {
cursor_manager->RemoveObserver(this);
}
shell->RemovePreTargetHandler(this);
SetTapDownTarget(nullptr);
auto* root_window = Shell::GetPrimaryRootWindow();
if (!root_window) {
return;
}
auto* host = root_window->GetHost();
if (!host) {
return;
}
auto* event_source = host->GetEventSource();
if (!event_source) {
return;
}
event_source->RemoveEventRewriter(drag_event_rewriter_.get());
}
float AutoclickController::GetStartGestureDelayRatioForTesting() {
return kStartGestureDelayRatio;
}
void AutoclickController::SetTapDownTarget(aura::Window* target) {
if (tap_down_target_ == target)
return;
if (tap_down_target_)
tap_down_target_->RemoveObserver(this);
tap_down_target_ = target;
if (tap_down_target_)
tap_down_target_->AddObserver(this);
}
void AutoclickController::SetEnabled(bool enabled,
bool show_confirmation_dialog) {
if (enabled_ == enabled)
return;
if (enabled) {
Shell::Get()->AddAccessibilityEventHandler(
this, AccessibilityEventHandlerManager::HandlerType::kAutoclick);
// Only create the bubble controller when needed. Most users will not enable
// automatic clicks, so there's no need to use these unless the feature
// is on.
menu_bubble_controller_ = std::make_unique<AutoclickMenuBubbleController>();
menu_bubble_controller_->ShowBubble(event_type_, menu_position_);
if (event_type_ == AutoclickEventType::kScroll) {
InitializeScrollLocation();
UpdateScrollPosition();
}
enabled_ = enabled;
} else {
if (show_confirmation_dialog) {
// If a dialog exists already, no need to show it again.
if (disable_dialog_)
return;
// Show a confirmation dialog before disabling autoclick.
auto* dialog = new AccessibilityFeatureDisableDialog(
IDS_ASH_AUTOCLICK_DISABLE_CONFIRMATION_TEXT,
// Callback for if the user accepts the dialog
base::BindOnce([]() {
// If they accept, actually disable autoclick.
Shell::Get()->autoclick_controller()->SetEnabled(
false, false /* do not show the dialog */);
}),
// Callback for if the user cancels the dialog - marks the
// feature as enabled again in prefs.
base::BindOnce([]() {
// If they cancel, ensure autoclick is enabled.
Shell::Get()->accessibility_controller()->autoclick().SetEnabled(
true);
}));
disable_dialog_ = dialog->GetWeakPtr();
} else {
HideScrollPosition();
Shell::Get()->RemoveAccessibilityEventHandler(this);
menu_bubble_controller_ = nullptr;
// Set the click type to left-click. This is the most useful click type
// and users will want this type when they re-enable. If users were to
// re-enable in scroll, or right-click, they would need to use the bubble
// menu to change types.
Shell::Get()->accessibility_controller()->SetAutoclickEventType(
AutoclickEventType::kLeftClick);
enabled_ = enabled;
}
}
CancelAutoclickAction();
}
bool AutoclickController::IsEnabled() const {
return enabled_;
}
void AutoclickController::SetAutoclickDelay(base::TimeDelta delay) {
delay_ = delay;
InitClickTimers();
}
void AutoclickController::SetAutoclickEventType(AutoclickEventType type) {
if (menu_bubble_controller_)
menu_bubble_controller_->SetEventType(type);
if (type == AutoclickEventType::kScroll) {
InitializeScrollLocation();
UpdateScrollPosition();
} else {
over_scroll_button_ = false;
HideScrollPosition();
}
if (event_type_ == type)
return;
CancelAutoclickAction();
event_type_ = type;
}
void AutoclickController::SetMovementThreshold(int movement_threshold) {
movement_threshold_ = movement_threshold;
UpdateRingSize();
}
void AutoclickController::SetMenuPosition(FloatingMenuPosition menu_position) {
menu_position_ = menu_position;
UpdateAutoclickMenuBoundsIfNeeded();
}
void AutoclickController::DoScrollAction(ScrollPadAction action) {
if (action == ScrollPadAction::kScrollClose) {
// Set the scroll_location_ back to the default.
scroll_location_ = gfx::Point(-kDefaultAutoclickMovementThreshold,
-kDefaultAutoclickMovementThreshold);
// Return to left click.
event_type_ = AutoclickEventType::kLeftClick;
Shell::Get()->accessibility_controller()->SetAutoclickEventType(
event_type_);
return;
}
// Otherwise, do a scroll action at the current scroll target point.
float scroll_x = 0.0f;
float scroll_y = 0.0f;
switch (action) {
case ScrollPadAction::kScrollUp:
scroll_y = kScrollDelta;
break;
case ScrollPadAction::kScrollDown:
scroll_y = -kScrollDelta;
break;
case ScrollPadAction::kScrollLeft:
scroll_x = kScrollDelta;
break;
case ScrollPadAction::kScrollRight:
scroll_x = -kScrollDelta;
break;
case ScrollPadAction::kScrollClose:
NOTREACHED();
}
// Generate a scroll event at the current scroll location.
aura::Window* root_window = window_util::GetRootWindowAt(scroll_location_);
gfx::Point location_in_pixels(scroll_location_);
::wm::ConvertPointFromScreen(root_window, &location_in_pixels);
aura::WindowTreeHost* host = root_window->GetHost();
host->ConvertDIPToPixels(&location_in_pixels);
ui::ScrollEvent scroll(
ui::EventType::kScroll, gfx::PointF(location_in_pixels),
gfx::PointF(location_in_pixels), ui::EventTimeForNow(),
mouse_event_flags_, scroll_x, scroll_y, 0 /* x_offset_ordinal */,
0 /* y_offset_ordinal */, 2 /* finger_count */);
ui::MouseWheelEvent wheel(scroll);
std::ignore = host->GetEventSink()->OnEventFromSource(&wheel);
}
void AutoclickController::OnEnteredScrollButton() {
if (start_gesture_timer_)
start_gesture_timer_->Stop();
if (autoclick_timer_)
autoclick_timer_->Stop();
autoclick_ring_handler_->StopGesture();
over_scroll_button_ = true;
}
void AutoclickController::OnExitedScrollButton() {
over_scroll_button_ = false;
// Reset the anchor_location_ so that gestures could begin immediately.
anchor_location_ = gfx::Point(-kDefaultAutoclickMovementThreshold,
-kDefaultAutoclickMovementThreshold);
}
void AutoclickController::HandleAutoclickScrollableBoundsFound(
const gfx::Rect& bounds_in_screen) {
// The very first time scrollable bounds are found, the default first
// position of the scrollbar to be next to the menu bubble.
if (is_initial_scroll_location_)
return;
menu_bubble_controller_->SetScrollPosition(bounds_in_screen,
scroll_location_);
if (scrollable_bounds_callback_for_testing_) {
scrollable_bounds_callback_for_testing_.Run(bounds_in_screen);
}
}
void AutoclickController::UpdateAutoclickMenuBoundsIfNeeded() {
if (menu_bubble_controller_)
menu_bubble_controller_->SetPosition(menu_position_);
}
void AutoclickController::UpdateAutoclickWidgetPosition(
gfx::NativeView native_view,
aura::Window* root_window) {
if (native_view->GetRootWindow() != root_window) {
views::Widget::ReparentNativeView(
native_view,
Shell::GetContainer(root_window, kShellWindowId_OverlayContainer));
}
}
void AutoclickController::DoAutoclickAction() {
// The gesture_anchor_location_ is the position at which the animation is
// anchored, and where the click should occur.
aura::Window* root_window =
window_util::GetRootWindowAt(gesture_anchor_location_);
DCHECK(root_window) << "Root window not found while attempting autoclick.";
// But if the thing that would be acted upon is an autoclick menu button, do a
// fake click instead of whatever other action type we would have done. This
// ensures that no matter the autoclick setting, users can always change to
// another autoclick setting. By using a fake click we avoid closing dialogs
// and menus, allowing autoclick users to interact with those items.
if (!DragInProgress() &&
AutoclickMenuContainsPoint(gesture_anchor_location_)) {
menu_bubble_controller_->ClickOnBubble(gesture_anchor_location_,
mouse_event_flags_);
// Reset UI.
CancelAutoclickAction();
return;
}
// Set the in-progress event type locally so that if the event type is updated
// in the middle of this event being executed it doesn't change execution.
AutoclickEventType in_progress_event_type = event_type_;
RecordUserAction(in_progress_event_type);
if (in_progress_event_type == AutoclickEventType::kScroll) {
// A dwell during a scroll.
// Check if the event is over the scroll bubble controller, and if it is,
// click on the scroll bubble.
if (AutoclickScrollContainsPoint(gesture_anchor_location_)) {
menu_bubble_controller_->ClickOnScrollBubble(gesture_anchor_location_,
mouse_event_flags_);
} else {
scroll_location_ = gesture_anchor_location_;
is_initial_scroll_location_ = false;
UpdateScrollPosition();
Shell::Get()
->accessibility_controller()
->RequestAutoclickScrollableBoundsForPoint(scroll_location_);
base::RecordAction(
base::UserMetricsAction("Accessibility.Autoclick.ChangeScrollPoint"));
}
return;
}
gfx::Point location_in_pixels(gesture_anchor_location_);
::wm::ConvertPointFromScreen(root_window, &location_in_pixels);
aura::WindowTreeHost* host = root_window->GetHost();
host->ConvertDIPToPixels(&location_in_pixels);
bool drag_start =
in_progress_event_type == AutoclickEventType::kDragAndDrop &&
!drag_event_rewriter_->IsEnabled();
bool drag_stop = DragInProgress();
if (in_progress_event_type == AutoclickEventType::kLeftClick ||
in_progress_event_type == AutoclickEventType::kRightClick ||
in_progress_event_type == AutoclickEventType::kDoubleClick ||
drag_start || drag_stop) {
int button = in_progress_event_type == AutoclickEventType::kRightClick
? ui::EF_RIGHT_MOUSE_BUTTON
: ui::EF_LEFT_MOUSE_BUTTON;
ui::EventDispatchDetails details;
if (!drag_stop) {
// Left click, right click, double click, and beginning of a drag have
// a pressed event next.
ui::MouseEvent press_event(
ui::EventType::kMousePressed, location_in_pixels, location_in_pixels,
ui::EventTimeForNow(), mouse_event_flags_ | button, button);
details = host->GetEventSink()->OnEventFromSource(&press_event);
if (drag_start) {
drag_event_rewriter_->SetEnabled(true);
return;
}
if (details.dispatcher_destroyed) {
OnActionCompleted(in_progress_event_type);
return;
}
}
if (drag_stop)
drag_event_rewriter_->SetEnabled(false);
ui::MouseEvent release_event(
ui::EventType::kMouseReleased, location_in_pixels, location_in_pixels,
ui::EventTimeForNow(), mouse_event_flags_ | button, button);
details = host->GetEventSink()->OnEventFromSource(&release_event);
// Now a single click, or half the drag & drop, has been completed.
if (in_progress_event_type != AutoclickEventType::kDoubleClick ||
details.dispatcher_destroyed) {
OnActionCompleted(in_progress_event_type);
return;
}
ui::MouseEvent double_press_event(
ui::EventType::kMousePressed, location_in_pixels, location_in_pixels,
ui::EventTimeForNow(),
mouse_event_flags_ | button | ui::EF_IS_DOUBLE_CLICK, button);
ui::MouseEvent double_release_event(
ui::EventType::kMouseReleased, location_in_pixels, location_in_pixels,
ui::EventTimeForNow(),
mouse_event_flags_ | button | ui::EF_IS_DOUBLE_CLICK, button);
details = host->GetEventSink()->OnEventFromSource(&double_press_event);
if (details.dispatcher_destroyed) {
OnActionCompleted(in_progress_event_type);
return;
}
details = host->GetEventSink()->OnEventFromSource(&double_release_event);
OnActionCompleted(in_progress_event_type);
}
}
void AutoclickController::StartAutoclickGesture() {
if (event_type_ == AutoclickEventType::kNoAction) {
// If we are set to "no action" and the gesture wouldn't occur over
// the autoclick menu, cancel and return early rather than starting the
// gesture.
if (!AutoclickMenuContainsPoint(gesture_anchor_location_)) {
CancelAutoclickAction();
return;
}
// Otherwise, go ahead and start the gesture.
}
// The anchor is always the point in the screen where the timer starts, and is
// used to determine when the cursor has moved far enough to cancel the
// autoclick.
anchor_location_ = gesture_anchor_location_;
autoclick_ring_handler_->StartGesture(
delay_ - CalculateStartGestureDelay(delay_), anchor_location_,
ring_widget_.get());
autoclick_timer_->Reset();
}
void AutoclickController::CancelAutoclickAction() {
if (start_gesture_timer_)
start_gesture_timer_->Stop();
if (autoclick_timer_)
autoclick_timer_->Stop();
autoclick_ring_handler_->StopGesture();
// If we are dragging, complete the drag, so as not to leave the UI in a
// weird state.
if (DragInProgress()) {
DoAutoclickAction();
}
drag_event_rewriter_->SetEnabled(false);
SetTapDownTarget(nullptr);
}
void AutoclickController::OnActionCompleted(
AutoclickEventType completed_event_type) {
// No need to change to left click if the setting is not enabled or the
// event that just executed already was a left click.
if (!revert_to_left_click_ || event_type_ == AutoclickEventType::kLeftClick ||
completed_event_type == AutoclickEventType::kLeftClick) {
return;
}
// Change the preference, but set it locally so we do not reset any state when
// AutoclickController::SetAutoclickEventType is called.
event_type_ = AutoclickEventType::kLeftClick;
Shell::Get()->accessibility_controller()->SetAutoclickEventType(event_type_);
}
void AutoclickController::InitClickTimers() {
CancelAutoclickAction();
base::TimeDelta start_gesture_delay = CalculateStartGestureDelay(delay_);
if (autoclick_timer_ && autoclick_timer_->IsRunning())
autoclick_timer_->Stop();
if (start_gesture_timer_ && start_gesture_timer_->IsRunning())
start_gesture_timer_->Stop();
autoclick_timer_ = std::make_unique<base::RetainingOneShotTimer>(
FROM_HERE, delay_ - start_gesture_delay,
base::BindRepeating(&AutoclickController::DoAutoclickAction,
base::Unretained(this)));
start_gesture_timer_ = std::make_unique<base::RetainingOneShotTimer>(
FROM_HERE, start_gesture_delay,
base::BindRepeating(&AutoclickController::StartAutoclickGesture,
base::Unretained(this)));
}
void AutoclickController::UpdateRingWidget() {
aura::Window* const target =
window_util::GetRootWindowAt(last_mouse_location_);
SetTapDownTarget(target);
aura::Window* const root_window = target->GetRootWindow();
if (ring_widget_) {
UpdateAutoclickWidgetPosition(ring_widget_->GetNativeView(), root_window);
} else {
ring_widget_ = std::make_unique<views::Widget>(
CreateAutoclickOverlayWidgetParams(root_window));
ring_widget_->SetOpacity(1.0f);
}
}
void AutoclickController::UpdateRingSize() {
autoclick_ring_handler_->SetSize(movement_threshold_);
}
void AutoclickController::InitializeScrollLocation() {
// Sets the scroll location to the center of the root window.
scroll_location_ =
Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen().CenterPoint();
is_initial_scroll_location_ = true;
Shell::Get()
->accessibility_controller()
->RequestAutoclickScrollableBoundsForPoint(scroll_location_);
}
void AutoclickController::UpdateScrollPosition() {
if (!enabled_)
return;
aura::Window* const target = window_util::GetRootWindowAt(scroll_location_);
SetTapDownTarget(target);
aura::Window* const root_window = target->GetRootWindow();
if (autoclick_scroll_position_handler_) {
UpdateAutoclickWidgetPosition(
autoclick_scroll_position_handler_->GetNativeView(), root_window);
} else {
autoclick_scroll_position_handler_ =
std::make_unique<AutoclickScrollPositionHandler>(
std::make_unique<views::Widget>(
CreateAutoclickOverlayWidgetParams(root_window)));
}
autoclick_scroll_position_handler_->SetScrollPointCenterInScreen(
scroll_location_);
}
void AutoclickController::HideScrollPosition() {
autoclick_scroll_position_handler_.reset();
// TODO(katie): Clear any Autoclick scroll focus rings here.
}
bool AutoclickController::DragInProgress() const {
return event_type_ == AutoclickEventType::kDragAndDrop &&
drag_event_rewriter_->IsEnabled();
}
bool AutoclickController::AutoclickMenuContainsPoint(
const gfx::Point& point) const {
return menu_bubble_controller_ &&
menu_bubble_controller_->ContainsPointInScreen(point);
}
bool AutoclickController::AutoclickScrollContainsPoint(
const gfx::Point& point) const {
return menu_bubble_controller_ &&
menu_bubble_controller_->ScrollBubbleContainsPointInScreen(point);
}
void AutoclickController::RecordUserAction(
AutoclickEventType event_type) const {
switch (event_type) {
case AutoclickEventType::kLeftClick:
base::RecordAction(
base::UserMetricsAction("Accessibility.Autoclick.LeftClick"));
return;
case AutoclickEventType::kRightClick:
base::RecordAction(
base::UserMetricsAction("Accessibility.Autoclick.RightClick"));
return;
case AutoclickEventType::kDoubleClick:
base::RecordAction(
base::UserMetricsAction("Accessibility.Autoclick.DoubleClick"));
return;
case AutoclickEventType::kDragAndDrop:
// Only log drag-and-drop once per drag-and-drop. It takes two "dwells"
// to complete a full drag-and-drop cycle, which could lead to double
// the events logged.
if (DragInProgress())
return;
base::RecordAction(
base::UserMetricsAction("Accessibility.Autoclick.DragAndDrop"));
return;
case AutoclickEventType::kScroll:
// Scroll users actions will be recorded from AutoclickScrollView.
case AutoclickEventType::kNoAction:
// No action shouldn't have a UserAction, so we return.
return;
}
}
void AutoclickController::OnMouseEvent(ui::MouseEvent* event) {
DCHECK(event->target());
if (event->type() == ui::EventType::kMouseCaptureChanged) {
return;
}
last_mouse_location_ = event->target()->GetScreenLocation(*event);
if (over_scroll_button_)
return;
if (!(event->flags() & ui::EF_IS_SYNTHESIZED) &&
(event->type() == ui::EventType::kMouseMoved ||
(event->type() == ui::EventType::kMouseDragged &&
drag_event_rewriter_->IsEnabled()))) {
mouse_event_flags_ = event->flags();
// Update the point even if the animation is not currently being shown.
UpdateRingWidget();
// The distance between the mouse location and the anchor location
// must exceed a certain threshold to initiate a new autoclick countdown.
// This ensures that mouse jitter caused by poor motor control does not
// 1. initiate an unwanted autoclick from rest
// 2. prevent the autoclick from ever occurring when the mouse
// arrives at the target.
gfx::Vector2d delta = last_mouse_location_ - anchor_location_;
if (delta.LengthSquared() >= movement_threshold_ * movement_threshold_) {
anchor_location_ = last_mouse_location_;
gesture_anchor_location_ = last_mouse_location_;
// Stop all the timers, restarting the gesture timer only. This keeps
// the animation from being drawn while the user is still moving quickly.
start_gesture_timer_->Reset();
if (autoclick_timer_) {
autoclick_timer_->Stop();
}
autoclick_ring_handler_->StopGesture();
} else if (start_gesture_timer_->IsRunning()) {
// Keep track of where the gesture will be anchored.
gesture_anchor_location_ = last_mouse_location_;
} else if (autoclick_timer_->IsRunning() && !stabilize_click_position_) {
// If we are not stabilizing the click position, update the gesture
// center with each mouse move event.
gesture_anchor_location_ = last_mouse_location_;
autoclick_ring_handler_->SetGestureCenter(last_mouse_location_,
ring_widget_.get());
}
} else if (event->type() == ui::EventType::kMousePressed ||
event->type() == ui::EventType::kMouseReleased ||
event->type() == ui::EventType::kMousewheel) {
CancelAutoclickAction();
}
}
void AutoclickController::OnKeyEvent(ui::KeyEvent* event) {
int modifier_mask = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN |
ui::EF_IS_EXTENDED_KEY;
int new_modifiers = event->flags() & modifier_mask;
mouse_event_flags_ = (mouse_event_flags_ & ~modifier_mask) | new_modifiers;
if (!IsModifierKey(event->key_code()))
CancelAutoclickAction();
}
void AutoclickController::OnTouchEvent(ui::TouchEvent* event) {
CancelAutoclickAction();
}
void AutoclickController::OnGestureEvent(ui::GestureEvent* event) {
CancelAutoclickAction();
}
void AutoclickController::OnScrollEvent(ui::ScrollEvent* event) {
// A single tap can create a scroll event, so ignore scroll starts and
// cancels but cancel autoclicks when scrolls actually occur.
if (event->type() == ui::EventType::kScrollFlingStart ||
event->type() == ui::EventType::kScrollFlingCancel) {
return;
}
CancelAutoclickAction();
}
void AutoclickController::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(tap_down_target_, window);
CancelAutoclickAction();
}
void AutoclickController::OnCursorVisibilityChanged(bool is_visible) {
if (!menu_bubble_controller_)
return;
// TODO(katie): Check that the display which is fullscreen is the same as the
// one containing the bubble, to determine whether to hide the bubble.
// Currently just checking if the display under the mouse is fullscreen.
aura::Window* window = GetWindowForFullscreenModeInRoot(
window_util::GetRootWindowAt(last_mouse_location_));
bool is_fullscreen = window != nullptr;
// Hide the bubble when the cursor is gone in fullscreen mode.
// Always show it otherwise.
menu_bubble_controller_->SetBubbleVisibility(is_fullscreen ? is_visible
: true);
}
} // namespace ash