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/root_window_controller.h"
10#include "ash/screen_ash.h"
11#include "ash/shelf/shelf_layout_manager.h"
12#include "ash/shelf/shelf_model.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_test_api.h"
19#include "ash/test/shelf_view_test_api.h"
20#include "ash/test/shell_test_api.h"
21#include "ash/test/test_shelf_delegate.h"
22#include "ash/wm/coordinate_conversion.h"
23#include "ash/wm/panels/panel_layout_manager.h"
24#include "ash/wm/window_resizer.h"
25#include "ash/wm/window_state.h"
26#include "ash/wm/window_util.h"
27#include "base/basictypes.h"
28#include "base/command_line.h"
29#include "base/strings/string_number_conversions.h"
30#include "ui/aura/client/aura_constants.h"
31#include "ui/aura/root_window.h"
32#include "ui/aura/test/test_window_delegate.h"
33#include "ui/aura/window.h"
34#include "ui/base/hit_test.h"
35#include "ui/gfx/screen.h"
36#include "ui/views/widget/widget.h"
37
38namespace ash {
39namespace internal {
40
41class DockedWindowLayoutManagerTest
42    : public test::AshTestBase,
43      public testing::WithParamInterface<aura::client::WindowType> {
44 public:
45  DockedWindowLayoutManagerTest() : window_type_(GetParam()) {}
46  virtual ~DockedWindowLayoutManagerTest() {}
47
48  virtual void SetUp() OVERRIDE {
49    CommandLine::ForCurrentProcess()->AppendSwitch(
50        ash::switches::kAshEnableDockedWindows);
51    AshTestBase::SetUp();
52    UpdateDisplay("600x600");
53    ASSERT_TRUE(test::TestShelfDelegate::instance());
54
55    shelf_view_test_.reset(new test::ShelfViewTestAPI(
56        test::LauncherTestAPI(Launcher::ForPrimaryDisplay()).shelf_view()));
57    shelf_view_test_->SetAnimationDuration(1);
58  }
59
60 protected:
61  enum DockedEdge {
62    DOCKED_EDGE_NONE,
63    DOCKED_EDGE_LEFT,
64    DOCKED_EDGE_RIGHT,
65  };
66
67  int min_dock_gap() const { return DockedWindowLayoutManager::kMinDockGap; }
68  int ideal_width() const { return DockedWindowLayoutManager::kIdealWidth; }
69  int docked_width(const DockedWindowLayoutManager* layout_manager) const {
70    return layout_manager->docked_width_;
71  }
72
73  aura::Window* CreateTestWindow(const gfx::Rect& bounds) {
74    aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
75        NULL, window_type_, 0, bounds);
76    if (window_type_ == aura::client::WINDOW_TYPE_PANEL) {
77      test::TestShelfDelegate* shelf_delegate =
78          test::TestShelfDelegate::instance();
79      shelf_delegate->AddLauncherItem(window);
80      PanelLayoutManager* manager =
81          static_cast<PanelLayoutManager*>(GetPanelContainer(window)->
82              layout_manager());
83      manager->Relayout();
84    }
85    return window;
86  }
87
88  aura::Window* CreateTestWindowWithDelegate(
89      const gfx::Rect& bounds,
90      aura::test::TestWindowDelegate* delegate) {
91    aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
92        delegate, window_type_, 0, bounds);
93    if (window_type_ == aura::client::WINDOW_TYPE_PANEL) {
94      test::TestShelfDelegate* shelf_delegate =
95          test::TestShelfDelegate::instance();
96      shelf_delegate->AddLauncherItem(window);
97      PanelLayoutManager* manager =
98          static_cast<PanelLayoutManager*>(GetPanelContainer(window)->
99              layout_manager());
100      manager->Relayout();
101    }
102    return window;
103  }
104
105  aura::Window* GetPanelContainer(aura::Window* panel) {
106    return Shell::GetContainer(panel->GetRootWindow(),
107                               internal::kShellWindowId_PanelContainer);
108  }
109
110  static WindowResizer* CreateSomeWindowResizer(
111      aura::Window* window,
112      const gfx::Point& point_in_parent,
113      int window_component) {
114    return CreateWindowResizer(
115        window,
116        point_in_parent,
117        window_component,
118        aura::client::WINDOW_MOVE_SOURCE_MOUSE).release();
119  }
120
121  void DragStart(aura::Window* window) {
122    DragStartAtOffsetFromwindowOrigin(window, 0, 0);
123  }
124
125  void DragStartAtOffsetFromwindowOrigin(aura::Window* window,
126                                         int dx, int dy) {
127    initial_location_in_parent_ =
128        window->bounds().origin() + gfx::Vector2d(dx, dy);
129    resizer_.reset(CreateSomeWindowResizer(window,
130                                           initial_location_in_parent_,
131                                           HTCAPTION));
132    ASSERT_TRUE(resizer_.get());
133  }
134
135  void DragMove(int dx, int dy) {
136    resizer_->Drag(initial_location_in_parent_ + gfx::Vector2d(dx, dy), 0);
137  }
138
139  void DragEnd() {
140    resizer_->CompleteDrag(0);
141    resizer_.reset();
142  }
143
144  void DragRevert() {
145    resizer_->RevertDrag();
146    resizer_.reset();
147  }
148
149  // Panels are parented by panel container during drags.
150  // Docked windows are parented by dock container during drags.
151  // All other windows that we are testing here have default container as a
152  // parent.
153  int CorrectContainerIdDuringDrag() {
154    if (window_type_ == aura::client::WINDOW_TYPE_PANEL)
155      return internal::kShellWindowId_PanelContainer;
156    return internal::kShellWindowId_DockedContainer;
157  }
158
159  // Test dragging the window vertically (to detach if it is a panel) and then
160  // horizontally to the edge with an added offset from the edge of |dx|.
161  void DragRelativeToEdge(DockedEdge edge,
162                          aura::Window* window,
163                          int dx) {
164    DragVerticallyAndRelativeToEdge(
165        edge,
166        window,
167        dx,
168        window_type_ == aura::client::WINDOW_TYPE_PANEL ? -100 : 20);
169  }
170
171  void DragToVerticalPositionAndToEdge(DockedEdge edge,
172                                       aura::Window* window,
173                                       int y) {
174    DragToVerticalPositionRelativeToEdge(edge, window, 0, y);
175  }
176
177  void DragToVerticalPositionRelativeToEdge(DockedEdge edge,
178                                            aura::Window* window,
179                                            int dx,
180                                            int y) {
181    gfx::Rect initial_bounds = window->GetBoundsInScreen();
182    DragVerticallyAndRelativeToEdge(edge, window, dx, y - initial_bounds.y());
183  }
184
185  // Detach if our window is a panel, then drag it vertically by |dy| and
186  // horizontally to the edge with an added offset from the edge of |dx|.
187  void DragVerticallyAndRelativeToEdge(DockedEdge edge,
188                                       aura::Window* window,
189                                       int dx, int dy) {
190    gfx::Rect initial_bounds = window->GetBoundsInScreen();
191    // avoid snap by clicking away from the border
192    ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(window, 25, 5));
193
194    gfx::Rect work_area =
195        Shell::GetScreen()->GetDisplayNearestWindow(window).work_area();
196    gfx::Point initial_location_in_screen = initial_location_in_parent_;
197    wm::ConvertPointToScreen(window->parent(), &initial_location_in_screen);
198    // Drag the window left or right to the edge (or almost to it).
199    if (edge == DOCKED_EDGE_LEFT)
200      dx += work_area.x() - initial_location_in_screen.x();
201    else if (edge == DOCKED_EDGE_RIGHT)
202      dx += work_area.right() - 1 - initial_location_in_screen.x();
203    DragMove(dx, dy);
204    EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
205    // Release the mouse and the panel should be attached to the dock.
206    DragEnd();
207
208    // x-coordinate can get adjusted by snapping or sticking.
209    // y-coordinate could be changed by possible automatic layout if docked.
210    if (window->parent()->id() != internal::kShellWindowId_DockedContainer &&
211        !wm::GetWindowState(window)->HasRestoreBounds()) {
212      EXPECT_EQ(initial_bounds.y() + dy, window->GetBoundsInScreen().y());
213    }
214  }
215
216 private:
217  scoped_ptr<WindowResizer> resizer_;
218  scoped_ptr<test::ShelfViewTestAPI> shelf_view_test_;
219  aura::client::WindowType window_type_;
220
221  // Location at start of the drag in |window->parent()|'s coordinates.
222  gfx::Point initial_location_in_parent_;
223
224  DISALLOW_COPY_AND_ASSIGN(DockedWindowLayoutManagerTest);
225};
226
227// Tests that a created window is successfully added to the dock
228// layout manager.
229TEST_P(DockedWindowLayoutManagerTest, AddOneWindow) {
230  if (!SupportsHostWindowResize())
231    return;
232
233  gfx::Rect bounds(0, 0, 201, 201);
234  scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
235  DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
236
237  // The window should be attached and docked at the right edge.
238  // Its width should shrink or grow to ideal width.
239  EXPECT_EQ(window->GetRootWindow()->bounds().right(),
240            window->GetBoundsInScreen().right());
241  EXPECT_EQ(ideal_width(), window->bounds().width());
242  EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
243}
244
245// Tests that with a window docked on the left the auto-placing logic in
246// RearrangeVisibleWindowOnShow places windows flush with work area edges.
247TEST_P(DockedWindowLayoutManagerTest, AutoPlacingLeft) {
248  if (!SupportsHostWindowResize())
249    return;
250
251  gfx::Rect bounds(0, 0, 201, 201);
252  scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
253  DragRelativeToEdge(DOCKED_EDGE_LEFT, window.get(), 0);
254
255  // The window should be attached and snapped to the right side of the screen.
256  EXPECT_EQ(window->GetRootWindow()->bounds().x(),
257            window->GetBoundsInScreen().x());
258  EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
259
260  DockedWindowLayoutManager* manager = static_cast<DockedWindowLayoutManager*>(
261      window->parent()->layout_manager());
262
263  // Create two additional windows and test their auto-placement
264  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
265  gfx::Rect desktop_area = window1->parent()->bounds();
266  wm::GetWindowState(window1.get())->set_window_position_managed(true);
267  window1->Hide();
268  window1->SetBounds(gfx::Rect(250, 32, 231, 320));
269  window1->Show();
270  // |window1| should be centered in work area.
271  EXPECT_EQ(base::IntToString(
272      docked_width(manager) + min_dock_gap() +
273      (desktop_area.width() - docked_width(manager) -
274       min_dock_gap() - window1->bounds().width()) / 2) +
275      ",32 231x320", window1->bounds().ToString());
276
277  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2));
278  wm::GetWindowState(window2.get())->set_window_position_managed(true);
279  // To avoid any auto window manager changes due to SetBounds, the window
280  // gets first hidden and then shown again.
281  window2->Hide();
282  window2->SetBounds(gfx::Rect(250, 48, 150, 300));
283  window2->Show();
284
285  // |window1| should be flush left and |window2| flush right.
286  EXPECT_EQ(
287      base::IntToString(docked_width(manager) + min_dock_gap()) +
288      ",32 231x320", window1->bounds().ToString());
289  EXPECT_EQ(
290      base::IntToString(
291          desktop_area.width() - window2->bounds().width()) +
292      ",48 150x300", window2->bounds().ToString());
293}
294
295// Tests that with a window docked on the right the auto-placing logic in
296// RearrangeVisibleWindowOnShow places windows flush with work area edges.
297TEST_P(DockedWindowLayoutManagerTest, AutoPlacingRight) {
298  if (!SupportsHostWindowResize())
299    return;
300
301  gfx::Rect bounds(0, 0, 201, 201);
302  scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
303  DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
304
305  // The window should be attached and snapped to the right side of the screen.
306  EXPECT_EQ(window->GetRootWindow()->bounds().right(),
307            window->GetBoundsInScreen().right());
308  EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
309
310  DockedWindowLayoutManager* manager = static_cast<DockedWindowLayoutManager*>(
311      window->parent()->layout_manager());
312
313  // Create two additional windows and test their auto-placement
314  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
315  gfx::Rect desktop_area = window1->parent()->bounds();
316  wm::GetWindowState(window1.get())->set_window_position_managed(true);
317  window1->Hide();
318  window1->SetBounds(gfx::Rect(16, 32, 231, 320));
319  window1->Show();
320
321  // |window1| should be centered in work area.
322  EXPECT_EQ(base::IntToString(
323      (desktop_area.width() - docked_width(manager) -
324       min_dock_gap() - window1->bounds().width()) / 2) +
325      ",32 231x320", window1->bounds().ToString());
326
327  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2));
328  wm::GetWindowState(window2.get())->set_window_position_managed(true);
329  // To avoid any auto window manager changes due to SetBounds, the window
330  // gets first hidden and then shown again.
331  window2->Hide();
332  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
333  window2->Show();
334
335  // |window1| should be flush left and |window2| flush right.
336  EXPECT_EQ("0,32 231x320", window1->bounds().ToString());
337  EXPECT_EQ(
338      base::IntToString(
339          desktop_area.width() - window2->bounds().width() -
340          docked_width(manager) - min_dock_gap()) +
341      ",48 256x512", window2->bounds().ToString());
342}
343
344// Tests that with a window docked on the right the auto-placing logic in
345// RearrangeVisibleWindowOnShow places windows flush with work area edges.
346// Test case for the secondary screen.
347TEST_P(DockedWindowLayoutManagerTest, AutoPlacingRightSecondScreen) {
348  if (!SupportsMultipleDisplays() || !SupportsHostWindowResize())
349    return;
350
351  // Create two screen layout.
352  UpdateDisplay("600x600,600x600");
353
354  gfx::Rect bounds(600, 0, 201, 201);
355  scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
356  // Drag pointer to the right edge of the second screen.
357  DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
358
359  // The window should be attached and snapped to the right side of the screen.
360  EXPECT_EQ(window->GetRootWindow()->GetBoundsInScreen().right(),
361            window->GetBoundsInScreen().right());
362  EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
363
364  DockedWindowLayoutManager* manager = static_cast<DockedWindowLayoutManager*>(
365      window->parent()->layout_manager());
366
367  // Create two additional windows and test their auto-placement
368  bounds = gfx::Rect(616, 32, 231, 320);
369  scoped_ptr<aura::Window> window1(
370      CreateTestWindowInShellWithDelegate(NULL, 1, bounds));
371  gfx::Rect desktop_area = window1->parent()->bounds();
372  wm::GetWindowState(window1.get())->set_window_position_managed(true);
373  window1->Hide();
374  window1->Show();
375
376  // |window1| should be centered in work area.
377  EXPECT_EQ(base::IntToString(
378      600 + (desktop_area.width() - docked_width(manager) -
379          min_dock_gap() - window1->bounds().width()) / 2) +
380      ",32 231x320", window1->GetBoundsInScreen().ToString());
381
382  bounds = gfx::Rect(632, 48, 256, 512);
383  scoped_ptr<aura::Window> window2(
384      CreateTestWindowInShellWithDelegate(NULL, 2, bounds));
385  wm::GetWindowState(window2.get())->set_window_position_managed(true);
386  // To avoid any auto window manager changes due to SetBounds, the window
387  // gets first hidden and then shown again.
388  window2->Hide();
389  window2->Show();
390
391  // |window1| should be flush left and |window2| flush right.
392  EXPECT_EQ("600,32 231x320", window1->GetBoundsInScreen().ToString());
393  EXPECT_EQ(
394      base::IntToString(
395          600 + desktop_area.width() - window2->bounds().width() -
396          docked_width(manager) - min_dock_gap()) +
397      ",48 256x512", window2->GetBoundsInScreen().ToString());
398}
399
400// Adds two windows and tests that the gaps are evenly distributed.
401TEST_P(DockedWindowLayoutManagerTest, AddTwoWindows) {
402  if (!SupportsHostWindowResize())
403    return;
404
405  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
406  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202)));
407  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
408  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
409
410  // The windows should be attached and snapped to the right side of the screen.
411  EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
412            w1->GetBoundsInScreen().right());
413  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
414  EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
415            w2->GetBoundsInScreen().right());
416  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
417
418  // Test that the gaps differ at most by a single pixel.
419  gfx::Rect work_area =
420      Shell::GetScreen()->GetDisplayNearestWindow(w1.get()).work_area();
421  int gap1 = w1->GetBoundsInScreen().y();
422  int gap2 = w2->GetBoundsInScreen().y() - w1->GetBoundsInScreen().bottom();
423  int gap3 = work_area.bottom() - w2->GetBoundsInScreen().bottom();
424  EXPECT_NEAR(gap1, min_dock_gap(), 1);
425  EXPECT_NEAR(gap2, min_dock_gap(), 1);
426  EXPECT_NEAR(gap3, min_dock_gap(), 1);
427}
428
429// Adds two non-overlapping windows and tests layout after a drag.
430TEST_P(DockedWindowLayoutManagerTest, TwoWindowsDragging) {
431  if (!SupportsHostWindowResize())
432    return;
433
434  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
435  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202)));
436  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
437  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
438
439  // The windows should be attached and snapped to the right side of the screen.
440  EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
441            w1->GetBoundsInScreen().right());
442  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
443  EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
444            w2->GetBoundsInScreen().right());
445  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
446
447  // Drag w2 above w1.
448  ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w2.get(), 0, 20));
449  DragMove(0, -w2->bounds().height() / 2 - min_dock_gap() - 1);
450  DragEnd();
451
452  // Test the new windows order and that the gaps differ at most by a pixel.
453  gfx::Rect work_area =
454      Shell::GetScreen()->GetDisplayNearestWindow(w1.get()).work_area();
455  int gap1 = w2->GetBoundsInScreen().y() - work_area.y();
456  int gap2 = w1->GetBoundsInScreen().y() - w2->GetBoundsInScreen().bottom();
457  int gap3 = work_area.bottom() - w1->GetBoundsInScreen().bottom();
458  EXPECT_NEAR(gap1, min_dock_gap(), 1);
459  EXPECT_NEAR(gap2, min_dock_gap(), 1);
460  EXPECT_NEAR(gap3, min_dock_gap(), 1);
461}
462
463// Adds three overlapping windows and tests layout after a drag.
464TEST_P(DockedWindowLayoutManagerTest, ThreeWindowsDragging) {
465  if (!SupportsHostWindowResize())
466    return;
467  UpdateDisplay("600x1000");
468
469  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 310)));
470  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
471  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 310)));
472  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 500);
473  scoped_ptr<aura::Window> w3(CreateTestWindow(gfx::Rect(0, 0, 220, 310)));
474  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w3.get(), 600);
475
476  // All windows should be attached and snapped to the right side of the screen.
477  EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
478            w1->GetBoundsInScreen().right());
479  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
480  EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
481            w2->GetBoundsInScreen().right());
482  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
483  EXPECT_EQ(w3->GetRootWindow()->bounds().right(),
484            w3->GetBoundsInScreen().right());
485  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w3->parent()->id());
486
487  // Test that the top and bottom windows are clamped in work area and
488  // that the gaps between the windows differ at most by a pixel.
489  gfx::Rect work_area =
490      Shell::GetScreen()->GetDisplayNearestWindow(w1.get()).work_area();
491  int gap1 = w1->GetBoundsInScreen().y() - work_area.y();
492  int gap2 = w2->GetBoundsInScreen().y() - w1->GetBoundsInScreen().bottom();
493  int gap3 = w3->GetBoundsInScreen().y() - w2->GetBoundsInScreen().bottom();
494  int gap4 = work_area.bottom() - w3->GetBoundsInScreen().bottom();
495  EXPECT_NEAR(gap1, min_dock_gap(), 1);
496  EXPECT_NEAR(gap2, min_dock_gap(), 1);
497  EXPECT_NEAR(gap3, min_dock_gap(), 1);
498  EXPECT_NEAR(gap4, min_dock_gap(), 1);
499
500  // Drag w1 below the point where w1 and w2 would swap places. This point is
501  // half way between the tops of those two windows.
502  // A bit more vertical drag is needed to account for a window bounds changing
503  // to its restore bounds during the drag.
504  ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w1.get(), 0, 20));
505  DragMove(0, min_dock_gap() + w2->bounds().height() / 2 + 10);
506
507  // During the drag the windows get rearranged and the top and the bottom
508  // should be limited by the work area + |kMinDockGap|.
509  EXPECT_EQ(min_dock_gap(), w2->GetBoundsInScreen().y());
510  EXPECT_GT(w1->GetBoundsInScreen().y(), w2->GetBoundsInScreen().y());
511  EXPECT_EQ(min_dock_gap(),
512            work_area.bottom() - w3->GetBoundsInScreen().bottom());
513  DragEnd();
514
515  // Test the new windows order and that the gaps differ at most by a pixel.
516  gap1 = w2->GetBoundsInScreen().y() - work_area.y();
517  gap2 = w1->GetBoundsInScreen().y() - w2->GetBoundsInScreen().bottom();
518  gap3 = w3->GetBoundsInScreen().y() - w1->GetBoundsInScreen().bottom();
519  gap4 = work_area.bottom() - w3->GetBoundsInScreen().bottom();
520  EXPECT_NEAR(gap1, min_dock_gap(), 1);
521  EXPECT_NEAR(gap2, min_dock_gap(), 1);
522  EXPECT_NEAR(gap3, min_dock_gap(), 1);
523  EXPECT_NEAR(gap4, min_dock_gap(), 1);
524}
525
526// Adds three windows in bottom display and tests layout after a drag.
527TEST_P(DockedWindowLayoutManagerTest, ThreeWindowsDraggingSecondScreen) {
528  if (!SupportsMultipleDisplays() || !SupportsHostWindowResize())
529    return;
530
531  // Create two screen vertical layout.
532  UpdateDisplay("600x1000,600x1000");
533  // Layout the secondary display to the bottom of the primary.
534  DisplayLayout layout(DisplayLayout::BOTTOM, 0);
535  ASSERT_GT(Shell::GetScreen()->GetNumDisplays(), 1);
536  Shell::GetInstance()->display_manager()->
537      SetLayoutForCurrentDisplays(layout);
538
539  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 1000, 201, 310)));
540  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 1000 + 20);
541  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 1000, 210, 310)));
542  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 1000 + 500);
543  scoped_ptr<aura::Window> w3(CreateTestWindow(gfx::Rect(0, 1000, 220, 310)));
544  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w3.get(), 1000 + 600);
545
546  // All windows should be attached and snapped to the right side of the screen.
547  EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
548            w1->GetBoundsInScreen().right());
549  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
550  EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
551            w2->GetBoundsInScreen().right());
552  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
553  EXPECT_EQ(w3->GetRootWindow()->bounds().right(),
554            w3->GetBoundsInScreen().right());
555  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w3->parent()->id());
556
557  gfx::Rect work_area =
558      Shell::GetScreen()->GetDisplayNearestWindow(w1.get()).work_area();
559  // Test that the top and bottom windows are clamped in work area and
560  // that the overlaps between the windows differ at most by a pixel.
561  int gap1 = w1->GetBoundsInScreen().y() - work_area.y();
562  int gap2 = w2->GetBoundsInScreen().y() - w1->GetBoundsInScreen().bottom();
563  int gap3 = w3->GetBoundsInScreen().y() - w2->GetBoundsInScreen().bottom();
564  int gap4 = work_area.bottom() - w3->GetBoundsInScreen().bottom();
565  EXPECT_NEAR(gap1, min_dock_gap(), 1);
566  EXPECT_NEAR(gap2, min_dock_gap(), 1);
567  EXPECT_NEAR(gap3, min_dock_gap(), 1);
568  EXPECT_NEAR(gap4, min_dock_gap(), 1);
569
570  // Drag w1 below the point where w1 and w2 would swap places. This point is
571  // half way between the tops of those two windows.
572  // A bit more vertical drag is needed to account for a window bounds changing
573  // to its restore bounds during the drag.
574  ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w1.get(), 0, 20));
575  DragMove(0, min_dock_gap() + w2->bounds().height() / 2 + 10);
576
577  // During the drag the windows get rearranged and the top and the bottom
578  // should be limited by the work area + |kMinDockGap|.
579  EXPECT_EQ(work_area.y() + min_dock_gap(), w2->GetBoundsInScreen().y());
580  EXPECT_GT(w1->GetBoundsInScreen().y(), w2->GetBoundsInScreen().y());
581  EXPECT_EQ(min_dock_gap(),
582            work_area.bottom() - w3->GetBoundsInScreen().bottom());
583  DragEnd();
584
585  // Test the new windows order and that the overlaps differ at most by a pixel.
586  gap1 = w2->GetBoundsInScreen().y() - work_area.y();
587  gap2 = w1->GetBoundsInScreen().y() - w2->GetBoundsInScreen().bottom();
588  gap3 = w3->GetBoundsInScreen().y() - w1->GetBoundsInScreen().bottom();
589  gap4 = work_area.bottom() - w3->GetBoundsInScreen().bottom();
590  EXPECT_NEAR(gap1, min_dock_gap(), 1);
591  EXPECT_NEAR(gap2, min_dock_gap(), 1);
592  EXPECT_NEAR(gap3, min_dock_gap(), 1);
593  EXPECT_NEAR(gap4, min_dock_gap(), 1);
594}
595
596// Tests that a second window added to the dock is resized to match.
597TEST_P(DockedWindowLayoutManagerTest, TwoWindowsWidthNew) {
598  if (!SupportsHostWindowResize())
599    return;
600
601  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
602  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 280, 202)));
603  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
604  // The first window should get resized to ideal width.
605  EXPECT_EQ(ideal_width(), w1->bounds().width());
606
607  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
608  // The second window should get resized to the existing dock.
609  EXPECT_EQ(ideal_width(), w2->bounds().width());
610}
611
612// Tests that a first non-resizable window added to the dock is not resized.
613TEST_P(DockedWindowLayoutManagerTest, TwoWindowsWidthNonResizableFirst) {
614  if (!SupportsHostWindowResize())
615    return;
616
617  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
618  w1->SetProperty(aura::client::kCanResizeKey, false);
619  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 280, 202)));
620  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
621  // The first window should not get resized.
622  EXPECT_EQ(201, w1->bounds().width());
623
624  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
625  // The second window should get resized to the first window's width.
626  EXPECT_EQ(w1->bounds().width(), w2->bounds().width());
627}
628
629// Tests that a second non-resizable window added to the dock is not resized.
630TEST_P(DockedWindowLayoutManagerTest, TwoWindowsWidthNonResizableSecond) {
631  if (!SupportsHostWindowResize())
632    return;
633
634  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
635  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 280, 202)));
636  w2->SetProperty(aura::client::kCanResizeKey, false);
637  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
638  // The first window should get resized to ideal width.
639  EXPECT_EQ(ideal_width(), w1->bounds().width());
640
641  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
642  // The second window should not get resized.
643  EXPECT_EQ(280, w2->bounds().width());
644
645  // The first window should get resized again - to match the second window.
646  EXPECT_EQ(w1->bounds().width(), w2->bounds().width());
647}
648
649// Test that restrictions on minimum and maximum width of windows are honored.
650TEST_P(DockedWindowLayoutManagerTest, TwoWindowsWidthRestrictions) {
651  if (!SupportsHostWindowResize())
652    return;
653
654  aura::test::TestWindowDelegate delegate1;
655  delegate1.set_maximum_size(gfx::Size(240, 0));
656  scoped_ptr<aura::Window> w1(CreateTestWindowWithDelegate(
657      gfx::Rect(0, 0, 201, 201), &delegate1));
658  aura::test::TestWindowDelegate delegate2;
659  delegate2.set_minimum_size(gfx::Size(260, 0));
660  scoped_ptr<aura::Window> w2(CreateTestWindowWithDelegate(
661      gfx::Rect(0, 0, 280, 202), &delegate2));
662  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
663  // The first window should get resized to its maximum width.
664  EXPECT_EQ(240, w1->bounds().width());
665
666  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
667  // The second window should get resized to its minimum width.
668  EXPECT_EQ(260, w2->bounds().width());
669
670  // The first window should be centered relative to the second.
671  EXPECT_EQ(w1->bounds().CenterPoint().x(), w2->bounds().CenterPoint().x());
672}
673
674// Test that restrictions on minimum width of windows are honored.
675TEST_P(DockedWindowLayoutManagerTest, WidthMoreThanMax) {
676  if (!SupportsHostWindowResize())
677    return;
678
679  aura::test::TestWindowDelegate delegate;
680  delegate.set_minimum_size(gfx::Size(400, 0));
681  scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(
682      gfx::Rect(0, 0, 400, 201), &delegate));
683  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, window.get(), 20);
684
685  // Secondary drag ensures that we are testing the minimum size restriction
686  // and not just failure to get past the tiling step in SnapSizer.
687  ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(window.get(),
688                                                            25, 5));
689  DragMove(150,0);
690  DragEnd();
691
692  // The window should not get docked even though it is dragged past the edge.
693  EXPECT_NE(window->GetRootWindow()->bounds().right(),
694            window->GetBoundsInScreen().right());
695  EXPECT_NE(internal::kShellWindowId_DockedContainer, window->parent()->id());
696}
697
698// Docks three windows and tests that the very first window gets minimized.
699TEST_P(DockedWindowLayoutManagerTest, ThreeWindowsMinimize) {
700  if (!SupportsHostWindowResize())
701    return;
702
703  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
704  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
705  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202)));
706  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 200);
707  scoped_ptr<aura::Window> w3(CreateTestWindow(gfx::Rect(0, 0, 220, 204)));
708  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w3.get(), 300);
709
710  // The last two windows should be attached and snapped to the right edge.
711  EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
712            w2->GetBoundsInScreen().right());
713  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
714  EXPECT_EQ(w3->GetRootWindow()->bounds().right(),
715            w3->GetBoundsInScreen().right());
716  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w3->parent()->id());
717
718  // The first window should get minimized but parented by the dock container.
719  EXPECT_TRUE(wm::GetWindowState(w1.get())->IsMinimized());
720  EXPECT_TRUE(wm::GetWindowState(w2.get())->IsNormalShowState());
721  EXPECT_TRUE(wm::GetWindowState(w3.get())->IsNormalShowState());
722  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
723}
724
725// Docks up to three windows and tests that they split vertical space.
726TEST_P(DockedWindowLayoutManagerTest, ThreeWindowsSplitHeightEvenly) {
727  if (!SupportsHostWindowResize())
728    return;
729
730  UpdateDisplay("600x1000");
731  scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
732  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
733  scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202)));
734  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 200);
735
736  // The two windows should be attached and snapped to the right edge.
737  EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
738            w1->GetBoundsInScreen().right());
739  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
740  EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
741            w2->GetBoundsInScreen().right());
742  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
743
744  // The two windows should be same size vertically and almost 1/2 of work area.
745  gfx::Rect work_area =
746      Shell::GetScreen()->GetDisplayNearestWindow(w1.get()).work_area();
747  EXPECT_NEAR(w1->GetBoundsInScreen().height(),
748              w2->GetBoundsInScreen().height(),
749              1);
750  EXPECT_NEAR(work_area.height() / 2, w1->GetBoundsInScreen().height(),
751              min_dock_gap() * 2);
752
753  // Create and dock the third window.
754  scoped_ptr<aura::Window> w3(CreateTestWindow(gfx::Rect(0, 0, 220, 204)));
755  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w3.get(), 300);
756
757  // All three windows should be docked and snapped to the right edge.
758  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
759  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
760  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w3->parent()->id());
761
762  // All windows should be near same size vertically and about 1/3 of work area.
763  EXPECT_NEAR(w1->GetBoundsInScreen().height(),
764              w2->GetBoundsInScreen().height(),
765              1);
766  EXPECT_NEAR(w2->GetBoundsInScreen().height(),
767              w3->GetBoundsInScreen().height(),
768              1);
769  EXPECT_NEAR(work_area.height() / 3, w1->GetBoundsInScreen().height(),
770              min_dock_gap() * 2);
771}
772
773// Docks two windows and tests that restrictions on vertical size are honored.
774TEST_P(DockedWindowLayoutManagerTest, TwoWindowsHeightRestrictions) {
775  if (!SupportsHostWindowResize())
776    return;
777
778  // The first window is fixed height.
779  aura::test::TestWindowDelegate delegate1;
780  delegate1.set_minimum_size(gfx::Size(0, 300));
781  delegate1.set_maximum_size(gfx::Size(0, 300));
782  scoped_ptr<aura::Window> w1(CreateTestWindowWithDelegate(
783      gfx::Rect(0, 0, 201, 300), &delegate1));
784  // The second window has maximum height.
785  aura::test::TestWindowDelegate delegate2;
786  delegate2.set_maximum_size(gfx::Size(0, 100));
787  scoped_ptr<aura::Window> w2(CreateTestWindowWithDelegate(
788      gfx::Rect(0, 0, 280, 90), &delegate2));
789
790  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
791  DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 200);
792
793  // The two windows should be attached and snapped to the right edge.
794  EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
795            w1->GetBoundsInScreen().right());
796  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
797  EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
798            w2->GetBoundsInScreen().right());
799  EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
800
801  // The two windows should have their heights restricted.
802  EXPECT_EQ(300, w1->GetBoundsInScreen().height());
803  EXPECT_EQ(100, w2->GetBoundsInScreen().height());
804
805  // w1 should be more than half of the work area height (even with a margin).
806  // w2 should be less than half of the work area height (even with a margin).
807  gfx::Rect work_area =
808      Shell::GetScreen()->GetDisplayNearestWindow(w1.get()).work_area();
809  EXPECT_GT(w1->GetBoundsInScreen().height(), work_area.height() / 2 + 10);
810  EXPECT_LT(w2->GetBoundsInScreen().height(), work_area.height() / 2 - 10);
811}
812
813// Tests run twice - on both panels and normal windows
814INSTANTIATE_TEST_CASE_P(NormalOrPanel,
815                        DockedWindowLayoutManagerTest,
816                        testing::Values(aura::client::WINDOW_TYPE_NORMAL,
817                                        aura::client::WINDOW_TYPE_PANEL));
818}  // namespace internal
819}  // namespace ash
820