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
5 #include "ash/system/holding_space/holding_space_tray_bubble.h"
6
7 #include <vector>
8
9 #include "ash/public/cpp/holding_space/holding_space_constants.h"
10 #include "ash/public/cpp/holding_space/holding_space_item.h"
11 #include "ash/public/cpp/holding_space/holding_space_metrics.h"
12 #include "ash/public/cpp/holding_space/holding_space_prefs.h"
13 #include "ash/public/cpp/shelf_config.h"
14 #include "ash/session/session_controller_impl.h"
15 #include "ash/shell.h"
16 #include "ash/system/holding_space/holding_space_item_view.h"
17 #include "ash/system/holding_space/holding_space_tray.h"
18 #include "ash/system/holding_space/pinned_files_container.h"
19 #include "ash/system/holding_space/recent_files_container.h"
20 #include "ash/system/tray/tray_bubble_wrapper.h"
21 #include "ash/system/tray/tray_constants.h"
22 #include "ash/system/tray/tray_utils.h"
23 #include "ash/wm/work_area_insets.h"
24 #include "ui/aura/window.h"
25 #include "ui/gfx/geometry/insets.h"
26 #include "ui/views/layout/box_layout.h"
27
28 namespace ash {
29
30 namespace {
31
32 // Helpers ---------------------------------------------------------------------
33
34 // Finds all visible `HoldingSpaceItem`s in `parent`'s view hierarchy.
FindVisibleHoldingSpaceItems(views::View * parent,std::vector<const HoldingSpaceItem * > * result)35 void FindVisibleHoldingSpaceItems(
36 views::View* parent,
37 std::vector<const HoldingSpaceItem*>* result) {
38 for (views::View* view : parent->children()) {
39 if (view->GetVisible() && HoldingSpaceItemView::IsInstance(view))
40 result->push_back(HoldingSpaceItemView::Cast(view)->item());
41 FindVisibleHoldingSpaceItems(view, result);
42 }
43 }
44
45 // Records the time from first availability to first entry into holding space.
RecordTimeFromFirstAvailabilityToFirstEntry(PrefService * prefs)46 void RecordTimeFromFirstAvailabilityToFirstEntry(PrefService* prefs) {
47 base::Time time_of_first_availability =
48 holding_space_prefs::GetTimeOfFirstAvailability(prefs).value();
49 base::Time time_of_first_entry =
50 holding_space_prefs::GetTimeOfFirstEntry(prefs).value();
51 holding_space_metrics::RecordTimeFromFirstAvailabilityToFirstEntry(
52 time_of_first_entry - time_of_first_availability);
53 }
54
55 // Sets up the layer for the specified `view`.
SetupViewLayer(views::View * view)56 void SetupViewLayer(views::View* view) {
57 view->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
58
59 auto* layer = view->layer();
60 layer->SetRoundedCornerRadius(gfx::RoundedCornersF{kUnifiedTrayCornerRadius});
61 layer->SetColor(AshColorProvider::Get()->GetBaseLayerColor(
62 AshColorProvider::BaseLayerType::kTransparent80));
63 layer->SetBackgroundBlur(kUnifiedMenuBackgroundBlur);
64 layer->SetFillsBoundsOpaquely(false);
65 layer->SetIsFastRoundedCorner(true);
66 }
67
68 // HoldingSpaceBubbleContainer -------------------------------------------------
69
70 class HoldingSpaceBubbleContainer : public views::View {
71 public:
HoldingSpaceBubbleContainer()72 HoldingSpaceBubbleContainer() {
73 layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
74 views::BoxLayout::Orientation::kVertical, gfx::Insets(),
75 kHoldingSpaceContainerSpacing));
76 }
77
SetFlexForChild(views::View * child,int flex)78 void SetFlexForChild(views::View* child, int flex) {
79 layout_->SetFlexForView(child, flex);
80 }
81
82 private:
83 // views::View:
ChildPreferredSizeChanged(views::View * child)84 void ChildPreferredSizeChanged(views::View* child) override {
85 if (GetWidget())
86 PreferredSizeChanged();
87 }
88
ChildVisibilityChanged(views::View * child)89 void ChildVisibilityChanged(views::View* child) override {
90 if (GetWidget())
91 PreferredSizeChanged();
92 }
93
94 views::BoxLayout* layout_ = nullptr;
95 };
96
97 } // namespace
98
99 // HoldingSpaceTrayBubble ------------------------------------------------------
100
HoldingSpaceTrayBubble(HoldingSpaceTray * holding_space_tray,bool show_by_click)101 HoldingSpaceTrayBubble::HoldingSpaceTrayBubble(
102 HoldingSpaceTray* holding_space_tray,
103 bool show_by_click)
104 : holding_space_tray_(holding_space_tray) {
105 TrayBubbleView::InitParams init_params;
106 init_params.delegate = holding_space_tray;
107 init_params.parent_window = holding_space_tray->GetBubbleWindowContainer();
108 init_params.anchor_view = nullptr;
109 init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
110 init_params.anchor_rect =
111 holding_space_tray->shelf()->GetSystemTrayAnchorRect();
112 init_params.insets = GetTrayBubbleInsets();
113 init_params.shelf_alignment = holding_space_tray->shelf()->alignment();
114 init_params.preferred_width = kHoldingSpaceBubbleWidth;
115 init_params.close_on_deactivate = true;
116 init_params.show_by_click = show_by_click;
117 init_params.has_shadow = false;
118
119 // Create and customize bubble view.
120 TrayBubbleView* bubble_view = new TrayBubbleView(init_params);
121 bubble_view->SetMaxHeight(CalculateMaxHeight());
122
123 HoldingSpaceBubbleContainer* bubble_container = bubble_view->AddChildView(
124 std::make_unique<HoldingSpaceBubbleContainer>());
125
126 // Add pinned files container.
127 pinned_files_container_ = bubble_container->AddChildView(
128 std::make_unique<PinnedFilesContainer>(&delegate_));
129 bubble_container->SetFlexForChild(pinned_files_container_, 1);
130 SetupViewLayer(pinned_files_container_);
131
132 // Add recent files container.
133 recent_files_container_ = bubble_container->AddChildView(
134 std::make_unique<RecentFilesContainer>(&delegate_));
135 SetupViewLayer(recent_files_container_);
136
137 // Populate both containers if holding space model has already been attached.
138 HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
139 if (model) {
140 pinned_files_container_->OnHoldingSpaceModelAttached(model);
141 recent_files_container_->OnHoldingSpaceModelAttached(model);
142 }
143
144 // Show the bubble.
145 bubble_wrapper_ = std::make_unique<TrayBubbleWrapper>(
146 holding_space_tray, bubble_view, false /* is_persistent */);
147
148 // Set bubble frame to be invisible.
149 bubble_wrapper_->GetBubbleWidget()
150 ->non_client_view()
151 ->frame_view()
152 ->SetVisible(false);
153
154 PrefService* const prefs =
155 Shell::Get()->session_controller()->GetLastActiveUserPrefService();
156
157 // Mark when holding space was first entered. If this is not the first entry
158 // into holding space, this will no-op. If this is the first entry, record the
159 // amount of time from first availability to first entry into holding space.
160 if (holding_space_prefs::MarkTimeOfFirstEntry(prefs))
161 RecordTimeFromFirstAvailabilityToFirstEntry(prefs);
162
163 // Record visible holding space items.
164 std::vector<const HoldingSpaceItem*> visible_items;
165 FindVisibleHoldingSpaceItems(bubble_view, &visible_items);
166 holding_space_metrics::RecordItemCounts(visible_items);
167
168 shelf_observer_.Add(holding_space_tray_->shelf());
169 tablet_mode_observer_.Add(Shell::Get()->tablet_mode_controller());
170 }
171
~HoldingSpaceTrayBubble()172 HoldingSpaceTrayBubble::~HoldingSpaceTrayBubble() {
173 bubble_wrapper_->bubble_view()->ResetDelegate();
174
175 // Explicitly reset holding space item view containers so that they will stop
176 // observing the holding space controller/model while they are asynchronously
177 // destroyed.
178 pinned_files_container_->Reset();
179 recent_files_container_->Reset();
180 }
181
AnchorUpdated()182 void HoldingSpaceTrayBubble::AnchorUpdated() {
183 bubble_wrapper_->bubble_view()->UpdateBubble();
184 }
185
GetBubbleView()186 TrayBubbleView* HoldingSpaceTrayBubble::GetBubbleView() {
187 return bubble_wrapper_->bubble_view();
188 }
189
GetBubbleWidget()190 views::Widget* HoldingSpaceTrayBubble::GetBubbleWidget() {
191 return bubble_wrapper_->GetBubbleWidget();
192 }
193
CalculateMaxHeight() const194 int HoldingSpaceTrayBubble::CalculateMaxHeight() const {
195 const WorkAreaInsets* work_area = WorkAreaInsets::ForWindow(
196 holding_space_tray_->shelf()->GetWindow()->GetRootWindow());
197
198 const int bottom =
199 holding_space_tray_->shelf()->IsHorizontalAlignment()
200 ? holding_space_tray_->shelf()->GetShelfBoundsInScreen().y()
201 : work_area->user_work_area_bounds().bottom();
202
203 const int free_space_height_above_anchor =
204 bottom - work_area->user_work_area_bounds().y();
205
206 const gfx::Insets insets = GetTrayBubbleInsets();
207 const int bubble_vertical_margin = insets.top() + insets.bottom();
208
209 return free_space_height_above_anchor - bubble_vertical_margin;
210 }
211
UpdateBubbleBounds()212 void HoldingSpaceTrayBubble::UpdateBubbleBounds() {
213 bubble_wrapper_->bubble_view()->SetMaxHeight(CalculateMaxHeight());
214 bubble_wrapper_->bubble_view()->ChangeAnchorRect(
215 holding_space_tray_->shelf()->GetSystemTrayAnchorRect());
216 }
217
OnDisplayConfigurationChanged()218 void HoldingSpaceTrayBubble::OnDisplayConfigurationChanged() {
219 UpdateBubbleBounds();
220 }
221
OnAutoHideStateChanged(ShelfAutoHideState state)222 void HoldingSpaceTrayBubble::OnAutoHideStateChanged(ShelfAutoHideState state) {
223 UpdateBubbleBounds();
224 }
225
OnTabletModeStarted()226 void HoldingSpaceTrayBubble::OnTabletModeStarted() {
227 UpdateBubbleBounds();
228 }
229
OnTabletModeEnded()230 void HoldingSpaceTrayBubble::OnTabletModeEnded() {
231 UpdateBubbleBounds();
232 }
233
234 } // namespace ash
235