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( ...@@ -27,6 +27,10 @@ void HoldingSpaceModel::AddItems(
std::vector<const HoldingSpaceItem*> item_ptrs; std::vector<const HoldingSpaceItem*> item_ptrs;
for (std::unique_ptr<HoldingSpaceItem>& item : items) { for (std::unique_ptr<HoldingSpaceItem>& item : items) {
DCHECK(!GetItem(item->id())); DCHECK(!GetItem(item->id()));
if (item->IsFinalized())
++finalized_item_counts_by_type_[item->type()];
item_ptrs.push_back(item.get()); item_ptrs.push_back(item.get());
items_.push_back(std::move(item)); items_.push_back(std::move(item));
} }
...@@ -64,6 +68,8 @@ void HoldingSpaceModel::FinalizeOrRemoveItem(const std::string& id, ...@@ -64,6 +68,8 @@ void HoldingSpaceModel::FinalizeOrRemoveItem(const std::string& id,
DCHECK(!item->IsFinalized()); DCHECK(!item->IsFinalized());
item->Finalize(file_system_url); item->Finalize(file_system_url);
++finalized_item_counts_by_type_[item->type()];
for (auto& observer : observers_) for (auto& observer : observers_)
observer.OnHoldingSpaceItemFinalized(item); observer.OnHoldingSpaceItemFinalized(item);
} }
...@@ -99,6 +105,9 @@ void HoldingSpaceModel::RemoveIf(Predicate predicate) { ...@@ -99,6 +105,9 @@ void HoldingSpaceModel::RemoveIf(Predicate predicate) {
item_ptrs.push_back(item.get()); item_ptrs.push_back(item.get());
items.push_back(std::move(item)); items.push_back(std::move(item));
items_.erase(items_.begin() + i); 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() { ...@@ -116,6 +125,8 @@ void HoldingSpaceModel::RemoveAll() {
ItemList items; ItemList items;
items.swap(items_); items.swap(items_);
finalized_item_counts_by_type_.clear();
std::vector<const HoldingSpaceItem*> item_ptrs; std::vector<const HoldingSpaceItem*> item_ptrs;
for (auto& item : items) for (auto& item : items)
item_ptrs.push_back(item.get()); item_ptrs.push_back(item.get());
...@@ -156,6 +167,12 @@ bool HoldingSpaceModel::ContainsItem(HoldingSpaceItem::Type type, ...@@ -156,6 +167,12 @@ bool HoldingSpaceModel::ContainsItem(HoldingSpaceItem::Type type,
return GetItem(type, file_path) != nullptr; 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) { void HoldingSpaceModel::AddObserver(HoldingSpaceModelObserver* observer) {
observers_.AddObserver(observer); observers_.AddObserver(observer);
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_MODEL_H_ #ifndef ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_MODEL_H_
#define ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_MODEL_H_ #define ASH_PUBLIC_CPP_HOLDING_SPACE_HOLDING_SPACE_MODEL_H_
#include <map>
#include <memory> #include <memory>
#include <set> #include <set>
#include <string> #include <string>
...@@ -85,6 +86,10 @@ class ASH_PUBLIC_EXPORT HoldingSpaceModel { ...@@ -85,6 +86,10 @@ class ASH_PUBLIC_EXPORT HoldingSpaceModel {
bool ContainsItem(HoldingSpaceItem::Type type, bool ContainsItem(HoldingSpaceItem::Type type,
const base::FilePath& file_path) const; 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_; } const ItemList& items() const { return items_; }
void AddObserver(HoldingSpaceModelObserver* observer); void AddObserver(HoldingSpaceModelObserver* observer);
...@@ -95,6 +100,11 @@ class ASH_PUBLIC_EXPORT HoldingSpaceModel { ...@@ -95,6 +100,11 @@ class ASH_PUBLIC_EXPORT HoldingSpaceModel {
// the model. // the model.
ItemList items_; 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_; base::ObserverList<HoldingSpaceModelObserver> observers_;
}; };
......
...@@ -9,11 +9,10 @@ ...@@ -9,11 +9,10 @@
#include "ash/public/cpp/holding_space/holding_space_model.h" #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.h"
#include "ash/system/holding_space/holding_space_item_view_delegate.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 "base/auto_reset.h"
#include "ui/compositor/callback_layer_animation_observer.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_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h" #include "ui/compositor/layer_animator.h"
#include "ui/views/controls/scroll_view.h" #include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout.h"
...@@ -39,61 +38,6 @@ void InitLayerForAnimations(views::View* view) { ...@@ -39,61 +38,6 @@ void InitLayerForAnimations(views::View* view) {
ui::LayerAnimator::PreemptionStrategy::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); 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 // Returns a callback which deletes the associated animation observer after
// running another `callback`. // running another `callback`.
using AnimationCompletedCallback = using AnimationCompletedCallback =
...@@ -173,7 +117,7 @@ class HoldingSpaceScrollView : public views::ScrollView, ...@@ -173,7 +117,7 @@ class HoldingSpaceScrollView : public views::ScrollView,
HoldingSpaceItemViewsSection::HoldingSpaceItemViewsSection( HoldingSpaceItemViewsSection::HoldingSpaceItemViewsSection(
HoldingSpaceItemViewDelegate* delegate, HoldingSpaceItemViewDelegate* delegate,
std::vector<HoldingSpaceItem::Type> supported_types, std::set<HoldingSpaceItem::Type> supported_types,
const base::Optional<size_t>& max_count) const base::Optional<size_t>& max_count)
: delegate_(delegate), : delegate_(delegate),
supported_types_(std::move(supported_types)), supported_types_(std::move(supported_types)),
...@@ -231,11 +175,15 @@ void HoldingSpaceItemViewsSection::Init() { ...@@ -231,11 +175,15 @@ void HoldingSpaceItemViewsSection::Init() {
// Views. // Views.
HoldingSpaceModel* model = HoldingSpaceController::Get()->model(); 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 // Sections are not animated during initialization as their respective
// bubbles will be animated in instead. // bubbles will be animated in instead.
base::AutoReset<bool> scoped_disable_animations(&disable_animations_, true); base::AutoReset<bool> scoped_disable_animations(&disable_animations_, true);
OnHoldingSpaceModelAttached(model); OnHoldingSpaceItemsAdded(item_ptrs);
} }
// Re-enable propagation of `PreferredSizeChanged()` after initializing. // Re-enable propagation of `PreferredSizeChanged()` after initializing.
...@@ -304,22 +252,6 @@ void HoldingSpaceItemViewsSection::ViewHierarchyChanged( ...@@ -304,22 +252,6 @@ void HoldingSpaceItemViewsSection::ViewHierarchyChanged(
PreferredSizeChanged(); 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( void HoldingSpaceItemViewsSection::OnHoldingSpaceItemsAdded(
const std::vector<const HoldingSpaceItem*>& items) { const std::vector<const HoldingSpaceItem*>& items) {
const bool needs_update = std::any_of( const bool needs_update = std::any_of(
...@@ -347,6 +279,15 @@ void HoldingSpaceItemViewsSection::OnHoldingSpaceItemFinalized( ...@@ -347,6 +279,15 @@ void HoldingSpaceItemViewsSection::OnHoldingSpaceItemFinalized(
MaybeAnimateOut(); 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() { std::unique_ptr<views::View> HoldingSpaceItemViewsSection::CreatePlaceholder() {
return nullptr; return nullptr;
} }
...@@ -420,15 +361,19 @@ void HoldingSpaceItemViewsSection::AnimateIn( ...@@ -420,15 +361,19 @@ void HoldingSpaceItemViewsSection::AnimateIn(
// If the `header_` is not opaque, this section was not previously visible // 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. // to the user so the `header_` needs to be animated in alongside any content.
const bool animate_in_header = header_->layer()->GetTargetOpacity() != 1.f; const bool animate_in_header = header_->layer()->GetTargetOpacity() != 1.f;
if (animate_in_header) if (animate_in_header) {
DoAnimateIn(header_, animation_duration, animation_delay, observer); holding_space_util::AnimateIn(header_, animation_duration, animation_delay,
observer);
}
if (views_by_item_id_.empty() && placeholder_) { 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; return;
} }
DoAnimateIn(container_, animation_duration, animation_delay, observer); holding_space_util::AnimateIn(container_, animation_duration, animation_delay,
observer);
} }
void HoldingSpaceItemViewsSection::AnimateOut( void HoldingSpaceItemViewsSection::AnimateOut(
...@@ -446,25 +391,24 @@ void HoldingSpaceItemViewsSection::AnimateOut( ...@@ -446,25 +391,24 @@ void HoldingSpaceItemViewsSection::AnimateOut(
if (animate_out_header) { if (animate_out_header) {
HoldingSpaceModel* model = HoldingSpaceController::Get()->model(); HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
if (model) { if (model) {
animate_out_header = animate_out_header = std::none_of(
std::none_of(model->items().begin(), model->items().end(), supported_types_.begin(), supported_types_.end(),
[this](const auto& item) { [&model](HoldingSpaceItem::Type supported_type) {
return item->IsFinalized() && return model->ContainsFinalizedItemOfType(supported_type);
base::Contains(supported_types_, item->type()); });
});
} }
} }
if (animate_out_header) if (animate_out_header)
DoAnimateOut(header_, animation_duration, observer); holding_space_util::AnimateOut(header_, animation_duration, observer);
if (placeholder_ && placeholder_->GetVisible()) { if (placeholder_ && placeholder_->GetVisible()) {
DCHECK(views_by_item_id_.empty()); DCHECK(views_by_item_id_.empty());
DoAnimateOut(placeholder_, animation_duration, observer); holding_space_util::AnimateOut(placeholder_, animation_duration, observer);
return; return;
} }
DoAnimateOut(container_, animation_duration, observer); holding_space_util::AnimateOut(container_, animation_duration, observer);
} }
void HoldingSpaceItemViewsSection::OnAnimateInCompleted( void HoldingSpaceItemViewsSection::OnAnimateInCompleted(
...@@ -514,11 +458,7 @@ void HoldingSpaceItemViewsSection::OnAnimateOutCompleted( ...@@ -514,11 +458,7 @@ void HoldingSpaceItemViewsSection::OnAnimateOutCompleted(
}, },
base::Unretained(this))); base::Unretained(this)));
if (!container_->children().empty()) { RemoveAllHoldingSpaceItemViews();
container_->RemoveAllChildViews(/*delete_children=*/true);
views_by_item_id_.clear();
}
DCHECK(views_by_item_id_.empty()); DCHECK(views_by_item_id_.empty());
HoldingSpaceModel* model = HoldingSpaceController::Get()->model(); HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
......
...@@ -27,15 +27,13 @@ namespace ash { ...@@ -27,15 +27,13 @@ namespace ash {
class HoldingSpaceItemView; class HoldingSpaceItemView;
class HoldingSpaceItemViewDelegate; class HoldingSpaceItemViewDelegate;
class HoldingSpaceModel;
// A section of holding space item views in a `HoldingSpaceTrayChildBubble`. // A section of holding space item views in a `HoldingSpaceTrayChildBubble`.
class HoldingSpaceItemViewsSection : public views::View { class HoldingSpaceItemViewsSection : public views::View {
public: public:
HoldingSpaceItemViewsSection( HoldingSpaceItemViewsSection(HoldingSpaceItemViewDelegate* delegate,
HoldingSpaceItemViewDelegate* delegate, std::set<HoldingSpaceItem::Type> supported_types,
std::vector<HoldingSpaceItem::Type> supported_types, const base::Optional<size_t>& max_count);
const base::Optional<size_t>& max_count);
HoldingSpaceItemViewsSection(const HoldingSpaceItemViewsSection& other) = HoldingSpaceItemViewsSection(const HoldingSpaceItemViewsSection& other) =
delete; delete;
HoldingSpaceItemViewsSection& operator=( HoldingSpaceItemViewsSection& operator=(
...@@ -56,15 +54,29 @@ class HoldingSpaceItemViewsSection : public views::View { ...@@ -56,15 +54,29 @@ class HoldingSpaceItemViewsSection : public views::View {
void PreferredSizeChanged() override; void PreferredSizeChanged() override;
void ViewHierarchyChanged(const views::ViewHierarchyChangedDetails&) override; void ViewHierarchyChanged(const views::ViewHierarchyChangedDetails&) override;
// `HoldingSpaceControllerObserver` and `HoldingSpaceModelObserver` events // `HoldingSpaceModelObserver` events forwarded from the parent
// forwarded from the parent `HoldingSpaceTrayChildBubble`. Events may be // `HoldingSpaceTrayChildBubble`. Note that events may be withheld from this
// withheld from this view if, for example, its parent is animating out. // view if, for example, its parent is animating out.
void OnHoldingSpaceModelAttached(HoldingSpaceModel* model);
void OnHoldingSpaceModelDetached(HoldingSpaceModel* model);
void OnHoldingSpaceItemsAdded(const std::vector<const HoldingSpaceItem*>&); void OnHoldingSpaceItemsAdded(const std::vector<const HoldingSpaceItem*>&);
void OnHoldingSpaceItemsRemoved(const std::vector<const HoldingSpaceItem*>&); void OnHoldingSpaceItemsRemoved(const std::vector<const HoldingSpaceItem*>&);
void OnHoldingSpaceItemFinalized(const HoldingSpaceItem* item); 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: protected:
// Invoked to create the `header_` for this section. // Invoked to create the `header_` for this section.
virtual std::unique_ptr<views::View> CreateHeader() = 0; virtual std::unique_ptr<views::View> CreateHeader() = 0;
...@@ -117,7 +129,7 @@ class HoldingSpaceItemViewsSection : public views::View { ...@@ -117,7 +129,7 @@ class HoldingSpaceItemViewsSection : public views::View {
void OnAnimateOutCompleted(const ui::CallbackLayerAnimationObserver&); void OnAnimateOutCompleted(const ui::CallbackLayerAnimationObserver&);
HoldingSpaceItemViewDelegate* const delegate_; HoldingSpaceItemViewDelegate* const delegate_;
const std::vector<HoldingSpaceItem::Type> supported_types_; const std::set<HoldingSpaceItem::Type> supported_types_;
const base::Optional<size_t> max_count_; const base::Optional<size_t> max_count_;
// Owned by view hierarchy. // Owned by view hierarchy.
......
...@@ -4,16 +4,58 @@ ...@@ -4,16 +4,58 @@
#include "ash/system/holding_space/holding_space_tray_child_bubble.h" #include "ash/system/holding_space/holding_space_tray_child_bubble.h"
#include <set>
#include "ash/public/cpp/holding_space/holding_space_constants.h" #include "ash/public/cpp/holding_space/holding_space_constants.h"
#include "ash/style/ash_color_provider.h" #include "ash/style/ash_color_provider.h"
#include "ash/system/holding_space/holding_space_item_views_section.h" #include "ash/system/holding_space/holding_space_item_views_section.h"
#include "ash/system/holding_space/holding_space_util.h"
#include "ash/system/tray/tray_constants.h" #include "ash/system/tray/tray_constants.h"
#include "ui/compositor/callback_layer_animation_observer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout.h"
namespace ash { namespace ash {
namespace { namespace {
// Animation.
constexpr base::TimeDelta kAnimationDuration =
base::TimeDelta::FromMilliseconds(167);
// Helpers ---------------------------------------------------------------------
// Returns a callback which deletes the associated animation observer after
// running another `callback`.
using AnimationCompletedCallback = base::OnceCallback<void(bool aborted)>;
base::RepeatingCallback<bool(const ui::CallbackLayerAnimationObserver&)>
DeleteObserverAfterRunning(AnimationCompletedCallback callback) {
return base::BindRepeating(
[](AnimationCompletedCallback callback,
const ui::CallbackLayerAnimationObserver& observer) {
// NOTE: It's safe to move `callback` since this code will only run
// once due to deletion of the associated `observer`. The `observer` is
// deleted by returning `true`.
std::move(callback).Run(/*aborted=*/observer.aborted_count() > 0);
return true;
},
base::Passed(std::move(callback)));
}
// Returns whether the given holding space `model` contains any finalized items
// which are supported by the specified holding space item views `section`.
bool ModelContainsFinalizedItemsForSection(
const HoldingSpaceModel* model,
const HoldingSpaceItemViewsSection* section) {
const auto& supported_types = section->supported_types();
return std::any_of(
supported_types.begin(), supported_types.end(),
[&model](HoldingSpaceItem::Type supported_type) {
return model->ContainsFinalizedItemOfType(supported_type);
});
}
// TopAlignedBoxLayout --------------------------------------------------------- // TopAlignedBoxLayout ---------------------------------------------------------
// A vertical `views::BoxLayout` which overrides layout behavior when there is // A vertical `views::BoxLayout` which overrides layout behavior when there is
...@@ -83,11 +125,14 @@ void HoldingSpaceTrayChildBubble::Init() { ...@@ -83,11 +125,14 @@ void HoldingSpaceTrayChildBubble::Init() {
// Layer. // Layer.
SetPaintToLayer(ui::LAYER_SOLID_COLOR); SetPaintToLayer(ui::LAYER_SOLID_COLOR);
layer()->GetAnimator()->set_preemption_strategy(
ui::LayerAnimator::PreemptionStrategy::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
layer()->SetBackgroundBlur(kUnifiedMenuBackgroundBlur); layer()->SetBackgroundBlur(kUnifiedMenuBackgroundBlur);
layer()->SetColor(AshColorProvider::Get()->GetBaseLayerColor( layer()->SetColor(AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kTransparent80)); AshColorProvider::BaseLayerType::kTransparent80));
layer()->SetFillsBoundsOpaquely(false); layer()->SetFillsBoundsOpaquely(false);
layer()->SetIsFastRoundedCorner(true); layer()->SetIsFastRoundedCorner(true);
layer()->SetOpacity(0.f);
layer()->SetRoundedCornerRadius( layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF{kUnifiedTrayCornerRadius}); gfx::RoundedCornersF{kUnifiedTrayCornerRadius});
...@@ -99,8 +144,13 @@ void HoldingSpaceTrayChildBubble::Init() { ...@@ -99,8 +144,13 @@ void HoldingSpaceTrayChildBubble::Init() {
} }
void HoldingSpaceTrayChildBubble::Reset() { void HoldingSpaceTrayChildBubble::Reset() {
// Prevent animation callbacks from running when the holding space bubble is
// being asynchronously closed. This view will be imminently deleted.
weak_factory_.InvalidateWeakPtrs();
model_observer_.Reset(); model_observer_.Reset();
controller_observer_.Reset(); controller_observer_.Reset();
for (HoldingSpaceItemViewsSection* section : sections_) for (HoldingSpaceItemViewsSection* section : sections_)
section->Reset(); section->Reset();
} }
...@@ -108,33 +158,67 @@ void HoldingSpaceTrayChildBubble::Reset() { ...@@ -108,33 +158,67 @@ void HoldingSpaceTrayChildBubble::Reset() {
void HoldingSpaceTrayChildBubble::OnHoldingSpaceModelAttached( void HoldingSpaceTrayChildBubble::OnHoldingSpaceModelAttached(
HoldingSpaceModel* model) { HoldingSpaceModel* model) {
model_observer_.Observe(model); model_observer_.Observe(model);
for (HoldingSpaceItemViewsSection* section : sections_)
section->OnHoldingSpaceModelAttached(model); // New model contents, if available, will be populated and animated in after
// the out animation completes.
MaybeAnimateOut();
} }
void HoldingSpaceTrayChildBubble::OnHoldingSpaceModelDetached( void HoldingSpaceTrayChildBubble::OnHoldingSpaceModelDetached(
HoldingSpaceModel* model) { HoldingSpaceModel* model) {
model_observer_.Reset(); model_observer_.Reset();
for (HoldingSpaceItemViewsSection* section : sections_) MaybeAnimateOut();
section->OnHoldingSpaceModelDetached(model);
} }
void HoldingSpaceTrayChildBubble::OnHoldingSpaceItemsAdded( void HoldingSpaceTrayChildBubble::OnHoldingSpaceItemsAdded(
const std::vector<const HoldingSpaceItem*>& items) { const std::vector<const HoldingSpaceItem*>& items) {
for (HoldingSpaceItemViewsSection* section : sections_) // Ignore new items while the bubble is animating out. The bubble content will
section->OnHoldingSpaceItemsAdded(items); // be updated to match the model after the out animation completes.
if (!is_animating_out_) {
for (HoldingSpaceItemViewsSection* section : sections_)
section->OnHoldingSpaceItemsAdded(items);
}
} }
void HoldingSpaceTrayChildBubble::OnHoldingSpaceItemsRemoved( void HoldingSpaceTrayChildBubble::OnHoldingSpaceItemsRemoved(
const std::vector<const HoldingSpaceItem*>& items) { const std::vector<const HoldingSpaceItem*>& items) {
// Ignore item removal while the bubble is animating out. The bubble content
// will be updated to match the model after the out animation completes.
if (is_animating_out_)
return;
HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
DCHECK(model);
// This child bubble should animate out if the attached model does not
// contain finalized items supported by any of its sections. The exception is
// if a section has a placeholder to show in lieu of holding space items. If
// a placeholder exists, the child bubble should persist.
const bool animate_out = std::none_of(
sections_.begin(), sections_.end(),
[&model](const HoldingSpaceItemViewsSection* section) {
return section->has_placeholder() ||
ModelContainsFinalizedItemsForSection(model, section);
});
if (animate_out) {
MaybeAnimateOut();
return;
}
for (HoldingSpaceItemViewsSection* section : sections_) for (HoldingSpaceItemViewsSection* section : sections_)
section->OnHoldingSpaceItemsRemoved(items); section->OnHoldingSpaceItemsRemoved(items);
} }
void HoldingSpaceTrayChildBubble::OnHoldingSpaceItemFinalized( void HoldingSpaceTrayChildBubble::OnHoldingSpaceItemFinalized(
const HoldingSpaceItem* item) { const HoldingSpaceItem* item) {
for (HoldingSpaceItemViewsSection* section : sections_) // Ignore item finalization while the bubble is animating out. The bubble
section->OnHoldingSpaceItemFinalized(item); // content will be updated to match the model after the out animation
// completes.
if (!is_animating_out_) {
for (HoldingSpaceItemViewsSection* section : sections_)
section->OnHoldingSpaceItemFinalized(item);
}
} }
const char* HoldingSpaceTrayChildBubble::GetClassName() const { const char* HoldingSpaceTrayChildBubble::GetClassName() const {
...@@ -156,10 +240,122 @@ void HoldingSpaceTrayChildBubble::ChildVisibilityChanged(views::View* child) { ...@@ -156,10 +240,122 @@ void HoldingSpaceTrayChildBubble::ChildVisibilityChanged(views::View* child) {
} }
} }
if (visible != GetVisible()) if (visible != GetVisible()) {
SetVisible(visible); SetVisible(visible);
// When the child bubble becomes visible, its due to one of its sections
// becoming visible. In this case, the child bubble should animate in.
if (GetVisible())
MaybeAnimateIn();
}
PreferredSizeChanged(); PreferredSizeChanged();
} }
void HoldingSpaceTrayChildBubble::MaybeAnimateIn() {
// Don't preempt an out animation as new content will populate and be animated
// in, if any exists, once the out animation completes.
if (is_animating_out_)
return;
// Don't attempt to animate in this bubble unnecessarily as it will cause
// opacity to revert to zero before proceeding to animate in. Ensure that
// event processing is enabled as it may have been disabled while animating
// this bubble out.
if (layer()->GetTargetOpacity() == 1.f) {
SetCanProcessEventsWithinSubtree(true);
return;
}
// NOTE: `animate_in_observer` is deleted after `OnAnimateInCompleted()`.
ui::CallbackLayerAnimationObserver* animate_in_observer =
new ui::CallbackLayerAnimationObserver(DeleteObserverAfterRunning(
base::BindOnce(&HoldingSpaceTrayChildBubble::OnAnimateInCompleted,
weak_factory_.GetWeakPtr())));
AnimateIn(animate_in_observer);
animate_in_observer->SetActive();
}
void HoldingSpaceTrayChildBubble::MaybeAnimateOut() {
if (is_animating_out_)
return;
// Child bubbles should not process events when being animated as the model
// objects backing their views may no longer exist. Event processing will be
// re-enabled on animation completion.
SetCanProcessEventsWithinSubtree(false);
// NOTE: `animate_out_observer` is deleted after `OnAnimateOutCompleted()`.
ui::CallbackLayerAnimationObserver* animate_out_observer =
new ui::CallbackLayerAnimationObserver(DeleteObserverAfterRunning(
base::BindOnce(&HoldingSpaceTrayChildBubble::OnAnimateOutCompleted,
weak_factory_.GetWeakPtr())));
AnimateOut(animate_out_observer);
animate_out_observer->SetActive();
}
void HoldingSpaceTrayChildBubble::AnimateIn(
ui::LayerAnimationObserver* observer) {
DCHECK(!is_animating_out_);
// Delay in animations to give the holding space bubble time to animate its
// layout changes. This ensures that there is sufficient space to display the
// child bubble before it is displayed to the user.
const base::TimeDelta animation_delay = kAnimationDuration;
holding_space_util::AnimateIn(this, kAnimationDuration, animation_delay,
observer);
}
void HoldingSpaceTrayChildBubble::AnimateOut(
ui::LayerAnimationObserver* observer) {
DCHECK(!is_animating_out_);
is_animating_out_ = true;
// Animation is only necessary if this view is visible to the user.
const base::TimeDelta animation_duration =
IsDrawn() ? kAnimationDuration : base::TimeDelta();
holding_space_util::AnimateOut(this, animation_duration, observer);
}
void HoldingSpaceTrayChildBubble::OnAnimateInCompleted(bool aborted) {
// Restore event processing once the child bubble has fully animated in. Its
// contents are guaranteed to exist in the model and can be acted upon by the
// user.
if (!aborted)
SetCanProcessEventsWithinSubtree(true);
}
void HoldingSpaceTrayChildBubble::OnAnimateOutCompleted(bool aborted) {
DCHECK(is_animating_out_);
is_animating_out_ = false;
if (aborted)
return;
// Once the child bubble has animated out it is transparent but still
// "visible" as far as the views framework is concerned and so takes up layout
// space. Hide the view so that the holding space bubble will animate the
// re-layout of its view hierarchy with this child bubble taking no space.
SetVisible(false);
for (HoldingSpaceItemViewsSection* section : sections_)
section->RemoveAllHoldingSpaceItemViews();
HoldingSpaceModel* model = HoldingSpaceController::Get()->model();
if (!model || model->items().empty())
return;
std::vector<const HoldingSpaceItem*> item_ptrs;
for (const auto& item : model->items())
item_ptrs.push_back(item.get());
// Populating a `section` may cause it's visibility to change if the `model`
// contains finalized items of types which it supports. This, in turn, will
// cause visibility of this child bubble to update and animate in if needed.
for (HoldingSpaceItemViewsSection* section : sections_)
section->OnHoldingSpaceItemsAdded(item_ptrs);
}
} // namespace ash } // namespace ash
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
#include "base/scoped_observation.h" #include "base/scoped_observation.h"
#include "ui/views/view.h" #include "ui/views/view.h"
namespace ui {
class LayerAnimationObserver;
} // namespace ui
namespace ash { namespace ash {
class HoldingSpaceItemViewDelegate; class HoldingSpaceItemViewDelegate;
...@@ -64,17 +68,37 @@ class HoldingSpaceTrayChildBubble : public views::View, ...@@ -64,17 +68,37 @@ class HoldingSpaceTrayChildBubble : public views::View,
void ChildPreferredSizeChanged(views::View* child) override; void ChildPreferredSizeChanged(views::View* child) override;
void ChildVisibilityChanged(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_; HoldingSpaceItemViewDelegate* const delegate_;
// Views owned by view hierarchy. // Views owned by view hierarchy.
std::vector<HoldingSpaceItemViewsSection*> sections_; std::vector<HoldingSpaceItemViewsSection*> sections_;
// Whether or not this view is currently being animated out.
bool is_animating_out_ = false;
base::ScopedObservation<HoldingSpaceController, base::ScopedObservation<HoldingSpaceController,
HoldingSpaceControllerObserver> HoldingSpaceControllerObserver>
controller_observer_{this}; controller_observer_{this};
base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver> base::ScopedObservation<HoldingSpaceModel, HoldingSpaceModelObserver>
model_observer_{this}; model_observer_{this};
base::WeakPtrFactory<HoldingSpaceTrayChildBubble> weak_factory_{this};
}; };
} // namespace ash } // namespace ash
......
...@@ -5,11 +5,76 @@ ...@@ -5,11 +5,76 @@
#include "ash/system/holding_space/holding_space_util.h" #include "ash/system/holding_space/holding_space_util.h"
#include "ash/style/ash_color_provider.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" #include "ui/views/controls/label.h"
namespace ash { namespace ash {
namespace holding_space_util { 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, std::unique_ptr<views::Label> CreateLabel(LabelStyle style,
const base::string16& text) { const base::string16& text) {
auto label = std::make_unique<views::Label>(text); auto label = std::make_unique<views::Label>(text);
......
...@@ -6,14 +6,33 @@ ...@@ -6,14 +6,33 @@
#define ASH_SYSTEM_HOLDING_SPACE_HOLDING_SPACE_UTIL_H_ #define ASH_SYSTEM_HOLDING_SPACE_HOLDING_SPACE_UTIL_H_
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/time/time.h"
namespace ui {
class LayerAnimationObserver;
} // namespace ui
namespace views { namespace views {
class Label; class Label;
class View;
} // namespace views } // namespace views
namespace ash { namespace ash {
namespace holding_space_util { 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. // Enumeration of supported label styles.
enum class LabelStyle { enum class LabelStyle {
kBadge, 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