blob: 05858ecd7919624f9a5d5f1f46dc87b0b516cb52 [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/splitview/split_view_controller.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/client/transient_window_client.h"
#include "ui/aura/scoped_window_event_targeting_blocker.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/layout/layout_provider.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);
};
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()),
original_clip_rect_(window_->layer()->clip_rect()) {
type_ = GetWindowDimensionsType(window);
std::vector<aura::Window*> transient_children_to_hide;
for (auto* transient : GetTransientTreeIterator(window)) {
event_targeting_blocker_map_[transient] =
std::make_unique<aura::ScopedWindowEventTargetingBlocker>(transient);
transient->SetProperty(kIsShowingInOverviewKey, true);
// Hide transient children which have been specified to be hidden in
// overview mode.
if (transient != window && transient->GetProperty(kHideInOverviewKey))
transient_children_to_hide.push_back(transient);
}
if (!transient_children_to_hide.empty()) {
hidden_transient_children_ = std::make_unique<ScopedOverviewHideWindows>(
std::move(transient_children_to_hide), /*forced_hidden=*/true);
}
aura::client::GetTransientWindowClient()->AddObserver(this);
// Tablet mode grid layout has scrolling, so all windows must be stacked under
// the current split view window if they share the same parent so that during
// scrolls, they get scrolled underneath the split view window. The window
// will be returned to its proper z-order on exiting overview if it is
// activated.
// TODO(sammiequon): This does not handle the case if either the snapped
// window or this window is an always on top window.
auto* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
if (ShouldUseTabletModeGridLayout() &&
split_view_controller->InSplitViewMode()) {
aura::Window* snapped_window =
split_view_controller->GetDefaultSnappedWindow();
if (window->parent() == snapped_window->parent()) {
// Helper to get the z order of a window in its parent.
auto get_z_order = [](aura::Window* window) -> size_t {
for (size_t i = 0u; i < window->parent()->children().size(); ++i) {
if (window == window->parent()->children()[i])
return i;
}
NOTREACHED();
return 0u;
};
if (get_z_order(window_) > get_z_order(snapped_window))
window_->parent()->StackChildBelow(window_, snapped_window);
}
}
}
ScopedOverviewTransformWindow::~ScopedOverviewTransformWindow() {
for (auto* transient : GetTransientTreeIterator(window_)) {
transient->ClearProperty(kIsShowingInOverviewKey);
DCHECK(event_targeting_blocker_map_.contains(transient));
event_targeting_blocker_map_.erase(transient);
}
// No need to update the clip since we're about to restore it to
// `original_clip_rect_`.
UpdateRoundedCorners(/*show=*/false, /*update_clip=*/false);
aura::client::GetTransientWindowClient()->RemoveObserver(this);
window_->layer()->SetClipRect(original_clip_rect_);
}
// 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));
}
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));
}
}
bool ScopedOverviewTransformWindow::Contains(const aura::Window* target) const {
for (auto* window : 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::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 WindowState::Get(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::EnsureVisible() {
original_opacity_ = 1.f;
}
void ScopedOverviewTransformWindow::UpdateWindowDimensionsType() {
type_ = GetWindowDimensionsType(window_);
overview_bounds_.reset();
}
void ScopedOverviewTransformWindow::UpdateRoundedCorners(bool show,
bool update_clip) {
// Hide the corners if minimized, CaptionContainerView will handle showing the
// rounded corners on the UI.
const bool show_corners = show && !IsMinimized();
// Add the mask which gives the overview item rounded corners, and add the
// shadow around the window.
ui::Layer* layer = window_->layer();
const float scale = layer->transform().Scale2d().x();
const int radius =
views::LayoutProvider::Get()->GetCornerRadiusMetric(views::EMPHASIS_LOW);
const gfx::RoundedCornersF radii(show_corners ? (radius / scale) : 0.0f);
layer->SetRoundedCornerRadius(radii);
layer->SetIsFastRoundedCorner(true);
if (!update_clip || layer->GetAnimator()->is_animating() || IsMinimized())
return;
const int top_inset = GetTopInset();
if (top_inset > 0) {
gfx::Rect clip_rect(window_->bounds().size());
// We add 1 to the top_inset, because in some cases, the header is not
// clipped fully due to what seems to be a rounding error.
// TODO(afakhry|sammiequon): Investigate a proper fix for this.
clip_rect.Inset(0, top_inset + 1, 0, 0);
ScopedOverviewAnimationSettings settings(
OVERVIEW_ANIMATION_FRAME_HEADER_CLIP, window_);
layer->SetClipRect(clip_rect);
}
}
void ScopedOverviewTransformWindow::OnTransientChildWindowAdded(
aura::Window* parent,
aura::Window* transient_child) {
if (parent != window_ && !::wm::HasTransientAncestor(parent, window_))
return;
DCHECK(!event_targeting_blocker_map_.contains(transient_child));
event_targeting_blocker_map_[transient_child] =
std::make_unique<aura::ScopedWindowEventTargetingBlocker>(
transient_child);
transient_child->SetProperty(kIsShowingInOverviewKey, true);
}
void ScopedOverviewTransformWindow::OnTransientChildWindowRemoved(
aura::Window* parent,
aura::Window* transient_child) {
if (parent != window_ && !::wm::HasTransientAncestor(parent, window_))
return;
transient_child->ClearProperty(kIsShowingInOverviewKey);
DCHECK(event_targeting_blocker_map_.contains(transient_child));
event_targeting_blocker_map_.erase(transient_child);
}
void ScopedOverviewTransformWindow::CloseWidget() {
aura::Window* parent_window = ::wm::GetTransientRoot(window_);
if (parent_window)
window_util::CloseWidgetForWindow(parent_window);
}
// static
void ScopedOverviewTransformWindow::SetImmediateCloseForTests() {
immediate_close_for_tests = true;
}
} // namespace ash