#include "ash/quick_insert/views/quick_insert_search_field_view.h"
#include <string>
#include <string_view>
#include "ash/ash_element_identifiers.h"
#include "ash/quick_insert/metrics/quick_insert_performance_metrics.h"
#include "ash/quick_insert/views/quick_insert_focus_indicator.h"
#include "ash/quick_insert/views/quick_insert_key_event_handler.h"
#include "ash/quick_insert/views/quick_insert_pseudo_focus.h"
#include "ash/quick_insert/views/quick_insert_search_bar_textfield.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/icon_button.h"
#include "ash/style/style_util.h"
#include "ash/style/typography.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/time/time.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/range/range.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
constexpr auto kSearchFieldVerticalPadding = gfx::Insets::VH(6, 0);
constexpr auto kButtonHorizontalMargin = gfx::Insets::VH(0, 8);
constexpr int kDefaultTextfieldHorizontalMargin = 16;
constexpr auto kTextfieldFocusIndicatorMargins = gfx::Insets::VH(6, 0);
}
QuickInsertSearchFieldView::QuickInsertSearchFieldView(
SearchCallback search_callback,
BackCallback back_callback,
QuickInsertKeyEventHandler* key_event_handler,
QuickInsertPerformanceMetrics* performance_metrics)
: search_callback_(std::move(search_callback)),
key_event_handler_(key_event_handler),
performance_metrics_(performance_metrics) {
views::Builder<QuickInsertSearchFieldView>(this)
.SetOrientation(views::LayoutOrientation::kHorizontal)
.SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
.SetProperty(views::kMarginsKey, kSearchFieldVerticalPadding)
.AddChildren(
views::Builder<views::ImageButton>(
std::make_unique<IconButton>(
std::move(back_callback), IconButton::Type::kSmallFloating,
&vector_icons::kArrowBackIcon, IDS_ACCNAME_BACK))
.CopyAddressTo(&back_button_)
.SetProperty(views::kMarginsKey, kButtonHorizontalMargin)
.SetVisible(false),
views::Builder<QuickInsertSearchBarTextfield>(
std::make_unique<QuickInsertSearchBarTextfield>(this))
.CopyAddressTo(&textfield_)
.SetProperty(views::kElementIdentifierKey,
kQuickInsertSearchFieldTextfieldElementId)
.SetController(this)
.SetBackgroundColor(SK_ColorTRANSPARENT)
.SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
TypographyToken::kCrosBody2))
.SetProperty(views::kBoxLayoutFlexKey,
views::BoxLayoutFlexSpecification().WithWeight(1)))
.AddChild(views::Builder<views::ImageButton>(
std::make_unique<IconButton>(
base::BindRepeating(
&QuickInsertSearchFieldView::ClearButtonPressed,
base::Unretained(this)),
IconButton::Type::kSmallFloating, &views::kIcCloseIcon,
IDS_APP_LIST_CLEAR_SEARCHBOX))
.CopyAddressTo(&clear_button_)
.SetProperty(views::kMarginsKey, kButtonHorizontalMargin)
.SetVisible(false))
.BuildChildren();
StyleUtil::SetUpInkDropForButton(back_button_, gfx::Insets(),
true,
true);
StyleUtil::SetUpInkDropForButton(clear_button_, gfx::Insets(),
true,
true);
UpdateTextfieldBorder();
}
QuickInsertSearchFieldView::~QuickInsertSearchFieldView() = default;
void QuickInsertSearchFieldView::RequestFocus() {
textfield_->RequestFocus();
}
void QuickInsertSearchFieldView::AddedToWidget() {
GetFocusManager()->AddFocusChangeListener(this);
}
void QuickInsertSearchFieldView::RemovedFromWidget() {
GetFocusManager()->RemoveFocusChangeListener(this);
}
void QuickInsertSearchFieldView::OnPaint(gfx::Canvas* canvas) {
views::View::OnPaint(canvas);
if (should_show_focus_indicator_) {
PaintQuickInsertFocusIndicator(
canvas, gfx::Point(0, kTextfieldFocusIndicatorMargins.top()),
height() - kTextfieldFocusIndicatorMargins.height(),
GetColorProvider()->GetColor(cros_tokens::kCrosSysFocusRing));
}
}
void QuickInsertSearchFieldView::ContentsChanged(
views::Textfield* sender,
const std::u16string& new_contents) {
ContentsChangedInternal(new_contents);
search_callback_.Run(new_contents);
}
void QuickInsertSearchFieldView::ContentsChangedInternal(
std::u16string_view new_contents) {
performance_metrics_->MarkContentsChanged();
clear_button_->SetVisible(!new_contents.empty());
UpdateTextfieldBorder();
ScheduleNotifyInitialActiveDescendantForA11y();
}
bool QuickInsertSearchFieldView::HandleKeyEvent(views::Textfield* sender,
const ui::KeyEvent& key_event) {
return key_event_handler_->HandleKeyEvent(key_event);
}
void QuickInsertSearchFieldView::OnDidChangeFocus(View* focused_before,
View* focused_now) {
if (focused_now == textfield_) {
performance_metrics_->MarkInputFocus();
}
ScheduleNotifyInitialActiveDescendantForA11y();
}
std::u16string_view QuickInsertSearchFieldView::GetPlaceholderText() const {
return textfield_->GetPlaceholderText();
}
void QuickInsertSearchFieldView::SetPlaceholderText(
std::u16string_view new_placeholder_text) {
textfield_->SetPlaceholderText(new_placeholder_text);
textfield_->GetViewAccessibility().SetName(
std::u16string(new_placeholder_text));
}
void QuickInsertSearchFieldView::SetTextfieldActiveDescendant(
views::View* view) {
if (!textfield_->HasFocus() ||
notify_initial_active_descendant_timer_.IsRunning()) {
active_descendant_tracker_.SetView(view);
return;
}
if (view) {
textfield_->GetViewAccessibility().SetActiveDescendant(*view);
} else {
textfield_->GetViewAccessibility().ClearActiveDescendant();
}
active_descendant_tracker_.SetView(nullptr);
}
std::u16string_view QuickInsertSearchFieldView::GetQueryText() const {
return textfield_->GetText();
}
void QuickInsertSearchFieldView::SetQueryText(std::u16string text) {
if (text != GetQueryText()) {
textfield_->SetText(std::move(text));
ContentsChangedInternal(GetQueryText());
}
}
void QuickInsertSearchFieldView::SetBackButtonVisible(bool visible) {
back_button_->SetVisible(visible);
UpdateTextfieldBorder();
}
void QuickInsertSearchFieldView::SetShouldShowFocusIndicator(
bool should_show_focus_indicator) {
if (should_show_focus_indicator_ == should_show_focus_indicator) {
return;
}
should_show_focus_indicator_ = should_show_focus_indicator;
SchedulePaint();
}
views::View* QuickInsertSearchFieldView::GetViewLeftOf(views::View* view) {
if (!Contains(view)) {
return nullptr;
}
views::View* left_view = GetNextQuickInsertPseudoFocusableView(
view, QuickInsertPseudoFocusDirection::kBackward, false);
return Contains(left_view) ? left_view : nullptr;
}
views::View* QuickInsertSearchFieldView::GetViewRightOf(views::View* view) {
if (!Contains(view)) {
return nullptr;
}
views::View* right_view = GetNextQuickInsertPseudoFocusableView(
view, QuickInsertPseudoFocusDirection::kForward, false);
return Contains(right_view) ? right_view : nullptr;
}
bool QuickInsertSearchFieldView::LeftEventShouldMoveCursor(
views::View* pseudo_focused_view) {
if (pseudo_focused_view == textfield_ &&
textfield_->GetCursorPosition() != GetQueryStartIndexForTraversal()) {
return true;
}
return GetViewLeftOf(pseudo_focused_view) == nullptr;
}
bool QuickInsertSearchFieldView::RightEventShouldMoveCursor(
views::View* pseudo_focused_view) {
if (pseudo_focused_view == textfield_ &&
textfield_->GetCursorPosition() != GetQueryEndIndexForTraversal()) {
return true;
}
return GetViewRightOf(pseudo_focused_view) == nullptr;
}
void QuickInsertSearchFieldView::OnGainedPseudoFocusFromLeftEvent(
views::View* pseudo_focused_view) {
if (pseudo_focused_view == textfield_) {
textfield_->SetSelectedRange(gfx::Range(GetQueryEndIndexForTraversal()));
}
}
void QuickInsertSearchFieldView::OnGainedPseudoFocusFromRightEvent(
views::View* pseudo_focused_view) {
if (pseudo_focused_view == textfield_) {
textfield_->SetSelectedRange(gfx::Range(GetQueryStartIndexForTraversal()));
}
}
void QuickInsertSearchFieldView::ClearButtonPressed() {
textfield_->SetText(u"");
ContentsChanged(textfield_, u"");
}
void QuickInsertSearchFieldView::UpdateTextfieldBorder() {
textfield_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
0, back_button_->GetVisible() ? 0 : kDefaultTextfieldHorizontalMargin, 0,
clear_button_->GetVisible() ? 0 : kDefaultTextfieldHorizontalMargin)));
}
void QuickInsertSearchFieldView::
ScheduleNotifyInitialActiveDescendantForA11y() {
notify_initial_active_descendant_timer_.Start(
FROM_HERE, kNotifyInitialActiveDescendantA11yDelay,
base::BindOnce(
&QuickInsertSearchFieldView::NotifyInitialActiveDescendantForA11y,
base::Unretained(this)));
}
void QuickInsertSearchFieldView::NotifyInitialActiveDescendantForA11y() {
if (active_descendant_tracker_) {
SetTextfieldActiveDescendant(active_descendant_tracker_.view());
}
}
size_t QuickInsertSearchFieldView::GetQueryStartIndexForTraversal() {
return base::i18n::IsRTL() ? GetQueryText().length() : 0;
}
size_t QuickInsertSearchFieldView::GetQueryEndIndexForTraversal() {
return base::i18n::IsRTL() ? 0 : GetQueryText().length();
}
BEGIN_METADATA(QuickInsertSearchFieldView)
END_METADATA
}