1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167

ash / quick_insert / views / quick_insert_search_field_view.h [blame]

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_SEARCH_FIELD_VIEW_H_
#define ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_SEARCH_FIELD_VIEW_H_

#include <string_view>

#include "ash/ash_export.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/view.h"
#include "ui/views/view_tracker.h"

namespace views {
class Textfield;
class ImageButton;
}  // namespace views

namespace ash {

class QuickInsertKeyEventHandler;
class QuickInsertPerformanceMetrics;
class QuickInsertSearchBarTextfield;

// View for the Quick Insert search field.
class ASH_EXPORT QuickInsertSearchFieldView
    : public views::BoxLayoutView,
      public views::TextfieldController,
      public views::FocusChangeListener {
  METADATA_HEADER(QuickInsertSearchFieldView, views::BoxLayoutView)

 public:
  using SearchCallback =
      base::RepeatingCallback<void(const std::u16string& query)>;
  using BackCallback = base::RepeatingClosure;

  // The delay before notifying the initial active descendant when the textfield
  // is focused. Same value as Launcher.
  static constexpr base::TimeDelta kNotifyInitialActiveDescendantA11yDelay =
      base::Milliseconds(1500);

  // `search_callback` is called asynchronously whenever the contents of the
  // search field changes (with debouncing logic to avoid unnecessary calls).
  // `key_event_handler` and `performance_metrics` must live as long as this
  // class. `delay` is the time to wait before calling `search_callback` for
  // debouncing.
  //
  // `back_callback` is called when clicking on the back button.
  explicit QuickInsertSearchFieldView(
      SearchCallback search_callback,
      BackCallback back_callback,
      QuickInsertKeyEventHandler* key_event_handler,
      QuickInsertPerformanceMetrics* performance_metrics);
  QuickInsertSearchFieldView(const QuickInsertSearchFieldView&) = delete;
  QuickInsertSearchFieldView& operator=(const QuickInsertSearchFieldView&) =
      delete;
  ~QuickInsertSearchFieldView() override;

  // views::View:
  void RequestFocus() override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  void OnPaint(gfx::Canvas* canvas) override;

  // views::TextfieldController:
  void ContentsChanged(views::Textfield* sender,
                       const std::u16string& new_contents) override;
  bool HandleKeyEvent(views::Textfield* sender,
                      const ui::KeyEvent& key_event) override;

  // views::FocusChangeListener:
  void OnWillChangeFocus(View* focused_before, View* focused_now) override;
  void OnDidChangeFocus(View* focused_before, View* focused_now) override;

  // Should be called every time the contents of the text field changes, even
  // if the search callback should not be called.
  void ContentsChangedInternal(std::u16string_view new_contents);

  // Gets or sets the placeholder text to show when the textfield is empty.
  const std::u16string& GetPlaceholderText() const;
  void SetPlaceholderText(const std::u16string& new_placeholder_text);

  // Sets the active descendant of the underlying textfield to `view` for screen
  // readers. `view` may be null, in which case the active descendant is
  // cleared.
  void SetTextfieldActiveDescendant(views::View* view);

  // Gets the current search query text.
  std::u16string_view GetQueryText() const;
  // Sets the current search query text. Does not call the search callback.
  void SetQueryText(std::u16string text);

  // Sets whether the back button is visible.
  void SetBackButtonVisible(bool visible);

  void SetShouldShowFocusIndicator(bool should_show_focus_indicator);

  // Returns the view directly to the left / right of `view`, or nullptr if
  // there is no such view in the QuickInsertSearchFieldView.
  views::View* GetViewLeftOf(views::View* view);
  views::View* GetViewRightOf(views::View* view);

  // Returns true if a left / right key event should move the cursor rather than
  // moving the currently pseudo focused view.
  bool LeftEventShouldMoveCursor(views::View* pseudo_focused_view);
  bool RightEventShouldMoveCursor(views::View* pseudo_focused_view);

  // Should be called when the search field or one of its child views gains
  // pseudo focus after a left / right key event.
  void OnGainedPseudoFocusFromLeftEvent(views::View* pseudo_focused_view);
  void OnGainedPseudoFocusFromRightEvent(views::View* pseudo_focused_view);

  QuickInsertSearchBarTextfield* textfield() { return textfield_; }

  QuickInsertSearchBarTextfield& textfield_for_testing() { return *textfield_; }
  views::ImageButton& back_button_for_testing() { return *back_button_; }
  views::ImageButton& clear_button_for_testing() { return *clear_button_; }

 private:
  void ClearButtonPressed();

  // Updates the textfield border when the clear button visibility changes.
  void UpdateTextfieldBorder();
  // Schedules a delayed announcement of the initial active descendant.
  void ScheduleNotifyInitialActiveDescendantForA11y();
  // Notifies the initial active descendant for the screen reader.
  void NotifyInitialActiveDescendantForA11y();

  // Gets the start and end indices of the current search query text, to use
  // when moving pseudo focus to and from the textfield. Note that the start and
  // end are swapped in RTL locales since we swapped left and right key events
  // when traversing the Quick Insert UI in RTL.
  size_t GetQueryStartIndexForTraversal();
  size_t GetQueryEndIndexForTraversal();

  bool should_show_focus_indicator_ = false;

  SearchCallback search_callback_;
  raw_ptr<QuickInsertKeyEventHandler> key_event_handler_ = nullptr;
  raw_ptr<QuickInsertPerformanceMetrics> performance_metrics_ = nullptr;
  raw_ptr<QuickInsertSearchBarTextfield> textfield_ = nullptr;
  raw_ptr<views::ImageButton> back_button_ = nullptr;
  raw_ptr<views::ImageButton> clear_button_ = nullptr;

  // Tracks pending active descendant change when the textfield is not focused.
  views::ViewTracker active_descendant_tracker_;
  // Delay the initial active descendant change notification for a query.
  base::OneShotTimer notify_initial_active_descendant_timer_;
};

BEGIN_VIEW_BUILDER(ASH_EXPORT, QuickInsertSearchFieldView, views::BoxLayoutView)
VIEW_BUILDER_PROPERTY(std::u16string, PlaceholderText)
END_VIEW_BUILDER

}  // namespace ash

DEFINE_VIEW_BUILDER(ASH_EXPORT, ash::QuickInsertSearchFieldView)

#endif  // ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_SEARCH_FIELD_VIEW_H_