chromium/ash/app_list/views/search_result_view.h

// Copyright 2012 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_APP_LIST_VIEWS_SEARCH_RESULT_VIEW_H_
#define ASH_APP_LIST_VIEWS_SEARCH_RESULT_VIEW_H_

#include <memory>
#include <vector>

#include "ash/app_list/model/search/search_result.h"
#include "ash/app_list/views/search_result_actions_view_delegate.h"
#include "ash/app_list/views/search_result_base_view.h"
#include "ash/ash_export.h"
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/controls/progress_bar.h"

namespace views {
class FlexLayoutView;
class ImageView;
class Label;
}  // namespace views

namespace ash {

namespace test {
class SearchResultListViewTest;
class SearchResultViewWidgetTest;
}  // namespace test

class AppListViewDelegate;
class MaskedImageView;
class SearchResult;
class SearchResultListView;
class SearchResultPageDialogController;

// Search result view uses `views::FlexLayout` to show results in different
// configurations.
// +----------------------------------------------------------------------+
// |`text_container_`                                                     |
// | +----------------------+ +-----------------------------------------+ |
// | |`big_title_container_'| |`body_text_container_`                   | |
// | |                      | | +------------------------------+        | |
// | |                      | | |`title_and_details_container_`|        | |
// | |                      | | +------------------------------+        | |
// | |                      | | +------------------------------+        | |
// | |                      | | |`keyboard_shortcut_container_`|        | |
// | |                      | | +------------------------------+        | |
// | |                      | | +------------------------------+        | |
// | |                      | | |`progress_bar_container_`     |        | |
// | |                      | | +------------------------------+        | |
// | |                      | | +------------------------------+        | |
// | |                      | | |`system_details_container_`   |        | |
// | |                      | | +------------------------------+        | |
// | +----------------------+ +-----------------------------------------+ |
// +----------------------------------------------------------------------+
//
// +-------------------------------------------------------------------------+
// |`big_title_container_`                                                   |
// | +--------------------------------+ +----------------------------------+ |
// | |'big_title_main_text_container_'| |`big_title_superscript_container_`| |
// | +--------------------------------+ +----------------------------------+ |
// +-------------------------------------------------------------------------+
//
// The `title_and_details_container_` has three possible layouts depending on
// `view_type_` and whether `keyboard_shortcut_container_` has results
//
// Layout used when the view_type_ == SearchResultViewType::kDefault OR
// `has_keyboard_shortcut_contents_` is set.
//
// +--------------------------------------------------------------------+
// |`title_and_details_container_`                                      |
// | +------------------+  +------------------+  +--------------------+ |
// | |'title_container_'|  |`separator_label_`|  |`details_container_`| |
// | +------------------+  +------------------+  +--------------------+ |
// +--------------------------------------------------------------------+
//
// layout used when view type is SearchResultViewType::kAnswerCard or
// SearchResultViewType::kClassic.
// +-------------------------------+
// |`title_and_details_container_` |
// | +------------------+          |
// | |'title_container_'|          |
// | +------------------+          |
// | +--------------------+        |
// | |'details_container_'|        |
// | +--------------------+        |
// +-------------------------------+
//
// Layout used when `is_progress_bar_answer_card_` is true.
// +-------------------------------+
// |`title_and_details_container_` |
// | +---------------------------+ |
// | | `progress_bar_container_` | |
// | +-------------------------- + |
// | +--------------------+        |
// | |'details_container_'|        |
// | +--------------------+        |
// +-------------------------------+
//
// Layout used when the result is a System Info Answer Card and the result
// contains a right hand description.
// +---------------------------------------------------------------+
// |`title_and_details_container_`                                 |
// | +---------------------------+                                 |
// | | `progress_bar_container_` |                                 |
// | +-------------------------- +                                 |
// | +-----------------------------------------------------------+ |
// | | `system_details_container_`                               | |
// | | +-------------------------+  +--------------------------+ | |
// | | |`left_details_container_`|  |`right_details_container_`| | |
// | | +-------------------------+  +--------------------------+ | |
// | +-----------------------------------------------------------+ |
// +---------------------------------------------------------------+

class ASH_EXPORT SearchResultView : public SearchResultBaseView,
                                    public SearchResultActionsViewDelegate {
  METADATA_HEADER(SearchResultView, SearchResultBaseView)

 public:
  class LabelAndTag {
   public:
    LabelAndTag(views::Label* label, SearchResult::Tags tags);

    LabelAndTag(const LabelAndTag& other);
    LabelAndTag& operator=(const LabelAndTag& other);
    ~LabelAndTag();

    views::Label* GetLabel() const { return label_; }
    SearchResult::Tags GetTags() const { return tags_; }

   private:
    raw_ptr<views::Label, DanglingUntriaged>
        label_;  // Owned by views hierarchy.
    SearchResult::Tags tags_;
  };

  enum class SearchResultViewType {
    // The default view type used for the majority of search results.
    kDefault,
    // Inline Answer views are used to directly answer questions posed by the
    // search query.
    kAnswerCard,
  };

  enum class LabelType {
    kBigTitle,
    kBigTitleSuperscript,
    kTitle,
    kDetails,
    kKeyboardShortcut,
  };

  SearchResultView(SearchResultListView* list_view,
                   AppListViewDelegate* view_delegate,
                   SearchResultPageDialogController* dialog_controller,
                   SearchResultViewType view_type);

  SearchResultView(const SearchResultView&) = delete;
  SearchResultView& operator=(const SearchResultView&) = delete;

  ~SearchResultView() override;

  // Sets/gets SearchResult displayed by this view.
  void OnResultChanged() override;

  void SetSearchResultViewType(SearchResultViewType type);
  void ClearBigTitleContainer();
  SearchResultViewType view_type() { return view_type_; }

  views::LayoutOrientation TitleAndDetailsOrientationForTest();

  // Calculates the width of the `title_container_` and 'details_container_'
  // for SearchResultView's custom eliding behavior.
  // total_width is the total width allocated to `title_and_details_container_`
  static int GetTargetTitleWidth(int total_width,
                                 int separator_width,
                                 int target_details_width);
  static int GetMinimumDetailsWidth(int total_width,
                                    int details_width,
                                    int details_no_elide_width);

  // Set flex layout weights for title and details containers to support custom
  // eliding behavior.
  static void SetFlexBehaviorForTextContents(
      int total_width,
      int separator_width,
      int non_elided_details_width,
      views::FlexLayoutView* title_container,
      views::FlexLayoutView* details_container);

  void set_multi_line_details_height_for_test(int height) {
    multi_line_details_height_ = height;
  }

  void set_multi_line_title_height_for_test(int height) {
    multi_line_title_height_ = height;
  }

  views::FlexLayoutView* get_keyboard_shortcut_container_for_test() {
    return keyboard_shortcut_container_;
  }

  views::FlexLayoutView* get_title_container_for_test() {
    return title_container_;
  }

  views::FlexLayoutView* get_details_container_for_test() {
    return details_container_;
  }

  views::Label* get_result_text_separator_label_for_test() {
    return result_text_separator_label_;
  }

  views::FlexLayoutView* get_progress_bar_container_for_test() {
    return progress_bar_container_;
  }

 private:
  friend class test::SearchResultListViewTest;
  friend class SearchResultListView;
  friend class SearchResultViewWidgetTest;

  int PreferredHeight() const;
  int PrimaryTextHeight() const;
  int SecondaryTextHeight() const;
  int ActionButtonRightMargin() const;

  std::vector<LabelAndTag> SetupContainerViewForTextVector(
      views::FlexLayoutView* parent,
      const std::vector<SearchResult::TextItem>& text_vector,
      LabelType label_type,
      bool has_keyboard_shortcut_contents,
      bool is_multi_line);
  void UpdateBadgeIcon();
  void UpdateBigTitleContainer();
  void UpdateBigTitleSuperscriptContainer();
  void UpdateIconAndBadgeIcon();
  void UpdateTitleContainer();
  void UpdateDetailsContainer();
  void UpdateKeyboardShortcutContainer();
  void UpdateProgressBarContainer();
  void UpdateRating();

  void StyleLabel(views::Label* label, const SearchResult::Tags& tags);
  void StyleBigTitleContainer();
  void StyleBigTitleSuperscriptContainer();
  void StyleTitleContainer();
  void StyleDetailsContainer();
  void StyleKeyboardShortcutContainer();

  // Callback for query suggstion removal confirmation.
  void OnQueryRemovalAccepted(bool accepted);

  // Called when the result selection controller selects a new result.
  void OnSelectedResultChanged();

  // views::View overrides:
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;
  void Layout(PassKey) override;
  bool OnKeyPressed(const ui::KeyEvent& event) override;
  void PaintButtonContents(gfx::Canvas* canvas) override;
  void OnMouseEntered(const ui::MouseEvent& event) override;
  void OnMouseExited(const ui::MouseEvent& event) override;
  void OnThemeChanged() override;

  // ui::EventHandler overrides:
  void OnGestureEvent(ui::GestureEvent* event) override;

  // SearchResultObserver overrides:
  void OnMetadataChanged() override;

  void OnButtonPressed(const ui::Event& event);

  void SetIconImage(const gfx::ImageSkia& source,
                    views::ImageView* const icon,
                    const gfx::Size& size);

  // SearchResultActionsViewDelegate overrides:
  void OnSearchResultActionActivated(size_t index) override;
  bool IsSearchResultHoveredOrSelected() override;

  bool IsInlineSearchResult();

  // Uses icon view bounds to calculate the host badge view bounds.
  gfx::Rect GetIconBadgeViewBounds(const gfx::Rect& icon_view_bounds) const;
  gfx::Size CalculateRegularIconImageSize(
      const gfx::ImageSkia& icon_image) const;

  // Parent list view. Owned by views hierarchy.
  const raw_ptr<SearchResultListView> list_view_;

  const raw_ptr<AppListViewDelegate> view_delegate_;

  const raw_ptr<SearchResultPageDialogController, DanglingUntriaged>
      dialog_controller_;

  raw_ptr<MaskedImageView> icon_view_ = nullptr;  // Owned by views hierarchy.
  raw_ptr<views::ImageView> badge_icon_view_ =
      nullptr;  // Owned by views hierarchy.

  raw_ptr<views::FlexLayoutView> text_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> big_title_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> big_title_main_text_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> big_title_superscript_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> body_text_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> title_and_details_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> title_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> details_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> keyboard_shortcut_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> progress_bar_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> system_details_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> left_details_container_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::FlexLayoutView> right_details_container_ =
      nullptr;  // Owned by views hierarchy.

  std::vector<LabelAndTag> big_title_label_tags_;  // Owned by views hierarchy.
  std::vector<LabelAndTag>
      big_title_superscript_label_tags_;         // Owned by views hierarchy.
  std::vector<LabelAndTag> title_label_tags_;    // Owned by views hierarchy.
  std::vector<LabelAndTag> details_label_tags_;  // Owned by views hierarchy.
  std::vector<LabelAndTag>
      keyboard_shortcut_container_tags_;  // Owned by views hierarchy.
  std::vector<LabelAndTag>
      right_details_label_tags_;  // Owned by views hierarchy.

  raw_ptr<views::Label> result_text_separator_label_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::Label> rating_separator_label_ =
      nullptr;                              // Owned by views hierarchy.
  raw_ptr<views::Label> rating_ = nullptr;  // Owned by views hierarchy.
  raw_ptr<views::ImageView> rating_star_ =
      nullptr;  // Owned by views hierarchy.
  raw_ptr<views::ProgressBar, DanglingUntriaged> progress_bar_ = nullptr;

  // Whether the removal confirmation dialog is invoked by long press touch.
  bool confirm_remove_by_long_press_ = false;

  // Separator label is shown for `kDefault` when details text is not empty,
  bool should_show_result_text_separator_label_ = false;

  // Used to override `title_and_details_container_` layout when
  // `keyboard_shortcut_container_` is populated.
  bool has_keyboard_shortcut_contents_ = false;

  // Used to insert a `progress_bar_container_` within the
  // `title_and_details_container_` when the result has a set bar chart.
  bool is_progress_bar_answer_card_ = false;

  SearchResultViewType view_type_;

  // Search result view can have one non-elided label. Cache the its for flex
  // layout weight calculations.
  int non_elided_details_label_width_ = 0;

  // Search result view can have one multi-line title label. Cache its height
  // for calculating PreferredHeight() and PrimaryTextHeight().
  int multi_line_title_height_ = 0;

  // Search result view can have one multi-line details label. Cache its height
  // for calculating PreferredHeight() and SecondaryTextHeight().
  int multi_line_details_height_ = 0;

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

}  // namespace ash

#endif  // ASH_APP_LIST_VIEWS_SEARCH_RESULT_VIEW_H_