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