1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/layout/animating_layout_manager.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <set>
10 #include <utility>
11 #include <vector>
12
13 #include "base/auto_reset.h"
14 #include "base/stl_util.h"
15 #include "ui/gfx/animation/animation_container.h"
16 #include "ui/gfx/animation/slide_animation.h"
17 #include "ui/views/animation/animation_delegate_views.h"
18 #include "ui/views/layout/normalized_geometry.h"
19 #include "ui/views/view.h"
20
21 namespace views {
22
23 namespace {
24
25 // Returns the ChildLayout data for the child view in the proposed layout, or
26 // nullptr if not found.
FindChildViewInLayout(const ProposedLayout & layout,const View * view)27 const ChildLayout* FindChildViewInLayout(const ProposedLayout& layout,
28 const View* view) {
29 if (!view)
30 return nullptr;
31
32 // The number of children should be small enough that this is more efficient
33 // than caching a lookup set.
34 for (auto& child_layout : layout.child_layouts) {
35 if (child_layout.child_view == view)
36 return &child_layout;
37 }
38 return nullptr;
39 }
40
FindChildViewInLayout(ProposedLayout * layout,const View * view)41 ChildLayout* FindChildViewInLayout(ProposedLayout* layout, const View* view) {
42 return const_cast<ChildLayout*>(FindChildViewInLayout(*layout, view));
43 }
44
45 // Describes the type of fade, used by LayoutFadeInfo (see below).
46 enum class LayoutFadeType {
47 // This view is fading in as part of the current animation.
48 kFadingIn,
49 // This view is fading out as part of the current animation.
50 kFadingOut,
51 // This view was fading as part of a previous animation that was interrupted
52 // and redirected. No child views in the current animation should base their
53 // position off of it.
54 kContinuingFade
55 };
56
57 } // namespace
58
59 // Holds data about a view that is fading in or out as part of an animation.
60 struct AnimatingLayoutManager::LayoutFadeInfo {
61 // How the child view is fading.
62 LayoutFadeType fade_type;
63 // The child view which is fading.
64 View* child_view = nullptr;
65 // The view previous (leading side) to the fading view which is in both the
66 // starting and target layout, or null if none.
67 View* prev_view = nullptr;
68 // The view next (trailing side) to the fading view which is in both the
69 // starting and target layout, or null if none.
70 View* next_view = nullptr;
71 // The full-size bounds, normalized to the orientation of the layout manaer,
72 // that |child_view| starts with, if fading out, or ends with, if fading in.
73 NormalizedRect reference_bounds;
74 // The offset from the end of |prev_view| and the start of |next_view|. Insets
75 // may be negative if the views overlap.
76 Inset1D offsets;
77 };
78
79 // Manages the animation and various callbacks from the animation system that
80 // are required to update the layout during animations.
81 class AnimatingLayoutManager::AnimationDelegate
82 : public AnimationDelegateViews {
83 public:
84 explicit AnimationDelegate(AnimatingLayoutManager* layout_manager);
85 ~AnimationDelegate() override = default;
86
87 // Returns true after the host view is added to a widget or animation has been
88 // enabled by a unit test.
89 //
90 // Before that, animation is not possible, so all changes to the host view
91 // should result in the host view's layout being snapped directly to the
92 // target layout.
ready_to_animate() const93 bool ready_to_animate() const { return ready_to_animate_; }
94
95 // Pushes animation configuration (tween type, duration) through to the
96 // animation itself.
97 void UpdateAnimationParameters();
98
99 // Starts the animation.
100 void Animate();
101
102 // Cancels and resets the current animation (if any).
103 void Reset();
104
105 // If the current layout is not yet ready to animate, transitions into the
106 // ready-to-animate state, possibly resetting the current layout and
107 // invalidating the host to make sure the layout is up to date.
108 void MakeReadyForAnimation();
109
110 private:
111 // Observer used to watch for the host view being parented to a widget.
112 class ViewWidgetObserver : public ViewObserver {
113 public:
ViewWidgetObserver(AnimationDelegate * animation_delegate)114 explicit ViewWidgetObserver(AnimationDelegate* animation_delegate)
115 : animation_delegate_(animation_delegate) {}
116
OnViewAddedToWidget(View * observed_view)117 void OnViewAddedToWidget(View* observed_view) override {
118 animation_delegate_->MakeReadyForAnimation();
119 }
120
OnViewIsDeleting(View * observed_view)121 void OnViewIsDeleting(View* observed_view) override {
122 if (animation_delegate_->scoped_observer_.IsObserving(observed_view))
123 animation_delegate_->scoped_observer_.Remove(observed_view);
124 }
125
126 private:
127 AnimationDelegate* const animation_delegate_;
128 };
129 friend class Observer;
130
131 // AnimationDelegateViews:
132 void AnimationProgressed(const gfx::Animation* animation) override;
133 void AnimationCanceled(const gfx::Animation* animation) override;
134 void AnimationEnded(const gfx::Animation* animation) override;
135
136 bool ready_to_animate_ = false;
137 bool resetting_animation_ = false;
138 AnimatingLayoutManager* const target_layout_manager_;
139 std::unique_ptr<gfx::SlideAnimation> animation_;
140 ViewWidgetObserver view_widget_observer_{this};
141 ScopedObserver<View, ViewObserver> scoped_observer_{&view_widget_observer_};
142 };
143
AnimationDelegate(AnimatingLayoutManager * layout_manager)144 AnimatingLayoutManager::AnimationDelegate::AnimationDelegate(
145 AnimatingLayoutManager* layout_manager)
146 : AnimationDelegateViews(layout_manager->host_view()),
147 target_layout_manager_(layout_manager),
148 animation_(std::make_unique<gfx::SlideAnimation>(this)) {
149 animation_->SetContainer(new gfx::AnimationContainer());
150 View* const host_view = layout_manager->host_view();
151 DCHECK(host_view);
152 if (host_view->GetWidget())
153 MakeReadyForAnimation();
154 else
155 scoped_observer_.Add(host_view);
156 UpdateAnimationParameters();
157 }
158
UpdateAnimationParameters()159 void AnimatingLayoutManager::AnimationDelegate::UpdateAnimationParameters() {
160 animation_->SetTweenType(target_layout_manager_->tween_type());
161 animation_->SetSlideDuration(target_layout_manager_->animation_duration());
162 }
163
Animate()164 void AnimatingLayoutManager::AnimationDelegate::Animate() {
165 DCHECK(ready_to_animate_);
166 Reset();
167 animation_->Show();
168 }
169
Reset()170 void AnimatingLayoutManager::AnimationDelegate::Reset() {
171 if (!ready_to_animate_)
172 return;
173 base::AutoReset<bool> setter(&resetting_animation_, true);
174 animation_->Reset();
175 }
176
MakeReadyForAnimation()177 void AnimatingLayoutManager::AnimationDelegate::MakeReadyForAnimation() {
178 if (!ready_to_animate_) {
179 target_layout_manager_->ResetLayout();
180 ready_to_animate_ = true;
181 if (scoped_observer_.IsObserving(target_layout_manager_->host_view()))
182 scoped_observer_.Remove(target_layout_manager_->host_view());
183 }
184 }
185
AnimationProgressed(const gfx::Animation * animation)186 void AnimatingLayoutManager::AnimationDelegate::AnimationProgressed(
187 const gfx::Animation* animation) {
188 DCHECK(animation_.get() == animation);
189 target_layout_manager_->AnimateTo(animation->GetCurrentValue());
190 }
191
AnimationCanceled(const gfx::Animation * animation)192 void AnimatingLayoutManager::AnimationDelegate::AnimationCanceled(
193 const gfx::Animation* animation) {
194 AnimationEnded(animation);
195 }
196
AnimationEnded(const gfx::Animation * animation)197 void AnimatingLayoutManager::AnimationDelegate::AnimationEnded(
198 const gfx::Animation* animation) {
199 if (resetting_animation_)
200 return;
201 DCHECK(animation_.get() == animation);
202 target_layout_manager_->AnimateTo(1.0);
203 }
204
205 // AnimatingLayoutManager:
206
207 AnimatingLayoutManager::AnimatingLayoutManager() = default;
208 AnimatingLayoutManager::~AnimatingLayoutManager() = default;
209
SetShouldAnimateBounds(bool should_animate_bounds)210 AnimatingLayoutManager& AnimatingLayoutManager::SetShouldAnimateBounds(
211 bool should_animate_bounds) {
212 if (should_animate_bounds_ != should_animate_bounds) {
213 should_animate_bounds_ = should_animate_bounds;
214 ResetLayout();
215 }
216 return *this;
217 }
218
SetAnimationDuration(base::TimeDelta animation_duration)219 AnimatingLayoutManager& AnimatingLayoutManager::SetAnimationDuration(
220 base::TimeDelta animation_duration) {
221 DCHECK_GE(animation_duration, base::TimeDelta());
222 animation_duration_ = animation_duration;
223 if (animation_delegate_)
224 animation_delegate_->UpdateAnimationParameters();
225 return *this;
226 }
227
SetTweenType(gfx::Tween::Type tween_type)228 AnimatingLayoutManager& AnimatingLayoutManager::SetTweenType(
229 gfx::Tween::Type tween_type) {
230 tween_type_ = tween_type;
231 if (animation_delegate_)
232 animation_delegate_->UpdateAnimationParameters();
233 return *this;
234 }
235
SetOrientation(LayoutOrientation orientation)236 AnimatingLayoutManager& AnimatingLayoutManager::SetOrientation(
237 LayoutOrientation orientation) {
238 if (orientation_ != orientation) {
239 orientation_ = orientation;
240 ResetLayout();
241 }
242 return *this;
243 }
244
SetDefaultFadeMode(FadeInOutMode default_fade_mode)245 AnimatingLayoutManager& AnimatingLayoutManager::SetDefaultFadeMode(
246 FadeInOutMode default_fade_mode) {
247 default_fade_mode_ = default_fade_mode;
248 return *this;
249 }
250
ResetLayout()251 void AnimatingLayoutManager::ResetLayout() {
252 if (!target_layout_manager())
253 return;
254 ResetLayoutToTargetSize();
255 InvalidateHost(false);
256 }
257
FadeOut(View * child_view)258 void AnimatingLayoutManager::FadeOut(View* child_view) {
259 DCHECK(child_view);
260 DCHECK(child_view->parent());
261 DCHECK_EQ(host_view(), child_view->parent());
262
263 // If the view in question is already incapable of being visible, either:
264 // 1. the view wasn't capable of being visible in the first place
265 // 2. the view is already invisible because the layout has chosen to hide it
266 // In either case, it is generally useful to recalculate the layout just in
267 // case the caller has made other changes that won't directly cause a layout -
268 // for example, the user has changed a layout-affecting class property. Worst
269 // case this ends up being a slightly costly no-op but we don't expect this
270 // method to be called very often.
271 if (!CanBeVisible(child_view)) {
272 InvalidateHost(true);
273 return;
274 }
275
276 // Indicate that the view should become hidden in the layout without
277 // immediately changing its visibility. Instead, this triggers an animation
278 // which results in the view being hidden.
279 //
280 // This method is typically only called from View and has a private final
281 // implementation in LayoutManagerBase so we have to cast to call it.
282 static_cast<LayoutManager*>(this)->ViewVisibilitySet(
283 host_view(), child_view, child_view->GetVisible(), false);
284 }
285
FadeIn(View * child_view)286 void AnimatingLayoutManager::FadeIn(View* child_view) {
287 DCHECK(child_view);
288 DCHECK(child_view->parent());
289 DCHECK_EQ(host_view(), child_view->parent());
290
291 // If the view in question is already capable of being visible, either:
292 // 1. the view is already visible so this is a no-op
293 // 2. the view is not visible because the target layout has chosen to hide it
294 // In either case, it is generally useful to recalculate the layout just in
295 // case the caller has made other changes that won't directly cause a layout -
296 // for example, the user has changed a layout-affecting class property. Worst
297 // case this ends up being a slightly costly no-op but we don't expect this
298 // method to be called very often.
299 if (CanBeVisible(child_view)) {
300 InvalidateHost(true);
301 return;
302 }
303
304 // Indicate that the view should become visible in the layout without
305 // immediately changing its visibility. Instead, this triggers an animation
306 // which results in the view being shown.
307 //
308 // This method is typically only called from View and has a private final
309 // implementation in LayoutManagerBase so we have to cast to call it.
310 static_cast<LayoutManager*>(this)->ViewVisibilitySet(
311 host_view(), child_view, child_view->GetVisible(), true);
312 }
313
AddObserver(Observer * observer)314 void AnimatingLayoutManager::AddObserver(Observer* observer) {
315 if (!observers_.HasObserver(observer))
316 observers_.AddObserver(observer);
317 }
318
RemoveObserver(Observer * observer)319 void AnimatingLayoutManager::RemoveObserver(Observer* observer) {
320 if (observers_.HasObserver(observer))
321 observers_.RemoveObserver(observer);
322 }
323
HasObserver(Observer * observer) const324 bool AnimatingLayoutManager::HasObserver(Observer* observer) const {
325 return observers_.HasObserver(observer);
326 }
327
GetPreferredSize(const View * host) const328 gfx::Size AnimatingLayoutManager::GetPreferredSize(const View* host) const {
329 if (!target_layout_manager())
330 return gfx::Size();
331
332 return should_animate_bounds_
333 ? current_layout_.host_size
334 : target_layout_manager()->GetPreferredSize(host);
335 }
336
GetMinimumSize(const View * host) const337 gfx::Size AnimatingLayoutManager::GetMinimumSize(const View* host) const {
338 if (!target_layout_manager())
339 return gfx::Size();
340 // TODO(dfried): consider cases where the minimum size might not be just the
341 // minimum size of the embedded layout.
342 gfx::Size minimum_size = target_layout_manager()->GetMinimumSize(host);
343 if (should_animate_bounds_)
344 minimum_size.SetToMin(current_layout_.host_size);
345 return minimum_size;
346 }
347
GetPreferredHeightForWidth(const View * host,int width) const348 int AnimatingLayoutManager::GetPreferredHeightForWidth(const View* host,
349 int width) const {
350 if (!target_layout_manager())
351 return 0;
352
353 // TODO(dfried): revisit this computation.
354 return should_animate_bounds_
355 ? current_layout_.host_size.height()
356 : target_layout_manager()->GetPreferredHeightForWidth(host, width);
357 }
358
GetChildViewsInPaintOrder(const View * host) const359 std::vector<View*> AnimatingLayoutManager::GetChildViewsInPaintOrder(
360 const View* host) const {
361 DCHECK_EQ(host_view(), host);
362
363 if (!is_animating())
364 return LayoutManagerBase::GetChildViewsInPaintOrder(host);
365
366 std::vector<View*> result;
367 std::set<View*> fading;
368
369 // Put all fading views to the front of the list (back of the Z-order).
370 for (const LayoutFadeInfo& fade_info : fade_infos_) {
371 result.push_back(fade_info.child_view);
372 fading.insert(fade_info.child_view);
373 }
374
375 // Add the result of the views.
376 for (View* child : host->children()) {
377 if (!base::Contains(fading, child))
378 result.push_back(child);
379 }
380
381 return result;
382 }
383
OnViewRemoved(View * host,View * view)384 bool AnimatingLayoutManager::OnViewRemoved(View* host, View* view) {
385 // Remove any fade infos corresponding to the removed view.
386 base::EraseIf(fade_infos_, [view](const LayoutFadeInfo& fade_info) {
387 return fade_info.child_view == view;
388 });
389
390 // Remove any elements in the current layout corresponding to the removed
391 // view.
392 base::EraseIf(current_layout_.child_layouts,
393 [view](const ChildLayout& child_layout) {
394 return child_layout.child_view == view;
395 });
396
397 return LayoutManagerBase::OnViewRemoved(host, view);
398 }
399
PostOrQueueAction(base::OnceClosure action)400 void AnimatingLayoutManager::PostOrQueueAction(base::OnceClosure action) {
401 queued_actions_.push_back(std::move(action));
402 if (!is_animating())
403 PostQueuedActions();
404 }
405
GetDefaultFlexRule() const406 FlexRule AnimatingLayoutManager::GetDefaultFlexRule() const {
407 return base::BindRepeating(&AnimatingLayoutManager::DefaultFlexRuleImpl,
408 base::Unretained(this));
409 }
410
411 gfx::AnimationContainer*
GetAnimationContainerForTesting()412 AnimatingLayoutManager::GetAnimationContainerForTesting() {
413 DCHECK(animation_delegate_);
414 animation_delegate_->MakeReadyForAnimation();
415 DCHECK(animation_delegate_->ready_to_animate());
416 return animation_delegate_->container();
417 }
418
EnableAnimationForTesting()419 void AnimatingLayoutManager::EnableAnimationForTesting() {
420 DCHECK(animation_delegate_);
421 animation_delegate_->MakeReadyForAnimation();
422 DCHECK(animation_delegate_->ready_to_animate());
423 }
424
CalculateProposedLayout(const SizeBounds & size_bounds) const425 ProposedLayout AnimatingLayoutManager::CalculateProposedLayout(
426 const SizeBounds& size_bounds) const {
427 // This class directly overrides Layout() so GetProposedLayout() and
428 // CalculateProposedLayout() are not called.
429 NOTREACHED();
430 return ProposedLayout();
431 }
432
OnInstalled(View * host)433 void AnimatingLayoutManager::OnInstalled(View* host) {
434 DCHECK(!animation_delegate_);
435 animation_delegate_ = std::make_unique<AnimationDelegate>(this);
436 }
437
OnLayoutChanged()438 void AnimatingLayoutManager::OnLayoutChanged() {
439 // This replaces the normal behavior of clearing cached layouts.
440 RecalculateTarget();
441 }
442
LayoutImpl()443 void AnimatingLayoutManager::LayoutImpl() {
444 // Changing the size of a view directly will lead to a layout call rather
445 // than an invalidation. This should reset the layout (but see the note in
446 // RecalculateTarget() below).
447 const gfx::Size host_size = host_view()->size();
448 if (should_animate_bounds_) {
449 // Reset the layout immediately if the current or target layout exceeds the
450 // host size or the available space.
451 const SizeBounds available_size = GetAvailableHostSize();
452 const base::Optional<int> bounds_main =
453 GetMainAxis(orientation(), available_size);
454 const int host_main = GetMainAxis(orientation(), host_size);
455 const int current_main =
456 GetMainAxis(orientation(), current_layout_.host_size);
457 if (current_main > host_main ||
458 (bounds_main && current_main > *bounds_main)) {
459 last_available_host_size_ = available_size;
460 ResetLayoutToSize(host_size);
461 } else if (available_size != last_available_host_size_) {
462 // May need to re-trigger animation if our bounds were relaxed; let us
463 // expand into the new available space.
464 RecalculateTarget();
465 }
466
467 // Verify that the last available size has been updated.
468 DCHECK_EQ(available_size, last_available_host_size_);
469
470 } else if (!cached_layout_size() || host_size != *cached_layout_size()) {
471 // Host size changed, so reset the layout.
472 ResetLayoutToTargetSize();
473 }
474
475 ApplyLayout(current_layout_);
476
477 // Send animating stopped events on layout so the current layout during the
478 // event represents the final state instead of an intermediate state.
479 if (is_animating_ && current_offset_ == 1.0)
480 OnAnimationEnded();
481 }
482
OnAnimationEnded()483 void AnimatingLayoutManager::OnAnimationEnded() {
484 DCHECK(is_animating_);
485 is_animating_ = false;
486 fade_infos_.clear();
487 PostQueuedActions();
488 NotifyIsAnimatingChanged();
489 }
490
ResetLayoutToTargetSize()491 void AnimatingLayoutManager::ResetLayoutToTargetSize() {
492 ResetLayoutToSize(GetAvailableTargetLayoutSize());
493 }
494
ResetLayoutToSize(const gfx::Size & target_size)495 void AnimatingLayoutManager::ResetLayoutToSize(const gfx::Size& target_size) {
496 if (animation_delegate_)
497 animation_delegate_->Reset();
498
499 ResolveFades();
500
501 target_layout_ = target_layout_manager()->GetProposedLayout(target_size);
502 current_layout_ = target_layout_;
503 starting_layout_ = current_layout_;
504 fade_infos_.clear();
505 current_offset_ = 1.0;
506 set_cached_layout_size(target_size);
507
508 if (is_animating_)
509 OnAnimationEnded();
510 }
511
RecalculateTarget()512 bool AnimatingLayoutManager::RecalculateTarget() {
513 constexpr double kResetAnimationThreshold = 0.8;
514
515 if (!target_layout_manager())
516 return false;
517
518 if (!cached_layout_size() || !animation_delegate_ ||
519 !animation_delegate_->ready_to_animate()) {
520 ResetLayoutToTargetSize();
521 return true;
522 }
523
524 const gfx::Size target_size = GetAvailableTargetLayoutSize();
525
526 // For layouts that are confined to available space, changing the available
527 // space causes a fresh layout, not an animation.
528 // TODO(dfried): define a way for views to animate into and out of empty
529 // space as adjacent child views appear/disappear. This will be useful in
530 // animating tab titles, which currently slide over when the favicon
531 // disappears.
532 if (!should_animate_bounds_ && *cached_layout_size() != target_size) {
533 ResetLayoutToSize(target_size);
534 return true;
535 }
536
537 set_cached_layout_size(target_size);
538
539 // If there has been no appreciable change in layout, there's no reason to
540 // start or update an animation.
541 const ProposedLayout proposed_layout =
542 target_layout_manager()->GetProposedLayout(target_size);
543 if (target_layout_ == proposed_layout)
544 return false;
545
546 target_layout_ = proposed_layout;
547 if (current_offset_ > kResetAnimationThreshold) {
548 starting_layout_ = current_layout_;
549 starting_offset_ = 0.0;
550 current_offset_ = 0.0;
551 animation_delegate_->Animate();
552 if (!is_animating_) {
553 is_animating_ = true;
554 NotifyIsAnimatingChanged();
555 }
556 } else if (current_offset_ > starting_offset_) {
557 // Only update the starting layout if the animation has progressed. This has
558 // the effect of "batching up" changes that all happen on the same frame,
559 // keeping the same starting point. (A common example of this is multiple
560 // child views' visibility changing.)
561 starting_layout_ = current_layout_;
562 starting_offset_ = current_offset_;
563 }
564 CalculateFadeInfos();
565 UpdateCurrentLayout(0.0);
566
567 return true;
568 }
569
AnimateTo(double value)570 void AnimatingLayoutManager::AnimateTo(double value) {
571 DCHECK_GE(value, 0.0);
572 DCHECK_LE(value, 1.0);
573 DCHECK_GE(value, starting_offset_);
574 DCHECK_GE(value, current_offset_);
575 if (current_offset_ == value)
576 return;
577 current_offset_ = value;
578 const double percent =
579 (current_offset_ - starting_offset_) / (1.0 - starting_offset_);
580 UpdateCurrentLayout(percent);
581 InvalidateHost(false);
582 }
583
NotifyIsAnimatingChanged()584 void AnimatingLayoutManager::NotifyIsAnimatingChanged() {
585 for (auto& observer : observers_)
586 observer.OnLayoutIsAnimatingChanged(this, is_animating());
587 }
588
RunQueuedActions()589 void AnimatingLayoutManager::RunQueuedActions() {
590 run_queued_actions_is_pending_ = false;
591 std::vector<base::OnceClosure> actions = std::move(queued_actions_to_run_);
592 for (auto& action : actions)
593 std::move(action).Run();
594 }
595
PostQueuedActions()596 void AnimatingLayoutManager::PostQueuedActions() {
597 // Move queued actions over to actions that should run during the next
598 // PostTask(). This prevents a race between old PostTask() calls and new
599 // delayed actions. See the header for more detail.
600 for (auto& action : queued_actions_)
601 queued_actions_to_run_.push_back(std::move(action));
602 queued_actions_.clear();
603
604 // Early return to prevent multiple RunQueuedAction() tasks.
605 if (run_queued_actions_is_pending_)
606 return;
607
608 // Post to self (instead of posting the queued actions directly) which lets
609 // us:
610 // * Keep "AnimatingLayoutManager::RunDelayedActions" in the stack frame.
611 // * Tie the task lifetimes to AnimatingLayoutManager.
612 run_queued_actions_is_pending_ =
613 base::ThreadTaskRunnerHandle::Get()->PostTask(
614 FROM_HERE, base::BindOnce(&AnimatingLayoutManager::RunQueuedActions,
615 weak_ptr_factory_.GetWeakPtr()));
616 }
617
UpdateCurrentLayout(double percent)618 void AnimatingLayoutManager::UpdateCurrentLayout(double percent) {
619 // This drops out any child view elements that don't exist in the target
620 // layout. We'll add them back in later.
621 current_layout_ =
622 ProposedLayoutBetween(percent, starting_layout_, target_layout_);
623
624 for (const LayoutFadeInfo& fade_info : fade_infos_) {
625 // This shouldn't happen but we should ensure that with a check.
626 DCHECK_NE(-1, host_view()->GetIndexOf(fade_info.child_view));
627
628 // Views that were previously fading are animated as normal, so nothing to
629 // do here.
630 if (fade_info.fade_type == LayoutFadeType::kContinuingFade)
631 continue;
632
633 ChildLayout child_layout;
634
635 if (percent == 1.0) {
636 // At the end of the animation snap to the final state of the child view.
637 child_layout.child_view = fade_info.child_view;
638 switch (fade_info.fade_type) {
639 case LayoutFadeType::kFadingIn:
640 child_layout.visible = true;
641 child_layout.bounds =
642 Denormalize(orientation(), fade_info.reference_bounds);
643 break;
644 case LayoutFadeType::kFadingOut:
645 child_layout.visible = false;
646 break;
647 case LayoutFadeType::kContinuingFade:
648 NOTREACHED();
649 continue;
650 }
651 } else if (default_fade_mode_ == FadeInOutMode::kHide) {
652 child_layout.child_view = fade_info.child_view;
653 child_layout.visible = false;
654 } else {
655 const double scale_percent =
656 fade_info.fade_type == LayoutFadeType::kFadingIn ? percent
657 : 1.0 - percent;
658
659 switch (default_fade_mode_) {
660 case FadeInOutMode::kHide:
661 NOTREACHED();
662 break;
663 case FadeInOutMode::kScaleFromMinimum:
664 child_layout = CalculateScaleFade(fade_info, scale_percent,
665 /* scale_from_zero */ false);
666 break;
667 case FadeInOutMode::kScaleFromZero:
668 child_layout = CalculateScaleFade(fade_info, scale_percent,
669 /* scale_from_zero */ true);
670 break;
671 case FadeInOutMode::kSlideFromLeadingEdge:
672 child_layout = CalculateSlideFade(fade_info, scale_percent,
673 /* slide_from_leading */ true);
674 break;
675 case FadeInOutMode::kSlideFromTrailingEdge:
676 child_layout = CalculateSlideFade(fade_info, scale_percent,
677 /* slide_from_leading */ false);
678 break;
679 }
680 }
681
682 ChildLayout* const to_overwrite =
683 FindChildViewInLayout(¤t_layout_, fade_info.child_view);
684 if (to_overwrite)
685 *to_overwrite = child_layout;
686 else
687 current_layout_.child_layouts.push_back(child_layout);
688 }
689 }
690
CalculateFadeInfos()691 void AnimatingLayoutManager::CalculateFadeInfos() {
692 // Save any views that were previously fading so we don't try to key off of
693 // them when calculating leading/trailing edge.
694 std::set<const View*> previously_fading;
695 for (const auto& fade_info : fade_infos_)
696 previously_fading.insert(fade_info.child_view);
697
698 fade_infos_.clear();
699
700 struct ChildInfo {
701 base::Optional<size_t> start;
702 NormalizedRect start_bounds;
703 bool start_visible = false;
704 base::Optional<size_t> target;
705 NormalizedRect target_bounds;
706 bool target_visible = false;
707 };
708
709 std::map<View*, ChildInfo> child_to_info;
710 std::map<int, View*> start_leading_edges;
711 std::map<int, View*> target_leading_edges;
712
713 // Collect some bookkeping info to prevent linear searches later.
714
715 for (View* child : host_view()->children()) {
716 if (IsChildIncludedInLayout(child, /* include hidden */ true))
717 child_to_info.emplace(child, ChildInfo());
718 }
719
720 for (size_t i = 0; i < starting_layout_.child_layouts.size(); ++i) {
721 const auto& child_layout = starting_layout_.child_layouts[i];
722 auto it = child_to_info.find(child_layout.child_view);
723 if (it != child_to_info.end()) {
724 it->second.start = i;
725 it->second.start_bounds = Normalize(orientation(), child_layout.bounds);
726 it->second.start_visible = child_layout.visible;
727 }
728 }
729
730 for (size_t i = 0; i < target_layout_.child_layouts.size(); ++i) {
731 const auto& child_layout = target_layout_.child_layouts[i];
732 auto it = child_to_info.find(child_layout.child_view);
733 if (it != child_to_info.end()) {
734 it->second.target = i;
735 it->second.target_bounds = Normalize(orientation(), child_layout.bounds);
736 it->second.target_visible = child_layout.visible;
737 }
738 }
739
740 for (View* child : host_view()->children()) {
741 const auto& index = child_to_info[child];
742 if (index.start_visible && index.target_visible &&
743 !base::Contains(previously_fading, child)) {
744 start_leading_edges.emplace(index.start_bounds.origin_main(), child);
745 target_leading_edges.emplace(index.target_bounds.origin_main(), child);
746 }
747 }
748
749 // Build the LayoutFadeInfo data.
750
751 const NormalizedSize start_host_size =
752 Normalize(orientation(), starting_layout_.host_size);
753 const NormalizedSize target_host_size =
754 Normalize(orientation(), target_layout_.host_size);
755
756 for (View* child : host_view()->children()) {
757 const auto& current = child_to_info[child];
758 if (current.start_visible && !current.target_visible) {
759 LayoutFadeInfo fade_info;
760 fade_info.fade_type = LayoutFadeType::kFadingOut;
761 fade_info.child_view = child;
762 fade_info.reference_bounds = current.start_bounds;
763 auto next =
764 start_leading_edges.upper_bound(current.start_bounds.origin_main());
765 if (next == start_leading_edges.end()) {
766 fade_info.next_view = nullptr;
767 fade_info.offsets.set_trailing(start_host_size.main() -
768 current.start_bounds.max_main());
769 } else {
770 fade_info.next_view = next->second;
771 fade_info.offsets.set_trailing(next->first -
772 current.start_bounds.max_main());
773 }
774 if (next == start_leading_edges.begin()) {
775 fade_info.prev_view = nullptr;
776 fade_info.offsets.set_leading(current.start_bounds.origin_main());
777 } else {
778 auto prev = next;
779 --prev;
780 const auto& prev_info = child_to_info[prev->second];
781 fade_info.prev_view = prev->second;
782 fade_info.offsets.set_leading(current.start_bounds.origin_main() -
783 prev_info.start_bounds.max_main());
784 }
785 fade_infos_.push_back(fade_info);
786 } else if (!current.start_visible && current.target_visible) {
787 LayoutFadeInfo fade_info;
788 fade_info.fade_type = LayoutFadeType::kFadingIn;
789 fade_info.child_view = child;
790 fade_info.reference_bounds = current.target_bounds;
791 auto next =
792 target_leading_edges.upper_bound(current.target_bounds.origin_main());
793 if (next == target_leading_edges.end()) {
794 fade_info.next_view = nullptr;
795 fade_info.offsets.set_trailing(target_host_size.main() -
796 current.target_bounds.max_main());
797 } else {
798 fade_info.next_view = next->second;
799 fade_info.offsets.set_trailing(next->first -
800 current.target_bounds.max_main());
801 }
802 if (next == target_leading_edges.begin()) {
803 fade_info.prev_view = nullptr;
804 fade_info.offsets.set_leading(current.target_bounds.origin_main());
805 } else {
806 auto prev = next;
807 --prev;
808 const auto& prev_info = child_to_info[prev->second];
809 fade_info.prev_view = prev->second;
810 fade_info.offsets.set_leading(current.target_bounds.origin_main() -
811 prev_info.target_bounds.max_main());
812 }
813 fade_infos_.push_back(fade_info);
814 } else if (base::Contains(previously_fading, child)) {
815 // Capture the fact that this view was fading as part of an animation that
816 // was interrupted. (It is therefore technically still fading.) This
817 // status goes away when the animation ends.
818 LayoutFadeInfo fade_info;
819 fade_info.fade_type = LayoutFadeType::kContinuingFade;
820 fade_info.child_view = child;
821 // No reference bounds or offsets since we'll use the normal animation
822 // pathway for this view.
823 fade_infos_.push_back(fade_info);
824 }
825 }
826 }
827
ResolveFades()828 void AnimatingLayoutManager::ResolveFades() {
829 // Views that need faded out are views which were were fading out previously
830 // because they were set to not be visible, either by calling SetVisible() or
831 // FadeOut(). Those views will not be included in the new layout but may not
832 // have been allowed to become invisible yet because of the fade-out
833 // animation. Even in the case of FadeInOutMode::kHide, if no frames of the
834 // animation have run, the relevant view may still be visible.
835 for (const LayoutFadeInfo& fade_info : fade_infos_) {
836 View* const child = fade_info.child_view;
837 if (fade_info.fade_type == LayoutFadeType::kFadingOut &&
838 host_view()->GetIndexOf(child) >= 0 &&
839 !IsChildViewIgnoredByLayout(child) && !IsChildIncludedInLayout(child)) {
840 SetViewVisibility(child, false);
841 }
842 }
843 }
844
CalculateScaleFade(const LayoutFadeInfo & fade_info,double scale_percent,bool scale_from_zero) const845 ChildLayout AnimatingLayoutManager::CalculateScaleFade(
846 const LayoutFadeInfo& fade_info,
847 double scale_percent,
848 bool scale_from_zero) const {
849 ChildLayout child_layout;
850
851 int leading_reference_point = 0;
852 if (fade_info.prev_view) {
853 // Since prev/next view is always a view in the start and target layouts, it
854 // should also be in the current layout. Therefore this should never return
855 // null.
856 const ChildLayout* const prev_layout =
857 FindChildViewInLayout(current_layout_, fade_info.prev_view);
858 leading_reference_point =
859 Normalize(orientation(), prev_layout->bounds).max_main();
860 }
861 leading_reference_point += fade_info.offsets.leading();
862
863 int trailing_reference_point;
864 if (fade_info.next_view) {
865 // Since prev/next view is always a view in the start and target layouts, it
866 // should also be in the current layout. Therefore this should never return
867 // null.
868 const ChildLayout* const next_layout =
869 FindChildViewInLayout(current_layout_, fade_info.next_view);
870 trailing_reference_point =
871 Normalize(orientation(), next_layout->bounds).origin_main();
872 } else {
873 trailing_reference_point =
874 Normalize(orientation(), current_layout_.host_size).main();
875 }
876 trailing_reference_point -= fade_info.offsets.trailing();
877
878 const int new_size =
879 std::min(int{scale_percent * fade_info.reference_bounds.size_main()},
880 trailing_reference_point - leading_reference_point);
881
882 child_layout.child_view = fade_info.child_view;
883 if (new_size > 0 &&
884 (scale_from_zero ||
885 new_size >=
886 Normalize(orientation(), fade_info.child_view->GetMinimumSize())
887 .main())) {
888 child_layout.visible = true;
889 NormalizedRect new_bounds = fade_info.reference_bounds;
890 switch (fade_info.fade_type) {
891 case LayoutFadeType::kFadingIn:
892 new_bounds.set_origin_main(leading_reference_point);
893 break;
894 case LayoutFadeType::kFadingOut:
895 new_bounds.set_origin_main(trailing_reference_point - new_size);
896 break;
897 case LayoutFadeType::kContinuingFade:
898 NOTREACHED();
899 break;
900 }
901 new_bounds.set_size_main(new_size);
902 child_layout.bounds = Denormalize(orientation(), new_bounds);
903 }
904
905 return child_layout;
906 }
907
CalculateSlideFade(const LayoutFadeInfo & fade_info,double scale_percent,bool slide_from_leading) const908 ChildLayout AnimatingLayoutManager::CalculateSlideFade(
909 const LayoutFadeInfo& fade_info,
910 double scale_percent,
911 bool slide_from_leading) const {
912 // Fall back to kScaleFromMinimum if there is no edge to slide out from.
913 if (!fade_info.prev_view && !fade_info.next_view)
914 return CalculateScaleFade(fade_info, scale_percent, false);
915
916 // Slide from the other direction if against the edge of the host view.
917 if (slide_from_leading && !fade_info.prev_view)
918 slide_from_leading = false;
919 else if (!slide_from_leading && !fade_info.next_view)
920 slide_from_leading = true;
921
922 NormalizedRect new_bounds = fade_info.reference_bounds;
923
924 // Determine which layout the sliding view will be completely faded in.
925 const ProposedLayout* fully_faded_layout;
926 switch (fade_info.fade_type) {
927 case LayoutFadeType::kFadingIn:
928 fully_faded_layout = &starting_layout_;
929 break;
930 case LayoutFadeType::kFadingOut:
931 fully_faded_layout = &target_layout_;
932 break;
933 case LayoutFadeType::kContinuingFade:
934 NOTREACHED();
935 break;
936 }
937
938 if (slide_from_leading) {
939 // Get the layout info for the leading child.
940 const ChildLayout* const leading_child =
941 FindChildViewInLayout(*fully_faded_layout, fade_info.prev_view);
942
943 // This is the right side of the leading control that will eclipse the
944 // sliding view at the start/end of the animation.
945 const int initial_trailing =
946 Normalize(orientation(), leading_child->bounds).max_main();
947
948 // Interpolate between initial and final trailing edge.
949 const int new_trailing = gfx::Tween::IntValueBetween(
950 scale_percent, initial_trailing, new_bounds.max_main());
951
952 // Adjust the bounding rectangle of the view.
953 new_bounds.Offset(new_trailing - new_bounds.max_main(), 0);
954
955 } else {
956 // Get the layout info for the trailing child.
957 const ChildLayout* const trailing_child =
958 FindChildViewInLayout(*fully_faded_layout, fade_info.next_view);
959
960 // This is the left side of the trailing control that will eclipse the
961 // sliding view at the start/end of the animation.
962 const int initial_leading =
963 Normalize(orientation(), trailing_child->bounds).origin_main();
964
965 // Interpolate between initial and final leading edge.
966 const int new_leading = gfx::Tween::IntValueBetween(
967 scale_percent, initial_leading, new_bounds.origin_main());
968
969 // Adjust the bounding rectangle of the view.
970 new_bounds.Offset(new_leading - new_bounds.origin_main(), 0);
971 }
972
973 // Actual bounds are a linear interpolation between starting and reference
974 // bounds.
975 ChildLayout child_layout;
976 child_layout.child_view = fade_info.child_view;
977 child_layout.visible = true;
978 child_layout.bounds = Denormalize(orientation(), new_bounds);
979
980 return child_layout;
981 }
982
983 // Returns the space in which to calculate the target layout.
GetAvailableTargetLayoutSize()984 gfx::Size AnimatingLayoutManager::GetAvailableTargetLayoutSize() {
985 if (!should_animate_bounds_)
986 return host_view()->size();
987
988 const SizeBounds bounds = GetAvailableHostSize();
989 last_available_host_size_ = bounds;
990 const gfx::Size preferred_size =
991 target_layout_manager()->GetPreferredSize(host_view());
992 if (!bounds.width() || *bounds.width() > preferred_size.width()) {
993 return gfx::Size(preferred_size.width(),
994 bounds.height()
995 ? std::min(preferred_size.height(), *bounds.height())
996 : preferred_size.height());
997 }
998
999 const int height = target_layout_manager()->GetPreferredHeightForWidth(
1000 host_view(), *bounds.width());
1001 return gfx::Size(*bounds.width(), bounds.height()
1002 ? std::min(height, *bounds.height())
1003 : height);
1004 }
1005
1006 // static
DefaultFlexRuleImpl(const AnimatingLayoutManager * animating_layout,const View * view,const SizeBounds & size_bounds)1007 gfx::Size AnimatingLayoutManager::DefaultFlexRuleImpl(
1008 const AnimatingLayoutManager* animating_layout,
1009 const View* view,
1010 const SizeBounds& size_bounds) {
1011 DCHECK_EQ(view->GetLayoutManager(), animating_layout);
1012
1013 // This is the current preferred size, which takes animation into account.
1014 const gfx::Size preferred_size = animating_layout->GetPreferredSize(view);
1015
1016 // Does the preferred size fit in the bounds? If so, return the preferred
1017 // size. Note that the *target* size might not fit in the bounds, but we'll
1018 // recalculate that the next time we lay out.
1019 if (CanFitInBounds(preferred_size, size_bounds))
1020 return preferred_size;
1021
1022 // Special case - if we're being asked for a zero-size layout we'll return the
1023 // minimum size of the layout. This is because we're being probed for how
1024 // small we can get, not being asked for an actual size.
1025 const base::Optional<int> bounds_main =
1026 GetMainAxis(animating_layout->orientation(), size_bounds);
1027 if (bounds_main && *bounds_main <= 0)
1028 return animating_layout->GetMinimumSize(view);
1029
1030 // We know our current size does not fit into the bounds being given to us.
1031 // This is going to force a snap to a new size, which will be the ideal size
1032 // of the target layout in the provided space.
1033 const LayoutManagerBase* const target_layout =
1034 animating_layout->target_layout_manager();
1035
1036 // Easiest case is that the target layout's preferred size *does* fit, in
1037 // which case we can use that.
1038 const gfx::Size target_preferred = target_layout->GetPreferredSize(view);
1039 if (CanFitInBounds(target_preferred, size_bounds))
1040 return target_preferred;
1041
1042 // We know that at least one of the width and height are constrained, so we
1043 // need to ask the target layout how large it wants to be in the space
1044 // provided.
1045 gfx::Size size;
1046 if (size_bounds.width() && size_bounds.height()) {
1047 // Both width and height are specified. Constraining the width may change
1048 // the desired height, so we can't just blindly return the minimum in both
1049 // dimensions. Instead, query the target layout in the constrained space
1050 // and return its size.
1051 size = gfx::Size(*size_bounds.width(), *size_bounds.height());
1052 } else if (size_bounds.width()) {
1053 // The width is specified and too small. Use the height-for-width
1054 // calculation.
1055 // TODO(dfried): This should be rare, but it is also inefficient. See if we
1056 // can't add an alternative to GetPreferredHeightForWidth() that actually
1057 // calculates the layout in this space so we don't have to do it twice.
1058 const int width = *size_bounds.width();
1059 size = gfx::Size(width,
1060 target_layout->GetPreferredHeightForWidth(view, width));
1061 } else {
1062 DCHECK(size_bounds.height());
1063 // The height is specified and too small. Fortunately the height of a
1064 // layout can't (shouldn't?) affect its width.
1065 size = gfx::Size(target_preferred.width(), *size_bounds.height());
1066 }
1067
1068 return target_layout->GetProposedLayout(size).host_size;
1069 }
1070
1071 } // namespace views
1072