| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // 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_controller.h" |
| |
| #include "ash/accessibility/accessibility_controller_impl.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/accessibility/floating_menu_utils.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 "base/check.h" |
| #include "base/notreached.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr int kFloatingMenuHeight = 64; |
| constexpr base::TimeDelta kAnimationDuration = |
| base::TimeDelta::FromMilliseconds(150); |
| |
| } // namespace |
| |
| FloatingAccessibilityController::FloatingAccessibilityController( |
| AccessibilityControllerImpl* accessibility_controller) |
| : accessibility_controller_(accessibility_controller) { |
| Shell::Get()->locale_update_controller()->AddObserver(this); |
| accessibility_controller_->AddObserver(this); |
| } |
| FloatingAccessibilityController::~FloatingAccessibilityController() { |
| Shell::Get()->locale_update_controller()->RemoveObserver(this); |
| accessibility_controller_->RemoveObserver(this); |
| if (bubble_widget_ && !bubble_widget_->IsClosed()) |
| bubble_widget_->CloseNow(); |
| } |
| |
| void FloatingAccessibilityController::Show(FloatingMenuPosition position) { |
| // Kiosk check. |
| if (!Shell::Get()->session_controller()->IsRunningInAppMode()) { |
| NOTREACHED() |
| << "Floating accessibility menu can only be run in a kiosk session."; |
| return; |
| } |
| |
| DCHECK(!bubble_view_); |
| |
| TrayBubbleView::InitParams init_params; |
| init_params.delegate = this; |
| // Our view uses SettingsBubbleContainer since it is activatable and is |
| // included in the collision detection logic. |
| init_params.parent_window = Shell::GetContainer( |
| Shell::GetPrimaryRootWindow(), kShellWindowId_SettingBubbleContainer); |
| init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect; |
| // The widget's shadow is drawn below and on the sides of the view, with a |
| // width of kCollisionWindowWorkAreaInsetsDp. Set the top inset to 0 to ensure |
| // the detailed view is drawn at kCollisionWindowWorkAreaInsetsDp above the |
| // bubble menu when the position is at the bottom of the screen. The space |
| // between the bubbles belongs to the detailed view bubble's shadow. |
| init_params.insets = gfx::Insets(0, kCollisionWindowWorkAreaInsetsDp, |
| kCollisionWindowWorkAreaInsetsDp, |
| kCollisionWindowWorkAreaInsetsDp); |
| init_params.corner_radius = kUnifiedTrayCornerRadius; |
| init_params.has_shadow = false; |
| init_params.max_height = kFloatingMenuHeight; |
| init_params.translucent = true; |
| init_params.close_on_deactivate = false; |
| bubble_view_ = new FloatingAccessibilityBubbleView(init_params); |
| |
| menu_view_ = new FloatingAccessibilityView(this); |
| menu_view_->SetBorder( |
| views::CreateEmptyBorder(kUnifiedTopShortcutSpacing, 0, 0, 0)); |
| bubble_view_->AddChildView(menu_view_); |
| bubble_view_->SetFocusBehavior( |
| ActionableView::FocusBehavior::ACCESSIBLE_ONLY); |
| |
| menu_view_->SetPaintToLayer(); |
| menu_view_->layer()->SetFillsBoundsOpaquely(false); |
| |
| bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_); |
| bubble_view_->SetCanActivate(true); |
| TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_); |
| bubble_view_->InitializeAndShowBubble(); |
| |
| menu_view_->Initialize(); |
| |
| SetMenuPosition(position); |
| } |
| |
| void FloatingAccessibilityController::SetMenuPosition( |
| FloatingMenuPosition new_position) { |
| if (!menu_view_ || !bubble_view_ || !bubble_widget_) |
| return; |
| |
| // Update the menu view's UX if the position has changed, or if it's not the |
| // default position (because that can change with language direction). |
| if (position_ != new_position || |
| new_position == FloatingMenuPosition::kSystemDefault) { |
| menu_view_->SetMenuPosition(new_position); |
| } |
| position_ = new_position; |
| |
| // If this is the default system position, pick the position based on the |
| // language direction. |
| if (new_position == FloatingMenuPosition::kSystemDefault) |
| new_position = DefaultSystemFloatingMenuPosition(); |
| |
| gfx::Rect new_bounds = GetOnScreenBoundsForFloatingMenuPosition( |
| menu_view_->GetPreferredSize(), new_position); |
| |
| gfx::Rect resting_bounds = |
| CollisionDetectionUtils::AdjustToFitMovementAreaByGravity( |
| display::Screen::GetScreen()->GetDisplayNearestWindow( |
| bubble_widget_->GetNativeWindow()), |
| new_bounds); |
| // Un-inset the bounds to get the widget's bounds, which includes the drop |
| // shadow. |
| resting_bounds.Inset(-kCollisionWindowWorkAreaInsetsDp, 0, |
| -kCollisionWindowWorkAreaInsetsDp, |
| -kCollisionWindowWorkAreaInsetsDp); |
| |
| if (bubble_widget_->GetWindowBoundsInScreen() == resting_bounds) |
| return; |
| |
| ui::ScopedLayerAnimationSettings settings( |
| bubble_widget_->GetLayer()->GetAnimator()); |
| settings.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| settings.SetTransitionDuration(kAnimationDuration); |
| settings.SetTweenType(gfx::Tween::EASE_OUT); |
| bubble_widget_->SetBounds(resting_bounds); |
| |
| if (detailed_menu_controller_) { |
| detailed_menu_controller_->UpdateAnchorRect( |
| resting_bounds, GetAnchorAlignmentForFloatingMenuPosition(position_)); |
| } |
| } |
| |
| void FloatingAccessibilityController::FocusOnMenu() { |
| bubble_view_->GetFocusManager()->ClearFocus(); |
| bubble_view_->GetFocusManager()->AdvanceFocus(false /* reverse */); |
| } |
| |
| void FloatingAccessibilityController::OnDetailedMenuEnabled(bool enabled) { |
| if (enabled) { |
| detailed_menu_controller_ = |
| std::make_unique<FloatingAccessibilityDetailedController>(this); |
| gfx::Rect anchor_rect = bubble_view_->GetBoundsInScreen(); |
| anchor_rect.Inset(-kCollisionWindowWorkAreaInsetsDp, 0, |
| -kCollisionWindowWorkAreaInsetsDp, |
| -kCollisionWindowWorkAreaInsetsDp); |
| detailed_menu_controller_->Show( |
| anchor_rect, GetAnchorAlignmentForFloatingMenuPosition(position_)); |
| menu_view_->SetDetailedViewShown(true); |
| } else { |
| detailed_menu_controller_.reset(); |
| // We may need to update the autoclick bounds. |
| Shell::Get() |
| ->accessibility_controller() |
| ->UpdateAutoclickMenuBoundsIfNeeded(); |
| } |
| } |
| |
| void FloatingAccessibilityController::OnLayoutChanged() { |
| if (on_layout_change_) |
| on_layout_change_.Run(); |
| SetMenuPosition(position_); |
| } |
| |
| void FloatingAccessibilityController::OnDetailedMenuClosed() { |
| detailed_menu_controller_.reset(); |
| |
| if (!menu_view_) |
| return; |
| menu_view_->SetDetailedViewShown(false); |
| if (bubble_widget_->IsActive()) |
| menu_view_->FocusOnDetailedViewButton(); |
| } |
| |
| views::Widget* FloatingAccessibilityController::GetBubbleWidget() { |
| return bubble_widget_; |
| } |
| |
| void FloatingAccessibilityController::BubbleViewDestroyed() { |
| bubble_view_ = nullptr; |
| bubble_widget_ = nullptr; |
| menu_view_ = nullptr; |
| } |
| |
| std::u16string FloatingAccessibilityController::GetAccessibleNameForBubble() { |
| return l10n_util::GetStringUTF16(IDS_ASH_FLOATING_ACCESSIBILITY_MAIN_MENU); |
| } |
| |
| void FloatingAccessibilityController::OnLocaleChanged() { |
| // Layout update is needed when language changes between LTR and RTL, if the |
| // position is the system default. |
| if (position_ == FloatingMenuPosition::kSystemDefault) |
| SetMenuPosition(position_); |
| } |
| |
| void FloatingAccessibilityController::OnAccessibilityStatusChanged() { |
| // Some features may change the available screen area(docked magnifier), we |
| // will update the location of the menu in such cases. |
| SetMenuPosition(position_); |
| if (detailed_menu_controller_) |
| detailed_menu_controller_->OnAccessibilityStatusChanged(); |
| } |
| |
| } // namespace ash |