1 // Copyright 2020 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 "ash/system/accessibility/switch_access_menu_bubble_controller.h"
6 
7 #include "ash/public/cpp/shell_window_ids.h"
8 #include "ash/shell.h"
9 #include "ash/system/accessibility/switch_access_back_button_bubble_controller.h"
10 #include "ash/system/accessibility/switch_access_menu_view.h"
11 #include "ash/system/tray/tray_background_view.h"
12 #include "ash/system/tray/tray_constants.h"
13 #include "ash/system/unified/unified_system_tray_view.h"
14 #include "ash/wm/collision_detection/collision_detection_utils.h"
15 #include "ui/accessibility/ax_enums.mojom.h"
16 
17 namespace ash {
18 
SwitchAccessMenuBubbleController()19 SwitchAccessMenuBubbleController::SwitchAccessMenuBubbleController()
20     : back_button_controller_(
21           std::make_unique<SwitchAccessBackButtonBubbleController>()) {}
22 
~SwitchAccessMenuBubbleController()23 SwitchAccessMenuBubbleController::~SwitchAccessMenuBubbleController() {
24   if (widget_ && !widget_->IsClosed())
25     widget_->CloseNow();
26 }
27 
ShowBackButton(const gfx::Rect & anchor)28 void SwitchAccessMenuBubbleController::ShowBackButton(const gfx::Rect& anchor) {
29   back_button_controller_->ShowBackButton(anchor, /*show_focus_ring=*/true,
30                                           menu_open_);
31 }
32 
ShowMenu(const gfx::Rect & anchor,const std::vector<std::string> & actions_to_show)33 void SwitchAccessMenuBubbleController::ShowMenu(
34     const gfx::Rect& anchor,
35     const std::vector<std::string>& actions_to_show) {
36   menu_open_ = true;
37   if (!widget_) {
38     TrayBubbleView::InitParams init_params;
39     init_params.delegate = this;
40     // Anchor within the overlay container.
41     init_params.parent_window =
42         Shell::GetContainer(Shell::GetPrimaryRootWindow(),
43                             kShellWindowId_AccessibilityBubbleContainer);
44     init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
45     init_params.is_anchored_to_status_area = false;
46     init_params.insets = gfx::Insets(kUnifiedMenuPadding, kUnifiedMenuPadding);
47     init_params.corner_radius = kUnifiedTrayCornerRadius;
48     init_params.has_shadow = false;
49     init_params.translucent = true;
50     bubble_view_ = new TrayBubbleView(init_params);
51     bubble_view_->SetArrow(views::BubbleBorder::Arrow::TOP_LEFT);
52 
53     menu_view_ = new SwitchAccessMenuView();
54     menu_view_->SetBorder(
55         views::CreateEmptyBorder(gfx::Insets(kUnifiedMenuPadding)));
56     bubble_view_->AddChildView(menu_view_);
57 
58     menu_view_->SetPaintToLayer();
59     menu_view_->layer()->SetFillsBoundsOpaquely(false);
60 
61     widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
62     TrayBackgroundView::InitializeBubbleAnimations(widget_);
63     bubble_view_->InitializeAndShowBubble();
64 
65     CollisionDetectionUtils::MarkWindowPriorityForCollisionDetection(
66         widget_->GetNativeWindow(),
67         CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu);
68   }
69 
70   DCHECK(bubble_view_);
71 
72   menu_view_->SetActions(actions_to_show);
73   bubble_view_->SetPreferredWidth(menu_view_->GetBubbleWidthDip());
74   bubble_view_->ChangeAnchorRect(anchor);
75 
76   gfx::Rect new_bounds = widget_->GetWindowBoundsInScreen();
77 
78   // Adjust the bounds to fit entirely within the screen.
79   gfx::Rect display_bounds =
80       display::Screen::GetScreen()->GetDisplayMatching(new_bounds).bounds();
81   new_bounds.AdjustToFit(display_bounds);
82 
83   // Update the preferred bounds based on other system windows.
84   gfx::Rect resting_bounds = CollisionDetectionUtils::AvoidObstacles(
85       display::Screen::GetScreen()->GetDisplayNearestWindow(
86           widget_->GetNativeWindow()),
87       new_bounds, CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu);
88 
89   widget_->SetBounds(resting_bounds);
90   widget_->Show();
91   bubble_view_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
92                                          true);
93 
94   // The resting bounds includes padding on each side of the menu.
95   // Remove that before passing to the back button controller so the back button
96   // appears in the correct position.
97   resting_bounds.Inset(kUnifiedMenuPadding, kUnifiedMenuPadding);
98   back_button_controller_->ShowBackButton(resting_bounds,
99                                           /*show_focus_ring=*/false,
100                                           /*for_menu=*/true);
101 }
102 
HideBackButton()103 void SwitchAccessMenuBubbleController::HideBackButton() {
104   if (widget_ && widget_->IsVisible())
105     back_button_controller_->HideFocusRing();
106   else
107     back_button_controller_->Hide();
108 }
109 
HideMenuBubble()110 void SwitchAccessMenuBubbleController::HideMenuBubble() {
111   menu_open_ = false;
112   back_button_controller_->Hide();
113   if (widget_)
114     widget_->Hide();
115   if (bubble_view_)
116     bubble_view_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
117                                            true);
118 }
119 
BubbleViewDestroyed()120 void SwitchAccessMenuBubbleController::BubbleViewDestroyed() {
121   bubble_view_ = nullptr;
122   menu_view_ = nullptr;
123   widget_ = nullptr;
124 }
125 
126 }  // namespace ash
127