| // Copyright (c) 2012 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/system/tray/tray_background_view.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "ash/focus_cycler.h" |
| #include "ash/login/ui/lock_screen.h" |
| #include "ash/public/cpp/session/session_observer.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/login_shelf_view.h" |
| #include "ash/shelf/shelf_focus_cycler.h" |
| #include "ash/shelf/shelf_layout_manager.h" |
| #include "ash/shelf/shelf_navigation_widget.h" |
| #include "ash/shelf/shelf_widget.h" |
| #include "ash/shell.h" |
| #include "ash/style/style_util.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/system/status_area_widget_delegate.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_container.h" |
| #include "ash/system/tray/tray_event_filter.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/bind.h" |
| #include "base/scoped_multi_source_observation.h" |
| #include "base/time/time.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/models/menu_model.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/transform.h" |
| #include "ui/gfx/interpolated_transform.h" |
| #include "ui/gfx/scoped_canvas.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/animation/animation_builder.h" |
| #include "ui/views/animation/ink_drop.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/focus_ring.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/menu/menu_runner.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/painter.h" |
| #include "ui/views/view_class_properties.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/window_animations.h" |
| |
| namespace ash { |
| namespace { |
| |
| const int kAnimationDurationForBubblePopupMs = 200; |
| |
| // Duration of opacity animation for visibility changes. |
| constexpr base::TimeDelta kAnimationDurationForVisibilityMs = |
| base::Milliseconds(250); |
| |
| // Duration of opacity animation for hide animation. |
| constexpr base::TimeDelta kAnimationDurationForHideMs = base::Milliseconds(100); |
| |
| // Bounce animation constants |
| const base::TimeDelta kAnimationDurationForBounceElement = |
| base::Milliseconds(250); |
| const int kAnimationBounceUpDistance = 16; |
| const int kAnimationBounceDownDistance = 8; |
| const float kAnimationBounceScaleFactor = 0.5; |
| |
| // When becoming visible delay the animation so that StatusAreaWidgetDelegate |
| // can animate sibling views out of the position to be occupied by the |
| // TrayBackgroundView. |
| const base::TimeDelta kShowAnimationDelayMs = base::Milliseconds(100); |
| |
| // Switches left and right insets if RTL mode is active. |
| void MirrorInsetsIfNecessary(gfx::Insets* insets) { |
| if (base::i18n::IsRTL()) { |
| insets->Set(insets->top(), insets->right(), insets->bottom(), |
| insets->left()); |
| } |
| } |
| |
| // Returns background insets relative to the contents bounds of the view and |
| // mirrored if RTL mode is active. |
| gfx::Insets GetMirroredBackgroundInsets(bool is_shelf_horizontal) { |
| gfx::Insets insets; |
| // "Primary" is the same direction as the shelf, "secondary" is orthogonal. |
| const int primary_padding = 0; |
| const int secondary_padding = |
| -ash::ShelfConfig::Get()->status_area_hit_region_padding(); |
| |
| if (is_shelf_horizontal) { |
| insets.Set(secondary_padding, primary_padding, secondary_padding, |
| primary_padding + ash::kTraySeparatorWidth); |
| } else { |
| insets.Set(primary_padding, secondary_padding, |
| primary_padding + ash::kTraySeparatorWidth, secondary_padding); |
| } |
| MirrorInsetsIfNecessary(&insets); |
| return insets; |
| } |
| |
| class HighlightPathGenerator : public views::HighlightPathGenerator { |
| public: |
| explicit HighlightPathGenerator(TrayBackgroundView* tray_background_view) |
| : tray_background_view_(tray_background_view), insets_(gfx::Insets()) {} |
| |
| HighlightPathGenerator(TrayBackgroundView* tray_background_view, |
| gfx::Insets insets) |
| : tray_background_view_(tray_background_view), insets_(insets) {} |
| |
| HighlightPathGenerator(const HighlightPathGenerator&) = delete; |
| HighlightPathGenerator& operator=(const HighlightPathGenerator&) = delete; |
| |
| // HighlightPathGenerator: |
| absl::optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override { |
| gfx::RectF bounds(tray_background_view_->GetBackgroundBounds()); |
| bounds.Inset(insets_); |
| return gfx::RRectF(bounds, ShelfConfig::Get()->control_border_radius()); |
| } |
| |
| private: |
| TrayBackgroundView* const tray_background_view_; |
| const gfx::Insets insets_; |
| }; |
| |
| } // namespace |
| |
| // Used to track when the anchor widget changes position on screen so that the |
| // bubble position can be updated. |
| class TrayBackgroundView::TrayWidgetObserver : public views::WidgetObserver { |
| public: |
| explicit TrayWidgetObserver(TrayBackgroundView* host) : host_(host) {} |
| |
| TrayWidgetObserver(const TrayWidgetObserver&) = delete; |
| TrayWidgetObserver& operator=(const TrayWidgetObserver&) = delete; |
| |
| void OnWidgetBoundsChanged(views::Widget* widget, |
| const gfx::Rect& new_bounds) override { |
| host_->AnchorUpdated(); |
| } |
| |
| void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override { |
| host_->AnchorUpdated(); |
| } |
| |
| void Add(views::Widget* widget) { observations_.AddObservation(widget); } |
| |
| private: |
| TrayBackgroundView* host_; |
| base::ScopedMultiSourceObservation<views::Widget, views::WidgetObserver> |
| observations_{this}; |
| }; |
| |
| // Handles `TrayBackgroundView`'s animation on session changed. |
| class TrayBackgroundView::TrayBackgroundViewSessionChangeHandler |
| : public SessionObserver { |
| public: |
| explicit TrayBackgroundViewSessionChangeHandler( |
| TrayBackgroundView* tray_background_view) |
| : tray_(tray_background_view) { |
| DCHECK(tray_); |
| } |
| TrayBackgroundViewSessionChangeHandler( |
| const TrayBackgroundViewSessionChangeHandler&) = delete; |
| TrayBackgroundViewSessionChangeHandler& operator=( |
| const TrayBackgroundViewSessionChangeHandler&) = delete; |
| ~TrayBackgroundViewSessionChangeHandler() override = default; |
| |
| private: // SessionObserver: |
| void OnSessionStateChanged(session_manager::SessionState state) override { |
| DisableShowAnimationInSequence(); |
| } |
| void OnActiveUserSessionChanged(const AccountId& account_id) override { |
| DisableShowAnimationInSequence(); |
| } |
| |
| // Disables the `TrayBackgroundView`'s show animation until all queued tasks |
| // in the current task sequence are run. |
| void DisableShowAnimationInSequence() { |
| base::ScopedClosureRunner callback = tray_->DisableShowAnimation(); |
| base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| callback.Release()); |
| } |
| |
| TrayBackgroundView* const tray_; |
| ScopedSessionObserver session_observer_{this}; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // TrayBackgroundView |
| |
| TrayBackgroundView::TrayBackgroundView(Shelf* shelf) |
| // Note the ink drop style is ignored. |
| : ActionableView(TrayPopupInkDropStyle::FILL_BOUNDS), |
| shelf_(shelf), |
| tray_container_(new TrayContainer(shelf)), |
| is_active_(false), |
| separator_visible_(true), |
| visible_preferred_(false), |
| show_with_virtual_keyboard_(false), |
| show_when_collapsed_(true), |
| widget_observer_(new TrayWidgetObserver(this)), |
| handler_(new TrayBackgroundViewSessionChangeHandler(this)) { |
| DCHECK(shelf_); |
| SetNotifyEnterExitOnChild(true); |
| |
| // Override the settings of inkdrop ripple only since others like Highlight |
| // has been set up in the base class ActionableView. |
| StyleUtil::SetRippleParams(this, GetBackgroundInsets()); |
| views::InkDrop::Get(this)->SetMode( |
| views::InkDropHost::InkDropMode::ON_NO_GESTURE_HANDLER); |
| |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| SetInstallFocusRingOnFocus(true); |
| |
| views::FocusRing* const focus_ring = views::FocusRing::Get(this); |
| focus_ring->SetColor(AshColorProvider::Get()->GetControlsLayerColor( |
| AshColorProvider::ControlsLayerType::kFocusRingColor)); |
| focus_ring->SetPathGenerator(std::make_unique<HighlightPathGenerator>( |
| this, kTrayBackgroundFocusPadding)); |
| SetFocusPainter(nullptr); |
| |
| views::HighlightPathGenerator::Install( |
| this, std::make_unique<HighlightPathGenerator>(this)); |
| |
| AddChildView(tray_container_); |
| |
| tray_event_filter_ = std::make_unique<TrayEventFilter>(); |
| |
| // Use layer color to provide background color. Note that children views |
| // need to have their own layers to be visible. |
| SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| layer()->SetFillsBoundsOpaquely(false); |
| |
| // Start the tray items not visible, because visibility changes are animated. |
| views::View::SetVisible(false); |
| } |
| |
| TrayBackgroundView::~TrayBackgroundView() { |
| Shell::Get()->system_tray_model()->virtual_keyboard()->RemoveObserver(this); |
| widget_observer_.reset(); |
| handler_.reset(); |
| } |
| |
| void TrayBackgroundView::Initialize() { |
| widget_observer_->Add(GetWidget()); |
| Shell::Get()->system_tray_model()->virtual_keyboard()->AddObserver(this); |
| |
| UpdateBackground(); |
| } |
| |
| // static |
| void TrayBackgroundView::InitializeBubbleAnimations( |
| views::Widget* bubble_widget) { |
| aura::Window* window = bubble_widget->GetNativeWindow(); |
| ::wm::SetWindowVisibilityAnimationType( |
| window, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); |
| ::wm::SetWindowVisibilityAnimationTransition(window, ::wm::ANIMATE_HIDE); |
| ::wm::SetWindowVisibilityAnimationDuration( |
| window, base::Milliseconds(kAnimationDurationForBubblePopupMs)); |
| } |
| |
| void TrayBackgroundView::SetVisiblePreferred(bool visible_preferred) { |
| if (visible_preferred_ == visible_preferred) |
| return; |
| visible_preferred_ = visible_preferred; |
| StartVisibilityAnimation(GetEffectiveVisibility()); |
| |
| // We need to update which trays overflow after showing or hiding a tray. |
| // If the hide animation is still playing, we do the `UpdateStatusArea(bool |
| // should_log_visible_pod_count)` when the animation is finished. |
| if (!layer()->GetAnimator()->is_animating() || visible_preferred_) |
| UpdateStatusArea(true /*should_log_visible_pod_count*/); |
| } |
| |
| bool TrayBackgroundView::IsShowingMenu() const { |
| return context_menu_runner_ && context_menu_runner_->IsRunning(); |
| } |
| |
| void TrayBackgroundView::StartVisibilityAnimation(bool visible) { |
| if (visible == layer()->GetTargetVisibility()) |
| return; |
| |
| base::AutoReset<bool> is_starting_animation(&is_starting_animation_, true); |
| |
| if (visible) { |
| views::View::SetVisible(true); |
| // If SetVisible(true) is called while animating to not visible, then |
| // views::View::SetVisible(true) is a no-op. When the previous animation |
| // ends layer->SetVisible(false) is called. To prevent this |
| // layer->SetVisible(true) immediately interrupts the animation of this |
| // property, and keeps the layer visible. |
| layer()->SetVisible(true); |
| |
| // We only show visible animation when `IsShowAnimationEnabled()`. |
| if (IsShowAnimationEnabled()) { |
| if (use_bounce_in_animation_) |
| BounceInAnimation(); |
| else |
| FadeInAnimation(); |
| } else { |
| // The opacity and scale of the `layer()` may have been manipulated, so |
| // reset it before it is shown. |
| layer()->SetOpacity(1.0f); |
| layer()->SetTransform(gfx::Transform()); |
| } |
| } else { |
| HideAnimation(); |
| } |
| } |
| |
| base::ScopedClosureRunner TrayBackgroundView::DisableShowAnimation() { |
| if (layer()->GetAnimator()->is_animating()) |
| layer()->GetAnimator()->StopAnimating(); |
| |
| ++disable_show_animation_count_; |
| if (disable_show_animation_count_ == 1u) |
| OnShouldShowAnimationChanged(false); |
| |
| return base::ScopedClosureRunner(base::BindOnce( |
| [](const base::WeakPtr<TrayBackgroundView>& ptr) { |
| if (ptr) { |
| --ptr->disable_show_animation_count_; |
| if (ptr->IsShowAnimationEnabled()) |
| ptr->OnShouldShowAnimationChanged(true); |
| } |
| }, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void TrayBackgroundView::UpdateStatusArea(bool should_log_visible_pod_count) { |
| auto* status_area_widget = shelf_->GetStatusAreaWidget(); |
| if (status_area_widget) { |
| status_area_widget->UpdateCollapseState(); |
| if (should_log_visible_pod_count) |
| status_area_widget->LogVisiblePodCountMetric(); |
| } |
| } |
| |
| void TrayBackgroundView::OnVisibilityAnimationFinished( |
| bool should_log_visible_pod_count, |
| bool aborted) { |
| if (aborted && is_starting_animation_) |
| return; |
| if (!visible_preferred_) { |
| views::View::SetVisible(false); |
| UpdateStatusArea(should_log_visible_pod_count); |
| } |
| } |
| |
| void TrayBackgroundView::ShowContextMenuForViewImpl( |
| views::View* source, |
| const gfx::Point& point, |
| ui::MenuSourceType source_type) { |
| context_menu_model_ = CreateContextMenuModel(); |
| if (!context_menu_model_) |
| return; |
| |
| const int run_types = views::MenuRunner::USE_TOUCHABLE_LAYOUT | |
| views::MenuRunner::CONTEXT_MENU | |
| views::MenuRunner::FIXED_ANCHOR; |
| context_menu_runner_ = std::make_unique<views::MenuRunner>( |
| context_menu_model_.get(), run_types, |
| base::BindRepeating(&Shelf::UpdateAutoHideState, |
| base::Unretained(shelf_))); |
| views::MenuAnchorPosition anchor; |
| switch (shelf_->alignment()) { |
| case ShelfAlignment::kBottom: |
| case ShelfAlignment::kBottomLocked: |
| anchor = views::MenuAnchorPosition::kBubbleTopRight; |
| break; |
| case ShelfAlignment::kLeft: |
| anchor = views::MenuAnchorPosition::kBubbleRight; |
| break; |
| case ShelfAlignment::kRight: |
| anchor = views::MenuAnchorPosition::kBubbleLeft; |
| break; |
| } |
| |
| context_menu_runner_->RunMenuAt( |
| source->GetWidget(), /*button_controller=*/nullptr, |
| source->GetBoundsInScreen(), anchor, source_type); |
| } |
| |
| void TrayBackgroundView::AboutToRequestFocusFromTabTraversal(bool reverse) { |
| Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow()); |
| StatusAreaWidgetDelegate* delegate = |
| shelf->GetStatusAreaWidget()->status_area_widget_delegate(); |
| if (!delegate || !delegate->ShouldFocusOut(reverse)) |
| return; |
| |
| shelf_->shelf_focus_cycler()->FocusOut(reverse, SourceView::kStatusAreaView); |
| } |
| |
| void TrayBackgroundView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| ActionableView::GetAccessibleNodeData(node_data); |
| node_data->SetName(GetAccessibleNameForTray()); |
| |
| if (LockScreen::HasInstance()) { |
| GetViewAccessibility().OverrideNextFocus(LockScreen::Get()->widget()); |
| } |
| |
| Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow()); |
| ShelfWidget* shelf_widget = shelf->shelf_widget(); |
| GetViewAccessibility().OverridePreviousFocus(shelf_widget->hotseat_widget()); |
| GetViewAccessibility().OverrideNextFocus(shelf_widget->navigation_widget()); |
| } |
| |
| void TrayBackgroundView::ChildPreferredSizeChanged(views::View* child) { |
| PreferredSizeChanged(); |
| } |
| |
| std::unique_ptr<ui::Layer> TrayBackgroundView::RecreateLayer() { |
| if (layer()->GetAnimator()->is_animating()) |
| OnVisibilityAnimationFinished(/*should_log_visible_pod_count=*/false, |
| /*aborted=*/false); |
| |
| return views::View::RecreateLayer(); |
| } |
| |
| void TrayBackgroundView::OnThemeChanged() { |
| ActionableView::OnThemeChanged(); |
| UpdateBackground(); |
| StyleUtil::ConfigureInkDropAttributes(this, StyleUtil::kBaseColor | |
| StyleUtil::kInkDropOpacity | |
| StyleUtil::kHighlightOpacity); |
| } |
| |
| void TrayBackgroundView::OnVirtualKeyboardVisibilityChanged() { |
| // We call the base class' SetVisible to skip animations. |
| if (GetVisible() != GetEffectiveVisibility()) |
| views::View::SetVisible(GetEffectiveVisibility()); |
| } |
| |
| TrayBubbleView* TrayBackgroundView::GetBubbleView() { |
| return nullptr; |
| } |
| |
| views::Widget* TrayBackgroundView::GetBubbleWidget() const { |
| return nullptr; |
| } |
| |
| void TrayBackgroundView::CloseBubble() {} |
| |
| void TrayBackgroundView::ShowBubble() {} |
| |
| void TrayBackgroundView::CalculateTargetBounds() { |
| tray_container_->CalculateTargetBounds(); |
| } |
| |
| void TrayBackgroundView::UpdateLayout() { |
| UpdateBackground(); |
| tray_container_->UpdateLayout(); |
| } |
| |
| void TrayBackgroundView::UpdateAfterLoginStatusChange() { |
| // Handled in subclasses. |
| } |
| |
| void TrayBackgroundView::UpdateAfterStatusAreaCollapseChange() { |
| views::View::SetVisible(GetEffectiveVisibility()); |
| } |
| |
| void TrayBackgroundView::BubbleResized(const TrayBubbleView* bubble_view) {} |
| |
| void TrayBackgroundView::UpdateBackground() { |
| const float radius = ShelfConfig::Get()->control_border_radius(); |
| gfx::RoundedCornersF rounded_corners = {radius, radius, radius, radius}; |
| layer()->SetRoundedCornerRadius(rounded_corners); |
| layer()->SetIsFastRoundedCorner(true); |
| layer()->SetBackgroundBlur( |
| ShelfConfig::Get()->GetShelfControlButtonBlurRadius()); |
| layer()->SetColor(ShelfConfig::Get()->GetShelfControlButtonColor()); |
| layer()->SetClipRect(GetBackgroundBounds()); |
| } |
| |
| void TrayBackgroundView::OnAnimationAborted() { |
| OnVisibilityAnimationFinished(/*should_log_visible_pod_count=*/true, |
| /*aborted=*/true); |
| } |
| void TrayBackgroundView::OnAnimationEnded() { |
| OnVisibilityAnimationFinished(/*should_log_visible_pod_count=*/true, |
| /*aborted=*/false); |
| } |
| |
| void TrayBackgroundView::FadeInAnimation() { |
| gfx::Transform transform; |
| if (shelf_->IsHorizontalAlignment()) |
| transform.Translate(width(), 0.0f); |
| else |
| transform.Translate(0.0f, height()); |
| |
| views::AnimationBuilder() |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .OnAborted(base::BindOnce( |
| [](base::WeakPtr<TrayBackgroundView> view) { |
| if (view) |
| view->OnAnimationAborted(); |
| }, |
| weak_factory_.GetWeakPtr())) |
| .OnEnded(base::BindOnce( |
| [](base::WeakPtr<TrayBackgroundView> view) { |
| if (view) |
| view->OnAnimationEnded(); |
| }, |
| weak_factory_.GetWeakPtr())) |
| .Once() |
| .SetDuration(kShowAnimationDelayMs) |
| .Then() |
| .SetDuration(base::TimeDelta()) |
| .SetOpacity(this, 0.0f) |
| .SetTransform(this, transform) |
| .Then() |
| .SetDuration(kAnimationDurationForVisibilityMs) |
| .SetOpacity(this, 1.0f) |
| .SetTransform(this, gfx::Transform()); |
| } |
| |
| void TrayBackgroundView::BounceInAnimation() { |
| gfx::Vector2dF bounce_up_location; |
| gfx::Vector2dF bounce_down_location; |
| |
| switch (shelf_->alignment()) { |
| case ShelfAlignment::kLeft: |
| bounce_up_location = gfx::Vector2dF(kAnimationBounceUpDistance, 0); |
| bounce_down_location = gfx::Vector2dF(-kAnimationBounceDownDistance, 0); |
| break; |
| case ShelfAlignment::kRight: |
| bounce_up_location = gfx::Vector2dF(-kAnimationBounceUpDistance, 0); |
| bounce_down_location = gfx::Vector2dF(kAnimationBounceDownDistance, 0); |
| break; |
| case ShelfAlignment::kBottom: |
| case ShelfAlignment::kBottomLocked: |
| default: |
| bounce_up_location = gfx::Vector2dF(0, -kAnimationBounceUpDistance); |
| bounce_down_location = gfx::Vector2dF(0, kAnimationBounceDownDistance); |
| } |
| |
| gfx::Transform initial_scale; |
| initial_scale.Scale3d(kAnimationBounceScaleFactor, |
| kAnimationBounceScaleFactor, 1); |
| |
| gfx::Transform initial_state = |
| gfx::TransformAboutPivot(GetLocalBounds().CenterPoint(), initial_scale); |
| |
| gfx::Transform scale_about_pivot = gfx::TransformAboutPivot( |
| GetLocalBounds().CenterPoint(), gfx::Transform()); |
| scale_about_pivot.Translate(bounce_up_location); |
| |
| gfx::Transform move_down; |
| move_down.Translate(bounce_down_location); |
| |
| views::AnimationBuilder() |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .OnAborted(base::BindOnce( |
| [](base::WeakPtr<TrayBackgroundView> view) { |
| if (view) |
| view->OnAnimationAborted(); |
| }, |
| weak_factory_.GetWeakPtr())) |
| .OnEnded(base::BindOnce( |
| [](base::WeakPtr<TrayBackgroundView> view) { |
| if (view) |
| view->OnAnimationEnded(); |
| }, |
| weak_factory_.GetWeakPtr())) |
| .Once() |
| .SetDuration(base::TimeDelta()) |
| .SetOpacity(this, 1.0) |
| .SetTransform(this, std::move(initial_state)) |
| .Then() |
| .SetDuration(kAnimationDurationForBounceElement) |
| .SetTransform(this, std::move(scale_about_pivot), |
| gfx::Tween::FAST_OUT_SLOW_IN_3) |
| .Then() |
| .SetDuration(kAnimationDurationForBounceElement) |
| .SetTransform(this, std::move(move_down), gfx::Tween::EASE_OUT_4) |
| .Then() |
| .SetDuration(kAnimationDurationForBounceElement) |
| .SetTransform(this, gfx::Transform(), gfx::Tween::FAST_OUT_SLOW_IN_3); |
| } |
| |
| // Any visibility updates should be called after the hide animation is |
| // finished, otherwise the view will disappear immediately without animation |
| // once the view's visibility is set to false. |
| void TrayBackgroundView::HideAnimation() { |
| gfx::Transform scale; |
| scale.Scale3d(kAnimationBounceScaleFactor, kAnimationBounceScaleFactor, 1); |
| |
| gfx::Transform scale_about_pivot = |
| gfx::TransformAboutPivot(GetLocalBounds().CenterPoint(), scale); |
| |
| views::AnimationBuilder() |
| .SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .OnAborted(base::BindOnce( |
| [](base::WeakPtr<TrayBackgroundView> view) { |
| if (view) |
| view->OnAnimationAborted(); |
| }, |
| weak_factory_.GetWeakPtr())) |
| .OnEnded(base::BindOnce( |
| [](base::WeakPtr<TrayBackgroundView> view) { |
| if (view) |
| view->OnAnimationEnded(); |
| }, |
| weak_factory_.GetWeakPtr())) |
| .Once() |
| .SetDuration(kAnimationDurationForHideMs) |
| .SetVisibility(this, false) |
| .SetTransform(this, std::move(scale_about_pivot)) |
| .SetOpacity(this, 0.0f); |
| } |
| |
| void TrayBackgroundView::SetIsActive(bool is_active) { |
| if (is_active_ == is_active) |
| return; |
| is_active_ = is_active; |
| views::InkDrop::Get(this)->AnimateToState( |
| is_active_ ? views::InkDropState::ACTIVATED |
| : views::InkDropState::DEACTIVATED, |
| nullptr); |
| } |
| |
| views::View* TrayBackgroundView::GetBubbleAnchor() const { |
| return tray_container_; |
| } |
| |
| gfx::Insets TrayBackgroundView::GetBubbleAnchorInsets() const { |
| gfx::Insets anchor_insets = GetBubbleAnchor()->GetInsets(); |
| gfx::Insets tray_bg_insets = GetInsets(); |
| if (shelf_->alignment() == ShelfAlignment::kBottom || |
| shelf_->alignment() == ShelfAlignment::kBottomLocked) { |
| return gfx::Insets(-tray_bg_insets.top(), anchor_insets.left(), |
| -tray_bg_insets.bottom(), anchor_insets.right()); |
| } else { |
| return gfx::Insets(anchor_insets.top(), -tray_bg_insets.left(), |
| anchor_insets.bottom(), -tray_bg_insets.right()); |
| } |
| } |
| |
| aura::Window* TrayBackgroundView::GetBubbleWindowContainer() { |
| return Shell::GetContainer( |
| tray_container()->GetWidget()->GetNativeWindow()->GetRootWindow(), |
| kShellWindowId_SettingBubbleContainer); |
| } |
| |
| gfx::Rect TrayBackgroundView::GetBackgroundBounds() const { |
| gfx::Rect bounds = GetLocalBounds(); |
| bounds.Inset(GetBackgroundInsets()); |
| return bounds; |
| } |
| |
| bool TrayBackgroundView::PerformAction(const ui::Event& event) { |
| if (GetBubbleWidget()) |
| CloseBubble(); |
| else |
| ShowBubble(); |
| return true; |
| } |
| |
| void TrayBackgroundView::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
| UpdateBackground(); |
| |
| ActionableView::OnBoundsChanged(previous_bounds); |
| } |
| |
| bool TrayBackgroundView::ShouldEnterPushedState(const ui::Event& event) { |
| if (is_active_) |
| return false; |
| |
| return ActionableView::ShouldEnterPushedState(event); |
| } |
| |
| void TrayBackgroundView::HandlePerformActionResult(bool action_performed, |
| const ui::Event& event) { |
| // When an action is performed, ink drop ripple is handled in SetIsActive(). |
| if (action_performed) |
| return; |
| ActionableView::HandlePerformActionResult(action_performed, event); |
| } |
| |
| std::unique_ptr<ui::SimpleMenuModel> |
| TrayBackgroundView::CreateContextMenuModel() { |
| return nullptr; |
| } |
| |
| views::PaintInfo::ScaleType TrayBackgroundView::GetPaintScaleType() const { |
| return views::PaintInfo::ScaleType::kUniformScaling; |
| } |
| |
| gfx::Insets TrayBackgroundView::GetBackgroundInsets() const { |
| gfx::Insets insets = |
| GetMirroredBackgroundInsets(shelf_->IsHorizontalAlignment()); |
| |
| // |insets| are relative to contents bounds. Change them to be relative to |
| // local bounds. |
| gfx::Insets local_contents_insets = |
| GetLocalBounds().InsetsFrom(GetContentsBounds()); |
| MirrorInsetsIfNecessary(&local_contents_insets); |
| insets += local_contents_insets; |
| |
| if (Shell::Get()->IsInTabletMode() && ShelfConfig::Get()->is_in_app()) { |
| insets += gfx::Insets( |
| ShelfConfig::Get()->in_app_control_button_height_inset(), 0); |
| } |
| |
| return insets; |
| } |
| |
| bool TrayBackgroundView::GetEffectiveVisibility() { |
| // When the virtual keyboard is visible, the effective visibility of the view |
| // is solely determined by |show_with_virtual_keyboard_|. |
| if (Shell::Get()->system_tray_model()->virtual_keyboard()->visible()) |
| return show_with_virtual_keyboard_; |
| |
| if (!visible_preferred_) |
| return false; |
| |
| DCHECK(GetWidget()); |
| |
| // When the status area is collapsed, the effective visibility of the view is |
| // determined by |show_when_collapsed_|. |
| StatusAreaWidget::CollapseState collapse_state = |
| Shelf::ForWindow(GetWidget()->GetNativeWindow()) |
| ->GetStatusAreaWidget() |
| ->collapse_state(); |
| if (collapse_state == StatusAreaWidget::CollapseState::COLLAPSED) |
| return show_when_collapsed_; |
| |
| return true; |
| } |
| |
| BEGIN_METADATA(TrayBackgroundView, ActionableView) |
| END_METADATA |
| |
| } // namespace ash |