| // 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/shell_window_ids.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" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The space between the starting and ending desks screenshots in dips. |
| constexpr int kDesksSpacing = 50; |
| |
| // 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; |
| |
| constexpr base::TimeDelta kAnimationDuration = |
| base::TimeDelta::FromMilliseconds(300); |
| |
| // 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. |
| // 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); |
| gfx::Rect layer_bounds(root->layer()->size()); |
| layer_bounds.set_width(2 * layer_bounds.width() + kDesksSpacing); |
| animation_layer->SetBounds(layer_bounds); |
| animation_layer->set_name("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) { |
| // We don't take a screenshot of the root because that will be a screenshot of |
| // a screenshot when the starting desk screenshot layer is placed on top of |
| // everything. The container `kShellWindowId_ScreenRotationContainer` is |
| // created for the purpose of taking screenshots of the screen content while |
| // performing the screen rotation animation. |
| // TODO(afakhry): Consider renaming this container. |
| auto* screenshot_layer = |
| root->GetChildById(kShellWindowId_ScreenRotationContainer)->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_selection(request_bounds); |
| |
| screenshot_layer->RequestCopyOfOutput(std::move(screenshot_request)); |
| } |
| |
| // Given a screenshot |copy_result|, creates a texture layer that contains the |
| // content of that screenshot. |
| std::unique_ptr<ui::Layer> CreateLayerFromScreenshotResult( |
| std::unique_ptr<viz::CopyOutputResult> copy_result) { |
| DCHECK(copy_result); |
| DCHECK(!copy_result->IsEmpty()); |
| DCHECK_EQ(copy_result->format(), viz::CopyOutputResult::Format::RGBA_TEXTURE); |
| |
| const gfx::Size layer_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, layer_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->SetTransferableResource( |
| transferable_resource, std::move(take_texture_ownership_callback), |
| layer_size); |
| |
| return screenshot_layer; |
| } |
| |
| } // namespace |
| |
| RootWindowDeskSwitchAnimator::RootWindowDeskSwitchAnimator( |
| aura::Window* root, |
| const Desk* ending_desk, |
| Delegate* delegate, |
| bool move_left) |
| : root_window_(root), |
| ending_desk_(ending_desk), |
| delegate_(delegate), |
| animation_layer_owner_(CreateAnimationLayerOwner(root)), |
| x_translation_offset_(root->layer()->size().width() + kDesksSpacing), |
| move_left_(move_left) { |
| DCHECK(root_window_); |
| DCHECK(ending_desk_); |
| DCHECK(delegate_); |
| } |
| |
| 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() { |
| 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(ending_desk_screenshot_taken_); |
| DCHECK(!animation_finished_); |
| |
| gfx::Transform animation_layer_ending_transfrom; |
| |
| if (move_left_) { |
| // Starting desk is one the left, so the ending transform of the parent |
| // "animation layer" is then a translation to the left such that at the end, |
| // the ending screenshot layer becomes the one visible on the screen. |
| // |
| // +-----------+ |
| // | Animation | |
| // | layer | |
| // +-----------+ |
| // / \ |
| // +------------+ +------------+ |
| // | start desk | | end desk | |
| // | screenshot | | screenshot | |
| // | layer | | layer | |
| // +------------+ +------------+ |
| // ^ |
| // start here |
| // |
| // |<------------------| |
| // ^ |
| // `x_translation_offset_` |
| // |
| animation_layer_ending_transfrom.Translate(-x_translation_offset_, 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_transfrom); |
| } |
| |
| void RootWindowDeskSwitchAnimator::OnImplicitAnimationsCompleted() { |
| StopObservingImplicitAnimations(); |
| |
| animation_finished_ = true; |
| delegate_->OnDeskSwitchAnimationFinished(); |
| } |
| |
| 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_); |
| } |
| |
| return; |
| } |
| |
| ui::Layer* starting_desk_screenshot_layer = |
| CreateLayerFromScreenshotResult(std::move(copy_result)).release(); |
| |
| gfx::Rect screenshot_bounds(root_window_->layer()->size()); |
| gfx::Transform animation_layer_starting_transfrom; |
| |
| if (!move_left_) { |
| // Starting desk is one the right, so we need to offset the screenshot layer |
| // horizontally to the right by an amount equal to its width plus |
| // kDesksSpacing (|x_translation_offset_|). |
| // |
| // +-----------+ |
| // | Animation | |
| // | layer | |
| // +-----------+ |
| // / \ |
| // +------------+ +------------+ |
| // | end desk | | start desk | |
| // | screenshot | | screenshot | |
| // | layer | | layer | |
| // +------------+ +------------+ |
| // ^ |
| // |----------------->| start here |
| // ^ |
| // `x_translation_offset_` |
| // |
| screenshot_bounds.Offset(x_translation_offset_, 0); |
| |
| // However the parent "animation layer" is startingly translated by the same |
| // amount in the opposite direction such that starting desk screenshot is |
| // the one shown on the screen. |
| animation_layer_starting_transfrom.Translate(-x_translation_offset_, 0); |
| } |
| |
| starting_desk_screenshot_layer->set_name("Starting desk screenshot"); |
| starting_desk_screenshot_layer->SetBounds(screenshot_bounds); |
| auto* animation_layer = animation_layer_owner_->root(); |
| animation_layer->Add(starting_desk_screenshot_layer); |
| animation_layer->SetTransform(animation_layer_starting_transfrom); |
| |
| // 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); |
| root_layer->StackAtTop(animation_layer); |
| |
| starting_desk_screenshot_taken_ = true; |
| delegate_->OnStartingDeskScreenshotTaken(ending_desk_); |
| } |
| |
| 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(std::move(copy_result)).release(); |
| |
| gfx::Rect screenshot_bounds(root_window_->layer()->size()); |
| |
| if (move_left_) { |
| // Starting desk is one the left, so we need to offset the ending desk |
| // screenshot layer horizontally to the right by an amount equal to its |
| // width plus kDesksSpacing (|x_translation_offset_|). |
| // |
| // +-----------+ |
| // | Animation | |
| // | layer | |
| // +-----------+ |
| // / \ |
| // +------------+ +------------+ |
| // | start desk | | end desk | |
| // | screenshot | | screenshot | |
| // | layer | | layer | |
| // +------------+ +------------+ |
| // ^ |
| // start here |
| // |
| // |------------------>| |
| // ^ |
| // `x_translation_offset_` |
| // |
| screenshot_bounds.Offset(x_translation_offset_, 0); |
| } |
| |
| ending_desk_screenshot_layer->set_name("Ending desk screenshot"); |
| ending_desk_screenshot_layer->SetBounds(screenshot_bounds); |
| |
| auto* animation_layer = animation_layer_owner_->root(); |
| animation_layer->Add(ending_desk_screenshot_layer); |
| |
| ending_desk_screenshot_taken_ = true; |
| delegate_->OnEndingDeskScreenshotTaken(); |
| } |
| |
| } // namespace ash |