blob: bb7a7f9e6bf3ed712be49ecff654e578b2bb2c92 [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/window_state.h"
#include <memory>
#include <utility>
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/window_animation_types.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/public/cpp/window_state_type.h"
#include "ash/public/interfaces/window_pin_type.mojom.h"
#include "ash/public/interfaces/window_state_type.mojom.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/wm/default_state.h"
#include "ash/wm/immersive_gesture_drag_handler.h"
#include "ash/wm/pip/pip_positioner.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/window_state_delegate.h"
#include "ash/wm/window_state_observer.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_event.h"
#include "base/auto_reset.h"
#include "services/ws/public/mojom/window_tree_constants.mojom.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/painter.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/ime_util_chromeos.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace wm {
namespace {
// TODO(edcourtney): Move this to a PIP specific file, once it's created.
const int kPipRoundedCornerRadius = 8;
bool IsTabletModeEnabled() {
return Shell::Get()
->tablet_mode_controller()
->IsTabletModeWindowManagerEnabled();
}
// A tentative class to set the bounds on the window.
// TODO(oshima): Once all logic is cleaned up, move this to the real layout
// manager with proper friendship.
class BoundsSetter : public aura::LayoutManager {
public:
BoundsSetter() = default;
~BoundsSetter() override = default;
// aura::LayoutManager overrides:
void OnWindowResized() override {}
void OnWindowAddedToLayout(aura::Window* child) override {}
void OnWillRemoveWindowFromLayout(aura::Window* child) override {}
void OnWindowRemovedFromLayout(aura::Window* child) override {}
void OnChildWindowVisibilityChanged(aura::Window* child,
bool visible) override {}
void SetChildBounds(aura::Window* child,
const gfx::Rect& requested_bounds) override {}
void SetBounds(aura::Window* window, const gfx::Rect& bounds) {
SetChildBoundsDirect(window, bounds);
}
private:
DISALLOW_COPY_AND_ASSIGN(BoundsSetter);
};
WMEventType WMEventTypeFromShowState(ui::WindowShowState requested_show_state) {
switch (requested_show_state) {
case ui::SHOW_STATE_DEFAULT:
case ui::SHOW_STATE_NORMAL:
return WM_EVENT_NORMAL;
case ui::SHOW_STATE_MINIMIZED:
return WM_EVENT_MINIMIZE;
case ui::SHOW_STATE_MAXIMIZED:
return WM_EVENT_MAXIMIZE;
case ui::SHOW_STATE_FULLSCREEN:
return WM_EVENT_FULLSCREEN;
case ui::SHOW_STATE_INACTIVE:
return WM_EVENT_SHOW_INACTIVE;
case ui::SHOW_STATE_END:
NOTREACHED() << "No WMEvent defined for the show state:"
<< requested_show_state;
}
return WM_EVENT_NORMAL;
}
WMEventType WMEventTypeFromWindowPinType(ash::mojom::WindowPinType type) {
switch (type) {
case ash::mojom::WindowPinType::NONE:
return WM_EVENT_NORMAL;
case ash::mojom::WindowPinType::PINNED:
return WM_EVENT_PIN;
case ash::mojom::WindowPinType::TRUSTED_PINNED:
return WM_EVENT_TRUSTED_PIN;
}
NOTREACHED() << "No WMEvent defined for the window pin type:" << type;
return WM_EVENT_NORMAL;
}
float GetCurrentSnappedWidthRatio(aura::Window* window) {
gfx::Rect maximized_bounds =
screen_util::GetMaximizedWindowBoundsInParent(window);
return static_cast<float>(window->bounds().width()) /
static_cast<float>(maximized_bounds.width());
}
// Move all transient children to |dst_root|, including the ones in the child
// windows and transient children of the transient children.
void MoveAllTransientChildrenToNewRoot(aura::Window* window) {
aura::Window* dst_root = window->GetRootWindow();
for (aura::Window* transient_child : ::wm::GetTransientChildren(window)) {
if (!transient_child->parent())
continue;
const int container_id = transient_child->parent()->id();
DCHECK_GE(container_id, 0);
aura::Window* container = dst_root->GetChildById(container_id);
if (container->Contains(transient_child))
continue;
gfx::Rect child_bounds = transient_child->bounds();
::wm::ConvertRectToScreen(dst_root, &child_bounds);
container->AddChild(transient_child);
transient_child->SetBoundsInScreen(
child_bounds,
display::Screen::GetScreen()->GetDisplayNearestWindow(window));
// Transient children may have transient children.
MoveAllTransientChildrenToNewRoot(transient_child);
}
// Move transient children of the child windows if any.
for (aura::Window* child : window->children())
MoveAllTransientChildrenToNewRoot(child);
}
} // namespace
class WindowState::PipMask : public ui::LayerDelegate,
public aura::WindowObserver {
public:
explicit PipMask(aura::Window* window)
: layer_(ui::LAYER_TEXTURED), window_(window) {
DCHECK(window);
DCHECK(window->layer());
window_->AddObserver(this);
layer_.set_delegate(this);
layer_.SetFillsBoundsOpaquely(false);
layer_.SetBounds(window->layer()->bounds());
}
~PipMask() override {
if (window_)
window_->RemoveObserver(this);
layer_.set_delegate(nullptr);
}
ui::Layer* layer() { return &layer_; }
const aura::Window* window() const { return window_; }
private:
// ui::LayerDelegate overridden:
void OnPaintLayer(const ui::PaintContext& context) override {
cc::PaintFlags flags;
flags.setAlpha(255);
flags.setAntiAlias(true);
flags.setStyle(cc::PaintFlags::kFill_Style);
const int radius = kPipRoundedCornerRadius;
SkScalar radii[8] = {radius, radius, // top-left
radius, radius, // top-right
radius, radius, // bottom-right
radius, radius}; // bottom-left
SkPath path;
path.addRoundRect(gfx::RectToSkRect(gfx::Rect(layer()->size())), 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 overridden:
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_;
aura::Window* window_;
DISALLOW_COPY_AND_ASSIGN(PipMask);
};
constexpr base::TimeDelta WindowState::kBoundsChangeSlideDuration;
WindowState::~WindowState() {
// WindowState is registered as an owned property of |window_|, and window
// unregisters all of its observers in its d'tor before destroying its
// properties. As a result, window_->RemoveObserver() doesn't need to (and
// shouldn't) be called here.
}
bool WindowState::HasDelegate() const {
return !!delegate_;
}
void WindowState::SetDelegate(std::unique_ptr<WindowStateDelegate> delegate) {
DCHECK(!delegate_.get());
delegate_ = std::move(delegate);
}
mojom::WindowStateType WindowState::GetStateType() const {
return current_state_->GetType();
}
bool WindowState::IsMinimized() const {
return GetStateType() == mojom::WindowStateType::MINIMIZED;
}
bool WindowState::IsMaximized() const {
return GetStateType() == mojom::WindowStateType::MAXIMIZED;
}
bool WindowState::IsFullscreen() const {
return GetStateType() == mojom::WindowStateType::FULLSCREEN;
}
bool WindowState::IsMaximizedOrFullscreenOrPinned() const {
return GetStateType() == mojom::WindowStateType::MAXIMIZED ||
GetStateType() == mojom::WindowStateType::FULLSCREEN || IsPinned();
}
bool WindowState::IsSnapped() const {
return GetStateType() == mojom::WindowStateType::LEFT_SNAPPED ||
GetStateType() == mojom::WindowStateType::RIGHT_SNAPPED;
}
bool WindowState::IsPinned() const {
return GetStateType() == mojom::WindowStateType::PINNED ||
GetStateType() == mojom::WindowStateType::TRUSTED_PINNED;
}
bool WindowState::IsTrustedPinned() const {
return GetStateType() == mojom::WindowStateType::TRUSTED_PINNED;
}
bool WindowState::IsPip() const {
return GetStateType() == mojom::WindowStateType::PIP;
}
bool WindowState::IsNormalStateType() const {
return GetStateType() == mojom::WindowStateType::NORMAL ||
GetStateType() == mojom::WindowStateType::DEFAULT;
}
bool WindowState::IsNormalOrSnapped() const {
return IsNormalStateType() || IsSnapped();
}
bool WindowState::IsActive() const {
return ::wm::IsActiveWindow(window_);
}
bool WindowState::IsUserPositionable() const {
return (window_->type() == aura::client::WINDOW_TYPE_NORMAL ||
window_->type() == aura::client::WINDOW_TYPE_PANEL);
}
bool WindowState::HasMaximumWidthOrHeight() const {
if (!window_->delegate())
return false;
const gfx::Size max_size = window_->delegate()->GetMaximumSize();
return max_size.width() || max_size.height();
}
bool WindowState::CanMaximize() const {
// Window must allow maximization and have no maximum width or height.
if ((window_->GetProperty(aura::client::kResizeBehaviorKey) &
ws::mojom::kResizeBehaviorCanMaximize) == 0) {
return false;
}
return !HasMaximumWidthOrHeight();
}
bool WindowState::CanMinimize() const {
return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
ws::mojom::kResizeBehaviorCanMinimize) != 0;
}
bool WindowState::CanResize() const {
return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
ws::mojom::kResizeBehaviorCanResize) != 0;
}
bool WindowState::CanActivate() const {
return ::wm::CanActivateWindow(window_);
}
bool WindowState::CanSnap() const {
const bool is_panel_window =
window_->type() == aura::client::WINDOW_TYPE_PANEL;
if (!CanResize() || is_panel_window || IsPip())
return false;
// Allow windows with no maximum width or height to be snapped.
// TODO(oshima): We should probably snap if the maximum size is defined
// and greater than the snapped size.
return !HasMaximumWidthOrHeight();
}
bool WindowState::HasRestoreBounds() const {
return window_->GetProperty(aura::client::kRestoreBoundsKey) != nullptr;
}
void WindowState::Maximize() {
::wm::SetWindowState(window_, ui::SHOW_STATE_MAXIMIZED);
}
void WindowState::Minimize() {
::wm::SetWindowState(window_, ui::SHOW_STATE_MINIMIZED);
}
void WindowState::Unminimize() {
::wm::Unminimize(window_);
}
void WindowState::Activate() {
wm::ActivateWindow(window_);
}
void WindowState::Deactivate() {
wm::DeactivateWindow(window_);
}
void WindowState::Restore() {
if (!IsNormalStateType()) {
const WMEvent event(WM_EVENT_NORMAL);
OnWMEvent(&event);
}
}
void WindowState::DisableAlwaysOnTop(aura::Window* window_on_top) {
if (GetAlwaysOnTop()) {
// |window_| is hidden first to avoid canceling fullscreen mode when it is
// no longer always on top and gets added to default container. This avoids
// sending redundant OnFullscreenStateChanged to the layout manager. The
// |window_| visibility is restored after it no longer obscures the
// |window_on_top|.
bool visible = window_->IsVisible();
if (visible)
window_->Hide();
window_->SetProperty(aura::client::kAlwaysOnTopKey, false);
// Technically it is possible that a |window_| could make itself
// always_on_top really quickly. This is probably not a realistic case but
// check if the two windows are in the same container just in case.
if (window_on_top && window_on_top->parent() == window_->parent())
window_->parent()->StackChildAbove(window_on_top, window_);
if (visible)
window_->Show();
cached_always_on_top_ = true;
}
}
void WindowState::RestoreAlwaysOnTop() {
if (cached_always_on_top_) {
cached_always_on_top_ = false;
window_->SetProperty(aura::client::kAlwaysOnTopKey, true);
}
}
void WindowState::OnWMEvent(const WMEvent* event) {
current_state_->OnWMEvent(this, event);
UpdateSnappedWidthRatio(event);
}
void WindowState::SaveCurrentBoundsForRestore() {
gfx::Rect bounds_in_screen = window_->bounds();
::wm::ConvertRectToScreen(window_->parent(), &bounds_in_screen);
SetRestoreBoundsInScreen(bounds_in_screen);
}
gfx::Rect WindowState::GetRestoreBoundsInScreen() const {
gfx::Rect* restore_bounds =
window_->GetProperty(aura::client::kRestoreBoundsKey);
return restore_bounds ? *restore_bounds : gfx::Rect();
}
gfx::Rect WindowState::GetRestoreBoundsInParent() const {
gfx::Rect result = GetRestoreBoundsInScreen();
::wm::ConvertRectFromScreen(window_->parent(), &result);
return result;
}
void WindowState::SetRestoreBoundsInScreen(const gfx::Rect& bounds) {
window_->SetProperty(aura::client::kRestoreBoundsKey, new gfx::Rect(bounds));
}
void WindowState::SetRestoreBoundsInParent(const gfx::Rect& bounds) {
gfx::Rect bounds_in_screen = bounds;
::wm::ConvertRectToScreen(window_->parent(), &bounds_in_screen);
SetRestoreBoundsInScreen(bounds_in_screen);
}
void WindowState::ClearRestoreBounds() {
window_->ClearProperty(aura::client::kRestoreBoundsKey);
window_->ClearProperty(::wm::kVirtualKeyboardRestoreBoundsKey);
}
std::unique_ptr<WindowState::State> WindowState::SetStateObject(
std::unique_ptr<WindowState::State> new_state) {
current_state_->DetachState(this);
std::unique_ptr<WindowState::State> old_object = std::move(current_state_);
current_state_ = std::move(new_state);
current_state_->AttachState(this, old_object.get());
return old_object;
}
void WindowState::UpdateSnappedWidthRatio(const WMEvent* event) {
if (!IsSnapped()) {
snapped_width_ratio_.reset();
return;
}
const WMEventType type = event->type();
// Initializes |snapped_width_ratio_| whenever |event| is snapping event.
if (type == WM_EVENT_SNAP_LEFT || type == WM_EVENT_SNAP_RIGHT ||
type == WM_EVENT_CYCLE_SNAP_LEFT || type == WM_EVENT_CYCLE_SNAP_RIGHT) {
// Since |UpdateSnappedWidthRatio()| is called post WMEvent taking effect,
// |window_|'s bounds is in a correct state for ratio update.
snapped_width_ratio_ =
base::make_optional(GetCurrentSnappedWidthRatio(window_));
return;
}
// |snapped_width_ratio_| under snapped state may change due to bounds event.
if (event->IsBoundsEvent()) {
snapped_width_ratio_ =
base::make_optional(GetCurrentSnappedWidthRatio(window_));
}
}
void WindowState::SetPreAutoManageWindowBounds(const gfx::Rect& bounds) {
pre_auto_manage_window_bounds_ = base::make_optional(bounds);
}
void WindowState::SetPreAddedToWorkspaceWindowBounds(const gfx::Rect& bounds) {
pre_added_to_workspace_window_bounds_ = base::make_optional(bounds);
}
void WindowState::SetPersistentWindowInfo(
const PersistentWindowInfo& persistent_window_info) {
persistent_window_info_ = base::make_optional(persistent_window_info);
}
void WindowState::ResetPersistentWindowInfo() {
persistent_window_info_.reset();
}
void WindowState::AddObserver(WindowStateObserver* observer) {
observer_list_.AddObserver(observer);
}
void WindowState::RemoveObserver(WindowStateObserver* observer) {
observer_list_.RemoveObserver(observer);
}
bool WindowState::GetHideShelfWhenFullscreen() const {
return window_->GetProperty(kHideShelfWhenFullscreenKey);
}
void WindowState::SetHideShelfWhenFullscreen(bool value) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(kHideShelfWhenFullscreenKey, value);
}
bool WindowState::GetWindowPositionManaged() const {
return window_->GetProperty(kWindowPositionManagedTypeKey);
}
void WindowState::SetWindowPositionManaged(bool managed) {
window_->SetProperty(kWindowPositionManagedTypeKey, managed);
}
bool WindowState::CanConsumeSystemKeys() const {
return window_->GetProperty(kCanConsumeSystemKeysKey);
}
void WindowState::SetCanConsumeSystemKeys(bool can_consume_system_keys) {
window_->SetProperty(kCanConsumeSystemKeysKey, can_consume_system_keys);
}
bool WindowState::IsInImmersiveFullscreen() const {
return window_->GetProperty(kImmersiveIsActive);
}
void WindowState::set_bounds_changed_by_user(bool bounds_changed_by_user) {
bounds_changed_by_user_ = bounds_changed_by_user;
if (bounds_changed_by_user) {
pre_auto_manage_window_bounds_.reset();
pre_added_to_workspace_window_bounds_.reset();
persistent_window_info_.reset();
}
}
void WindowState::OnDragStarted(int window_component) {
DCHECK(drag_details_);
if (delegate_)
delegate_->OnDragStarted(window_component);
}
void WindowState::OnCompleteDrag(const gfx::Point& location) {
DCHECK(drag_details_);
if (delegate_)
delegate_->OnDragFinished(/*canceled=*/false, location);
}
void WindowState::OnRevertDrag(const gfx::Point& location) {
DCHECK(drag_details_);
if (delegate_)
delegate_->OnDragFinished(/*canceled=*/true, location);
}
display::Display WindowState::GetDisplay() {
return display::Screen::GetScreen()->GetDisplayNearestWindow(window());
}
void WindowState::CreateDragDetails(const gfx::Point& point_in_parent,
int window_component,
::wm::WindowMoveSource source) {
drag_details_ = std::make_unique<DragDetails>(window_, point_in_parent,
window_component, source);
}
void WindowState::DeleteDragDetails() {
drag_details_.reset();
}
void WindowState::SetAndClearRestoreBounds() {
DCHECK(HasRestoreBounds());
SetBoundsInScreen(GetRestoreBoundsInScreen());
ClearRestoreBounds();
}
WindowState::WindowState(aura::Window* window)
: window_(window),
bounds_changed_by_user_(false),
ignored_by_shelf_(false),
can_consume_system_keys_(false),
unminimize_to_restore_bounds_(false),
hide_shelf_when_fullscreen_(true),
autohide_shelf_when_maximized_or_fullscreen_(false),
cached_always_on_top_(false),
ignore_property_change_(false),
current_state_(new DefaultState(ToWindowStateType(GetShowState()))) {
window_->AddObserver(this);
UpdatePipState(/*was_pip=*/false);
}
bool WindowState::GetAlwaysOnTop() const {
return window_->GetProperty(aura::client::kAlwaysOnTopKey);
}
ui::WindowShowState WindowState::GetShowState() const {
return window_->GetProperty(aura::client::kShowStateKey);
}
ash::mojom::WindowPinType WindowState::GetPinType() const {
return window_->GetProperty(kWindowPinTypeKey);
}
void WindowState::SetBoundsInScreen(const gfx::Rect& bounds_in_screen) {
gfx::Rect bounds_in_parent = bounds_in_screen;
::wm::ConvertRectFromScreen(window_->parent(), &bounds_in_parent);
window_->SetBounds(bounds_in_parent);
}
void WindowState::AdjustSnappedBounds(gfx::Rect* bounds) {
if (is_dragged() || !IsSnapped())
return;
gfx::Rect maximized_bounds =
screen_util::GetMaximizedWindowBoundsInParent(window_);
if (snapped_width_ratio_) {
bounds->set_width(
static_cast<int>(*snapped_width_ratio_ * maximized_bounds.width()));
}
if (GetStateType() == mojom::WindowStateType::LEFT_SNAPPED)
bounds->set_x(maximized_bounds.x());
else if (GetStateType() == mojom::WindowStateType::RIGHT_SNAPPED)
bounds->set_x(maximized_bounds.right() - bounds->width());
bounds->set_y(maximized_bounds.y());
bounds->set_height(maximized_bounds.height());
}
void WindowState::UpdateWindowPropertiesFromStateType() {
ui::WindowShowState new_window_state =
ToWindowShowState(current_state_->GetType());
// Clear |kPreMinimizedShowStateKey| property only when the window is actually
// Unminimized and not in tablet mode.
if (new_window_state != ui::SHOW_STATE_MINIMIZED && IsMinimized() &&
!IsTabletModeEnabled()) {
window()->ClearProperty(aura::client::kPreMinimizedShowStateKey);
}
if (new_window_state != GetShowState()) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(aura::client::kShowStateKey, new_window_state);
}
if (GetStateType() != window_->GetProperty(kWindowStateTypeKey)) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(kWindowStateTypeKey, GetStateType());
}
// sync up current window show state with PinType property.
ash::mojom::WindowPinType pin_type = ash::mojom::WindowPinType::NONE;
if (GetStateType() == mojom::WindowStateType::PINNED)
pin_type = ash::mojom::WindowPinType::PINNED;
else if (GetStateType() == mojom::WindowStateType::TRUSTED_PINNED)
pin_type = ash::mojom::WindowPinType::TRUSTED_PINNED;
if (pin_type != GetPinType()) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(kWindowPinTypeKey, pin_type);
}
}
void WindowState::NotifyPreStateTypeChange(
mojom::WindowStateType old_window_state_type) {
for (auto& observer : observer_list_)
observer.OnPreWindowStateTypeChange(this, old_window_state_type);
UpdatePipState(old_window_state_type == mojom::WindowStateType::PIP);
}
void WindowState::NotifyPostStateTypeChange(
mojom::WindowStateType old_window_state_type) {
for (auto& observer : observer_list_)
observer.OnPostWindowStateTypeChange(this, old_window_state_type);
}
void WindowState::SetBoundsDirect(const gfx::Rect& bounds) {
gfx::Rect actual_new_bounds(bounds);
// Ensure we don't go smaller than our minimum bounds in "normal" window
// modes
if (window_->delegate() && !IsMaximized() && !IsFullscreen()) {
// Get the minimum usable size of the minimum size and the screen size.
gfx::Size min_size = window_->delegate()
? window_->delegate()->GetMinimumSize()
: gfx::Size();
const display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
min_size.SetToMin(display.work_area().size());
actual_new_bounds.set_width(
std::max(min_size.width(), actual_new_bounds.width()));
actual_new_bounds.set_height(
std::max(min_size.height(), actual_new_bounds.height()));
}
BoundsSetter().SetBounds(window_, actual_new_bounds);
::wm::SnapWindowToPixelBoundary(window_);
}
void WindowState::SetBoundsConstrained(const gfx::Rect& bounds) {
gfx::Rect work_area_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(window_);
gfx::Rect child_bounds(bounds);
AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds);
SetBoundsDirect(child_bounds);
}
void WindowState::SetBoundsDirectAnimated(const gfx::Rect& bounds,
base::TimeDelta duration) {
ui::Layer* layer = window_->layer();
ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator());
slide_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
slide_settings.SetTransitionDuration(duration);
SetBoundsDirect(bounds);
}
void WindowState::SetBoundsDirectCrossFade(const gfx::Rect& new_bounds,
gfx::Tween::Type animation_type) {
// Some test results in invoking CrossFadeToBounds when window is not visible.
// No animation is necessary in that case, thus just change the bounds and
// quit.
if (!window_->TargetVisibility()) {
SetBoundsConstrained(new_bounds);
return;
}
// If the window already has a transform in place, do not use the cross fade
// animation, set the bounds directly instead.
if (!window_->layer()->GetTargetTransform().IsIdentity()) {
SetBoundsDirect(new_bounds);
return;
}
// Create fresh layers for the window and all its children to paint into.
// Takes ownership of the old layer and all its children, which will be
// cleaned up after the animation completes.
// Specify |set_bounds| to true here to keep the old bounds in the child
// windows of |window|.
std::unique_ptr<ui::LayerTreeOwner> old_layer_owner =
::wm::RecreateLayers(window_);
// Resize the window to the new size, which will force a layout and paint.
SetBoundsDirect(new_bounds);
CrossFadeAnimation(window_, std::move(old_layer_owner), animation_type);
}
void WindowState::UpdatePipRoundedCorners() {
if (!features::IsPipRoundedCornersEnabled())
return;
auto* layer = window()->layer();
if (!IsPip()) {
// Only remove the mask layer if it is from the existing PIP mask.
if (layer && pip_mask_ && layer->layer_mask_layer() == pip_mask_->layer())
layer->SetMaskLayer(nullptr);
pip_mask_.reset();
return;
}
gfx::Rect bounds = window()->bounds();
if (layer && (!pip_mask_ || pip_mask_->layer()->size() != bounds.size())) {
layer->SetMaskLayer(nullptr);
if (!pip_mask_ || window() != pip_mask_->window())
pip_mask_ = std::make_unique<PipMask>(window());
layer->SetFillsBoundsOpaquely(false);
layer->SetMaskLayer(pip_mask_->layer());
}
}
void WindowState::UpdatePipState(bool was_pip) {
if (IsPip()) {
::wm::SetWindowVisibilityAnimationType(
window(), WINDOW_VISIBILITY_ANIMATION_TYPE_SLIDE_OUT);
} else if (was_pip) {
::wm::SetWindowVisibilityAnimationType(
window(), ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
}
}
void WindowState::UpdatePipBounds() {
gfx::Rect new_bounds =
PipPositioner::GetPositionAfterMovementAreaChange(this);
if (window()->GetBoundsInScreen() != new_bounds) {
wm::SetBoundsEvent event(wm::WM_EVENT_SET_BOUNDS, new_bounds,
/*animate=*/true);
OnWMEvent(&event);
}
}
WindowState* GetActiveWindowState() {
aura::Window* active = GetActiveWindow();
return active ? GetWindowState(active) : nullptr;
}
WindowState* GetWindowState(aura::Window* window) {
if (!window)
return nullptr;
WindowState* settings = window->GetProperty(kWindowStateKey);
if (!settings) {
settings = new WindowState(window);
window->SetProperty(kWindowStateKey, settings);
}
return settings;
}
const WindowState* GetWindowState(const aura::Window* window) {
return GetWindowState(const_cast<aura::Window*>(window));
}
void WindowState::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
DCHECK_EQ(window_, window);
if (key == aura::client::kShowStateKey) {
if (!ignore_property_change_) {
WMEvent event(WMEventTypeFromShowState(GetShowState()));
OnWMEvent(&event);
}
return;
}
if (key == kWindowPinTypeKey) {
if (!ignore_property_change_) {
WMEvent event(WMEventTypeFromWindowPinType(GetPinType()));
OnWMEvent(&event);
}
return;
}
if (key == kWindowPipTypeKey) {
if (window->GetProperty(kWindowPipTypeKey)) {
WMEvent event(WM_EVENT_PIP);
OnWMEvent(&event);
} else {
// Currently "restore" is not implemented.
NOTIMPLEMENTED();
}
return;
}
if (key == kWindowStateTypeKey) {
if (!ignore_property_change_) {
// This change came from somewhere else. Revert it.
window->SetProperty(kWindowStateTypeKey, GetStateType());
}
return;
}
if (key == kHideShelfWhenFullscreenKey || key == kImmersiveIsActive) {
if (!ignore_property_change_) {
// This change came from outside ash. Update our shelf visibility based
// on our changed state.
ash::Shell::Get()->UpdateShelfVisibility();
}
if (key == kImmersiveIsActive) {
if (IsInImmersiveFullscreen()) {
if (!immersive_gesture_drag_handler_) {
immersive_gesture_drag_handler_ =
std::make_unique<ImmersiveGestureDragHandler>(window);
}
} else {
immersive_gesture_drag_handler_.reset();
}
}
return;
}
}
void WindowState::OnWindowAddedToRootWindow(aura::Window* window) {
DCHECK_EQ(window_, window);
if (::wm::GetTransientParent(window))
return;
MoveAllTransientChildrenToNewRoot(window);
}
void WindowState::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(window_, window);
immersive_gesture_drag_handler_.reset();
current_state_->OnWindowDestroying(this);
delegate_.reset();
}
void WindowState::OnWindowLayerRecreated(aura::Window* window) {
DCHECK_EQ(window_, window);
// THe mask layer will be moved with old layer.
DCHECK(!window_->layer()->layer_mask_layer());
pip_mask_.reset();
if (IsPip())
UpdatePipRoundedCorners();
}
} // namespace wm
} // namespace ash