feature_pods_container_view.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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/system/unified/feature_pods_container_view.h"
  5. #include "ash/public/cpp/pagination/pagination_controller.h"
  6. #include "ash/public/cpp/pagination/pagination_model.h"
  7. #include "ash/system/tray/tray_constants.h"
  8. #include "ash/system/unified/feature_pod_button.h"
  9. #include "ash/system/unified/unified_system_tray_controller.h"
  10. namespace ash {
  11. FeaturePodsContainerView::FeaturePodsContainerView(
  12. UnifiedSystemTrayController* controller,
  13. bool initially_expanded)
  14. : controller_(controller),
  15. pagination_model_(controller->model()->pagination_model()),
  16. expanded_amount_(initially_expanded ? 1.0 : 0.0),
  17. feature_pod_rows_(kUnifiedFeaturePodMaxRows) {
  18. pagination_model_->AddObserver(this);
  19. }
  20. FeaturePodsContainerView::~FeaturePodsContainerView() {
  21. DCHECK(pagination_model_);
  22. pagination_model_->RemoveObserver(this);
  23. }
  24. void FeaturePodsContainerView::SetExpandedAmount(double expanded_amount) {
  25. DCHECK(0.0 <= expanded_amount && expanded_amount <= 1.0);
  26. if (expanded_amount_ == expanded_amount)
  27. return;
  28. expanded_amount_ = expanded_amount;
  29. int visible_index = 0;
  30. for (auto* view : children()) {
  31. FeaturePodButton* button = static_cast<FeaturePodButton*>(view);
  32. // When collapsing from page 1, buttons below the second row fade out
  33. // while the rest move up into a single row for the collapsed state.
  34. // When collapsing from page > 1, each row of buttons fades out one by one
  35. // and once expanded_amount is less than kCollapseThreshold we begin to
  36. // fade in the single row of buttons for the collapsed state.
  37. if (expanded_amount_ > 0.0 && expanded_amount_ < kCollapseThreshold &&
  38. pagination_model_->selected_page() > 0) {
  39. button->SetExpandedAmount(1.0 - expanded_amount,
  40. true /* fade_icon_button */);
  41. } else if (visible_index > kUnifiedFeaturePodMaxItemsInCollapsed) {
  42. int row =
  43. (visible_index / kUnifiedFeaturePodItemsInRow) % feature_pod_rows_;
  44. double button_expanded_amount =
  45. expanded_amount
  46. ? std::min(1.0, expanded_amount +
  47. (0.25 * (feature_pod_rows_ - row - 1)))
  48. : expanded_amount;
  49. button->SetExpandedAmount(button_expanded_amount,
  50. true /* fade_icon_button */);
  51. } else {
  52. button->SetExpandedAmount(expanded_amount, false /* fade_icon_button */);
  53. }
  54. if (view->GetVisible())
  55. visible_index++;
  56. }
  57. UpdateChildVisibility();
  58. // We have to call Layout() explicitly here.
  59. Layout();
  60. }
  61. int FeaturePodsContainerView::GetExpandedHeight() const {
  62. const int visible_count = GetVisibleCount();
  63. // floor(visible_count / kUnifiedFeaturePodItemsInRow)
  64. int number_of_lines = (visible_count + kUnifiedFeaturePodItemsInRow - 1) /
  65. kUnifiedFeaturePodItemsInRow;
  66. number_of_lines = std::min(number_of_lines, feature_pod_rows_);
  67. return kUnifiedFeaturePodBottomPadding +
  68. (kUnifiedFeaturePodVerticalPadding + kUnifiedFeaturePodSize.height()) *
  69. std::max(0, number_of_lines - 1) +
  70. kUnifiedFeaturePodSize.height() + kUnifiedFeaturePodTopPadding;
  71. }
  72. int FeaturePodsContainerView::GetCollapsedHeight() const {
  73. return 2 * kUnifiedFeaturePodCollapsedVerticalPadding +
  74. kUnifiedFeaturePodCollapsedSize.height();
  75. }
  76. gfx::Size FeaturePodsContainerView::CalculatePreferredSize() const {
  77. return gfx::Size(
  78. kTrayMenuWidth,
  79. static_cast<int>(GetCollapsedHeight() * (1.0 - expanded_amount_) +
  80. GetExpandedHeight() * expanded_amount_));
  81. }
  82. void FeaturePodsContainerView::ChildVisibilityChanged(View* child) {
  83. // ChildVisibilityChanged can change child visibility using
  84. // SetVisibleByContainer() in UpdateChildVisibility(), so we have to prevent
  85. // reentrancy.
  86. if (changing_visibility_)
  87. return;
  88. // Visibility change is caused by the child's SetVisible(), so update actual
  89. // visibility and propagate the container size change to the parent.
  90. UpdateChildVisibility();
  91. PreferredSizeChanged();
  92. Layout();
  93. SchedulePaint();
  94. }
  95. void FeaturePodsContainerView::ViewHierarchyChanged(
  96. const views::ViewHierarchyChangedDetails& details) {
  97. UpdateChildVisibility();
  98. }
  99. void FeaturePodsContainerView::Layout() {
  100. UpdateCollapsedSidePadding();
  101. CalculateIdealBoundsForFeaturePods();
  102. for (size_t i = 0; i < visible_buttons_.view_size(); ++i) {
  103. auto* button = visible_buttons_.view_at(i);
  104. button->SetBoundsRect(visible_buttons_.ideal_bounds(i));
  105. }
  106. }
  107. const char* FeaturePodsContainerView::GetClassName() const {
  108. return "FeaturePodsContainerView";
  109. }
  110. void FeaturePodsContainerView::UpdateChildVisibility() {
  111. DCHECK(!changing_visibility_);
  112. changing_visibility_ = true;
  113. int visible_count = 0;
  114. for (auto* view : children()) {
  115. auto* child = static_cast<FeaturePodButton*>(view);
  116. bool visible = IsButtonVisible(child, visible_count);
  117. child->SetVisibleByContainer(visible);
  118. if (visible) {
  119. if (!visible_buttons_.GetIndexOfView(child).has_value())
  120. visible_buttons_.Add(child, visible_count);
  121. ++visible_count;
  122. } else {
  123. if (auto index = visible_buttons_.GetIndexOfView(child);
  124. index.has_value()) {
  125. visible_buttons_.Remove(index.value());
  126. }
  127. }
  128. }
  129. UpdateTotalPages();
  130. changing_visibility_ = false;
  131. }
  132. bool FeaturePodsContainerView::IsButtonVisible(FeaturePodButton* button,
  133. int index) {
  134. return button->visible_preferred() &&
  135. (expanded_amount_ > 0.0 ||
  136. index < kUnifiedFeaturePodMaxItemsInCollapsed);
  137. }
  138. int FeaturePodsContainerView::GetVisibleCount() const {
  139. return std::count_if(
  140. children().cbegin(), children().cend(), [](const auto* v) {
  141. return static_cast<const FeaturePodButton*>(v)->visible_preferred();
  142. });
  143. }
  144. void FeaturePodsContainerView::EnsurePageWithButton(views::View* button) {
  145. auto index = visible_buttons_.GetIndexOfView(button->parent());
  146. if (!index.has_value())
  147. return;
  148. int tiles_per_page = GetTilesPerPage();
  149. size_t first_index = pagination_model_->selected_page() * tiles_per_page;
  150. size_t last_index =
  151. ((pagination_model_->selected_page() + 1) * tiles_per_page) - 1;
  152. if (index.value() < first_index || index.value() > last_index) {
  153. int page = ((index.value() + 1) / tiles_per_page) +
  154. ((index.value() + 1) % tiles_per_page ? 1 : 0) - 1;
  155. pagination_model_->SelectPage(page, true /*animate*/);
  156. }
  157. }
  158. gfx::Point FeaturePodsContainerView::GetButtonPosition(
  159. int visible_index) const {
  160. int row = visible_index / kUnifiedFeaturePodItemsInRow;
  161. int column = visible_index % kUnifiedFeaturePodItemsInRow;
  162. int x = kUnifiedFeaturePodHorizontalSidePadding +
  163. (kUnifiedFeaturePodSize.width() +
  164. kUnifiedFeaturePodHorizontalMiddlePadding) *
  165. column;
  166. int y = kUnifiedFeaturePodTopPadding + (kUnifiedFeaturePodSize.height() +
  167. kUnifiedFeaturePodVerticalPadding) *
  168. row;
  169. // Only feature pods visible in the collapsed state (i.e. the first 5 pods)
  170. // move during expansion/collapse. Otherwise, the button position will always
  171. // be constant.
  172. if (expanded_amount_ == 1.0 ||
  173. visible_index > kUnifiedFeaturePodMaxItemsInCollapsed ||
  174. (pagination_model_->selected_page() > 0 &&
  175. expanded_amount_ >= kCollapseThreshold)) {
  176. return gfx::Point(x, y);
  177. }
  178. int collapsed_x =
  179. collapsed_side_padding_ + (kUnifiedFeaturePodCollapsedSize.width() +
  180. kUnifiedFeaturePodCollapsedHorizontalPadding) *
  181. visible_index;
  182. int collapsed_y = kUnifiedFeaturePodCollapsedVerticalPadding;
  183. // When fully collapsed or collapsing from a different page to the first
  184. // page, just return the collapsed position.
  185. if (expanded_amount_ == 0.0 || (expanded_amount_ < kCollapseThreshold &&
  186. pagination_model_->selected_page() > 0))
  187. return gfx::Point(collapsed_x, collapsed_y);
  188. // Button width is different between expanded and collapsed states.
  189. // During the transition, expanded width is used, so it should be adjusted.
  190. collapsed_x -= (kUnifiedFeaturePodSize.width() -
  191. kUnifiedFeaturePodCollapsedSize.width()) /
  192. 2;
  193. return gfx::Point(
  194. x * expanded_amount_ + collapsed_x * (1.0 - expanded_amount_),
  195. y * expanded_amount_ + collapsed_y * (1.0 - expanded_amount_));
  196. }
  197. int FeaturePodsContainerView::CalculateRowsFromHeight(int height) {
  198. int available_height =
  199. height - kUnifiedFeaturePodBottomPadding - kUnifiedFeaturePodTopPadding;
  200. int row_height =
  201. kUnifiedFeaturePodSize.height() + kUnifiedFeaturePodVerticalPadding;
  202. // Only use the max number of rows when there is enough space
  203. // to show the fully expanded message center and quick settings.
  204. if (available_height > (kUnifiedFeaturePodMaxRows * row_height) &&
  205. available_height - (kUnifiedFeaturePodMaxRows * row_height) >
  206. kMessageCenterCollapseThreshold) {
  207. return kUnifiedFeaturePodMaxRows;
  208. }
  209. // Use 1 less than the max number of rows when there is enough
  210. // space to show the message center in the collapsed state along
  211. // with the expanded quick settings.
  212. int feature_pod_rows = kUnifiedFeaturePodMaxRows - 1;
  213. if (available_height > (feature_pod_rows * row_height) &&
  214. available_height - (feature_pod_rows * row_height) >
  215. kStackedNotificationBarHeight) {
  216. return feature_pod_rows;
  217. }
  218. return kUnifiedFeaturePodMinRows;
  219. }
  220. void FeaturePodsContainerView::SetMaxHeight(int max_height) {
  221. int feature_pod_rows = CalculateRowsFromHeight(max_height);
  222. if (feature_pod_rows_ != feature_pod_rows) {
  223. feature_pod_rows_ = feature_pod_rows;
  224. UpdateTotalPages();
  225. }
  226. }
  227. void FeaturePodsContainerView::UpdateCollapsedSidePadding() {
  228. const int visible_count =
  229. std::min(GetVisibleCount(), kUnifiedFeaturePodMaxItemsInCollapsed);
  230. int contents_width =
  231. visible_count * kUnifiedFeaturePodCollapsedSize.width() +
  232. (visible_count - 1) * kUnifiedFeaturePodCollapsedHorizontalPadding;
  233. collapsed_side_padding_ = (kTrayMenuWidth - contents_width) / 2;
  234. DCHECK(collapsed_side_padding_ > 0);
  235. }
  236. void FeaturePodsContainerView::AddFeaturePodButton(FeaturePodButton* button) {
  237. size_t view_size = visible_buttons_.view_size();
  238. if (IsButtonVisible(button, view_size)) {
  239. visible_buttons_.Add(button, view_size);
  240. }
  241. AddChildView(button);
  242. UpdateTotalPages();
  243. }
  244. const gfx::Vector2d FeaturePodsContainerView::CalculateTransitionOffset(
  245. int page_of_view) const {
  246. gfx::Size grid_size = CalculatePreferredSize();
  247. // If there is a transition, calculates offset for current and target page.
  248. const int current_page = pagination_model_->selected_page();
  249. const PaginationModel::Transition& transition =
  250. pagination_model_->transition();
  251. const bool is_valid =
  252. pagination_model_->is_valid_page(transition.target_page);
  253. // Transition to previous page means negative offset.
  254. const int direction = transition.target_page > current_page ? -1 : 1;
  255. int x_offset = 0;
  256. int y_offset = 0;
  257. // Page size including padding pixels. A tile.x + page_width means the same
  258. // tile slot in the next page.
  259. const int page_width = grid_size.width() + kUnifiedFeaturePodsPageSpacing;
  260. if (page_of_view < current_page)
  261. x_offset = -page_width;
  262. else if (page_of_view > current_page)
  263. x_offset = page_width;
  264. if (is_valid) {
  265. if (page_of_view == current_page ||
  266. page_of_view == transition.target_page) {
  267. x_offset += transition.progress * page_width * direction;
  268. }
  269. }
  270. return gfx::Vector2d(x_offset, y_offset);
  271. }
  272. void FeaturePodsContainerView::CalculateIdealBoundsForFeaturePods() {
  273. for (size_t i = 0; i < visible_buttons_.view_size(); ++i) {
  274. gfx::Rect tile_bounds;
  275. gfx::Size child_size;
  276. // When we are on the first page we calculate bounds for an expanded tray
  277. // when expanded_amount is greater than zero. However, when not on the first
  278. // page, we only calculate bounds for an expanded tray until expanded_amount
  279. // is above kCollapseThreshold. Below kCollapseThreshold we return collapsed
  280. // bounds.
  281. if ((expanded_amount_ > 0.0 && pagination_model_->selected_page() == 0) ||
  282. expanded_amount_ >= kCollapseThreshold) {
  283. child_size = kUnifiedFeaturePodSize;
  284. // Flexibly give more height if the child view doesn't fit into the
  285. // default height, so that label texts won't be broken up in the middle.
  286. child_size.set_height(std::max(
  287. child_size.height(),
  288. visible_buttons_.view_at(i)->GetHeightForWidth(child_size.height())));
  289. tile_bounds =
  290. gfx::Rect(GetButtonPosition(i % GetTilesPerPage()), child_size);
  291. // TODO(amehfooz): refactor this logic so that the ideal_bounds are set
  292. // once when the transition starts and the actual feature pod bounds are
  293. // interpolated using the ideal_bounds as the transition progresses.
  294. tile_bounds.Offset(CalculateTransitionOffset(i / GetTilesPerPage()));
  295. } else {
  296. child_size = kUnifiedFeaturePodCollapsedSize;
  297. tile_bounds = gfx::Rect(GetButtonPosition(i), child_size);
  298. }
  299. visible_buttons_.set_ideal_bounds(i, tile_bounds);
  300. }
  301. }
  302. int FeaturePodsContainerView::GetTilesPerPage() const {
  303. return kUnifiedFeaturePodItemsInRow * feature_pod_rows_;
  304. }
  305. void FeaturePodsContainerView::UpdateTotalPages() {
  306. int total_pages = 0;
  307. size_t total_visible = visible_buttons_.view_size();
  308. int tiles_per_page = GetTilesPerPage();
  309. if (!visible_buttons_.view_size() || !tiles_per_page) {
  310. total_pages = 0;
  311. } else {
  312. total_pages = (total_visible / tiles_per_page) +
  313. (total_visible % tiles_per_page ? 1 : 0);
  314. }
  315. pagination_model_->SetTotalPages(total_pages);
  316. }
  317. void FeaturePodsContainerView::TransitionChanged() {
  318. const PaginationModel::Transition& transition =
  319. pagination_model_->transition();
  320. if (pagination_model_->is_valid_page(transition.target_page))
  321. Layout();
  322. }
  323. void FeaturePodsContainerView::OnGestureEvent(ui::GestureEvent* event) {
  324. if (controller_->pagination_controller()->OnGestureEvent(*event,
  325. GetContentsBounds()))
  326. event->SetHandled();
  327. }
  328. void FeaturePodsContainerView::OnScrollEvent(ui::ScrollEvent* event) {
  329. controller_->pagination_controller()->OnScroll(
  330. gfx::Vector2d(event->x_offset(), event->y_offset()), event->type());
  331. event->SetHandled();
  332. }
  333. bool FeaturePodsContainerView::OnMouseWheel(const ui::MouseWheelEvent& event) {
  334. return controller_->pagination_controller()->OnScroll(event.offset(),
  335. event.type());
  336. }
  337. } // namespace ash