search_result_actions_view.cc 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // Copyright 2013 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/app_list/views/search_result_actions_view.h"
  5. #include <stddef.h>
  6. #include <algorithm>
  7. #include <memory>
  8. #include "ash/app_list/app_list_util.h"
  9. #include "ash/app_list/views/search_result_actions_view_delegate.h"
  10. #include "ash/app_list/views/search_result_view.h"
  11. #include "ash/constants/ash_features.h"
  12. #include "ash/public/cpp/app_list/app_list_color_provider.h"
  13. #include "ash/public/cpp/app_list/app_list_config.h"
  14. #include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
  15. #include "ash/public/cpp/style/scoped_light_mode_as_default.h"
  16. #include "ash/style/icon_button.h"
  17. #include "base/bind.h"
  18. #include "third_party/abseil-cpp/absl/types/optional.h"
  19. #include "ui/base/resource/resource_bundle.h"
  20. #include "ui/gfx/canvas.h"
  21. #include "ui/gfx/geometry/insets.h"
  22. #include "ui/gfx/image/image_skia_operations.h"
  23. #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
  24. #include "ui/views/animation/ink_drop.h"
  25. #include "ui/views/animation/ink_drop_highlight.h"
  26. #include "ui/views/border.h"
  27. #include "ui/views/controls/button/image_button.h"
  28. #include "ui/views/controls/button/md_text_button.h"
  29. #include "ui/views/controls/highlight_path_generator.h"
  30. #include "ui/views/layout/box_layout.h"
  31. namespace ash {
  32. namespace {
  33. constexpr int kActionButtonBetweenSpacing = 8;
  34. } // namespace
  35. // SearchResultActionButton renders the button defined by SearchResult::Action.
  36. class SearchResultActionButton : public IconButton {
  37. public:
  38. SearchResultActionButton(SearchResultActionsView* parent,
  39. const SearchResult::Action& action,
  40. PressedCallback callback,
  41. Type type,
  42. const gfx::VectorIcon* icon,
  43. const std::u16string& accessible_name);
  44. SearchResultActionButton(const SearchResultActionButton&) = delete;
  45. SearchResultActionButton& operator=(const SearchResultActionButton&) = delete;
  46. ~SearchResultActionButton() override {}
  47. // IconButton:
  48. void OnGestureEvent(ui::GestureEvent* event) override;
  49. void OnThemeChanged() override;
  50. // Updates the button visibility upon state change of the button or the
  51. // search result view associated with it.
  52. void UpdateOnStateChanged();
  53. private:
  54. // views::ImageButton:
  55. void OnPaintBackground(gfx::Canvas* canvas) override;
  56. int GetButtonRadius() const;
  57. const char* GetClassName() const override;
  58. SearchResultActionsView* parent_;
  59. const bool visible_on_hover_;
  60. bool to_be_activate_by_long_press_ = false;
  61. };
  62. SearchResultActionButton::SearchResultActionButton(
  63. SearchResultActionsView* parent,
  64. const SearchResult::Action& action,
  65. PressedCallback callback,
  66. Type type,
  67. const gfx::VectorIcon* icon,
  68. const std::u16string& accessible_name)
  69. : IconButton(callback,
  70. type,
  71. icon,
  72. action.tooltip_text,
  73. /*is_togglable=*/false,
  74. /*has_border=*/false),
  75. parent_(parent),
  76. visible_on_hover_(action.visible_on_hover) {
  77. SetFocusBehavior(FocusBehavior::ALWAYS);
  78. SetVisible(!visible_on_hover_);
  79. }
  80. void SearchResultActionButton::OnGestureEvent(ui::GestureEvent* event) {
  81. switch (event->type()) {
  82. case ui::ET_GESTURE_LONG_PRESS:
  83. to_be_activate_by_long_press_ = true;
  84. event->SetHandled();
  85. break;
  86. case ui::ET_GESTURE_END:
  87. if (to_be_activate_by_long_press_) {
  88. NotifyClick(*event);
  89. SetState(STATE_NORMAL);
  90. to_be_activate_by_long_press_ = false;
  91. event->SetHandled();
  92. }
  93. break;
  94. default:
  95. break;
  96. }
  97. if (!event->handled())
  98. Button::OnGestureEvent(event);
  99. }
  100. void SearchResultActionButton::OnThemeChanged() {
  101. absl::optional<ScopedLightModeAsDefault> default_light_mode;
  102. // Non-productivity launcher search UI has light background.
  103. if (!features::IsProductivityLauncherEnabled())
  104. default_light_mode.emplace();
  105. IconButton::OnThemeChanged();
  106. }
  107. void SearchResultActionButton::UpdateOnStateChanged() {
  108. // Show button if the associated result row is hovered or selected, or one
  109. // of the action buttons is selected.
  110. if (visible_on_hover_)
  111. SetVisible(parent_->IsSearchResultHoveredOrSelected());
  112. }
  113. void SearchResultActionButton::OnPaintBackground(gfx::Canvas* canvas) {
  114. if (HasFocus() || parent_->GetSelectedAction() == tag()) {
  115. PaintFocusRing(canvas, GetLocalBounds().CenterPoint(), GetButtonRadius());
  116. }
  117. }
  118. int SearchResultActionButton::GetButtonRadius() const {
  119. return width() / 2;
  120. }
  121. const char* SearchResultActionButton::GetClassName() const {
  122. return "SearchResultActionButton";
  123. }
  124. SearchResultActionsView::SearchResultActionsView(
  125. SearchResultActionsViewDelegate* delegate)
  126. : delegate_(delegate) {
  127. DCHECK(delegate_);
  128. SetLayoutManager(std::make_unique<views::BoxLayout>(
  129. views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
  130. kActionButtonBetweenSpacing));
  131. }
  132. SearchResultActionsView::~SearchResultActionsView() {}
  133. void SearchResultActionsView::SetActions(const SearchResult::Actions& actions) {
  134. if (selected_action_.has_value())
  135. selected_action_.reset();
  136. subscriptions_.clear();
  137. RemoveAllChildViews();
  138. for (size_t i = 0; i < actions.size(); ++i)
  139. CreateImageButton(actions[i], i);
  140. PreferredSizeChanged();
  141. }
  142. bool SearchResultActionsView::IsValidActionIndex(size_t action_index) const {
  143. return action_index < GetActionCount();
  144. }
  145. bool SearchResultActionsView::IsSearchResultHoveredOrSelected() const {
  146. return delegate_->IsSearchResultHoveredOrSelected();
  147. }
  148. void SearchResultActionsView::HideActions() {
  149. for (views::View* child : children())
  150. child->SetVisible(false);
  151. }
  152. void SearchResultActionsView::UpdateButtonsOnStateChanged() {
  153. for (views::View* child : children())
  154. static_cast<SearchResultActionButton*>(child)->UpdateOnStateChanged();
  155. }
  156. const char* SearchResultActionsView::GetClassName() const {
  157. return "SearchResultActionsView";
  158. }
  159. bool SearchResultActionsView::SelectInitialAction(bool reverse_tab_order) {
  160. if (GetActionCount() == 0)
  161. return false;
  162. if (reverse_tab_order) {
  163. selected_action_ = GetActionCount() - 1;
  164. } else {
  165. selected_action_.reset();
  166. }
  167. UpdateButtonsOnStateChanged();
  168. return selected_action_.has_value();
  169. }
  170. bool SearchResultActionsView::SelectNextAction(bool reverse_tab_order) {
  171. if (GetActionCount() == 0)
  172. return false;
  173. // For reverse tab order, consider moving to non-selected state.
  174. if (reverse_tab_order) {
  175. if (!selected_action_.has_value())
  176. return false;
  177. if (selected_action_.value() == 0) {
  178. ClearSelectedAction();
  179. return true;
  180. }
  181. }
  182. const int next_index =
  183. selected_action_.value_or(-1) + (reverse_tab_order ? -1 : 1);
  184. if (!IsValidActionIndex(next_index))
  185. return false;
  186. selected_action_ = next_index;
  187. UpdateButtonsOnStateChanged();
  188. return true;
  189. }
  190. views::View* SearchResultActionsView::GetSelectedView() {
  191. DCHECK(HasSelectedAction());
  192. int selected_action = GetSelectedAction();
  193. for (views::View* child : children()) {
  194. if (static_cast<views::Button*>(child)->tag() == selected_action)
  195. return child;
  196. }
  197. return nullptr;
  198. }
  199. void SearchResultActionsView::ClearSelectedAction() {
  200. selected_action_.reset();
  201. UpdateButtonsOnStateChanged();
  202. }
  203. int SearchResultActionsView::GetSelectedAction() const {
  204. return selected_action_.value_or(-1);
  205. }
  206. bool SearchResultActionsView::HasSelectedAction() const {
  207. return selected_action_.has_value();
  208. }
  209. void SearchResultActionsView::CreateImageButton(
  210. const SearchResult::Action& action,
  211. int action_index) {
  212. const gfx::VectorIcon* icon = nullptr;
  213. switch (action.type) {
  214. case SearchResultActionType::kRemove:
  215. icon = &ash::kSearchResultRemoveIcon;
  216. break;
  217. case ash::SearchResultActionType::kAppend:
  218. icon = &ash::kSearchResultAppendIcon;
  219. break;
  220. case ash::SearchResultActionType::kSearchResultActionTypeMax:
  221. NOTREACHED();
  222. }
  223. DCHECK(icon);
  224. auto* const button = AddChildView(std::make_unique<SearchResultActionButton>(
  225. this, action,
  226. base::BindRepeating(
  227. &SearchResultActionsViewDelegate::OnSearchResultActionActivated,
  228. base::Unretained(delegate_), action_index),
  229. features::IsProductivityLauncherEnabled()
  230. ? IconButton::Type::kSmallFloating
  231. : IconButton::Type::kMediumFloating,
  232. icon, action.tooltip_text));
  233. button->set_tag(action_index);
  234. subscriptions_.push_back(button->AddStateChangedCallback(
  235. base::BindRepeating(&SearchResultActionsView::UpdateButtonsOnStateChanged,
  236. base::Unretained(this))));
  237. }
  238. size_t SearchResultActionsView::GetActionCount() const {
  239. return children().size();
  240. }
  241. void SearchResultActionsView::ChildVisibilityChanged(views::View* child) {
  242. PreferredSizeChanged();
  243. }
  244. } // namespace ash