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 "ash/wm/desks/root_window_desk_switch_animator.h"
6 
7 #include "ash/public/cpp/ash_features.h"
8 #include "ash/public/cpp/shell_window_ids.h"
9 #include "ash/screen_util.h"
10 #include "ash/wm/desks/desk.h"
11 #include "ash/wm/desks/desks_controller.h"
12 #include "ash/wm/desks/desks_util.h"
13 #include "base/auto_reset.h"
14 #include "base/logging.h"
15 #include "base/numerics/ranges.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "components/viz/common/frame_sinks/copy_output_request.h"
18 #include "components/viz/common/frame_sinks/copy_output_result.h"
19 #include "third_party/khronos/GLES2/gl2.h"
20 #include "third_party/skia/include/core/SkColor.h"
21 #include "ui/aura/window.h"
22 #include "ui/compositor/layer.h"
23 #include "ui/compositor/layer_animation_observer.h"
24 #include "ui/compositor/layer_tree_owner.h"
25 #include "ui/compositor/scoped_layer_animation_settings.h"
26 #include "ui/gfx/transform.h"
27 #include "ui/wm/core/window_util.h"
28 
29 namespace ash {
30 
31 namespace {
32 
33 // The maximum number of times to retry taking a screenshot for either the
34 // starting or the ending desks. After this maximum number is reached, we ignore
35 // a failed screenshot request and proceed with next phases.
36 constexpr int kMaxScreenshotRetries = 2;
37 
38 // When using the touchpad to perform a continuous desk update, we may need a
39 // new screenshot request during the swipe. While updating the animation layer,
40 // if we are getting close to the edges of the animation layer by this amount,
41 // request a new screenshot.
42 constexpr int kMinDistanceBeforeScreenshotDp = 40;
43 
44 constexpr base::TimeDelta kAnimationDuration =
45     base::TimeDelta::FromMilliseconds(300);
46 
47 // The amount, by which the detached old layers of the removed desk's windows,
48 // is translated vertically during the for-remove desk switch animation.
49 constexpr int kRemovedDeskWindowYTranslation = 20;
50 constexpr base::TimeDelta kRemovedDeskWindowTranslationDuration =
51     base::TimeDelta::FromMilliseconds(100);
52 
53 // Create the layer that will be the parent of the screenshot layer, with a
54 // solid black color to act as the background showing behind the two
55 // screenshot layers in the |kDesksSpacing| region between them. It will get
56 // sized as children get added to it. This is the layer that will be animated.
CreateAnimationLayerOwner(aura::Window * root)57 std::unique_ptr<ui::LayerTreeOwner> CreateAnimationLayerOwner(
58     aura::Window* root) {
59   auto animation_layer = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
60   animation_layer->SetName("Desk switch animation layer");
61   animation_layer->SetColor(SK_ColorBLACK);
62   return std::make_unique<ui::LayerTreeOwner>(std::move(animation_layer));
63 }
64 
65 // Takes a screenshot of the screen content. |on_screenshot_taken| will be
66 // triggered when the screenshot is taken.
TakeScreenshot(aura::Window * root,viz::CopyOutputRequest::CopyOutputRequestCallback on_screenshot_taken)67 void TakeScreenshot(
68     aura::Window* root,
69     viz::CopyOutputRequest::CopyOutputRequestCallback on_screenshot_taken) {
70   auto* screenshot_layer =
71       root->GetChildById(kShellWindowId_ScreenAnimationContainer)->layer();
72 
73   const gfx::Rect request_bounds(screenshot_layer->size());
74   auto screenshot_request = std::make_unique<viz::CopyOutputRequest>(
75       viz::CopyOutputRequest::ResultFormat::RGBA_TEXTURE,
76       std::move(on_screenshot_taken));
77   screenshot_request->set_area(request_bounds);
78   screenshot_request->set_result_task_runner(
79       base::SequencedTaskRunnerHandle::Get());
80   screenshot_layer->RequestCopyOfOutput(std::move(screenshot_request));
81 }
82 
83 // Given a screenshot |copy_result|, creates a texture layer that contains the
84 // content of that screenshot. The result layer will be size |layer_size|, which
85 // is in dips.
CreateLayerFromScreenshotResult(const gfx::Size & layer_size,std::unique_ptr<viz::CopyOutputResult> copy_result)86 std::unique_ptr<ui::Layer> CreateLayerFromScreenshotResult(
87     const gfx::Size& layer_size,
88     std::unique_ptr<viz::CopyOutputResult> copy_result) {
89   DCHECK(copy_result);
90   DCHECK(!copy_result->IsEmpty());
91   DCHECK_EQ(copy_result->format(), viz::CopyOutputResult::Format::RGBA_TEXTURE);
92 
93   // |texture_size| is in pixels and is not used to size the layer otherwise we
94   // may lose some quality. See https://crbug.com/1134451.
95   const gfx::Size texture_size = copy_result->size();
96   viz::TransferableResource transferable_resource =
97       viz::TransferableResource::MakeGL(
98           copy_result->GetTextureResult()->mailbox, GL_LINEAR, GL_TEXTURE_2D,
99           copy_result->GetTextureResult()->sync_token, texture_size,
100           /*is_overlay_candidate=*/false);
101   std::unique_ptr<viz::SingleReleaseCallback> take_texture_ownership_callback =
102       copy_result->TakeTextureOwnership();
103   auto screenshot_layer = std::make_unique<ui::Layer>();
104   screenshot_layer->SetBounds(gfx::Rect(layer_size));
105   screenshot_layer->SetTransferableResource(
106       transferable_resource, std::move(take_texture_ownership_callback),
107       layer_size);
108 
109   return screenshot_layer;
110 }
111 
GetScreenshotLayerName(int index)112 std::string GetScreenshotLayerName(int index) {
113   return "Desk " + base::NumberToString(index) + " screenshot layer";
114 }
115 
116 // The values received from WmGestureHandler via DesksController are in touchpad
117 // units. Convert these units so that what is considered a full touchpad swipe
118 // shifts the animation layer one entire desk length.
TouchpadToXTranslation(float touchpad_x,int desk_length)119 float TouchpadToXTranslation(float touchpad_x, int desk_length) {
120   return desk_length * touchpad_x /
121          RootWindowDeskSwitchAnimator::kTouchpadSwipeLengthForDeskChange;
122 }
123 
124 }  // namespace
125 
RootWindowDeskSwitchAnimator(aura::Window * root,int starting_desk_index,int ending_desk_index,Delegate * delegate,bool for_remove)126 RootWindowDeskSwitchAnimator::RootWindowDeskSwitchAnimator(
127     aura::Window* root,
128     int starting_desk_index,
129     int ending_desk_index,
130     Delegate* delegate,
131     bool for_remove)
132     : root_window_(root),
133       starting_desk_index_(starting_desk_index),
134       ending_desk_index_(ending_desk_index),
135       visible_desk_index_(starting_desk_index),
136       delegate_(delegate),
137       animation_layer_owner_(CreateAnimationLayerOwner(root)),
138       root_window_size_(
139           screen_util::SnapBoundsToDisplayEdge(root->bounds(), root).size()),
140       x_translation_offset_(root_window_size_.width() + kDesksSpacing),
141       edge_padding_width_dp_(
142           std::round(root_window_size_.width() * kEdgePaddingRatio)),
143       for_remove_(for_remove) {
144   DCHECK(root_window_);
145   DCHECK_NE(starting_desk_index_, ending_desk_index_);
146   DCHECK(delegate_);
147 
148   screenshot_layers_.resize(desks_util::kMaxNumberOfDesks);
149 }
150 
~RootWindowDeskSwitchAnimator()151 RootWindowDeskSwitchAnimator::~RootWindowDeskSwitchAnimator() {
152   // TODO(afakhry): Determine if this is necessary, since generally this object
153   // is only deleted when all animations end, but there might be situations when
154   // we might need to kill the animations before they complete such as when a
155   // display is removed.
156   if (!attached_sequences().empty())
157     StopObservingImplicitAnimations();
158 }
159 
TakeStartingDeskScreenshot()160 void RootWindowDeskSwitchAnimator::TakeStartingDeskScreenshot() {
161   if (for_remove_) {
162     // The active desk is about to be removed. Recreate and detach its old
163     // layers to animate them in a jump-like animation.
164     auto* desk_container = DesksController::Get()
165                                ->desks()[starting_desk_index_]
166                                ->GetDeskContainerForRoot(root_window_);
167     old_windows_layer_tree_owner_ = wm::RecreateLayers(desk_container);
168     root_window_->layer()->Add(old_windows_layer_tree_owner_->root());
169     root_window_->layer()->StackAtTop(old_windows_layer_tree_owner_->root());
170 
171     // We don't take a screenshot of the soon-to-be-removed desk, we use an
172     // empty black solid color layer.
173     auto black_layer = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
174     black_layer->SetColor(SK_ColorBLACK);
175     CompleteAnimationPhase1WithLayer(std::move(black_layer));
176     return;
177   }
178 
179   TakeScreenshot(
180       root_window_,
181       base::BindOnce(
182           &RootWindowDeskSwitchAnimator::OnStartingDeskScreenshotTaken,
183           weak_ptr_factory_.GetWeakPtr()));
184 }
185 
TakeEndingDeskScreenshot()186 void RootWindowDeskSwitchAnimator::TakeEndingDeskScreenshot() {
187   DCHECK(starting_desk_screenshot_taken_);
188 
189   TakeScreenshot(
190       root_window_,
191       base::BindOnce(&RootWindowDeskSwitchAnimator::OnEndingDeskScreenshotTaken,
192                      weak_ptr_factory_.GetWeakPtr()));
193 }
194 
StartAnimation()195 void RootWindowDeskSwitchAnimator::StartAnimation() {
196   DCHECK(starting_desk_screenshot_taken_);
197   DCHECK(!animation_finished_);
198 
199   // Set a transform so that the ending desk will be visible.
200   gfx::Transform animation_layer_ending_transform;
201   animation_layer_ending_transform.Translate(
202       -GetXPositionOfScreenshot(ending_desk_index_), 0);
203 
204   // Animate the parent "animation layer" towards the ending transform.
205   ui::Layer* animation_layer = animation_layer_owner_->root();
206   ui::ScopedLayerAnimationSettings settings(animation_layer->GetAnimator());
207   settings.SetPreemptionStrategy(
208       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
209   settings.AddObserver(this);
210   settings.SetTransitionDuration(kAnimationDuration);
211   settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
212   animation_layer->SetTransform(animation_layer_ending_transform);
213 
214   if (for_remove_) {
215     DCHECK(old_windows_layer_tree_owner_);
216     auto* old_windows_layer = old_windows_layer_tree_owner_->root();
217     DCHECK(old_windows_layer);
218 
219     // Translate the old layers of removed desk's windows back down by
220     // `kRemovedDeskWindowYTranslation`.
221     gfx::Transform transform = old_windows_layer->GetTargetTransform();
222     ui::ScopedLayerAnimationSettings settings(old_windows_layer->GetAnimator());
223     settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
224     settings.SetTransitionDuration(kRemovedDeskWindowTranslationDuration);
225     settings.SetTweenType(gfx::Tween::EASE_IN);
226     transform.Translate(0, kRemovedDeskWindowYTranslation);
227     old_windows_layer->SetTransform(transform);
228   }
229 }
230 
ReplaceAnimation(int new_ending_desk_index)231 bool RootWindowDeskSwitchAnimator::ReplaceAnimation(int new_ending_desk_index) {
232   DCHECK(features::IsEnhancedDeskAnimations());
233   DCHECK(!for_remove_);
234   DCHECK_NE(new_ending_desk_index, ending_desk_index_);
235 
236   starting_desk_index_ = ending_desk_index_;
237   ending_desk_index_ = new_ending_desk_index;
238 
239   if (!!screenshot_layers_[ending_desk_index_]) {
240     // Notify the caller to start an animation to |ending_desk_index_|.
241     return false;
242   }
243 
244   ending_desk_screenshot_retries_ = 0;
245   ending_desk_screenshot_taken_ = false;
246 
247   // Notify the caller to activate the next desk and request a screenshot.
248   return true;
249 }
250 
UpdateSwipeAnimation(float scroll_delta_x)251 base::Optional<int> RootWindowDeskSwitchAnimator::UpdateSwipeAnimation(
252     float scroll_delta_x) {
253   if (!starting_desk_screenshot_taken_ || !ending_desk_screenshot_taken_)
254     return base::nullopt;
255 
256   const float translation_delta_x =
257       TouchpadToXTranslation(scroll_delta_x, x_translation_offset_);
258 
259   // The visible bounds to the user are the root window bounds which always have
260   // origin of 0,0. Therefore the rightmost edge of the visible bounds will be
261   // the width.
262   const int visible_bounds_width = root_window_size_.width();
263 
264   // Append the new offset to the current transform. Clamp the new transform so
265   // that we do not swipe past the edges.
266   auto* animation_layer = animation_layer_owner_->root();
267   float translation_x =
268       animation_layer->transform().To2dTranslation().x() + translation_delta_x;
269   translation_x = base::ClampToRange(
270       translation_x,
271       float{-animation_layer->bounds().width() + visible_bounds_width}, 0.f);
272   gfx::Transform transform;
273   transform.Translate(translation_x, 0.f);
274   base::AutoReset<bool> auto_reset(&setting_new_transform_, true);
275   animation_layer->SetTransform(transform);
276 
277   // The animation layer starts with two screenshot layers as the most common
278   // transition is from one desk to another adjacent desk. We may need to signal
279   // the delegate to request a new screenshot if the animating layer is about to
280   // slide past the bounds which are visible to the user (root window bounds).
281   //
282   //              moving right ---->
283   //   +---+------------------------------+---+
284   //   |   |               +-----------+  |   |
285   //   | c |      b        |     a     |  | c |
286   //   |   |               +___________+  |   |
287   //   +___+______________________________+___+
288   //
289   //  a - root window/visible bounds - (0,0-1000x500)
290   //  b - animating layer with two screenshots and edge padding - (0,0-2350x500)
291   //    - current second screenshot is visible (translation (-1200, 0))
292   //  c - Edge padding, equal to |kEdgePaddingRatio| x 1000 - 150 dips wide
293   //  We will notify the delegate to request a new screenshot once the x of b is
294   //  within |kMinDistanceBeforeScreenshotDp| of the x of a, not including the
295   //  edge padding (i.e. translation of (-190, 0)).
296   gfx::RectF transformed_animation_layer_bounds(animation_layer->bounds());
297   transform.TransformRect(&transformed_animation_layer_bounds);
298   transformed_animation_layer_bounds.Inset(edge_padding_width_dp_, 0);
299 
300   const bool moving_left = scroll_delta_x < 0.f;
301   const bool going_out_of_bounds =
302       moving_left
303           ? transformed_animation_layer_bounds.right() - visible_bounds_width <
304                 kMinDistanceBeforeScreenshotDp
305           : transformed_animation_layer_bounds.x() >
306                 -kMinDistanceBeforeScreenshotDp;
307 
308   // TODO(sammiequon): Make GetIndexOfMostVisibleDeskScreenshot() public and
309   // have DeskActivationAnimation keep track of |visible_desk_index_|. Right now
310   // OnVisibleDeskChanged will get called once for each display.
311   const int old_visible_desk_index = visible_desk_index_;
312   visible_desk_index_ = GetIndexOfMostVisibleDeskScreenshot();
313   if (old_visible_desk_index != visible_desk_index_)
314     delegate_->OnVisibleDeskChanged();
315 
316   if (!going_out_of_bounds)
317     return base::nullopt;
318 
319   // The upcoming desk we need to show will be an adjacent desk to the desk at
320   // |visible_desk_index_| based on |moving_left|.
321   const int new_desk_index = visible_desk_index_ + (moving_left ? 1 : -1);
322 
323   if (new_desk_index < 0 ||
324       new_desk_index >= int{DesksController::Get()->desks().size()}) {
325     return base::nullopt;
326   }
327 
328   return new_desk_index;
329 }
330 
PrepareForEndingDeskScreenshot(int new_ending_desk_index)331 void RootWindowDeskSwitchAnimator::PrepareForEndingDeskScreenshot(
332     int new_ending_desk_index) {
333   ending_desk_index_ = new_ending_desk_index;
334   ending_desk_screenshot_retries_ = 0;
335   ending_desk_screenshot_taken_ = false;
336 }
337 
EndSwipeAnimation()338 int RootWindowDeskSwitchAnimator::EndSwipeAnimation() {
339   // If the starting screenshot has not finished, just let our delegate know
340   // that the desk animation is finished (and |this| will soon be deleted), and
341   // go back to the starting desk.
342   if (!starting_desk_screenshot_taken_) {
343     animation_finished_ = true;
344     // Notifying the delegate may delete |this|. Store the target index in a
345     // local so we do not try to access a member of a deleted object.
346     const int ending_desk_index = starting_desk_index_;
347     delegate_->OnDeskSwitchAnimationFinished();
348     return ending_desk_index;
349   }
350 
351   // If the ending desk screenshot has not finished, |visible_desk_index_| will
352   // still return a valid desk index that we can animate to, but we need to make
353   // sure the ending desk screenshot callback does not get called.
354   if (!ending_desk_screenshot_taken_)
355     weak_ptr_factory_.InvalidateWeakPtrs();
356 
357   // In tests, StartAnimation() may trigger OnDeskSwitchAnimationFinished()
358   // right away which may delete |this|. Store the target index in a
359   // local so we do not try to access a member of a deleted object.
360   const int ending_desk_index = visible_desk_index_;
361   ending_desk_index_ = ending_desk_index;
362   StartAnimation();
363   return ending_desk_index;
364 }
365 
OnImplicitAnimationsCompleted()366 void RootWindowDeskSwitchAnimator::OnImplicitAnimationsCompleted() {
367   // |setting_new_transform_| is true we call SetTransform while an animation is
368   // under progress. Do not notify our delegate in that case.
369   if (setting_new_transform_)
370     return;
371 
372   StopObservingImplicitAnimations();
373   animation_finished_ = true;
374   delegate_->OnDeskSwitchAnimationFinished();
375 }
376 
GetAnimationLayerForTesting() const377 ui::Layer* RootWindowDeskSwitchAnimator::GetAnimationLayerForTesting() const {
378   return animation_layer_owner_->root();
379 }
380 
CompleteAnimationPhase1WithLayer(std::unique_ptr<ui::Layer> layer)381 void RootWindowDeskSwitchAnimator::CompleteAnimationPhase1WithLayer(
382     std::unique_ptr<ui::Layer> layer) {
383   DCHECK(layer);
384 
385   ui::Layer* starting_desk_screenshot_layer = layer.release();
386   screenshot_layers_[starting_desk_index_] = starting_desk_screenshot_layer;
387   starting_desk_screenshot_layer->SetName(
388       GetScreenshotLayerName(starting_desk_index_));
389 
390   auto* animation_layer = animation_layer_owner_->root();
391   animation_layer->Add(starting_desk_screenshot_layer);
392 
393   // Add the layers on top of everything, so that things that result from desk
394   // activation (such as showing and hiding windows, exiting overview mode ...
395   // etc.) are not visible to the user.
396   auto* root_layer = root_window_->layer();
397   root_layer->Add(animation_layer);
398 
399   if (for_remove_) {
400     DCHECK(old_windows_layer_tree_owner_);
401     auto* old_windows_layer = old_windows_layer_tree_owner_->root();
402     DCHECK(old_windows_layer);
403     root_layer->StackBelow(animation_layer, old_windows_layer);
404 
405     // Translate the old layers of the removed desk's windows up by
406     // `kRemovedDeskWindowYTranslation`.
407     gfx::Transform transform = old_windows_layer->GetTargetTransform();
408     ui::ScopedLayerAnimationSettings settings(old_windows_layer->GetAnimator());
409     settings.SetPreemptionStrategy(
410         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
411     settings.SetTransitionDuration(kRemovedDeskWindowTranslationDuration);
412     settings.SetTweenType(gfx::Tween::EASE_OUT);
413     transform.Translate(0, -kRemovedDeskWindowYTranslation);
414     old_windows_layer->SetTransform(transform);
415   } else {
416     root_layer->StackAtTop(animation_layer);
417   }
418 
419   starting_desk_screenshot_taken_ = true;
420   OnScreenshotLayerCreated();
421   delegate_->OnStartingDeskScreenshotTaken(ending_desk_index_);
422 }
423 
OnStartingDeskScreenshotTaken(std::unique_ptr<viz::CopyOutputResult> copy_result)424 void RootWindowDeskSwitchAnimator::OnStartingDeskScreenshotTaken(
425     std::unique_ptr<viz::CopyOutputResult> copy_result) {
426   if (!copy_result || copy_result->IsEmpty()) {
427     // A frame may be activated before the screenshot requests are satisfied,
428     // leading to us getting an empty |result|. Rerequest the screenshot.
429     // (See viz::Surface::ActivateFrame()).
430     if (++starting_desk_screenshot_retries_ <= kMaxScreenshotRetries) {
431       TakeStartingDeskScreenshot();
432     } else {
433       LOG(ERROR) << "Received multiple empty screenshots of the starting desk.";
434       NOTREACHED();
435       starting_desk_screenshot_taken_ = true;
436       delegate_->OnStartingDeskScreenshotTaken(ending_desk_index_);
437     }
438 
439     return;
440   }
441 
442   CompleteAnimationPhase1WithLayer(CreateLayerFromScreenshotResult(
443       root_window_size_, std::move(copy_result)));
444 }
445 
OnEndingDeskScreenshotTaken(std::unique_ptr<viz::CopyOutputResult> copy_result)446 void RootWindowDeskSwitchAnimator::OnEndingDeskScreenshotTaken(
447     std::unique_ptr<viz::CopyOutputResult> copy_result) {
448   if (!copy_result || copy_result->IsEmpty()) {
449     // A frame may be activated before the screenshot requests are satisfied,
450     // leading to us getting an empty |result|. Rerequest the screenshot.
451     // (See viz::Surface::ActivateFrame()).
452     if (++ending_desk_screenshot_retries_ <= kMaxScreenshotRetries) {
453       TakeEndingDeskScreenshot();
454     } else {
455       LOG(ERROR) << "Received multiple empty screenshots of the ending desk.";
456       NOTREACHED();
457       ending_desk_screenshot_taken_ = true;
458       delegate_->OnEndingDeskScreenshotTaken();
459     }
460 
461     return;
462   }
463 
464   ui::Layer* ending_desk_screenshot_layer =
465       CreateLayerFromScreenshotResult(root_window_size_, std::move(copy_result))
466           .release();
467   screenshot_layers_[ending_desk_index_] = ending_desk_screenshot_layer;
468   ending_desk_screenshot_layer->SetName(
469       GetScreenshotLayerName(ending_desk_index_));
470   animation_layer_owner_->root()->Add(ending_desk_screenshot_layer);
471 
472   ending_desk_screenshot_taken_ = true;
473   OnScreenshotLayerCreated();
474 
475   // On ending screenshot may delete |this|.
476   if (on_ending_screenshot_taken_callback_for_testing_)
477     std::move(on_ending_screenshot_taken_callback_for_testing_).Run();
478 
479   delegate_->OnEndingDeskScreenshotTaken();
480 }
481 
OnScreenshotLayerCreated()482 void RootWindowDeskSwitchAnimator::OnScreenshotLayerCreated() {
483   // Set the layer bounds. |screenshot_layers_| always matches the order of the
484   // desks, which is left to right.
485   int num_screenshots = 0;
486   DCHECK_EQ(x_translation_offset_, root_window_size_.width() + kDesksSpacing);
487   for (ui::Layer* layer : screenshot_layers_) {
488     if (!layer)
489       continue;
490 
491     const int x =
492         num_screenshots * x_translation_offset_ + edge_padding_width_dp_;
493     layer->SetBounds(gfx::Rect(gfx::Point(x, 0), root_window_size_));
494     ++num_screenshots;
495   }
496 
497   // The animation layer is sized to contain all the screenshot layers,
498   // |kDesksSpacing| between any two adjacent screenshot layers, and
499   // |edge_padding_width_dp_| on each side.
500   const gfx::Rect animation_layer_bounds(
501       num_screenshots * x_translation_offset_ - kDesksSpacing +
502           2 * edge_padding_width_dp_,
503       root_window_size_.height());
504   auto* animation_layer = animation_layer_owner_->root();
505   animation_layer->SetBounds(animation_layer_bounds);
506 
507   // Two examples of simple animations (two desks involved), one moving left and
508   // one moving right. Starting desk is one the left, so we start off with no
509   // offset and then slide the animation layer so that ending desk is visible
510   // (target transform of -|x_translation_offset_| translation).
511   //
512   //                         +-----------+
513   //                         | Animation |
514   //                         |  layer    |
515   //                         +-----------+
516   //                           /        \
517   //                +------------+      +------------+
518   //                | start desk |      | end desk   |
519   //                | screenshot |      | screenshot |
520   //                |  layer (1) |      |  layer (2) |
521   //                +------------+      +------------+
522   //                      ^
523   //                  start here
524   //
525   //                |------------------>|
526   //                          ^
527   //                `x_translation_offset_`
528   //
529   // Starting desk is one the right, so we need to offset the animation layer
530   // horizontally so that the starting desk is visible
531   // (-|x_translation_offset_|) and the slide the animation layer so that the
532   // ending desk is visible (target transform of 0 translation).
533   //
534   //                         +-----------+
535   //                         | Animation |
536   //                         |  layer    |
537   //                         +-----------+
538   //                           /        \
539   //                +------------+      +------------+
540   //                | end desk   |      | start desk |
541   //                | screenshot |      | screenshot |
542   //                |  layer (1) |      |  layer (2) |
543   //                +------------+      +------------+
544   //                                          ^
545   //                |----------------->| start here
546   //                         ^
547   //               `x_translation_offset_`
548   //
549   // Chained animation example, we are in the middle of animating from desk 3 to
550   // desk 2 (start' to end'), currently halfway through the animation. Desk 1 is
551   // added, so the x position of both desk 2 and desk 3 will get shifted by
552   // |x_translation_offset_|. Shift animation layer by -|x_translation_offset_|
553   // so that half of desk 3 and half of desk 2 are still visible. Without this
554   // shift, there will be a jump and we will see half of desk 2 and half of
555   // desk 1. We then animate from start to end.
556   //
557   //                +---------------------------------------+
558   //                |          Animation                    |
559   //                |           layer                       |
560   //                +---------------------------------------+
561   //                    /               |                  \
562   //          +------------+      +------------+      +------------+
563   //          | desk 1     |      | desk 2     |      | desk 3     |
564   //          | screenshot |      | screenshot |      | screenshot |
565   //          |  layer     |      |  layer     |      |  layer     |
566   //          +------------+      +------------+      +------------+
567   //          ^                   ^       ^           ^
568   //         end                 end'   start       start'
569 
570   // If there is an existing transform, continue animating from there.
571   gfx::Transform current_transform = animation_layer->transform();
572   DCHECK(current_transform.IsIdentityOr2DTranslation());
573   if (!current_transform.IsIdentity()) {
574     // If the new layer is located on the left of the prior created layers,
575     // shift the animation layer transform so that the content shown to users
576     // remain the same.
577     if (ending_desk_index_ < starting_desk_index_) {
578       // Setting a new transform will end an ongoing animation, which will
579       // trigger OnImplicitAnimationsCompleted, which notifies our delegate to
580       // delete us. For this case, set a flag so that
581       // OnImplicitAnimationsCompleted does no notifying.
582       current_transform.Translate(-x_translation_offset_, 0);
583       base::AutoReset<bool> auto_reset(&setting_new_transform_, true);
584       animation_layer->SetTransform(current_transform);
585     }
586     return;
587   }
588 
589   // Otherwise, transform |animation_layer| so that starting desk screenshot
590   // layer is the current visible layer.
591   gfx::Transform animation_layer_starting_transform;
592   animation_layer_starting_transform.Translate(
593       -GetXPositionOfScreenshot(starting_desk_index_), 0);
594   base::AutoReset<bool> auto_reset(&setting_new_transform_, true);
595   animation_layer->SetTransform(animation_layer_starting_transform);
596 }
597 
GetXPositionOfScreenshot(int index)598 int RootWindowDeskSwitchAnimator::GetXPositionOfScreenshot(int index) {
599   ui::Layer* layer = screenshot_layers_[index];
600   DCHECK(layer);
601   return layer->bounds().x();
602 }
603 
GetIndexOfMostVisibleDeskScreenshot() const604 int RootWindowDeskSwitchAnimator::GetIndexOfMostVisibleDeskScreenshot() const {
605   int index = -1;
606 
607   // The most visible desk is the one whose screenshot layer bounds, including
608   // the transform of its parent that has its origin closest to the root window
609   // origin (0, 0).
610   const gfx::Transform transform = animation_layer_owner_->root()->transform();
611   int min_distance = INT_MAX;
612   for (int i = 0; i < int{screenshot_layers_.size()}; ++i) {
613     ui::Layer* layer = screenshot_layers_[i];
614     if (!layer)
615       continue;
616 
617     gfx::RectF bounds(layer->bounds());
618     transform.TransformRect(&bounds);
619     const int distance = std::abs(bounds.x());
620     if (distance < min_distance) {
621       min_distance = distance;
622       index = i;
623     }
624   }
625 
626   // TODO(crbug.com/1134390): Convert back to DCHECK when the issue is fixed.
627   CHECK_GE(index, 0);
628   CHECK_LT(index, int{DesksController::Get()->desks().size()});
629   return index;
630 }
631 
632 }  // namespace ash
633