Commit 5595a447 authored by Mitsuru Oshima's avatar Mitsuru Oshima Committed by Commit Bot

Implement CENTER/SNAP for ClientConrolledState

* Move CenterWindow/CycleSnap to BaseState
* Updated CenterWindow to send event instead. This is
  is necessary in order for ClientControlledState to intercept
  bounds change request.
* Added animate parameter to SetBoundsEvent.

Animation on ARC++ windows are not implemented yet. It will
be handled in a separate CL.

BUG=b/30260227
TEST=covered by unittest

Change-Id: I7d3940f82f84331a717013005255e165818a7deb
Reviewed-on: https://chromium-review.googlesource.com/989837
Commit-Queue: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarQiang Xu <warx@google.com>
Cr-Commit-Position: refs/heads/master@{#547627}
parent 7b194a75
......@@ -5,6 +5,7 @@
#include "ash/wm/base_state.h"
#include "ash/public/cpp/window_state_type.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/window_animation_types.h"
......@@ -84,6 +85,58 @@ mojom::WindowStateType BaseState::GetStateForTransitionEvent(
return mojom::WindowStateType::NORMAL;
}
// static
void BaseState::CenterWindow(WindowState* window_state) {
if (!window_state->IsNormalOrSnapped())
return;
aura::Window* window = window_state->window();
if (window_state->IsSnapped()) {
gfx::Rect center_in_screen = display::Screen::GetScreen()
->GetDisplayNearestWindow(window)
.work_area();
gfx::Size size = window_state->HasRestoreBounds()
? window_state->GetRestoreBoundsInScreen().size()
: window->bounds().size();
center_in_screen.ClampToCenteredSize(size);
window_state->SetRestoreBoundsInScreen(center_in_screen);
window_state->Restore();
} else {
gfx::Rect center_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(window);
center_in_parent.ClampToCenteredSize(window->bounds().size());
const wm::SetBoundsEvent event(wm::WM_EVENT_SET_BOUNDS, center_in_parent,
/*animate=*/true);
window_state->OnWMEvent(&event);
}
// Centering window is treated as if a user moved and resized the window.
window_state->set_bounds_changed_by_user(true);
}
// static
void BaseState::CycleSnap(WindowState* window_state, WMEventType event) {
mojom::WindowStateType desired_snap_state =
event == WM_EVENT_CYCLE_SNAP_LEFT ? mojom::WindowStateType::LEFT_SNAPPED
: mojom::WindowStateType::RIGHT_SNAPPED;
if (window_state->CanSnap() &&
window_state->GetStateType() != desired_snap_state &&
window_state->window()->type() != aura::client::WINDOW_TYPE_PANEL) {
const wm::WMEvent event(desired_snap_state ==
mojom::WindowStateType::LEFT_SNAPPED
? wm::WM_EVENT_SNAP_LEFT
: wm::WM_EVENT_SNAP_RIGHT);
window_state->OnWMEvent(&event);
return;
}
if (window_state->IsSnapped()) {
window_state->Restore();
return;
}
::wm::AnimateWindow(window_state->window(),
::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
}
void BaseState::UpdateMinimizedState(
WindowState* window_state,
mojom::WindowStateType previous_state_type) {
......
......@@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_WM_BASE_STATE_H_
#define ASH_WM_BASE_STATE_H_
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "base/macros.h"
namespace aura {
......@@ -27,6 +31,9 @@ class BaseState : public WindowState::State {
static mojom::WindowStateType GetStateForTransitionEvent(
const WMEvent* event);
static void CenterWindow(WindowState* window_state);
static void CycleSnap(WindowState* window_state, WMEventType event);
// Handles workspace related events, such as DISPLAY_BOUNDS_CHANGED.
virtual void HandleWorkspaceEvents(WindowState* window_state,
const WMEvent* event) = 0;
......@@ -62,3 +69,5 @@ class BaseState : public WindowState::State {
} // namespace wm
} // namespace ash
#endif // ASH_WM_BASE_STATE_H_
......@@ -179,7 +179,7 @@ void ClientControlledState::HandleCompoundEvents(WindowState* window_state,
break;
case WM_EVENT_CYCLE_SNAP_LEFT:
case WM_EVENT_CYCLE_SNAP_RIGHT:
// TODO(oshima): implement this.
CycleSnap(window_state, event->type());
break;
default:
NOTREACHED() << "Invalid event :" << event->type();
......@@ -214,7 +214,7 @@ void ClientControlledState::HandleBoundsEvents(WindowState* window_state,
break;
}
case WM_EVENT_CENTER:
// TODO(oshima): implement this.
CenterWindow(window_state);
break;
default:
NOTREACHED() << "Unknown event:" << event->type();
......
......@@ -14,6 +14,7 @@
#include "base/macros.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/window_util.h"
namespace ash {
......@@ -53,7 +54,7 @@ class TestClientControlledStateDelegate
const gfx::Rect& requested_bounds() const { return requested_bounds_; }
void reset() {
void Reset() {
old_state_ = mojom::WindowStateType::DEFAULT;
new_state_ = mojom::WindowStateType::DEFAULT;
requested_bounds_.SetRect(0, 0, 0, 0);
......@@ -70,6 +71,26 @@ class TestClientControlledStateDelegate
DISALLOW_COPY_AND_ASSIGN(TestClientControlledStateDelegate);
};
class TestWidgetDelegate : public views::WidgetDelegateView {
public:
TestWidgetDelegate() = default;
~TestWidgetDelegate() override = default;
// views::WidgetDelegateView:
bool CanResize() const override { return can_snap_; }
bool CanMaximize() const override { return can_snap_; }
void EnableSnap() {
can_snap_ = true;
GetWidget()->OnSizeConstraintsChanged();
}
private:
bool can_snap_ = false;
DISALLOW_COPY_AND_ASSIGN(TestWidgetDelegate);
};
} // namespace
class ClientControlledStateTest : public AshTestBase {
......@@ -80,17 +101,21 @@ class ClientControlledStateTest : public AshTestBase {
void SetUp() override {
AshTestBase::SetUp();
widget_ = std::make_unique<views::Widget>();
widget_delegate_ = new TestWidgetDelegate();
views::Widget::InitParams params;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.parent = Shell::GetPrimaryRootWindow()->GetChildById(
kShellWindowId_DefaultContainer);
params.bounds = kInitialBounds;
params.delegate = widget_delegate_;
widget_ = std::make_unique<views::Widget>();
widget_->Init(params);
wm::WindowState* window_state = wm::GetWindowState(window());
window_state->set_allow_set_bounds_direct(true);
auto delegate = std::make_unique<TestClientControlledStateDelegate>();
delegate_ = delegate.get();
state_delegate_ = delegate.get();
auto state = std::make_unique<ClientControlledState>(std::move(delegate));
state_ = state.get();
window_state->SetStateObject(std::move(state));
......@@ -102,11 +127,13 @@ class ClientControlledStateTest : public AshTestBase {
AshTestBase::TearDown();
}
TestWidgetDelegate* widget_delegate() { return widget_delegate_; }
protected:
aura::Window* window() { return widget_->GetNativeWindow(); }
WindowState* window_state() { return GetWindowState(window()); }
ClientControlledState* state() { return state_; }
TestClientControlledStateDelegate* delegate() { return delegate_; }
TestClientControlledStateDelegate* delegate() { return state_delegate_; }
views::Widget* widget() { return widget_.get(); }
ScreenPinningController* GetScreenPinningController() {
return Shell::Get()->screen_pinning_controller();
......@@ -114,7 +141,8 @@ class ClientControlledStateTest : public AshTestBase {
private:
ClientControlledState* state_ = nullptr;
TestClientControlledStateDelegate* delegate_ = nullptr;
TestClientControlledStateDelegate* state_delegate_ = nullptr;
TestWidgetDelegate* widget_delegate_ = nullptr; // owned by itself.
std::unique_ptr<views::Widget> widget_;
DISALLOW_COPY_AND_ASSIGN(ClientControlledStateTest);
......@@ -267,7 +295,7 @@ TEST_F(ClientControlledStateTest, IgnoreWorkspace) {
state()->EnterNextState(window_state(), delegate()->new_state(),
ClientControlledState::kAnimationNone);
EXPECT_TRUE(widget()->IsMaximized());
delegate()->reset();
delegate()->Reset();
UpdateDisplay("1000x800");
......@@ -290,6 +318,61 @@ TEST_F(ClientControlledStateTest, SetBounds) {
EXPECT_EQ(new_bounds, widget()->GetWindowBoundsInScreen());
}
TEST_F(ClientControlledStateTest, CenterWindow) {
display::Screen* screen = display::Screen::GetScreen();
gfx::Rect bounds = screen->GetPrimaryDisplay().work_area();
const WMEvent center_event(WM_EVENT_CENTER);
window_state()->OnWMEvent(&center_event);
EXPECT_NEAR(bounds.CenterPoint().x(),
delegate()->requested_bounds().CenterPoint().x(), 1);
EXPECT_NEAR(bounds.CenterPoint().y(),
delegate()->requested_bounds().CenterPoint().y(), 1);
}
TEST_F(ClientControlledStateTest, SnapWindow) {
// Snap disabled.
display::Screen* screen = display::Screen::GetScreen();
gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
ASSERT_FALSE(window_state()->CanResize());
ASSERT_FALSE(window_state()->CanSnap());
// The event should be ignored.
const WMEvent snap_left_event(WM_EVENT_CYCLE_SNAP_LEFT);
window_state()->OnWMEvent(&snap_left_event);
EXPECT_FALSE(window_state()->IsSnapped());
EXPECT_TRUE(delegate()->requested_bounds().IsEmpty());
const WMEvent snap_right_event(WM_EVENT_CYCLE_SNAP_RIGHT);
window_state()->OnWMEvent(&snap_right_event);
EXPECT_FALSE(window_state()->IsSnapped());
EXPECT_TRUE(delegate()->requested_bounds().IsEmpty());
// Snap enabled.
widget_delegate()->EnableSnap();
ASSERT_TRUE(window_state()->CanResize());
ASSERT_TRUE(window_state()->CanSnap());
window_state()->OnWMEvent(&snap_left_event);
EXPECT_NEAR(work_area.CenterPoint().x(),
delegate()->requested_bounds().right(), 1);
EXPECT_EQ(work_area.height(), delegate()->requested_bounds().height());
EXPECT_TRUE(delegate()->requested_bounds().origin().IsOrigin());
EXPECT_EQ(mojom::WindowStateType::DEFAULT, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::LEFT_SNAPPED, delegate()->new_state());
delegate()->Reset();
window_state()->OnWMEvent(&snap_right_event);
EXPECT_NEAR(work_area.CenterPoint().x(), delegate()->requested_bounds().x(),
1);
EXPECT_EQ(work_area.height(), delegate()->requested_bounds().height());
EXPECT_EQ(work_area.bottom_right(),
delegate()->requested_bounds().bottom_right());
EXPECT_EQ(mojom::WindowStateType::DEFAULT, delegate()->old_state());
EXPECT_EQ(mojom::WindowStateType::RIGHT_SNAPPED, delegate()->new_state());
}
// Pin events should be applied immediately.
TEST_F(ClientControlledStateTest, Pinned) {
ASSERT_FALSE(window_state()->IsPinned());
......
......@@ -72,30 +72,6 @@ void MoveToDisplayForRestore(WindowState* window_state) {
}
}
void CycleSnap(WindowState* window_state, WMEventType event) {
mojom::WindowStateType desired_snap_state =
event == WM_EVENT_CYCLE_SNAP_LEFT ? mojom::WindowStateType::LEFT_SNAPPED
: mojom::WindowStateType::RIGHT_SNAPPED;
if (window_state->CanSnap() &&
window_state->GetStateType() != desired_snap_state &&
window_state->window()->type() != aura::client::WINDOW_TYPE_PANEL) {
const wm::WMEvent event(desired_snap_state ==
mojom::WindowStateType::LEFT_SNAPPED
? wm::WM_EVENT_SNAP_LEFT
: wm::WM_EVENT_SNAP_RIGHT);
window_state->OnWMEvent(&event);
return;
}
if (window_state->IsSnapped()) {
window_state->Restore();
return;
}
::wm::AnimateWindow(window_state->window(),
::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
}
} // namespace
DefaultState::DefaultState(mojom::WindowStateType initial_state_type)
......@@ -415,33 +391,12 @@ void DefaultState::SetBounds(WindowState* window_state,
// TODO(oshima|varkha): Is this still needed? crbug.com/485612.
window_state->SetBoundsDirect(event->requested_bounds());
} else if (!SetMaximizedOrFullscreenBounds(window_state)) {
window_state->SetBoundsConstrained(event->requested_bounds());
}
}
// static
void DefaultState::CenterWindow(WindowState* window_state) {
if (!window_state->IsNormalOrSnapped())
return;
aura::Window* window = window_state->window();
if (window_state->IsSnapped()) {
gfx::Rect center_in_screen = display::Screen::GetScreen()
->GetDisplayNearestWindow(window)
.work_area();
gfx::Size size = window_state->HasRestoreBounds()
? window_state->GetRestoreBoundsInScreen().size()
: window->bounds().size();
center_in_screen.ClampToCenteredSize(size);
window_state->SetRestoreBoundsInScreen(center_in_screen);
window_state->Restore();
} else {
gfx::Rect center_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(window);
center_in_parent.ClampToCenteredSize(window->bounds().size());
window_state->SetBoundsDirectAnimated(center_in_parent);
if (event->animate()) {
window_state->SetBoundsDirectAnimated(event->requested_bounds());
} else {
window_state->SetBoundsConstrained(event->requested_bounds());
}
}
// Centering window is treated as if a user moved and resized the window.
window_state->set_bounds_changed_by_user(true);
}
void DefaultState::EnterToNextState(WindowState* window_state,
......
......@@ -47,8 +47,6 @@ class DefaultState : public BaseState {
static void SetBounds(WindowState* window_state,
const SetBoundsEvent* bounds_event);
static void CenterWindow(WindowState* window_state);
// Enters next state. This is used when the state moves from one to another
// within the same desktop mode.
void EnterToNextState(wm::WindowState* window_state,
......
......@@ -349,6 +349,7 @@ class ASH_EXPORT WindowState : public aura::WindowObserver {
};
private:
friend class BaseState;
friend class DefaultState;
friend class ash::wm::ClientControlledState;
friend class ash::LockWindowState;
......
......@@ -82,8 +82,10 @@ bool WMEvent::IsTransitionEvent() const {
return false;
}
SetBoundsEvent::SetBoundsEvent(WMEventType type, const gfx::Rect& bounds)
: WMEvent(type), requested_bounds_(bounds) {}
SetBoundsEvent::SetBoundsEvent(WMEventType type,
const gfx::Rect& bounds,
bool animate)
: WMEvent(type), requested_bounds_(bounds), animate_(animate) {}
SetBoundsEvent::~SetBoundsEvent() = default;
......
......@@ -131,13 +131,18 @@ class ASH_EXPORT WMEvent {
// An WMEvent to request new bounds for the window.
class ASH_EXPORT SetBoundsEvent : public WMEvent {
public:
SetBoundsEvent(WMEventType type, const gfx::Rect& requested_bounds);
SetBoundsEvent(WMEventType type,
const gfx::Rect& requested_bounds,
bool animate = false);
~SetBoundsEvent() override;
const gfx::Rect& requested_bounds() const { return requested_bounds_; }
bool animate() const { return animate_; }
private:
gfx::Rect requested_bounds_;
bool animate_;
DISALLOW_COPY_AND_ASSIGN(SetBoundsEvent);
};
......
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