holding_space_tray_bubble.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. // Copyright 2020 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/holding_space/holding_space_tray_bubble.h"
  5. #include <vector>
  6. #include "ash/public/cpp/holding_space/holding_space_constants.h"
  7. #include "ash/public/cpp/holding_space/holding_space_item.h"
  8. #include "ash/public/cpp/holding_space/holding_space_metrics.h"
  9. #include "ash/public/cpp/holding_space/holding_space_prefs.h"
  10. #include "ash/public/cpp/shelf_config.h"
  11. #include "ash/session/session_controller_impl.h"
  12. #include "ash/shell.h"
  13. #include "ash/system/holding_space/holding_space_item_view.h"
  14. #include "ash/system/holding_space/holding_space_tray.h"
  15. #include "ash/system/holding_space/pinned_files_bubble.h"
  16. #include "ash/system/holding_space/recent_files_bubble.h"
  17. #include "ash/system/tray/tray_bubble_wrapper.h"
  18. #include "ash/system/tray/tray_constants.h"
  19. #include "ash/system/tray/tray_utils.h"
  20. #include "ash/wm/work_area_insets.h"
  21. #include "base/bind.h"
  22. #include "base/containers/adapters.h"
  23. #include "ui/aura/env.h"
  24. #include "ui/aura/window.h"
  25. #include "ui/compositor/compositor.h"
  26. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  27. #include "ui/gfx/animation/slide_animation.h"
  28. #include "ui/gfx/geometry/insets.h"
  29. #include "ui/views/animation/animation_delegate_views.h"
  30. #include "ui/views/layout/box_layout.h"
  31. #include "ui/views/layout/proposed_layout.h"
  32. namespace ash {
  33. namespace {
  34. // Animation.
  35. constexpr base::TimeDelta kAnimationDuration = base::Milliseconds(167);
  36. // Helpers ---------------------------------------------------------------------
  37. // Finds all visible `HoldingSpaceItem`s in `parent`'s view hierarchy.
  38. void FindVisibleHoldingSpaceItems(
  39. views::View* parent,
  40. std::vector<const HoldingSpaceItem*>* result) {
  41. for (views::View* view : parent->children()) {
  42. if (view->GetVisible() && HoldingSpaceItemView::IsInstance(view))
  43. result->push_back(HoldingSpaceItemView::Cast(view)->item());
  44. FindVisibleHoldingSpaceItems(view, result);
  45. }
  46. }
  47. // Records the time from first availability to first entry into holding space.
  48. void RecordTimeFromFirstAvailabilityToFirstEntry(PrefService* prefs) {
  49. base::Time time_of_first_availability =
  50. holding_space_prefs::GetTimeOfFirstAvailability(prefs).value();
  51. base::Time time_of_first_entry =
  52. holding_space_prefs::GetTimeOfFirstEntry(prefs).value();
  53. holding_space_metrics::RecordTimeFromFirstAvailabilityToFirstEntry(
  54. time_of_first_entry - time_of_first_availability);
  55. }
  56. // HoldingSpaceTrayBubbleEventHandler ------------------------------------------
  57. class HoldingSpaceTrayBubbleEventHandler : public ui::EventHandler {
  58. public:
  59. HoldingSpaceTrayBubbleEventHandler(HoldingSpaceTrayBubble* bubble,
  60. HoldingSpaceViewDelegate* delegate)
  61. : bubble_(bubble), delegate_(delegate) {
  62. aura::Env::GetInstance()->AddPreTargetHandler(
  63. this, ui::EventTarget::Priority::kSystem);
  64. }
  65. HoldingSpaceTrayBubbleEventHandler(
  66. const HoldingSpaceTrayBubbleEventHandler&) = delete;
  67. HoldingSpaceTrayBubbleEventHandler& operator=(
  68. const HoldingSpaceTrayBubbleEventHandler&) = delete;
  69. ~HoldingSpaceTrayBubbleEventHandler() override {
  70. aura::Env::GetInstance()->RemovePreTargetHandler(this);
  71. }
  72. private:
  73. // ui::EventHandler:
  74. void OnKeyEvent(ui::KeyEvent* event) override {
  75. if (event->type() != ui::ET_KEY_PRESSED)
  76. return;
  77. // Only handle `event`s that would otherwise escape the `bubble_` window.
  78. aura::Window* target = static_cast<aura::Window*>(event->target());
  79. aura::Window* bubble_window = bubble_->GetBubbleWidget()->GetNativeView();
  80. if (target && (bubble_window->Contains(target)))
  81. return;
  82. // If `delegate_` handles the `event`, prevent additional bubbling up.
  83. if (delegate_->OnHoldingSpaceTrayBubbleKeyPressed(*event))
  84. event->StopPropagation();
  85. }
  86. HoldingSpaceTrayBubble* const bubble_;
  87. HoldingSpaceViewDelegate* const delegate_;
  88. };
  89. // ChildBubbleContainerLayout --------------------------------------------------
  90. // A class similar to a `views::LayoutManager` which supports calculating and
  91. // applying `views::ProposedLayout`s. Views are laid out similar to a vertical
  92. // `views::BoxLayout` with the first child flexing to cede layout space if the
  93. // layout would otherwise exceed maximum height restrictions. Subsequent child
  94. // views will be laid outside of `host` bounds if there is insufficient space.
  95. class ChildBubbleContainerLayout {
  96. public:
  97. ChildBubbleContainerLayout(views::View* host, int child_spacing)
  98. : host_(host), child_spacing_(child_spacing) {}
  99. // Sets the maximum height restriction for the layout.
  100. void SetMaxHeight(int max_height) { max_height_ = max_height; }
  101. // Calculates and returns a `views::ProposedLayout` given current maximum
  102. // height restrictions and the current state of the view hierarchy. Note that
  103. // views are laid out similar to a vertical `views::BoxLayout` with the first
  104. // child flexing to cede layout space if the layout would otherwise exceed
  105. // maximum height restrictions.
  106. views::ProposedLayout CalculateProposedLayout() const {
  107. views::ProposedLayout layout;
  108. layout.host_size = gfx::Size(kHoldingSpaceBubbleWidth, 0);
  109. int top = 0;
  110. for (views::View* child : host_->children()) {
  111. if (!child->GetVisible()) {
  112. views::ChildLayout child_layout;
  113. child_layout.child_view = child;
  114. child_layout.bounds = gfx::Rect(0, top, layout.host_size.width(), 0);
  115. child_layout.visible = false;
  116. layout.child_layouts.push_back(std::move(child_layout));
  117. continue;
  118. }
  119. // Apply child spacing.
  120. if (top != 0) {
  121. top += child_spacing_;
  122. layout.host_size.Enlarge(0, child_spacing_);
  123. }
  124. const int height = child->GetHeightForWidth(layout.host_size.width());
  125. views::ChildLayout child_layout;
  126. child_layout.child_view = child;
  127. child_layout.bounds = gfx::Rect(0, top, layout.host_size.width(), height);
  128. child_layout.visible = true;
  129. layout.child_layouts.push_back(std::move(child_layout));
  130. layout.host_size.Enlarge(0, height);
  131. top += height;
  132. }
  133. // If maximum height restrictions are present and preferred height exceeds
  134. // maximum height, the first child view should cede layout space for others.
  135. // Note that subsequent child views will still be given their preferred
  136. // height so its possible they will be laid outside of `host_` view bounds.
  137. if (max_height_ && layout.host_size.height() > max_height_) {
  138. const int height_to_cede =
  139. std::min(layout.child_layouts[0].bounds.height(),
  140. layout.host_size.height() - max_height_);
  141. layout.child_layouts[0].bounds.Inset(
  142. gfx::Insets::TLBR(0, 0, height_to_cede, 0));
  143. for (size_t i = 1; i < layout.child_layouts.size(); ++i)
  144. layout.child_layouts[i].bounds.Offset(0, -height_to_cede);
  145. layout.host_size.Enlarge(0, -height_to_cede);
  146. }
  147. return layout;
  148. }
  149. // Applies the specified `layout` to the view hierarchy.
  150. void ApplyLayout(const views::ProposedLayout& layout) {
  151. for (const auto& child_layout : layout.child_layouts)
  152. child_layout.child_view->SetBoundsRect(child_layout.bounds);
  153. }
  154. views::View* const host_;
  155. const int child_spacing_;
  156. // Maximum height restriction for the layout. If zero, it is assumed that
  157. // there is no maximum height restriction.
  158. int max_height_ = 0;
  159. };
  160. } // namespace
  161. // HoldingSpaceTrayBubble::ChildBubbleContainer --------------------------------
  162. // The container for `HoldingSpaceTrayBubble` which parents its child bubbles
  163. // and animates layout changes. Note that this view uses a pseudo layout manager
  164. // to calculate bounds for its children, but animates any layout changes itself.
  165. class HoldingSpaceTrayBubble::ChildBubbleContainer
  166. : public views::View,
  167. public views::AnimationDelegateViews {
  168. public:
  169. ChildBubbleContainer()
  170. : views::AnimationDelegateViews(this),
  171. layout_manager_(this, kHoldingSpaceBubbleContainerChildSpacing) {}
  172. // Sets the maximum height restriction for the layout.
  173. void SetMaxHeight(int max_height) {
  174. layout_manager_.SetMaxHeight(max_height);
  175. PreferredSizeChanged();
  176. }
  177. // views::View:
  178. int GetHeightForWidth(int width) const override {
  179. DCHECK_EQ(width, kHoldingSpaceBubbleWidth);
  180. if (current_layout_.host_size.IsEmpty())
  181. current_layout_ = layout_manager_.CalculateProposedLayout();
  182. return current_layout_.host_size.height();
  183. }
  184. void ChildPreferredSizeChanged(views::View* child) override {
  185. PreferredSizeChanged();
  186. }
  187. void ChildVisibilityChanged(views::View* child) override {
  188. PreferredSizeChanged();
  189. }
  190. void PreferredSizeChanged() override {
  191. if (!GetWidget())
  192. return;
  193. const views::ProposedLayout target_layout(
  194. layout_manager_.CalculateProposedLayout());
  195. // If `target_layout_` is unchanged then a layout animation is in progress
  196. // and the only thing needed is to propagate the event up the tree so that
  197. // the widget will be resized and re-anchored.
  198. if (target_layout == target_layout_) {
  199. views::View::PreferredSizeChanged();
  200. return;
  201. }
  202. // If `current_layout_` is empty then this is the first layout. Don't
  203. // animate the first layout.
  204. if (current_layout_.host_size.IsEmpty()) {
  205. current_layout_ = target_layout_ = target_layout;
  206. views::View::PreferredSizeChanged();
  207. return;
  208. }
  209. start_layout_ = current_layout_;
  210. target_layout_ = target_layout;
  211. // Animate changes from the `current_layout_` to the `target_layout_`. Note
  212. // the use of a throughput tracker to record layout animation smoothness.
  213. layout_animation_ = std::make_unique<gfx::SlideAnimation>(this);
  214. layout_animation_->SetSlideDuration(
  215. ui::ScopedAnimationDurationScaleMode::duration_multiplier() *
  216. kAnimationDuration);
  217. layout_animation_->SetTweenType(gfx::Tween::Type::FAST_OUT_SLOW_IN);
  218. layout_animation_throughput_tracker_ =
  219. GetWidget()->GetCompositor()->RequestNewThroughputTracker();
  220. layout_animation_throughput_tracker_->Start(
  221. metrics_util::ForSmoothness(base::BindRepeating(
  222. holding_space_metrics::RecordBubbleResizeAnimationSmoothness)));
  223. layout_animation_->Show();
  224. }
  225. void Layout() override { layout_manager_.ApplyLayout(current_layout_); }
  226. // views::AnimationDelegateViews:
  227. void AnimationProgressed(const gfx::Animation* animation) override {
  228. current_layout_ = views::ProposedLayoutBetween(
  229. animation->GetCurrentValue(), start_layout_, target_layout_);
  230. PreferredSizeChanged();
  231. }
  232. void AnimationEnded(const gfx::Animation* animation) override {
  233. current_layout_ = target_layout_;
  234. PreferredSizeChanged();
  235. // Record layout animation smoothness.
  236. layout_animation_throughput_tracker_->Stop();
  237. layout_animation_throughput_tracker_.reset();
  238. }
  239. private:
  240. // A pseudo layout manager which supports calculating and applying
  241. // `views::ProposedLayouts`. It lays out views similarly to a vertical
  242. // `views::BoxLayout` with the first view flexing to cede layout space to
  243. // siblings if maximum height restrictions would otherwise be exceeded.
  244. ChildBubbleContainerLayout layout_manager_;
  245. mutable views::ProposedLayout start_layout_; // Layout being animated from.
  246. mutable views::ProposedLayout current_layout_; // Current layout.
  247. mutable views::ProposedLayout target_layout_; // Layout being animated to.
  248. std::unique_ptr<gfx::SlideAnimation> layout_animation_;
  249. absl::optional<ui::ThroughputTracker> layout_animation_throughput_tracker_;
  250. };
  251. // HoldingSpaceTrayBubble ------------------------------------------------------
  252. HoldingSpaceTrayBubble::HoldingSpaceTrayBubble(
  253. HoldingSpaceTray* holding_space_tray)
  254. : holding_space_tray_(holding_space_tray) {
  255. TrayBubbleView::InitParams init_params;
  256. init_params.delegate = holding_space_tray->GetWeakPtr();
  257. init_params.parent_window = holding_space_tray->GetBubbleWindowContainer();
  258. init_params.anchor_view = nullptr;
  259. init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
  260. init_params.anchor_rect =
  261. holding_space_tray->shelf()->GetSystemTrayAnchorRect();
  262. init_params.insets = GetTrayBubbleInsets();
  263. init_params.shelf_alignment = holding_space_tray->shelf()->alignment();
  264. init_params.preferred_width = kHoldingSpaceBubbleWidth;
  265. init_params.close_on_deactivate = true;
  266. init_params.has_shadow = false;
  267. init_params.reroute_event_handler = true;
  268. init_params.transparent = true;
  269. // Create and customize bubble view.
  270. TrayBubbleView* bubble_view = new TrayBubbleView(init_params);
  271. // Ensure bubble frame does not draw background behind bubble view.
  272. child_bubble_container_ =
  273. bubble_view->AddChildView(std::make_unique<ChildBubbleContainer>());
  274. child_bubble_container_->SetMaxHeight(CalculateMaxHeight());
  275. // Add pinned files child bubble.
  276. child_bubbles_.push_back(child_bubble_container_->AddChildView(
  277. std::make_unique<PinnedFilesBubble>(&delegate_)));
  278. // Add recent files child bubble.
  279. child_bubbles_.push_back(child_bubble_container_->AddChildView(
  280. std::make_unique<RecentFilesBubble>(&delegate_)));
  281. // Initialize child bubbles.
  282. for (HoldingSpaceTrayChildBubble* child_bubble : child_bubbles_)
  283. child_bubble->Init();
  284. // Show the bubble.
  285. bubble_wrapper_ =
  286. std::make_unique<TrayBubbleWrapper>(holding_space_tray, bubble_view);
  287. event_handler_ =
  288. std::make_unique<HoldingSpaceTrayBubbleEventHandler>(this, &delegate_);
  289. PrefService* const prefs =
  290. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  291. // Mark when holding space was first entered. If this is not the first entry
  292. // into holding space, this will no-op. If this is the first entry, record the
  293. // amount of time from first availability to first entry into holding space.
  294. if (holding_space_prefs::MarkTimeOfFirstEntry(prefs))
  295. RecordTimeFromFirstAvailabilityToFirstEntry(prefs);
  296. // Record visible holding space items.
  297. std::vector<const HoldingSpaceItem*> visible_items;
  298. FindVisibleHoldingSpaceItems(bubble_view, &visible_items);
  299. holding_space_metrics::RecordItemCounts(visible_items);
  300. shelf_observation_.Observe(holding_space_tray_->shelf());
  301. tablet_mode_observation_.Observe(Shell::Get()->tablet_mode_controller());
  302. }
  303. HoldingSpaceTrayBubble::~HoldingSpaceTrayBubble() {
  304. bubble_wrapper_->bubble_view()->ResetDelegate();
  305. // Explicitly reset child bubbles so that they will stop observing the holding
  306. // space controller/model while they are asynchronously destroyed.
  307. for (HoldingSpaceTrayChildBubble* child_bubble : child_bubbles_)
  308. child_bubble->Reset();
  309. }
  310. void HoldingSpaceTrayBubble::AnchorUpdated() {
  311. bubble_wrapper_->bubble_view()->UpdateBubble();
  312. }
  313. TrayBubbleView* HoldingSpaceTrayBubble::GetBubbleView() {
  314. return bubble_wrapper_->bubble_view();
  315. }
  316. views::Widget* HoldingSpaceTrayBubble::GetBubbleWidget() {
  317. return bubble_wrapper_->GetBubbleWidget();
  318. }
  319. std::vector<HoldingSpaceItemView*>
  320. HoldingSpaceTrayBubble::GetHoldingSpaceItemViews() {
  321. std::vector<HoldingSpaceItemView*> views;
  322. for (HoldingSpaceTrayChildBubble* child_bubble : child_bubbles_) {
  323. auto child_bubble_views = child_bubble->GetHoldingSpaceItemViews();
  324. views.insert(views.end(), child_bubble_views.begin(),
  325. child_bubble_views.end());
  326. }
  327. return views;
  328. }
  329. int HoldingSpaceTrayBubble::CalculateMaxHeight() const {
  330. const WorkAreaInsets* work_area = WorkAreaInsets::ForWindow(
  331. holding_space_tray_->shelf()->GetWindow()->GetRootWindow());
  332. const int bottom =
  333. holding_space_tray_->shelf()->IsHorizontalAlignment()
  334. ? holding_space_tray_->shelf()->GetShelfBoundsInScreen().y()
  335. : work_area->user_work_area_bounds().bottom();
  336. const int free_space_height_above_anchor =
  337. bottom - work_area->user_work_area_bounds().y();
  338. const gfx::Insets insets = GetTrayBubbleInsets();
  339. const int bubble_vertical_margin = insets.top() + insets.bottom();
  340. return free_space_height_above_anchor - bubble_vertical_margin;
  341. }
  342. void HoldingSpaceTrayBubble::UpdateBubbleBounds() {
  343. child_bubble_container_->SetMaxHeight(CalculateMaxHeight());
  344. bubble_wrapper_->bubble_view()->ChangeAnchorRect(
  345. holding_space_tray_->shelf()->GetSystemTrayAnchorRect());
  346. }
  347. void HoldingSpaceTrayBubble::OnDisplayConfigurationChanged() {
  348. UpdateBubbleBounds();
  349. }
  350. void HoldingSpaceTrayBubble::OnAutoHideStateChanged(ShelfAutoHideState state) {
  351. UpdateBubbleBounds();
  352. }
  353. void HoldingSpaceTrayBubble::OnTabletModeStarted() {
  354. UpdateBubbleBounds();
  355. }
  356. void HoldingSpaceTrayBubble::OnTabletModeEnded() {
  357. UpdateBubbleBounds();
  358. }
  359. } // namespace ash