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
ash / wm / overview / scoped_overview_transform_window_unittest.cc [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/overview/scoped_overview_transform_window.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/overview/overview_types.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/numerics/safe_conversions.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
float GetItemScale(int source_height,
int target_height,
int top_view_inset,
int title_height) {
return ScopedOverviewTransformWindow::GetItemScale(
source_height, target_height, top_view_inset, title_height);
}
} // namespace
using ScopedOverviewTransformWindowTest = AshTestBase;
// Tests that transformed Rect scaling preserves its aspect ratio. The window
// scale is determined by the target height and so the test is actually testing
// that the width is calculated correctly. Since all calculations are done with
// floating point values and then safely converted to integers (using ceiled and
// floored values where appropriate), the expectations are forgiving (use
// *_NEAR) within a single pixel.
TEST_F(ScopedOverviewTransformWindowTest, TransformedRectMaintainsAspect) {
std::unique_ptr<aura::Window> window =
CreateTestWindow(gfx::Rect(10, 10, 100, 100));
ScopedOverviewTransformWindow transform_window(nullptr, window.get());
gfx::RectF rect(50.f, 50.f, 200.f, 400.f);
gfx::RectF bounds(100.f, 100.f, 50.f, 50.f);
gfx::RectF transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(rect, bounds, 0, 0);
float scale = GetItemScale(rect.height(), bounds.height(), 0, 0);
EXPECT_NEAR(scale * rect.width(), transformed_rect.width(), 1);
EXPECT_NEAR(scale * rect.height(), transformed_rect.height(), 1);
rect = gfx::RectF(50.f, 50.f, 400.f, 200.f);
scale = GetItemScale(rect.height(), bounds.height(), 0, 0);
transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(rect, bounds, 0, 0);
EXPECT_NEAR(scale * rect.width(), transformed_rect.width(), 1);
EXPECT_NEAR(scale * rect.height(), transformed_rect.height(), 1);
rect = gfx::RectF(50.f, 50.f, 25.f, 25.f);
scale = GetItemScale(rect.height(), bounds.height(), 0, 0);
transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(rect, bounds, 0, 0);
EXPECT_NEAR(scale * rect.width(), transformed_rect.width(), 1);
EXPECT_NEAR(scale * rect.height(), transformed_rect.height(), 1);
rect = gfx::RectF(50.f, 50.f, 25.f, 50.f);
scale = GetItemScale(rect.height(), bounds.height(), 0, 0);
transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(rect, bounds, 0, 0);
EXPECT_NEAR(scale * rect.width(), transformed_rect.width(), 1);
EXPECT_NEAR(scale * rect.height(), transformed_rect.height(), 1);
rect = gfx::RectF(50.f, 50.f, 50.f, 25.f);
scale = GetItemScale(rect.height(), bounds.height(), 0, 0);
transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(rect, bounds, 0, 0);
EXPECT_NEAR(scale * rect.width(), transformed_rect.width(), 1);
EXPECT_NEAR(scale * rect.height(), transformed_rect.height(), 1);
}
// Tests that transformed Rect fits in target bounds and is vertically centered.
TEST_F(ScopedOverviewTransformWindowTest, TransformedRectIsCentered) {
std::unique_ptr<aura::Window> window =
CreateTestWindow(gfx::Rect(10, 10, 100, 100));
ScopedOverviewTransformWindow transform_window(nullptr, window.get());
gfx::RectF rect(50.f, 50.f, 200.f, 400.f);
gfx::RectF bounds(100.f, 100.f, 50.f, 50.f);
gfx::RectF transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(rect, bounds, 0, 0);
EXPECT_GE(transformed_rect.x(), bounds.x());
EXPECT_LE(transformed_rect.right(), bounds.right());
EXPECT_GE(transformed_rect.y(), bounds.y());
EXPECT_LE(transformed_rect.bottom(), bounds.bottom());
EXPECT_NEAR(transformed_rect.x() - bounds.x(),
bounds.right() - transformed_rect.right(), 1);
EXPECT_NEAR(transformed_rect.y() - bounds.y(),
bounds.bottom() - transformed_rect.bottom(), 1);
}
// Tests that transformed Rect fits in target bounds and is vertically centered
// when inset and header height are specified.
TEST_F(ScopedOverviewTransformWindowTest, TransformedRectIsCenteredWithInset) {
std::unique_ptr<aura::Window> window =
CreateTestWindow(gfx::Rect(10, 10, 100, 100));
ScopedOverviewTransformWindow transform_window(nullptr, window.get());
gfx::RectF rect(50.f, 50.f, 400.f, 200.f);
gfx::RectF bounds(100.f, 100.f, 50.f, 50.f);
const int inset = 20;
const int header_height = 10;
const float scale =
GetItemScale(rect.height(), bounds.height(), inset, header_height);
gfx::RectF transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(rect, bounds, inset,
header_height);
// The |rect| width does not fit and therefore it gets centered outside
// |bounds| starting before |bounds.x()| and ending after |bounds.right()|.
EXPECT_LE(transformed_rect.x(), bounds.x());
EXPECT_GE(transformed_rect.right(), bounds.right());
EXPECT_GE(
transformed_rect.y() + base::ClampCeil(scale * inset) - header_height,
bounds.y());
EXPECT_LE(transformed_rect.bottom(), bounds.bottom());
EXPECT_NEAR(transformed_rect.x() - bounds.x(),
bounds.right() - transformed_rect.right(), 1);
EXPECT_NEAR(
transformed_rect.y() + (int)(scale * inset) - header_height - bounds.y(),
bounds.bottom() - transformed_rect.bottom(), 1);
}
// Verify that a window which will be displayed like a letter box on the window
// grid has the correct bounds.
TEST_F(ScopedOverviewTransformWindowTest, TransformingLetteredRect) {
// Create a window whose width is more than twice the height.
const gfx::Rect original_bounds(10, 10, 300, 100);
const int scale = 3;
std::unique_ptr<aura::Window> window = CreateTestWindow(original_bounds);
ScopedOverviewTransformWindow transform_window(nullptr, window.get());
EXPECT_EQ(OverviewItemFillMode::kLetterBoxed, transform_window.fill_mode());
// Without any headers, the width should match the target, and the height
// should be such that the aspect ratio of |original_bounds| is maintained.
const gfx::RectF overview_bounds(100.f, 100.f);
gfx::RectF transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(
gfx::RectF(original_bounds), overview_bounds, 0, 0);
EXPECT_EQ(overview_bounds.width(), transformed_rect.width());
EXPECT_NEAR(overview_bounds.height() / scale, transformed_rect.height(), 1);
// With headers, the width should still match the target. The height should
// still be such that the aspect ratio is maintained, but the original header
// which is hidden in overview needs to be accounted for.
const int original_header = 10;
const int overview_header = 20;
transformed_rect = transform_window.ShrinkRectToFitPreservingAspectRatio(
gfx::RectF(original_bounds), overview_bounds, original_header,
overview_header);
EXPECT_EQ(overview_bounds.width(), transformed_rect.width());
EXPECT_NEAR((overview_bounds.height() - original_header) / scale,
transformed_rect.height() - original_header / scale, 1);
EXPECT_TRUE(overview_bounds.Contains(transformed_rect));
}
// Verify that a window which will be displayed like a pillar box on the window
// grid has the correct bounds.
TEST_F(ScopedOverviewTransformWindowTest, TransformingPillaredRect) {
// Create a window whose height is more than twice the width.
const gfx::Rect original_bounds(10, 10, 150, 450);
const int scale = 3;
std::unique_ptr<aura::Window> window = CreateTestWindow(original_bounds);
ScopedOverviewTransformWindow transform_window(nullptr, window.get());
EXPECT_EQ(OverviewItemFillMode::kPillarBoxed, transform_window.fill_mode());
// Without any headers, the height should match the target, and the width
// should be such that the aspect ratio of |original_bounds| is maintained.
const gfx::RectF overview_bounds(100.f, 100.f);
gfx::RectF transformed_rect =
transform_window.ShrinkRectToFitPreservingAspectRatio(
gfx::RectF(original_bounds), overview_bounds, 0, 0);
EXPECT_EQ(overview_bounds.height(), transformed_rect.height());
EXPECT_NEAR(overview_bounds.width() / scale, transformed_rect.width(), 1);
// With headers, the height should not include the area reserved for the
// overview window title. It also needs to account for the original header
// which will become hidden in overview mode.
const int original_header = 10;
const int overview_header = 20;
transformed_rect = transform_window.ShrinkRectToFitPreservingAspectRatio(
gfx::RectF(original_bounds), overview_bounds, original_header,
overview_header);
const float overview_scale =
original_bounds.height() / overview_bounds.height();
const float expected_height = overview_bounds.height() - overview_header +
original_header / overview_scale;
EXPECT_NEAR(expected_height, transformed_rect.height(), 1);
EXPECT_TRUE(overview_bounds.Contains(transformed_rect));
}
// Tests the cases when very wide or tall windows enter overview mode.
TEST_F(ScopedOverviewTransformWindowTest, ExtremeWindowBounds) {
// Add three windows which in overview mode will be considered wide, tall and
// normal. Window |wide|, with size (400, 160) will be resized to (300, 160)
// when the 400x300 is rotated to 300x400, and should be considered a normal
// overview window after display change.
UpdateDisplay("400x300");
std::unique_ptr<aura::Window> wide = CreateTestWindow(gfx::Rect(400, 160));
std::unique_ptr<aura::Window> tall = CreateTestWindow(gfx::Rect(100, 300));
std::unique_ptr<aura::Window> normal = CreateTestWindow(gfx::Rect(300, 300));
ScopedOverviewTransformWindow scoped_wide(nullptr, wide.get());
ScopedOverviewTransformWindow scoped_tall(nullptr, tall.get());
ScopedOverviewTransformWindow scoped_normal(nullptr, normal.get());
// Verify the window dimension type is as expected after entering overview
// mode.
EXPECT_EQ(OverviewItemFillMode::kLetterBoxed, scoped_wide.fill_mode());
EXPECT_EQ(OverviewItemFillMode::kPillarBoxed, scoped_tall.fill_mode());
EXPECT_EQ(OverviewItemFillMode::kNormal, scoped_normal.fill_mode());
display::Screen* screen = display::Screen::GetScreen();
const display::Display& display = screen->GetPrimaryDisplay();
display_manager()->SetDisplayRotation(
display.id(), display::Display::ROTATE_90,
display::Display::RotationSource::ACTIVE);
scoped_wide.UpdateOverviewItemFillMode();
scoped_tall.UpdateOverviewItemFillMode();
scoped_normal.UpdateOverviewItemFillMode();
// Verify that |wide| has its window dimension type updated after the display
// change.
EXPECT_EQ(OverviewItemFillMode::kNormal, scoped_wide.fill_mode());
EXPECT_EQ(OverviewItemFillMode::kPillarBoxed, scoped_tall.fill_mode());
EXPECT_EQ(OverviewItemFillMode::kNormal, scoped_normal.fill_mode());
}
// Tests that transients which should be invisible in overview do not have their
// transforms or opacities altered.
TEST_F(ScopedOverviewTransformWindowTest, InvisibleTransients) {
auto window = CreateTestWindow(gfx::Rect(200, 200));
auto child = CreateTestWindow(gfx::Rect(100, 190, 100, 10),
aura::client::WINDOW_TYPE_POPUP);
auto child2 = CreateTestWindow(gfx::Rect(0, 190, 100, 10),
aura::client::WINDOW_TYPE_POPUP);
::wm::AddTransientChild(window.get(), child.get());
::wm::AddTransientChild(window.get(), child2.get());
child2->SetProperty(kHideInOverviewKey, true);
for (auto* it : {window.get(), child.get(), child2.get()}) {
it->SetTransform(gfx::Transform());
it->layer()->SetOpacity(1.f);
}
ScopedOverviewTransformWindow scoped_window(nullptr, window.get());
scoped_window.SetOpacity(0.5f);
EXPECT_EQ(0.5f, window->layer()->opacity());
EXPECT_EQ(0.5f, child->layer()->opacity());
EXPECT_EQ(0.f, child2->layer()->opacity());
EXPECT_TRUE(window->IsVisible());
EXPECT_TRUE(child->IsVisible());
EXPECT_FALSE(child2->IsVisible());
auto transform = gfx::Transform::MakeTranslation(10.f, 10.f);
window_util::SetTransform(window.get(), transform);
EXPECT_EQ(transform, window->transform());
EXPECT_EQ(transform, child->transform());
EXPECT_TRUE(child2->transform().IsIdentity());
}
// Tests that the transient window which should be invisible in overview is not
// visible even if the window property is changed after initializing
// ScopedOverviewTransformWindow.
TEST_F(ScopedOverviewTransformWindowTest,
InvisibleTransientsPropertyChangeAfterInit) {
auto window = CreateTestWindow(gfx::Rect(200, 200));
auto child = CreateTestWindow(gfx::Rect(100, 190, 100, 10),
aura::client::WINDOW_TYPE_POPUP);
::wm::AddTransientChild(window.get(), child.get());
ScopedOverviewTransformWindow scoped_window(nullptr, window.get());
EXPECT_TRUE(window->IsVisible());
EXPECT_TRUE(child->IsVisible());
// Change property after construction of |scoped_window|.
child->SetProperty(kHideInOverviewKey, true);
EXPECT_TRUE(window->IsVisible());
EXPECT_FALSE(child->IsVisible());
// Clear property after construction of |scoped_window|.
child->ClearProperty(kHideInOverviewKey);
EXPECT_TRUE(window->IsVisible());
EXPECT_TRUE(child->IsVisible());
// Change to hide again.
child->SetProperty(kHideInOverviewKey, true);
EXPECT_TRUE(window->IsVisible());
EXPECT_FALSE(child->IsVisible());
}
// Tests that the transient window which should be invisible in overview is not
// visible even if the window is added after initializing
// ScopedOverviewTransformWindow.
TEST_F(ScopedOverviewTransformWindowTest, InvisibleTransientsAddedAfterInit) {
auto window = CreateTestWindow(gfx::Rect(200, 200));
auto child = CreateTestWindow(gfx::Rect(100, 190, 100, 10),
aura::client::WINDOW_TYPE_POPUP);
auto child2 = CreateTestWindow(gfx::Rect(0, 190, 100, 10),
aura::client::WINDOW_TYPE_POPUP);
child2->SetProperty(kHideInOverviewKey, true);
ScopedOverviewTransformWindow scoped_window(nullptr, window.get());
// Add visible transient after construction of |scoped_window|.
::wm::AddTransientChild(window.get(), child.get());
EXPECT_TRUE(window->IsVisible());
EXPECT_TRUE(child->IsVisible());
// Add invisible transient after construction of |scoped_window|.
::wm::AddTransientChild(window.get(), child2.get());
EXPECT_TRUE(window->IsVisible());
EXPECT_TRUE(child->IsVisible());
EXPECT_FALSE(child2->IsVisible());
}
// Tests that the event targeting policies of a given window and transient
// descendants gets set as expected.
TEST_F(ScopedOverviewTransformWindowTest, EventTargetingPolicy) {
using etp = aura::EventTargetingPolicy;
// Helper for creating popups that will be transients for testing.
auto create_popup = [this] {
std::unique_ptr<aura::Window> popup =
CreateTestWindow(gfx::Rect(10, 10), aura::client::WINDOW_TYPE_POPUP);
popup->SetEventTargetingPolicy(etp::kTargetAndDescendants);
return popup;
};
auto window = CreateTestWindow(gfx::Rect(200, 200));
window->SetEventTargetingPolicy(etp::kTargetAndDescendants);
auto transient = create_popup();
auto transient1 = create_popup();
auto transient2 = create_popup();
::wm::AddTransientChild(window.get(), transient.get());
{
// Tests that after creating the scoped object, the window and its current
// transient child have |kNone| targeting policy.
ScopedOverviewTransformWindow scoped_window(nullptr, window.get());
EXPECT_EQ(etp::kNone, window->event_targeting_policy());
EXPECT_EQ(etp::kNone, transient->event_targeting_policy());
// Tests that after adding transient children, one to the window itself and
// one to the current transient child, they will both have |kNone| targeting
// policy.
::wm::AddTransientChild(window.get(), transient1.get());
::wm::AddTransientChild(transient.get(), transient2.get());
EXPECT_EQ(etp::kNone, transient1->event_targeting_policy());
EXPECT_EQ(etp::kNone, transient2->event_targeting_policy());
// Tests that adding a transient child which does not have |window| as its
// descendant does not have its targeting policy altered.
auto window2 = CreateTestWindow(gfx::Rect(200, 200));
auto transient3 = create_popup();
::wm::AddTransientChild(window2.get(), transient3.get());
EXPECT_EQ(etp::kTargetAndDescendants, transient3->event_targeting_policy());
// Tests that removing a transient child from |window| will reset its
// targeting policy.
::wm::RemoveTransientChild(window.get(), transient1.get());
EXPECT_EQ(etp::kTargetAndDescendants, transient1->event_targeting_policy());
}
// Tests that when the scoped object is destroyed, the targeting policies all
// get reset.
EXPECT_EQ(etp::kTargetAndDescendants, window->event_targeting_policy());
EXPECT_EQ(etp::kTargetAndDescendants, transient->event_targeting_policy());
EXPECT_EQ(etp::kTargetAndDescendants, transient2->event_targeting_policy());
}
} // namespace ash