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/floating_accessibility_view.h"
6
7 #include "ash/accessibility/accessibility_controller_impl.h"
8 #include "ash/keyboard/ui/keyboard_ui_controller.h"
9 #include "ash/public/cpp/system_tray.h"
10 #include "ash/resources/vector_icons/vector_icons.h"
11 #include "ash/root_window_controller.h"
12 #include "ash/shelf/shelf.h"
13 #include "ash/shell.h"
14 #include "ash/strings/grit/ash_strings.h"
15 #include "ash/style/ash_color_provider.h"
16 #include "ash/system/accessibility/dictation_button_tray.h"
17 #include "ash/system/accessibility/floating_menu_button.h"
18 #include "ash/system/accessibility/select_to_speak_tray.h"
19 #include "ash/system/tray/tray_constants.h"
20 #include "ash/system/virtual_keyboard/virtual_keyboard_tray.h"
21 #include "base/bind.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/views/controls/separator.h"
24 #include "ui/views/layout/box_layout.h"
25 #include "ui/views/metadata/metadata_impl_macros.h"
26
27 namespace ash {
28
29 namespace {
30
31 // These constants are defined in DIP.
32 constexpr int kPanelPositionButtonPadding = 14;
33 constexpr int kPanelPositionButtonSize = 36;
34 constexpr int kSeparatorHeight = 16;
35
36 // The view that hides itself if all of its children are not visible.
37 class DynamicRowView : public views::View {
38 public:
39 DynamicRowView() = default;
40
41 protected:
42 // views::View:
ChildVisibilityChanged(views::View * child)43 void ChildVisibilityChanged(views::View* child) override {
44 bool any_visible = false;
45 for (auto* view : children()) {
46 any_visible |= view->GetVisible();
47 }
48 SetVisible(any_visible);
49 }
50 };
51
CreateSeparator()52 std::unique_ptr<views::Separator> CreateSeparator() {
53 auto separator = std::make_unique<views::Separator>();
54 separator->SetColor(AshColorProvider::Get()->GetContentLayerColor(
55 AshColorProvider::ContentLayerType::kSeparatorColor));
56 separator->SetPreferredHeight(kSeparatorHeight);
57 int total_height = kUnifiedTopShortcutSpacing * 2 + kTrayItemSize;
58 int separator_spacing = (total_height - kSeparatorHeight) / 2;
59 separator->SetBorder(views::CreateEmptyBorder(
60 separator_spacing - kUnifiedTopShortcutSpacing, 0, separator_spacing, 0));
61 return separator;
62 }
63
CreateButtonRowContainer(int padding)64 std::unique_ptr<views::View> CreateButtonRowContainer(int padding) {
65 auto button_container = std::make_unique<DynamicRowView>();
66 button_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
67 views::BoxLayout::Orientation::kHorizontal,
68 gfx::Insets(0, padding, padding, padding), padding));
69 return button_container;
70 }
71
GetDescriptionForMovedToPosition(FloatingMenuPosition position)72 std::string GetDescriptionForMovedToPosition(FloatingMenuPosition position) {
73 switch (position) {
74 case FloatingMenuPosition::kBottomRight:
75 return l10n_util::GetStringUTF8(
76 IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_BOTTOM_RIGHT);
77 case FloatingMenuPosition::kBottomLeft:
78 return l10n_util::GetStringUTF8(
79 IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_BOTTOM_LEFT);
80 case FloatingMenuPosition::kTopLeft:
81 return l10n_util::GetStringUTF8(
82 IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_TOP_LEFT);
83 case FloatingMenuPosition::kTopRight:
84 return l10n_util::GetStringUTF8(
85 IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_TOP_RIGHT);
86 case FloatingMenuPosition::kSystemDefault:
87 NOTREACHED();
88 return std::string();
89 }
90 }
91
92 } // namespace
93
FloatingAccessibilityBubbleView(const TrayBubbleView::InitParams & init_params)94 FloatingAccessibilityBubbleView::FloatingAccessibilityBubbleView(
95 const TrayBubbleView::InitParams& init_params)
96 : TrayBubbleView(init_params) {
97 // Intercept ESC keypresses.
98 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
99 }
100
101 FloatingAccessibilityBubbleView::~FloatingAccessibilityBubbleView() = default;
102
IsAnchoredToStatusArea() const103 bool FloatingAccessibilityBubbleView::IsAnchoredToStatusArea() const {
104 return false;
105 }
106
AcceleratorPressed(const ui::Accelerator & accelerator)107 bool FloatingAccessibilityBubbleView::AcceleratorPressed(
108 const ui::Accelerator& accelerator) {
109 DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
110 GetWidget()->Deactivate();
111 return true;
112 }
113
BEGIN_METADATA(FloatingAccessibilityBubbleView,TrayBubbleView)114 BEGIN_METADATA(FloatingAccessibilityBubbleView, TrayBubbleView)
115 END_METADATA
116
117 FloatingAccessibilityView::FloatingAccessibilityView(Delegate* delegate)
118 : delegate_(delegate) {
119
120 Shelf* shelf = RootWindowController::ForTargetRootWindow()->shelf();
121 std::unique_ptr<views::View> feature_buttons_container =
122 CreateButtonRowContainer(kPanelPositionButtonPadding);
123 dictation_button_ = feature_buttons_container->AddChildView(
124 std::make_unique<DictationButtonTray>(shelf));
125 select_to_speak_button_ = feature_buttons_container->AddChildView(
126 std::make_unique<SelectToSpeakTray>(shelf));
127 virtual_keyboard_button_ = feature_buttons_container->AddChildView(
128 std::make_unique<VirtualKeyboardTray>(shelf));
129
130 // It will be visible again as soon as any of the children becomes visible.
131 feature_buttons_container->SetVisible(false);
132
133 std::unique_ptr<views::View> tray_button_container =
134 CreateButtonRowContainer(kUnifiedTopShortcutSpacing);
135 a11y_tray_button_ =
136 tray_button_container->AddChildView(std::make_unique<FloatingMenuButton>(
137 base::BindRepeating(
138 &FloatingAccessibilityView::OnA11yTrayButtonPressed,
139 base::Unretained(this)),
140 kUnifiedMenuAccessibilityIcon,
141 IDS_ASH_FLOATING_ACCESSIBILITY_DETAILED_MENU_OPEN,
142 /*flip_for_rtl*/ true));
143
144 std::unique_ptr<views::View> position_button_container =
145 CreateButtonRowContainer(kPanelPositionButtonPadding);
146 position_button_ = position_button_container->AddChildView(
147 std::make_unique<FloatingMenuButton>(
148 base::BindRepeating(
149 &FloatingAccessibilityView::OnPositionButtonPressed,
150 base::Unretained(this)),
151 kAutoclickPositionBottomLeftIcon,
152 IDS_ASH_AUTOCLICK_OPTION_CHANGE_POSITION, /*flip_for_rtl*/ false,
153 kPanelPositionButtonSize, false, /* is_a11y_togglable */ false));
154
155 AddChildView(std::move(feature_buttons_container));
156 AddChildView(std::move(tray_button_container));
157 AddChildView(CreateSeparator());
158 AddChildView(std::move(position_button_container));
159
160 // Set view IDs for testing.
161 position_button_->SetID(static_cast<int>(ButtonId::kPosition));
162 a11y_tray_button_->SetID(static_cast<int>(ButtonId::kSettingsList));
163 dictation_button_->SetID(static_cast<int>(ButtonId::kDictation));
164 select_to_speak_button_->SetID(static_cast<int>(ButtonId::kSelectToSpeak));
165 virtual_keyboard_button_->SetID(static_cast<int>(ButtonId::kVirtualKeyboard));
166 }
167
~FloatingAccessibilityView()168 FloatingAccessibilityView::~FloatingAccessibilityView() {}
169
Initialize()170 void FloatingAccessibilityView::Initialize() {
171 for (auto* feature_view :
172 {dictation_button_, select_to_speak_button_, virtual_keyboard_button_}) {
173 feature_view->Initialize();
174 feature_view->CalculateTargetBounds();
175 feature_view->UpdateLayout();
176 feature_view->AddObserver(this);
177 }
178 }
179
SetMenuPosition(FloatingMenuPosition position)180 void FloatingAccessibilityView::SetMenuPosition(FloatingMenuPosition position) {
181 switch (position) {
182 case FloatingMenuPosition::kBottomRight:
183 position_button_->SetVectorIcon(kAutoclickPositionBottomRightIcon);
184 return;
185 case FloatingMenuPosition::kBottomLeft:
186 position_button_->SetVectorIcon(kAutoclickPositionBottomLeftIcon);
187 return;
188 case FloatingMenuPosition::kTopLeft:
189 position_button_->SetVectorIcon(kAutoclickPositionTopLeftIcon);
190 return;
191 case FloatingMenuPosition::kTopRight:
192 position_button_->SetVectorIcon(kAutoclickPositionTopRightIcon);
193 return;
194 case FloatingMenuPosition::kSystemDefault:
195 position_button_->SetVectorIcon(base::i18n::IsRTL()
196 ? kAutoclickPositionBottomLeftIcon
197 : kAutoclickPositionBottomRightIcon);
198 return;
199 }
200 }
201
SetDetailedViewShown(bool shown)202 void FloatingAccessibilityView::SetDetailedViewShown(bool shown) {
203 a11y_tray_button_->SetToggled(shown);
204 }
205
FocusOnDetailedViewButton()206 void FloatingAccessibilityView::FocusOnDetailedViewButton() {
207 a11y_tray_button_->RequestFocus();
208 }
209
OnA11yTrayButtonPressed()210 void FloatingAccessibilityView::OnA11yTrayButtonPressed() {
211 delegate_->OnDetailedMenuEnabled(!a11y_tray_button_->GetToggled());
212 }
213
OnPositionButtonPressed()214 void FloatingAccessibilityView::OnPositionButtonPressed() {
215 FloatingMenuPosition new_position;
216 // Rotate clockwise throughout the screen positions.
217 switch (Shell::Get()->accessibility_controller()->GetFloatingMenuPosition()) {
218 case FloatingMenuPosition::kBottomRight:
219 new_position = FloatingMenuPosition::kBottomLeft;
220 break;
221 case FloatingMenuPosition::kBottomLeft:
222 new_position = FloatingMenuPosition::kTopLeft;
223 break;
224 case FloatingMenuPosition::kTopLeft:
225 new_position = FloatingMenuPosition::kTopRight;
226 break;
227 case FloatingMenuPosition::kTopRight:
228 new_position = FloatingMenuPosition::kBottomRight;
229 break;
230 case FloatingMenuPosition::kSystemDefault:
231 new_position = base::i18n::IsRTL() ? FloatingMenuPosition::kTopLeft
232 : FloatingMenuPosition::kBottomLeft;
233 break;
234 }
235 Shell::Get()->accessibility_controller()->SetFloatingMenuPosition(
236 new_position);
237 Shell::Get()
238 ->accessibility_controller()
239 ->TriggerAccessibilityAlertWithMessage(
240 GetDescriptionForMovedToPosition(new_position));
241 }
242
OnViewVisibilityChanged(views::View * observed_view,views::View * starting_view)243 void FloatingAccessibilityView::OnViewVisibilityChanged(
244 views::View* observed_view,
245 views::View* starting_view) {
246 if (observed_view != starting_view)
247 return;
248 delegate_->OnLayoutChanged();
249 }
250
251 BEGIN_METADATA(FloatingAccessibilityView, views::BoxLayoutView)
252 END_METADATA
253
254 } // namespace ash
255