| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/shelf/hotseat_widget.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/focus_cycler.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/public/cpp/shelf_model.h" |
| #include "ash/public/cpp/shelf_types.h" |
| #include "ash/public/cpp/wallpaper/wallpaper_controller_observer.h" |
| #include "ash/shelf/hotseat_transition_animator.h" |
| #include "ash/shelf/scrollable_shelf_view.h" |
| #include "ash/shelf/shelf_app_button.h" |
| #include "ash/shelf/shelf_layout_manager.h" |
| #include "ash/shelf/shelf_navigation_widget.h" |
| #include "ash/shelf/shelf_view.h" |
| #include "ash/shell.h" |
| #include "ash/style/system_shadow.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/wallpaper/wallpaper_controller_impl.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_observer.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/i18n/rtl.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "ui/aura/scoped_window_targeter.h" |
| #include "ui/aura/window_targeter.h" |
| #include "ui/compositor/animation_throughput_reporter.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/gfx/color_analysis.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/gfx/geometry/rounded_corners_f.h" |
| #include "ui/views/background.h" |
| #include "ui/views/highlight_border.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/view_targeter_delegate.h" |
| #include "ui/views/widget/widget_delegate.h" |
| |
| namespace ash { |
| namespace { |
| |
| void DoScopedAnimationSetting( |
| ui::ScopedLayerAnimationSettings* animation_setter) { |
| animation_setter->SetTransitionDuration( |
| ShelfConfig::Get()->shelf_animation_duration()); |
| animation_setter->SetTweenType(gfx::Tween::EASE_OUT); |
| animation_setter->SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| } |
| |
| // Returns whether there is special hotseat animation for |transition|. |
| bool HasSpecialAnimation(HotseatWidget::StateTransition transition) { |
| switch (transition) { |
| case HotseatWidget::StateTransition::kHomeLauncherAndExtended: |
| case HotseatWidget::StateTransition::kHomeLauncherAndHidden: |
| return true; |
| case HotseatWidget::StateTransition::kHiddenAndExtended: |
| case HotseatWidget::StateTransition::kOther: |
| return false; |
| } |
| } |
| |
| // Calculates the state transition type for the given previous state and |
| // the target state. |
| HotseatWidget::StateTransition CalculateHotseatStateTransition( |
| HotseatState previous_state, |
| HotseatState target_state) { |
| if (previous_state == HotseatState::kNone || |
| target_state == HotseatState::kNone) { |
| return HotseatWidget::StateTransition::kOther; |
| } |
| |
| if (previous_state == target_state) |
| return HotseatWidget::StateTransition::kOther; |
| |
| const bool related_to_homelauncher = |
| (previous_state == HotseatState::kShownHomeLauncher || |
| target_state == HotseatState::kShownHomeLauncher); |
| const bool related_to_extended = (previous_state == HotseatState::kExtended || |
| target_state == HotseatState::kExtended); |
| const bool related_to_hidden = (previous_state == HotseatState::kHidden || |
| target_state == HotseatState::kHidden); |
| |
| if (related_to_homelauncher && related_to_extended) |
| return HotseatWidget::StateTransition::kHomeLauncherAndExtended; |
| |
| if (related_to_homelauncher && related_to_hidden) |
| return HotseatWidget::StateTransition::kHomeLauncherAndHidden; |
| |
| if (related_to_extended && related_to_hidden) |
| return HotseatWidget::StateTransition::kHiddenAndExtended; |
| |
| return HotseatWidget::StateTransition::kOther; |
| } |
| |
| // Base class for hotseat animation transition. |
| class HotseatStateTransitionAnimation : public ui::LayerAnimationElement { |
| public: |
| HotseatStateTransitionAnimation(const gfx::Rect& target_bounds_in_screen, |
| double target_opacity, |
| ui::Layer* hotseat_layer, |
| HotseatWidget* hotseat_widget) |
| : ui::LayerAnimationElement( |
| LayerAnimationElement::BOUNDS | LayerAnimationElement::OPACITY, |
| hotseat_layer->GetAnimator()->GetTransitionDuration()), |
| target_widget_bounds_(target_bounds_in_screen), |
| target_opacity_(target_opacity), |
| tween_type_(hotseat_layer->GetAnimator()->tween_type()), |
| hotseat_widget_(hotseat_widget) {} |
| |
| ~HotseatStateTransitionAnimation() override = default; |
| |
| HotseatStateTransitionAnimation(const HotseatStateTransitionAnimation& rhs) = |
| delete; |
| HotseatStateTransitionAnimation& operator=( |
| const HotseatStateTransitionAnimation& rhs) = delete; |
| |
| protected: |
| // ui::LayerAnimationElement: |
| void OnGetTarget(TargetValue* target) const override { |
| target->opacity = target_opacity_; |
| target->bounds = target_widget_bounds_; |
| } |
| |
| ScrollableShelfView* GetScrollableShelfView() { |
| return hotseat_widget_->scrollable_shelf_view(); |
| } |
| |
| // Hotseat widget's target bounds in screen. |
| gfx::Rect target_widget_bounds_; |
| |
| // Hotseat widget's initial opacity. |
| double start_opacity_ = 0.f; |
| |
| // Hotseat widget's target opacity. |
| double target_opacity_ = 0.f; |
| |
| gfx::Tween::Type tween_type_ = gfx::Tween::LINEAR; |
| |
| HotseatWidget* hotseat_widget_ = nullptr; |
| }; |
| |
| // Animation implemented specifically for the transition between the home |
| // launcher state and the extended state. |
| class HomeAndExtendedTransitionAnimation |
| : public HotseatStateTransitionAnimation { |
| public: |
| HomeAndExtendedTransitionAnimation(const gfx::Rect& target_bounds_in_screen, |
| double target_opacity, |
| ui::Layer* hotseat_layer, |
| HotseatWidget* hotseat_widget) |
| : HotseatStateTransitionAnimation(target_bounds_in_screen, |
| target_opacity, |
| hotseat_layer, |
| hotseat_widget) {} |
| ~HomeAndExtendedTransitionAnimation() override = default; |
| |
| HomeAndExtendedTransitionAnimation( |
| const HomeAndExtendedTransitionAnimation& rhs) = delete; |
| HomeAndExtendedTransitionAnimation& operator=( |
| const HomeAndExtendedTransitionAnimation& rhs) = delete; |
| |
| private: |
| // HotseatStateTransitionAnimation: |
| void OnStart(ui::LayerAnimationDelegate* delegate) override { |
| DCHECK(hotseat_widget_->GetShelfView()->shelf()->IsHorizontalAlignment()); |
| |
| ScrollableShelfView* scrollable_shelf_view = GetScrollableShelfView(); |
| scrollable_shelf_view->set_is_padding_configured_externally( |
| /*is_padding_configured_externally=*/true); |
| |
| // Save initial and target padding insets. |
| initial_padding_insets_ = scrollable_shelf_view->edge_padding_insets(); |
| target_padding_insets_ = |
| scrollable_shelf_view->CalculateMirroredEdgePadding( |
| /*use_target_bounds=*/true); |
| |
| // Save initial opacity. |
| start_opacity_ = hotseat_widget_->GetNativeView()->layer()->opacity(); |
| |
| // Save initial hotseat background bounds. |
| initial_hotseat_background_in_screen_ = |
| hotseat_widget_->GetWindowBoundsInScreen(); |
| initial_hotseat_background_in_screen_.Inset(initial_padding_insets_); |
| |
| // Save target hotseat background bounds. |
| target_hotseat_background_in_screen_ = target_widget_bounds_; |
| target_hotseat_background_in_screen_.Inset(target_padding_insets_); |
| } |
| |
| // HotseatStateTransitionAnimation: |
| bool OnProgress(double current, |
| ui::LayerAnimationDelegate* delegate) override { |
| const double tweened = gfx::Tween::CalculateValue(tween_type_, current); |
| |
| // Set scrollable shelf view's padding insets. |
| gfx::Insets insets_in_animation_progress; |
| insets_in_animation_progress.set_left(gfx::Tween::LinearIntValueBetween( |
| tweened, initial_padding_insets_.left(), |
| target_padding_insets_.left())); |
| insets_in_animation_progress.set_right(gfx::Tween::LinearIntValueBetween( |
| tweened, initial_padding_insets_.right(), |
| target_padding_insets_.right())); |
| ScrollableShelfView* scrollable_shelf_view = GetScrollableShelfView(); |
| scrollable_shelf_view->SetEdgePaddingInsets(insets_in_animation_progress); |
| |
| // Update hotseat widget opacity. |
| delegate->SetOpacityFromAnimation( |
| gfx::Tween::DoubleValueBetween(tweened, start_opacity_, |
| target_opacity_), |
| ui::PropertyChangeReason::FROM_ANIMATION); |
| |
| // Calculate the hotseat widget's bounds. |
| const gfx::Rect hotseat_background_in_progress = |
| gfx::Tween::RectValueBetween(tweened, |
| initial_hotseat_background_in_screen_, |
| target_hotseat_background_in_screen_); |
| gfx::Rect widget_bounds_in_progress = hotseat_background_in_progress; |
| widget_bounds_in_progress.Inset( |
| -scrollable_shelf_view->edge_padding_insets()); |
| |
| // Update hotseat widget bounds. |
| delegate->SetBoundsFromAnimation(widget_bounds_in_progress, |
| ui::PropertyChangeReason::FROM_ANIMATION); |
| |
| // Do recovering when the animation ends. |
| if (current == 1.f) { |
| scrollable_shelf_view->set_is_padding_configured_externally( |
| /*is_padding_configured_externally=*/false); |
| } |
| |
| return true; |
| } |
| |
| // HotseatStateTransitionAnimation: |
| void OnAbort(ui::LayerAnimationDelegate* delegate) override { |
| GetScrollableShelfView()->set_is_padding_configured_externally( |
| /*is_padding_configured_externally=*/false); |
| } |
| |
| // Scrollable shelf's initial padding insets. |
| gfx::Insets initial_padding_insets_; |
| |
| // Scrollable shelf's target padding insets. |
| gfx::Insets target_padding_insets_; |
| |
| // Hotseat background's initial bounds in screen. |
| gfx::Rect initial_hotseat_background_in_screen_; |
| |
| // Hotseat background's target bounds in screen. |
| gfx::Rect target_hotseat_background_in_screen_; |
| }; |
| |
| // Animation implemented specifically for the transition between the home |
| // launcher state and the hidden state. |
| class HomeAndHiddenTransitionAnimation |
| : public HotseatStateTransitionAnimation { |
| public: |
| HomeAndHiddenTransitionAnimation(const gfx::Rect& target_bounds_in_screen, |
| double target_opacity, |
| ui::Layer* hotseat_layer, |
| HotseatWidget* hotseat_widget) |
| : HotseatStateTransitionAnimation(target_bounds_in_screen, |
| target_opacity, |
| hotseat_layer, |
| hotseat_widget) {} |
| ~HomeAndHiddenTransitionAnimation() override = default; |
| |
| protected: |
| // HotseatStateTransitionAnimation: |
| void OnStart(ui::LayerAnimationDelegate* delegate) override { |
| DCHECK(hotseat_widget_->GetShelfView()->shelf()->IsHorizontalAlignment()); |
| |
| start_opacity_ = hotseat_widget_->GetNativeView()->layer()->opacity(); |
| |
| if (hotseat_widget_->state() == HotseatState::kHidden) |
| will_be_hidden_ = true; |
| |
| ScrollableShelfView* scrollable_shelf_view = GetScrollableShelfView(); |
| const gfx::Rect current_widget_bounds( |
| hotseat_widget_->GetWindowBoundsInScreen()); |
| |
| // Ensure that hotseat only has vertical movement during animation. |
| if (will_be_hidden_) { |
| animation_initial_bounds_ = current_widget_bounds; |
| |
| animation_target_bounds_ = current_widget_bounds; |
| animation_target_bounds_.set_y(target_widget_bounds_.y()); |
| } else { |
| animation_initial_bounds_ = target_widget_bounds_; |
| animation_initial_bounds_.set_y(current_widget_bounds.y()); |
| |
| // Ensure that hotseat is set with the target bounds at the end of |
| // animation when hotseat is going to show in home launcher. |
| animation_target_bounds_ = target_widget_bounds_; |
| const gfx::Insets target_padding_insets = |
| scrollable_shelf_view->CalculateMirroredEdgePadding( |
| /*use_target_bounds=*/true); |
| scrollable_shelf_view->SetEdgePaddingInsets(target_padding_insets); |
| delegate->SetBoundsFromAnimation( |
| animation_initial_bounds_, ui::PropertyChangeReason::FROM_ANIMATION); |
| } |
| } |
| |
| // HotseatStateTransitionAnimation: |
| bool OnProgress(double current, |
| ui::LayerAnimationDelegate* delegate) override { |
| const double tweened = gfx::Tween::CalculateValue(tween_type_, current); |
| delegate->SetOpacityFromAnimation( |
| gfx::Tween::DoubleValueBetween(tweened, start_opacity_, |
| target_opacity_), |
| ui::PropertyChangeReason::FROM_ANIMATION); |
| |
| const gfx::Rect widget_bounds_in_progress = gfx::Tween::RectValueBetween( |
| tweened, animation_initial_bounds_, animation_target_bounds_); |
| |
| const bool reach_end = current == 1.f; |
| |
| // When hotseat is going to be hidden, |animation_target_bounds_| is not |
| // equal to |target_widget_bounds_|. So hotseat is set with the target |
| // bounds at the end of animation. It does not bring animation regression |
| // since hotseat is invisible to the user when setting bounds. |
| delegate->SetBoundsFromAnimation(will_be_hidden_ && reach_end |
| ? target_widget_bounds_ |
| : widget_bounds_in_progress, |
| ui::PropertyChangeReason::FROM_ANIMATION); |
| |
| return true; |
| } |
| |
| // HotseatStateTransitionAnimation: |
| void OnAbort(ui::LayerAnimationDelegate* delegate) override {} |
| |
| private: |
| // Whether hotseat widget is hidden after state transition animation. |
| bool will_be_hidden_ = false; |
| |
| // Note that |animation_initial_bounds_| and |animation_target_bounds_| may |
| // not be the hotseat's current bounds and |target_widget_bounds_| |
| // respectively. |
| gfx::Rect animation_initial_bounds_; |
| gfx::Rect animation_target_bounds_; |
| }; |
| |
| // Custom window targeter for the hotseat. Used so the hotseat only processes |
| // events that land on the visible portion of the hotseat, and only while the |
| // hotseat is not animating. |
| class HotseatWindowTargeter : public aura::WindowTargeter { |
| public: |
| explicit HotseatWindowTargeter(HotseatWidget* hotseat_widget) |
| : hotseat_widget_(hotseat_widget) {} |
| ~HotseatWindowTargeter() override = default; |
| |
| HotseatWindowTargeter(const HotseatWindowTargeter& other) = delete; |
| HotseatWindowTargeter& operator=(const HotseatWindowTargeter& rhs) = delete; |
| |
| // aura::WindowTargeter: |
| bool SubtreeShouldBeExploredForEvent(aura::Window* window, |
| const ui::LocatedEvent& event) override { |
| // Do not handle events if the hotseat window is animating as it may animate |
| // over other items which want to process events. |
| if (hotseat_widget_->GetLayer()->GetAnimator()->is_animating()) |
| return false; |
| return aura::WindowTargeter::SubtreeShouldBeExploredForEvent(window, event); |
| } |
| |
| bool GetHitTestRects(aura::Window* target, |
| gfx::Rect* hit_test_rect_mouse, |
| gfx::Rect* hit_test_rect_touch) const override { |
| if (target == hotseat_widget_->GetNativeWindow()) { |
| // Shrink the hit bounds from the size of the window to the size of the |
| // hotseat translucent background. |
| gfx::Rect hit_bounds = target->bounds(); |
| hit_bounds.ClampToCenteredSize( |
| hotseat_widget_->GetTranslucentBackgroundSize()); |
| *hit_test_rect_mouse = *hit_test_rect_touch = hit_bounds; |
| return true; |
| } |
| return aura::WindowTargeter::GetHitTestRects(target, hit_test_rect_mouse, |
| hit_test_rect_touch); |
| } |
| |
| private: |
| // Unowned and guaranteed to be not null for the duration of |this|. |
| HotseatWidget* const hotseat_widget_; |
| }; |
| |
| } // namespace |
| |
| class HotseatWidget::DelegateView : public HotseatTransitionAnimator::Observer, |
| public views::WidgetDelegateView, |
| public views::ViewTargeterDelegate, |
| public OverviewObserver, |
| public WallpaperControllerObserver { |
| public: |
| DelegateView() { |
| SetEventTargeter(std::make_unique<views::ViewTargeter>(this)); |
| SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| } |
| |
| DelegateView(const DelegateView&) = delete; |
| DelegateView& operator=(const DelegateView&) = delete; |
| |
| ~DelegateView() override; |
| |
| // views::ViewTargetDelegate: |
| View* TargetForRect(View* root, const gfx::Rect& rect) override { |
| // If a context menu for a shelf app button is shown, redirect all events to |
| // the shelf app button. Context menus generally capture all events, but |
| // shelf app buttons' context menu redirect gesture events to the hotseat |
| // widget so shelf app button can continue handling drag events. |
| // See also HotseatWidget::OnGestureEvent(). |
| views::View* item_with_context_menu = |
| scrollable_shelf_view_->shelf_view()->GetShelfItemViewWithContextMenu(); |
| if (item_with_context_menu) |
| return item_with_context_menu; |
| return views::ViewTargeterDelegate::TargetForRect(root, rect); |
| } |
| |
| // Initializes the view. |
| void Init(ScrollableShelfView* scrollable_shelf_view, |
| HotseatWidget* hotseat_widget); |
| |
| // Updates the hotseat background. |
| void UpdateTranslucentBackground(); |
| |
| void SetTranslucentBackground(const gfx::Rect& translucent_background_bounds); |
| |
| // Sets whether the background should be blurred as requested by the argument, |
| // unless the feature flag is disabled or |disable_blur_for_animations_| is |
| // true, in which case this disables background blur. |
| void SetBackgroundBlur(bool enable_blur); |
| |
| // HotseatTransitionAnimator::Observer: |
| void OnHotseatTransitionAnimationWillStart(HotseatState from_state, |
| HotseatState to_state) override; |
| void OnHotseatTransitionAnimationEnded(HotseatState from_state, |
| HotseatState to_state) override; |
| void OnHotseatTransitionAnimationAborted() override; |
| |
| // views::View: |
| void OnThemeChanged() override; |
| |
| // views::WidgetDelegateView: |
| bool CanActivate() const override; |
| |
| // OverviewObserver: |
| void OnOverviewModeWillStart() override; |
| void OnOverviewModeEndingAnimationComplete(bool canceled) override; |
| |
| // WallpaperControllerObserver: |
| void OnWallpaperColorsChanged() override; |
| |
| void set_focus_cycler(FocusCycler* focus_cycler) { |
| focus_cycler_ = focus_cycler; |
| } |
| |
| int background_blur() const { |
| return translucent_background_->layer()->background_blur(); |
| } |
| |
| bool is_translucent_background_visible_for_test() { |
| return translucent_background_->layer()->GetTargetVisibility(); |
| } |
| |
| private: |
| FocusCycler* focus_cycler_ = nullptr; |
| // A background layer that may be visible depending on HotseatState. |
| views::View* translucent_background_ = nullptr; |
| ScrollableShelfView* scrollable_shelf_view_ = nullptr; // unowned. |
| HotseatWidget* hotseat_widget_ = nullptr; // unowned. |
| // Blur is disabled during animations to improve performance. |
| int blur_lock_ = 0; |
| |
| // The most recent color that the |translucent_background_| has been animated |
| // to. |
| SkColor target_color_ = SK_ColorTRANSPARENT; |
| |
| std::unique_ptr<SystemShadow> shadow_; |
| }; |
| |
| HotseatWidget::DelegateView::~DelegateView() { |
| WallpaperControllerImpl* wallpaper_controller = |
| Shell::Get()->wallpaper_controller(); |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| if (wallpaper_controller) |
| wallpaper_controller->RemoveObserver(this); |
| if (overview_controller) |
| overview_controller->RemoveObserver(this); |
| } |
| |
| void HotseatWidget::DelegateView::Init( |
| ScrollableShelfView* scrollable_shelf_view, |
| HotseatWidget* hotseat_widget) { |
| hotseat_widget_ = hotseat_widget; |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| |
| WallpaperControllerImpl* wallpaper_controller = |
| Shell::Get()->wallpaper_controller(); |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| if (wallpaper_controller) |
| wallpaper_controller->AddObserver(this); |
| if (overview_controller) { |
| overview_controller->AddObserver(this); |
| if (overview_controller->InOverviewSession()) |
| ++blur_lock_; |
| } |
| DCHECK(scrollable_shelf_view); |
| scrollable_shelf_view_ = scrollable_shelf_view; |
| |
| // A container view added here is to prevent the `translucent_background_` |
| // being stretched by the fill layout. |
| auto* background_container_view = |
| AddChildViewAt(std::make_unique<views::View>(), 0); |
| background_container_view->SetEnabled(false); |
| translucent_background_ = |
| background_container_view->AddChildView(std::make_unique<views::View>()); |
| if (features::IsDarkLightModeEnabled()) { |
| translucent_background_->SetPaintToLayer(); |
| translucent_background_->layer()->SetFillsBoundsOpaquely(false); |
| } else { |
| translucent_background_->SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| } |
| translucent_background_->layer()->SetName("hotseat/Background"); |
| |
| // Create a shadow and stack at the bottom. |
| shadow_ = SystemShadow::CreateShadowOnTextureLayer( |
| SystemShadow::Type::kElevation12); |
| auto* parent_layer = translucent_background_->layer()->parent(); |
| auto* shadow_layer = shadow_->GetLayer(); |
| parent_layer->Add(shadow_layer); |
| parent_layer->StackAtBottom(shadow_layer); |
| } |
| |
| void HotseatWidget::DelegateView::UpdateTranslucentBackground() { |
| if (!HotseatWidget::ShouldShowHotseatBackground()) { |
| translucent_background_->SetVisible(false); |
| SetBackgroundBlur(false); |
| shadow_->GetLayer()->SetVisible(false); |
| return; |
| } |
| |
| DCHECK(scrollable_shelf_view_); |
| SetTranslucentBackground( |
| scrollable_shelf_view_->GetHotseatBackgroundBounds()); |
| |
| // Hide the shadow when home launcher is showing in tablet mode. |
| if (hotseat_widget_->state() == HotseatState::kShownHomeLauncher) { |
| shadow_->GetLayer()->SetVisible(false); |
| return; |
| } |
| |
| // Update the shadow content bounds and corner radius. |
| shadow_->GetLayer()->SetVisible(true); |
| gfx::Rect background_bounds = translucent_background_->bounds(); |
| shadow_->SetRoundedCornerRadius(background_bounds.height() / 2); |
| shadow_->SetContentBounds(background_bounds); |
| } |
| |
| void HotseatWidget::DelegateView::SetTranslucentBackground( |
| const gfx::Rect& background_bounds) { |
| DCHECK(HotseatWidget::ShouldShowHotseatBackground()); |
| |
| translucent_background_->SetVisible(true); |
| SetBackgroundBlur(/*enable_blur=*/true); |
| |
| auto* animator = translucent_background_->layer()->GetAnimator(); |
| |
| absl::optional<ui::AnimationThroughputReporter> reporter; |
| if (hotseat_widget_ && hotseat_widget_->state() != HotseatState::kNone) { |
| reporter.emplace(animator, |
| hotseat_widget_->GetTranslucentBackgroundReportCallback()); |
| } |
| |
| if (ShelfConfig::Get()->GetDefaultShelfColor() != target_color_) { |
| ui::ScopedLayerAnimationSettings color_animation_setter(animator); |
| DoScopedAnimationSetting(&color_animation_setter); |
| target_color_ = ShelfConfig::Get()->GetDefaultShelfColor(); |
| if (features::IsDarkLightModeEnabled()) { |
| translucent_background_->SetBackground( |
| views::CreateSolidBackground(target_color_)); |
| } else { |
| translucent_background_->layer()->SetColor(target_color_); |
| } |
| } |
| |
| // Animate the bounds change if there's a change of width (for instance when |
| // dragging an app into, or out of, the shelf) and meanwhile scrollable |
| // shelf's bounds does not update at the same time. |
| const bool animate_bounds = |
| background_bounds.width() != translucent_background_->bounds().width() && |
| (scrollable_shelf_view_ && |
| !scrollable_shelf_view_->NeedUpdateToTargetBounds()); |
| absl::optional<ui::ScopedLayerAnimationSettings> bounds_animation_setter; |
| if (animate_bounds) { |
| bounds_animation_setter.emplace(animator); |
| DoScopedAnimationSetting(&bounds_animation_setter.value()); |
| } |
| |
| const float radius = hotseat_widget_->GetHotseatSize() / 2.0f; |
| gfx::RoundedCornersF rounded_corners = {radius, radius, radius, radius}; |
| if (translucent_background_->layer()->rounded_corner_radii() != |
| rounded_corners) { |
| translucent_background_->layer()->SetRoundedCornerRadius(rounded_corners); |
| if (features::IsDarkLightModeEnabled()) { |
| translucent_background_->SetBorder( |
| std::make_unique<views::HighlightBorder>( |
| radius, views::HighlightBorder::Type::kHighlightBorder1, |
| /*use_light_colors=*/!features::IsDarkLightModeEnabled())); |
| } |
| } |
| |
| const gfx::Rect mirrored_bounds = GetMirroredRect(background_bounds); |
| if (translucent_background_->layer()->GetTargetBounds() != mirrored_bounds) |
| translucent_background_->SetBoundsRect(mirrored_bounds); |
| } |
| |
| void HotseatWidget::DelegateView::SetBackgroundBlur(bool enable_blur) { |
| if (!features::IsBackgroundBlurEnabled() || blur_lock_ > 0) |
| return; |
| |
| const int blur_radius = |
| enable_blur ? ShelfConfig::Get()->shelf_blur_radius() : 0; |
| if (translucent_background_->layer()->background_blur() != blur_radius) |
| translucent_background_->layer()->SetBackgroundBlur(blur_radius); |
| } |
| |
| void HotseatWidget::DelegateView::OnHotseatTransitionAnimationWillStart( |
| HotseatState from_state, |
| HotseatState to_state) { |
| DCHECK_LE(blur_lock_, 2); |
| |
| SetBackgroundBlur(false); |
| ++blur_lock_; |
| } |
| |
| void HotseatWidget::DelegateView::OnHotseatTransitionAnimationEnded( |
| HotseatState from_state, |
| HotseatState to_state) { |
| DCHECK_GT(blur_lock_, 0); |
| |
| --blur_lock_; |
| SetBackgroundBlur(true); |
| } |
| |
| void HotseatWidget::DelegateView::OnHotseatTransitionAnimationAborted() { |
| DCHECK_GT(blur_lock_, 0); |
| |
| --blur_lock_; |
| } |
| |
| void HotseatWidget::DelegateView::OnThemeChanged() { |
| views::WidgetDelegateView::OnThemeChanged(); |
| |
| // Only update the background when the `scrollable_shelf_view_` is |
| // initialized. |
| if (scrollable_shelf_view_) |
| UpdateTranslucentBackground(); |
| } |
| |
| bool HotseatWidget::DelegateView::CanActivate() const { |
| // We don't want mouse clicks to activate us, but we need to allow |
| // activation when the user is using the keyboard (FocusCycler). |
| return focus_cycler_ && focus_cycler_->widget_activating() == GetWidget(); |
| } |
| |
| void HotseatWidget::DelegateView::OnOverviewModeWillStart() { |
| DCHECK_LE(blur_lock_, 2); |
| |
| SetBackgroundBlur(false); |
| ++blur_lock_; |
| } |
| |
| void HotseatWidget::DelegateView::OnOverviewModeEndingAnimationComplete( |
| bool canceled) { |
| DCHECK_GT(blur_lock_, 0); |
| |
| --blur_lock_; |
| SetBackgroundBlur(true); |
| } |
| |
| void HotseatWidget::DelegateView::OnWallpaperColorsChanged() { |
| UpdateTranslucentBackground(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ScopedInStateTransition |
| |
| HotseatWidget::ScopedInStateTransition::ScopedInStateTransition( |
| HotseatWidget* hotseat_widget, |
| HotseatState old_state, |
| HotseatState target_state) |
| : hotseat_widget_(hotseat_widget) { |
| hotseat_widget_->state_transition_in_progress_ = |
| CalculateHotseatStateTransition(old_state, target_state); |
| } |
| |
| HotseatWidget::ScopedInStateTransition::~ScopedInStateTransition() { |
| hotseat_widget_->state_transition_in_progress_.reset(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // HotseatWidget |
| |
| HotseatWidget::HotseatWidget() : delegate_view_(new DelegateView()) { |
| ShelfConfig::Get()->AddObserver(this); |
| } |
| |
| HotseatWidget::~HotseatWidget() { |
| ui::LayerAnimator* hotseat_layer_animator = |
| GetNativeView()->layer()->GetAnimator(); |
| if (hotseat_layer_animator->is_animating()) |
| hotseat_layer_animator->AbortAllAnimations(); |
| |
| ShelfConfig::Get()->RemoveObserver(this); |
| shelf_->shelf_widget()->hotseat_transition_animator()->RemoveObserver( |
| delegate_view_); |
| // Remove ScrollableShelfView to avoid any children accessing NativeWidget |
| // after its destruction in ~Widget() before RootView clears. |
| // TODO(pbos): This is defensive, consider having children observe |
| // destruction and/or check the result of GetNativeWidget() and others. |
| GetContentsView()->RemoveChildViewT(scrollable_shelf_view_); |
| } |
| |
| bool HotseatWidget::ShouldShowHotseatBackground() { |
| return Shell::Get()->tablet_mode_controller() && |
| Shell::Get()->tablet_mode_controller()->InTabletMode(); |
| } |
| |
| void HotseatWidget::Initialize(aura::Window* container, Shelf* shelf) { |
| DCHECK(container); |
| DCHECK(shelf); |
| shelf_ = shelf; |
| views::Widget::InitParams params( |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| params.name = "HotseatWidget"; |
| params.delegate = delegate_view_; |
| params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.parent = container; |
| params.layer_type = ui::LAYER_NOT_DRAWN; |
| Init(std::move(params)); |
| set_focus_on_creation(false); |
| |
| scrollable_shelf_view_ = GetContentsView()->AddChildView( |
| std::make_unique<ScrollableShelfView>(ShelfModel::Get(), shelf)); |
| delegate_view_->Init(scrollable_shelf_view(), this); |
| delegate_view_->SetEnableArrowKeyTraversal(true); |
| |
| // The initialization of scrollable shelf should update the translucent |
| // background which is stored in |delegate_view_|. So initializes |
| // |scrollable_shelf_view_| after |delegate_view_|. |
| scrollable_shelf_view_->Init(); |
| } |
| |
| void HotseatWidget::OnHotseatTransitionAnimatorCreated( |
| HotseatTransitionAnimator* animator) { |
| shelf_->shelf_widget()->hotseat_transition_animator()->AddObserver( |
| delegate_view_); |
| } |
| |
| void HotseatWidget::OnMouseEvent(ui::MouseEvent* event) { |
| if (event->type() == ui::ET_MOUSE_PRESSED) |
| keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser(); |
| views::Widget::OnMouseEvent(event); |
| } |
| |
| void HotseatWidget::OnGestureEvent(ui::GestureEvent* event) { |
| if (event->type() == ui::ET_GESTURE_TAP_DOWN) |
| keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser(); |
| |
| // Context menus for shelf app button forward gesture events to hotseat |
| // widget, so the shelf app button can continue handling drag even after the |
| // context menu starts capturing events. Ignore events not interesting to the |
| // shelf app button in this state. |
| ShelfAppButton* item_with_context_menu = |
| GetShelfView()->GetShelfItemViewWithContextMenu(); |
| if (item_with_context_menu && |
| !ShelfAppButton::ShouldHandleEventFromContextMenu(event)) { |
| event->SetHandled(); |
| return; |
| } |
| |
| if (!event->handled()) |
| views::Widget::OnGestureEvent(event); |
| |
| // Ensure that the app button's drag state gets cleared on gesture end even if |
| // the event doesn't get delivered to the app button. |
| if (item_with_context_menu && event->type() == ui::ET_GESTURE_END) |
| item_with_context_menu->ClearDragStateOnGestureEnd(); |
| } |
| |
| bool HotseatWidget::OnNativeWidgetActivationChanged(bool active) { |
| if (!Widget::OnNativeWidgetActivationChanged(active)) |
| return false; |
| |
| scrollable_shelf_view_->OnFocusRingActivationChanged(active); |
| return true; |
| } |
| |
| void HotseatWidget::OnShelfConfigUpdated() { |
| set_manually_extended(false); |
| } |
| |
| bool HotseatWidget::IsExtended() const { |
| DCHECK(GetShelfView()->shelf()->IsHorizontalAlignment()); |
| const int extended_bottom = |
| display::Screen::GetScreen() |
| ->GetDisplayNearestView(GetShelfView()->GetWidget()->GetNativeView()) |
| .bounds() |
| .bottom() - |
| (ShelfConfig::Get()->shelf_size() + |
| ShelfConfig::Get()->hotseat_bottom_padding()); |
| return GetWindowBoundsInScreen().bottom() == extended_bottom; |
| } |
| |
| void HotseatWidget::FocusFirstOrLastFocusableChild(bool last) { |
| GetShelfView()->FindFirstOrLastFocusableChild(last)->RequestFocus(); |
| } |
| |
| void HotseatWidget::OnTabletModeChanged() { |
| GetShelfView()->OnTabletModeChanged(); |
| } |
| |
| float HotseatWidget::CalculateShelfViewOpacity() const { |
| const float target_opacity = |
| GetShelfView()->shelf()->shelf_layout_manager()->GetOpacity(); |
| // Hotseat's shelf view should not be dimmed if hotseat is kExtended. |
| return (state() == HotseatState::kExtended) ? 1.0f : target_opacity; |
| } |
| |
| void HotseatWidget::UpdateTranslucentBackground() { |
| delegate_view_->UpdateTranslucentBackground(); |
| } |
| |
| int HotseatWidget::CalculateHotseatYInScreen( |
| HotseatState hotseat_target_state) const { |
| DCHECK(shelf_->IsHorizontalAlignment()); |
| int hotseat_distance_from_bottom_of_display = 0; |
| const int hotseat_size = GetHotseatSize(); |
| switch (hotseat_target_state) { |
| case HotseatState::kShownClamshell: |
| hotseat_distance_from_bottom_of_display = hotseat_size; |
| break; |
| case HotseatState::kShownHomeLauncher: |
| // When the hotseat state is HotseatState::kShownHomeLauncher, the home |
| // launcher is showing in tablet mode. Elevate the hotseat a few px to |
| // match the navigation and status area. |
| hotseat_distance_from_bottom_of_display = |
| hotseat_size + ShelfConfig::Get()->hotseat_bottom_padding(); |
| break; |
| case HotseatState::kHidden: |
| // Show the hotseat offscreen. |
| hotseat_distance_from_bottom_of_display = 0; |
| break; |
| case HotseatState::kExtended: |
| // Show the hotseat at its extended position. |
| hotseat_distance_from_bottom_of_display = |
| ShelfConfig::Get()->in_app_shelf_size() + |
| ShelfConfig::Get()->hotseat_bottom_padding() + hotseat_size; |
| break; |
| case HotseatState::kNone: |
| NOTREACHED(); |
| } |
| const int target_shelf_size = |
| shelf_->shelf_widget()->GetTargetBounds().size().height(); |
| const int hotseat_y_in_shelf = |
| -(hotseat_distance_from_bottom_of_display - target_shelf_size); |
| const int shelf_y = shelf_->shelf_widget()->GetTargetBounds().y(); |
| return hotseat_y_in_shelf + shelf_y; |
| } |
| |
| gfx::Size HotseatWidget::CalculateTargetBoundsSize( |
| HotseatState hotseat_target_state) const { |
| const gfx::Rect shelf_bounds = shelf_->shelf_widget()->GetTargetBounds(); |
| |
| // |hotseat_size| is the height in horizontal alignment or the width in |
| // vertical alignment. |
| const int hotseat_size = GetHotseatSize(); |
| |
| if (hotseat_target_state != HotseatState::kShownHomeLauncher && |
| hotseat_target_state != HotseatState::kShownClamshell) { |
| DCHECK(shelf_->IsHorizontalAlignment()); |
| // Give the hotseat more space if it is shown outside of the shelf. |
| return gfx::Size(shelf_bounds.width(), hotseat_size); |
| } |
| |
| const gfx::Size status_size = |
| shelf_->status_area_widget()->GetTargetBounds().size(); |
| const gfx::Rect nav_bounds = shelf_->navigation_widget()->GetVisibleBounds(); |
| |
| // The navigation widget has extra padding on the hotseat side, to center the |
| // buttons inside of it. Make sure to get the extra nav widget padding and |
| // take it into account when calculating the hotseat size. |
| const int nav_widget_padding = |
| nav_bounds.size().IsEmpty() |
| ? 0 |
| : ShelfConfig::Get()->control_button_edge_spacing( |
| true /* is_primary_axis_edge */); |
| |
| // The minimum gap between hotseat widget and other shelf components including |
| // the status area widget and shelf navigation widget (or the edge of display, |
| // if the shelf navigation widget does not show). |
| const int group_margin = ShelfConfig::Get()->GetAppIconGroupMargin(); |
| |
| if (shelf_->IsHorizontalAlignment()) { |
| const int width = shelf_bounds.width() - nav_bounds.size().width() + |
| nav_widget_padding - 2 * group_margin - |
| status_size.width(); |
| return gfx::Size(width, hotseat_size); |
| } |
| |
| const int height = shelf_bounds.height() - nav_bounds.size().height() + |
| nav_widget_padding - 2 * group_margin - |
| status_size.height(); |
| return gfx::Size(hotseat_size, height); |
| } |
| |
| void HotseatWidget::CalculateTargetBounds() { |
| ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager(); |
| const HotseatState hotseat_target_state = |
| layout_manager->CalculateHotseatState(layout_manager->visibility_state(), |
| layout_manager->auto_hide_state()); |
| const gfx::Size hotseat_target_size = |
| CalculateTargetBoundsSize(hotseat_target_state); |
| |
| if (hotseat_target_state == HotseatState::kShownHomeLauncher) { |
| target_size_for_shown_state_ = hotseat_target_size; |
| } else { |
| target_size_for_shown_state_ = |
| CalculateTargetBoundsSize(HotseatState::kShownHomeLauncher); |
| } |
| |
| const gfx::Rect shelf_bounds = shelf_->shelf_widget()->GetTargetBounds(); |
| const gfx::Rect status_area_bounds = |
| shelf_->status_area_widget()->GetTargetBounds(); |
| |
| // The minimum gap between hotseat widget and other shelf components including |
| // the status area widget and shelf navigation widget (or the edge of display, |
| // if the shelf navigation widget does not show). |
| const int group_margin = ShelfConfig::Get()->GetAppIconGroupMargin(); |
| |
| gfx::Point hotseat_origin; |
| if (shelf_->IsHorizontalAlignment()) { |
| int hotseat_x; |
| if (hotseat_target_state != HotseatState::kShownHomeLauncher && |
| hotseat_target_state != HotseatState::kShownClamshell) { |
| hotseat_x = shelf_bounds.x(); |
| } else { |
| hotseat_x = base::i18n::IsRTL() |
| ? status_area_bounds.right() + group_margin |
| : status_area_bounds.x() - group_margin - |
| hotseat_target_size.width(); |
| } |
| |
| hotseat_origin = |
| gfx::Point(hotseat_x, CalculateHotseatYInScreen(hotseat_target_state)); |
| } else { |
| hotseat_origin = |
| gfx::Point(shelf_bounds.x(), status_area_bounds.y() - group_margin - |
| hotseat_target_size.height()); |
| } |
| |
| target_bounds_ = gfx::Rect(hotseat_origin, hotseat_target_size); |
| |
| // Check whether |target_bounds_| will change the state of app scaling. If |
| // so, update |target_bounds_| here to avoid re-layout later. |
| MaybeAdjustTargetBoundsForAppScaling(hotseat_target_state); |
| } |
| |
| gfx::Rect HotseatWidget::GetTargetBounds() const { |
| return target_bounds_; |
| } |
| |
| void HotseatWidget::UpdateLayout(bool animate) { |
| const LayoutInputs new_layout_inputs = GetLayoutInputs(); |
| if (layout_inputs_ == new_layout_inputs) |
| return; |
| |
| // Never show this widget outside of an active session. |
| if (!new_layout_inputs.is_active_session_state) |
| Hide(); |
| |
| ui::Layer* shelf_view_layer = GetShelfView()->layer(); |
| { |
| ui::ScopedLayerAnimationSettings animation_setter( |
| shelf_view_layer->GetAnimator()); |
| animation_setter.SetTransitionDuration( |
| animate ? ShelfConfig::Get()->shelf_animation_duration() |
| : base::Milliseconds(0)); |
| animation_setter.SetTweenType(gfx::Tween::EASE_OUT); |
| animation_setter.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| |
| absl::optional<ui::AnimationThroughputReporter> reporter; |
| if (animate && state_ != HotseatState::kNone) { |
| reporter.emplace(animation_setter.GetAnimator(), |
| shelf_->GetHotseatTransitionReportCallback(state_)); |
| } |
| |
| shelf_view_layer->SetOpacity(new_layout_inputs.shelf_view_opacity); |
| } |
| |
| // If shelf view is invisible, the hotseat should be as well. Otherwise the |
| // hotseat opacity should be 1.0f to preserve background blur. |
| const double target_opacity = |
| (new_layout_inputs.shelf_view_opacity == 0.f ? 0.f : 1.f); |
| const gfx::Rect& target_bounds = new_layout_inputs.bounds; |
| |
| if (animate) { |
| LayoutHotseatByAnimation(target_opacity, target_bounds); |
| } else { |
| ui::Layer* hotseat_layer = GetNativeView()->layer(); |
| |
| // If the running bounds animation is not aborted, it will be interrupted |
| // and set hotseat widget with the old target bounds which may differ from |
| // |target_bounds| greatly and bring DCHECK errors. For example, |
| // if hotseat animation is interrupted by the bounds setting triggered by |
| // shelf alignment update, hotseat will be caught in an intermediate state |
| // where the shelf alignment is new and the hotseat bounds are old. |
| hotseat_layer->GetAnimator()->AbortAllAnimations(); |
| |
| hotseat_layer->SetOpacity(target_opacity); |
| hotseat_layer->SetTransform(gfx::Transform()); |
| SetBounds(target_bounds); |
| } |
| |
| layout_inputs_ = new_layout_inputs; |
| delegate_view_->UpdateTranslucentBackground(); |
| |
| // Setting visibility during an animation causes the visibility property to |
| // animate. Set the visibility property without an animation. |
| if (new_layout_inputs.shelf_view_opacity != 0.0f && |
| new_layout_inputs.is_active_session_state) { |
| ShowInactive(); |
| } |
| } |
| |
| void HotseatWidget::UpdateTargetBoundsForGesture(int shelf_position) { |
| if (shelf_->IsHorizontalAlignment()) |
| target_bounds_.set_y(shelf_position); |
| else |
| target_bounds_.set_x(shelf_position); |
| } |
| |
| gfx::Size HotseatWidget::GetTranslucentBackgroundSize() const { |
| DCHECK(scrollable_shelf_view_); |
| return scrollable_shelf_view_->GetHotseatBackgroundBounds().size(); |
| } |
| |
| void HotseatWidget::SetFocusCycler(FocusCycler* focus_cycler) { |
| delegate_view_->set_focus_cycler(focus_cycler); |
| if (focus_cycler) |
| focus_cycler->AddWidget(this); |
| } |
| |
| ShelfView* HotseatWidget::GetShelfView() { |
| DCHECK(scrollable_shelf_view_); |
| return scrollable_shelf_view_->shelf_view(); |
| } |
| |
| int HotseatWidget::GetHotseatSize() const { |
| return ShelfConfig::Get()->GetShelfButtonSize(target_hotseat_density_); |
| } |
| |
| int HotseatWidget::GetHotseatFullDragAmount() const { |
| ShelfConfig* shelf_config = ShelfConfig::Get(); |
| return shelf_config->shelf_size() + shelf_config->hotseat_bottom_padding() + |
| GetHotseatSize(); |
| } |
| |
| bool HotseatWidget::UpdateTargetHotseatDensityIfNeeded() { |
| if (CalculateTargetHotseatDensity() == target_hotseat_density_) { |
| return false; |
| } |
| |
| shelf_->shelf_layout_manager()->LayoutShelf(/*animate=*/true); |
| return true; |
| } |
| |
| int HotseatWidget::GetHotseatBackgroundBlurForTest() const { |
| return delegate_view_->background_blur(); |
| } |
| |
| bool HotseatWidget::GetIsTranslucentBackgroundVisibleForTest() const { |
| return delegate_view_->is_translucent_background_visible_for_test(); |
| } |
| |
| bool HotseatWidget::IsShowingShelfMenu() const { |
| return GetShelfView()->IsShowingMenu(); |
| } |
| |
| bool HotseatWidget::EventTargetsShelfView(const ui::LocatedEvent& event) const { |
| DCHECK_EQ(event.target(), GetNativeWindow()); |
| gfx::Point location_in_shelf_view = event.location(); |
| views::View::ConvertPointFromWidget(scrollable_shelf_view_, |
| &location_in_shelf_view); |
| return scrollable_shelf_view_->GetHotseatBackgroundBounds().Contains( |
| location_in_shelf_view); |
| } |
| |
| const ShelfView* HotseatWidget::GetShelfView() const { |
| return const_cast<const ShelfView*>( |
| const_cast<HotseatWidget*>(this)->GetShelfView()); |
| } |
| |
| metrics_util::ReportCallback |
| HotseatWidget::GetTranslucentBackgroundReportCallback() { |
| return shelf_->GetTranslucentBackgroundReportCallback(state_); |
| } |
| |
| void HotseatWidget::SetState(HotseatState state) { |
| if (state_ == state) |
| return; |
| |
| state_ = state; |
| |
| // If the hotseat is not extended we can use the normal targeting as the |
| // hidden parts of the hotseat will not block non-shelf items from taking |
| if (state == HotseatState::kExtended) { |
| hotseat_window_targeter_ = std::make_unique<aura::ScopedWindowTargeter>( |
| GetNativeWindow(), std::make_unique<HotseatWindowTargeter>(this)); |
| } else { |
| hotseat_window_targeter_.reset(); |
| } |
| } |
| |
| ui::Layer* HotseatWidget::GetLayerForNudgeAnimation() { |
| return delegate_view_->layer(); |
| } |
| |
| HotseatWidget::LayoutInputs HotseatWidget::GetLayoutInputs() const { |
| const ShelfLayoutManager* layout_manager = shelf_->shelf_layout_manager(); |
| return {target_bounds_, CalculateShelfViewOpacity(), |
| layout_manager->is_active_session_state()}; |
| } |
| |
| void HotseatWidget::MaybeAdjustTargetBoundsForAppScaling( |
| HotseatState hotseat_target_state) { |
| // Return early if app scaling state does not change. |
| HotseatDensity new_target_hotseat_density = CalculateTargetHotseatDensity(); |
| if (new_target_hotseat_density == target_hotseat_density_) |
| return; |
| |
| target_hotseat_density_ = new_target_hotseat_density; |
| |
| // Update app icons of shelf view. |
| scrollable_shelf_view_->shelf_view()->OnShelfConfigUpdated(); |
| |
| const gfx::Point adjusted_hotseat_origin = gfx::Point( |
| target_bounds_.x(), CalculateHotseatYInScreen(hotseat_target_state)); |
| target_bounds_ = |
| gfx::Rect(adjusted_hotseat_origin, |
| gfx::Size(target_bounds_.width(), GetHotseatSize())); |
| } |
| |
| HotseatDensity HotseatWidget::CalculateTargetHotseatDensity() const { |
| // App scaling is only applied to the standard shelf. So the hotseat density |
| // should not update in dense shelf. |
| if (ShelfConfig::Get()->is_dense()) |
| return target_hotseat_density_; |
| |
| // TODO(crbug.com/1081476): Currently the scaling animation of hotseat bounds |
| // and that of shelf icons do not synchronize due to performance issue. As a |
| // result, shelf scaling is not applied to the hotseat state transition, such |
| // as the transition from the home launcher state to the extended state. |
| // Hotseat density relies on the hotseat bounds in the home launcher state |
| // instead of the current hotseat state. |
| |
| // Try candidate button sizes in decreasing order. If shelf buttons in one |
| // size can show without scrolling, return the density type corresponding to |
| // that particular size; if no candidate size can make it, return |
| // HotseatDensity::kDense. |
| const std::vector<HotseatDensity> kCandidates = {HotseatDensity::kNormal, |
| HotseatDensity::kSemiDense}; |
| for (const auto& candidate : kCandidates) { |
| if (!scrollable_shelf_view_->RequiresScrollingForItemSize( |
| target_size_for_shown_state_, |
| ShelfConfig::Get()->GetShelfButtonSize(candidate))) { |
| return candidate; |
| } |
| } |
| return HotseatDensity::kDense; |
| } |
| |
| void HotseatWidget::LayoutHotseatByAnimation(double target_opacity, |
| const gfx::Rect& target_bounds) { |
| ui::Layer* hotseat_layer = GetNativeView()->layer(); |
| |
| // Bounds animations do not take transforms into account, but animations |
| // between hidden and extended state use transform to animate. Clear any |
| // transform that may have been set by the previous animation, and update |
| // current bounds to match it. |
| gfx::RectF current_bounds_f(GetNativeView()->GetBoundsInScreen()); |
| hotseat_layer->transform().TransformRect(¤t_bounds_f); |
| gfx::Rect current_bounds = gfx::ToEnclosingRect(current_bounds_f); |
| |
| // If the bounds size has not changed, set the target bounds immediately, and |
| // animate using transform. |
| // Avoid transforms if the transition has a horizontal component - logic for |
| // setting scrollable shelf padding depends on the horizontal in screen |
| // bounds, and setting horizontal translation would interfere with that. Note |
| // that generally, if horizontal widget position changes, so will its width. |
| const bool animate_transform = |
| target_bounds.size() == current_bounds.size() && |
| target_bounds.x() == current_bounds.x(); |
| if (animate_transform) { |
| SetBounds(target_bounds); |
| |
| gfx::Transform initial_transform; |
| initial_transform.Translate(current_bounds.origin() - |
| target_bounds.origin()); |
| hotseat_layer->SetTransform(initial_transform); |
| } else { |
| hotseat_layer->SetTransform(gfx::Transform()); |
| SetBounds(current_bounds); |
| } |
| |
| ui::ScopedLayerAnimationSettings animation_setter( |
| hotseat_layer->GetAnimator()); |
| animation_setter.SetTransitionDuration( |
| ShelfConfig::Get()->shelf_animation_duration()); |
| animation_setter.SetTweenType(gfx::Tween::EASE_OUT); |
| animation_setter.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| |
| absl::optional<ui::AnimationThroughputReporter> reporter; |
| if (state_ != HotseatState::kNone) { |
| reporter.emplace(animation_setter.GetAnimator(), |
| shelf_->GetHotseatTransitionReportCallback(state_)); |
| } |
| |
| if (animate_transform) { |
| hotseat_layer->SetOpacity(target_opacity); |
| hotseat_layer->SetTransform(gfx::Transform()); |
| return; |
| } |
| |
| if (!state_transition_in_progress_.has_value()) { |
| // Hotseat animation is not triggered by the update in |state_|. So apply |
| // the normal bounds animation. |
| StartNormalBoundsAnimation(target_opacity, target_bounds); |
| return; |
| } |
| |
| if (HasSpecialAnimation(*state_transition_in_progress_)) { |
| StartHotseatTransitionAnimation(*state_transition_in_progress_, |
| target_opacity, target_bounds); |
| } else { |
| StartNormalBoundsAnimation(target_opacity, target_bounds); |
| } |
| } |
| |
| void HotseatWidget::StartHotseatTransitionAnimation( |
| StateTransition state_transition, |
| double target_opacity, |
| const gfx::Rect& target_bounds) { |
| ui::Layer* hotseat_layer = GetNativeView()->layer(); |
| std::unique_ptr<ui::LayerAnimationElement> animation_elements; |
| switch (state_transition) { |
| case StateTransition::kHomeLauncherAndExtended: |
| animation_elements = std::make_unique<HomeAndExtendedTransitionAnimation>( |
| target_bounds, target_opacity, hotseat_layer, |
| /*hotseat_widget=*/this); |
| break; |
| case StateTransition::kHomeLauncherAndHidden: |
| animation_elements = std::make_unique<HomeAndHiddenTransitionAnimation>( |
| target_bounds, target_opacity, hotseat_layer, |
| /*hotseat_widget=*/this); |
| break; |
| case StateTransition::kHiddenAndExtended: |
| case StateTransition::kOther: |
| NOTREACHED(); |
| } |
| |
| auto* sequence = |
| new ui::LayerAnimationSequence(std::move(animation_elements)); |
| hotseat_layer->GetAnimator()->StartAnimation(sequence); |
| } |
| |
| void HotseatWidget::StartNormalBoundsAnimation(double target_opacity, |
| const gfx::Rect& target_bounds) { |
| GetNativeView()->layer()->SetOpacity(target_opacity); |
| SetBounds(target_bounds); |
| } |
| |
| } // namespace ash |