Commit 68084907 authored by Eliot Courtney's avatar Eliot Courtney Committed by Commit Bot

Let ClientControlledState apply animation during bounds change.

This CL introduces a new BoundsChangeAnimationType for
ClientControlledState which will animate the window bounds for a
specified duration.

Bug: b/115291749
Bug: 841886
Test: Tested with local PIP prototype - applies animation.
Change-Id: I8eda8b2131b8d3f679ade5cfbe64d7378efd6881
Reviewed-on: https://chromium-review.googlesource.com/c/1218385
Commit-Queue: Eliot Courtney <edcourtney@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599132}
parent a53cbb1c
......@@ -68,7 +68,8 @@ void ClientControlledState::HandleTransitionEvents(WindowState* window_state,
bool was_pinned = window_state->IsPinned();
bool was_trusted_pinned = window_state->IsTrustedPinned();
EnterNextState(window_state, next_state_type, kAnimationCrossFade);
set_next_bounds_change_animation_type(kAnimationCrossFade);
EnterNextState(window_state, next_state_type);
VLOG(1) << "Processing Pinned Transtion: event=" << event->type()
<< ", state=" << old_state_type << "=>" << next_state_type
......@@ -193,20 +194,25 @@ void ClientControlledState::HandleBoundsEvents(WindowState* window_state,
return;
switch (event->type()) {
case WM_EVENT_SET_BOUNDS: {
const gfx::Rect& bounds =
static_cast<const SetBoundsEvent*>(event)->requested_bounds();
const auto* set_bounds_event = static_cast<const SetBoundsEvent*>(event);
const gfx::Rect& bounds = set_bounds_event->requested_bounds();
if (set_bounds_locally_) {
switch (bounds_change_animation_type_) {
switch (next_bounds_change_animation_type_) {
case kAnimationNone:
window_state->SetBoundsDirect(bounds);
break;
case kAnimationCrossFade:
window_state->SetBoundsDirectCrossFade(bounds);
break;
case kAnimationAnimated:
window_state->SetBoundsDirectAnimated(
bounds, bounds_change_animation_duration_);
break;
}
bounds_change_animation_type_ = kAnimationNone;
next_bounds_change_animation_type_ = kAnimationNone;
} else if (!window_state->IsPinned()) {
// TODO(oshima): Define behavior for pinned app.
bounds_change_animation_duration_ = set_bounds_event->duration();
delegate_->HandleBoundsRequest(window_state,
window_state->GetStateType(), bounds);
}
......@@ -226,13 +232,11 @@ void ClientControlledState::OnWindowDestroying(WindowState* window_state) {
bool ClientControlledState::EnterNextState(
WindowState* window_state,
mojom::WindowStateType next_state_type,
BoundsChangeAnimationType animation_type) {
mojom::WindowStateType next_state_type) {
// Do nothing if we're already in the same state, or delegate has already
// been deleted.
if (state_type_ == next_state_type || !delegate_)
return false;
bounds_change_animation_type_ = animation_type;
mojom::WindowStateType previous_state_type = state_type_;
state_type_ = next_state_type;
......
......@@ -65,8 +65,16 @@ class ASH_EXPORT ClientControlledState : public BaseState {
enum BoundsChangeAnimationType {
kAnimationNone,
kAnimationCrossFade,
kAnimationAnimated,
};
// Sets the type of animation for the next bounds change
// applied locally.
void set_next_bounds_change_animation_type(
BoundsChangeAnimationType animation_type) {
next_bounds_change_animation_type_ = animation_type;
}
// WindowState::State:
void AttachState(WindowState* window_state,
WindowState::State* previous_state) override;
......@@ -86,18 +94,17 @@ class ASH_EXPORT ClientControlledState : public BaseState {
// Enters next state. This is used when the state moves from one to another
// within the same desktop mode. Returns true if the state has changed, or
// false otherwise.
// |animation_type| specifies the type of animation to be applied when
// bounds changes.
bool EnterNextState(wm::WindowState* window_state,
mojom::WindowStateType next_state_type,
BoundsChangeAnimationType animation_type);
mojom::WindowStateType next_state_type);
private:
std::unique_ptr<Delegate> delegate_;
bool set_bounds_locally_ = false;
base::TimeDelta bounds_change_animation_duration_ =
WindowState::kBoundsChangeSlideDuration;
BoundsChangeAnimationType bounds_change_animation_type_ = kAnimationNone;
BoundsChangeAnimationType next_bounds_change_animation_type_ = kAnimationNone;
DISALLOW_COPY_AND_ASSIGN(ClientControlledState);
};
......
......@@ -160,8 +160,7 @@ TEST_F(ClientControlledStateTest, Maximize) {
EXPECT_EQ(mojom::WindowStateType::DEFAULT, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::MAXIMIZED, delegate()->new_state());
// Now enters the new state.
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsMaximized());
// Bounds is controlled by client.
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -176,8 +175,7 @@ TEST_F(ClientControlledStateTest, Maximize) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::MAXIMIZED, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::NORMAL, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_FALSE(widget()->IsMaximized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
}
......@@ -188,8 +186,7 @@ TEST_F(ClientControlledStateTest, Minimize) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::DEFAULT, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::MINIMIZED, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsMinimized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -198,8 +195,7 @@ TEST_F(ClientControlledStateTest, Minimize) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::MINIMIZED, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::NORMAL, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_FALSE(widget()->IsMinimized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -209,8 +205,7 @@ TEST_F(ClientControlledStateTest, Minimize) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::NORMAL, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::MINIMIZED, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsMinimized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -222,8 +217,7 @@ TEST_F(ClientControlledStateTest, Minimize) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::MINIMIZED, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::NORMAL, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_FALSE(widget()->IsMinimized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
}
......@@ -234,8 +228,7 @@ TEST_F(ClientControlledStateTest, Fullscreen) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::DEFAULT, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::FULLSCREEN, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsFullscreen());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -243,8 +236,7 @@ TEST_F(ClientControlledStateTest, Fullscreen) {
EXPECT_TRUE(widget()->IsFullscreen());
EXPECT_EQ(mojom::WindowStateType::FULLSCREEN, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::NORMAL, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_FALSE(widget()->IsFullscreen());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
}
......@@ -257,8 +249,7 @@ TEST_F(ClientControlledStateTest, MaximizeToFullscreen) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::DEFAULT, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::MAXIMIZED, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsMaximized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -267,8 +258,7 @@ TEST_F(ClientControlledStateTest, MaximizeToFullscreen) {
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
EXPECT_EQ(mojom::WindowStateType::MAXIMIZED, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::FULLSCREEN, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsFullscreen());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -276,8 +266,7 @@ TEST_F(ClientControlledStateTest, MaximizeToFullscreen) {
EXPECT_TRUE(widget()->IsFullscreen());
EXPECT_EQ(mojom::WindowStateType::FULLSCREEN, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::MAXIMIZED, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsMaximized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
......@@ -285,16 +274,14 @@ TEST_F(ClientControlledStateTest, MaximizeToFullscreen) {
EXPECT_TRUE(widget()->IsMaximized());
EXPECT_EQ(mojom::WindowStateType::MAXIMIZED, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::NORMAL, delegate()->new_state());
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_FALSE(widget()->IsMaximized());
EXPECT_EQ(kInitialBounds, widget()->GetWindowBoundsInScreen());
}
TEST_F(ClientControlledStateTest, IgnoreWorkspace) {
widget()->Maximize();
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
state()->EnterNextState(window_state(), delegate()->new_state());
EXPECT_TRUE(widget()->IsMaximized());
delegate()->Reset();
......
......@@ -392,7 +392,8 @@ void DefaultState::SetBounds(WindowState* window_state,
window_state->SetBoundsDirect(event->requested_bounds());
} else if (!SetMaximizedOrFullscreenBounds(window_state)) {
if (event->animate()) {
window_state->SetBoundsDirectAnimated(event->requested_bounds());
window_state->SetBoundsDirectAnimated(event->requested_bounds(),
event->duration());
} else {
window_state->SetBoundsConstrained(event->requested_bounds());
}
......
......@@ -146,6 +146,8 @@ void MoveAllTransientChildrenToNewRoot(aura::Window* window) {
} // namespace
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
......@@ -621,15 +623,13 @@ void WindowState::SetBoundsConstrained(const gfx::Rect& bounds) {
SetBoundsDirect(child_bounds);
}
void WindowState::SetBoundsDirectAnimated(const gfx::Rect& bounds) {
const int kBoundsChangeSlideDurationMs = 120;
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(
base::TimeDelta::FromMilliseconds(kBoundsChangeSlideDurationMs));
slide_settings.SetTransitionDuration(duration);
SetBoundsDirect(bounds);
}
......
......@@ -61,6 +61,10 @@ ASH_EXPORT const WindowState* GetWindowState(const aura::Window* window);
// accessing the window using |window()| is cheap.
class ASH_EXPORT WindowState : public aura::WindowObserver {
public:
// The default duration for an animation between two sets of bounds.
static constexpr base::TimeDelta kBoundsChangeSlideDuration =
base::TimeDelta::FromMilliseconds(120);
// A subclass of State class represents one of the window's states
// that corresponds to WindowStateType in Ash environment, e.g.
// maximized, minimized or side snapped, as subclass.
......@@ -391,8 +395,10 @@ class ASH_EXPORT WindowState : public aura::WindowObserver {
void SetBoundsConstrained(const gfx::Rect& bounds);
// Sets the wndow's |bounds| and transitions to the new bounds with
// a scale animation.
void SetBoundsDirectAnimated(const gfx::Rect& bounds);
// a scale animation, with duration specified by |duration|.
void SetBoundsDirectAnimated(
const gfx::Rect& bounds,
base::TimeDelta duration = kBoundsChangeSlideDuration);
// Sets the window's |bounds| and transition to the new bounds with
// a cross fade animation.
......
......@@ -84,8 +84,12 @@ bool WMEvent::IsTransitionEvent() const {
SetBoundsEvent::SetBoundsEvent(WMEventType type,
const gfx::Rect& bounds,
bool animate)
: WMEvent(type), requested_bounds_(bounds), animate_(animate) {}
bool animate,
base::TimeDelta duration)
: WMEvent(type),
requested_bounds_(bounds),
animate_(animate),
duration_(duration) {}
SetBoundsEvent::~SetBoundsEvent() = default;
......
......@@ -6,7 +6,9 @@
#define ASH_WM_WM_EVENT_H_
#include "ash/ash_export.h"
#include "ash/wm/window_state.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "ui/gfx/geometry/rect.h"
namespace ash {
......@@ -131,18 +133,23 @@ class ASH_EXPORT WMEvent {
// An WMEvent to request new bounds for the window.
class ASH_EXPORT SetBoundsEvent : public WMEvent {
public:
SetBoundsEvent(WMEventType type,
SetBoundsEvent(
WMEventType type,
const gfx::Rect& requested_bounds,
bool animate = false);
bool animate = false,
base::TimeDelta duration = WindowState::kBoundsChangeSlideDuration);
~SetBoundsEvent() override;
const gfx::Rect& requested_bounds() const { return requested_bounds_; }
bool animate() const { return animate_; }
base::TimeDelta duration() const { return duration_; }
private:
gfx::Rect requested_bounds_;
bool animate_;
base::TimeDelta duration_;
DISALLOW_COPY_AND_ASSIGN(SetBoundsEvent);
};
......
......@@ -881,8 +881,15 @@ bool ClientControlledShellSurface::OnPreWidgetCommit() {
}
ash::wm::WindowState* window_state = GetWindowState();
if (window_state->GetStateType() == pending_window_state_)
if (window_state->GetStateType() == pending_window_state_) {
// Animate PIP window movement unless it is being dragged.
if (window_state->IsPip() && !window_state->is_dragged()) {
client_controlled_state_->set_next_bounds_change_animation_type(
ash::wm::ClientControlledState::kAnimationAnimated);
}
return true;
}
if (IsPinned(window_state)) {
VLOG(1) << "State change was requested while pinned";
......@@ -920,8 +927,12 @@ bool ClientControlledShellSurface::OnPreWidgetCommit() {
widget_->widget_delegate()->set_can_activate(true);
}
client_controlled_state_->EnterNextState(window_state, pending_window_state_,
if (client_controlled_state_->EnterNextState(window_state,
pending_window_state_)) {
client_controlled_state_->set_next_bounds_change_animation_type(
animation_type);
}
return true;
}
......
......@@ -47,6 +47,8 @@
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_targeter.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor_extra/shadow.h"
#include "ui/display/display.h"
#include "ui/display/test/display_manager_test_api.h"
......@@ -1885,4 +1887,51 @@ TEST_F(ClientControlledShellSurfaceDisplayTest,
ASSERT_EQ(1, bounds_change_count());
}
TEST_F(ClientControlledShellSurfaceTest, SetPipWindowBoundsAnimates) {
const gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface());
auto shell_surface =
exo_test_helper()->CreateClientControlledShellSurface(surface.get());
surface->Attach(buffer.get());
surface->Commit();
shell_surface->SetPip();
surface->Commit();
shell_surface->GetWidget()->Show();
ui::ScopedAnimationDurationScaleMode animation_scale_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
window->SetBounds(gfx::Rect(10, 10, 10, 10));
EXPECT_EQ(gfx::Rect(10, 10, 10, 10), window->layer()->GetTargetBounds());
EXPECT_EQ(gfx::Rect(0, 0, 256, 256), window->layer()->bounds());
}
TEST_F(ClientControlledShellSurfaceTest, PipWindowDragDoesNotAnimate) {
const gfx::Size buffer_size(256, 256);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
std::unique_ptr<Surface> surface(new Surface());
auto shell_surface =
exo_test_helper()->CreateClientControlledShellSurface(surface.get());
surface->Attach(buffer.get());
surface->Commit();
shell_surface->SetPip();
surface->Commit();
shell_surface->GetWidget()->Show();
aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
ui::ScopedAnimationDurationScaleMode animation_scale_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
std::unique_ptr<ash::WindowResizer> resizer(ash::CreateWindowResizer(
window, gfx::Point(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_MOUSE));
resizer->Drag(gfx::Point(10, 0), 0);
EXPECT_EQ(gfx::Rect(10, 0, 256, 256), window->layer()->GetTargetBounds());
EXPECT_EQ(gfx::Rect(10, 0, 256, 256), window->layer()->bounds());
resizer->CompleteDrag();
}
} // namespace exo
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment