| // Copyright 2020 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/system/accessibility/floating_accessibility_view.h" |
| |
| #include "ash/accessibility/accessibility_controller.h" |
| #include "ash/ime/ime_controller_impl.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.h" |
| #include "ash/public/cpp/keyboard/keyboard_controller.h" |
| #include "ash/public/cpp/system_tray.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/ash_color_provider.h" |
| #include "ash/system/accessibility/dictation_button_tray.h" |
| #include "ash/system/accessibility/floating_menu_button.h" |
| #include "ash/system/accessibility/select_to_speak/select_to_speak_tray.h" |
| #include "ash/system/ime_menu/ime_menu_tray.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "ash/system/tray/tray_bubble_view.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/virtual_keyboard/virtual_keyboard_tray.h" |
| #include "base/functional/bind.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/color/color_id.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/view.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // These constants are defined in DIP. |
| constexpr int kPanelPositionButtonPadding = 14; |
| constexpr int kPanelPositionButtonSize = 36; |
| constexpr int kSeparatorHeight = 16; |
| |
| // The view that hides itself if all of its children are not visible. |
| class DynamicRowView : public views::View { |
| public: |
| DynamicRowView() = default; |
| |
| protected: |
| // views::View: |
| void ChildVisibilityChanged(views::View* child) override { |
| bool any_visible = false; |
| for (views::View* view : children()) { |
| any_visible |= view->GetVisible(); |
| } |
| SetVisible(any_visible); |
| } |
| }; |
| |
| std::unique_ptr<views::Separator> CreateSeparator() { |
| auto separator = std::make_unique<views::Separator>(); |
| separator->SetColorId(ui::kColorAshSystemUIMenuSeparator); |
| separator->SetPreferredLength(kSeparatorHeight); |
| int total_height = kUnifiedTopShortcutSpacing * 2 + kTrayItemSize; |
| int separator_spacing = (total_height - kSeparatorHeight) / 2; |
| separator->SetBorder(views::CreateEmptyBorder( |
| gfx::Insets::TLBR(separator_spacing - kUnifiedTopShortcutSpacing, 0, |
| separator_spacing, 0))); |
| return separator; |
| } |
| |
| std::unique_ptr<views::View> CreateButtonRowContainer(int padding) { |
| auto button_container = std::make_unique<DynamicRowView>(); |
| button_container->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets::TLBR(0, padding, padding, padding), padding)); |
| return button_container; |
| } |
| |
| std::string GetDescriptionForMovedToPosition(FloatingMenuPosition position) { |
| switch (position) { |
| case FloatingMenuPosition::kBottomRight: |
| return l10n_util::GetStringUTF8( |
| IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_BOTTOM_RIGHT); |
| case FloatingMenuPosition::kBottomLeft: |
| return l10n_util::GetStringUTF8( |
| IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_BOTTOM_LEFT); |
| case FloatingMenuPosition::kTopLeft: |
| return l10n_util::GetStringUTF8( |
| IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_TOP_LEFT); |
| case FloatingMenuPosition::kTopRight: |
| return l10n_util::GetStringUTF8( |
| IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU_MOVED_TOP_RIGHT); |
| case FloatingMenuPosition::kSystemDefault: |
| NOTREACHED(); |
| return std::string(); |
| } |
| } |
| |
| bool IsKioskImeButtonEnabled() { |
| return Shell::Get()->session_controller()->IsRunningInAppMode() && |
| base::FeatureList::IsEnabled(features::kKioskEnableImeButton) && |
| Shell::Get()->ime_controller()->GetVisibleImes().size() > 1; |
| } |
| |
| } // namespace |
| |
| FloatingAccessibilityBubbleView::FloatingAccessibilityBubbleView( |
| const TrayBubbleView::InitParams& init_params) |
| : TrayBubbleView(init_params) { |
| // Intercept ESC keypresses. |
| AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); |
| } |
| |
| FloatingAccessibilityBubbleView::~FloatingAccessibilityBubbleView() = default; |
| |
| bool FloatingAccessibilityBubbleView::IsAnchoredToStatusArea() const { |
| return false; |
| } |
| |
| bool FloatingAccessibilityBubbleView::AcceleratorPressed( |
| const ui::Accelerator& accelerator) { |
| DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE); |
| GetWidget()->Deactivate(); |
| return true; |
| } |
| |
| void FloatingAccessibilityBubbleView::GetAccessibleNodeData( |
| ui::AXNodeData* node_data) { |
| // Preset values to avoid AccessibilityPaintChecks. |
| node_data->role = ax::mojom::Role::kWindow; |
| node_data->SetNameExplicitlyEmpty(); |
| TrayBubbleView::GetAccessibleNodeData(node_data); |
| } |
| |
| BEGIN_METADATA(FloatingAccessibilityBubbleView) |
| END_METADATA |
| |
| FloatingAccessibilityView::FloatingAccessibilityView(Delegate* delegate) |
| : delegate_(delegate) { |
| Shelf* shelf = RootWindowController::ForTargetRootWindow()->shelf(); |
| std::unique_ptr<views::View> feature_buttons_container = |
| CreateButtonRowContainer(kPanelPositionButtonPadding); |
| dictation_button_ = feature_buttons_container->AddChildView( |
| std::make_unique<DictationButtonTray>( |
| shelf, TrayBackgroundViewCatalogName::kDictationAccesibilityWindow)); |
| select_to_speak_button_ = feature_buttons_container->AddChildView( |
| std::make_unique<SelectToSpeakTray>( |
| shelf, |
| TrayBackgroundViewCatalogName::kSelectToSpeakAccessibilityWindow)); |
| virtual_keyboard_button_ = feature_buttons_container->AddChildView( |
| std::make_unique<VirtualKeyboardTray>( |
| shelf, |
| TrayBackgroundViewCatalogName::kVirtualKeyboardAccessibilityWindow)); |
| |
| // It will be visible again as soon as any of the children becomes visible. |
| feature_buttons_container->SetVisible(false); |
| |
| std::unique_ptr<views::View> tray_button_container = |
| CreateButtonRowContainer(kUnifiedTopShortcutSpacing); |
| a11y_tray_button_ = |
| tray_button_container->AddChildView(std::make_unique<FloatingMenuButton>( |
| base::BindRepeating( |
| &FloatingAccessibilityView::OnA11yTrayButtonPressed, |
| base::Unretained(this)), |
| kUnifiedMenuAccessibilityIcon, |
| IDS_ASH_FLOATING_ACCESSIBILITY_DETAILED_MENU_OPEN, |
| /*flip_for_rtl*/ true)); |
| |
| std::unique_ptr<views::View> position_button_container = |
| CreateButtonRowContainer(kPanelPositionButtonPadding); |
| position_button_ = position_button_container->AddChildView( |
| std::make_unique<FloatingMenuButton>( |
| base::BindRepeating( |
| &FloatingAccessibilityView::OnPositionButtonPressed, |
| base::Unretained(this)), |
| kAutoclickPositionBottomLeftIcon, |
| IDS_ASH_AUTOCLICK_OPTION_CHANGE_POSITION, /*flip_for_rtl*/ false, |
| kPanelPositionButtonSize, false, /* is_a11y_togglable */ false)); |
| |
| if (IsKioskImeButtonEnabled()) { |
| Shell::Get()->ime_controller()->SetExtraInputOptionsEnabledState( |
| /*is_extra_input_options_enabled*/ false, /*is_emoji_enabled*/ false, |
| /*is_handwriting_enabled*/ false, /*is_voice_enabled*/ false); |
| std::unique_ptr<views::View> ime_button_container = |
| CreateButtonRowContainer(kPanelPositionButtonPadding); |
| ime_button_ = ime_button_container->AddChildView( |
| std::make_unique<ImeMenuTray>(shelf)); |
| ime_button_container->SetVisible(true); |
| ime_button_->SetVisiblePreferred(true); |
| |
| AddChildView(std::move(ime_button_container)); |
| AddChildView(CreateSeparator()); |
| } |
| |
| AddChildView(std::move(feature_buttons_container)); |
| AddChildView(std::move(tray_button_container)); |
| AddChildView(CreateSeparator()); |
| AddChildView(std::move(position_button_container)); |
| |
| // Set view IDs for testing. |
| position_button_->SetID(static_cast<int>(ButtonId::kPosition)); |
| a11y_tray_button_->SetID(static_cast<int>(ButtonId::kSettingsList)); |
| dictation_button_->SetID(static_cast<int>(ButtonId::kDictation)); |
| select_to_speak_button_->SetID(static_cast<int>(ButtonId::kSelectToSpeak)); |
| virtual_keyboard_button_->SetID(static_cast<int>(ButtonId::kVirtualKeyboard)); |
| if (IsKioskImeButtonEnabled()) { |
| ime_button_->SetID(static_cast<int>(ButtonId::kIme)); |
| } |
| } |
| |
| FloatingAccessibilityView::~FloatingAccessibilityView() { |
| KeyboardController::Get()->RemoveObserver(this); |
| Shell::Get()->system_tray_notifier()->RemoveSystemTrayObserver(this); |
| } |
| |
| void FloatingAccessibilityView::Initialize() { |
| Shell::Get()->system_tray_notifier()->AddSystemTrayObserver(this); |
| KeyboardController::Get()->AddObserver(this); |
| for (TrayBackgroundView* feature_view : { |
| dictation_button_, |
| select_to_speak_button_, |
| virtual_keyboard_button_, |
| }) { |
| feature_view->Initialize(); |
| feature_view->CalculateTargetBounds(); |
| feature_view->UpdateLayout(); |
| feature_view->AddObserver(this); |
| } |
| if (IsKioskImeButtonEnabled()) { |
| ime_button_->Initialize(); |
| ime_button_->CalculateTargetBounds(); |
| ime_button_->UpdateLayout(); |
| ime_button_->AddObserver(this); |
| ime_button_->SetVisible(true); |
| } |
| } |
| |
| void FloatingAccessibilityView::SetMenuPosition(FloatingMenuPosition position) { |
| switch (position) { |
| case FloatingMenuPosition::kBottomRight: |
| position_button_->SetVectorIcon(kAutoclickPositionBottomRightIcon); |
| return; |
| case FloatingMenuPosition::kBottomLeft: |
| position_button_->SetVectorIcon(kAutoclickPositionBottomLeftIcon); |
| return; |
| case FloatingMenuPosition::kTopLeft: |
| position_button_->SetVectorIcon(kAutoclickPositionTopLeftIcon); |
| return; |
| case FloatingMenuPosition::kTopRight: |
| position_button_->SetVectorIcon(kAutoclickPositionTopRightIcon); |
| return; |
| case FloatingMenuPosition::kSystemDefault: |
| position_button_->SetVectorIcon(base::i18n::IsRTL() |
| ? kAutoclickPositionBottomLeftIcon |
| : kAutoclickPositionBottomRightIcon); |
| return; |
| } |
| } |
| |
| void FloatingAccessibilityView::SetDetailedViewShown(bool shown) { |
| a11y_tray_button_->SetToggled(shown); |
| } |
| |
| void FloatingAccessibilityView::FocusOnDetailedViewButton() { |
| a11y_tray_button_->RequestFocus(); |
| } |
| |
| void FloatingAccessibilityView::OnA11yTrayButtonPressed() { |
| delegate_->OnDetailedMenuEnabled(!a11y_tray_button_->GetToggled()); |
| } |
| |
| void FloatingAccessibilityView::OnPositionButtonPressed() { |
| FloatingMenuPosition new_position; |
| // Rotate clockwise throughout the screen positions. |
| switch (Shell::Get()->accessibility_controller()->GetFloatingMenuPosition()) { |
| case FloatingMenuPosition::kBottomRight: |
| new_position = FloatingMenuPosition::kBottomLeft; |
| break; |
| case FloatingMenuPosition::kBottomLeft: |
| new_position = FloatingMenuPosition::kTopLeft; |
| break; |
| case FloatingMenuPosition::kTopLeft: |
| new_position = FloatingMenuPosition::kTopRight; |
| break; |
| case FloatingMenuPosition::kTopRight: |
| new_position = FloatingMenuPosition::kBottomRight; |
| break; |
| case FloatingMenuPosition::kSystemDefault: |
| new_position = base::i18n::IsRTL() ? FloatingMenuPosition::kTopLeft |
| : FloatingMenuPosition::kBottomLeft; |
| break; |
| } |
| Shell::Get()->accessibility_controller()->SetFloatingMenuPosition( |
| new_position); |
| Shell::Get() |
| ->accessibility_controller() |
| ->TriggerAccessibilityAlertWithMessage( |
| GetDescriptionForMovedToPosition(new_position)); |
| } |
| |
| void FloatingAccessibilityView::OnViewVisibilityChanged( |
| views::View* observed_view, |
| views::View* starting_view) { |
| if (observed_view != starting_view) |
| return; |
| delegate_->OnLayoutChanged(); |
| } |
| |
| void FloatingAccessibilityView::OnFocusLeavingSystemTray(bool reverse) {} |
| |
| void FloatingAccessibilityView::OnImeMenuTrayBubbleShown() { |
| delegate_->OnDetailedMenuEnabled(false); |
| } |
| |
| void FloatingAccessibilityView::OnKeyboardVisibilityChanged(bool visible) { |
| // To avoid the collision with the virtual keyboard |
| // Accessibility tray is closed after opening the virtual keyboard tray |
| if (visible) |
| delegate_->OnDetailedMenuEnabled(false); |
| } |
| |
| BEGIN_METADATA(FloatingAccessibilityView) |
| END_METADATA |
| |
| } // namespace ash |