#include "ash/system/accessibility/floating_accessibility_controller.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/bubble/bubble_constants.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/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
namespace ash {
namespace {
constexpr int kFloatingMenuHeight = 64;
constexpr base::TimeDelta kAnimationDuration = base::Milliseconds(150);
class ScopedBubbleViewActivator {
public:
ScopedBubbleViewActivator(ash::FloatingAccessibilityBubbleView* bubble_view,
std::u16string accessible_name)
: bubble_view_(bubble_view) {
DCHECK(bubble_view_);
bubble_view_->SetCanActivate(true);
bubble_view_->GetViewAccessibility().SetName(accessible_name);
}
ScopedBubbleViewActivator(const ScopedBubbleViewActivator&) = delete;
ScopedBubbleViewActivator& operator=(const ScopedBubbleViewActivator&) =
delete;
~ScopedBubbleViewActivator() {
bubble_view_->SetCanActivate(false);
bubble_view_->GetViewAccessibility().SetName(std::u16string());
}
private:
raw_ptr<ash::FloatingAccessibilityBubbleView> bubble_view_ = nullptr;
};
}
FloatingAccessibilityController::FloatingAccessibilityController(
AccessibilityController* 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) {
if (!Shell::Get()->session_controller()->IsRunningInAppMode()) {
NOTREACHED()
<< "Floating accessibility menu can only be run in a kiosk session.";
}
DCHECK(!bubble_view_);
TrayBubbleView::InitParams init_params;
init_params.delegate = GetWeakPtr();
init_params.parent_window = Shell::GetContainer(
Shell::GetPrimaryRootWindow(), kShellWindowId_SettingBubbleContainer);
init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
init_params.insets = gfx::Insets::TLBR(0, kCollisionWindowWorkAreaInsetsDp,
kCollisionWindowWorkAreaInsetsDp,
kCollisionWindowWorkAreaInsetsDp);
init_params.max_height = kFloatingMenuHeight;
init_params.translucent = true;
init_params.close_on_deactivate = false;
init_params.type = TrayBubbleView::TrayBubbleType::kAccessibilityBubble;
bubble_view_ = new FloatingAccessibilityBubbleView(init_params);
menu_view_ = new FloatingAccessibilityView(this);
menu_view_->SetBorder(views::CreateEmptyBorder(
gfx::Insets::TLBR(kUnifiedTopShortcutSpacing, 0, 0, 0)));
bubble_view_->AddChildViewRaw(menu_view_.get());
bubble_view_->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
bubble_view_->SetCanActivate(false);
TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
bubble_view_->InitializeAndShowBubble();
UpdateOpacity();
menu_view_->Initialize();
SetMenuPosition(position);
}
void FloatingAccessibilityController::SetMenuPosition(
FloatingMenuPosition new_position) {
if (!menu_view_ || !bubble_view_ || !bubble_widget_) {
return;
}
if (position_ != new_position ||
new_position == FloatingMenuPosition::kSystemDefault) {
menu_view_->SetMenuPosition(new_position);
}
position_ = new_position;
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::Get()->GetDisplayNearestWindow(
bubble_widget_->GetNativeWindow()),
new_bounds);
resting_bounds.Inset(gfx::Insets::TLBR(0, -kCollisionWindowWorkAreaInsetsDp,
-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() {
ScopedBubbleViewActivator activator(bubble_view_,
GetAccessibleNameForBubble());
bubble_view_->GetFocusManager()->ClearFocus();
bubble_view_->GetFocusManager()->AdvanceFocus(false );
}
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(gfx::Insets::TLBR(0, -kCollisionWindowWorkAreaInsetsDp,
-kCollisionWindowWorkAreaInsetsDp,
-kCollisionWindowWorkAreaInsetsDp));
detailed_menu_controller_->Show(
anchor_rect, GetAnchorAlignmentForFloatingMenuPosition(position_));
menu_view_->SetDetailedViewShown(true);
} else {
detailed_menu_controller_.reset();
Shell::Get()
->accessibility_controller()
->UpdateAutoclickMenuBoundsIfNeeded();
bubble_view_->GetFocusManager()->ClearFocus();
}
}
void FloatingAccessibilityController::OnLayoutChanged() {
if (on_layout_change_) {
on_layout_change_.Run();
}
SetMenuPosition(position_);
}
void FloatingAccessibilityController::OnFocused() {
UpdateOpacity();
}
void FloatingAccessibilityController::OnBlurred() {
UpdateOpacity();
}
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::HideBubble(
const TrayBubbleView* bubble_view) {}
void FloatingAccessibilityController::OnMouseEnteredView() {
UpdateOpacity();
}
void FloatingAccessibilityController::OnMouseExitedView() {
UpdateOpacity();
}
void FloatingAccessibilityController::OnLocaleChanged() {
if (position_ == FloatingMenuPosition::kSystemDefault) {
SetMenuPosition(position_);
}
}
void FloatingAccessibilityController::OnAccessibilityStatusChanged() {
SetMenuPosition(position_);
if (detailed_menu_controller_) {
detailed_menu_controller_->OnAccessibilityStatusChanged();
}
}
void FloatingAccessibilityController::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
SetMenuPosition(position_);
}
void FloatingAccessibilityController::UpdateOpacity() {
const bool focus_in_children =
bubble_view_->Contains(bubble_view_->GetFocusManager()->GetFocusedView());
const bool is_opaque = bubble_view_->IsMouseHovered() || focus_in_children;
bubble_view_->layer()->SetOpacity(is_opaque ? 1.0f : 0.65f);
}
}