Commit af6570ea authored by David Black's avatar David Black Committed by Chromium LUCI CQ

Implement animate in/out of holding space child bubbles.

Holding space child bubbles animate out when their sections no longer
have finalized items to display. They animate in when at least one of
their sections becomes populated.

Bug: 1154998
Change-Id: I2d4bc1dc27346aa085c7d12e373d5618639926a1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2606667
Commit-Queue: David Black <dmblack@google.com>
Reviewed-by: default avatarToni Baržić <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#839711}
parent 0e7d7a98
......@@ -27,6 +27,10 @@ void HoldingSpaceModel::AddItems(
std::vector<const HoldingSpaceItem*> item_ptrs;
for (std::unique_ptr<HoldingSpaceItem>& item : items) {
DCHECK(!GetItem(item->id()));
if (item->IsFinalized())
++finalized_item_counts_by_type_[item->type()];
item_ptrs.push_back(item.get());
items_.push_back(std::move(item));
}
......@@ -64,6 +68,8 @@ void HoldingSpaceModel::FinalizeOrRemoveItem(const std::string& id,
DCHECK(!item->IsFinalized());
item->Finalize(file_system_url);
++finalized_item_counts_by_type_[item->type()];
for (auto& observer : observers_)
observer.OnHoldingSpaceItemFinalized(item);
}
......@@ -99,6 +105,9 @@ void HoldingSpaceModel::RemoveIf(Predicate predicate) {
item_ptrs.push_back(item.get());
items.push_back(std::move(item));
items_.erase(items_.begin() + i);
if (item_ptrs.back()->IsFinalized())
--finalized_item_counts_by_type_[item_ptrs.back()->type()];
}
}
......@@ -116,6 +125,8 @@ void HoldingSpaceModel::RemoveAll() {
ItemList items;
items.swap(items_);
finalized_item_counts_by_type_.clear();
std::vector<const HoldingSpaceItem*> item_ptrs;
for (auto& item : items)
item_ptrs.push_back(item.get());
......@@ -156,6 +167,12 @@ bool HoldingSpaceModel::ContainsItem(HoldingSpaceItem::Type type,
return GetItem(type, file_path) != nullptr;
}
bool HoldingSpaceModel::ContainsFinalizedItemOfType(
HoldingSpaceItem::Type type) const {
auto it = finalized_item_counts_by_type_.find(type);
return it != finalized_item_counts_by_type_.end() && it->second > 0u;
}
void HoldingSpaceModel::AddObserver(HoldingSpaceModelObserver* observer) {
observers_.AddObserver(observer);
}
......
......@@ -5,6 +5,7 @@
#ifndef ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_MODEL_H_
#define ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_MODEL_H_
#include <map>
#include <memory>
#include <set>
#include <string>
......@@ -85,6 +86,10 @@ class ASH_PUBLIC_EXPORT HoldingSpaceModel {
bool ContainsItem(HoldingSpaceItem::Type type,
const base::FilePath& file_path) const;
// Returns true if the model contains any finalized items of the specified
// `type`, false otherwise.
bool ContainsFinalizedItemOfType(HoldingSpaceItem::Type type) const;
const ItemList& items() const { return items_; }
void AddObserver(HoldingSpaceModelObserver* observer);
......@@ -95,6 +100,11 @@ class ASH_PUBLIC_EXPORT HoldingSpaceModel {
// the model.
ItemList items_;
// Caches the count of finalized items in the model for each holding space
// item type. Used to quickly look up whether the model contains any finalized
// items of a given type.
std::map<HoldingSpaceItem::Type, size_t> finalized_item_counts_by_type_;
base::ObserverList<HoldingSpaceModelObserver> observers_;
};
......
......@@ -9,11 +9,10 @@
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "ash/system/holding_space/holding_space_item_view.h"
#include "ash/system/holding_space/holding_space_item_view_delegate.h"
#include "ash/system/holding_space/holding_space_util.h"
#include "base/auto_reset.h"
#include "ui/compositor/callback_layer_animation_observer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/box_layout.h"
......@@ -39,61 +38,6 @@ void InitLayerForAnimations(views::View* view) {
ui::LayerAnimator::PreemptionStrategy::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
}
// Creates a `ui::LayerAnimationSequence` for the specified `element` with
// optional `delay`, observed by the specified `observer`.
std::unique_ptr<ui::LayerAnimationSequence> CreateObservedSequence(
std::unique_ptr<ui::LayerAnimationElement> element,
base::TimeDelta delay,
ui::LayerAnimationObserver* observer) {
auto sequence = std::make_unique<ui::LayerAnimationSequence>();
if (!delay.is_zero()) {
sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement(
element->properties(), delay));
}
sequence->AddElement(std::move(element));
sequence->AddObserver(observer);
return sequence;
}
// Animates the specified `view` to a target `opacity` with the specified
// `duration` and optional `delay`, associating `observer` with the created
// animation sequences.
void DoAnimateTo(views::View* view,
float opacity,
base::TimeDelta duration,
base::TimeDelta delay,
ui::LayerAnimationObserver* observer) {
// Opacity animation.
auto opacity_element =
ui::LayerAnimationElement::CreateOpacityElement(opacity, duration);
opacity_element->set_tween_type(gfx::Tween::Type::LINEAR);
// Note that the `ui::LayerAnimator` takes ownership of any animation
// sequences so they need to be released.
view->layer()->GetAnimator()->StartAnimation(
CreateObservedSequence(std::move(opacity_element), delay, observer)
.release());
}
// Animates in the specified `view` with the specified `duration` and optional
// `delay`, associating `observer` with the created animation sequences.
void DoAnimateIn(views::View* view,
base::TimeDelta duration,
base::TimeDelta delay,
ui::LayerAnimationObserver* observer) {
view->layer()->SetOpacity(0.f);
DoAnimateTo(view, /*opacity=*/1.f, duration, delay, observer);
}
// Animates out the specified `view` with the specified `duration, associating
// `observer` with the created animation sequences.
void DoAnimateOut(views::View* view,
base::TimeDelta duration,
ui::LayerAnimationObserver* observer) {
DoAnimateTo(view, /*opacity=*/0.f, duration, /*delay=*/base::TimeDelta(),
observer);
}
// Returns a callback which deletes the associated animation observer after
// running another `callback`.
using AnimationCompletedCallback =
......@@ -173,7 +117,7 @@ class HoldingSpaceScrollView : public views::ScrollView,
HoldingSpaceItemViewsSection::HoldingSpaceItemViewsSection(
HoldingSpaceItemViewDelegate* delegate,
std::vector<HoldingSpaceItem::Type> supported_types,
std::set<HoldingSpaceItem::Type> supported_types,
const base::Optional<size_t>& max_count)
: delegate_(delegate),
supported_types_(std::move(supported_types)),
......@@ -231,11 +175,15 @@ void HoldingSpaceItemViewsSection::Init() {
// Views.
HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
if (model) {
if (model && !model->items().empty()) {
std::vector<const HoldingSpaceItem*> item_ptrs;
for (const auto& item : model->items())
item_ptrs.push_back(item.get());
// Sections are not animated during initialization as their respective
// bubbles will be animated in instead.
base::AutoReset<bool> scoped_disable_animations(&disable_animations_, true);
OnHoldingSpaceModelAttached(model);
OnHoldingSpaceItemsAdded(item_ptrs);
}
// Re-enable propagation of `PreferredSizeChanged()` after initializing.
......@@ -304,22 +252,6 @@ void HoldingSpaceItemViewsSection::ViewHierarchyChanged(
PreferredSizeChanged();
}
void HoldingSpaceItemViewsSection::OnHoldingSpaceModelAttached(
HoldingSpaceModel* model) {
std::vector<const HoldingSpaceItem*> item_ptrs;
for (const auto& item : model->items())
item_ptrs.push_back(item.get());
if (!item_ptrs.empty())
OnHoldingSpaceItemsAdded(item_ptrs);
}
void HoldingSpaceItemViewsSection::OnHoldingSpaceModelDetached(
HoldingSpaceModel* model) {
if (!container_->children().empty())
MaybeAnimateOut();
}
void HoldingSpaceItemViewsSection::OnHoldingSpaceItemsAdded(
const std::vector<const HoldingSpaceItem*>& items) {
const bool needs_update = std::any_of(
......@@ -347,6 +279,15 @@ void HoldingSpaceItemViewsSection::OnHoldingSpaceItemFinalized(
MaybeAnimateOut();
}
void HoldingSpaceItemViewsSection::RemoveAllHoldingSpaceItemViews() {
// Holding space item views should only be removed when the `container_` is
// not visible to the user.
DCHECK(!IsDrawn() || !container_->IsDrawn() ||
container_->layer()->opacity() == 0.f);
container_->RemoveAllChildViews(/*delete_children=*/true);
views_by_item_id_.clear();
}
std::unique_ptr<views::View> HoldingSpaceItemViewsSection::CreatePlaceholder() {
return nullptr;
}
......@@ -420,15 +361,19 @@ void HoldingSpaceItemViewsSection::AnimateIn(
// If the `header_` is not opaque, this section was not previously visible
// to the user so the `header_` needs to be animated in alongside any content.
const bool animate_in_header = header_->layer()->GetTargetOpacity() != 1.f;
if (animate_in_header)
DoAnimateIn(header_, animation_duration, animation_delay, observer);
if (animate_in_header) {
holding_space_util::AnimateIn(header_, animation_duration, animation_delay,
observer);
}
if (views_by_item_id_.empty() && placeholder_) {
DoAnimateIn(placeholder_, animation_duration, animation_delay, observer);
holding_space_util::AnimateIn(placeholder_, animation_duration,
animation_delay, observer);
return;
}
DoAnimateIn(container_, animation_duration, animation_delay, observer);
holding_space_util::AnimateIn(container_, animation_duration, animation_delay,
observer);
}
void HoldingSpaceItemViewsSection::AnimateOut(
......@@ -446,25 +391,24 @@ void HoldingSpaceItemViewsSection::AnimateOut(
if (animate_out_header) {
HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
if (model) {
animate_out_header =
std::none_of(model->items().begin(), model->items().end(),
[this](const auto& item) {
return item->IsFinalized() &&
base::Contains(supported_types_, item->type());
});
animate_out_header = std::none_of(
supported_types_.begin(), supported_types_.end(),
[&model](HoldingSpaceItem::Type supported_type) {
return model->ContainsFinalizedItemOfType(supported_type);
});
}
}
if (animate_out_header)
DoAnimateOut(header_, animation_duration, observer);
holding_space_util::AnimateOut(header_, animation_duration, observer);
if (placeholder_ && placeholder_->GetVisible()) {
DCHECK(views_by_item_id_.empty());
DoAnimateOut(placeholder_, animation_duration, observer);
holding_space_util::AnimateOut(placeholder_, animation_duration, observer);
return;
}
DoAnimateOut(container_, animation_duration, observer);
holding_space_util::AnimateOut(container_, animation_duration, observer);
}
void HoldingSpaceItemViewsSection::OnAnimateInCompleted(
......@@ -514,11 +458,7 @@ void HoldingSpaceItemViewsSection::OnAnimateOutCompleted(
},
base::Unretained(this)));
if (!container_->children().empty()) {
container_->RemoveAllChildViews(/*delete_children=*/true);
views_by_item_id_.clear();
}
RemoveAllHoldingSpaceItemViews();
DCHECK(views_by_item_id_.empty());
HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
......
......@@ -27,15 +27,13 @@ namespace ash {
class HoldingSpaceItemView;
class HoldingSpaceItemViewDelegate;
class HoldingSpaceModel;
// A section of holding space item views in a `HoldingSpaceTrayChildBubble`.
class HoldingSpaceItemViewsSection : public views::View {
public:
HoldingSpaceItemViewsSection(
HoldingSpaceItemViewDelegate* delegate,
std::vector<HoldingSpaceItem::Type> supported_types,
const base::Optional<size_t>& max_count);
HoldingSpaceItemViewsSection(HoldingSpaceItemViewDelegate* delegate,
std::set<HoldingSpaceItem::Type> supported_types,
const base::Optional<size_t>& max_count);
HoldingSpaceItemViewsSection(const HoldingSpaceItemViewsSection& other) =
delete;
HoldingSpaceItemViewsSection& operator=(
......@@ -56,15 +54,29 @@ class HoldingSpaceItemViewsSection : public views::View {
void PreferredSizeChanged() override;
void ViewHierarchyChanged(const views::ViewHierarchyChangedDetails&) override;
// `HoldingSpaceControllerObserver` and `HoldingSpaceModelObserver` events
// forwarded from the parent `HoldingSpaceTrayChildBubble`. Events may be
// withheld from this view if, for example, its parent is animating out.
void OnHoldingSpaceModelAttached(HoldingSpaceModel* model);
void OnHoldingSpaceModelDetached(HoldingSpaceModel* model);
// `HoldingSpaceModelObserver` events forwarded from the parent
// `HoldingSpaceTrayChildBubble`. Note that events may be withheld from this
// view if, for example, its parent is animating out.
void OnHoldingSpaceItemsAdded(const std::vector<const HoldingSpaceItem*>&);
void OnHoldingSpaceItemsRemoved(const std::vector<const HoldingSpaceItem*>&);
void OnHoldingSpaceItemFinalized(const HoldingSpaceItem* item);
// Removes all holding space item views from this section. This method is
// expected to only be called:
// * from the parent `HoldingSpaceTrayChildBubble` when this view is hidden.
// * internally after having animated out the `container_` just prior to
// swapping in new contents.
void RemoveAllHoldingSpaceItemViews();
// Returns whether this section has a placeholder to show in lieu of item
// views when the model contains no finalized items of supported types.
bool has_placeholder() const { return !!placeholder_; }
// Returns the types of holding space items supported by this section.
const std::set<HoldingSpaceItem::Type>& supported_types() const {
return supported_types_;
}
protected:
// Invoked to create the `header_` for this section.
virtual std::unique_ptr<views::View> CreateHeader() = 0;
......@@ -117,7 +129,7 @@ class HoldingSpaceItemViewsSection : public views::View {
void OnAnimateOutCompleted(const ui::CallbackLayerAnimationObserver&);
HoldingSpaceItemViewDelegate* const delegate_;
const std::vector<HoldingSpaceItem::Type> supported_types_;
const std::set<HoldingSpaceItem::Type> supported_types_;
const base::Optional<size_t> max_count_;
// Owned by view hierarchy.
......
......@@ -15,6 +15,10 @@
#include "base/scoped_observation.h"
#include "ui/views/view.h"
namespace ui {
class LayerAnimationObserver;
} // namespace ui
namespace ash {
class HoldingSpaceItemViewDelegate;
......@@ -64,17 +68,37 @@ class HoldingSpaceTrayChildBubble : public views::View,
void ChildPreferredSizeChanged(views::View* child) override;
void ChildVisibilityChanged(views::View* child) override;
// Invoked to animate in/out this view if necessary.
void MaybeAnimateIn();
void MaybeAnimateOut();
// Invoked to animate in/out this view. These methods should only be called
// from `MaybeAnimateIn()`/`MaybeAnimateOut()` respectively as those methods
// contain gating criteria for when these methods may be invoked.
void AnimateIn(ui::LayerAnimationObserver* observer);
void AnimateOut(ui::LayerAnimationObserver* observer);
// Invoked when an in/out animation has completed. If `aborted` is true,
// the animation was cancelled and did not animation to target end values.
void OnAnimateInCompleted(bool aborted);
void OnAnimateOutCompleted(bool aborted);
HoldingSpaceItemViewDelegate* const delegate_;
// Views owned by view hierarchy.
std::vector<HoldingSpaceItemViewsSection*> sections_;
// Whether or not this view is currently being animated out.
bool is_animating_out_ = false;
base::ScopedObservation<HoldingSpaceController,
HoldingSpaceControllerObserver>
controller_observer_{this};
base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
model_observer_{this};
base::WeakPtrFactory<HoldingSpaceTrayChildBubble> weak_factory_{this};
};
} // namespace ash
......
......@@ -5,11 +5,76 @@
#include "ash/system/holding_space/holding_space_util.h"
#include "ash/style/ash_color_provider.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/views/controls/label.h"
namespace ash {
namespace holding_space_util {
namespace {
// Helpers ---------------------------------------------------------------------
// Creates a `ui::LayerAnimationSequence` for the specified `element` with
// optional `delay`, observed by the specified `observer`.
std::unique_ptr<ui::LayerAnimationSequence> CreateObservedSequence(
std::unique_ptr<ui::LayerAnimationElement> element,
base::TimeDelta delay,
ui::LayerAnimationObserver* observer) {
auto sequence = std::make_unique<ui::LayerAnimationSequence>();
if (!delay.is_zero()) {
sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement(
element->properties(), delay));
}
sequence->AddElement(std::move(element));
sequence->AddObserver(observer);
return sequence;
}
// Animates the specified `view` to a target `opacity` with the specified
// `duration` and optional `delay`, associating `observer` with the created
// animation sequences.
void AnimateTo(views::View* view,
float opacity,
base::TimeDelta duration,
base::TimeDelta delay,
ui::LayerAnimationObserver* observer) {
// Opacity animation.
auto opacity_element =
ui::LayerAnimationElement::CreateOpacityElement(opacity, duration);
opacity_element->set_tween_type(gfx::Tween::Type::LINEAR);
// Note that the `ui::LayerAnimator` takes ownership of any animation
// sequences so they need to be released.
view->layer()->GetAnimator()->StartAnimation(
CreateObservedSequence(std::move(opacity_element), delay, observer)
.release());
}
} // namespace
// Animates in the specified `view` with the specified `duration` and optional
// `delay`, associating `observer` with the created animation sequences.
void AnimateIn(views::View* view,
base::TimeDelta duration,
base::TimeDelta delay,
ui::LayerAnimationObserver* observer) {
view->layer()->SetOpacity(0.f);
AnimateTo(view, /*opacity=*/1.f, duration, delay, observer);
}
// Animates out the specified `view` with the specified `duration, associating
// `observer` with the created animation sequences.
void AnimateOut(views::View* view,
base::TimeDelta duration,
ui::LayerAnimationObserver* observer) {
AnimateTo(view, /*opacity=*/0.f, duration, /*delay=*/base::TimeDelta(),
observer);
}
std::unique_ptr<views::Label> CreateLabel(LabelStyle style,
const base::string16& text) {
auto label = std::make_unique<views::Label>(text);
......
......@@ -6,14 +6,33 @@
#define ASH_SYSTEM_HOLDING_SPACE_HOLDING_SPACE_UTIL_H_
#include "base/strings/string16.h"
#include "base/time/time.h"
namespace ui {
class LayerAnimationObserver;
} // namespace ui
namespace views {
class Label;
class View;
} // namespace views
namespace ash {
namespace holding_space_util {
// Animates in the specified `view` with the specified `duration` and optional
// `delay`, associating `observer` with the created animation sequences.
void AnimateIn(views::View* view,
base::TimeDelta duration,
base::TimeDelta delay,
ui::LayerAnimationObserver* observer);
// Animates out the specified `view` with the specified `duration, associating
// `observer` with the created animation sequences.
void AnimateOut(views::View* view,
base::TimeDelta duration,
ui::LayerAnimationObserver* observer);
// Enumeration of supported label styles.
enum class LabelStyle {
kBadge,
......
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