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