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