Commit 6be323c7 authored by Toni Barzic's avatar Toni Barzic Committed by Chromium LUCI CQ

Updates previews icon transition to animate the icon size

It uses slide animation to be consistent with how other tray items -
mainly the unified system tray - animate their size. On each animation
step, it updates the tray icon preferred size, which updates the shelf
layout.

Adds a previews container view to HoldingSpaceTrayIcon, to make it
easier to compose the transform changes on the previews during resize.
A preview movement has two components:
*   shift animation to handle preview index change within the icon
*   offset due to icon size change. The icon is expected to grow
    towards the start of the icon, which is opposite of how bounds grow
    by default. As the size changes, the items have to shift so their
    position remains constant relative to the end of the icon.
The latter is handled by updating transform on the container view
layer as the resize animation progresses.

Bug: 1142572
Change-Id: I411844c9a9c8fc4a270feaa94530f34be7bae088
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2601757
Commit-Queue: Toni Baržić <tbarzic@chromium.org>
Reviewed-by: default avatarDavid Black <dmblack@google.com>
Cr-Commit-Position: refs/heads/master@{#839201}
parent 26475afe
......@@ -19,7 +19,10 @@
#include "base/containers/unique_ptr_adapters.h"
#include "base/stl_util.h"
#include "ui/base/l10n/l10n_util.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/metadata/metadata_impl_macros.h"
......@@ -34,6 +37,74 @@ constexpr base::TimeDelta kPreviewItemUpdateDelayIncrement =
} // namespace
// Animation for resizing the previews icon. The animation updates the icon
// view's preferred size, which causes the status area (and the shelf) to
// relayout.
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_.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
animation_.SetSlideDuration(
ui::ScopedAnimationDurationScaleMode::duration_multiplier() *
base::TimeDelta::FromMilliseconds(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());
}
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_.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_;
};
// HoldingSpaceTrayIcon --------------------------------------------------------
HoldingSpaceTrayIcon::HoldingSpaceTrayIcon(Shelf* shelf) : shelf_(shelf) {
......@@ -67,21 +138,35 @@ int HoldingSpaceTrayIcon::GetHeightForWidth(int width) const {
return GetPreferredSize().height();
}
gfx::Size HoldingSpaceTrayIcon::CalculatePreferredSize() const {
const int num_visible_previews =
std::min(kHoldingSpaceTrayIconMaxVisiblePreviews,
static_cast<int>(previews_by_id_.size()));
int primary_axis_size = kTrayItemSize;
if (num_visible_previews > 1)
primary_axis_size += (num_visible_previews - 1) * kTrayItemSize / 2;
return shelf_->PrimaryAxisValue(
/*horizontal=*/gfx::Size(primary_axis_size, kTrayItemSize),
/*vertical=*/gfx::Size(kTrayItemSize, primary_axis_size));
}
void HoldingSpaceTrayIcon::InitLayout() {
SetLayoutManager(std::make_unique<views::FillLayout>());
SetPreferredSize(gfx::Size(kTrayItemSize, kTrayItemSize));
// As holding space items are added to the model, child layers will be added
// to `this` view's layer to represent them.
SetPaintToLayer();
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);
layer()->SetMasksToBounds(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::UpdatePreviews(
......@@ -107,7 +192,8 @@ void HoldingSpaceTrayIcon::UpdatePreviews(
continue;
}
auto preview = std::make_unique<HoldingSpaceTrayIconPreview>(this, item);
auto preview = std::make_unique<HoldingSpaceTrayIconPreview>(
shelf_, previews_container_, item);
preview->set_pending_index(index);
previews_by_id_.emplace(item->id(), std::move(preview));
}
......@@ -149,24 +235,13 @@ void HoldingSpaceTrayIcon::OnShelfAlignmentChanged(
for (const auto& preview : previews_by_id_)
preview.second->OnShelfAlignmentChanged(old_alignment, shelf_->alignment());
UpdatePreferredSize();
}
void HoldingSpaceTrayIcon::UpdatePreferredSize() {
const int num_visible_previews =
std::min(kHoldingSpaceTrayIconMaxVisiblePreviews,
static_cast<int>(previews_by_id_.size()));
int primary_axis_size = kTrayItemSize;
if (num_visible_previews > 1)
primary_axis_size += (num_visible_previews - 1) * kTrayItemSize / 2;
gfx::Size preferred_size = shelf_->PrimaryAxisValue(
/*horizontal=*/gfx::Size(primary_axis_size, kTrayItemSize),
/*vertical=*/gfx::Size(kTrayItemSize, primary_axis_size));
if (resize_animation_) {
resize_animation_->AdvanceToEnd();
resize_animation_.reset();
}
if (preferred_size != GetPreferredSize())
SetPreferredSize(preferred_size);
SetPreferredSize(CalculatePreferredSize());
previews_container_->SetTransform(gfx::Transform());
}
void HoldingSpaceTrayIcon::OnOldItemAnimatedOut(
......@@ -177,18 +252,31 @@ void HoldingSpaceTrayIcon::OnOldItemAnimatedOut(
}
void HoldingSpaceTrayIcon::OnOldItemsRemoved() {
// Now that the old items have been removed, update the icon preferred size.
// Note that this may change the icon bounds, and the relative position of
// existing items within the icon (as the icon origin will move). Adjust the
// position of existing items to maintain their position relative to the "end"
// visible bounds.
gfx::Size initial_size = GetPreferredSize();
UpdatePreferredSize();
gfx::Vector2d adjustment(GetPreferredSize().width() - initial_size.width(),
GetPreferredSize().height() - initial_size.height());
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);
resize_animation_ = std::make_unique<ResizeAnimation>(
this, previews_container_, initial_size, target_size);
resize_animation_->Start();
}
// Note: the order is important - `AnimateInNewItems()` will set the new item
// indices, and `ShiftExistingItems()` depends on the preview index value to
// detect whether an item is new.
......@@ -200,7 +288,7 @@ void HoldingSpaceTrayIcon::OnOldItemsRemoved() {
auto preview_it = previews_by_id_.find(item_id);
HoldingSpaceTrayIconPreview* preview_ptr = preview_it->second.get();
if (preview_ptr->layer())
layer()->StackAtBottom(preview_ptr->layer());
previews_container_->layer()->StackAtBottom(preview_ptr->layer());
}
}
......
......@@ -60,16 +60,18 @@ class ASH_EXPORT HoldingSpaceTrayIcon : public views::View,
void OnHoldingSpaceItemFinalized(const HoldingSpaceItem* item);
private:
class ResizeAnimation;
// views::View:
base::string16 GetTooltipText(const gfx::Point& point) const override;
int GetHeightForWidth(int width) const override;
gfx::Size CalculatePreferredSize() const override;
// ShellObserver:
void OnShelfAlignmentChanged(aura::Window* root_window,
ShelfAlignment old_alignment) override;
void InitLayout();
void UpdatePreferredSize();
// Invoked when the specified preview has completed animating out. At this
// point it is owned by `removed_previews_` and should be destroyed.
......@@ -104,6 +106,13 @@ class ASH_EXPORT HoldingSpaceTrayIcon : public views::View,
// (including items that are not currently visible).
std::vector<std::string> item_ids_;
// A view that serves as a parent for previews' layers. Used to easily
// translate all the previews within the icon during resize animation.
views::View* previews_container_ = nullptr;
// Helper to run icon resize animation.
std::unique_ptr<ResizeAnimation> resize_animation_;
ScopedObserver<Shell,
ShellObserver,
&Shell::AddShellObserver,
......
......@@ -152,13 +152,14 @@ class ContentsImage : public gfx::ImageSkia {
// HoldingSpaceTrayIconPreview -------------------------------------------------
HoldingSpaceTrayIconPreview::HoldingSpaceTrayIconPreview(
HoldingSpaceTrayIcon* icon,
Shelf* shelf,
views::View* container,
const HoldingSpaceItem* item)
: icon_(icon), item_(item) {
: shelf_(shelf), container_(container), item_(item) {
contents_image_ = std::make_unique<ContentsImage>(
item_, base::BindRepeating(&HoldingSpaceTrayIconPreview::InvalidateLayer,
base::Unretained(this)));
icon_observer_.Add(icon_);
container_observer_.Observe(container_);
}
HoldingSpaceTrayIconPreview::~HoldingSpaceTrayIconPreview() = default;
......@@ -367,7 +368,7 @@ void HoldingSpaceTrayIconPreview::OnDeviceScaleFactorChanged(
void HoldingSpaceTrayIconPreview::OnImplicitAnimationsCompleted() {
if (!NeedsLayer()) {
icon_->layer()->Remove(layer_.get());
container_->layer()->Remove(layer_.get());
layer_.reset();
}
......@@ -377,14 +378,14 @@ void HoldingSpaceTrayIconPreview::OnImplicitAnimationsCompleted() {
}
void HoldingSpaceTrayIconPreview::OnViewBoundsChanged(views::View* view) {
DCHECK_EQ(icon_, view);
DCHECK_EQ(container_, view);
if (layer_)
UpdateLayerBounds();
}
void HoldingSpaceTrayIconPreview::OnViewIsDeleting(views::View* view) {
DCHECK_EQ(icon_, view);
icon_observer_.Remove(icon_);
DCHECK_EQ(container_, view);
container_observer_.Reset();
}
void HoldingSpaceTrayIconPreview::CreateLayer(
......@@ -396,7 +397,7 @@ void HoldingSpaceTrayIconPreview::CreateLayer(
layer_->set_delegate(this);
UpdateLayerBounds();
icon_->layer()->Add(layer_.get());
container_->layer()->Add(layer_.get());
}
bool HoldingSpaceTrayIconPreview::NeedsLayer() const {
......@@ -410,7 +411,7 @@ void HoldingSpaceTrayIconPreview::InvalidateLayer() {
void HoldingSpaceTrayIconPreview::AdjustForShelfAlignmentAndTextDirection(
gfx::Vector2dF* vector_2df) {
if (!icon_->shelf()->IsHorizontalAlignment()) {
if (!shelf_->IsHorizontalAlignment()) {
const float x = vector_2df->x();
vector_2df->set_x(vector_2df->y());
vector_2df->set_y(x);
......@@ -430,9 +431,9 @@ void HoldingSpaceTrayIconPreview::UpdateLayerBounds() {
// with a positive offset.
const gfx::Size size = GetPreviewSize();
gfx::Point origin;
if (icon_->shelf()->IsHorizontalAlignment() && base::i18n::IsRTL()) {
origin =
icon_->GetLocalBounds().top_right() - gfx::Vector2d(size.width(), 0);
if (shelf_->IsHorizontalAlignment() && base::i18n::IsRTL()) {
origin = container_->GetLocalBounds().top_right() -
gfx::Vector2d(size.width(), 0);
}
gfx::Rect bounds(origin, size);
if (bounds != layer_->bounds())
......
......@@ -10,7 +10,7 @@
#include "ash/ash_export.h"
#include "base/callback.h"
#include "base/scoped_observer.h"
#include "base/scoped_observation.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/views/view.h"
......@@ -27,7 +27,7 @@ class Layer;
namespace ash {
class HoldingSpaceItem;
class HoldingSpaceTrayIcon;
class Shelf;
enum class ShelfAlignment;
// Class to visually represent a single holding space item within the holding
......@@ -38,7 +38,9 @@ class ASH_EXPORT HoldingSpaceTrayIconPreview
public ui::ImplicitAnimationObserver,
public views::ViewObserver {
public:
HoldingSpaceTrayIconPreview(HoldingSpaceTrayIcon*, const HoldingSpaceItem*);
HoldingSpaceTrayIconPreview(Shelf* shelf,
views::View* container,
const HoldingSpaceItem* item);
HoldingSpaceTrayIconPreview(const HoldingSpaceTrayIconPreview&) = delete;
HoldingSpaceTrayIconPreview& operator=(const HoldingSpaceTrayIconPreview&) =
delete;
......@@ -62,8 +64,8 @@ class ASH_EXPORT HoldingSpaceTrayIconPreview
// Transform is updated without animation.
void AdjustTransformForContainerSizeChange(const gfx::Vector2d& size_change);
// Invoked when the shelf associated with `icon_` has changed from
// `old_shelf_alignment` to `new_shelf_alignment`.
// Invoked when the `shelf_` has changed from `old_shelf_alignment` to
// `new_shelf_alignment`.
void OnShelfAlignmentChanged(ShelfAlignment old_shelf_alignment,
ShelfAlignment new_shelf_alignment);
......@@ -92,13 +94,13 @@ class ASH_EXPORT HoldingSpaceTrayIconPreview
// Creates the `layer_` for this preview. Note that `layer_` may be created
// multiple times throughout this preview's lifetime as `layer_` will only
// exist while in the viewport for the holding space tray `icon_`.
// exist while in the viewport for the holding space tray `container_`.
// |initial_transform| - The transform that should be set on the layer.
void CreateLayer(const gfx::Transform& initial_transform);
// Returns whether this preview needs a layer for its current `transform_`.
// Since we only maintain `layer_` while it appears in the viewport for the
// holding space tray `icon_`, this is used to gate creation/deletion of
// holding space tray `container_`, this is used to gate creation/deletion of
// `layer_`.
bool NeedsLayer() const;
......@@ -113,7 +115,14 @@ class ASH_EXPORT HoldingSpaceTrayIconPreview
// alignment in LTR and will be adjusted for vertical alignment and/or RTL.
void AdjustForShelfAlignmentAndTextDirection(gfx::Vector2dF* vector_2df);
HoldingSpaceTrayIcon* const icon_;
// The shelf whose holding space tray icon this preview belongs.
Shelf* const shelf_;
// The view that contains all preview layers belonging to the holding space
// icon.
views::View* const container_;
// The holding space item this preview represents.
const HoldingSpaceItem* item_;
// A cached representation of the associated holding space `item_`'s image
......@@ -122,14 +131,15 @@ class ASH_EXPORT HoldingSpaceTrayIconPreview
std::unique_ptr<gfx::ImageSkia> contents_image_;
// This is a proxy for `layer_`'s transform and represents the target
// position of this preview. Because `layer_` only exists while in `icon_`'s
// viewport, we need to manage transform ourselves and continue to update it
// even when `layer_` doesn't exist.
// position of this preview. Because `layer_` only exists while in
// `container_`'s viewport, we need to manage transform ourselves and continue
// to update it even when `layer_` doesn't exist.
gfx::Transform transform_;
// The layer serving as the visual representation of the associated holding
// space `item_` in the holding space `icon_` in the shelf. This only exists
// while in the `icon_`s viewport as determined by the current `transform_`.
// space `item_` in the holding space icon in the shelf. This only exists
// while in the `container_`s viewport as determined by the current
// `transform_`.
std::unique_ptr<ui::Layer> layer_;
// Closure to invoke on completion of `AnimateOut()`. It is expected that this
......@@ -144,10 +154,11 @@ class ASH_EXPORT HoldingSpaceTrayIconPreview
// is about to move. Set while the holding space tray icon is updating.
base::Optional<size_t> pending_index_;
// The `layer_` for this preview is parented by `icon_`'s layer. It is
// necessary to observe and react to bounds changes in `icon_` to keep
// The `layer_` for this preview is parented by `container_`'s layer. It is
// necessary to observe and react to bounds changes in `container_` to keep
// `layer_`'s bounds in sync.
ScopedObserver<views::View, views::ViewObserver> icon_observer_{this};
base::ScopedObservation<views::View, views::ViewObserver> container_observer_{
this};
base::WeakPtrFactory<HoldingSpaceTrayIconPreview> weak_factory_{this};
};
......
......@@ -491,6 +491,9 @@ class HoldingSpaceUiPreviewsBrowserTest : public HoldingSpaceUiBrowserTest {
// Verifies that previews can be toggled via context menu.
IN_PROC_BROWSER_TEST_F(HoldingSpaceUiPreviewsBrowserTest, TogglePreviews) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
ASSERT_TRUE(IsShowingInShelf());
// Initially, the default icon should be shown.
......@@ -501,6 +504,8 @@ IN_PROC_BROWSER_TEST_F(HoldingSpaceUiPreviewsBrowserTest, TogglePreviews) {
auto* previews_tray_icon = GetPreviewsTrayIcon();
ASSERT_TRUE(previews_tray_icon);
ASSERT_TRUE(previews_tray_icon->layer());
ASSERT_EQ(1u, previews_tray_icon->layer()->children().size());
auto* previews_container_layer = previews_tray_icon->layer()->children()[0];
EXPECT_FALSE(previews_tray_icon->GetVisible());
// After pinning a file, we should have a single preview in the tray icon.
......@@ -510,7 +515,7 @@ IN_PROC_BROWSER_TEST_F(HoldingSpaceUiPreviewsBrowserTest, TogglePreviews) {
EXPECT_FALSE(default_tray_icon->GetVisible());
EXPECT_TRUE(previews_tray_icon->GetVisible());
EXPECT_EQ(1u, previews_tray_icon->layer()->children().size());
EXPECT_EQ(1u, previews_container_layer->children().size());
EXPECT_EQ(gfx::Size(32, 32), previews_tray_icon->size());
// After downloading a file, we should have two previews in the tray icon.
......@@ -519,7 +524,7 @@ IN_PROC_BROWSER_TEST_F(HoldingSpaceUiPreviewsBrowserTest, TogglePreviews) {
EXPECT_FALSE(default_tray_icon->GetVisible());
EXPECT_TRUE(previews_tray_icon->GetVisible());
EXPECT_EQ(2u, previews_tray_icon->layer()->children().size());
EXPECT_EQ(2u, previews_container_layer->children().size());
EXPECT_EQ(gfx::Size(48, 32), previews_tray_icon->size());
// After taking a screenshot, we should have three previews in the tray icon.
......@@ -528,7 +533,7 @@ IN_PROC_BROWSER_TEST_F(HoldingSpaceUiPreviewsBrowserTest, TogglePreviews) {
EXPECT_FALSE(default_tray_icon->GetVisible());
EXPECT_TRUE(previews_tray_icon->GetVisible());
EXPECT_EQ(3u, previews_tray_icon->layer()->children().size());
EXPECT_EQ(3u, previews_container_layer->children().size());
EXPECT_EQ(gfx::Size(64, 32), previews_tray_icon->size());
// Right click the tray icon, and expect a context menu to be shown which will
......@@ -566,7 +571,7 @@ IN_PROC_BROWSER_TEST_F(HoldingSpaceUiPreviewsBrowserTest, TogglePreviews) {
EXPECT_FALSE(default_tray_icon->GetVisible());
EXPECT_TRUE(previews_tray_icon->GetVisible());
EXPECT_EQ(3u, previews_tray_icon->layer()->children().size());
EXPECT_EQ(3u, previews_container_layer->children().size());
EXPECT_EQ(gfx::Size(64, 32), previews_tray_icon->size());
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment