1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ash/wm/dock/docked_window_layout_manager.h" 6 7 #include "ash/ash_switches.h" 8 #include "ash/launcher/launcher.h" 9 #include "ash/launcher/launcher_model.h" 10 #include "ash/root_window_controller.h" 11 #include "ash/screen_ash.h" 12 #include "ash/shelf/shelf_layout_manager.h" 13 #include "ash/shelf/shelf_types.h" 14 #include "ash/shelf/shelf_widget.h" 15 #include "ash/shell.h" 16 #include "ash/shell_window_ids.h" 17 #include "ash/test/ash_test_base.h" 18 #include "ash/test/launcher_view_test_api.h" 19 #include "ash/test/shell_test_api.h" 20 #include "ash/test/test_launcher_delegate.h" 21 #include "ash/wm/panels/panel_layout_manager.h" 22 #include "ash/wm/window_properties.h" 23 #include "ash/wm/window_resizer.h" 24 #include "base/basictypes.h" 25 #include "base/command_line.h" 26 #include "ui/aura/client/aura_constants.h" 27 #include "ui/aura/root_window.h" 28 #include "ui/aura/window.h" 29 #include "ui/base/hit_test.h" 30 #include "ui/views/widget/widget.h" 31 32 namespace ash { 33 namespace internal { 34 35 class DockedWindowLayoutManagerTest 36 : public test::AshTestBase, 37 public testing::WithParamInterface<aura::client::WindowType> { 38 public: 39 DockedWindowLayoutManagerTest() : window_type_(GetParam()) {} 40 virtual ~DockedWindowLayoutManagerTest() {} 41 42 virtual void SetUp() OVERRIDE { 43 CommandLine::ForCurrentProcess()->AppendSwitch( 44 ash::switches::kAshEnableStickyEdges); 45 CommandLine::ForCurrentProcess()->AppendSwitch( 46 ash::switches::kAshEnableDockedWindows); 47 AshTestBase::SetUp(); 48 UpdateDisplay("600x600"); 49 ASSERT_TRUE(test::TestLauncherDelegate::instance()); 50 51 launcher_view_test_.reset(new test::LauncherViewTestAPI( 52 Launcher::ForPrimaryDisplay()->GetLauncherViewForTest())); 53 launcher_view_test_->SetAnimationDuration(1); 54 } 55 56 protected: 57 enum DockedEdge { 58 DOCKED_EDGE_NONE, 59 DOCKED_EDGE_LEFT, 60 DOCKED_EDGE_RIGHT, 61 }; 62 63 enum DockedState { 64 UNDOCKED, 65 DOCKED, 66 }; 67 68 aura::Window* CreateTestWindow(const gfx::Rect& bounds) { 69 aura::Window* window = CreateTestWindowInShellWithDelegateAndType( 70 NULL, 71 window_type_, 72 0, 73 bounds); 74 if (window_type_ == aura::client::WINDOW_TYPE_PANEL) { 75 test::TestLauncherDelegate* launcher_delegate = 76 test::TestLauncherDelegate::instance(); 77 launcher_delegate->AddLauncherItem(window); 78 PanelLayoutManager* manager = 79 static_cast<PanelLayoutManager*>(GetPanelContainer(window)-> 80 layout_manager()); 81 manager->Relayout(); 82 } 83 return window; 84 } 85 86 aura::Window* GetPanelContainer(aura::Window* panel) { 87 return Shell::GetContainer(panel->GetRootWindow(), 88 internal::kShellWindowId_PanelContainer); 89 } 90 91 static WindowResizer* CreateSomeWindowResizer( 92 aura::Window* window, 93 const gfx::Point& point_in_parent, 94 int window_component) { 95 return CreateWindowResizer( 96 window, 97 point_in_parent, 98 window_component, 99 aura::client::WINDOW_MOVE_SOURCE_MOUSE).release(); 100 } 101 102 void DragStart(aura::Window* window) { 103 initial_location_in_parent_ = window->bounds().origin(); 104 resizer_.reset(CreateSomeWindowResizer(window, 105 initial_location_in_parent_, 106 HTCAPTION)); 107 ASSERT_TRUE(resizer_.get()); 108 } 109 110 void DragStartAtOffsetFromwindowOrigin(aura::Window* window, 111 int dx, 112 int dy) { 113 initial_location_in_parent_ = 114 window->bounds().origin() + gfx::Vector2d(dx, dy); 115 resizer_.reset(CreateSomeWindowResizer(window, 116 initial_location_in_parent_, 117 HTCAPTION)); 118 ASSERT_TRUE(resizer_.get()); 119 } 120 121 void DragMove(int dx, int dy) { 122 resizer_->Drag(initial_location_in_parent_ + gfx::Vector2d(dx, dy), 0); 123 } 124 125 void DragEnd() { 126 resizer_->CompleteDrag(0); 127 resizer_.reset(); 128 } 129 130 void DragRevert() { 131 resizer_->RevertDrag(); 132 resizer_.reset(); 133 } 134 135 // Panels are parented by panel container during drags. 136 // Docked windows are parented by dock container during drags. 137 // All other windows that we are testing here have default container as a 138 // parent. 139 int CorrectContainerIdDuringDrag(DockedState is_docked) { 140 if (window_type_ == aura::client::WINDOW_TYPE_PANEL) 141 return internal::kShellWindowId_PanelContainer; 142 if (is_docked == DOCKED) 143 return internal::kShellWindowId_DockedContainer; 144 return internal::kShellWindowId_DefaultContainer; 145 } 146 147 // Test dragging the window vertically (to detach if it is a panel) and then 148 // horizontally to the edge with an added offset from the edge of |dx|. 149 void DragRelativeToEdge(DockedEdge edge, 150 aura::Window* window, 151 int dx) { 152 DragVerticallyAndRelativeToEdge( 153 edge, 154 window, 155 dx, 156 window_type_ == aura::client::WINDOW_TYPE_PANEL ? -100 : 20); 157 } 158 159 void DragToVerticalPositionAndToEdge(DockedEdge edge, 160 aura::Window* window, 161 int y) { 162 DragToVerticalPositionRelativeToEdge(edge, window, 0, y); 163 } 164 165 void DragToVerticalPositionRelativeToEdge(DockedEdge edge, 166 aura::Window* window, 167 int dx, 168 int y) { 169 gfx::Rect initial_bounds = window->GetBoundsInScreen(); 170 DragVerticallyAndRelativeToEdge(edge, window, dx, y - initial_bounds.y()); 171 } 172 173 // Detach if our window is a panel, then drag it vertically by |dy| and 174 // horizontally to the edge with an added offset from the edge of |dx|. 175 void DragVerticallyAndRelativeToEdge(DockedEdge edge, 176 aura::Window* window, 177 int dx, 178 int dy) { 179 aura::RootWindow* root_window = window->GetRootWindow(); 180 gfx::Rect initial_bounds = window->GetBoundsInScreen(); 181 182 if (window_type_ == aura::client::WINDOW_TYPE_PANEL) { 183 ASSERT_NO_FATAL_FAILURE(DragStart(window)); 184 EXPECT_TRUE(window->GetProperty(kPanelAttachedKey)); 185 186 // Drag enough to detach since our tests assume panels to be initially 187 // detached. 188 DragMove(0, dy); 189 EXPECT_EQ(CorrectContainerIdDuringDrag(UNDOCKED), window->parent()->id()); 190 EXPECT_EQ(initial_bounds.x(), window->GetBoundsInScreen().x()); 191 EXPECT_EQ(initial_bounds.y() + dy, window->GetBoundsInScreen().y()); 192 193 // The panel should be detached when the drag completes. 194 DragEnd(); 195 196 EXPECT_FALSE(window->GetProperty(kPanelAttachedKey)); 197 EXPECT_EQ(internal::kShellWindowId_DefaultContainer, 198 window->parent()->id()); 199 EXPECT_EQ(root_window, window->GetRootWindow()); 200 } 201 202 // avoid snap by clicking away from the border 203 ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(window, 25, 5)); 204 205 // Drag the window left or right to the edge (or almost to it). 206 if (edge == DOCKED_EDGE_LEFT) 207 dx += window->GetRootWindow()->bounds().x() - initial_bounds.x(); 208 else if (edge == DOCKED_EDGE_RIGHT) 209 dx += window->GetRootWindow()->bounds().right() - initial_bounds.right(); 210 DragMove(dx, window_type_ == aura::client::WINDOW_TYPE_PANEL ? 0 : dy); 211 EXPECT_EQ(CorrectContainerIdDuringDrag(UNDOCKED), window->parent()->id()); 212 // Release the mouse and the panel should be attached to the dock. 213 DragEnd(); 214 215 // x-coordinate can get adjusted by snapping or sticking. 216 // y-coordinate could be changed by possible automatic layout if docked. 217 if (window->parent()->id() != internal::kShellWindowId_DockedContainer) 218 EXPECT_EQ(initial_bounds.y() + dy, window->GetBoundsInScreen().y()); 219 } 220 221 private: 222 scoped_ptr<WindowResizer> resizer_; 223 scoped_ptr<test::LauncherViewTestAPI> launcher_view_test_; 224 aura::client::WindowType window_type_; 225 226 // Location at start of the drag in |window->parent()|'s coordinates. 227 gfx::Point initial_location_in_parent_; 228 229 DISALLOW_COPY_AND_ASSIGN(DockedWindowLayoutManagerTest); 230 }; 231 232 // Tests that a created window is successfully added to the dock 233 // layout manager. 234 TEST_P(DockedWindowLayoutManagerTest, AddOneWindow) { 235 if (!SupportsHostWindowResize()) 236 return; 237 238 gfx::Rect bounds(0, 0, 201, 201); 239 scoped_ptr<aura::Window> window(CreateTestWindow(bounds)); 240 DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0); 241 242 // The window should be attached and snapped to the right side of the screen. 243 EXPECT_EQ(window->GetRootWindow()->bounds().right(), 244 window->GetBoundsInScreen().right()); 245 EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id()); 246 } 247 248 // Adds two windows and tests that the gaps are evenly distributed. 249 TEST_P(DockedWindowLayoutManagerTest, AddTwoWindows) { 250 if (!SupportsHostWindowResize()) 251 return; 252 253 scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201))); 254 scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202))); 255 DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20); 256 DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300); 257 258 // The windows should be attached and snapped to the right side of the screen. 259 EXPECT_EQ(w1->GetRootWindow()->bounds().right(), 260 w1->GetBoundsInScreen().right()); 261 EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id()); 262 EXPECT_EQ(w2->GetRootWindow()->bounds().right(), 263 w2->GetBoundsInScreen().right()); 264 EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id()); 265 266 // Test that the gaps differ at most by a single pixel. 267 int gap1 = w1->GetBoundsInScreen().y(); 268 int gap2 = w2->GetBoundsInScreen().y() - w1->GetBoundsInScreen().bottom(); 269 int gap3 = ScreenAsh::GetDisplayWorkAreaBoundsInParent(w1.get()).bottom() - 270 w2->GetBoundsInScreen().bottom(); 271 EXPECT_LE(abs(gap1 - gap2), 1); 272 EXPECT_LE(abs(gap2 - gap3), 1); 273 EXPECT_LE(abs(gap3 - gap1), 1); 274 } 275 276 // Adds two non-overlapping windows and tests layout after a drag. 277 TEST_P(DockedWindowLayoutManagerTest, TwoWindowsDragging) { 278 if (!SupportsHostWindowResize()) 279 return; 280 281 scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201))); 282 scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202))); 283 DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20); 284 DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300); 285 286 // The windows should be attached and snapped to the right side of the screen. 287 EXPECT_EQ(w1->GetRootWindow()->bounds().right(), 288 w1->GetBoundsInScreen().right()); 289 EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id()); 290 EXPECT_EQ(w2->GetRootWindow()->bounds().right(), 291 w2->GetBoundsInScreen().right()); 292 EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id()); 293 294 // Drag w2 above w1. 295 ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w2.get(), 0, 20)); 296 DragMove(0, w1->bounds().y() - w2->bounds().y() - 20); 297 DragEnd(); 298 299 // Test the new windows order and that the gaps differ at most by a pixel. 300 int gap1 = w2->GetBoundsInScreen().y(); 301 int gap2 = w1->GetBoundsInScreen().y() - w2->GetBoundsInScreen().bottom(); 302 int gap3 = ScreenAsh::GetDisplayWorkAreaBoundsInParent(w1.get()).bottom() - 303 w1->GetBoundsInScreen().bottom(); 304 EXPECT_LE(abs(gap1 - gap2), 1); 305 EXPECT_LE(abs(gap2 - gap3), 1); 306 EXPECT_LE(abs(gap3 - gap1), 1); 307 } 308 309 // Adds three overlapping windows and tests layout after a drag. 310 TEST_P(DockedWindowLayoutManagerTest, ThreeWindowsDragging) { 311 if (!SupportsHostWindowResize()) 312 return; 313 314 scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201))); 315 DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20); 316 scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202))); 317 DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 100); 318 scoped_ptr<aura::Window> w3(CreateTestWindow(gfx::Rect(0, 0, 220, 204))); 319 DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w3.get(), 300); 320 321 // All windows should be attached and snapped to the right side of the screen. 322 EXPECT_EQ(w1->GetRootWindow()->bounds().right(), 323 w1->GetBoundsInScreen().right()); 324 EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id()); 325 EXPECT_EQ(w2->GetRootWindow()->bounds().right(), 326 w2->GetBoundsInScreen().right()); 327 EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id()); 328 EXPECT_EQ(w3->GetRootWindow()->bounds().right(), 329 w3->GetBoundsInScreen().right()); 330 EXPECT_EQ(internal::kShellWindowId_DockedContainer, w3->parent()->id()); 331 332 // Test that the top and bottom windows are clamped in work area and 333 // that the overlaps between the windows differ at most by a pixel. 334 int overlap1 = w1->GetBoundsInScreen().y(); 335 int overlap2 = w1->GetBoundsInScreen().bottom() - w2->GetBoundsInScreen().y(); 336 int overlap3 = w2->GetBoundsInScreen().bottom() - w3->GetBoundsInScreen().y(); 337 int overlap4 = 338 ScreenAsh::GetDisplayWorkAreaBoundsInParent(w3.get()).bottom() - 339 w3->GetBoundsInScreen().bottom(); 340 EXPECT_EQ(0, overlap1); 341 EXPECT_LE(abs(overlap2 - overlap3), 1); 342 EXPECT_EQ(0, overlap4); 343 344 // Drag w1 below w2. 345 ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w1.get(), 0, 20)); 346 DragMove(0, w2->bounds().y() - w1->bounds().y() + 20); 347 348 // During the drag the windows get rearranged and the top and the bottom 349 // should be clamped by the work area. 350 EXPECT_EQ(0, w2->GetBoundsInScreen().y()); 351 EXPECT_GT(w1->GetBoundsInScreen().y(), w2->GetBoundsInScreen().y()); 352 EXPECT_EQ(ScreenAsh::GetDisplayWorkAreaBoundsInParent(w3.get()).bottom(), 353 w3->GetBoundsInScreen().bottom()); 354 DragEnd(); 355 356 // Test the new windows order and that the overlaps differ at most by a pixel. 357 overlap1 = w2->GetBoundsInScreen().y(); 358 overlap2 = w2->GetBoundsInScreen().bottom() - w1->GetBoundsInScreen().y(); 359 overlap3 = w1->GetBoundsInScreen().bottom() - w3->GetBoundsInScreen().y(); 360 overlap4 = ScreenAsh::GetDisplayWorkAreaBoundsInParent(w3.get()).bottom() - 361 w3->GetBoundsInScreen().bottom(); 362 EXPECT_EQ(0, overlap1); 363 EXPECT_LE(abs(overlap2 - overlap3), 1); 364 EXPECT_EQ(0, overlap4); 365 } 366 367 // Tests run twice - on both panels and normal windows 368 INSTANTIATE_TEST_CASE_P(NormalOrPanel, 369 DockedWindowLayoutManagerTest, 370 testing::Values(aura::client::WINDOW_TYPE_NORMAL, 371 aura::client::WINDOW_TYPE_PANEL)); 372 } // namespace internal 373 } // namespace ash 374