chromium/ash/picker/views/picker_pseudo_focus.cc

// Copyright 2024 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/picker/views/picker_pseudo_focus.h"

#include "ash/picker/views/picker_item_view.h"
#include "ash/picker/views/picker_list_item_view.h"
#include "ash/picker/views/picker_search_bar_textfield.h"
#include "base/functional/bind.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/types/event_type.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"

namespace ash {

void ApplyPickerPseudoFocusToView(views::View* view) {
  if (view == nullptr) {
    return;
  }

  // PickerItemView has special pseudo focus behavior, so handle it separately.
  if (auto* item_view = views::AsViewClass<PickerItemView>(view)) {
    item_view->SetItemState(PickerItemView::ItemState::kPseudoFocused);
    if (auto* list_item_view = views::AsViewClass<PickerListItemView>(view)) {
      list_item_view->SetBadgeVisible(true);
    }
    return;
  }

  // PickerSearchBarTextfield has special pseudo focus appearance.
  if (auto* textfield = views::AsViewClass<PickerSearchBarTextfield>(view)) {
    textfield->SetShouldShowFocusIndicator(true);
    return;
  }

  // Otherwise, default to drawing a focus ring around the view.
  // TODO: b/328144222 - Add accessibility announcement when a view gains
  // pseudo focus.
  if (views::FocusRing* focus_ring = views::FocusRing::Get(view)) {
    focus_ring->SetHasFocusPredicate(
        base::BindRepeating([](const views::View* view) { return true; }));
    focus_ring->SchedulePaint();
  }
}

void RemovePickerPseudoFocusFromView(views::View* view) {
  if (view == nullptr) {
    return;
  }

  // PickerItemView has special pseudo focus behavior, so handle it separately.
  if (auto* item_view = views::AsViewClass<PickerItemView>(view)) {
    item_view->SetItemState(PickerItemView::ItemState::kNormal);
    if (auto* list_item_view = views::AsViewClass<PickerListItemView>(view)) {
      list_item_view->SetBadgeVisible(false);
    }
    return;
  }

  // PickerSearchBarTextfield has special pseudo focus appearance.
  if (auto* textfield = views::AsViewClass<PickerSearchBarTextfield>(view)) {
    textfield->SetShouldShowFocusIndicator(false);
    return;
  }

  // Otherwise, default to removing the focus ring around the view.
  // TODO: b/328144222 - Add accessibility announcement when a view loses
  // pseudo focus.
  if (views::FocusRing* focus_ring = views::FocusRing::Get(view)) {
    focus_ring->SetHasFocusPredicate(
        base::BindRepeating([](const views::View* view) { return false; }));
    focus_ring->SchedulePaint();
  }
}

bool DoPickerPseudoFocusedActionOnView(views::View* view) {
  if (view == nullptr) {
    return false;
  }

  // PickerSearchBarTextfield has no pseudo focus action.
  if (views::IsViewClass<PickerSearchBarTextfield>(view)) {
    return true;
  }

  // PickerItemView has special pseudo focus behavior, so handle it separately.
  if (auto* item_view = views::AsViewClass<PickerItemView>(view)) {
    item_view->SelectItem();
    return true;
  }

  // Otherwise, default to behaving the same way as pressing the enter key.
  // Here we check that `view` does not have actual focus, to ensure we won't
  // trigger an infinite recursive loop of pseudo focused actions when manually
  // calling `OnKeyEvent` (since the focused view may forward key events to be
  // handled by the pseudo focused view).
  CHECK(!view->HasFocus());
  ui::KeyEvent key_event(ui::EventType::kKeyPressed, ui::VKEY_RETURN,
                         ui::DomCode::ENTER, ui::EF_NONE);
  view->OnKeyEvent(&key_event);
  return key_event.handled();
}

views::View* GetNextPickerPseudoFocusableView(
    views::View* view,
    PickerPseudoFocusDirection direction,
    bool should_loop) {
  return view == nullptr || view->GetFocusManager() == nullptr
             ? nullptr
             : view->GetFocusManager()->GetNextFocusableView(
                   view, view->GetWidget(),
                   direction == PickerPseudoFocusDirection::kBackward,
                   !should_loop);
}

}  // namespace ash