blob: 9821a124e19adf414eaafec7c505d75933b4a381 [file] [log] [blame]
// Copyright 2013 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/overview/scoped_overview_transform_window.h"
#include <algorithm>
#include <utility>
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/wm/overview/delayed_animation_observer_impl.h"
#include "ash/wm/overview/overview_constants.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/scoped_overview_animation_settings.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_preview_view.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_transient_descendant_iterator.h"
#include "ash/wm/window_util.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_observer.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/gfx/transform_util.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/shadow_controller.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
// When set to true by tests makes closing the widget synchronous.
bool immediate_close_for_tests = false;
// Delay closing window to allow it to shrink and fade out.
constexpr int kCloseWindowDelayInMilliseconds = 150;
ScopedOverviewTransformWindow::GridWindowFillMode GetWindowDimensionsType(
aura::Window* window) {
if (window->bounds().width() >
window->bounds().height() *
ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold) {
return ScopedOverviewTransformWindow::GridWindowFillMode::kLetterBoxed;
}
if (window->bounds().height() >
window->bounds().width() *
ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold) {
return ScopedOverviewTransformWindow::GridWindowFillMode::kPillarBoxed;
}
return ScopedOverviewTransformWindow::GridWindowFillMode::kNormal;
}
} // namespace
class ScopedOverviewTransformWindow::LayerCachingAndFilteringObserver
: public ui::LayerObserver {
public:
explicit LayerCachingAndFilteringObserver(ui::Layer* layer) : layer_(layer) {
layer_->AddObserver(this);
layer_->AddCacheRenderSurfaceRequest();
layer_->AddTrilinearFilteringRequest();
}
~LayerCachingAndFilteringObserver() override {
if (layer_) {
layer_->RemoveTrilinearFilteringRequest();
layer_->RemoveCacheRenderSurfaceRequest();
layer_->RemoveObserver(this);
}
}
// ui::LayerObserver overrides:
void LayerDestroyed(ui::Layer* layer) override {
layer_->RemoveObserver(this);
layer_ = nullptr;
}
private:
ui::Layer* layer_;
DISALLOW_COPY_AND_ASSIGN(LayerCachingAndFilteringObserver);
};
// WindowMask is applied to overview windows to give them rounded edges while
// they are in overview mode.
class ScopedOverviewTransformWindow::WindowMask : public ui::LayerDelegate,
public aura::WindowObserver {
public:
explicit WindowMask(aura::Window* window)
: layer_(ui::LAYER_TEXTURED), window_(window) {
window_->AddObserver(this);
layer_.set_delegate(this);
layer_.SetFillsBoundsOpaquely(false);
}
~WindowMask() override {
if (window_)
window_->RemoveObserver(this);
layer_.set_delegate(nullptr);
}
void set_top_inset(int top_inset) { top_inset_ = top_inset; }
ui::Layer* layer() { return &layer_; }
private:
// ui::LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override {
cc::PaintFlags flags;
flags.setAlpha(255);
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
// The amount of round applied on the mask gets scaled as |window_| gets
// transformed, so reverse the transform so the final scaled round matches
// |kOverviewWindowRoundingDp|.
const gfx::Vector2dF scale = window_->transform().Scale2d();
const SkScalar r_x =
SkIntToScalar(std::round(kOverviewWindowRoundingDp / scale.x()));
const SkScalar r_y =
SkIntToScalar(std::round(kOverviewWindowRoundingDp / scale.y()));
SkPath path;
SkScalar radii[8] = {r_x, r_y, r_x, r_y, r_x, r_y, r_x, r_y};
gfx::Rect bounds(layer()->size());
bounds.Inset(0, top_inset_, 0, 0);
path.addRoundRect(gfx::RectToSkRect(bounds), radii);
ui::PaintRecorder recorder(context, layer()->size());
recorder.canvas()->DrawPath(path, flags);
}
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {}
// aura::WindowObserver:
void OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) override {
layer_.SetBounds(new_bounds);
}
void OnWindowDestroying(aura::Window* window) override {
window_->RemoveObserver(this);
window_ = nullptr;
}
ui::Layer layer_;
int top_inset_ = 0;
// Pointer to the window of which this is a mask to.
aura::Window* window_;
DISALLOW_COPY_AND_ASSIGN(WindowMask);
};
ScopedOverviewTransformWindow::ScopedOverviewTransformWindow(
OverviewItem* overview_item,
aura::Window* window)
: overview_item_(overview_item),
window_(window),
original_opacity_(window->layer()->GetTargetOpacity()),
original_mask_layer_(window_->layer()->layer_mask_layer()),
weak_ptr_factory_(this) {
type_ = GetWindowDimensionsType(window);
original_event_targeting_policy_ = window_->event_targeting_policy();
window_->SetEventTargetingPolicy(ws::mojom::EventTargetingPolicy::NONE);
window_->SetProperty(kIsShowingInOverviewKey, true);
// Hide transient children which have been specified to be hidden in overview
// mode.
std::vector<aura::Window*> transient_children_to_hide;
for (auto* transient : wm::GetTransientTreeIterator(window)) {
if (transient == window)
continue;
if (transient->GetProperty(kHideInOverviewKey))
transient_children_to_hide.push_back(transient);
transient->SetProperty(kIsShowingInOverviewKey, true);
}
if (!transient_children_to_hide.empty()) {
hidden_transient_children_ = std::make_unique<ScopedOverviewHideWindows>(
std::move(transient_children_to_hide), /*forced_hidden=*/true);
}
}
ScopedOverviewTransformWindow::~ScopedOverviewTransformWindow() {
for (auto* transient : wm::GetTransientTreeIterator(window_))
transient->ClearProperty(kIsShowingInOverviewKey);
DCHECK(!window_->GetProperty(kIsShowingInOverviewKey));
window_->SetEventTargetingPolicy(original_event_targeting_policy_);
UpdateMask(/*show=*/false);
StopObservingImplicitAnimations();
}
// static
float ScopedOverviewTransformWindow::GetItemScale(const gfx::SizeF& source,
const gfx::SizeF& target,
int top_view_inset,
int title_height) {
return std::min(2.0f, (target.height() - title_height) /
(source.height() - top_view_inset));
}
// static
gfx::Transform ScopedOverviewTransformWindow::GetTransformForRect(
const gfx::RectF& src_rect,
const gfx::RectF& dst_rect) {
DCHECK(!src_rect.IsEmpty());
gfx::Transform transform;
transform.Translate(dst_rect.x() - src_rect.x(), dst_rect.y() - src_rect.y());
transform.Scale(dst_rect.width() / src_rect.width(),
dst_rect.height() / src_rect.height());
return transform;
}
void ScopedOverviewTransformWindow::RestoreWindow(bool reset_transform) {
// Shadow controller may be null on shutdown.
if (Shell::Get()->shadow_controller())
Shell::Get()->shadow_controller()->UpdateShadowForWindow(window_);
if (IsMinimized()) {
// Minimized windows may have had their transforms altered by swiping up
// from the shelf.
SetTransform(window_, gfx::Transform());
return;
}
if (reset_transform) {
ScopedAnimationSettings animation_settings_list;
BeginScopedAnimation(overview_item_->GetExitTransformAnimationType(),
&animation_settings_list);
for (auto& settings : animation_settings_list) {
auto exit_observer = std::make_unique<ExitAnimationObserver>();
settings->AddObserver(exit_observer.get());
Shell::Get()->overview_controller()->AddExitAnimationObserver(
std::move(exit_observer));
}
// Use identity transform directly to reset window's transform when exiting
// overview.
SetTransform(window_, gfx::Transform());
// Add requests to cache render surface and perform trilinear filtering for
// the exit animation of overview mode. The requests will be removed when
// the exit animation finishes.
if (features::IsTrilinearFilteringEnabled()) {
for (auto& settings : animation_settings_list) {
settings->CacheRenderSurface();
settings->TrilinearFiltering();
}
}
}
ScopedOverviewAnimationSettings animation_settings(
overview_item_->GetExitOverviewAnimationType(), window_);
SetOpacity(original_opacity_);
window_->layer()->SetMaskLayer(original_mask_layer_);
}
void ScopedOverviewTransformWindow::BeginScopedAnimation(
OverviewAnimationType animation_type,
ScopedAnimationSettings* animation_settings) {
if (animation_type == OVERVIEW_ANIMATION_NONE)
return;
for (auto* window : GetVisibleTransientTreeIterator(window_)) {
auto settings = std::make_unique<ScopedOverviewAnimationSettings>(
animation_type, window);
settings->DeferPaint();
// Create an EnterAnimationObserver if this is an enter overview layout
// animation.
if (animation_type == OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_ON_ENTER) {
auto enter_observer = std::make_unique<EnterAnimationObserver>();
settings->AddObserver(enter_observer.get());
Shell::Get()->overview_controller()->AddEnterAnimationObserver(
std::move(enter_observer));
}
animation_settings->push_back(std::move(settings));
}
if (animation_type == OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW &&
animation_settings->size() > 0u) {
animation_settings->front()->AddObserver(this);
}
}
bool ScopedOverviewTransformWindow::Contains(const aura::Window* target) const {
for (auto* window : wm::GetTransientTreeIterator(window_)) {
if (window->Contains(target))
return true;
}
if (!IsMinimized())
return false;
return overview_item_->item_widget()->GetNativeWindow()->Contains(target);
}
gfx::RectF ScopedOverviewTransformWindow::GetTransformedBounds() const {
return ::ash::GetTransformedBounds(window_, GetTopInset());
}
int ScopedOverviewTransformWindow::GetTopInset() const {
// Mirror window doesn't have insets.
if (IsMinimized())
return 0;
for (auto* window : GetVisibleTransientTreeIterator(window_)) {
// If there are regular windows in the transient ancestor tree, all those
// windows are shown in the same overview item and the header is not masked.
if (window != window_ &&
window->type() == aura::client::WINDOW_TYPE_NORMAL) {
return 0;
}
}
return window_->GetProperty(aura::client::kTopViewInset);
}
void ScopedOverviewTransformWindow::OnWindowDestroyed() {
window_ = nullptr;
}
void ScopedOverviewTransformWindow::SetOpacity(float opacity) {
for (auto* window : GetVisibleTransientTreeIterator(GetOverviewWindow()))
window->layer()->SetOpacity(opacity);
}
gfx::RectF ScopedOverviewTransformWindow::ShrinkRectToFitPreservingAspectRatio(
const gfx::RectF& rect,
const gfx::RectF& bounds,
int top_view_inset,
int title_height) {
DCHECK(!rect.IsEmpty());
DCHECK_LE(top_view_inset, rect.height());
const float scale =
GetItemScale(rect.size(), bounds.size(), top_view_inset, title_height);
const float horizontal_offset = 0.5 * (bounds.width() - scale * rect.width());
const float width = bounds.width() - 2.f * horizontal_offset;
const float vertical_offset = title_height - scale * top_view_inset;
const float height =
std::min(scale * rect.height(), bounds.height() - vertical_offset);
gfx::RectF new_bounds(bounds.x() + horizontal_offset,
bounds.y() + vertical_offset, width, height);
switch (type()) {
case ScopedOverviewTransformWindow::GridWindowFillMode::kLetterBoxed:
case ScopedOverviewTransformWindow::GridWindowFillMode::kPillarBoxed: {
// Attempt to scale |rect| to fit |bounds|. Maintain the aspect ratio of
// |rect|. Letter boxed windows' width will match |bounds|'s height and
// pillar boxed windows' height will match |bounds|'s height.
const bool is_pillar =
type() ==
ScopedOverviewTransformWindow::GridWindowFillMode::kPillarBoxed;
gfx::RectF src = rect;
new_bounds = bounds;
src.Inset(0, top_view_inset, 0, 0);
new_bounds.Inset(0, title_height, 0, 0);
float scale = is_pillar ? new_bounds.height() / src.height()
: new_bounds.width() / src.width();
gfx::SizeF size(is_pillar ? src.width() * scale : new_bounds.width(),
is_pillar ? new_bounds.height() : src.height() * scale);
new_bounds.ClampToCenteredSize(size);
// Extend |new_bounds| in the vertical direction to account for the header
// that will be hidden.
if (top_view_inset > 0)
new_bounds.Inset(0, -(scale * top_view_inset), 0, 0);
// Save the original bounds minus the title into |overview_bounds_|
// so a larger backdrop can be drawn behind the window after.
overview_bounds_ = bounds;
overview_bounds_->Inset(0, title_height, 0, 0);
break;
}
default:
break;
}
return new_bounds;
}
aura::Window* ScopedOverviewTransformWindow::GetOverviewWindow() const {
if (IsMinimized())
return overview_item_->item_widget()->GetNativeWindow();
return window_;
}
void ScopedOverviewTransformWindow::Close() {
if (immediate_close_for_tests) {
CloseWidget();
return;
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ScopedOverviewTransformWindow::CloseWidget,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kCloseWindowDelayInMilliseconds));
}
bool ScopedOverviewTransformWindow::IsMinimized() const {
return wm::GetWindowState(window_)->IsMinimized();
}
void ScopedOverviewTransformWindow::PrepareForOverview() {
Shell::Get()->shadow_controller()->UpdateShadowForWindow(window_);
DCHECK(!overview_started_);
overview_started_ = true;
// Add requests to cache render surface and perform trilinear filtering. The
// requests will be removed in dtor. So the requests will be valid during the
// enter animation and the whole time during overview mode. For the exit
// animation of overview mode, we need to add those requests again.
if (features::IsTrilinearFilteringEnabled()) {
for (auto* window : GetVisibleTransientTreeIterator(GetOverviewWindow())) {
cached_and_filtered_layer_observers_.push_back(
std::make_unique<LayerCachingAndFilteringObserver>(window->layer()));
}
}
}
void ScopedOverviewTransformWindow::CloseWidget() {
aura::Window* parent_window = ::wm::GetTransientRoot(window_);
if (parent_window)
wm::CloseWidgetForWindow(parent_window);
}
// static
void ScopedOverviewTransformWindow::SetImmediateCloseForTests() {
immediate_close_for_tests = true;
}
void ScopedOverviewTransformWindow::EnsureVisible() {
original_opacity_ = 1.f;
}
void ScopedOverviewTransformWindow::UpdateWindowDimensionsType() {
type_ = GetWindowDimensionsType(window_);
overview_bounds_.reset();
}
void ScopedOverviewTransformWindow::UpdateMask(bool show) {
// Minimized windows have their corners rounded in CaptionContainerView.
if (IsMinimized())
return;
// Add the mask which gives the overview item rounded corners, and add the
// shadow around the window.
ui::Layer* layer = window_->layer();
if (ash::features::ShouldUseShaderRoundedCorner()) {
const float scale = layer->transform().Scale2d().x();
const gfx::RoundedCornersF radii(show ? kOverviewWindowRoundingDp / scale
: 0.0f);
layer->SetRoundedCornerRadius(radii);
layer->SetIsFastRoundedCorner(true);
return;
}
if (!base::FeatureList::IsEnabled(features::kEnableOverviewRoundedCorners) ||
!show) {
mask_.reset();
return;
}
mask_ = std::make_unique<WindowMask>(window_);
mask_->layer()->SetBounds(layer->bounds());
mask_->set_top_inset(GetTopInset());
layer->SetMaskLayer(mask_->layer());
}
void ScopedOverviewTransformWindow::CancelAnimationsListener() {
StopObservingImplicitAnimations();
}
void ScopedOverviewTransformWindow::OnLayerAnimationStarted(
ui::LayerAnimationSequence* sequence) {
// Remove the mask before animating because masks affect animation
// performance. The mask will be added back once the animation is completed.
overview_item_->UpdateMaskAndShadow();
}
void ScopedOverviewTransformWindow::OnImplicitAnimationsCompleted() {
overview_item_->UpdateMaskAndShadow();
overview_item_->OnDragAnimationCompleted();
}
gfx::Rect ScopedOverviewTransformWindow::GetMaskBoundsForTesting() const {
if (!mask_)
return gfx::Rect();
return mask_->layer()->bounds();
}
} // namespace ash