#include "ash/system/accessibility/autoclick_scroll_bubble_controller.h"
#include "ash/bubble/bubble_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/tray/tray_background_view.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/wm/collision_detection/collision_detection_utils.h"
#include "ash/wm/work_area_insets.h"
#include "ash/wm/workspace/workspace_layout_manager.h"
#include "ash/wm/workspace_controller.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/display/manager/display_manager.h"
#include "ui/events/event_utils.h"
#include "ui/views/border.h"
namespace ash {
namespace {
constexpr int kAutoclickScrollMenuSizeDips = 192;
const int kScrollPointBufferDips = 16;
const int kScrollRectBufferDips = 8;
struct Position {
int distance;
views::BubbleBorder::Arrow arrow;
bool is_horizontal;
};
bool comparePositions(Position first, Position second) {
return first.distance < second.distance;
}
}
AutoclickScrollBubbleController::AutoclickScrollBubbleController() = default;
AutoclickScrollBubbleController::~AutoclickScrollBubbleController() {
if (bubble_widget_ && !bubble_widget_->IsClosed())
bubble_widget_->CloseNow();
bubble_view_ = nullptr;
scroll_view_ = nullptr;
bubble_widget_ = nullptr;
}
void AutoclickScrollBubbleController::UpdateAnchorRect(
gfx::Rect rect,
views::BubbleBorder::Arrow alignment) {
menu_bubble_rect_ = rect;
menu_bubble_alignment_ = alignment;
if (set_scroll_rect_ || !bubble_view_)
return;
bubble_view_->UpdateAnchorRect(rect, alignment);
}
void AutoclickScrollBubbleController::SetScrollPosition(
gfx::Rect scroll_bounds_in_dips,
const gfx::Point& scroll_point_in_dips) {
if (!bubble_view_)
return;
bubble_view_->UpdateInsets(
gfx::Insets::VH(kBubbleMenuPadding, kBubbleMenuPadding));
aura::Window* window = Shell::GetPrimaryRootWindow();
gfx::Rect work_area =
WorkAreaInsets::ForWindow(window)->user_work_area_bounds();
gfx::Rect on_screen_scroll_bounds(scroll_bounds_in_dips);
on_screen_scroll_bounds.Intersect(work_area);
if (on_screen_scroll_bounds.width() > 2 * kAutoclickScrollMenuSizeDips &&
on_screen_scroll_bounds.height() > 2 * kAutoclickScrollMenuSizeDips) {
set_scroll_rect_ = true;
gfx::Rect anchor =
gfx::Rect(scroll_point_in_dips.x(), scroll_point_in_dips.y(), 0, 0);
anchor.Inset(gfx::Insets::VH(0, -kScrollPointBufferDips));
bubble_view_->UpdateAnchorRect(anchor,
views::BubbleBorder::Arrow::LEFT_TOP);
return;
}
work_area.Inset(kAutoclickScrollMenuSizeDips);
scroll_bounds_in_dips.Inset(-kScrollRectBufferDips);
bool fits_left = scroll_bounds_in_dips.x() > work_area.x();
bool fits_right = scroll_bounds_in_dips.right() < work_area.right();
bool fits_above = scroll_bounds_in_dips.y() > work_area.y();
bool fits_below = scroll_bounds_in_dips.bottom() < work_area.bottom();
std::vector<Position> positions;
if (fits_right) {
positions.push_back(
{scroll_bounds_in_dips.right() - scroll_point_in_dips.x(),
base::i18n::IsRTL() ? views::BubbleBorder::Arrow::RIGHT_CENTER
: views::BubbleBorder::Arrow::LEFT_CENTER,
true});
}
if (fits_left) {
positions.push_back({scroll_point_in_dips.x() - scroll_bounds_in_dips.x(),
base::i18n::IsRTL()
? views::BubbleBorder::Arrow::LEFT_CENTER
: views::BubbleBorder::Arrow::RIGHT_CENTER,
true});
}
if (fits_below) {
positions.push_back(
{scroll_bounds_in_dips.bottom() - scroll_point_in_dips.y(),
views::BubbleBorder::Arrow::TOP_CENTER, false});
}
if (fits_above) {
positions.push_back({scroll_point_in_dips.y() - scroll_bounds_in_dips.y(),
views::BubbleBorder::Arrow::BOTTOM_CENTER, false});
}
set_scroll_rect_ = !positions.empty();
if (!set_scroll_rect_) {
bubble_view_->UpdateInsets(gfx::Insets::TLBR(
0, kBubbleMenuPadding, kBubbleMenuPadding, kBubbleMenuPadding));
UpdateAnchorRect(menu_bubble_rect_, menu_bubble_alignment_);
return;
}
std::stable_sort(positions.begin(), positions.end(), comparePositions);
if (positions.front().is_horizontal) {
bubble_view_->UpdateAnchorRect(
gfx::Rect(scroll_bounds_in_dips.x(), scroll_point_in_dips.y(),
scroll_bounds_in_dips.width(), 0),
positions.at(0).arrow);
} else {
bubble_view_->UpdateAnchorRect(
gfx::Rect(scroll_point_in_dips.x(), scroll_bounds_in_dips.y(), 0,
scroll_bounds_in_dips.height()),
positions.at(0).arrow);
}
}
void AutoclickScrollBubbleController::ShowBubble(
gfx::Rect anchor_rect,
views::BubbleBorder::Arrow alignment) {
if (bubble_widget_)
return;
DCHECK(!bubble_view_);
TrayBubbleView::InitParams init_params;
init_params.delegate = GetWeakPtr();
init_params.parent_window =
Shell::GetContainer(Shell::GetPrimaryRootWindow(),
kShellWindowId_AccessibilityBubbleContainer);
init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
init_params.anchor_rect = anchor_rect;
init_params.insets = gfx::Insets::TLBR(
0, kBubbleMenuPadding, kBubbleMenuPadding, kBubbleMenuPadding);
init_params.preferred_width = kAutoclickScrollMenuSizeDips;
init_params.max_height = kAutoclickScrollMenuSizeDips;
init_params.translucent = true;
init_params.type = TrayBubbleView::TrayBubbleType::kAccessibilityBubble;
bubble_view_ = new AutoclickScrollBubbleView(init_params);
bubble_view_->SetArrow(alignment);
scroll_view_ = new AutoclickScrollView();
scroll_view_->SetBorder(views::CreateEmptyBorder(
gfx::Insets::TLBR(kUnifiedTopShortcutSpacing, 0, 0, 0)));
bubble_view_->AddChildViewRaw(scroll_view_.get());
bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
CollisionDetectionUtils::MarkWindowPriorityForCollisionDetection(
bubble_widget_->GetNativeWindow(),
CollisionDetectionUtils::RelativePriority::kAutomaticClicksScrollMenu);
bubble_view_->InitializeAndShowBubble();
}
void AutoclickScrollBubbleController::CloseBubble() {
if (!bubble_widget_ || bubble_widget_->IsClosed())
return;
bubble_widget_->Close();
}
void AutoclickScrollBubbleController::SetBubbleVisibility(bool is_visible) {
if (!bubble_widget_)
return;
if (is_visible)
bubble_widget_->Show();
else
bubble_widget_->Hide();
}
void AutoclickScrollBubbleController::ClickOnBubble(gfx::Point location_in_dips,
int mouse_event_flags) {
if (!bubble_widget_ || !bubble_view_)
return;
location_in_dips -= bubble_view_->GetBoundsInScreen().OffsetFromOrigin();
const ui::MouseEvent press_event(
ui::EventType::kMousePressed, location_in_dips, location_in_dips,
ui::EventTimeForNow(), mouse_event_flags | ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
const ui::MouseEvent release_event(
ui::EventType::kMouseReleased, location_in_dips, location_in_dips,
ui::EventTimeForNow(), mouse_event_flags | ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
bubble_widget_->GetRootView()->OnMousePressed(press_event);
bubble_widget_->GetRootView()->OnMouseReleased(release_event);
}
bool AutoclickScrollBubbleController::ContainsPointInScreen(
const gfx::Point& point) {
return bubble_view_ && bubble_view_->GetBoundsInScreen().Contains(point);
}
void AutoclickScrollBubbleController::BubbleViewDestroyed() {
bubble_view_ = nullptr;
bubble_widget_ = nullptr;
scroll_view_ = nullptr;
}
std::u16string AutoclickScrollBubbleController::GetAccessibleNameForBubble() {
return l10n_util::GetStringUTF16(IDS_ASH_AUTOCLICK_SCROLL_BUBBLE);
}
void AutoclickScrollBubbleController::HideBubble(
const TrayBubbleView* bubble_view) {
}
}