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/shelf/hotseat_widget.h"
6
7 #include <utility>
8
9 #include "ash/focus_cycler.h"
10 #include "ash/keyboard/ui/keyboard_ui_controller.h"
11 #include "ash/public/cpp/ash_features.h"
12 #include "ash/public/cpp/shelf_config.h"
13 #include "ash/public/cpp/shelf_model.h"
14 #include "ash/public/cpp/shelf_types.h"
15 #include "ash/public/cpp/wallpaper_controller_observer.h"
16 #include "ash/shelf/hotseat_transition_animator.h"
17 #include "ash/shelf/scrollable_shelf_view.h"
18 #include "ash/shelf/shelf_app_button.h"
19 #include "ash/shelf/shelf_layout_manager.h"
20 #include "ash/shelf/shelf_navigation_widget.h"
21 #include "ash/shelf/shelf_view.h"
22 #include "ash/shell.h"
23 #include "ash/system/status_area_widget.h"
24 #include "ash/wallpaper/wallpaper_controller_impl.h"
25 #include "ash/wm/overview/overview_controller.h"
26 #include "ash/wm/overview/overview_observer.h"
27 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
28 #include "base/metrics/histogram_macros.h"
29 #include "ui/aura/scoped_window_targeter.h"
30 #include "ui/aura/window_targeter.h"
31 #include "ui/compositor/animation_throughput_reporter.h"
32 #include "ui/compositor/layer_animation_sequence.h"
33 #include "ui/compositor/scoped_layer_animation_settings.h"
34 #include "ui/gfx/color_analysis.h"
35 #include "ui/gfx/color_palette.h"
36 #include "ui/gfx/color_utils.h"
37 #include "ui/gfx/geometry/rounded_corners_f.h"
38 #include "ui/views/layout/fill_layout.h"
39 #include "ui/views/view_targeter_delegate.h"
40 #include "ui/views/widget/widget_delegate.h"
41
42 namespace ash {
43 namespace {
44
DoScopedAnimationSetting(ui::ScopedLayerAnimationSettings * animation_setter)45 void DoScopedAnimationSetting(
46 ui::ScopedLayerAnimationSettings* animation_setter) {
47 animation_setter->SetTransitionDuration(
48 ShelfConfig::Get()->shelf_animation_duration());
49 animation_setter->SetTweenType(gfx::Tween::EASE_OUT);
50 animation_setter->SetPreemptionStrategy(
51 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
52 }
53
54 // Returns whether there is special hotseat animation for |transition|.
HasSpecialAnimation(HotseatWidget::StateTransition transition)55 bool HasSpecialAnimation(HotseatWidget::StateTransition transition) {
56 switch (transition) {
57 case HotseatWidget::StateTransition::kHomeLauncherAndExtended:
58 case HotseatWidget::StateTransition::kHomeLauncherAndHidden:
59 return true;
60 case HotseatWidget::StateTransition::kHiddenAndExtended:
61 case HotseatWidget::StateTransition::kOther:
62 return false;
63 }
64 }
65
66 // Calculates the state transition type for the given previous state and
67 // the target state.
CalculateHotseatStateTransition(HotseatState previous_state,HotseatState target_state)68 HotseatWidget::StateTransition CalculateHotseatStateTransition(
69 HotseatState previous_state,
70 HotseatState target_state) {
71 if (previous_state == HotseatState::kNone ||
72 target_state == HotseatState::kNone) {
73 return HotseatWidget::StateTransition::kOther;
74 }
75
76 if (previous_state == target_state)
77 return HotseatWidget::StateTransition::kOther;
78
79 const bool related_to_homelauncher =
80 (previous_state == HotseatState::kShownHomeLauncher ||
81 target_state == HotseatState::kShownHomeLauncher);
82 const bool related_to_extended = (previous_state == HotseatState::kExtended ||
83 target_state == HotseatState::kExtended);
84 const bool related_to_hidden = (previous_state == HotseatState::kHidden ||
85 target_state == HotseatState::kHidden);
86
87 if (related_to_homelauncher && related_to_extended)
88 return HotseatWidget::StateTransition::kHomeLauncherAndExtended;
89
90 if (related_to_homelauncher && related_to_hidden)
91 return HotseatWidget::StateTransition::kHomeLauncherAndHidden;
92
93 if (related_to_extended && related_to_hidden)
94 return HotseatWidget::StateTransition::kHiddenAndExtended;
95
96 return HotseatWidget::StateTransition::kOther;
97 }
98
99 // Base class for hotseat animation transition.
100 class HotseatStateTransitionAnimation : public ui::LayerAnimationElement {
101 public:
HotseatStateTransitionAnimation(const gfx::Rect & target_bounds_in_screen,double target_opacity,ui::Layer * hotseat_layer,HotseatWidget * hotseat_widget)102 HotseatStateTransitionAnimation(const gfx::Rect& target_bounds_in_screen,
103 double target_opacity,
104 ui::Layer* hotseat_layer,
105 HotseatWidget* hotseat_widget)
106 : ui::LayerAnimationElement(
107 LayerAnimationElement::BOUNDS | LayerAnimationElement::OPACITY,
108 hotseat_layer->GetAnimator()->GetTransitionDuration()),
109 target_widget_bounds_(target_bounds_in_screen),
110 target_opacity_(target_opacity),
111 tween_type_(hotseat_layer->GetAnimator()->tween_type()),
112 hotseat_widget_(hotseat_widget) {}
113
114 ~HotseatStateTransitionAnimation() override = default;
115
116 HotseatStateTransitionAnimation(const HotseatStateTransitionAnimation& rhs) =
117 delete;
118 HotseatStateTransitionAnimation& operator=(
119 const HotseatStateTransitionAnimation& rhs) = delete;
120
121 protected:
122 // ui::LayerAnimationElement:
OnGetTarget(TargetValue * target) const123 void OnGetTarget(TargetValue* target) const override {
124 target->opacity = target_opacity_;
125 target->bounds = target_widget_bounds_;
126 }
127
GetScrollableShelfView()128 ScrollableShelfView* GetScrollableShelfView() {
129 return hotseat_widget_->scrollable_shelf_view();
130 }
131
132 // Hotseat widget's target bounds in screen.
133 gfx::Rect target_widget_bounds_;
134
135 // Hotseat widget's initial opacity.
136 double start_opacity_ = 0.f;
137
138 // Hotseat widget's target opacity.
139 double target_opacity_ = 0.f;
140
141 gfx::Tween::Type tween_type_ = gfx::Tween::LINEAR;
142
143 HotseatWidget* hotseat_widget_ = nullptr;
144 };
145
146 // Animation implemented specifically for the transition between the home
147 // launcher state and the extended state.
148 class HomeAndExtendedTransitionAnimation
149 : public HotseatStateTransitionAnimation {
150 public:
HomeAndExtendedTransitionAnimation(const gfx::Rect & target_bounds_in_screen,double target_opacity,ui::Layer * hotseat_layer,HotseatWidget * hotseat_widget)151 HomeAndExtendedTransitionAnimation(const gfx::Rect& target_bounds_in_screen,
152 double target_opacity,
153 ui::Layer* hotseat_layer,
154 HotseatWidget* hotseat_widget)
155 : HotseatStateTransitionAnimation(target_bounds_in_screen,
156 target_opacity,
157 hotseat_layer,
158 hotseat_widget) {}
159 ~HomeAndExtendedTransitionAnimation() override = default;
160
161 HomeAndExtendedTransitionAnimation(
162 const HomeAndExtendedTransitionAnimation& rhs) = delete;
163 HomeAndExtendedTransitionAnimation& operator=(
164 const HomeAndExtendedTransitionAnimation& rhs) = delete;
165
166 private:
167 // HotseatStateTransitionAnimation:
OnStart(ui::LayerAnimationDelegate * delegate)168 void OnStart(ui::LayerAnimationDelegate* delegate) override {
169 DCHECK(hotseat_widget_->GetShelfView()->shelf()->IsHorizontalAlignment());
170
171 ScrollableShelfView* scrollable_shelf_view = GetScrollableShelfView();
172 scrollable_shelf_view->set_is_padding_configured_externally(
173 /*is_padding_configured_externally=*/true);
174
175 // Save initial and target padding insets.
176 initial_padding_insets_ = scrollable_shelf_view->edge_padding_insets();
177 target_padding_insets_ =
178 scrollable_shelf_view->CalculateEdgePadding(/*use_target_bounds=*/true);
179
180 // Save initial opacity.
181 start_opacity_ = hotseat_widget_->GetNativeView()->layer()->opacity();
182
183 // Save initial hotseat background bounds.
184 initial_hotseat_background_in_screen_ =
185 hotseat_widget_->GetWindowBoundsInScreen();
186 initial_hotseat_background_in_screen_.Inset(initial_padding_insets_);
187
188 // Save target hotseat background bounds.
189 target_hotseat_background_in_screen_ = target_widget_bounds_;
190 target_hotseat_background_in_screen_.Inset(target_padding_insets_);
191 }
192
193 // HotseatStateTransitionAnimation:
OnProgress(double current,ui::LayerAnimationDelegate * delegate)194 bool OnProgress(double current,
195 ui::LayerAnimationDelegate* delegate) override {
196 const double tweened = gfx::Tween::CalculateValue(tween_type_, current);
197
198 // Set scrollable shelf view's padding insets.
199 gfx::Insets insets_in_animation_progress;
200 insets_in_animation_progress.set_left(gfx::Tween::LinearIntValueBetween(
201 tweened, initial_padding_insets_.left(),
202 target_padding_insets_.left()));
203 insets_in_animation_progress.set_right(gfx::Tween::LinearIntValueBetween(
204 tweened, initial_padding_insets_.right(),
205 target_padding_insets_.right()));
206 ScrollableShelfView* scrollable_shelf_view = GetScrollableShelfView();
207 scrollable_shelf_view->SetEdgePaddingInsets(insets_in_animation_progress);
208
209 // Update hotseat widget opacity.
210 delegate->SetOpacityFromAnimation(
211 gfx::Tween::DoubleValueBetween(tweened, start_opacity_,
212 target_opacity_),
213 ui::PropertyChangeReason::FROM_ANIMATION);
214
215 // Calculate the hotseat widget's bounds.
216 const gfx::Rect hotseat_background_in_progress =
217 gfx::Tween::RectValueBetween(tweened,
218 initial_hotseat_background_in_screen_,
219 target_hotseat_background_in_screen_);
220 gfx::Rect widget_bounds_in_progress = hotseat_background_in_progress;
221 widget_bounds_in_progress.Inset(
222 -scrollable_shelf_view->edge_padding_insets());
223
224 // Update hotseat widget bounds.
225 delegate->SetBoundsFromAnimation(widget_bounds_in_progress,
226 ui::PropertyChangeReason::FROM_ANIMATION);
227
228 // Do recovering when the animation ends.
229 if (current == 1.f) {
230 scrollable_shelf_view->set_is_padding_configured_externally(
231 /*is_padding_configured_externally=*/false);
232 }
233
234 return true;
235 }
236
237 // HotseatStateTransitionAnimation:
OnAbort(ui::LayerAnimationDelegate * delegate)238 void OnAbort(ui::LayerAnimationDelegate* delegate) override {
239 GetScrollableShelfView()->set_is_padding_configured_externally(
240 /*is_padding_configured_externally=*/false);
241 }
242
243 // Scrollable shelf's initial padding insets.
244 gfx::Insets initial_padding_insets_;
245
246 // Scrollable shelf's target padding insets.
247 gfx::Insets target_padding_insets_;
248
249 // Hotseat background's initial bounds in screen.
250 gfx::Rect initial_hotseat_background_in_screen_;
251
252 // Hotseat background's target bounds in screen.
253 gfx::Rect target_hotseat_background_in_screen_;
254 };
255
256 // Animation implemented specifically for the transition between the home
257 // launcher state and the hidden state.
258 class HomeAndHiddenTransitionAnimation
259 : public HotseatStateTransitionAnimation {
260 public:
HomeAndHiddenTransitionAnimation(const gfx::Rect & target_bounds_in_screen,double target_opacity,ui::Layer * hotseat_layer,HotseatWidget * hotseat_widget)261 HomeAndHiddenTransitionAnimation(const gfx::Rect& target_bounds_in_screen,
262 double target_opacity,
263 ui::Layer* hotseat_layer,
264 HotseatWidget* hotseat_widget)
265 : HotseatStateTransitionAnimation(target_bounds_in_screen,
266 target_opacity,
267 hotseat_layer,
268 hotseat_widget) {}
269 ~HomeAndHiddenTransitionAnimation() override = default;
270
271 protected:
272 // HotseatStateTransitionAnimation:
OnStart(ui::LayerAnimationDelegate * delegate)273 void OnStart(ui::LayerAnimationDelegate* delegate) override {
274 DCHECK(hotseat_widget_->GetShelfView()->shelf()->IsHorizontalAlignment());
275
276 start_opacity_ = hotseat_widget_->GetNativeView()->layer()->opacity();
277
278 if (hotseat_widget_->state() == HotseatState::kHidden)
279 will_be_hidden_ = true;
280
281 ScrollableShelfView* scrollable_shelf_view = GetScrollableShelfView();
282 const gfx::Rect current_widget_bounds =
283 hotseat_widget_->GetWindowBoundsInScreen();
284
285 // Ensure that hotseat only has vertical movement during animation.
286 if (will_be_hidden_) {
287 animation_initial_bounds_ = current_widget_bounds;
288
289 animation_target_bounds_ = current_widget_bounds;
290 animation_target_bounds_.set_y(target_widget_bounds_.y());
291 } else {
292 animation_initial_bounds_ = target_widget_bounds_;
293 animation_initial_bounds_.set_y(current_widget_bounds.y());
294
295 // Ensure that hotseat is set with the target bounds at the end of
296 // animation when hotseat is going to show in home launcher.
297 animation_target_bounds_ = target_widget_bounds_;
298 const gfx::Insets target_padding_insets =
299 scrollable_shelf_view->CalculateEdgePadding(
300 /*use_target_bounds=*/true);
301 scrollable_shelf_view->SetEdgePaddingInsets(target_padding_insets);
302 delegate->SetBoundsFromAnimation(
303 animation_initial_bounds_, ui::PropertyChangeReason::FROM_ANIMATION);
304 }
305 }
306
307 // HotseatStateTransitionAnimation:
OnProgress(double current,ui::LayerAnimationDelegate * delegate)308 bool OnProgress(double current,
309 ui::LayerAnimationDelegate* delegate) override {
310 const double tweened = gfx::Tween::CalculateValue(tween_type_, current);
311 delegate->SetOpacityFromAnimation(
312 gfx::Tween::DoubleValueBetween(tweened, start_opacity_,
313 target_opacity_),
314 ui::PropertyChangeReason::FROM_ANIMATION);
315
316 const gfx::Rect widget_bounds_in_progress = gfx::Tween::RectValueBetween(
317 tweened, animation_initial_bounds_, animation_target_bounds_);
318
319 const bool reach_end = current == 1.f;
320
321 // When hotseat is going to be hidden, |animation_target_bounds_| is not
322 // equal to |target_widget_bounds_|. So hotseat is set with the target
323 // bounds at the end of animation. It does not bring animation regression
324 // since hotseat is invisible to the user when setting bounds.
325 delegate->SetBoundsFromAnimation(will_be_hidden_ && reach_end
326 ? target_widget_bounds_
327 : widget_bounds_in_progress,
328 ui::PropertyChangeReason::FROM_ANIMATION);
329
330 return true;
331 }
332
333 // HotseatStateTransitionAnimation:
OnAbort(ui::LayerAnimationDelegate * delegate)334 void OnAbort(ui::LayerAnimationDelegate* delegate) override {}
335
336 private:
337 // Whether hotseat widget is hidden after state transition animation.
338 bool will_be_hidden_ = false;
339
340 // Note that |animation_initial_bounds_| and |animation_target_bounds_| may
341 // not be the hotseat's current bounds and |target_widget_bounds_|
342 // respectively.
343 gfx::Rect animation_initial_bounds_;
344 gfx::Rect animation_target_bounds_;
345 };
346
347 // Custom window targeter for the hotseat. Used so the hotseat only processes
348 // events that land on the visible portion of the hotseat, and only while the
349 // hotseat is not animating.
350 class HotseatWindowTargeter : public aura::WindowTargeter {
351 public:
HotseatWindowTargeter(HotseatWidget * hotseat_widget)352 explicit HotseatWindowTargeter(HotseatWidget* hotseat_widget)
353 : hotseat_widget_(hotseat_widget) {}
354 ~HotseatWindowTargeter() override = default;
355
356 HotseatWindowTargeter(const HotseatWindowTargeter& other) = delete;
357 HotseatWindowTargeter& operator=(const HotseatWindowTargeter& rhs) = delete;
358
359 // aura::WindowTargeter:
SubtreeShouldBeExploredForEvent(aura::Window * window,const ui::LocatedEvent & event)360 bool SubtreeShouldBeExploredForEvent(aura::Window* window,
361 const ui::LocatedEvent& event) override {
362 // Do not handle events if the hotseat window is animating as it may animate
363 // over other items which want to process events.
364 if (hotseat_widget_->GetLayer()->GetAnimator()->is_animating())
365 return false;
366 return aura::WindowTargeter::SubtreeShouldBeExploredForEvent(window, event);
367 }
368
GetHitTestRects(aura::Window * target,gfx::Rect * hit_test_rect_mouse,gfx::Rect * hit_test_rect_touch) const369 bool GetHitTestRects(aura::Window* target,
370 gfx::Rect* hit_test_rect_mouse,
371 gfx::Rect* hit_test_rect_touch) const override {
372 if (target == hotseat_widget_->GetNativeWindow()) {
373 // Shrink the hit bounds from the size of the window to the size of the
374 // hotseat translucent background.
375 gfx::Rect hit_bounds = target->bounds();
376 hit_bounds.ClampToCenteredSize(
377 hotseat_widget_->GetTranslucentBackgroundSize());
378 *hit_test_rect_mouse = *hit_test_rect_touch = hit_bounds;
379 return true;
380 }
381 return aura::WindowTargeter::GetHitTestRects(target, hit_test_rect_mouse,
382 hit_test_rect_touch);
383 }
384
385 private:
386 // Unowned and guaranteed to be not null for the duration of |this|.
387 HotseatWidget* const hotseat_widget_;
388 };
389
390 } // namespace
391
392 class HotseatWidget::DelegateView : public HotseatTransitionAnimator::Observer,
393 public views::WidgetDelegateView,
394 public views::ViewTargeterDelegate,
395 public OverviewObserver,
396 public WallpaperControllerObserver {
397 public:
DelegateView()398 DelegateView() : translucent_background_(ui::LAYER_SOLID_COLOR) {
399 translucent_background_.SetName("hotseat/Background");
400 SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
401 }
402 ~DelegateView() override;
403
404 // views::ViewTargetDelegate:
TargetForRect(View * root,const gfx::Rect & rect)405 View* TargetForRect(View* root, const gfx::Rect& rect) override {
406 // If a context menu for a shelf app button is shown, redirect all events to
407 // the shelf app button. Context menus generally capture all events, but
408 // shelf app buttons' context menu redirect gesture events to the hotseat
409 // widget so shelf app button can continue handling drag events.
410 // See also HotseatWidget::OnGestureEvent().
411 views::View* item_with_context_menu =
412 scrollable_shelf_view_->shelf_view()->GetShelfItemViewWithContextMenu();
413 if (item_with_context_menu)
414 return item_with_context_menu;
415 return views::ViewTargeterDelegate::TargetForRect(root, rect);
416 }
417
418 // Initializes the view.
419 void Init(ScrollableShelfView* scrollable_shelf_view,
420 ui::Layer* parent_layer,
421 HotseatWidget* hotseat_widget);
422
423 // Updates the hotseat background.
424 void UpdateTranslucentBackground();
425
426 void SetTranslucentBackground(const gfx::Rect& translucent_background_bounds);
427
428 // Sets whether the background should be blurred as requested by the argument,
429 // unless the feature flag is disabled or |disable_blur_for_animations_| is
430 // true, in which case this disables background blur.
431 void SetBackgroundBlur(bool enable_blur);
432
433 // HotseatTransitionAnimator::Observer:
434 void OnHotseatTransitionAnimationWillStart(HotseatState from_state,
435 HotseatState to_state) override;
436 void OnHotseatTransitionAnimationEnded(HotseatState from_state,
437 HotseatState to_state) override;
438 void OnHotseatTransitionAnimationAborted() override;
439
440 // views::WidgetDelegateView:
441 bool CanActivate() const override;
442 void ReorderChildLayers(ui::Layer* parent_layer) override;
443
444 // OverviewObserver:
445 void OnOverviewModeWillStart() override;
446 void OnOverviewModeEndingAnimationComplete(bool canceled) override;
447
448 // WallpaperControllerObserver:
449 void OnWallpaperColorsChanged() override;
450
set_focus_cycler(FocusCycler * focus_cycler)451 void set_focus_cycler(FocusCycler* focus_cycler) {
452 focus_cycler_ = focus_cycler;
453 }
454
background_blur() const455 int background_blur() const {
456 return translucent_background_.background_blur();
457 }
458
is_translucent_background_visible_for_test()459 bool is_translucent_background_visible_for_test() {
460 return translucent_background_.GetTargetVisibility();
461 }
462
463 private:
464 void SetParentLayer(ui::Layer* layer);
465
466 FocusCycler* focus_cycler_ = nullptr;
467 // A background layer that may be visible depending on HotseatState.
468 ui::Layer translucent_background_;
469 ScrollableShelfView* scrollable_shelf_view_ = nullptr; // unowned.
470 HotseatWidget* hotseat_widget_ = nullptr; // unowned.
471 // Blur is disabled during animations to improve performance.
472 int blur_lock_ = 0;
473
474 // The most recent color that the |translucent_background_| has been animated
475 // to.
476 SkColor target_color_ = SK_ColorTRANSPARENT;
477
478 DISALLOW_COPY_AND_ASSIGN(DelegateView);
479 };
480
~DelegateView()481 HotseatWidget::DelegateView::~DelegateView() {
482 WallpaperControllerImpl* wallpaper_controller =
483 Shell::Get()->wallpaper_controller();
484 OverviewController* overview_controller = Shell::Get()->overview_controller();
485 if (wallpaper_controller)
486 wallpaper_controller->RemoveObserver(this);
487 if (overview_controller)
488 overview_controller->RemoveObserver(this);
489 }
490
Init(ScrollableShelfView * scrollable_shelf_view,ui::Layer * parent_layer,HotseatWidget * hotseat_widget)491 void HotseatWidget::DelegateView::Init(
492 ScrollableShelfView* scrollable_shelf_view,
493 ui::Layer* parent_layer,
494 HotseatWidget* hotseat_widget) {
495 hotseat_widget_ = hotseat_widget;
496 SetLayoutManager(std::make_unique<views::FillLayout>());
497
498 WallpaperControllerImpl* wallpaper_controller =
499 Shell::Get()->wallpaper_controller();
500 OverviewController* overview_controller = Shell::Get()->overview_controller();
501 if (wallpaper_controller)
502 wallpaper_controller->AddObserver(this);
503 if (overview_controller) {
504 overview_controller->AddObserver(this);
505 if (overview_controller->InOverviewSession())
506 ++blur_lock_;
507 }
508 SetParentLayer(parent_layer);
509
510 DCHECK(scrollable_shelf_view);
511 scrollable_shelf_view_ = scrollable_shelf_view;
512 }
513
UpdateTranslucentBackground()514 void HotseatWidget::DelegateView::UpdateTranslucentBackground() {
515 if (!HotseatWidget::ShouldShowHotseatBackground()) {
516 translucent_background_.SetVisible(false);
517 SetBackgroundBlur(false);
518 return;
519 }
520
521 // Layer::SetBounds() does not mirror bounds under RTL. So set the mirrored
522 // bounds explicitly.
523 SetTranslucentBackground(scrollable_shelf_view_->GetMirroredRect(
524 scrollable_shelf_view_->GetHotseatBackgroundBounds()));
525 }
526
SetTranslucentBackground(const gfx::Rect & background_bounds)527 void HotseatWidget::DelegateView::SetTranslucentBackground(
528 const gfx::Rect& background_bounds) {
529 DCHECK(HotseatWidget::ShouldShowHotseatBackground());
530
531 translucent_background_.SetVisible(true);
532 SetBackgroundBlur(/*enable_blur=*/true);
533
534 auto* animator = translucent_background_.GetAnimator();
535
536 base::Optional<ui::AnimationThroughputReporter> reporter;
537 if (hotseat_widget_ && hotseat_widget_->state() != HotseatState::kNone) {
538 reporter.emplace(animator,
539 hotseat_widget_->GetTranslucentBackgroundReportCallback());
540 }
541
542 if (ShelfConfig::Get()->GetDefaultShelfColor() != target_color_) {
543 ui::ScopedLayerAnimationSettings color_animation_setter(animator);
544 DoScopedAnimationSetting(&color_animation_setter);
545 target_color_ = ShelfConfig::Get()->GetDefaultShelfColor();
546 translucent_background_.SetColor(target_color_);
547 }
548
549 // Animate the bounds change if there's a change of width (for instance when
550 // dragging an app into, or out of, the shelf) and meanwhile scrollable
551 // shelf's bounds does not update at the same time.
552 const bool animate_bounds =
553 background_bounds.width() != translucent_background_.bounds().width() &&
554 (scrollable_shelf_view_ &&
555 !scrollable_shelf_view_->NeedUpdateToTargetBounds());
556 base::Optional<ui::ScopedLayerAnimationSettings> bounds_animation_setter;
557 if (animate_bounds) {
558 bounds_animation_setter.emplace(animator);
559 DoScopedAnimationSetting(&bounds_animation_setter.value());
560 }
561
562 const int radius = hotseat_widget_->GetHotseatSize() / 2;
563 gfx::RoundedCornersF rounded_corners = {radius, radius, radius, radius};
564 if (translucent_background_.rounded_corner_radii() != rounded_corners)
565 translucent_background_.SetRoundedCornerRadius(rounded_corners);
566
567 if (translucent_background_.GetTargetBounds() != background_bounds)
568 translucent_background_.SetBounds(background_bounds);
569 }
570
SetBackgroundBlur(bool enable_blur)571 void HotseatWidget::DelegateView::SetBackgroundBlur(bool enable_blur) {
572 if (!features::IsBackgroundBlurEnabled() || blur_lock_ > 0)
573 return;
574
575 const int blur_radius =
576 enable_blur ? ShelfConfig::Get()->shelf_blur_radius() : 0;
577 if (translucent_background_.background_blur() != blur_radius)
578 translucent_background_.SetBackgroundBlur(blur_radius);
579 }
580
OnHotseatTransitionAnimationWillStart(HotseatState from_state,HotseatState to_state)581 void HotseatWidget::DelegateView::OnHotseatTransitionAnimationWillStart(
582 HotseatState from_state,
583 HotseatState to_state) {
584 DCHECK_LE(blur_lock_, 2);
585
586 SetBackgroundBlur(false);
587 ++blur_lock_;
588 }
589
OnHotseatTransitionAnimationEnded(HotseatState from_state,HotseatState to_state)590 void HotseatWidget::DelegateView::OnHotseatTransitionAnimationEnded(
591 HotseatState from_state,
592 HotseatState to_state) {
593 DCHECK_GT(blur_lock_, 0);
594
595 --blur_lock_;
596 SetBackgroundBlur(true);
597 }
598
OnHotseatTransitionAnimationAborted()599 void HotseatWidget::DelegateView::OnHotseatTransitionAnimationAborted() {
600 DCHECK_GT(blur_lock_, 0);
601
602 --blur_lock_;
603 }
604
CanActivate() const605 bool HotseatWidget::DelegateView::CanActivate() const {
606 // We don't want mouse clicks to activate us, but we need to allow
607 // activation when the user is using the keyboard (FocusCycler).
608 return focus_cycler_ && focus_cycler_->widget_activating() == GetWidget();
609 }
610
ReorderChildLayers(ui::Layer * parent_layer)611 void HotseatWidget::DelegateView::ReorderChildLayers(ui::Layer* parent_layer) {
612 views::View::ReorderChildLayers(parent_layer);
613 parent_layer->StackAtBottom(&translucent_background_);
614 }
615
OnOverviewModeWillStart()616 void HotseatWidget::DelegateView::OnOverviewModeWillStart() {
617 DCHECK_LE(blur_lock_, 2);
618
619 SetBackgroundBlur(false);
620 ++blur_lock_;
621 }
622
OnOverviewModeEndingAnimationComplete(bool canceled)623 void HotseatWidget::DelegateView::OnOverviewModeEndingAnimationComplete(
624 bool canceled) {
625 DCHECK_GT(blur_lock_, 0);
626
627 --blur_lock_;
628 SetBackgroundBlur(true);
629 }
630
OnWallpaperColorsChanged()631 void HotseatWidget::DelegateView::OnWallpaperColorsChanged() {
632 UpdateTranslucentBackground();
633 }
634
SetParentLayer(ui::Layer * layer)635 void HotseatWidget::DelegateView::SetParentLayer(ui::Layer* layer) {
636 layer->Add(&translucent_background_);
637 ReorderLayers();
638 }
639
640 ////////////////////////////////////////////////////////////////////////////////
641 // ScopedInStateTransition
642
ScopedInStateTransition(HotseatWidget * hotseat_widget,HotseatState old_state,HotseatState target_state)643 HotseatWidget::ScopedInStateTransition::ScopedInStateTransition(
644 HotseatWidget* hotseat_widget,
645 HotseatState old_state,
646 HotseatState target_state)
647 : hotseat_widget_(hotseat_widget) {
648 hotseat_widget_->state_transition_in_progress_ =
649 CalculateHotseatStateTransition(old_state, target_state);
650 }
651
~ScopedInStateTransition()652 HotseatWidget::ScopedInStateTransition::~ScopedInStateTransition() {
653 hotseat_widget_->state_transition_in_progress_.reset();
654 }
655
656 ////////////////////////////////////////////////////////////////////////////////
657 // HotseatWidget
658
HotseatWidget()659 HotseatWidget::HotseatWidget() : delegate_view_(new DelegateView()) {
660 ShelfConfig::Get()->AddObserver(this);
661 }
662
~HotseatWidget()663 HotseatWidget::~HotseatWidget() {
664 ui::LayerAnimator* hotseat_layer_animator =
665 GetNativeView()->layer()->GetAnimator();
666 if (hotseat_layer_animator->is_animating())
667 hotseat_layer_animator->AbortAllAnimations();
668
669 ShelfConfig::Get()->RemoveObserver(this);
670 shelf_->shelf_widget()->hotseat_transition_animator()->RemoveObserver(
671 delegate_view_);
672 }
673
ShouldShowHotseatBackground()674 bool HotseatWidget::ShouldShowHotseatBackground() {
675 return Shell::Get()->tablet_mode_controller() &&
676 Shell::Get()->tablet_mode_controller()->InTabletMode();
677 }
678
Initialize(aura::Window * container,Shelf * shelf)679 void HotseatWidget::Initialize(aura::Window* container, Shelf* shelf) {
680 DCHECK(container);
681 DCHECK(shelf);
682 shelf_ = shelf;
683 views::Widget::InitParams params(
684 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
685 params.name = "HotseatWidget";
686 params.delegate = delegate_view_;
687 params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
688 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
689 params.parent = container;
690 params.layer_type = ui::LAYER_NOT_DRAWN;
691 Init(std::move(params));
692 set_focus_on_creation(false);
693
694 scrollable_shelf_view_ = GetContentsView()->AddChildView(
695 std::make_unique<ScrollableShelfView>(ShelfModel::Get(), shelf));
696 delegate_view_->Init(scrollable_shelf_view(), GetLayer(), this);
697 delegate_view_->SetEnableArrowKeyTraversal(true);
698
699 // The initialization of scrollable shelf should update the translucent
700 // background which is stored in |delegate_view_|. So initializes
701 // |scrollable_shelf_view_| after |delegate_view_|.
702 scrollable_shelf_view_->Init();
703 }
704
OnHotseatTransitionAnimatorCreated(HotseatTransitionAnimator * animator)705 void HotseatWidget::OnHotseatTransitionAnimatorCreated(
706 HotseatTransitionAnimator* animator) {
707 shelf_->shelf_widget()->hotseat_transition_animator()->AddObserver(
708 delegate_view_);
709 }
710
OnMouseEvent(ui::MouseEvent * event)711 void HotseatWidget::OnMouseEvent(ui::MouseEvent* event) {
712 if (event->type() == ui::ET_MOUSE_PRESSED)
713 keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser();
714 views::Widget::OnMouseEvent(event);
715 }
716
OnGestureEvent(ui::GestureEvent * event)717 void HotseatWidget::OnGestureEvent(ui::GestureEvent* event) {
718 if (event->type() == ui::ET_GESTURE_TAP_DOWN)
719 keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser();
720
721 // Context menus for shelf app button forward gesture events to hotseat
722 // widget, so the shelf app button can continue handling drag even after the
723 // context menu starts capturing events. Ignore events not interesting to the
724 // shelf app button in this state.
725 ShelfAppButton* item_with_context_menu =
726 scrollable_shelf_view_->shelf_view()->GetShelfItemViewWithContextMenu();
727 if (item_with_context_menu &&
728 !ShelfAppButton::ShouldHandleEventFromContextMenu(event)) {
729 event->SetHandled();
730 return;
731 }
732
733 if (!event->handled())
734 views::Widget::OnGestureEvent(event);
735
736 // Ensure that the app button's drag state gets cleared on gesture end even if
737 // the event doesn't get delivered to the app button.
738 if (item_with_context_menu && event->type() == ui::ET_GESTURE_END)
739 item_with_context_menu->ClearDragStateOnGestureEnd();
740 }
741
OnNativeWidgetActivationChanged(bool active)742 bool HotseatWidget::OnNativeWidgetActivationChanged(bool active) {
743 if (!Widget::OnNativeWidgetActivationChanged(active))
744 return false;
745
746 scrollable_shelf_view_->OnFocusRingActivationChanged(active);
747 return true;
748 }
749
OnShelfConfigUpdated()750 void HotseatWidget::OnShelfConfigUpdated() {
751 set_manually_extended(false);
752 }
753
IsExtended() const754 bool HotseatWidget::IsExtended() const {
755 DCHECK(GetShelfView()->shelf()->IsHorizontalAlignment());
756 const int extended_bottom =
757 display::Screen::GetScreen()
758 ->GetDisplayNearestView(GetShelfView()->GetWidget()->GetNativeView())
759 .bounds()
760 .bottom() -
761 (ShelfConfig::Get()->shelf_size() +
762 ShelfConfig::Get()->hotseat_bottom_padding());
763 return GetWindowBoundsInScreen().bottom() == extended_bottom;
764 }
765
FocusFirstOrLastFocusableChild(bool last)766 void HotseatWidget::FocusFirstOrLastFocusableChild(bool last) {
767 GetShelfView()->FindFirstOrLastFocusableChild(last)->RequestFocus();
768 }
769
OnTabletModeChanged()770 void HotseatWidget::OnTabletModeChanged() {
771 GetShelfView()->OnTabletModeChanged();
772 }
773
CalculateShelfViewOpacity() const774 float HotseatWidget::CalculateShelfViewOpacity() const {
775 const float target_opacity =
776 GetShelfView()->shelf()->shelf_layout_manager()->GetOpacity();
777 // Hotseat's shelf view should not be dimmed if hotseat is kExtended.
778 return (state() == HotseatState::kExtended) ? 1.0f : target_opacity;
779 }
780
UpdateTranslucentBackground()781 void HotseatWidget::UpdateTranslucentBackground() {
782 delegate_view_->UpdateTranslucentBackground();
783 }
784
CalculateHotseatYInScreen(HotseatState hotseat_target_state) const785 int HotseatWidget::CalculateHotseatYInScreen(
786 HotseatState hotseat_target_state) const {
787 DCHECK(shelf_->IsHorizontalAlignment());
788 int hotseat_distance_from_bottom_of_display = 0;
789 const int hotseat_size = GetHotseatSize();
790 switch (hotseat_target_state) {
791 case HotseatState::kShownClamshell:
792 hotseat_distance_from_bottom_of_display = hotseat_size;
793 break;
794 case HotseatState::kShownHomeLauncher:
795 // When the hotseat state is HotseatState::kShownHomeLauncher, the home
796 // launcher is showing in tablet mode. Elevate the hotseat a few px to
797 // match the navigation and status area.
798 hotseat_distance_from_bottom_of_display =
799 hotseat_size + ShelfConfig::Get()->hotseat_bottom_padding();
800 break;
801 case HotseatState::kHidden:
802 // Show the hotseat offscreen.
803 hotseat_distance_from_bottom_of_display = 0;
804 break;
805 case HotseatState::kExtended:
806 // Show the hotseat at its extended position.
807 hotseat_distance_from_bottom_of_display =
808 ShelfConfig::Get()->in_app_shelf_size() +
809 ShelfConfig::Get()->hotseat_bottom_padding() + hotseat_size;
810 break;
811 case HotseatState::kNone:
812 NOTREACHED();
813 }
814 const int target_shelf_size =
815 shelf_->shelf_widget()->GetTargetBounds().size().height();
816 const int hotseat_y_in_shelf =
817 -(hotseat_distance_from_bottom_of_display - target_shelf_size);
818 const int shelf_y = shelf_->shelf_widget()->GetTargetBounds().y();
819 return hotseat_y_in_shelf + shelf_y;
820 }
821
CalculateTargetBoundsSize(HotseatState hotseat_target_state) const822 gfx::Size HotseatWidget::CalculateTargetBoundsSize(
823 HotseatState hotseat_target_state) const {
824 const gfx::Rect shelf_bounds = shelf_->shelf_widget()->GetTargetBounds();
825
826 // |hotseat_size| is the height in horizontal alignment or the width in
827 // vertical alignment.
828 const int hotseat_size = GetHotseatSize();
829
830 if (hotseat_target_state != HotseatState::kShownHomeLauncher &&
831 hotseat_target_state != HotseatState::kShownClamshell) {
832 DCHECK(shelf_->IsHorizontalAlignment());
833 // Give the hotseat more space if it is shown outside of the shelf.
834 return gfx::Size(shelf_bounds.width(), hotseat_size);
835 }
836
837 const gfx::Size status_size =
838 shelf_->status_area_widget()->GetTargetBounds().size();
839 const gfx::Rect nav_bounds = shelf_->navigation_widget()->GetVisibleBounds();
840
841 // The navigation widget has extra padding on the hotseat side, to center the
842 // buttons inside of it. Make sure to get the extra nav widget padding and
843 // take it into account when calculating the hotseat size.
844 const int nav_widget_padding =
845 nav_bounds.size().IsEmpty()
846 ? 0
847 : ShelfConfig::Get()->control_button_edge_spacing(
848 true /* is_primary_axis_edge */);
849
850 // The minimum gap between hotseat widget and other shelf components including
851 // the status area widget and shelf navigation widget (or the edge of display,
852 // if the shelf navigation widget does not show).
853 const int group_margin = ShelfConfig::Get()->GetAppIconGroupMargin();
854
855 if (shelf_->IsHorizontalAlignment()) {
856 const int width = shelf_bounds.width() - nav_bounds.size().width() +
857 nav_widget_padding - 2 * group_margin -
858 status_size.width();
859 return gfx::Size(width, hotseat_size);
860 }
861
862 const int height = shelf_bounds.height() - nav_bounds.size().height() +
863 nav_widget_padding - 2 * group_margin -
864 status_size.height();
865 return gfx::Size(hotseat_size, height);
866 }
867
CalculateTargetBounds()868 void HotseatWidget::CalculateTargetBounds() {
869 ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager();
870 const HotseatState hotseat_target_state =
871 layout_manager->CalculateHotseatState(layout_manager->visibility_state(),
872 layout_manager->auto_hide_state());
873 const gfx::Size hotseat_target_size =
874 CalculateTargetBoundsSize(hotseat_target_state);
875
876 if (hotseat_target_state == HotseatState::kShownHomeLauncher) {
877 target_size_for_shown_state_ = hotseat_target_size;
878 } else {
879 target_size_for_shown_state_ =
880 CalculateTargetBoundsSize(HotseatState::kShownHomeLauncher);
881 }
882
883 const gfx::Rect shelf_bounds = shelf_->shelf_widget()->GetTargetBounds();
884 const gfx::Rect status_area_bounds =
885 shelf_->status_area_widget()->GetTargetBounds();
886
887 // The minimum gap between hotseat widget and other shelf components including
888 // the status area widget and shelf navigation widget (or the edge of display,
889 // if the shelf navigation widget does not show).
890 const int group_margin = ShelfConfig::Get()->GetAppIconGroupMargin();
891
892 gfx::Point hotseat_origin;
893 if (shelf_->IsHorizontalAlignment()) {
894 int hotseat_x;
895 if (hotseat_target_state != HotseatState::kShownHomeLauncher &&
896 hotseat_target_state != HotseatState::kShownClamshell) {
897 hotseat_x = shelf_bounds.x();
898 } else {
899 hotseat_x = base::i18n::IsRTL()
900 ? status_area_bounds.right() + group_margin
901 : status_area_bounds.x() - group_margin -
902 hotseat_target_size.width();
903 }
904
905 hotseat_origin =
906 gfx::Point(hotseat_x, CalculateHotseatYInScreen(hotseat_target_state));
907 } else {
908 hotseat_origin =
909 gfx::Point(shelf_bounds.x(), status_area_bounds.y() - group_margin -
910 hotseat_target_size.height());
911 }
912
913 target_bounds_ = gfx::Rect(hotseat_origin, hotseat_target_size);
914
915 // Check whether |target_bounds_| will change the state of app scaling. If
916 // so, update |target_bounds_| here to avoid re-layout later.
917 MaybeAdjustTargetBoundsForAppScaling(hotseat_target_state);
918 }
919
GetTargetBounds() const920 gfx::Rect HotseatWidget::GetTargetBounds() const {
921 return target_bounds_;
922 }
923
UpdateLayout(bool animate)924 void HotseatWidget::UpdateLayout(bool animate) {
925 const LayoutInputs new_layout_inputs = GetLayoutInputs();
926 if (layout_inputs_ == new_layout_inputs)
927 return;
928
929 // Never show this widget outside of an active session.
930 if (!new_layout_inputs.is_active_session_state)
931 Hide();
932
933 ui::Layer* shelf_view_layer = GetShelfView()->layer();
934 {
935 ui::ScopedLayerAnimationSettings animation_setter(
936 shelf_view_layer->GetAnimator());
937 animation_setter.SetTransitionDuration(
938 animate ? ShelfConfig::Get()->shelf_animation_duration()
939 : base::TimeDelta::FromMilliseconds(0));
940 animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
941 animation_setter.SetPreemptionStrategy(
942 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
943
944 base::Optional<ui::AnimationThroughputReporter> reporter;
945 if (animate && state_ != HotseatState::kNone) {
946 reporter.emplace(animation_setter.GetAnimator(),
947 shelf_->GetHotseatTransitionReportCallback(state_));
948 }
949
950 shelf_view_layer->SetOpacity(new_layout_inputs.shelf_view_opacity);
951 }
952
953 // If shelf view is invisible, the hotseat should be as well. Otherwise the
954 // hotseat opacit should be 1.0f to preserve background blur.
955 const double target_opacity =
956 (new_layout_inputs.shelf_view_opacity == 0.f ? 0.f : 1.f);
957 const gfx::Rect& target_bounds = new_layout_inputs.bounds;
958
959 if (animate) {
960 LayoutHotseatByAnimation(target_opacity, target_bounds);
961 } else {
962 ui::Layer* hotseat_layer = GetNativeView()->layer();
963
964 // If the running bounds animation is not aborted, it will be interrupted
965 // and set hotseat widget with the old target bounds which may differ from
966 // |target_bounds| greatly and bring DCHECK errors. For example,
967 // if hotseat animation is interrupted by the bounds setting triggered by
968 // shelf alignment update, hotseat will be caught in an intermediate state
969 // where the shelf alignment is new and the hotseat bounds are old.
970 hotseat_layer->GetAnimator()->AbortAllAnimations();
971
972 hotseat_layer->SetOpacity(target_opacity);
973 SetBounds(target_bounds);
974 }
975
976 layout_inputs_ = new_layout_inputs;
977 delegate_view_->UpdateTranslucentBackground();
978
979 // Setting visibility during an animation causes the visibility property to
980 // animate. Set the visibility property without an animation.
981 if (new_layout_inputs.shelf_view_opacity != 0.0f &&
982 new_layout_inputs.is_active_session_state) {
983 ShowInactive();
984 }
985 }
986
UpdateTargetBoundsForGesture(int shelf_position)987 void HotseatWidget::UpdateTargetBoundsForGesture(int shelf_position) {
988 if (shelf_->IsHorizontalAlignment())
989 target_bounds_.set_y(shelf_position);
990 else
991 target_bounds_.set_x(shelf_position);
992 }
993
GetTranslucentBackgroundSize() const994 gfx::Size HotseatWidget::GetTranslucentBackgroundSize() const {
995 DCHECK(scrollable_shelf_view_);
996 return scrollable_shelf_view_->GetHotseatBackgroundBounds().size();
997 }
998
SetFocusCycler(FocusCycler * focus_cycler)999 void HotseatWidget::SetFocusCycler(FocusCycler* focus_cycler) {
1000 delegate_view_->set_focus_cycler(focus_cycler);
1001 if (focus_cycler)
1002 focus_cycler->AddWidget(this);
1003 }
1004
GetShelfView()1005 ShelfView* HotseatWidget::GetShelfView() {
1006 DCHECK(scrollable_shelf_view_);
1007 return scrollable_shelf_view_->shelf_view();
1008 }
1009
GetHotseatSize() const1010 int HotseatWidget::GetHotseatSize() const {
1011 return ShelfConfig::Get()->GetShelfButtonSize(target_hotseat_density_);
1012 }
1013
GetHotseatFullDragAmount() const1014 int HotseatWidget::GetHotseatFullDragAmount() const {
1015 ShelfConfig* shelf_config = ShelfConfig::Get();
1016 return shelf_config->shelf_size() + shelf_config->hotseat_bottom_padding() +
1017 GetHotseatSize();
1018 }
1019
UpdateTargetHotseatDensityIfNeeded()1020 bool HotseatWidget::UpdateTargetHotseatDensityIfNeeded() {
1021 if (CalculateTargetHotseatDensity() == target_hotseat_density_) {
1022 return false;
1023 }
1024
1025 shelf_->shelf_layout_manager()->LayoutShelf(/*animate=*/true);
1026 return true;
1027 }
1028
GetHotseatBackgroundBlurForTest() const1029 int HotseatWidget::GetHotseatBackgroundBlurForTest() const {
1030 return delegate_view_->background_blur();
1031 }
1032
GetIsTranslucentBackgroundVisibleForTest() const1033 bool HotseatWidget::GetIsTranslucentBackgroundVisibleForTest() const {
1034 return delegate_view_->is_translucent_background_visible_for_test();
1035 }
1036
IsShowingShelfMenu() const1037 bool HotseatWidget::IsShowingShelfMenu() const {
1038 return GetShelfView()->IsShowingMenu();
1039 }
1040
EventTargetsShelfView(const ui::LocatedEvent & event) const1041 bool HotseatWidget::EventTargetsShelfView(const ui::LocatedEvent& event) const {
1042 DCHECK_EQ(event.target(), GetNativeWindow());
1043 gfx::Point location_in_shelf_view = event.location();
1044 views::View::ConvertPointFromWidget(scrollable_shelf_view_,
1045 &location_in_shelf_view);
1046 return scrollable_shelf_view_->GetHotseatBackgroundBounds().Contains(
1047 location_in_shelf_view);
1048 }
1049
GetShelfView() const1050 const ShelfView* HotseatWidget::GetShelfView() const {
1051 return const_cast<const ShelfView*>(
1052 const_cast<HotseatWidget*>(this)->GetShelfView());
1053 }
1054
1055 metrics_util::ReportCallback
GetTranslucentBackgroundReportCallback()1056 HotseatWidget::GetTranslucentBackgroundReportCallback() {
1057 return shelf_->GetTranslucentBackgroundReportCallback(state_);
1058 }
1059
SetState(HotseatState state)1060 void HotseatWidget::SetState(HotseatState state) {
1061 if (state_ == state)
1062 return;
1063
1064 state_ = state;
1065
1066 // If the hotseat is not extended we can use the normal targeting as the
1067 // hidden parts of the hotseat will not block non-shelf items from taking
1068 if (state == HotseatState::kExtended) {
1069 hotseat_window_targeter_ = std::make_unique<aura::ScopedWindowTargeter>(
1070 GetNativeWindow(), std::make_unique<HotseatWindowTargeter>(this));
1071 } else {
1072 hotseat_window_targeter_.reset();
1073 }
1074 }
1075
GetLayoutInputs() const1076 HotseatWidget::LayoutInputs HotseatWidget::GetLayoutInputs() const {
1077 const ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager();
1078 return {target_bounds_, CalculateShelfViewOpacity(),
1079 layout_manager->is_active_session_state()};
1080 }
1081
MaybeAdjustTargetBoundsForAppScaling(HotseatState hotseat_target_state)1082 void HotseatWidget::MaybeAdjustTargetBoundsForAppScaling(
1083 HotseatState hotseat_target_state) {
1084 // Return early if app scaling state does not change.
1085 HotseatDensity new_target_hotseat_density = CalculateTargetHotseatDensity();
1086 if (new_target_hotseat_density == target_hotseat_density_)
1087 return;
1088
1089 target_hotseat_density_ = new_target_hotseat_density;
1090
1091 // Update app icons of shelf view.
1092 scrollable_shelf_view_->shelf_view()->OnShelfConfigUpdated();
1093
1094 const gfx::Point adjusted_hotseat_origin = gfx::Point(
1095 target_bounds_.x(), CalculateHotseatYInScreen(hotseat_target_state));
1096 target_bounds_ =
1097 gfx::Rect(adjusted_hotseat_origin,
1098 gfx::Size(target_bounds_.width(), GetHotseatSize()));
1099 }
1100
CalculateTargetHotseatDensity() const1101 HotseatDensity HotseatWidget::CalculateTargetHotseatDensity() const {
1102 // App scaling is only applied to the standard shelf. So the hotseat density
1103 // should not update in dense shelf.
1104 if (ShelfConfig::Get()->is_dense())
1105 return target_hotseat_density_;
1106
1107 // TODO(crbug.com/1081476): Currently the scaling animation of hotseat bounds
1108 // and that of shelf icons do not synchronize due to performance issue. As a
1109 // result, shelf scaling is not applied to the hotseat state transition, such
1110 // as the transition from the home launcher state to the extended state.
1111 // Hotseat density relies on the hotseat bounds in the home launcher state
1112 // instead of the current hotseat state.
1113
1114 // Try candidate button sizes in decreasing order. If shelf buttons in one
1115 // size can show without scrolling, return the density type corresponding to
1116 // that particular size; if no candidate size can make it, return
1117 // HotseatDensity::kDense.
1118 const std::vector<HotseatDensity> kCandidates = {HotseatDensity::kNormal,
1119 HotseatDensity::kSemiDense};
1120 for (const auto& candidate : kCandidates) {
1121 if (!scrollable_shelf_view_->RequiresScrollingForItemSize(
1122 target_size_for_shown_state_,
1123 ShelfConfig::Get()->GetShelfButtonSize(candidate))) {
1124 return candidate;
1125 }
1126 }
1127 return HotseatDensity::kDense;
1128 }
1129
LayoutHotseatByAnimation(double target_opacity,const gfx::Rect & target_bounds)1130 void HotseatWidget::LayoutHotseatByAnimation(double target_opacity,
1131 const gfx::Rect& target_bounds) {
1132 ui::Layer* hotseat_layer = GetNativeView()->layer();
1133
1134 ui::ScopedLayerAnimationSettings animation_setter(
1135 hotseat_layer->GetAnimator());
1136 animation_setter.SetTransitionDuration(
1137 ShelfConfig::Get()->shelf_animation_duration());
1138 animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
1139 animation_setter.SetPreemptionStrategy(
1140 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
1141
1142 base::Optional<ui::AnimationThroughputReporter> reporter;
1143 if (state_ != HotseatState::kNone) {
1144 reporter.emplace(animation_setter.GetAnimator(),
1145 shelf_->GetHotseatTransitionReportCallback(state_));
1146 }
1147
1148 if (!state_transition_in_progress_.has_value()) {
1149 // Hotseat animation is not triggered by the update in |state_|. So apply
1150 // the normal bounds animation.
1151 StartNormalBoundsAnimation(target_opacity, target_bounds);
1152 return;
1153 }
1154
1155 if (HasSpecialAnimation(*state_transition_in_progress_)) {
1156 StartHotseatTransitionAnimation(*state_transition_in_progress_,
1157 target_opacity, target_bounds);
1158 } else {
1159 StartNormalBoundsAnimation(target_opacity, target_bounds);
1160 }
1161 }
1162
StartHotseatTransitionAnimation(StateTransition state_transition,double target_opacity,const gfx::Rect & target_bounds)1163 void HotseatWidget::StartHotseatTransitionAnimation(
1164 StateTransition state_transition,
1165 double target_opacity,
1166 const gfx::Rect& target_bounds) {
1167 ui::Layer* hotseat_layer = GetNativeView()->layer();
1168 std::unique_ptr<ui::LayerAnimationElement> animation_elements;
1169 switch (state_transition) {
1170 case StateTransition::kHomeLauncherAndExtended:
1171 animation_elements = std::make_unique<HomeAndExtendedTransitionAnimation>(
1172 target_bounds, target_opacity, hotseat_layer,
1173 /*hotseat_widget=*/this);
1174 break;
1175 case StateTransition::kHomeLauncherAndHidden:
1176 animation_elements = std::make_unique<HomeAndHiddenTransitionAnimation>(
1177 target_bounds, target_opacity, hotseat_layer,
1178 /*hotseat_widget=*/this);
1179 break;
1180 case StateTransition::kHiddenAndExtended:
1181 case StateTransition::kOther:
1182 NOTREACHED();
1183 }
1184
1185 auto* sequence =
1186 new ui::LayerAnimationSequence(std::move(animation_elements));
1187 hotseat_layer->GetAnimator()->StartAnimation(sequence);
1188 }
1189
StartNormalBoundsAnimation(double target_opacity,const gfx::Rect & target_bounds)1190 void HotseatWidget::StartNormalBoundsAnimation(double target_opacity,
1191 const gfx::Rect& target_bounds) {
1192 GetNativeView()->layer()->SetOpacity(target_opacity);
1193 SetBounds(target_bounds);
1194 }
1195
1196 } // namespace ash
1197