1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
|
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/window_positioner.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_resizer.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/screen.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"
namespace ash {
const int WindowPositioner::kMinimumWindowOffset = 32;
// The number of pixels which are kept free top, left and right when a window
// gets positioned to its default location.
// static
const int WindowPositioner::kDesktopBorderSize = 16;
// Maximum width of a window even if there is more room on the desktop.
// static
const int WindowPositioner::kMaximumWindowWidth = 1100;
namespace {
// When a window gets opened in default mode and the screen is less than or
// equal to this width, the window will get opened in maximized mode. This value
// can be reduced to a "tame" number if the feature is disabled.
const int kForceMaximizeWidthLimit = 1366;
// The time in milliseconds which should be used to visually move a window
// through an automatic "intelligent" window management option.
const int kWindowAutoMoveDurationMS = 125;
// If set to true all window repositioning actions will be ignored. Set through
// WindowPositioner::SetIgnoreActivations().
static bool disable_auto_positioning = false;
// If set to true, by default the first window in ASH will be maximized.
static bool maximize_first_window = false;
// Check if any management should be performed (with a given |window|).
bool UseAutoWindowManager(const aura::Window* window) {
if (disable_auto_positioning)
return false;
const wm::WindowState* window_state = wm::GetWindowState(window);
return !window_state->is_dragged() && window_state->window_position_managed();
}
// Check if a given |window| can be managed. This includes that it's state is
// not minimized/maximized/the user has changed it's size by hand already.
// It furthermore checks for the WindowIsManaged status.
bool WindowPositionCanBeManaged(const aura::Window* window) {
if (disable_auto_positioning)
return false;
const wm::WindowState* window_state = wm::GetWindowState(window);
return window_state->window_position_managed() &&
!window_state->IsMinimized() &&
!window_state->IsMaximized() &&
!window_state->bounds_changed_by_user();
}
// Get the work area for a given |window| in parent coordinates.
gfx::Rect GetWorkAreaForWindowInParent(aura::Window* window) {
#if defined(OS_WIN)
// On Win 8, the host window can't be resized, so
// use window's bounds instead.
// TODO(oshima): Emulate host window resize on win8.
gfx::Rect work_area = gfx::Rect(window->parent()->bounds().size());
work_area.Inset(Shell::GetScreen()->GetDisplayMatching(
window->parent()->GetBoundsInScreen()).GetWorkAreaInsets());
return work_area;
#else
return ScreenUtil::GetDisplayWorkAreaBoundsInParent(window);
#endif
}
// Move the given |bounds| on the available |work_area| in the direction
// indicated by |move_right|. If |move_right| is true, the rectangle gets moved
// to the right edge, otherwise to the left one.
bool MoveRectToOneSide(const gfx::Rect& work_area,
bool move_right,
gfx::Rect* bounds) {
if (move_right) {
if (work_area.right() > bounds->right()) {
bounds->set_x(work_area.right() - bounds->width());
return true;
}
} else {
if (work_area.x() < bounds->x()) {
bounds->set_x(work_area.x());
return true;
}
}
return false;
}
// Move a |window| to new |bounds|. Animate if desired by user.
// Moves the transient children of the |window| as well by the same |offset| as
// the parent |window|.
void SetBoundsAndOffsetTransientChildren(aura::Window* window,
const gfx::Rect& bounds,
const gfx::Rect& work_area,
const gfx::Vector2d& offset) {
aura::Window::Windows transient_children =
::wm::GetTransientChildren(window);
for (aura::Window::Windows::iterator iter = transient_children.begin();
iter != transient_children.end(); ++iter) {
aura::Window* transient_child = *iter;
gfx::Rect child_bounds = transient_child->bounds();
gfx::Rect new_child_bounds = child_bounds + offset;
if ((child_bounds.x() <= work_area.x() &&
new_child_bounds.x() <= work_area.x()) ||
(child_bounds.right() >= work_area.right() &&
new_child_bounds.right() >= work_area.right())) {
continue;
}
if (new_child_bounds.right() > work_area.right())
new_child_bounds.set_x(work_area.right() - bounds.width());
else if (new_child_bounds.x() < work_area.x())
new_child_bounds.set_x(work_area.x());
SetBoundsAndOffsetTransientChildren(transient_child,
new_child_bounds, work_area, offset);
}
if (::wm::WindowAnimationsDisabled(window)) {
window->SetBounds(bounds);
return;
}
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kWindowAutoMoveDurationMS));
window->SetBounds(bounds);
}
// Move a |window| to new |bounds|. Animate if desired by user.
// Note: The function will do nothing if the bounds did not change.
void SetBoundsAnimated(aura::Window* window,
const gfx::Rect& bounds,
const gfx::Rect& work_area) {
gfx::Rect old_bounds = window->GetTargetBounds();
if (bounds == old_bounds)
return;
gfx::Vector2d offset(bounds.origin() - old_bounds.origin());
SetBoundsAndOffsetTransientChildren(window, bounds, work_area, offset);
}
// Move |window| into the center of the screen - or restore it to the previous
// position.
void AutoPlaceSingleWindow(aura::Window* window, bool animated) {
gfx::Rect work_area = GetWorkAreaForWindowInParent(window);
gfx::Rect bounds = window->bounds();
const gfx::Rect* user_defined_area =
wm::GetWindowState(window)->pre_auto_manage_window_bounds();
if (user_defined_area) {
bounds = *user_defined_area;
ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area, &bounds);
} else {
// Center the window (only in x).
bounds.set_x(work_area.x() + (work_area.width() - bounds.width()) / 2);
}
if (animated)
SetBoundsAnimated(window, bounds, work_area);
else
window->SetBounds(bounds);
}
// Get the first open (non minimized) window which is on the screen defined.
aura::Window* GetReferenceWindow(const aura::Window* root_window,
const aura::Window* exclude,
bool *single_window) {
if (single_window)
*single_window = true;
// Get the active window.
aura::Window* active = ash::wm::GetActiveWindow();
if (active && active->GetRootWindow() != root_window)
active = NULL;
// Get a list of all windows.
const std::vector<aura::Window*> windows =
ash::MruWindowTracker::BuildWindowList();
if (windows.empty())
return NULL;
aura::Window::Windows::const_iterator iter = windows.begin();
// Find the index of the current active window.
if (active)
iter = std::find(windows.begin(), windows.end(), active);
int index = (iter == windows.end()) ? 0 : (iter - windows.begin());
// Scan the cycle list backwards to see which is the second topmost window
// (and so on). Note that we might cycle a few indices twice if there is no
// suitable window. However - since the list is fairly small this should be
// very fast anyways.
aura::Window* found = NULL;
for (int i = index + windows.size(); i >= 0; i--) {
aura::Window* window = windows[i % windows.size()];
while (::wm::GetTransientParent(window))
window = ::wm::GetTransientParent(window);
if (window != exclude && window->type() == ui::wm::WINDOW_TYPE_NORMAL &&
window->GetRootWindow() == root_window && window->TargetVisibility() &&
wm::GetWindowState(window)->window_position_managed()) {
if (found && found != window) {
// no need to check !single_window because the function must have
// been already returned in the "if (!single_window)" below.
*single_window = false;
return found;
}
found = window;
// If there is no need to check single window, return now.
if (!single_window)
return found;
}
}
return found;
}
} // namespace
// static
int WindowPositioner::GetForceMaximizedWidthLimit() {
return kForceMaximizeWidthLimit;
}
// static
void WindowPositioner::GetBoundsAndShowStateForNewWindow(
const gfx::Screen* screen,
const aura::Window* new_window,
bool is_saved_bounds,
ui::WindowShowState show_state_in,
gfx::Rect* bounds_in_out,
ui::WindowShowState* show_state_out) {
// Always open new window in the target display.
aura::Window* target = Shell::GetTargetRootWindow();
aura::Window* top_window = GetReferenceWindow(target, NULL, NULL);
// Our window should not have any impact if we are already on top.
if (top_window == new_window)
top_window = NULL;
// If there is no valid other window we take and adjust the passed coordinates
// and show state.
if (!top_window) {
gfx::Rect work_area = screen->GetDisplayNearestWindow(target).work_area();
bounds_in_out->AdjustToFit(work_area);
// Use adjusted saved bounds, if there is one.
if (is_saved_bounds)
return;
// When using "small screens" we want to always open in full screen mode.
if (show_state_in == ui::SHOW_STATE_DEFAULT && (maximize_first_window ||
(work_area.width() <= GetForceMaximizedWidthLimit() &&
(!new_window || !wm::GetWindowState(new_window)->IsFullscreen())))) {
*show_state_out = ui::SHOW_STATE_MAXIMIZED;
}
return;
}
wm::WindowState* top_window_state = wm::GetWindowState(top_window);
bool maximized = top_window_state->IsMaximized();
// We ignore the saved show state, but look instead for the top level
// window's show state.
if (show_state_in == ui::SHOW_STATE_DEFAULT) {
*show_state_out = maximized ? ui::SHOW_STATE_MAXIMIZED :
ui::SHOW_STATE_DEFAULT;
}
if (maximized) {
bool has_restore_bounds = top_window_state->HasRestoreBounds();
if (has_restore_bounds) {
// For a maximized window ignore the real bounds of the top level window
// and use its restore bounds instead. Offset the bounds to prevent the
// windows from overlapping exactly when restored.
*bounds_in_out = top_window_state->GetRestoreBoundsInScreen() +
gfx::Vector2d(kMinimumWindowOffset, kMinimumWindowOffset);
}
if (is_saved_bounds || has_restore_bounds) {
gfx::Rect work_area = screen->GetDisplayNearestWindow(target).work_area();
bounds_in_out->AdjustToFit(work_area);
// Use adjusted saved bounds or restore bounds, if there is one.
return;
}
}
// Use the size of the other window. The window's bound will be rearranged
// in ash::WorkspaceLayoutManager using this location.
*bounds_in_out = top_window->GetBoundsInScreen();
}
// static
void WindowPositioner::RearrangeVisibleWindowOnHideOrRemove(
const aura::Window* removed_window) {
if (!UseAutoWindowManager(removed_window))
return;
// Find a single open browser window.
bool single_window;
aura::Window* other_shown_window = GetReferenceWindow(
removed_window->GetRootWindow(), removed_window, &single_window);
if (!other_shown_window || !single_window ||
!WindowPositionCanBeManaged(other_shown_window))
return;
AutoPlaceSingleWindow(other_shown_window, true);
}
// static
bool WindowPositioner::DisableAutoPositioning(bool ignore) {
bool old_state = disable_auto_positioning;
disable_auto_positioning = ignore;
return old_state;
}
// static
void WindowPositioner::RearrangeVisibleWindowOnShow(
aura::Window* added_window) {
wm::WindowState* added_window_state = wm::GetWindowState(added_window);
if (!added_window->TargetVisibility())
return;
if (!UseAutoWindowManager(added_window) ||
added_window_state->bounds_changed_by_user()) {
if (added_window_state->minimum_visibility()) {
// Guarantee minimum visibility within the work area.
gfx::Rect work_area = GetWorkAreaForWindowInParent(added_window);
gfx::Rect bounds = added_window->bounds();
gfx::Rect new_bounds = bounds;
ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area,
&new_bounds);
if (new_bounds != bounds)
added_window->SetBounds(new_bounds);
}
return;
}
// Find a single open managed window.
bool single_window;
aura::Window* other_shown_window = GetReferenceWindow(
added_window->GetRootWindow(), added_window, &single_window);
if (!other_shown_window) {
// It could be that this window is the first window joining the workspace.
if (!WindowPositionCanBeManaged(added_window) || other_shown_window)
return;
// Since we might be going from 0 to 1 window, we have to arrange the new
// window to a good default.
AutoPlaceSingleWindow(added_window, false);
return;
}
gfx::Rect other_bounds = other_shown_window->bounds();
gfx::Rect work_area = GetWorkAreaForWindowInParent(added_window);
bool move_other_right =
other_bounds.CenterPoint().x() > work_area.x() + work_area.width() / 2;
// Push the other window to the size only if there are two windows left.
if (single_window) {
// When going from one to two windows both windows loose their
// "positioned by user" flags.
added_window_state->set_bounds_changed_by_user(false);
wm::WindowState* other_window_state =
wm::GetWindowState(other_shown_window);
other_window_state->set_bounds_changed_by_user(false);
if (WindowPositionCanBeManaged(other_shown_window)) {
// Don't override pre auto managed bounds as the current bounds
// may not be original.
if (!other_window_state->pre_auto_manage_window_bounds())
other_window_state->SetPreAutoManageWindowBounds(other_bounds);
// Push away the other window after remembering its current position.
if (MoveRectToOneSide(work_area, move_other_right, &other_bounds))
SetBoundsAnimated(other_shown_window, other_bounds, work_area);
}
}
// Remember the current location of the window if it's new and push
// it also to the opposite location if needed. Since it is just
// being shown, we do not need to animate it.
gfx::Rect added_bounds = added_window->bounds();
if (!added_window_state->pre_auto_manage_window_bounds())
added_window_state->SetPreAutoManageWindowBounds(added_bounds);
if (MoveRectToOneSide(work_area, !move_other_right, &added_bounds))
added_window->SetBounds(added_bounds);
}
WindowPositioner::WindowPositioner()
: pop_position_offset_increment_x(0),
pop_position_offset_increment_y(0),
popup_position_offset_from_screen_corner_x(0),
popup_position_offset_from_screen_corner_y(0),
last_popup_position_x_(0),
last_popup_position_y_(0) {
}
WindowPositioner::~WindowPositioner() {
}
gfx::Rect WindowPositioner::GetDefaultWindowBounds(
const gfx::Display& display) {
const gfx::Rect work_area = display.work_area();
// There should be a 'desktop' border around the window at the left and right
// side.
int default_width = work_area.width() - 2 * kDesktopBorderSize;
// There should also be a 'desktop' border around the window at the top.
// Since the workspace excludes the tray area we only need one border size.
int default_height = work_area.height() - kDesktopBorderSize;
int offset_x = kDesktopBorderSize;
if (default_width > kMaximumWindowWidth) {
// The window should get centered on the screen and not follow the grid.
offset_x = (work_area.width() - kMaximumWindowWidth) / 2;
default_width = kMaximumWindowWidth;
}
return gfx::Rect(work_area.x() + offset_x,
work_area.y() + kDesktopBorderSize,
default_width,
default_height);
}
gfx::Rect WindowPositioner::GetPopupPosition(const gfx::Rect& old_pos) {
int grid = kMinimumWindowOffset;
popup_position_offset_from_screen_corner_x = grid;
popup_position_offset_from_screen_corner_y = grid;
if (!pop_position_offset_increment_x) {
// When the popup position increment is 0, the last popup position
// was not yet initialized.
last_popup_position_x_ = popup_position_offset_from_screen_corner_x;
last_popup_position_y_ = popup_position_offset_from_screen_corner_y;
}
pop_position_offset_increment_x = grid;
pop_position_offset_increment_y = grid;
// We handle the Multi monitor support by retrieving the active window's
// work area.
aura::Window* window = wm::GetActiveWindow();
const gfx::Rect work_area = window && window->IsVisible() ?
Shell::GetScreen()->GetDisplayNearestWindow(window).work_area() :
Shell::GetScreen()->GetPrimaryDisplay().work_area();
// Only try to reposition the popup when it is not spanning the entire
// screen.
if ((old_pos.width() + popup_position_offset_from_screen_corner_x >=
work_area.width()) ||
(old_pos.height() + popup_position_offset_from_screen_corner_y >=
work_area.height()))
return AlignPopupPosition(old_pos, work_area, grid);
const gfx::Rect result = SmartPopupPosition(old_pos, work_area, grid);
if (!result.IsEmpty())
return AlignPopupPosition(result, work_area, grid);
return NormalPopupPosition(old_pos, work_area);
}
// static
void WindowPositioner::SetMaximizeFirstWindow(bool maximize) {
maximize_first_window = maximize;
}
gfx::Rect WindowPositioner::NormalPopupPosition(
const gfx::Rect& old_pos,
const gfx::Rect& work_area) {
int w = old_pos.width();
int h = old_pos.height();
// Note: The 'last_popup_position' is checked and kept relative to the
// screen size. The offsetting will be done in the last step when the
// target rectangle gets returned.
bool reset = false;
if (last_popup_position_y_ + h > work_area.height() ||
last_popup_position_x_ + w > work_area.width()) {
// Popup does not fit on screen. Reset to next diagonal row.
last_popup_position_x_ -= last_popup_position_y_ -
popup_position_offset_from_screen_corner_x -
pop_position_offset_increment_x;
last_popup_position_y_ = popup_position_offset_from_screen_corner_y;
reset = true;
}
if (last_popup_position_x_ + w > work_area.width()) {
// Start again over.
last_popup_position_x_ = popup_position_offset_from_screen_corner_x;
last_popup_position_y_ = popup_position_offset_from_screen_corner_y;
reset = true;
}
int x = last_popup_position_x_;
int y = last_popup_position_y_;
if (!reset) {
last_popup_position_x_ += pop_position_offset_increment_x;
last_popup_position_y_ += pop_position_offset_increment_y;
}
return gfx::Rect(x + work_area.x(), y + work_area.y(), w, h);
}
gfx::Rect WindowPositioner::SmartPopupPosition(
const gfx::Rect& old_pos,
const gfx::Rect& work_area,
int grid) {
const std::vector<aura::Window*> windows =
MruWindowTracker::BuildWindowList();
std::vector<const gfx::Rect*> regions;
// Process the window list and check if we can bail immediately.
for (size_t i = 0; i < windows.size(); i++) {
// We only include opaque and visible windows.
if (windows[i] && windows[i]->IsVisible() && windows[i]->layer() &&
(!windows[i]->transparent() ||
windows[i]->layer()->GetTargetOpacity() == 1.0)) {
wm::WindowState* window_state = wm::GetWindowState(windows[i]);
// When any window is maximized we cannot find any free space.
if (window_state->IsMaximizedOrFullscreen())
return gfx::Rect(0, 0, 0, 0);
if (window_state->IsNormalOrSnapped())
regions.push_back(&windows[i]->bounds());
}
}
if (regions.empty())
return gfx::Rect(0, 0, 0, 0);
int w = old_pos.width();
int h = old_pos.height();
int x_end = work_area.width() / 2;
int x, x_increment;
// We parse for a proper location on the screen. We do this in two runs:
// The first run will start from the left, parsing down, skipping any
// overlapping windows it will encounter until the popup's height can not
// be served anymore. Then the next grid position to the right will be
// taken, and the same cycle starts again. This will be repeated until we
// hit the middle of the screen (or we find a suitable location).
// In the second run we parse beginning from the right corner downwards and
// then to the left.
// When no location was found, an empty rectangle will be returned.
for (int run = 0; run < 2; run++) {
if (run == 0) { // First run: Start left, parse right till mid screen.
x = 0;
x_increment = pop_position_offset_increment_x;
} else { // Second run: Start right, parse left till mid screen.
x = work_area.width() - w;
x_increment = -pop_position_offset_increment_x;
}
// Note: The passing (x,y,w,h) window is always relative to the work area's
// origin.
for (; x_increment > 0 ? (x < x_end) : (x > x_end); x += x_increment) {
int y = 0;
while (y + h <= work_area.height()) {
size_t i;
for (i = 0; i < regions.size(); i++) {
if (regions[i]->Intersects(gfx::Rect(x + work_area.x(),
y + work_area.y(), w, h))) {
y = regions[i]->bottom() - work_area.y();
break;
}
}
if (i >= regions.size())
return gfx::Rect(x + work_area.x(), y + work_area.y(), w, h);
}
}
}
return gfx::Rect(0, 0, 0, 0);
}
gfx::Rect WindowPositioner::AlignPopupPosition(
const gfx::Rect& pos,
const gfx::Rect& work_area,
int grid) {
if (grid <= 1)
return pos;
int x = pos.x() - (pos.x() - work_area.x()) % grid;
int y = pos.y() - (pos.y() - work_area.y()) % grid;
int w = pos.width();
int h = pos.height();
// If the alignment was pushing the window out of the screen, we ignore the
// alignment for that call.
if (abs(pos.right() - work_area.right()) < grid)
x = work_area.right() - w;
if (abs(pos.bottom() - work_area.bottom()) < grid)
y = work_area.bottom() - h;
return gfx::Rect(x, y, w, h);
}
} // namespace ash
|