#include "ui/views/layout/animating_layout_manager.h"
#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/observer_list.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/animation/animation.h"
#include "ui/gfx/animation/animation_container.h"
#include "ui/gfx/animation/multi_animation.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/layout/normalized_geometry.h"
#include "ui/views/layout/proposed_layout.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
BASE_FEATURE(kAvoidUnnecessaryShouldRenderRichAnimation,
base::FEATURE_DISABLED_BY_DEFAULT);
const ChildLayout* FindChildViewInLayout(const ProposedLayout& layout,
const View* view) {
if (!view) {
return nullptr;
}
for (auto& child_layout : layout.child_layouts) {
if (child_layout.child_view == view) {
return &child_layout;
}
}
return nullptr;
}
ChildLayout* FindChildViewInLayout(ProposedLayout* layout, const View* view) {
return const_cast<ChildLayout*>(FindChildViewInLayout(*layout, view));
}
enum class LayoutFadeType {
kFadingIn,
kFadingOut,
kContinuingFade
};
ProposedLayout WithOnlyVisibleViews(const ProposedLayout layout) {
ProposedLayout result;
result.host_size = layout.host_size;
std::ranges::copy_if(layout.child_layouts,
std::back_inserter(result.child_layouts),
&ChildLayout::visible);
return result;
}
bool HaveSameVisibleViews(const ProposedLayout& l1, const ProposedLayout& l2) {
return WithOnlyVisibleViews(l1) == WithOnlyVisibleViews(l2);
}
}
struct AnimatingLayoutManager::LayoutFadeInfo {
LayoutFadeType fade_type;
raw_ptr<View> child_view = nullptr;
raw_ptr<View> prev_view = nullptr;
raw_ptr<View> next_view = nullptr;
NormalizedRect reference_bounds;
Inset1D offsets;
};
class AnimatingLayoutManager::AnimationDelegate
: public AnimationDelegateViews {
public:
explicit AnimationDelegate(AnimatingLayoutManager* layout_manager);
~AnimationDelegate() override = default;
bool ready_to_animate() const { return ready_to_animate_; }
void UpdateAnimationParameters();
void Animate();
void Reset();
void MakeReadyForAnimation();
void SetAnimationContainerForTesting(gfx::AnimationContainer* container) {
animation_->SetContainer(container);
}
private:
class ViewWidgetObserver : public ViewObserver {
public:
explicit ViewWidgetObserver(AnimationDelegate* animation_delegate)
: animation_delegate_(animation_delegate) {}
void OnViewAddedToWidget(View* observed_view) override {
animation_delegate_->MakeReadyForAnimation();
}
void OnViewIsDeleting(View* observed_view) override {
animation_delegate_->scoped_observation_.Reset();
}
private:
const raw_ptr<AnimationDelegate> animation_delegate_;
};
friend class Observer;
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationCanceled(const gfx::Animation* animation) override;
void AnimationEnded(const gfx::Animation* animation) override;
bool ready_to_animate_ = false;
bool resetting_animation_ = false;
const raw_ptr<AnimatingLayoutManager> target_layout_manager_;
std::unique_ptr<gfx::MultiAnimation> fade_in_opacity_animation_;
std::unique_ptr<gfx::MultiAnimation> fade_out_opacity_animation_;
std::unique_ptr<gfx::SlideAnimation> animation_;
const raw_ptr<gfx::AnimationContainer> container_;
ViewWidgetObserver view_widget_observer_{this};
base::ScopedObservation<View, ViewObserver> scoped_observation_{
&view_widget_observer_};
};
AnimatingLayoutManager::AnimationDelegate::AnimationDelegate(
AnimatingLayoutManager* layout_manager)
: AnimationDelegateViews(layout_manager->host_view()),
target_layout_manager_(layout_manager),
animation_(std::make_unique<gfx::SlideAnimation>(this)),
container_(new gfx::AnimationContainer()) {
animation_->SetContainer(container_);
View* const host_view = layout_manager->host_view();
DCHECK(host_view);
if (host_view->GetWidget()) {
MakeReadyForAnimation();
} else {
scoped_observation_.Observe(host_view);
}
UpdateAnimationParameters();
}
void AnimatingLayoutManager::AnimationDelegate::UpdateAnimationParameters() {
animation_->SetTweenType(target_layout_manager_->tween_type());
animation_->SetSlideDuration(target_layout_manager_->animation_duration());
base::TimeDelta opacity_animation_duration =
std::min(target_layout_manager_->animation_duration(),
target_layout_manager_->opacity_animation_duration());
if (!opacity_animation_duration.is_zero()) {
fade_in_opacity_animation_ =
std::make_unique<gfx::MultiAnimation>(gfx::MultiAnimation::Parts{
{target_layout_manager_->animation_duration() -
opacity_animation_duration,
gfx::Tween::Type::LINEAR, 0.0, 0.0},
{opacity_animation_duration,
target_layout_manager_->opacity_tween_type(), 0.0, 1.0}});
fade_in_opacity_animation_->SetContainer(container_);
fade_in_opacity_animation_->set_continuous(false);
const base::TimeDelta fade_out_opacity_duration =
target_layout_manager_->animation_duration() ==
opacity_animation_duration
? opacity_animation_duration
: target_layout_manager_->animation_duration() -
opacity_animation_duration;
fade_out_opacity_animation_ =
std::make_unique<gfx::MultiAnimation>(gfx::MultiAnimation::Parts{
{fade_out_opacity_duration,
target_layout_manager_->opacity_tween_type(), 1.0, 0.0},
{target_layout_manager_->animation_duration() -
fade_out_opacity_duration,
gfx::Tween::Type::LINEAR, 0.0, 0.0}});
fade_out_opacity_animation_->SetContainer(container_);
fade_out_opacity_animation_->set_continuous(false);
}
}
void AnimatingLayoutManager::AnimationDelegate::Animate() {
DCHECK(ready_to_animate_);
Reset();
animation_->Show();
if (target_layout_manager_->opacity_animation_duration() >
base::Milliseconds(0)) {
if (fade_in_opacity_animation_.get()) {
fade_in_opacity_animation_->Start();
}
if (fade_out_opacity_animation_.get()) {
fade_out_opacity_animation_->Start();
}
}
}
void AnimatingLayoutManager::AnimationDelegate::Reset() {
if (!ready_to_animate_) {
return;
}
base::AutoReset<bool> setter(&resetting_animation_, true);
animation_->Reset();
if (fade_in_opacity_animation_.get()) {
fade_in_opacity_animation_->Stop();
}
if (fade_out_opacity_animation_.get()) {
fade_out_opacity_animation_->Stop();
}
}
void AnimatingLayoutManager::AnimationDelegate::MakeReadyForAnimation() {
if (!ready_to_animate_) {
target_layout_manager_->ResetLayout();
ready_to_animate_ = true;
scoped_observation_.Reset();
}
}
void AnimatingLayoutManager::AnimationDelegate::AnimationProgressed(
const gfx::Animation* animation) {
DCHECK(animation_.get() == animation);
double fade_in_opacity = fade_in_opacity_animation_.get()
? fade_in_opacity_animation_->GetCurrentValue()
: 1.0;
double fade_out_opacity = fade_out_opacity_animation_.get()
? fade_out_opacity_animation_->GetCurrentValue()
: 0.0;
target_layout_manager_->AnimateTo(animation->GetCurrentValue(),
fade_in_opacity, fade_out_opacity);
}
void AnimatingLayoutManager::AnimationDelegate::AnimationCanceled(
const gfx::Animation* animation) {
AnimationEnded(animation);
}
void AnimatingLayoutManager::AnimationDelegate::AnimationEnded(
const gfx::Animation* animation) {
if (resetting_animation_) {
return;
}
DCHECK(animation_.get() == animation);
target_layout_manager_->AnimateTo(1.0, 1.0, 0.0);
}
AnimatingLayoutManager::AnimatingLayoutManager() = default;
AnimatingLayoutManager::~AnimatingLayoutManager() = default;
AnimatingLayoutManager& AnimatingLayoutManager::SetBoundsAnimationMode(
BoundsAnimationMode bounds_animation_mode) {
if (bounds_animation_mode_ != bounds_animation_mode) {
bounds_animation_mode_ = bounds_animation_mode;
ResetLayout();
}
return *this;
}
AnimatingLayoutManager& AnimatingLayoutManager::SetAnimationDuration(
base::TimeDelta animation_duration) {
DCHECK_GE(animation_duration, base::TimeDelta());
animation_duration_ = animation_duration;
if (animation_delegate_) {
animation_delegate_->UpdateAnimationParameters();
}
return *this;
}
AnimatingLayoutManager& AnimatingLayoutManager::SetTweenType(
gfx::Tween::Type tween_type) {
tween_type_ = tween_type;
if (animation_delegate_) {
animation_delegate_->UpdateAnimationParameters();
}
return *this;
}
AnimatingLayoutManager& AnimatingLayoutManager::SetOpacityAnimationDuration(
base::TimeDelta animation_duration) {
DCHECK_GE(animation_duration, base::TimeDelta());
opacity_animation_duration_ = animation_duration;
if (animation_delegate_) {
animation_delegate_->UpdateAnimationParameters();
}
return *this;
}
AnimatingLayoutManager& AnimatingLayoutManager::SetOpacityTweenType(
gfx::Tween::Type tween_type) {
opacity_tween_type_ = tween_type;
if (animation_delegate_) {
animation_delegate_->UpdateAnimationParameters();
}
return *this;
}
AnimatingLayoutManager& AnimatingLayoutManager::SetOrientation(
LayoutOrientation orientation) {
if (orientation_ != orientation) {
orientation_ = orientation;
ResetLayout();
}
return *this;
}
AnimatingLayoutManager& AnimatingLayoutManager::SetDefaultFadeMode(
FadeInOutMode default_fade_mode) {
default_fade_mode_ = default_fade_mode;
return *this;
}
void AnimatingLayoutManager::ResetLayout() {
if (!target_layout_manager()) {
return;
}
ResetLayoutToTargetSize();
InvalidateHost(false);
}
void AnimatingLayoutManager::FadeOut(View* child_view) {
DCHECK(child_view);
DCHECK(child_view->parent());
DCHECK_EQ(host_view(), child_view->parent());
if (!CanBeVisible(child_view)) {
InvalidateHost(true);
return;
}
const ChildLayout* const current_layout =
FindChildViewInLayout(current_layout_, child_view);
if ((!current_layout || !current_layout->visible) &&
child_view->GetVisible()) {
SetViewVisibility(child_view, false);
}
static_cast<LayoutManager*>(this)->ViewVisibilitySet(
host_view(), child_view, child_view->GetVisible(), false);
}
void AnimatingLayoutManager::FadeIn(View* child_view) {
DCHECK(child_view);
DCHECK(child_view->parent());
DCHECK_EQ(host_view(), child_view->parent());
if (CanBeVisible(child_view)) {
InvalidateHost(true);
return;
}
static_cast<LayoutManager*>(this)->ViewVisibilitySet(
host_view(), child_view, child_view->GetVisible(), true);
}
void AnimatingLayoutManager::AddObserver(Observer* observer) {
if (!observers_.HasObserver(observer)) {
observers_.AddObserver(observer);
}
}
void AnimatingLayoutManager::RemoveObserver(Observer* observer) {
if (observers_.HasObserver(observer)) {
observers_.RemoveObserver(observer);
}
}
bool AnimatingLayoutManager::HasObserver(Observer* observer) const {
return observers_.HasObserver(observer);
}
gfx::Size AnimatingLayoutManager::GetPreferredSize(const View* host) const {
if (!target_layout_manager()) {
return gfx::Size();
}
if (base::FeatureList::IsEnabled(
kAvoidUnnecessaryShouldRenderRichAnimation) &&
bounds_animation_mode_ == BoundsAnimationMode::kUseHostBounds) {
return target_layout_manager()->GetPreferredSize(host);
}
if (!gfx::Animation::ShouldRenderRichAnimation()) {
return target_layout_manager()->GetPreferredSize(host);
}
switch (bounds_animation_mode_) {
case BoundsAnimationMode::kUseHostBounds: {
CHECK(!base::FeatureList::IsEnabled(
kAvoidUnnecessaryShouldRenderRichAnimation));
return target_layout_manager()->GetPreferredSize(host);
}
case BoundsAnimationMode::kAnimateMainAxis: {
gfx::Size result = current_layout_.host_size;
SetCrossAxis(
&result, orientation(),
GetCrossAxis(orientation(),
target_layout_manager()->GetPreferredSize(host)));
return result;
}
case BoundsAnimationMode::kAnimateBothAxes: {
return current_layout_.host_size;
}
}
}
gfx::Size AnimatingLayoutManager::GetPreferredSize(
const View* host,
const SizeBounds& available_size) const {
if (!target_layout_manager()) {
return gfx::Size();
}
if (!gfx::Animation::ShouldRenderRichAnimation()) {
return target_layout_manager()->GetPreferredSize(host, available_size);
}
switch (bounds_animation_mode_) {
case BoundsAnimationMode::kUseHostBounds:
return target_layout_manager()->GetPreferredSize(host, available_size);
case BoundsAnimationMode::kAnimateMainAxis: {
gfx::Size result = current_layout_.host_size;
SetCrossAxis(
&result, orientation(),
GetCrossAxis(orientation(), target_layout_manager()->GetPreferredSize(
host, available_size)));
return result;
}
case BoundsAnimationMode::kAnimateBothAxes:
return current_layout_.host_size;
}
}
gfx::Size AnimatingLayoutManager::GetMinimumSize(const View* host) const {
if (!target_layout_manager()) {
return gfx::Size();
}
gfx::Size minimum_size = target_layout_manager()->GetMinimumSize(host);
switch (bounds_animation_mode_) {
case BoundsAnimationMode::kUseHostBounds:
break;
case BoundsAnimationMode::kAnimateMainAxis:
SetMainAxis(
&minimum_size, orientation(),
std::min(GetMainAxis(orientation(), minimum_size),
GetMainAxis(orientation(), current_layout_.host_size)));
break;
case BoundsAnimationMode::kAnimateBothAxes:
minimum_size.SetToMin(current_layout_.host_size);
break;
}
return minimum_size;
}
int AnimatingLayoutManager::GetPreferredHeightForWidth(const View* host,
int width) const {
if (!target_layout_manager()) {
return 0;
}
if (bounds_animation_mode_ == BoundsAnimationMode::kAnimateBothAxes ||
(bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis &&
orientation() == LayoutOrientation::kVertical)) {
return current_layout_.host_size.height();
}
return target_layout_manager()->GetPreferredHeightForWidth(host, width);
}
std::vector<raw_ptr<View, VectorExperimental>>
AnimatingLayoutManager::GetChildViewsInPaintOrder(const View* host) const {
DCHECK_EQ(host_view(), host);
if (!is_animating()) {
return LayoutManagerBase::GetChildViewsInPaintOrder(host);
}
std::vector<raw_ptr<View, VectorExperimental>> result;
std::set<View*> fading;
for (const LayoutFadeInfo& fade_info : fade_infos_) {
result.push_back(fade_info.child_view.get());
fading.insert(fade_info.child_view);
}
for (View* child : host->children()) {
if (!base::Contains(fading, child)) {
result.push_back(child);
}
}
return result;
}
bool AnimatingLayoutManager::OnViewRemoved(View* host, View* view) {
std::erase_if(fade_infos_, [view](const LayoutFadeInfo& fade_info) {
return fade_info.child_view == view;
});
for (auto& fade_info : fade_infos_) {
if (fade_info.next_view == view) {
fade_info.next_view = nullptr;
}
if (fade_info.prev_view == view) {
fade_info.prev_view = nullptr;
}
}
std::erase_if(current_layout_.child_layouts,
[view](const ChildLayout& child_layout) {
return child_layout.child_view == view;
});
return LayoutManagerBase::OnViewRemoved(host, view);
}
void AnimatingLayoutManager::PostOrQueueAction(base::OnceClosure action) {
queued_actions_.push_back(std::move(action));
if (!is_animating() && !hold_queued_actions_for_layout_) {
PostQueuedActions();
}
}
FlexRule AnimatingLayoutManager::GetDefaultFlexRule() const {
return base::BindRepeating(&AnimatingLayoutManager::DefaultFlexRuleImpl,
base::Unretained(this));
}
gfx::AnimationContainer*
AnimatingLayoutManager::GetAnimationContainerForTesting() {
DCHECK(animation_delegate_);
animation_delegate_->MakeReadyForAnimation();
DCHECK(animation_delegate_->ready_to_animate());
return animation_delegate_->container();
}
void AnimatingLayoutManager::EnableAnimationForTesting() {
DCHECK(animation_delegate_);
animation_delegate_->MakeReadyForAnimation();
DCHECK(animation_delegate_->ready_to_animate());
}
ProposedLayout AnimatingLayoutManager::CalculateProposedLayout(
const SizeBounds& size_bounds) const {
NOTREACHED();
}
void AnimatingLayoutManager::OnInstalled(View* host) {
DCHECK(!animation_delegate_);
animation_delegate_ = std::make_unique<AnimationDelegate>(this);
}
bool AnimatingLayoutManager::OnViewAdded(View* host, View* view) {
if (IsChildIncludedInLayout(view) && cached_layout_size() && !is_animating_) {
const gfx::Size target_size = GetAvailableTargetLayoutSize();
ProposedLayout proposed_layout =
target_layout_manager()->GetProposedLayout(target_size);
if (HaveSameVisibleViews(current_layout_, proposed_layout)) {
SetViewVisibility(view, false);
current_layout_ = target_layout_ = proposed_layout;
return false;
}
}
return RecalculateTarget();
}
void AnimatingLayoutManager::OnLayoutChanged() {
RecalculateTarget();
}
void AnimatingLayoutManager::LayoutImpl() {
if (check_widget_ &&
(!host_view()->GetWidget() || host_view()->GetWidget()->IsClosed())) {
return;
}
const gfx::Size host_size = host_view()->size();
if (bounds_animation_mode_ == BoundsAnimationMode::kUseHostBounds) {
if (!cached_layout_size()) {
ResetLayoutToTargetSize();
} else if (host_size != *cached_layout_size()) {
RecalculateTarget();
}
} else {
const SizeBounds available_size = GetAvailableHostSize();
if (bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis &&
(!cached_layout_size() ||
GetCrossAxis(orientation(), host_size) !=
GetCrossAxis(orientation(), *cached_layout_size()))) {
last_available_host_size_ = available_size;
ResetLayoutToSize(host_size);
} else {
const SizeBound bounds_main = GetMainAxis(orientation(), available_size);
const int host_main = GetMainAxis(orientation(), host_size);
const int current_main =
GetMainAxis(orientation(), current_layout_.host_size);
if ((current_main > host_main) || (current_main > bounds_main)) {
last_available_host_size_ = available_size;
ResetLayoutToSize(host_size);
} else if (available_size != last_available_host_size_) {
RecalculateTarget();
}
}
DCHECK_EQ(available_size, last_available_host_size_);
}
ApplyLayout(current_layout_);
if (is_animating_ && current_offset_ == 1.0) {
EndAnimation();
}
if (hold_queued_actions_for_layout_ && !is_animating_) {
hold_queued_actions_for_layout_ = false;
PostQueuedActions();
}
}
void AnimatingLayoutManager::EndAnimation() {
for (auto fade_info : fade_infos_) {
if (fade_info.fade_type != LayoutFadeType::kFadingOut &&
fade_info.child_view->layer()) {
fade_info.child_view->layer()->SetOpacity(1);
}
}
fade_infos_.clear();
hold_queued_actions_for_layout_ = true;
if (std::exchange(is_animating_, false)) {
NotifyIsAnimatingChanged();
}
}
void AnimatingLayoutManager::ResetLayoutToTargetSize() {
ResetLayoutToSize(GetAvailableTargetLayoutSize());
}
void AnimatingLayoutManager::ResetLayoutToSize(const gfx::Size& target_size) {
if (animation_delegate_) {
animation_delegate_->Reset();
}
ResolveFades();
target_layout_ = target_layout_manager()->GetProposedLayout(target_size);
current_layout_ = target_layout_;
starting_layout_ = current_layout_;
current_offset_ = 1.0;
set_cached_layout_size(target_size);
EndAnimation();
}
bool AnimatingLayoutManager::RecalculateTarget() {
constexpr double kResetAnimationThreshold = 0.8;
if (!target_layout_manager()) {
return false;
}
if (!cached_layout_size() || !animation_delegate_ ||
!animation_delegate_->ready_to_animate()) {
ResetLayoutToTargetSize();
return true;
}
const gfx::Size target_size = GetAvailableTargetLayoutSize();
set_cached_layout_size(target_size);
const ProposedLayout proposed_layout =
target_layout_manager()->GetProposedLayout(target_size);
if (target_layout_ == proposed_layout) {
return false;
}
target_layout_ = proposed_layout;
if (current_offset_ > kResetAnimationThreshold) {
starting_layout_ = current_layout_;
starting_offset_ = 0.0;
current_offset_ = 0.0;
animation_delegate_->Animate();
if (!is_animating_) {
is_animating_ = true;
NotifyIsAnimatingChanged();
}
} else if (current_offset_ > starting_offset_) {
starting_layout_ = current_layout_;
starting_offset_ = current_offset_;
} else if (starting_layout_ == target_layout_) {
ResetLayoutToSize(target_size);
return false;
}
CalculateFadeInfos();
if (gfx::Animation::ShouldRenderRichAnimation()) {
UpdateCurrentLayout(0.0, 0.0, 1.0);
} else {
ResetLayoutToSize(target_size);
}
return true;
}
void AnimatingLayoutManager::AnimateTo(double value,
double fade_in_opacity,
double fade_out_opacity) {
DCHECK_GE(value, 0.0);
DCHECK_LE(value, 1.0);
DCHECK_GE(value, starting_offset_);
DCHECK_GE(value, current_offset_);
if (current_offset_ == value) {
return;
}
current_offset_ = value;
const double percent =
(current_offset_ - starting_offset_) / (1.0 - starting_offset_);
UpdateCurrentLayout(percent, fade_in_opacity, fade_out_opacity);
InvalidateHost(false);
}
void AnimatingLayoutManager::NotifyIsAnimatingChanged() {
observers_.Notify(&Observer::OnLayoutIsAnimatingChanged, this,
is_animating());
}
void AnimatingLayoutManager::RunQueuedActions() {
run_queued_actions_is_pending_ = false;
std::vector<base::OnceClosure> actions = std::move(queued_actions_to_run_);
for (auto& action : actions) {
std::move(action).Run();
}
}
void AnimatingLayoutManager::PostQueuedActions() {
for (auto& action : queued_actions_) {
queued_actions_to_run_.push_back(std::move(action));
}
queued_actions_.clear();
if (run_queued_actions_is_pending_) {
return;
}
run_queued_actions_is_pending_ =
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&AnimatingLayoutManager::RunQueuedActions,
weak_ptr_factory_.GetWeakPtr()));
}
void AnimatingLayoutManager::UpdateCurrentLayout(double percent,
double fade_in_opacity,
double fade_out_opacity) {
current_layout_ =
ProposedLayoutBetween(percent, starting_layout_, target_layout_);
for (const LayoutFadeInfo& fade_info : fade_infos_) {
DCHECK(host_view()->GetIndexOf(fade_info.child_view).has_value());
if (fade_info.fade_type == LayoutFadeType::kContinuingFade) {
continue;
}
ChildLayout child_layout;
if (percent == 1.0) {
child_layout.child_view = fade_info.child_view;
switch (fade_info.fade_type) {
case LayoutFadeType::kFadingIn:
child_layout.visible = true;
child_layout.bounds =
Denormalize(orientation(), fade_info.reference_bounds);
if (fade_info.child_view->layer()) {
fade_info.child_view->layer()->SetOpacity(1);
}
break;
case LayoutFadeType::kFadingOut:
child_layout.visible = false;
if (child_layout.child_view->layer()) {
child_layout.child_view->layer()->SetOpacity(0);
}
break;
case LayoutFadeType::kContinuingFade:
NOTREACHED();
}
} else if (default_fade_mode_ == FadeInOutMode::kHide) {
child_layout.child_view = fade_info.child_view;
child_layout.visible = false;
} else {
const double scale_percent =
fade_info.fade_type == LayoutFadeType::kFadingIn ? percent
: 1.0 - percent;
double opacity_value = fade_info.fade_type == LayoutFadeType::kFadingIn
? fade_in_opacity
: fade_out_opacity;
switch (default_fade_mode_) {
case FadeInOutMode::kHide:
NOTREACHED();
case FadeInOutMode::kScaleFromMinimum:
child_layout = CalculateScaleFade(fade_info, scale_percent,
false);
break;
case FadeInOutMode::kScaleFromZero:
child_layout = CalculateScaleFade(fade_info, scale_percent,
true);
break;
case FadeInOutMode::kSlideFromLeadingEdge:
child_layout = CalculateSlideFade(fade_info, scale_percent,
true);
break;
case FadeInOutMode::kSlideFromTrailingEdge:
child_layout = CalculateSlideFade(fade_info, scale_percent,
false);
break;
case FadeInOutMode::kFadeAndSlideFromTrailingEdge:
child_layout = CalculateFadeAndSlideFade(fade_info, scale_percent,
opacity_value, false);
break;
}
}
ChildLayout* const to_overwrite =
FindChildViewInLayout(¤t_layout_, fade_info.child_view);
if (to_overwrite) {
*to_overwrite = child_layout;
} else {
current_layout_.child_layouts.push_back(child_layout);
}
}
}
void AnimatingLayoutManager::CalculateFadeInfos() {
std::set<const View*> previously_fading;
for (const auto& fade_info : fade_infos_) {
previously_fading.insert(fade_info.child_view);
}
fade_infos_.clear();
struct ChildInfo {
std::optional<size_t> start;
NormalizedRect start_bounds;
bool start_visible = false;
std::optional<size_t> target;
NormalizedRect target_bounds;
bool target_visible = false;
};
std::map<View*, ChildInfo> child_to_info;
std::map<int, View*> start_leading_edges;
std::map<int, View*> target_leading_edges;
for (View* child : host_view()->children()) {
if (IsChildIncludedInLayout(child, true)) {
child_to_info.emplace(child, ChildInfo());
}
}
for (size_t i = 0; i < starting_layout_.child_layouts.size(); ++i) {
const auto& child_layout = starting_layout_.child_layouts[i];
auto it = child_to_info.find(child_layout.child_view);
if (it != child_to_info.end()) {
it->second.start = i;
it->second.start_bounds = Normalize(orientation(), child_layout.bounds);
it->second.start_visible = child_layout.visible;
}
}
for (size_t i = 0; i < target_layout_.child_layouts.size(); ++i) {
const auto& child_layout = target_layout_.child_layouts[i];
auto it = child_to_info.find(child_layout.child_view);
if (it != child_to_info.end()) {
it->second.target = i;
it->second.target_bounds = Normalize(orientation(), child_layout.bounds);
it->second.target_visible = child_layout.visible;
}
}
for (View* child : host_view()->children()) {
const auto& index = child_to_info[child];
if (index.start_visible && index.target_visible &&
!base::Contains(previously_fading, child)) {
start_leading_edges.emplace(index.start_bounds.origin_main(), child);
target_leading_edges.emplace(index.target_bounds.origin_main(), child);
}
}
const NormalizedSize start_host_size =
Normalize(orientation(), starting_layout_.host_size);
const NormalizedSize target_host_size =
Normalize(orientation(), target_layout_.host_size);
for (View* child : host_view()->children()) {
const auto& current = child_to_info[child];
if (current.start_visible && !current.target_visible) {
LayoutFadeInfo fade_info;
fade_info.fade_type = LayoutFadeType::kFadingOut;
fade_info.child_view = child;
fade_info.reference_bounds = current.start_bounds;
auto next =
start_leading_edges.upper_bound(current.start_bounds.origin_main());
if (next == start_leading_edges.end()) {
fade_info.next_view = nullptr;
fade_info.offsets.set_trailing(start_host_size.main() -
current.start_bounds.max_main());
} else {
fade_info.next_view = next->second;
fade_info.offsets.set_trailing(next->first -
current.start_bounds.max_main());
}
if (next == start_leading_edges.begin()) {
fade_info.prev_view = nullptr;
fade_info.offsets.set_leading(current.start_bounds.origin_main());
} else {
auto prev = next;
--prev;
const auto& prev_info = child_to_info[prev->second];
fade_info.prev_view = prev->second;
fade_info.offsets.set_leading(current.start_bounds.origin_main() -
prev_info.start_bounds.max_main());
}
fade_infos_.push_back(fade_info);
} else if (!current.start_visible && current.target_visible) {
LayoutFadeInfo fade_info;
fade_info.fade_type = LayoutFadeType::kFadingIn;
fade_info.child_view = child;
fade_info.reference_bounds = current.target_bounds;
auto next =
target_leading_edges.upper_bound(current.target_bounds.origin_main());
if (next == target_leading_edges.end()) {
fade_info.next_view = nullptr;
fade_info.offsets.set_trailing(target_host_size.main() -
current.target_bounds.max_main());
} else {
fade_info.next_view = next->second;
fade_info.offsets.set_trailing(next->first -
current.target_bounds.max_main());
}
if (next == target_leading_edges.begin()) {
fade_info.prev_view = nullptr;
fade_info.offsets.set_leading(current.target_bounds.origin_main());
} else {
auto prev = next;
--prev;
const auto& prev_info = child_to_info[prev->second];
fade_info.prev_view = prev->second;
fade_info.offsets.set_leading(current.target_bounds.origin_main() -
prev_info.target_bounds.max_main());
}
fade_infos_.push_back(fade_info);
} else if (base::Contains(previously_fading, child)) {
LayoutFadeInfo fade_info;
fade_info.fade_type = LayoutFadeType::kContinuingFade;
fade_info.child_view = child;
fade_infos_.push_back(fade_info);
}
}
}
void AnimatingLayoutManager::ResolveFades() {
for (const LayoutFadeInfo& fade_info : fade_infos_) {
View* const child = fade_info.child_view;
if (fade_info.fade_type == LayoutFadeType::kFadingOut &&
host_view()->GetIndexOf(child).has_value() &&
!child->GetProperty(kViewIgnoredByLayoutKey) &&
!IsChildIncludedInLayout(child)) {
SetViewVisibility(child, false);
}
if (default_fade_mode_ == FadeInOutMode::kFadeAndSlideFromTrailingEdge &&
fade_info.fade_type == LayoutFadeType::kFadingIn &&
host_view()->GetIndexOf(child).has_value() && child->layer()) {
child->layer()->SetOpacity(1);
}
}
}
ChildLayout AnimatingLayoutManager::CalculateScaleFade(
const LayoutFadeInfo& fade_info,
double scale_percent,
bool scale_from_zero) const {
ChildLayout child_layout;
int leading_reference_point = 0;
if (fade_info.prev_view) {
const ChildLayout* const prev_layout =
FindChildViewInLayout(current_layout_, fade_info.prev_view);
leading_reference_point =
Normalize(orientation(), prev_layout->bounds).max_main();
}
leading_reference_point += fade_info.offsets.leading();
int trailing_reference_point;
if (fade_info.next_view) {
const ChildLayout* const next_layout =
FindChildViewInLayout(current_layout_, fade_info.next_view);
trailing_reference_point =
Normalize(orientation(), next_layout->bounds).origin_main();
} else {
trailing_reference_point =
Normalize(orientation(), current_layout_.host_size).main();
}
trailing_reference_point -= fade_info.offsets.trailing();
const int new_size = std::min(
base::ClampRound(scale_percent * fade_info.reference_bounds.size_main()),
trailing_reference_point - leading_reference_point);
child_layout.child_view = fade_info.child_view;
if (new_size > 0 &&
(scale_from_zero ||
new_size >=
Normalize(orientation(), fade_info.child_view->GetMinimumSize())
.main())) {
child_layout.visible = true;
NormalizedRect new_bounds = fade_info.reference_bounds;
switch (fade_info.fade_type) {
case LayoutFadeType::kFadingIn:
new_bounds.set_origin_main(leading_reference_point);
break;
case LayoutFadeType::kFadingOut:
new_bounds.set_origin_main(trailing_reference_point - new_size);
break;
case LayoutFadeType::kContinuingFade:
NOTREACHED();
}
new_bounds.set_size_main(new_size);
child_layout.bounds = Denormalize(orientation(), new_bounds);
}
return child_layout;
}
ChildLayout AnimatingLayoutManager::CalculateSlideFade(
const LayoutFadeInfo& fade_info,
double scale_percent,
bool slide_from_leading) const {
if (!fade_info.prev_view && !fade_info.next_view) {
return CalculateScaleFade(fade_info, scale_percent, false);
}
if (slide_from_leading && !fade_info.prev_view) {
slide_from_leading = false;
} else if (!slide_from_leading && !fade_info.next_view) {
slide_from_leading = true;
}
NormalizedRect new_bounds = fade_info.reference_bounds;
const ProposedLayout* fully_faded_layout;
switch (fade_info.fade_type) {
case LayoutFadeType::kFadingIn:
fully_faded_layout = &starting_layout_;
break;
case LayoutFadeType::kFadingOut:
fully_faded_layout = &target_layout_;
break;
case LayoutFadeType::kContinuingFade:
NOTREACHED();
}
if (slide_from_leading) {
const ChildLayout* const leading_child =
FindChildViewInLayout(*fully_faded_layout, fade_info.prev_view);
const int initial_trailing =
Normalize(orientation(), leading_child->bounds).max_main();
const int new_trailing = gfx::Tween::IntValueBetween(
scale_percent, initial_trailing, new_bounds.max_main());
new_bounds.Offset(new_trailing - new_bounds.max_main(), 0);
} else {
const ChildLayout* const trailing_child =
FindChildViewInLayout(*fully_faded_layout, fade_info.next_view);
const int initial_leading =
Normalize(orientation(), trailing_child->bounds).origin_main();
const int new_leading = gfx::Tween::IntValueBetween(
scale_percent, initial_leading, new_bounds.origin_main());
new_bounds.Offset(new_leading - new_bounds.origin_main(), 0);
}
ChildLayout child_layout;
child_layout.child_view = fade_info.child_view;
child_layout.visible = true;
child_layout.bounds = Denormalize(orientation(), new_bounds);
return child_layout;
}
ChildLayout AnimatingLayoutManager::CalculateFadeAndSlideFade(
const LayoutFadeInfo& fade_info,
double scale_percent,
double opacity_value,
bool slide_from_leading) const {
if (!fade_info.child_view->layer()) {
return CalculateSlideFade(fade_info, scale_percent, slide_from_leading);
}
NormalizedRect new_bounds = fade_info.reference_bounds;
const ProposedLayout* fully_faded_layout;
switch (fade_info.fade_type) {
case LayoutFadeType::kFadingIn:
fully_faded_layout = &starting_layout_;
break;
case LayoutFadeType::kFadingOut:
fully_faded_layout = &target_layout_;
break;
case LayoutFadeType::kContinuingFade:
NOTREACHED();
}
if (!slide_from_leading) {
const ChildLayout* const trailing_child =
FindChildViewInLayout(*fully_faded_layout, fade_info.next_view);
const int host_trailing =
Normalize(orientation(), fully_faded_layout->host_size).main();
int leading_bound =
!trailing_child
? host_trailing
: Normalize(orientation(), trailing_child->bounds).origin_main();
const int new_leading = gfx::Tween::IntValueBetween(
scale_percent, leading_bound, new_bounds.origin_main());
new_bounds.Offset(new_leading - new_bounds.origin_main(), 0);
}
ChildLayout child_layout;
child_layout.child_view = fade_info.child_view;
child_layout.visible = true;
child_layout.bounds = Denormalize(orientation(), new_bounds);
fade_info.child_view->layer()->SetOpacity(opacity_value);
return child_layout;
}
gfx::Size AnimatingLayoutManager::GetAvailableTargetLayoutSize() {
if (bounds_animation_mode_ == BoundsAnimationMode::kUseHostBounds) {
return host_view()->size();
}
const SizeBounds bounds = GetAvailableHostSize();
last_available_host_size_ = bounds;
const gfx::Size preferred_size =
target_layout_manager()->GetPreferredSize(host_view());
int width;
if (orientation() == LayoutOrientation::kVertical &&
bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis) {
width = host_view()->width();
} else {
width = bounds.width().min_of(preferred_size.width());
}
int height;
if (orientation() == LayoutOrientation::kHorizontal &&
bounds_animation_mode_ == BoundsAnimationMode::kAnimateMainAxis) {
height = host_view()->height();
} else {
height = width < preferred_size.width()
? target_layout_manager()->GetPreferredHeightForWidth(
host_view(), width)
: preferred_size.height();
height = bounds.height().min_of(height);
}
return gfx::Size(width, height);
}
gfx::Size AnimatingLayoutManager::DefaultFlexRuleImpl(
const AnimatingLayoutManager* animating_layout,
const View* view,
const SizeBounds& size_bounds) {
DCHECK_EQ(view->GetLayoutManager(), animating_layout);
const gfx::Size preferred_size = animating_layout->GetPreferredSize(view);
if (!preferred_size.IsEmpty() &&
CanFitInBounds(preferred_size, size_bounds)) {
return preferred_size;
}
if (GetMainAxis(animating_layout->orientation(), size_bounds) <= 0) {
return animating_layout->GetMinimumSize(view);
}
const LayoutManagerBase* const target_layout =
animating_layout->target_layout_manager();
const gfx::Size target_preferred = target_layout->GetPreferredSize(view);
if (CanFitInBounds(target_preferred, size_bounds)) {
return target_preferred;
}
gfx::Size size;
if (size_bounds.width().is_bounded() && size_bounds.height().is_bounded()) {
size = gfx::Size(size_bounds.width().value(), size_bounds.height().value());
} else if (size_bounds.width().is_bounded()) {
const int width = size_bounds.width().value();
size = gfx::Size(width,
target_layout->GetPreferredHeightForWidth(view, width));
} else {
DCHECK(size_bounds.height().is_bounded());
size = gfx::Size(target_preferred.width(), size_bounds.height().value());
}
return target_layout->GetProposedLayout(size).host_size;
}
}