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
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310

ash / quick_insert / views / quick_insert_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_VIEW_H_
#define ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_VIEW_H_

#include <memory>
#include <optional>
#include <string>
#include <string_view>

#include "ash/ash_export.h"
#include "ash/quick_insert/metrics/quick_insert_performance_metrics.h"
#include "ash/quick_insert/model/quick_insert_search_results_section.h"
#include "ash/quick_insert/quick_insert_category.h"
#include "ash/quick_insert/quick_insert_controller.h"
#include "ash/quick_insert/quick_insert_search_result.h"
#include "ash/quick_insert/views/quick_insert_emoji_bar_view_delegate.h"
#include "ash/quick_insert/views/quick_insert_key_event_handler.h"
#include "ash/quick_insert/views/quick_insert_preview_bubble_controller.h"
#include "ash/quick_insert/views/quick_insert_pseudo_focus_handler.h"
#include "ash/quick_insert/views/quick_insert_search_results_view_delegate.h"
#include "ash/quick_insert/views/quick_insert_submenu_controller.h"
#include "ash/quick_insert/views/quick_insert_zero_state_view_delegate.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/view.h"
#include "ui/views/view_tracker.h"
#include "ui/views/widget/widget_delegate.h"

namespace views {
class Widget;
class NonClientFrameView;
}  // namespace views

namespace ash {

enum class QuickInsertCapsLockPosition;
enum class QuickInsertLayoutType;
enum class QuickInsertPositionType;
enum class QuickInsertPseudoFocusDirection;
class QuickInsertEmojiBarView;
class QuickInsertMainContainerView;
class QuickInsertSearchFieldView;
class QuickInsertPageView;
class QuickInsertSearchResultsSection;
class QuickInsertSearchResultsView;
class QuickInsertTraversableItemContainer;
class QuickInsertViewDelegate;
class QuickInsertZeroStateView;

// View for the Quick Insert widget.
class ASH_EXPORT QuickInsertView
    : public views::WidgetDelegateView,
      public QuickInsertZeroStateViewDelegate,
      public QuickInsertSearchResultsViewDelegate,
      public QuickInsertEmojiBarViewDelegate,
      public QuickInsertPseudoFocusHandler,
      public QuickInsertPreviewBubbleController::Observer {
  METADATA_HEADER(QuickInsertView, views::WidgetDelegateView)

 public:
  // `delegate` must remain valid for the lifetime of this class.
  explicit QuickInsertView(QuickInsertViewDelegate* delegate,
                           const gfx::Rect& anchor_bounds,
                           QuickInsertLayoutType layout_type,
                           QuickInsertPositionType position_type,
                           base::TimeTicks trigger_event_timestamp);
  QuickInsertView(const QuickInsertView&) = delete;
  QuickInsertView& operator=(const QuickInsertView&) = delete;
  ~QuickInsertView() override;

  // Time from when a search starts to when the previous set of results are
  // cleared.
  // Slightly longer than the real burn in period to ensure empty results do not
  // flash on the screen before showing burn-in results.
  static constexpr base::TimeDelta kClearResultsTimeout =
      QuickInsertController::kBurnInPeriod + base::Milliseconds(50);

  // views::WidgetDelegateView:
  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
  std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView(
      views::Widget* widget) override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  void Layout(PassKey) override;

  // QuickInsertZeroStateViewDelegate:
  void SelectZeroStateCategory(QuickInsertCategory category) override;
  void SelectZeroStateResult(const QuickInsertSearchResult& result) override;
  void GetZeroStateSuggestedResults(SuggestedResultsCallback callback) override;
  void RequestPseudoFocus(views::View* view) override;
  void OnZeroStateViewHeightChanged() override;
  void SetCapsLockDisplayed(bool displayed) override;
  QuickInsertCapsLockPosition GetCapsLockPosition() override;

  // QuickInsertSearchResultsViewDelegate:
  void SelectSearchResult(const QuickInsertSearchResult& result) override;
  void SelectMoreResults(QuickInsertSectionType type) override;
  QuickInsertActionType GetActionForResult(
      const QuickInsertSearchResult& result) override;
  void OnSearchResultsViewHeightChanged() override;

  // QuickInsertEmojiBarViewDelegate:
  void ToggleGifs(bool is_checked) override;
  void ShowEmojiPicker(ui::EmojiPickerCategory category) override;

  // QuickInsertPseudoFocusHandler:
  bool DoPseudoFocusedAction() override;
  bool MovePseudoFocusUp() override;
  bool MovePseudoFocusDown() override;
  bool MovePseudoFocusLeft() override;
  bool MovePseudoFocusRight() override;
  bool AdvancePseudoFocus(QuickInsertPseudoFocusDirection direction) override;

  // QuickInsertPreviewBubbleController::Observer:
  void OnPreviewBubbleVisibilityChanged(bool visible) override;

  // Returns the target bounds for this Quick Insert view. The target bounds try
  // to vertically align `search_field_view_` with `anchor_bounds`.
  // `anchor_bounds` and returned bounds should be in screen coordinates.
  gfx::Rect GetTargetBounds(const gfx::Rect& anchor_bounds,
                            QuickInsertLayoutType layout_type);

  QuickInsertSubmenuController& submenu_controller_for_testing() {
    return submenu_controller_;
  }
  QuickInsertPreviewBubbleController& preview_controller_for_testing() {
    return preview_controller_;
  }

  QuickInsertSearchFieldView& search_field_view_for_testing() {
    return *search_field_view_;
  }
  QuickInsertSearchResultsView& search_results_view_for_testing() {
    return *search_results_view_;
  }
  QuickInsertSearchResultsView& category_results_view_for_testing() {
    return *category_results_view_;
  }
  QuickInsertZeroStateView& zero_state_view_for_testing() {
    return *zero_state_view_;
  }
  QuickInsertEmojiBarView* emoji_bar_view_for_testing() {
    return emoji_bar_view_;
  }

 private:
  // Sets the search text field's query text to the query, focuses it, then
  // updates the active page - starting / ending a search if necessary.
  void UpdateSearchQueryAndActivePage(std::u16string query);

  // Updates the active page based on the search text field's query text, as
  // well as the active category.
  // If the search text field's query text is non-empty, this starts a search
  // and sets the active page to the search view after a delay via
  // `OnClearResultsTimerFired` and `PublishSearchResults`.
  // Otherwise, stops any previous searches and immediately sets the active page
  // to the zero state / category results view, fetching category results if
  // necessary.
  void UpdateActivePage();

  // Displays `results` in the emoji bar.
  void PublishEmojiResults(std::vector<QuickInsertEmojiResult> results);

  // Clears the search results and sets the active page to the search view.
  void OnClearResultsTimerFired();

  // Displays `results` in the search view and sets it as the active page.
  // If `results` is empty and no results were previously published, then a "no
  // results found" view is shown instead of a blank view.
  void PublishSearchResults(
      std::vector<QuickInsertSearchResultsSection> results);

  // Selects a category. This shows the category view and fetches zero-state
  // results for the category, which are returned to `PublishCategoryResults`.
  void SelectCategory(QuickInsertCategory category);

  // Selects a category. This shows the category view and fetches search
  // results for the category based on `query`, which are returned to
  // `PublishSearchResults`.
  void SelectCategoryWithQuery(QuickInsertCategory category,
                               std::u16string_view query);

  // Displays `results` in the category view.
  void PublishCategoryResults(
      QuickInsertCategory category,
      std::vector<QuickInsertSearchResultsSection> results);

  // Adds the main container, which includes the search field and contents
  // pages.
  void AddMainContainerView(QuickInsertLayoutType layout_type);

  // Adds the emoji bar, which contains emoji and other expression results and
  // is shown above the main container.
  void AddEmojiBarView();

  // Sets `page_view` as the active page in `main_container_view_`.
  void SetActivePage(QuickInsertPageView* page_view);

  // Sets emoji bar visibility, or does nothing if the emoji bar is not enabled.
  void SetEmojiBarVisibleIfEnabled(bool visible);

  // Moves pseudo focus between different parts of the QuickInsertView, i.e.
  // between the emoji bar and the main container.
  void AdvanceActiveItemContainer(QuickInsertPseudoFocusDirection direction);

  // Sets `view` as the pseudo focused view, i.e. the view which responds to
  // user actions that trigger `DoPseudoFocusedAction`. If `view` is null,
  // pseudo focus instead moves back to the search field.
  void SetPseudoFocusedView(views::View* view);

  views::View* GetPseudoFocusedView();

  // Removes the currently selected category filter, with the option to clear
  // the search field.
  void ResetSelectedCategory(bool reset_query);

  // Called when the search field back button is pressed.
  void OnSearchBackButtonPressed();

  // Clears the current results in the emoji bar and shows recent and
  // placeholder emojis instead.
  void ResetEmojiBarToZeroState();

  // Returns true if `view` is contained in a submenu of this QuickInsertView.
  bool IsContainedInSubmenu(views::View* view);

  // Called to indicate that the Quick Insert widget bounds need to be be
  // updated (e.g. to re-align the Quick Insert search field after results have
  // changed).
  void SetWidgetBoundsNeedsUpdate();

  // The currently selected category.
  // Should only be set to `std::nullopt` through `OnSearchBackButtonPressed`.
  // Should only be set to a value through `SelectCategory` and
  // `SelectCategoryWithQuery`.
  std::optional<QuickInsertCategory> selected_category_;

  // Whether the GIF toggle is checked (i.e. should only show GIF results).
  bool is_gif_toggle_checked_ = false;

  // The category which `category_results_view_` has results for.
  // Used for caching results if the user did not change their selected
  // category.
  // For example:
  // - When a user starts a filtered search from a category's suggested results,
  //   then clears the search query, the old suggested results are not cleared
  //   as `last_suggested_results_category_ == selected_category_`.
  // - When a user starts a non-filtered search from zero state, then filters
  //   results to a category, then clears the search query, new results will be
  //   fetched as the `last_suggested_results_category_ != selected_category_`.
  std::optional<QuickInsertCategory> last_suggested_results_category_;
  // The whitespace-trimmed query and category when `UpdateActivePage()` was
  // last called.
  // Used for avoid unnecessary searches if `UpdateActivePage()` is called again
  // with the same {query, selected_category, is_gif_toggle_checked}.
  std::u16string last_query_;
  std::optional<QuickInsertCategory> last_selected_category_;
  bool last_is_gif_toggle_checked_ = false;

  QuickInsertKeyEventHandler key_event_handler_;
  QuickInsertSubmenuController submenu_controller_;
  QuickInsertPreviewBubbleController preview_controller_;
  QuickInsertPerformanceMetrics performance_metrics_;
  raw_ptr<QuickInsertViewDelegate> delegate_ = nullptr;

  // The main container contains the search field and contents pages.
  raw_ptr<QuickInsertMainContainerView> main_container_view_ = nullptr;
  raw_ptr<QuickInsertSearchFieldView> search_field_view_ = nullptr;
  raw_ptr<QuickInsertZeroStateView> zero_state_view_ = nullptr;
  raw_ptr<QuickInsertSearchResultsView> category_results_view_ = nullptr;
  raw_ptr<QuickInsertSearchResultsView> search_results_view_ = nullptr;

  raw_ptr<QuickInsertEmojiBarView> emoji_bar_view_ = nullptr;

  // The item container which contains `pseudo_focused_view_` and will respond
  // to keyboard navigation events.
  raw_ptr<QuickInsertTraversableItemContainer> active_item_container_ = nullptr;

  // Tracks the currently pseudo focused view, which responds to user actions
  // that trigger `DoPseudoFocusedAction`.
  views::ViewTracker pseudo_focused_view_tracker_;

  // If true, the Widget bounds should be adjusted on the next layout.
  bool widget_bounds_needs_update_ = true;

  // Clears `search_results_view_`'s old search results when a new search is
  // started - after `kClearResultsTimeout`, or when the first search results
  // come in (whatever is earliest).
  // This timer is running iff the first set of results for the current search
  // have not been published yet.
  base::OneShotTimer clear_results_timer_;

  base::ScopedObservation<QuickInsertPreviewBubbleController,
                          QuickInsertPreviewBubbleController::Observer>
      preview_bubble_observation_{this};

  base::WeakPtrFactory<QuickInsertView> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_VIEW_H_