| // 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 FrameView; |
| } // 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::FrameView> CreateFrameView( |
| 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_ |