suggestion_chip_container_view.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // Copyright 2018 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/suggestion_chip_container_view.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include "ash/app_list/app_list_util.h"
  8. #include "ash/app_list/views/app_list_main_view.h"
  9. #include "ash/app_list/views/contents_view.h"
  10. #include "ash/app_list/views/search_box_view.h"
  11. #include "ash/public/cpp/app_list/app_list_config.h"
  12. #include "ash/public/cpp/app_list/app_list_features.h"
  13. #include "ash/public/cpp/app_list/app_list_notifier.h"
  14. #include "ash/public/cpp/app_list/app_list_types.h"
  15. #include "ash/public/cpp/app_list/internal_app_id_constants.h"
  16. #include "base/bind.h"
  17. #include "base/callback.h"
  18. #include "ui/compositor/layer.h"
  19. #include "ui/views/accessibility/view_accessibility.h"
  20. #include "ui/views/controls/textfield/textfield.h"
  21. #include "ui/views/focus/focus_manager.h"
  22. #include "ui/views/layout/box_layout.h"
  23. namespace ash {
  24. namespace {
  25. // The spacing between chips.
  26. constexpr int kChipSpacing = 8;
  27. // The minimum allowed number of suggestion chips shown in the container
  28. // (provided that the suggestion chip results contain at least that number of
  29. // items).
  30. constexpr int kMinimumSuggestionChipNumber = 3;
  31. bool IsPolicySuggestionChip(const SearchResult& result) {
  32. return result.display_type() == SearchResultDisplayType::kChip &&
  33. result.display_index() != SearchResultDisplayIndex::kUndefined;
  34. }
  35. struct CompareByDisplayIndexAndThenPositionPriority {
  36. bool operator()(const SearchResult* result1,
  37. const SearchResult* result2) const {
  38. // Sort increasing by display index, then decreasing by position priority.
  39. SearchResultDisplayIndex index1 = result1->display_index();
  40. SearchResultDisplayIndex index2 = result2->display_index();
  41. float priority1 = result1->position_priority();
  42. float priority2 = result2->position_priority();
  43. if (index1 != index2)
  44. return index1 < index2;
  45. return priority1 > priority2;
  46. }
  47. };
  48. } // namespace
  49. SuggestionChipContainerView::SuggestionChipContainerView(
  50. ContentsView* contents_view)
  51. : SearchResultContainerView(
  52. contents_view != nullptr
  53. ? contents_view->GetAppListMainView()->view_delegate()
  54. : nullptr),
  55. contents_view_(contents_view) {
  56. SetPaintToLayer();
  57. layer()->SetFillsBoundsOpaquely(false);
  58. DCHECK(contents_view);
  59. layout_manager_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
  60. views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), kChipSpacing));
  61. layout_manager_->set_main_axis_alignment(
  62. views::BoxLayout::MainAxisAlignment::kCenter);
  63. for (size_t i = 0;
  64. i < static_cast<size_t>(
  65. SharedAppListConfig::instance().num_start_page_tiles());
  66. ++i) {
  67. SearchResultSuggestionChipView* chip =
  68. new SearchResultSuggestionChipView(view_delegate());
  69. chip->SetVisible(false);
  70. chip->set_index_in_container(i);
  71. suggestion_chip_views_.emplace_back(chip);
  72. AddChildView(chip);
  73. }
  74. }
  75. SuggestionChipContainerView::~SuggestionChipContainerView() = default;
  76. SearchResultSuggestionChipView* SuggestionChipContainerView::GetResultViewAt(
  77. size_t index) {
  78. DCHECK(index >= 0 && index < suggestion_chip_views_.size());
  79. return suggestion_chip_views_[index];
  80. }
  81. int SuggestionChipContainerView::DoUpdate() {
  82. const size_t kMaxSuggestionChips =
  83. SharedAppListConfig::instance().num_start_page_tiles();
  84. if (!results()) {
  85. for (size_t i = 0; i < kMaxSuggestionChips; ++i)
  86. suggestion_chip_views_[i]->SetResult(nullptr);
  87. InvalidateLayout();
  88. return 0;
  89. }
  90. // Filter out priority suggestion chips with a non-default value
  91. // for |display_index|.
  92. auto filter_requested_index_chips = [](const SearchResult& r) -> bool {
  93. return IsPolicySuggestionChip(r);
  94. };
  95. std::vector<SearchResult*> requested_index_results =
  96. SearchModel::FilterSearchResultsByFunction(
  97. results(), base::BindRepeating(filter_requested_index_chips),
  98. kMaxSuggestionChips);
  99. std::sort(requested_index_results.begin(), requested_index_results.end(),
  100. CompareByDisplayIndexAndThenPositionPriority());
  101. // Handle placement issues that may arise when multiple app results have
  102. // the same requested index. Reassign the |display_index| of results
  103. // with lower priorities or with conflicting indexes so that the results are
  104. // added to the final display_results list in the correct order.
  105. int previous_index = -1;
  106. for (auto* result : requested_index_results) {
  107. int current_index = result->display_index();
  108. if (current_index <= previous_index) {
  109. current_index = previous_index + 1;
  110. }
  111. SearchResultDisplayIndex final_index =
  112. static_cast<SearchResultDisplayIndex>(current_index);
  113. result->set_display_index(final_index);
  114. previous_index = current_index;
  115. }
  116. // Filter to only kChip results. Also filter out all policy chips to prevent
  117. // duplicates.
  118. auto filter_chip_and_policy = [](const SearchResult& r) -> bool {
  119. return r.display_type() == SearchResultDisplayType::kChip &&
  120. !IsPolicySuggestionChip(r);
  121. };
  122. std::vector<SearchResult*> display_results =
  123. SearchModel::FilterSearchResultsByFunction(
  124. results(), base::BindRepeating(filter_chip_and_policy),
  125. kMaxSuggestionChips - requested_index_results.size());
  126. // Update display results list by placing policy result chips at their
  127. // specified |display_index|. Do not add with a |display_index| that is out
  128. // of bounds.
  129. for (auto* result : requested_index_results) {
  130. if (result->display_index() <= kMaxSuggestionChips - 1) {
  131. display_results.emplace(display_results.begin() + result->display_index(),
  132. result);
  133. }
  134. }
  135. // Update search results here, but wait until layout to add them as child
  136. // views when we know this view's bounds.
  137. for (size_t i = 0; i < kMaxSuggestionChips; ++i) {
  138. suggestion_chip_views_[i]->SetResult(
  139. i < display_results.size() ? display_results[i] : nullptr);
  140. }
  141. auto* notifier = view_delegate()->GetNotifier();
  142. if (notifier) {
  143. std::vector<AppListNotifier::Result> notifier_results;
  144. for (const auto* result : display_results)
  145. notifier_results.emplace_back(result->id(), result->metrics_type());
  146. notifier->NotifyResultsUpdated(SearchResultDisplayType::kChip,
  147. notifier_results);
  148. }
  149. Layout();
  150. return std::min(kMaxSuggestionChips, display_results.size());
  151. }
  152. const char* SuggestionChipContainerView::GetClassName() const {
  153. return "SuggestionChipContainerView";
  154. }
  155. void SuggestionChipContainerView::Layout() {
  156. // Only show the chips that fit in this view's contents bounds.
  157. int total_width = 0;
  158. const int max_width = GetContentsBounds().width();
  159. bool has_hidden_chip = false;
  160. std::vector<views::View*> shown_chips;
  161. for (auto* chip : suggestion_chip_views_) {
  162. layout_manager_->ClearFlexForView(chip);
  163. if (!chip->result()) {
  164. chip->SetVisible(false);
  165. continue;
  166. }
  167. const gfx::Size size = chip->GetPreferredSize();
  168. if (has_hidden_chip ||
  169. (size.width() + total_width > max_width &&
  170. shown_chips.size() >= kMinimumSuggestionChipNumber)) {
  171. chip->SetVisible(false);
  172. has_hidden_chip = true;
  173. continue;
  174. }
  175. chip->SetVisible(true);
  176. shown_chips.push_back(chip);
  177. total_width += (total_width == 0 ? 0 : kChipSpacing) + size.width();
  178. }
  179. // If current suggestion chip width is over the max value, reduce the width by
  180. // flexing views whose width is above average for the available space.
  181. if (total_width > max_width && shown_chips.size() > 0) {
  182. // Remove spacing between chips from total width to get the width available
  183. // to visible suggestion chip views.
  184. int available_width = std::max(
  185. 0, max_width - (kMinimumSuggestionChipNumber - 1) * kChipSpacing);
  186. std::vector<views::View*> views_to_flex;
  187. views_to_flex.swap(shown_chips);
  188. // Do not flex views whose width is below average available width per chip,
  189. // as flexing those would actually increase their size. Repeat this until
  190. // there are no more views to remove from consideration for flexing
  191. // (removing a view increases the average available space for the remaining
  192. // views, so another view's size might fit into the remaining space).
  193. for (size_t i = 0; i < kMinimumSuggestionChipNumber - 1; ++i) {
  194. if (views_to_flex.empty())
  195. break;
  196. std::vector<views::View*> next_views_to_flex;
  197. const int avg_width = available_width / views_to_flex.size();
  198. for (auto* view : views_to_flex) {
  199. gfx::Size view_size = view->GetPreferredSize();
  200. if (view_size.width() <= avg_width) {
  201. available_width -= view_size.width();
  202. } else {
  203. next_views_to_flex.push_back(view);
  204. }
  205. }
  206. if (views_to_flex.size() == next_views_to_flex.size())
  207. break;
  208. views_to_flex.swap(next_views_to_flex);
  209. }
  210. // Flex the views that are left over.
  211. for (auto* view : views_to_flex)
  212. layout_manager_->SetFlexForView(view, 1);
  213. }
  214. views::View::Layout();
  215. }
  216. bool SuggestionChipContainerView::OnKeyPressed(const ui::KeyEvent& event) {
  217. // Let the FocusManager handle Left/Right keys.
  218. if (!IsUnhandledUpDownKeyEvent(event))
  219. return false;
  220. // Up key moves focus to the search box. Down key moves focus to the first
  221. // app.
  222. views::View* v = nullptr;
  223. if (event.key_code() == ui::VKEY_UP) {
  224. v = contents_view_->GetSearchBoxView()->search_box();
  225. } else {
  226. // The first app is the next to this view's last focusable view.
  227. views::View* last_focusable_view =
  228. GetFocusManager()->GetNextFocusableView(this, nullptr, true, false);
  229. v = GetFocusManager()->GetNextFocusableView(last_focusable_view, nullptr,
  230. false, false);
  231. }
  232. if (v)
  233. v->RequestFocus();
  234. return true;
  235. }
  236. void SuggestionChipContainerView::DisableFocusForShowingActiveFolder(
  237. bool disabled) {
  238. for (auto* chip : suggestion_chip_views_)
  239. chip->SetEnabled(!disabled);
  240. // Ignore the container view in accessibility tree so that suggestion chips
  241. // will not be accessed by ChromeVox.
  242. SetViewIgnoredForAccessibility(this, disabled);
  243. }
  244. void SuggestionChipContainerView::OnTabletModeChanged(bool started) {
  245. in_tablet_mode_ = started;
  246. UpdateBlurState();
  247. }
  248. void SuggestionChipContainerView::SetBlurDisabled(bool blur_disabled) {
  249. if (blur_disabled_ == blur_disabled)
  250. return;
  251. blur_disabled_ = blur_disabled;
  252. UpdateBlurState();
  253. }
  254. void SuggestionChipContainerView::UpdateBlurState() {
  255. // Enable/Disable chips' background blur based on tablet mode, and whether
  256. // blur has been explicitly disabled.
  257. for (auto* chip : suggestion_chip_views_)
  258. chip->SetBackgroundBlurEnabled(in_tablet_mode_ && !blur_disabled_);
  259. }
  260. } // namespace ash