| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/wm/desks/root_window_desk_switch_animator.h" |
| |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/screen_util.h" |
| #include "ash/wm/desks/desk.h" |
| #include "ash/wm/desks/desks_controller.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "base/auto_reset.h" |
| #include "base/logging.h" |
| #include "base/numerics/ranges.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "components/viz/common/frame_sinks/copy_output_request.h" |
| #include "components/viz/common/frame_sinks/copy_output_result.h" |
| #include "third_party/khronos/GLES2/gl2.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/layer_tree_owner.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/gfx/transform.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The maximum number of times to retry taking a screenshot for either the |
| // starting or the ending desks. After this maximum number is reached, we ignore |
| // a failed screenshot request and proceed with next phases. |
| constexpr int kMaxScreenshotRetries = 2; |
| |
| // When using the touchpad to perform a continuous desk update, we may need a |
| // new screenshot request during the swipe. While updating the animation layer, |
| // if we are getting close to the edges of the animation layer by this amount, |
| // request a new screenshot. |
| constexpr int kMinDistanceBeforeScreenshotDp = 40; |
| |
| constexpr base::TimeDelta kAnimationDuration = |
| base::TimeDelta::FromMilliseconds(300); |
| |
| // The amount, by which the detached old layers of the removed desk's windows, |
| // is translated vertically during the for-remove desk switch animation. |
| constexpr int kRemovedDeskWindowYTranslation = 20; |
| constexpr base::TimeDelta kRemovedDeskWindowTranslationDuration = |
| base::TimeDelta::FromMilliseconds(100); |
| |
| // Create the layer that will be the parent of the screenshot layer, with a |
| // solid black color to act as the background showing behind the two |
| // screenshot layers in the |kDesksSpacing| region between them. It will get |
| // sized as children get added to it. This is the layer that will be animated. |
| std::unique_ptr<ui::LayerTreeOwner> CreateAnimationLayerOwner( |
| aura::Window* root) { |
| auto animation_layer = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR); |
| animation_layer->SetName("Desk switch animation layer"); |
| animation_layer->SetColor(SK_ColorBLACK); |
| return std::make_unique<ui::LayerTreeOwner>(std::move(animation_layer)); |
| } |
| |
| // Takes a screenshot of the screen content. |on_screenshot_taken| will be |
| // triggered when the screenshot is taken. |
| void TakeScreenshot( |
| aura::Window* root, |
| viz::CopyOutputRequest::CopyOutputRequestCallback on_screenshot_taken) { |
| auto* screenshot_layer = |
| root->GetChildById(kShellWindowId_ScreenAnimationContainer)->layer(); |
| |
| const gfx::Rect request_bounds(screenshot_layer->size()); |
| auto screenshot_request = std::make_unique<viz::CopyOutputRequest>( |
| viz::CopyOutputRequest::ResultFormat::RGBA_TEXTURE, |
| std::move(on_screenshot_taken)); |
| screenshot_request->set_area(request_bounds); |
| screenshot_request->set_result_task_runner( |
| base::SequencedTaskRunnerHandle::Get()); |
| screenshot_layer->RequestCopyOfOutput(std::move(screenshot_request)); |
| } |
| |
| // Given a screenshot |copy_result|, creates a texture layer that contains the |
| // content of that screenshot. The result layer will be size |layer_size|, which |
| // is in dips. |
| std::unique_ptr<ui::Layer> CreateLayerFromScreenshotResult( |
| const gfx::Size& layer_size, |
| std::unique_ptr<viz::CopyOutputResult> copy_result) { |
| DCHECK(copy_result); |
| DCHECK(!copy_result->IsEmpty()); |
| DCHECK_EQ(copy_result->format(), viz::CopyOutputResult::Format::RGBA_TEXTURE); |
| |
| // |texture_size| is in pixels and is not used to size the layer otherwise we |
| // may lose some quality. See https://crbug.com/1134451. |
| const gfx::Size texture_size = copy_result->size(); |
| viz::TransferableResource transferable_resource = |
| viz::TransferableResource::MakeGL( |
| copy_result->GetTextureResult()->mailbox, GL_LINEAR, GL_TEXTURE_2D, |
| copy_result->GetTextureResult()->sync_token, texture_size, |
| /*is_overlay_candidate=*/false); |
| std::unique_ptr<viz::SingleReleaseCallback> take_texture_ownership_callback = |
| copy_result->TakeTextureOwnership(); |
| auto screenshot_layer = std::make_unique<ui::Layer>(); |
| screenshot_layer->SetBounds(gfx::Rect(layer_size)); |
| screenshot_layer->SetTransferableResource( |
| transferable_resource, std::move(take_texture_ownership_callback), |
| layer_size); |
| |
| return screenshot_layer; |
| } |
| |
| std::string GetScreenshotLayerName(int index) { |
| return "Desk " + base::NumberToString(index) + " screenshot layer"; |
| } |
| |
| // The values received from WmGestureHandler via DesksController are in touchpad |
| // units. Convert these units so that what is considered a full touchpad swipe |
| // shifts the animation layer one entire desk length. |
| float TouchpadToXTranslation(float touchpad_x, int desk_length) { |
| return desk_length * touchpad_x / |
| RootWindowDeskSwitchAnimator::kTouchpadSwipeLengthForDeskChange; |
| } |
| |
| } // namespace |
| |
| RootWindowDeskSwitchAnimator::RootWindowDeskSwitchAnimator( |
| aura::Window* root, |
| int starting_desk_index, |
| int ending_desk_index, |
| Delegate* delegate, |
| bool for_remove) |
| : root_window_(root), |
| starting_desk_index_(starting_desk_index), |
| ending_desk_index_(ending_desk_index), |
| delegate_(delegate), |
| animation_layer_owner_(CreateAnimationLayerOwner(root)), |
| root_window_size_( |
| screen_util::SnapBoundsToDisplayEdge(root->bounds(), root).size()), |
| x_translation_offset_(root_window_size_.width() + kDesksSpacing), |
| edge_padding_width_dp_( |
| std::round(root_window_size_.width() * kEdgePaddingRatio)), |
| for_remove_(for_remove) { |
| DCHECK(root_window_); |
| DCHECK_NE(starting_desk_index_, ending_desk_index_); |
| DCHECK(delegate_); |
| |
| screenshot_layers_.resize(desks_util::GetMaxNumberOfDesks()); |
| } |
| |
| RootWindowDeskSwitchAnimator::~RootWindowDeskSwitchAnimator() { |
| // TODO(afakhry): Determine if this is necessary, since generally this object |
| // is only deleted when all animations end, but there might be situations when |
| // we might need to kill the animations before they complete such as when a |
| // display is removed. |
| if (!attached_sequences().empty()) |
| StopObservingImplicitAnimations(); |
| } |
| |
| void RootWindowDeskSwitchAnimator::TakeStartingDeskScreenshot() { |
| if (for_remove_) { |
| // The active desk is about to be removed. Recreate and detach its old |
| // layers to animate them in a jump-like animation. |
| auto* desk_container = DesksController::Get() |
| ->desks()[starting_desk_index_] |
| ->GetDeskContainerForRoot(root_window_); |
| old_windows_layer_tree_owner_ = wm::RecreateLayers(desk_container); |
| root_window_->layer()->Add(old_windows_layer_tree_owner_->root()); |
| root_window_->layer()->StackAtTop(old_windows_layer_tree_owner_->root()); |
| |
| // We don't take a screenshot of the soon-to-be-removed desk, we use an |
| // empty black solid color layer. |
| auto black_layer = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR); |
| black_layer->SetColor(SK_ColorBLACK); |
| CompleteAnimationPhase1WithLayer(std::move(black_layer)); |
| return; |
| } |
| |
| TakeScreenshot( |
| root_window_, |
| base::BindOnce( |
| &RootWindowDeskSwitchAnimator::OnStartingDeskScreenshotTaken, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void RootWindowDeskSwitchAnimator::TakeEndingDeskScreenshot() { |
| DCHECK(starting_desk_screenshot_taken_); |
| |
| TakeScreenshot( |
| root_window_, |
| base::BindOnce(&RootWindowDeskSwitchAnimator::OnEndingDeskScreenshotTaken, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void RootWindowDeskSwitchAnimator::StartAnimation() { |
| DCHECK(starting_desk_screenshot_taken_); |
| DCHECK(!animation_finished_); |
| |
| // Set a transform so that the ending desk will be visible. |
| gfx::Transform animation_layer_ending_transform; |
| animation_layer_ending_transform.Translate( |
| -GetXPositionOfScreenshot(ending_desk_index_), 0); |
| |
| // Animate the parent "animation layer" towards the ending transform. |
| ui::Layer* animation_layer = animation_layer_owner_->root(); |
| ui::ScopedLayerAnimationSettings settings(animation_layer->GetAnimator()); |
| settings.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| settings.AddObserver(this); |
| settings.SetTransitionDuration(kAnimationDuration); |
| settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN); |
| animation_layer->SetTransform(animation_layer_ending_transform); |
| |
| if (for_remove_) { |
| DCHECK(old_windows_layer_tree_owner_); |
| auto* old_windows_layer = old_windows_layer_tree_owner_->root(); |
| DCHECK(old_windows_layer); |
| |
| // Translate the old layers of removed desk's windows back down by |
| // `kRemovedDeskWindowYTranslation`. |
| gfx::Transform transform = old_windows_layer->GetTargetTransform(); |
| ui::ScopedLayerAnimationSettings settings(old_windows_layer->GetAnimator()); |
| settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION); |
| settings.SetTransitionDuration(kRemovedDeskWindowTranslationDuration); |
| settings.SetTweenType(gfx::Tween::EASE_IN); |
| transform.Translate(0, kRemovedDeskWindowYTranslation); |
| old_windows_layer->SetTransform(transform); |
| } |
| } |
| |
| bool RootWindowDeskSwitchAnimator::ReplaceAnimation(int new_ending_desk_index) { |
| DCHECK(features::IsEnhancedDeskAnimations()); |
| DCHECK(!for_remove_); |
| DCHECK_NE(new_ending_desk_index, ending_desk_index_); |
| |
| starting_desk_index_ = ending_desk_index_; |
| ending_desk_index_ = new_ending_desk_index; |
| |
| if (!!screenshot_layers_[ending_desk_index_]) { |
| // Notify the caller to start an animation to |ending_desk_index_|. |
| return false; |
| } |
| |
| ending_desk_screenshot_retries_ = 0; |
| ending_desk_screenshot_taken_ = false; |
| |
| // Notify the caller to activate the next desk and request a screenshot. |
| return true; |
| } |
| |
| base::Optional<int> RootWindowDeskSwitchAnimator::UpdateSwipeAnimation( |
| float scroll_delta_x) { |
| if (!starting_desk_screenshot_taken_ || !ending_desk_screenshot_taken_) |
| return base::nullopt; |
| |
| const float translation_delta_x = |
| TouchpadToXTranslation(scroll_delta_x, x_translation_offset_); |
| |
| // The visible bounds to the user are the root window bounds which always have |
| // origin of 0,0. Therefore the rightmost edge of the visible bounds will be |
| // the width. |
| const int visible_bounds_width = root_window_size_.width(); |
| |
| // Append the new offset to the current transform. Clamp the new transform so |
| // that we do not swipe past the edges. |
| auto* animation_layer = animation_layer_owner_->root(); |
| float translation_x = |
| animation_layer->transform().To2dTranslation().x() + translation_delta_x; |
| translation_x = base::ClampToRange( |
| translation_x, |
| float{-animation_layer->bounds().width() + visible_bounds_width}, 0.f); |
| gfx::Transform transform; |
| transform.Translate(translation_x, 0.f); |
| base::AutoReset<bool> auto_reset(&setting_new_transform_, true); |
| animation_layer->SetTransform(transform); |
| |
| // The animation layer starts with two screenshot layers as the most common |
| // transition is from one desk to another adjacent desk. We may need to signal |
| // the delegate to request a new screenshot if the animating layer is about to |
| // slide past the bounds which are visible to the user (root window bounds). |
| // |
| // moving right ----> |
| // +---+------------------------------+---+ |
| // | | +-----------+ | | |
| // | c | b | a | | c | |
| // | | +___________+ | | |
| // +___+______________________________+___+ |
| // |
| // a - root window/visible bounds - (0,0-1000x500) |
| // b - animating layer with two screenshots and edge padding - (0,0-2350x500) |
| // - current second screenshot is visible (translation (-1200, 0)) |
| // c - Edge padding, equal to |kEdgePaddingRatio| x 1000 - 150 dips wide |
| // We will notify the delegate to request a new screenshot once the x of b is |
| // within |kMinDistanceBeforeScreenshotDp| of the x of a, not including the |
| // edge padding (i.e. translation of (-190, 0)). |
| gfx::RectF transformed_animation_layer_bounds(animation_layer->bounds()); |
| transform.TransformRect(&transformed_animation_layer_bounds); |
| transformed_animation_layer_bounds.Inset(edge_padding_width_dp_, 0); |
| |
| const bool moving_left = scroll_delta_x < 0.f; |
| const bool going_out_of_bounds = |
| moving_left |
| ? transformed_animation_layer_bounds.right() - visible_bounds_width < |
| kMinDistanceBeforeScreenshotDp |
| : transformed_animation_layer_bounds.x() > |
| -kMinDistanceBeforeScreenshotDp; |
| |
| if (!going_out_of_bounds) |
| return base::nullopt; |
| |
| // The upcoming desk we need to show will be an adjacent desk to the desk at |
| // the visible desk index based on |moving_left|. |
| const int new_desk_index = |
| GetIndexOfMostVisibleDeskScreenshot() + (moving_left ? 1 : -1); |
| |
| if (new_desk_index < 0 || |
| new_desk_index >= int{DesksController::Get()->desks().size()}) { |
| return base::nullopt; |
| } |
| |
| return new_desk_index; |
| } |
| |
| void RootWindowDeskSwitchAnimator::PrepareForEndingDeskScreenshot( |
| int new_ending_desk_index) { |
| ending_desk_index_ = new_ending_desk_index; |
| ending_desk_screenshot_retries_ = 0; |
| ending_desk_screenshot_taken_ = false; |
| } |
| |
| int RootWindowDeskSwitchAnimator::EndSwipeAnimation() { |
| // If the starting screenshot has not finished, just let our delegate know |
| // that the desk animation is finished (and |this| will soon be deleted), and |
| // go back to the starting desk. |
| if (!starting_desk_screenshot_taken_) { |
| animation_finished_ = true; |
| // Notifying the delegate may delete |this|. Store the target index in a |
| // local so we do not try to access a member of a deleted object. |
| const int ending_desk_index = starting_desk_index_; |
| delegate_->OnDeskSwitchAnimationFinished(); |
| return ending_desk_index; |
| } |
| |
| // If the ending desk screenshot has not finished, |
| // GetIndexOfMostVisibleDeskScreenshot() will |
| // still return a valid desk index that we can animate to, but we need to make |
| // sure the ending desk screenshot callback does not get called. |
| if (!ending_desk_screenshot_taken_) |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| |
| // In tests, StartAnimation() may trigger OnDeskSwitchAnimationFinished() |
| // right away which may delete |this|. Store the target index in a |
| // local so we do not try to access a member of a deleted object. |
| const int ending_desk_index = GetIndexOfMostVisibleDeskScreenshot(); |
| ending_desk_index_ = ending_desk_index; |
| StartAnimation(); |
| return ending_desk_index; |
| } |
| |
| int RootWindowDeskSwitchAnimator::GetIndexOfMostVisibleDeskScreenshot() const { |
| int index = -1; |
| |
| // The most visible desk is the one whose screenshot layer bounds, including |
| // the transform of its parent that has its origin closest to the root window |
| // origin (0, 0). |
| const gfx::Transform transform = animation_layer_owner_->root()->transform(); |
| int min_distance = INT_MAX; |
| for (int i = 0; i < int{screenshot_layers_.size()}; ++i) { |
| ui::Layer* layer = screenshot_layers_[i]; |
| if (!layer) |
| continue; |
| |
| gfx::RectF bounds(layer->bounds()); |
| transform.TransformRect(&bounds); |
| const int distance = std::abs(bounds.x()); |
| if (distance < min_distance) { |
| min_distance = distance; |
| index = i; |
| } |
| } |
| |
| // TODO(crbug.com/1134390): Convert back to DCHECK when the issue is fixed. |
| CHECK_GE(index, 0); |
| CHECK_LT(index, int{DesksController::Get()->desks().size()}); |
| return index; |
| } |
| |
| void RootWindowDeskSwitchAnimator::OnImplicitAnimationsCompleted() { |
| // |setting_new_transform_| is true we call SetTransform while an animation is |
| // under progress. Do not notify our delegate in that case. |
| if (setting_new_transform_) |
| return; |
| |
| StopObservingImplicitAnimations(); |
| animation_finished_ = true; |
| delegate_->OnDeskSwitchAnimationFinished(); |
| } |
| |
| ui::Layer* RootWindowDeskSwitchAnimator::GetAnimationLayerForTesting() const { |
| return animation_layer_owner_->root(); |
| } |
| |
| void RootWindowDeskSwitchAnimator::CompleteAnimationPhase1WithLayer( |
| std::unique_ptr<ui::Layer> layer) { |
| DCHECK(layer); |
| |
| ui::Layer* starting_desk_screenshot_layer = layer.release(); |
| screenshot_layers_[starting_desk_index_] = starting_desk_screenshot_layer; |
| starting_desk_screenshot_layer->SetName( |
| GetScreenshotLayerName(starting_desk_index_)); |
| |
| auto* animation_layer = animation_layer_owner_->root(); |
| animation_layer->Add(starting_desk_screenshot_layer); |
| |
| // Add the layers on top of everything, so that things that result from desk |
| // activation (such as showing and hiding windows, exiting overview mode ... |
| // etc.) are not visible to the user. |
| auto* root_layer = root_window_->layer(); |
| root_layer->Add(animation_layer); |
| |
| if (for_remove_) { |
| DCHECK(old_windows_layer_tree_owner_); |
| auto* old_windows_layer = old_windows_layer_tree_owner_->root(); |
| DCHECK(old_windows_layer); |
| root_layer->StackBelow(animation_layer, old_windows_layer); |
| |
| // Translate the old layers of the removed desk's windows up by |
| // `kRemovedDeskWindowYTranslation`. |
| gfx::Transform transform = old_windows_layer->GetTargetTransform(); |
| ui::ScopedLayerAnimationSettings settings(old_windows_layer->GetAnimator()); |
| settings.SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| settings.SetTransitionDuration(kRemovedDeskWindowTranslationDuration); |
| settings.SetTweenType(gfx::Tween::EASE_OUT); |
| transform.Translate(0, -kRemovedDeskWindowYTranslation); |
| old_windows_layer->SetTransform(transform); |
| } else { |
| root_layer->StackAtTop(animation_layer); |
| } |
| |
| starting_desk_screenshot_taken_ = true; |
| OnScreenshotLayerCreated(); |
| delegate_->OnStartingDeskScreenshotTaken(ending_desk_index_); |
| } |
| |
| void RootWindowDeskSwitchAnimator::OnStartingDeskScreenshotTaken( |
| std::unique_ptr<viz::CopyOutputResult> copy_result) { |
| if (!copy_result || copy_result->IsEmpty()) { |
| // A frame may be activated before the screenshot requests are satisfied, |
| // leading to us getting an empty |result|. Rerequest the screenshot. |
| // (See viz::Surface::ActivateFrame()). |
| if (++starting_desk_screenshot_retries_ <= kMaxScreenshotRetries) { |
| TakeStartingDeskScreenshot(); |
| } else { |
| LOG(ERROR) << "Received multiple empty screenshots of the starting desk."; |
| NOTREACHED(); |
| starting_desk_screenshot_taken_ = true; |
| delegate_->OnStartingDeskScreenshotTaken(ending_desk_index_); |
| } |
| |
| return; |
| } |
| |
| CompleteAnimationPhase1WithLayer(CreateLayerFromScreenshotResult( |
| root_window_size_, std::move(copy_result))); |
| } |
| |
| void RootWindowDeskSwitchAnimator::OnEndingDeskScreenshotTaken( |
| std::unique_ptr<viz::CopyOutputResult> copy_result) { |
| if (!copy_result || copy_result->IsEmpty()) { |
| // A frame may be activated before the screenshot requests are satisfied, |
| // leading to us getting an empty |result|. Rerequest the screenshot. |
| // (See viz::Surface::ActivateFrame()). |
| if (++ending_desk_screenshot_retries_ <= kMaxScreenshotRetries) { |
| TakeEndingDeskScreenshot(); |
| } else { |
| LOG(ERROR) << "Received multiple empty screenshots of the ending desk."; |
| NOTREACHED(); |
| ending_desk_screenshot_taken_ = true; |
| delegate_->OnEndingDeskScreenshotTaken(); |
| } |
| |
| return; |
| } |
| |
| ui::Layer* ending_desk_screenshot_layer = |
| CreateLayerFromScreenshotResult(root_window_size_, std::move(copy_result)) |
| .release(); |
| screenshot_layers_[ending_desk_index_] = ending_desk_screenshot_layer; |
| ending_desk_screenshot_layer->SetName( |
| GetScreenshotLayerName(ending_desk_index_)); |
| animation_layer_owner_->root()->Add(ending_desk_screenshot_layer); |
| |
| ending_desk_screenshot_taken_ = true; |
| OnScreenshotLayerCreated(); |
| |
| // On ending screenshot may delete |this|. |
| if (on_ending_screenshot_taken_callback_for_testing_) |
| std::move(on_ending_screenshot_taken_callback_for_testing_).Run(); |
| |
| delegate_->OnEndingDeskScreenshotTaken(); |
| } |
| |
| void RootWindowDeskSwitchAnimator::OnScreenshotLayerCreated() { |
| // Set the layer bounds. |screenshot_layers_| always matches the order of the |
| // desks, which is left to right. |
| int num_screenshots = 0; |
| DCHECK_EQ(x_translation_offset_, root_window_size_.width() + kDesksSpacing); |
| for (ui::Layer* layer : screenshot_layers_) { |
| if (!layer) |
| continue; |
| |
| const int x = |
| num_screenshots * x_translation_offset_ + edge_padding_width_dp_; |
| layer->SetBounds(gfx::Rect(gfx::Point(x, 0), root_window_size_)); |
| ++num_screenshots; |
| } |
| |
| // The animation layer is sized to contain all the screenshot layers, |
| // |kDesksSpacing| between any two adjacent screenshot layers, and |
| // |edge_padding_width_dp_| on each side. |
| const gfx::Rect animation_layer_bounds( |
| num_screenshots * x_translation_offset_ - kDesksSpacing + |
| 2 * edge_padding_width_dp_, |
| root_window_size_.height()); |
| auto* animation_layer = animation_layer_owner_->root(); |
| animation_layer->SetBounds(animation_layer_bounds); |
| |
| // Two examples of simple animations (two desks involved), one moving left and |
| // one moving right. Starting desk is one the left, so we start off with no |
| // offset and then slide the animation layer so that ending desk is visible |
| // (target transform of -|x_translation_offset_| translation). |
| // |
| // +-----------+ |
| // | Animation | |
| // | layer | |
| // +-----------+ |
| // / \ |
| // +------------+ +------------+ |
| // | start desk | | end desk | |
| // | screenshot | | screenshot | |
| // | layer (1) | | layer (2) | |
| // +------------+ +------------+ |
| // ^ |
| // start here |
| // |
| // |------------------>| |
| // ^ |
| // `x_translation_offset_` |
| // |
| // Starting desk is one the right, so we need to offset the animation layer |
| // horizontally so that the starting desk is visible |
| // (-|x_translation_offset_|) and the slide the animation layer so that the |
| // ending desk is visible (target transform of 0 translation). |
| // |
| // +-----------+ |
| // | Animation | |
| // | layer | |
| // +-----------+ |
| // / \ |
| // +------------+ +------------+ |
| // | end desk | | start desk | |
| // | screenshot | | screenshot | |
| // | layer (1) | | layer (2) | |
| // +------------+ +------------+ |
| // ^ |
| // |----------------->| start here |
| // ^ |
| // `x_translation_offset_` |
| // |
| // Chained animation example, we are in the middle of animating from desk 3 to |
| // desk 2 (start' to end'), currently halfway through the animation. Desk 1 is |
| // added, so the x position of both desk 2 and desk 3 will get shifted by |
| // |x_translation_offset_|. Shift animation layer by -|x_translation_offset_| |
| // so that half of desk 3 and half of desk 2 are still visible. Without this |
| // shift, there will be a jump and we will see half of desk 2 and half of |
| // desk 1. We then animate from start to end. |
| // |
| // +---------------------------------------+ |
| // | Animation | |
| // | layer | |
| // +---------------------------------------+ |
| // / | \ |
| // +------------+ +------------+ +------------+ |
| // | desk 1 | | desk 2 | | desk 3 | |
| // | screenshot | | screenshot | | screenshot | |
| // | layer | | layer | | layer | |
| // +------------+ +------------+ +------------+ |
| // ^ ^ ^ ^ |
| // end end' start start' |
| |
| // If there is an existing transform, continue animating from there. |
| gfx::Transform current_transform = animation_layer->transform(); |
| DCHECK(current_transform.IsIdentityOr2DTranslation()); |
| if (!current_transform.IsIdentity()) { |
| // If the new layer is located on the left of the prior created layers, |
| // shift the animation layer transform so that the content shown to users |
| // remain the same. |
| if (ending_desk_index_ < starting_desk_index_) { |
| // Setting a new transform will end an ongoing animation, which will |
| // trigger OnImplicitAnimationsCompleted, which notifies our delegate to |
| // delete us. For this case, set a flag so that |
| // OnImplicitAnimationsCompleted does no notifying. |
| current_transform.Translate(-x_translation_offset_, 0); |
| base::AutoReset<bool> auto_reset(&setting_new_transform_, true); |
| animation_layer->SetTransform(current_transform); |
| } |
| return; |
| } |
| |
| // Otherwise, transform |animation_layer| so that starting desk screenshot |
| // layer is the current visible layer. |
| gfx::Transform animation_layer_starting_transform; |
| animation_layer_starting_transform.Translate( |
| -GetXPositionOfScreenshot(starting_desk_index_), 0); |
| base::AutoReset<bool> auto_reset(&setting_new_transform_, true); |
| animation_layer->SetTransform(animation_layer_starting_transform); |
| } |
| |
| int RootWindowDeskSwitchAnimator::GetXPositionOfScreenshot(int index) { |
| ui::Layer* layer = screenshot_layers_[index]; |
| DCHECK(layer); |
| return layer->bounds().x(); |
| } |
| |
| } // namespace ash |