1 // Copyright 2019 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/wm/desks/desks_bar_view.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <utility>
10
11 #include "ash/public/cpp/shell_window_ids.h"
12 #include "ash/public/cpp/window_properties.h"
13 #include "ash/shell.h"
14 #include "ash/style/ash_color_provider.h"
15 #include "ash/wm/desks/desk_mini_view.h"
16 #include "ash/wm/desks/desk_mini_view_animations.h"
17 #include "ash/wm/desks/desk_name_view.h"
18 #include "ash/wm/desks/desk_preview_view.h"
19 #include "ash/wm/desks/desks_util.h"
20 #include "ash/wm/desks/new_desk_button.h"
21 #include "ash/wm/overview/overview_controller.h"
22 #include "ash/wm/overview/overview_grid.h"
23 #include "ash/wm/overview/overview_highlight_controller.h"
24 #include "ash/wm/overview/overview_session.h"
25 #include "base/stl_util.h"
26 #include "third_party/skia/include/core/SkColor.h"
27 #include "ui/aura/window.h"
28 #include "ui/events/event_observer.h"
29 #include "ui/events/types/event_type.h"
30 #include "ui/gfx/geometry/insets.h"
31 #include "ui/views/event_monitor.h"
32 #include "ui/views/widget/widget.h"
33 #include "ui/wm/core/window_animations.h"
34
35 namespace ash {
36
37 namespace {
38
39 constexpr int kBarHeightInCompactLayout = 64;
40 constexpr int kUseCompactLayoutWidthThreshold = 600;
41
42 // In the non-compact layout, this is the height allocated for elements other
43 // than the desk preview (e.g. the DeskNameView, and the vertical paddings).
44 constexpr int kNonPreviewAllocatedHeight = 55;
45
46 // The local Y coordinate of the mini views in both non-compact and compact
47 // layouts respectively.
48 constexpr int kMiniViewsY = 16;
49 constexpr int kMiniViewsYCompact = 8;
50
51 // New desk button layout constants.
52 constexpr int kButtonRightMargin = 36;
53 constexpr int kIconAndTextHorizontalPadding = 16;
54 constexpr int kIconAndTextVerticalPadding = 8;
55
56 // Spacing between mini views.
57 constexpr int kMiniViewsSpacing = 12;
58
GetGestureEventScreenRect(const ui::Event & event)59 gfx::Rect GetGestureEventScreenRect(const ui::Event& event) {
60 DCHECK(event.IsGestureEvent());
61 return event.AsGestureEvent()->details().bounding_box();
62 }
63
GetHighlightController()64 OverviewHighlightController* GetHighlightController() {
65 auto* overview_controller = Shell::Get()->overview_controller();
66 DCHECK(overview_controller->InOverviewSession());
67 return overview_controller->overview_session()->highlight_controller();
68 }
69
70 } // namespace
71
72 // -----------------------------------------------------------------------------
73 // DeskBarHoverObserver:
74
75 class DeskBarHoverObserver : public ui::EventObserver {
76 public:
DeskBarHoverObserver(DesksBarView * owner,aura::Window * widget_window)77 DeskBarHoverObserver(DesksBarView* owner, aura::Window* widget_window)
78 : owner_(owner),
79 event_monitor_(views::EventMonitor::CreateWindowMonitor(
80 this,
81 widget_window,
82 {ui::ET_MOUSE_PRESSED, ui::ET_MOUSE_DRAGGED, ui::ET_MOUSE_RELEASED,
83 ui::ET_MOUSE_MOVED, ui::ET_MOUSE_ENTERED, ui::ET_MOUSE_EXITED,
84 ui::ET_GESTURE_LONG_PRESS, ui::ET_GESTURE_LONG_TAP,
85 ui::ET_GESTURE_TAP, ui::ET_GESTURE_TAP_DOWN})) {}
86
87 ~DeskBarHoverObserver() override = default;
88
89 // ui::EventObserver:
OnEvent(const ui::Event & event)90 void OnEvent(const ui::Event& event) override {
91 switch (event.type()) {
92 case ui::ET_MOUSE_PRESSED:
93 case ui::ET_MOUSE_DRAGGED:
94 case ui::ET_MOUSE_RELEASED:
95 case ui::ET_MOUSE_MOVED:
96 case ui::ET_MOUSE_ENTERED:
97 case ui::ET_MOUSE_EXITED:
98 owner_->OnHoverStateMayHaveChanged();
99 break;
100
101 case ui::ET_GESTURE_LONG_PRESS:
102 case ui::ET_GESTURE_LONG_TAP:
103 owner_->OnGestureTap(GetGestureEventScreenRect(event),
104 /*is_long_gesture=*/true);
105 break;
106
107 case ui::ET_GESTURE_TAP:
108 case ui::ET_GESTURE_TAP_DOWN:
109 owner_->OnGestureTap(GetGestureEventScreenRect(event),
110 /*is_long_gesture=*/false);
111 break;
112
113 default:
114 NOTREACHED();
115 break;
116 }
117 }
118
119 private:
120 DesksBarView* owner_;
121
122 std::unique_ptr<views::EventMonitor> event_monitor_;
123
124 DISALLOW_COPY_AND_ASSIGN(DeskBarHoverObserver);
125 };
126
127 // -----------------------------------------------------------------------------
128 // DesksBarView:
129
DesksBarView(OverviewGrid * overview_grid)130 DesksBarView::DesksBarView(OverviewGrid* overview_grid)
131 : background_view_(new views::View),
132 new_desk_button_(new NewDeskButton()),
133 overview_grid_(overview_grid) {
134 SetPaintToLayer();
135 layer()->SetFillsBoundsOpaquely(false);
136
137 background_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
138 background_view_->layer()->SetFillsBoundsOpaquely(false);
139
140 AddChildView(background_view_);
141 AddChildView(new_desk_button_);
142
143 DesksController::Get()->AddObserver(this);
144 }
145
~DesksBarView()146 DesksBarView::~DesksBarView() {
147 DesksController::Get()->RemoveObserver(this);
148 }
149
150 // static
GetBarHeightForWidth(aura::Window * root,const DesksBarView * desks_bar_view,int width)151 int DesksBarView::GetBarHeightForWidth(aura::Window* root,
152 const DesksBarView* desks_bar_view,
153 int width) {
154 if (width <= kUseCompactLayoutWidthThreshold ||
155 (desks_bar_view && width <= desks_bar_view->min_width_to_fit_contents_)) {
156 return kBarHeightInCompactLayout;
157 }
158
159 return DeskPreviewView::GetHeight(root, /*compact=*/false) +
160 kNonPreviewAllocatedHeight;
161 }
162
163 // static
CreateDesksWidget(aura::Window * root,const gfx::Rect & bounds)164 std::unique_ptr<views::Widget> DesksBarView::CreateDesksWidget(
165 aura::Window* root,
166 const gfx::Rect& bounds) {
167 DCHECK(root);
168 DCHECK(root->IsRootWindow());
169
170 auto widget = std::make_unique<views::Widget>();
171 views::Widget::InitParams params(
172 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
173 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
174 params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
175 params.accept_events = true;
176 params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
177 // This widget will be parented to the currently-active desk container on
178 // |root|.
179 params.context = root;
180 params.bounds = bounds;
181 params.name = "VirtualDesksWidget";
182
183 // Even though this widget exists on the active desk container, it should not
184 // show up in the MRU list, and it should not be mirrored in the desks
185 // mini_views.
186 params.init_properties_container.SetProperty(kExcludeInMruKey, true);
187 params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);
188 widget->Init(std::move(params));
189
190 auto* window = widget->GetNativeWindow();
191 window->set_id(kShellWindowId_DesksBarWindow);
192 ::wm::SetWindowVisibilityAnimationTransition(window, ::wm::ANIMATE_NONE);
193
194 return widget;
195 }
196
Init()197 void DesksBarView::Init() {
198 UpdateNewMiniViews(/*animate=*/false);
199 hover_observer_ = std::make_unique<DeskBarHoverObserver>(
200 this, GetWidget()->GetNativeWindow());
201 }
202
IsDeskNameBeingModified() const203 bool DesksBarView::IsDeskNameBeingModified() const {
204 if (!GetWidget()->IsActive())
205 return false;
206
207 for (auto* mini_view : mini_views_) {
208 if (mini_view->IsDeskNameBeingModified())
209 return true;
210 }
211 return false;
212 }
213
GetOnHoverWindowSizeScaleFactor() const214 float DesksBarView::GetOnHoverWindowSizeScaleFactor() const {
215 return float{height()} / overview_grid_->root_window()->bounds().height();
216 }
217
OnHoverStateMayHaveChanged()218 void DesksBarView::OnHoverStateMayHaveChanged() {
219 for (auto* mini_view : mini_views_)
220 mini_view->UpdateCloseButtonVisibility();
221 }
222
OnGestureTap(const gfx::Rect & screen_rect,bool is_long_gesture)223 void DesksBarView::OnGestureTap(const gfx::Rect& screen_rect,
224 bool is_long_gesture) {
225 for (auto* mini_view : mini_views_)
226 mini_view->OnWidgetGestureTap(screen_rect, is_long_gesture);
227 }
228
SetDragDetails(const gfx::Point & screen_location,bool dragged_item_over_bar)229 void DesksBarView::SetDragDetails(const gfx::Point& screen_location,
230 bool dragged_item_over_bar) {
231 last_dragged_item_screen_location_ = screen_location;
232 const bool old_dragged_item_over_bar = dragged_item_over_bar_;
233 dragged_item_over_bar_ = dragged_item_over_bar;
234
235 if (!old_dragged_item_over_bar && !dragged_item_over_bar)
236 return;
237
238 for (auto* mini_view : mini_views_)
239 mini_view->UpdateBorderColor();
240 }
241
GetClassName() const242 const char* DesksBarView::GetClassName() const {
243 return "DesksBarView";
244 }
245
Layout()246 void DesksBarView::Layout() {
247 background_view_->SetBoundsRect(bounds());
248
249 const bool compact = UsesCompactLayout();
250 new_desk_button_->SetLabelVisible(!compact);
251 gfx::Size new_desk_button_size = new_desk_button_->GetPreferredSize();
252 if (compact) {
253 new_desk_button_size.Enlarge(2 * kIconAndTextVerticalPadding,
254 2 * kIconAndTextVerticalPadding);
255 } else {
256 new_desk_button_size.Enlarge(2 * kIconAndTextHorizontalPadding,
257 2 * kIconAndTextVerticalPadding);
258 }
259
260 const gfx::Rect button_bounds{
261 bounds().right() - new_desk_button_size.width() - kButtonRightMargin,
262 (bounds().height() - new_desk_button_size.height()) / 2,
263 new_desk_button_size.width(), new_desk_button_size.height()};
264 new_desk_button_->SetBoundsRect(button_bounds);
265
266 if (mini_views_.empty())
267 return;
268
269 const gfx::Size mini_view_size = mini_views_[0]->GetPreferredSize();
270 const int total_width =
271 mini_views_.size() * (mini_view_size.width() + kMiniViewsSpacing) -
272 kMiniViewsSpacing;
273
274 int x = (width() - total_width) / 2;
275 const int y = compact ? kMiniViewsYCompact : kMiniViewsY;
276 for (auto* mini_view : mini_views_) {
277 mini_view->SetBoundsRect(gfx::Rect(gfx::Point(x, y), mini_view_size));
278 x += (mini_view_size.width() + kMiniViewsSpacing);
279 }
280 }
281
OnMousePressed(const ui::MouseEvent & event)282 bool DesksBarView::OnMousePressed(const ui::MouseEvent& event) {
283 DeskNameView::CommitChanges(GetWidget());
284 return false;
285 }
286
OnGestureEvent(ui::GestureEvent * event)287 void DesksBarView::OnGestureEvent(ui::GestureEvent* event) {
288 switch (event->type()) {
289 case ui::ET_GESTURE_LONG_PRESS:
290 case ui::ET_GESTURE_LONG_TAP:
291 case ui::ET_GESTURE_TAP:
292 case ui::ET_GESTURE_TAP_DOWN:
293 DeskNameView::CommitChanges(GetWidget());
294 break;
295
296 default:
297 break;
298 }
299 }
300
OnThemeChanged()301 void DesksBarView::OnThemeChanged() {
302 views::View::OnThemeChanged();
303 DCHECK_EQ(ui::LAYER_SOLID_COLOR, background_view_->layer()->type());
304 background_view_->layer()->SetColor(
305 AshColorProvider::Get()->GetShieldLayerColor(
306 AshColorProvider::ShieldLayerType::kShield80));
307 }
308
UsesCompactLayout() const309 bool DesksBarView::UsesCompactLayout() const {
310 return width() <= kUseCompactLayoutWidthThreshold ||
311 width() <= min_width_to_fit_contents_;
312 }
313
OnDeskAdded(const Desk * desk)314 void DesksBarView::OnDeskAdded(const Desk* desk) {
315 DeskNameView::CommitChanges(GetWidget());
316 UpdateNewMiniViews(/*animate=*/true);
317 }
318
OnDeskRemoved(const Desk * desk)319 void DesksBarView::OnDeskRemoved(const Desk* desk) {
320 DeskNameView::CommitChanges(GetWidget());
321 auto iter = std::find_if(
322 mini_views_.begin(), mini_views_.end(),
323 [desk](DeskMiniView* mini_view) { return desk == mini_view->desk(); });
324
325 DCHECK(iter != mini_views_.end());
326
327 // Let the highlight controller know the view is destroying before it is
328 // removed from the collection because it needs to know the index of the mini
329 // view, or the desk name view (if either is currently highlighted) relative
330 // to other traversable views.
331 auto* highlight_controller = GetHighlightController();
332 // The order here matters, we call it first on the desk_name_view since it
333 // comes later in the highlight order (See documentation of
334 // OnViewDestroyingOrDisabling()).
335 highlight_controller->OnViewDestroyingOrDisabling((*iter)->desk_name_view());
336 highlight_controller->OnViewDestroyingOrDisabling(*iter);
337
338 const int begin_x = GetFirstMiniViewXOffset();
339 // Remove the mini view from the list now. And remove it from its parent
340 // after the animation is done.
341 DeskMiniView* removed_mini_view = *iter;
342 auto partition_iter = mini_views_.erase(iter);
343
344 UpdateMinimumWidthToFitContents();
345 overview_grid_->OnDesksChanged();
346 new_desk_button_->UpdateButtonState();
347
348 for (auto* mini_view : mini_views_)
349 mini_view->UpdateCloseButtonVisibility();
350
351 PerformRemoveDeskMiniViewAnimation(
352 removed_mini_view,
353 std::vector<DeskMiniView*>(mini_views_.begin(), partition_iter),
354 std::vector<DeskMiniView*>(partition_iter, mini_views_.end()),
355 begin_x - GetFirstMiniViewXOffset());
356 }
357
OnDeskActivationChanged(const Desk * activated,const Desk * deactivated)358 void DesksBarView::OnDeskActivationChanged(const Desk* activated,
359 const Desk* deactivated) {
360 for (auto* mini_view : mini_views_) {
361 const Desk* desk = mini_view->desk();
362 if (desk == activated || desk == deactivated)
363 mini_view->UpdateBorderColor();
364 }
365 }
366
OnDeskSwitchAnimationLaunching()367 void DesksBarView::OnDeskSwitchAnimationLaunching() {}
368
OnDeskSwitchAnimationFinished()369 void DesksBarView::OnDeskSwitchAnimationFinished() {}
370
UpdateNewMiniViews(bool animate)371 void DesksBarView::UpdateNewMiniViews(bool animate) {
372 const auto& desks = DesksController::Get()->desks();
373 if (desks.size() < 2) {
374 // We do not show mini_views when we have a single desk.
375 DCHECK(mini_views_.empty());
376
377 // The bar background is initially translated off the screen.
378 gfx::Transform translate;
379 translate.Translate(0, -height());
380 background_view_->layer()->SetTransform(translate);
381 background_view_->layer()->SetOpacity(0);
382
383 return;
384 }
385
386 // This should not be called when a desk is removed.
387 DCHECK_LE(mini_views_.size(), desks.size());
388
389 const bool first_time_mini_views = mini_views_.empty();
390 const int begin_x = GetFirstMiniViewXOffset();
391 std::vector<DeskMiniView*> new_mini_views;
392
393 aura::Window* root_window = GetWidget()->GetNativeWindow()->GetRootWindow();
394 DCHECK(root_window);
395 for (const auto& desk : desks) {
396 if (!FindMiniViewForDesk(desk.get())) {
397 DeskMiniView* mini_view = AddChildView(
398 std::make_unique<DeskMiniView>(this, root_window, desk.get()));
399 mini_views_.push_back(mini_view);
400 new_mini_views.push_back(mini_view);
401 }
402 }
403
404 UpdateMinimumWidthToFitContents();
405 overview_grid_->OnDesksChanged();
406
407 if (!animate)
408 return;
409
410 PerformNewDeskMiniViewAnimation(this, new_mini_views,
411 begin_x - GetFirstMiniViewXOffset(),
412 first_time_mini_views);
413 }
414
FindMiniViewForDesk(const Desk * desk) const415 DeskMiniView* DesksBarView::FindMiniViewForDesk(const Desk* desk) const {
416 for (auto* mini_view : mini_views_) {
417 if (mini_view->desk() == desk)
418 return mini_view;
419 }
420
421 return nullptr;
422 }
423
GetFirstMiniViewXOffset() const424 int DesksBarView::GetFirstMiniViewXOffset() const {
425 return mini_views_.empty() ? bounds().CenterPoint().x()
426 : mini_views_[0]->bounds().x();
427 }
428
UpdateMinimumWidthToFitContents()429 void DesksBarView::UpdateMinimumWidthToFitContents() {
430 int button_width = new_desk_button_->GetMinSize(/*compact=*/false).width();
431 button_width += 2 * kIconAndTextHorizontalPadding;
432 button_width += kButtonRightMargin;
433
434 if (mini_views_.empty()) {
435 min_width_to_fit_contents_ = button_width;
436 return;
437 }
438
439 const int mini_view_width = mini_views_[0]->GetMinWidthForDefaultLayout();
440 const int total_mini_views_width =
441 mini_views_.size() * (mini_view_width + kMiniViewsSpacing) -
442 kMiniViewsSpacing;
443
444 min_width_to_fit_contents_ = total_mini_views_width + button_width * 2;
445 }
446
447 } // namespace ash
448