123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- // Copyright 2020 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/holding_space/holding_space_tray_icon.h"
- #include "ash/public/cpp/holding_space/holding_space_constants.h"
- #include "ash/public/cpp/holding_space/holding_space_item.h"
- #include "ash/public/cpp/holding_space/holding_space_metrics.h"
- #include "ash/public/cpp/holding_space/holding_space_prefs.h"
- #include "ash/public/cpp/shelf_config.h"
- #include "ash/resources/vector_icons/vector_icons.h"
- #include "ash/shelf/shelf.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/system/holding_space/holding_space_tray_icon_preview.h"
- #include "ash/system/tray/tray_constants.h"
- #include "base/barrier_closure.h"
- #include "base/bind.h"
- #include "base/containers/adapters.h"
- #include "base/containers/contains.h"
- #include "base/containers/cxx20_erase.h"
- #include "base/containers/unique_ptr_adapters.h"
- #include "base/i18n/rtl.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/base/metadata/metadata_impl_macros.h"
- #include "ui/compositor/compositor.h"
- #include "ui/compositor/layer.h"
- #include "ui/compositor/layer_animator.h"
- #include "ui/compositor/scoped_animation_duration_scale_mode.h"
- #include "ui/gfx/animation/slide_animation.h"
- #include "ui/gfx/paint_vector_icon.h"
- #include "ui/views/animation/animation_delegate_views.h"
- #include "ui/views/layout/fill_layout.h"
- #include "ui/views/widget/widget.h"
- namespace ash {
- namespace {
- // When in drop target state, previews are shifted to indices which are offset
- // from their standard positions by this fixed amount.
- constexpr int kPreviewIndexOffsetForDropTarget = 3;
- // The previews are animated in and shifted with a delay that increases
- // incrementally. This is the delay increment.
- constexpr base::TimeDelta kPreviewItemUpdateDelayIncrement =
- base::Milliseconds(50);
- // Helpers ---------------------------------------------------------------------
- // Returns the size of previews given the current shelf configuration.
- int GetPreviewSize() {
- ShelfConfig* const shelf_config = ShelfConfig::Get();
- return shelf_config->in_tablet_mode() && shelf_config->is_in_app()
- ? kHoldingSpaceTrayIconSmallPreviewSize
- : kHoldingSpaceTrayIconDefaultPreviewSize;
- }
- } // namespace
- // HoldingSpaceTrayIcon::ResizeAnimation ---------------------------------------
- // Animation for resizing the previews icon. The animation updates the icon
- // view's preferred size, which causes the status area (and the shelf) to
- // re-layout.
- class HoldingSpaceTrayIcon::ResizeAnimation
- : public views::AnimationDelegateViews {
- public:
- ResizeAnimation(HoldingSpaceTrayIcon* icon,
- views::View* previews_container,
- const gfx::Size& initial_size,
- const gfx::Size& target_size)
- : views::AnimationDelegateViews(icon),
- icon_(icon),
- previews_container_(previews_container),
- initial_size_(initial_size),
- target_size_(target_size),
- animation_(this),
- animation_throughput_tracker_(
- icon->GetWidget()->GetCompositor()->RequestNewThroughputTracker()) {
- animation_.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
- animation_.SetSlideDuration(
- ui::ScopedAnimationDurationScaleMode::duration_multiplier() *
- base::Milliseconds(250));
- }
- ResizeAnimation(const ResizeAnimation&) = delete;
- ResizeAnimation operator=(const ResizeAnimation&) = delete;
- ~ResizeAnimation() override = default;
- // views::AnimationDelegateViews:
- void AnimationEnded(const gfx::Animation* animation) override {
- icon_->SetPreferredSize(target_size_);
- previews_container_->SetTransform(gfx::Transform());
- // Record animation smoothness.
- animation_throughput_tracker_.Stop();
- }
- void AnimationProgressed(const gfx::Animation* animation) override {
- const gfx::Size current_size = gfx::Tween::SizeValueBetween(
- animation->GetCurrentValue(), initial_size_, target_size_);
- // Bounds grow from start to the end by default - for holding space tray
- // icon, the bounds are expected to grow from end to start. To achieve
- // growth from end to start, the position of previews is adjusted for target
- // bounds, and the previews container is translated so previews position
- // remains constant relative to the end of the icon.
- const gfx::Vector2d offset(current_size.width() - target_size_.width(),
- current_size.height() - target_size_.height());
- gfx::Transform transform;
- const int direction = base::i18n::IsRTL() ? -1 : 1;
- transform.Translate(direction * offset.x(), offset.y());
- previews_container_->SetTransform(transform);
- // This will update the shelf and status area layout.
- if (icon_->GetPreferredSize() != current_size)
- icon_->SetPreferredSize(current_size);
- }
- void Start() {
- animation_throughput_tracker_.Start(
- metrics_util::ForSmoothness(base::BindRepeating(
- holding_space_metrics::RecordPodResizeAnimationSmoothness)));
- animation_.Show();
- AnimationProgressed(&animation_);
- }
- void AdvanceToEnd() { animation_.End(); }
- private:
- HoldingSpaceTrayIcon* const icon_;
- views::View* const previews_container_;
- const gfx::Size initial_size_;
- const gfx::Size target_size_;
- gfx::SlideAnimation animation_;
- ui::ThroughputTracker animation_throughput_tracker_;
- };
- // HoldingSpaceTrayIcon --------------------------------------------------------
- HoldingSpaceTrayIcon::HoldingSpaceTrayIcon(Shelf* shelf) : shelf_(shelf) {
- SetID(kHoldingSpaceTrayPreviewsIconId);
- InitLayout();
- shell_observer_.Observe(Shell::Get());
- shelf_config_observer_.Observe(ShelfConfig::Get());
- }
- HoldingSpaceTrayIcon::~HoldingSpaceTrayIcon() = default;
- void HoldingSpaceTrayIcon::Clear() {
- previews_update_weak_factory_.InvalidateWeakPtrs();
- item_ids_.clear();
- previews_by_id_.clear();
- removed_previews_.clear();
- SetPreferredSize(CalculatePreferredSize());
- }
- int HoldingSpaceTrayIcon::GetHeightForWidth(int width) const {
- // The parent for this view (`TrayContainer`) uses a `BoxLayout` for its
- // `LayoutManager`. When the shelf orientation is vertical, the `BoxLayout`
- // will also have vertical orientation and will invoke `GetHeightForWidth()`
- // instead of `GetPreferredSize()` when determining preferred size.
- return GetPreferredSize().height();
- }
- gfx::Size HoldingSpaceTrayIcon::CalculatePreferredSize() const {
- const int num_visible_previews =
- std::min(kHoldingSpaceTrayIconMaxVisiblePreviews,
- static_cast<int>(previews_by_id_.size()));
- const int preview_size = GetPreviewSize();
- int primary_axis_size = preview_size;
- if (num_visible_previews > 1)
- primary_axis_size += (num_visible_previews - 1) * preview_size / 2;
- return shelf_->PrimaryAxisValue(
- /*horizontal=*/gfx::Size(primary_axis_size, kTrayItemSize),
- /*vertical=*/gfx::Size(kTrayItemSize, primary_axis_size));
- }
- void HoldingSpaceTrayIcon::OnThemeChanged() {
- views::View::OnThemeChanged();
- for (auto& preview_by_id : previews_by_id_)
- preview_by_id.second->OnThemeChanged();
- for (auto& preview : removed_previews_)
- preview->OnThemeChanged();
- }
- void HoldingSpaceTrayIcon::InitLayout() {
- SetLayoutManager(std::make_unique<views::FillLayout>());
- const int preview_size = GetPreviewSize();
- SetPreferredSize(gfx::Size(preview_size, preview_size));
- SetPaintToLayer(ui::LAYER_NOT_DRAWN);
- layer()->SetFillsBoundsOpaquely(false);
- const int radius = ShelfConfig::Get()->control_border_radius();
- gfx::RoundedCornersF rounded_corners(radius);
- layer()->SetRoundedCornerRadius(rounded_corners);
- layer()->SetIsFastRoundedCorner(true);
- previews_container_ = AddChildView(std::make_unique<views::View>());
- // As holding space items are added to the model, child layers will be added
- // to `previews_container_` view's layer to represent them.
- previews_container_->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
- }
- void HoldingSpaceTrayIcon::UpdateDropTargetState(bool is_drop_target,
- bool did_drop_to_pin) {
- if (is_drop_target_ == is_drop_target)
- return;
- is_drop_target_ = is_drop_target;
- // If the user performed a drag-and-drop to pin action, no handling is needed
- // to transition the holding space tray icon out of drop target state. When
- // the model updates, an `UpdatePreviews()` event will follow which will
- // restore standard indexing to the new and existing previews.
- if (!is_drop_target_ && did_drop_to_pin)
- return;
- DCHECK(!did_drop_to_pin);
- for (size_t i = 0; i < item_ids_.size(); ++i) {
- auto* preview = previews_by_id_.find(item_ids_[i])->second.get();
- DCHECK(preview->index());
- DCHECK(!preview->pending_index());
- size_t pending_index = i;
- base::TimeDelta delay;
- if (is_drop_target_) {
- // When in drop target state, preview indices are offset from their
- // standard positions by a fixed amount.
- pending_index += kPreviewIndexOffsetForDropTarget;
- } else {
- // When transitioning into drop target state, all previews shift out in
- // sync. When transitioning out of drop target state, previews shift in
- // with incremental `delay`.
- delay = i * kPreviewItemUpdateDelayIncrement;
- }
- preview->set_pending_index(pending_index);
- preview->AnimateShift(delay);
- }
- EnsurePreviewLayerStackingOrder();
- }
- void HoldingSpaceTrayIcon::UpdatePreviews(
- const std::vector<const HoldingSpaceItem*> items) {
- // Cancel any in progress updates.
- previews_update_weak_factory_.InvalidateWeakPtrs();
- item_ids_.clear();
- // When in drop target state, indices are offset from their standard position.
- const int offset = is_drop_target_ ? kPreviewIndexOffsetForDropTarget : 0;
- // Go over the new item list, create previews for new items, and assign new
- // indices to existing items.
- std::set<std::string> item_ids;
- for (size_t index = 0; index < items.size(); ++index) {
- const HoldingSpaceItem* item = items[index];
- DCHECK(item->IsInitialized());
- item_ids.insert(item->id());
- item_ids_.push_back(item->id());
- auto preview_it = previews_by_id_.find(item->id());
- if (preview_it != previews_by_id_.end()) {
- preview_it->second->set_pending_index(index + offset);
- continue;
- }
- auto preview = std::make_unique<HoldingSpaceTrayIconPreview>(
- shelf_, previews_container_, item);
- preview->set_pending_index(index + offset);
- previews_by_id_.emplace(item->id(), std::move(preview));
- }
- // Collect the list of items that should be removed.
- std::vector<std::string> items_to_remove;
- for (const auto& preview_pair : previews_by_id_) {
- if (!base::Contains(item_ids, preview_pair.first))
- items_to_remove.push_back(preview_pair.first);
- }
- if (!should_animate_updates_) {
- for (auto& item_id : items_to_remove)
- previews_by_id_.erase(item_id);
- items_to_remove.clear();
- }
- if (items_to_remove.empty()) {
- OnOldItemsRemoved();
- return;
- }
- // Animate out all items that should be removed.
- base::RepeatingClosure items_removed_callback = base::BarrierClosure(
- items_to_remove.size(),
- base::BindOnce(&HoldingSpaceTrayIcon::OnOldItemsRemoved,
- previews_update_weak_factory_.GetWeakPtr()));
- for (auto& item_id : items_to_remove) {
- auto preview_it = previews_by_id_.find(item_id);
- HoldingSpaceTrayIconPreview* preview_ptr = preview_it->second.get();
- removed_previews_.push_back(std::move(preview_it->second));
- previews_by_id_.erase(preview_it);
- preview_ptr->AnimateOut(base::BindOnce(
- &HoldingSpaceTrayIcon::OnOldItemAnimatedOut, base::Unretained(this),
- base::Unretained(preview_ptr), items_removed_callback));
- }
- }
- void HoldingSpaceTrayIcon::OnShellDestroying() {
- shell_observer_.Reset();
- }
- void HoldingSpaceTrayIcon::OnShelfAlignmentChanged(
- aura::Window* root_window,
- ShelfAlignment old_alignment) {
- // Each display has its own shelf. The shelf undergoing an alignment change
- // may not be the `shelf_` associated with this holding space tray icon.
- if (shelf_ != Shelf::ForWindow(root_window))
- return;
- if (!removed_previews_.empty()) {
- removed_previews_.clear();
- OnOldItemsRemoved();
- }
- for (const auto& preview : previews_by_id_)
- preview.second->OnShelfAlignmentChanged(old_alignment, shelf_->alignment());
- if (resize_animation_) {
- resize_animation_->AdvanceToEnd();
- resize_animation_.reset();
- }
- SetPreferredSize(CalculatePreferredSize());
- previews_container_->SetTransform(gfx::Transform());
- }
- void HoldingSpaceTrayIcon::OnShelfConfigUpdated() {
- if (!removed_previews_.empty()) {
- removed_previews_.clear();
- OnOldItemsRemoved();
- }
- for (const auto& preview : previews_by_id_)
- preview.second->OnShelfConfigChanged();
- if (resize_animation_) {
- resize_animation_->AdvanceToEnd();
- resize_animation_.reset();
- }
- SetPreferredSize(CalculatePreferredSize());
- previews_container_->SetTransform(gfx::Transform());
- }
- void HoldingSpaceTrayIcon::OnOldItemAnimatedOut(
- HoldingSpaceTrayIconPreview* preview,
- const base::RepeatingClosure& callback) {
- base::EraseIf(removed_previews_, base::MatchesUniquePtr(preview));
- callback.Run();
- }
- void HoldingSpaceTrayIcon::OnOldItemsRemoved() {
- if (resize_animation_) {
- resize_animation_->AdvanceToEnd();
- resize_animation_.reset();
- }
- // Now that the old items have been removed, resize the icon, and update
- // previews position within the icon.
- const gfx::Size initial_size = size();
- const gfx::Size target_size = CalculatePreferredSize();
- if (initial_size != target_size) {
- // Changing icon bounds changes the relative position of existing item
- // layers within the icon (as the icon origin moves). Adjust the
- // position of existing items to maintain their position relative to the
- // "end" visible bounds.
- gfx::Vector2d adjustment(target_size.width() - initial_size.width(),
- target_size.height() - initial_size.height());
- for (auto& preview_pair : previews_by_id_)
- preview_pair.second->AdjustTransformForContainerSizeChange(adjustment);
- if (should_animate_updates_) {
- resize_animation_ = std::make_unique<ResizeAnimation>(
- this, previews_container_, initial_size, target_size);
- resize_animation_->Start();
- } else {
- SetPreferredSize(target_size);
- }
- }
- // Shift existing items to their new positions. Note that this must be done
- // *prior* to animating in new items as `CalculateAnimateShiftParams()` relies
- // on index being unset in order to distinguish new items from existing items.
- for (const PreviewAnimationParams& params : CalculateAnimateShiftParams()) {
- params.preview->AnimateShift(params.delay);
- if (!should_animate_updates_ && params.preview->layer())
- params.preview->layer()->GetAnimator()->StopAnimating();
- }
- // Add new items. Note that this must be done *after* animating existing items
- // as `CalculateAnimateInParams()` relies on index being unset in order to
- // distinguish new items from existing items.
- for (const PreviewAnimationParams& params : CalculateAnimateInParams()) {
- params.preview->AnimateIn(params.delay);
- if (!should_animate_updates_ && params.preview->layer())
- params.preview->layer()->GetAnimator()->StopAnimating();
- }
- EnsurePreviewLayerStackingOrder();
- }
- std::vector<HoldingSpaceTrayIcon::PreviewAnimationParams>
- HoldingSpaceTrayIcon::CalculateAnimateShiftParams() {
- // Items shift with an incremental delay. For items shifting towards the end
- // of the icon, the delay should decrease with the index. For items shifting
- // towards the start of the icon, the delay should increase with the index.
- // This ensures items moving in the same direction do not fly over each other.
- std::vector<PreviewAnimationParams> animation_params;
- // Calculate the starting delay for items that will be shifting towards the
- // end of the icon.
- base::TimeDelta shift_out_delay;
- for (size_t i = 0; i < item_ids_.size(); ++i) {
- auto* preview = previews_by_id_.find(item_ids_[i])->second.get();
- if (!preview->index().has_value())
- continue;
- DCHECK(preview->pending_index());
- if (*preview->index() <= kHoldingSpaceTrayIconMaxVisiblePreviews &&
- *preview->index() < *preview->pending_index()) {
- shift_out_delay += kPreviewItemUpdateDelayIncrement;
- }
- }
- base::TimeDelta shift_in_delay;
- for (auto& item_id : item_ids_) {
- auto* preview = previews_by_id_.find(item_id)->second.get();
- // Existing items have current index set.
- if (preview->index().has_value()) {
- const bool shift_out = *preview->index() < *preview->pending_index();
- animation_params.emplace_back(PreviewAnimationParams{
- .preview = preview,
- .delay = shift_out ? shift_out_delay : shift_in_delay,
- });
- if (shift_out) {
- shift_out_delay -= kPreviewItemUpdateDelayIncrement;
- if (shift_out_delay < base::TimeDelta())
- shift_out_delay = base::TimeDelta();
- } else {
- shift_in_delay += kPreviewItemUpdateDelayIncrement;
- }
- }
- }
- return animation_params;
- }
- std::vector<HoldingSpaceTrayIcon::PreviewAnimationParams>
- HoldingSpaceTrayIcon::CalculateAnimateInParams() {
- // Items animating in should do so with a delay that increases in order of
- // addition, which is reverse of order in `items_`.
- std::vector<PreviewAnimationParams> animation_params;
- // Calculate the max delay which will be used for the first item.
- // NOTE: When animating in, an extra preview may be visible before items
- // before it drops into their position.
- base::TimeDelta addition_delay;
- for (size_t i = 0;
- i < kHoldingSpaceTrayIconMaxVisiblePreviews + 1 && i < item_ids_.size();
- ++i) {
- auto* preview = previews_by_id_.find(item_ids_[i])->second.get();
- if (!preview->index().has_value())
- addition_delay += kPreviewItemUpdateDelayIncrement;
- }
- for (auto& item_id : item_ids_) {
- auto* preview = previews_by_id_.find(item_id)->second.get();
- // New items do not have current index set.
- if (!preview->index().has_value()) {
- animation_params.emplace_back(PreviewAnimationParams{
- .preview = preview,
- .delay = addition_delay,
- });
- addition_delay -= kPreviewItemUpdateDelayIncrement;
- if (addition_delay < base::TimeDelta())
- addition_delay = base::TimeDelta();
- }
- }
- return animation_params;
- }
- void HoldingSpaceTrayIcon::EnsurePreviewLayerStackingOrder() {
- for (const auto& item_id : item_ids_) {
- auto* preview = previews_by_id_.find(item_id)->second.get();
- if (preview->layer())
- previews_container_->layer()->StackAtBottom(preview->layer());
- }
- }
- BEGIN_METADATA(HoldingSpaceTrayIcon, views::View)
- END_METADATA
- } // namespace ash
|