| Flutter macOS Embedder |

FlutterMutatorViewTest.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter 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 #import <OCMock/OCMock.h>
6 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.h"
7 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
8
9 #include "third_party/googletest/googletest/include/gtest/gtest.h"
10
11 #import "flutter/shell/platform/darwin/macos/framework/Source/NSView+ClipsToBounds.h"
12
13 @interface FlutterMutatorView (Private)
14
15 @property(readonly, nonatomic, nonnull) NSMutableArray<NSView*>* pathClipViews;
16 @property(readonly, nonatomic, nullable) NSView* platformViewContainer;
17
18 @end
19
20 static constexpr float kMaxErr = 1e-10;
21
22 namespace {
23 void ApplyFlutterLayer(FlutterMutatorView* view,
24 FlutterSize size,
25 const std::vector<FlutterPlatformViewMutation>& mutations) {
26 flutter::PlatformViewLayer layer(0, // identifier
27 mutations,
28 // Offset is ignored by mutator view, the bounding rect is
29 // determined by width and transform.
30 FlutterPoint{0, 0}, // offset
31 size);
32
33 [view applyFlutterLayer:&layer];
34 }
35
36 // Expect that each element within two CATransform3Ds is within an error bound.
37 //
38 // In order to avoid architecture-specific floating point differences we don't check for exact
39 // equality using, for example, CATransform3DEqualToTransform.
40 void ExpectTransform3DEqual(const CATransform3D& t, const CATransform3D& u) {
41 EXPECT_NEAR(t.m11, u.m11, kMaxErr);
42 EXPECT_NEAR(t.m12, u.m12, kMaxErr);
43 EXPECT_NEAR(t.m13, u.m13, kMaxErr);
44 EXPECT_NEAR(t.m14, u.m14, kMaxErr);
45
46 EXPECT_NEAR(t.m21, u.m21, kMaxErr);
47 EXPECT_NEAR(t.m22, u.m22, kMaxErr);
48 EXPECT_NEAR(t.m23, u.m23, kMaxErr);
49 EXPECT_NEAR(t.m24, u.m24, kMaxErr);
50
51 EXPECT_NEAR(t.m31, u.m31, kMaxErr);
52 EXPECT_NEAR(t.m32, u.m32, kMaxErr);
53 EXPECT_NEAR(t.m33, u.m33, kMaxErr);
54 EXPECT_NEAR(t.m34, u.m34, kMaxErr);
55
56 EXPECT_NEAR(t.m41, u.m41, kMaxErr);
57 EXPECT_NEAR(t.m42, u.m42, kMaxErr);
58 EXPECT_NEAR(t.m43, u.m43, kMaxErr);
59 EXPECT_NEAR(t.m44, u.m44, kMaxErr);
60 }
61 } // namespace
62
63 TEST(FlutterMutatorViewTest, BasicFrameIsCorrect) {
64 NSView* platformView = [[NSView alloc] init];
65 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
66
67 EXPECT_EQ(mutatorView.platformView, platformView);
68
69 std::vector<FlutterPlatformViewMutation> mutations{
70 {
71 .type = kFlutterPlatformViewMutationTypeTransformation,
72 .transformation =
73 FlutterTransformation{
74 .scaleX = 1,
75 .transX = 100,
76 .scaleY = 1,
77 .transY = 50,
78 },
79 },
80 };
81
82 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
83
84 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
85 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
86 EXPECT_EQ(mutatorView.pathClipViews.count, 0ull);
87 EXPECT_NE(mutatorView.platformViewContainer, nil);
88 }
89
90 TEST(FlutterMutatorViewTest, ClipsToBounds) {
91 NSView* platformView = [[NSView alloc] init];
92 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
93 EXPECT_TRUE(mutatorView.clipsToBounds);
94 }
95
96 TEST(FlutterMutatorViewTest, TransformedFrameIsCorrect) {
97 NSView* platformView = [[NSView alloc] init];
98 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
99 NSView* mutatorViewParent = [[NSView alloc] init];
100 mutatorViewParent.wantsLayer = YES;
101 mutatorViewParent.layer.contentsScale = 2.0;
102 [mutatorViewParent addSubview:mutatorView];
103
104 std::vector<FlutterPlatformViewMutation> mutations{
105 {
106 .type = kFlutterPlatformViewMutationTypeTransformation,
107 .transformation =
108 FlutterTransformation{
109 .scaleX = 2,
110 .scaleY = 2,
111 },
112 },
113 {
114 .type = kFlutterPlatformViewMutationTypeTransformation,
115 .transformation =
116 FlutterTransformation{
117 .scaleX = 1,
118 .transX = 100,
119 .scaleY = 1,
120 .transY = 50,
121 },
122 },
123 {
124 .type = kFlutterPlatformViewMutationTypeTransformation,
125 .transformation =
126 FlutterTransformation{
127 .scaleX = 1.5,
128 .transX = -7.5,
129 .scaleY = 1.5,
130 .transY = -5,
131 },
132 },
133 };
134
135 // PlatformView size form engine comes in physical pixels
136 ApplyFlutterLayer(mutatorView, FlutterSize{30 * 2, 20 * 2}, mutations);
137 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(92.5, 45, 45, 30)));
138 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
139
140 ExpectTransform3DEqual(mutatorView.platformViewContainer.layer.sublayerTransform,
141 CATransform3DMakeScale(1.5, 1.5, 1));
142 }
143
144 TEST(FlutterMutatorViewTest, FrameWithLooseClipIsCorrect) {
145 NSView* platformView = [[NSView alloc] init];
146 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
147
148 EXPECT_EQ(mutatorView.platformView, platformView);
149
150 std::vector<FlutterPlatformViewMutation> mutations{
151 {
152 .type = kFlutterPlatformViewMutationTypeClipRect,
153 .clip_rect = FlutterRect{80, 40, 200, 100},
154 },
155 {
156 .type = kFlutterPlatformViewMutationTypeTransformation,
157 .transformation =
158 FlutterTransformation{
159 .scaleX = 1,
160 .transX = 100,
161 .scaleY = 1,
162 .transY = 50,
163 },
164 },
165 };
166
167 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
168
169 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
170 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
171 }
172
173 TEST(FlutterMutatorViewTest, FrameWithTightClipIsCorrect) {
174 NSView* platformView = [[NSView alloc] init];
175 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
176
177 EXPECT_EQ(mutatorView.platformView, platformView);
178
179 std::vector<FlutterPlatformViewMutation> mutations{
180 {
181 .type = kFlutterPlatformViewMutationTypeClipRect,
182 .clip_rect = FlutterRect{80, 40, 200, 100},
183 },
184 {
185 .type = kFlutterPlatformViewMutationTypeClipRect,
186 .clip_rect = FlutterRect{110, 55, 120, 65},
187 },
188 {
189 .type = kFlutterPlatformViewMutationTypeTransformation,
190 .transformation =
191 FlutterTransformation{
192 .scaleX = 1,
193 .transX = 100,
194 .scaleY = 1,
195 .transY = 50,
196 },
197 },
198 };
199
200 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
201
202 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 55, 10, 10)));
203 EXPECT_TRUE(
204 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -5, 30, 20)));
205 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
206 }
207
208 TEST(FlutterMutatorViewTest, FrameWithTightClipAndTransformIsCorrect) {
209 NSView* platformView = [[NSView alloc] init];
210 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
211 NSView* mutatorViewParent = [[NSView alloc] init];
212 mutatorViewParent.wantsLayer = YES;
213 mutatorViewParent.layer.contentsScale = 2.0;
214 [mutatorViewParent addSubview:mutatorView];
215
216 std::vector<FlutterPlatformViewMutation> mutations{
217 {
218 .type = kFlutterPlatformViewMutationTypeTransformation,
219 .transformation =
220 FlutterTransformation{
221 .scaleX = 2,
222 .scaleY = 2,
223 },
224 },
225 {
226 .type = kFlutterPlatformViewMutationTypeClipRect,
227 .clip_rect = FlutterRect{80, 40, 200, 100},
228 },
229 {
230 .type = kFlutterPlatformViewMutationTypeClipRect,
231 .clip_rect = FlutterRect{110, 55, 120, 65},
232 },
233 {
234 .type = kFlutterPlatformViewMutationTypeTransformation,
235 .transformation =
236 FlutterTransformation{
237 .scaleX = 1,
238 .transX = 100,
239 .scaleY = 1,
240 .transY = 50,
241 },
242 },
243 {
244 .type = kFlutterPlatformViewMutationTypeTransformation,
245 .transformation =
246 FlutterTransformation{
247 .scaleX = 1.5,
248 .transX = -7.5,
249 .scaleY = 1.5,
250 .transY = -5,
251 },
252 },
253 };
254
255 ApplyFlutterLayer(mutatorView, FlutterSize{30 * 2, 20 * 2}, mutations);
256
257 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 55, 10, 10)));
258 EXPECT_TRUE(
259 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-17.5, -10, 45, 30)));
260 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
261 }
262
263 // Rounded rectangle without hitting the corner
264 TEST(FlutterMutatorViewTest, RoundRectClipsToSimpleRectangle) {
265 NSView* platformView = [[NSView alloc] init];
266 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
267
268 std::vector<FlutterPlatformViewMutation> mutations{
269 {
270 .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
271 .clip_rounded_rect =
272 FlutterRoundedRect{
273 .rect = FlutterRect{110, 30, 120, 90},
274 .upper_left_corner_radius = FlutterSize{10, 10},
275 .upper_right_corner_radius = FlutterSize{10, 10},
276 .lower_right_corner_radius = FlutterSize{10, 10},
277 .lower_left_corner_radius = FlutterSize{10, 10},
278 },
279 },
280 {
281 .type = kFlutterPlatformViewMutationTypeTransformation,
282 .transformation =
283 FlutterTransformation{
284 .scaleX = 1,
285 .transX = 100,
286 .scaleY = 1,
287 .transY = 50,
288 },
289 },
290 };
291
292 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
293
294 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 50, 10, 20)));
295 EXPECT_TRUE(
296 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, 0, 30, 20)));
297 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
298 EXPECT_EQ(mutatorView.pathClipViews.count, 0ul);
299 }
300
301 // Ensure that the mutator view, clip views, and container all use a flipped y axis. The transforms
302 // sent from the framework assume this, and so aside from the consistency with every other embedder,
303 // we can avoid a lot of extra math.
304 TEST(FlutterMutatorViewTest, ViewsSetIsFlipped) {
305 NSView* platformView = [[NSView alloc] init];
306 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
307
308 std::vector<FlutterPlatformViewMutation> mutations{
309 {
310 .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
311 .clip_rounded_rect =
312 FlutterRoundedRect{
313 .rect = FlutterRect{110, 60, 150, 150},
314 .upper_left_corner_radius = FlutterSize{10, 10},
315 .upper_right_corner_radius = FlutterSize{10, 10},
316 .lower_right_corner_radius = FlutterSize{10, 10},
317 .lower_left_corner_radius = FlutterSize{10, 10},
318 },
319 },
320 {
321 .type = kFlutterPlatformViewMutationTypeTransformation,
322 .transformation =
323 FlutterTransformation{
324 .scaleX = 1,
325 .transX = 100,
326 .scaleY = 1,
327 .transY = 50,
328 },
329 },
330 };
331
332 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
333
334 EXPECT_TRUE(mutatorView.isFlipped);
335 ASSERT_EQ(mutatorView.pathClipViews.count, 1ul);
336 EXPECT_TRUE(mutatorView.pathClipViews.firstObject.isFlipped);
337 EXPECT_TRUE(mutatorView.platformViewContainer.isFlipped);
338 }
339
340 TEST(FlutterMutatorViewTest, RectsClipsToPathWhenRotated) {
341 NSView* platformView = [[NSView alloc] init];
342 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
343 std::vector<FlutterPlatformViewMutation> mutations{
344 {
345 .type = kFlutterPlatformViewMutationTypeTransformation,
346 // Roation M_PI / 8
347 .transformation =
348 FlutterTransformation{
349 .scaleX = 0.9238795325112867,
350 .skewX = -0.3826834323650898,
351 .skewY = 0.3826834323650898,
352 .scaleY = 0.9238795325112867,
353 },
354 },
355 {
356 .type = kFlutterPlatformViewMutationTypeClipRect,
357 .clip_rect = FlutterRect{110, 60, 150, 150},
358 },
359 {
360 .type = kFlutterPlatformViewMutationTypeTransformation,
361 .transformation =
362 FlutterTransformation{
363 .scaleX = 1,
364 .transX = 100,
365 .scaleY = 1,
366 .transY = 50,
367 },
368 },
369 };
370 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
371 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
372 EXPECT_NEAR(mutatorView.platformViewContainer.frame.size.width, 35.370054622640396, kMaxErr);
373 EXPECT_NEAR(mutatorView.platformViewContainer.frame.size.height, 29.958093621178421, kMaxErr);
374 }
375
376 TEST(FlutterMutatorViewTest, RoundRectClipsToPath) {
377 NSView* platformView = [[NSView alloc] init];
378 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
379
380 std::vector<FlutterPlatformViewMutation> mutations{
381 {
382 .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
383 .clip_rounded_rect =
384 FlutterRoundedRect{
385 .rect = FlutterRect{110, 60, 150, 150},
386 .upper_left_corner_radius = FlutterSize{10, 10},
387 .upper_right_corner_radius = FlutterSize{10, 10},
388 .lower_right_corner_radius = FlutterSize{10, 10},
389 .lower_left_corner_radius = FlutterSize{10, 10},
390 },
391 },
392 {
393 .type = kFlutterPlatformViewMutationTypeTransformation,
394 .transformation =
395 FlutterTransformation{
396 .scaleX = 1,
397 .transX = 100,
398 .scaleY = 1,
399 .transY = 50,
400 },
401 },
402 };
403
404 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
405
406 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 60, 20, 10)));
407 EXPECT_TRUE(
408 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -10, 30, 20)));
409 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
410 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
411 ExpectTransform3DEqual(mutatorView.pathClipViews.firstObject.layer.mask.transform,
412 CATransform3DMakeTranslation(-100, -50, 0));
413 }
414
415 TEST(FlutterMutatorViewTest, PathClipViewsAreAddedAndRemoved) {
416 NSView* platformView = [[NSView alloc] init];
417 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
418
419 std::vector<FlutterPlatformViewMutation> mutations{
420 {
421 .type = kFlutterPlatformViewMutationTypeTransformation,
422 .transformation =
423 FlutterTransformation{
424 .scaleX = 1,
425 .transX = 100,
426 .scaleY = 1,
427 .transY = 50,
428 },
429 },
430 };
431
432 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
433
434 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
435 EXPECT_EQ(mutatorView.pathClipViews.count, 0ull);
436
437 std::vector<FlutterPlatformViewMutation> mutations2{
438 {
439 .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
440 .clip_rounded_rect =
441 FlutterRoundedRect{
442 .rect = FlutterRect{110, 60, 150, 150},
443 .upper_left_corner_radius = FlutterSize{10, 10},
444 .upper_right_corner_radius = FlutterSize{10, 10},
445 .lower_right_corner_radius = FlutterSize{10, 10},
446 .lower_left_corner_radius = FlutterSize{10, 10},
447 },
448 },
449 {
450 .type = kFlutterPlatformViewMutationTypeTransformation,
451 .transformation =
452 FlutterTransformation{
453 .scaleX = 1,
454 .transX = 100,
455 .scaleY = 1,
456 .transY = 50,
457 },
458 },
459 };
460
461 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations2);
462
463 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 60, 20, 10)));
464 EXPECT_TRUE(
465 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -10, 30, 20)));
466 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
467 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
468
469 EXPECT_EQ(platformView.superview, mutatorView.platformViewContainer);
470 EXPECT_EQ(mutatorView.platformViewContainer.superview, mutatorView.pathClipViews[0]);
471 EXPECT_EQ(mutatorView.pathClipViews[0].superview, mutatorView);
472
473 std::vector<FlutterPlatformViewMutation> mutations3{
474 {
475 .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
476 .clip_rounded_rect =
477 FlutterRoundedRect{
478 .rect = FlutterRect{110, 55, 150, 150},
479 .upper_left_corner_radius = FlutterSize{10, 10},
480 .upper_right_corner_radius = FlutterSize{10, 10},
481 .lower_right_corner_radius = FlutterSize{10, 10},
482 .lower_left_corner_radius = FlutterSize{10, 10},
483 },
484 },
485 {
486 .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
487 .clip_rounded_rect =
488 FlutterRoundedRect{
489 .rect = FlutterRect{30, 30, 120, 65},
490 .upper_left_corner_radius = FlutterSize{10, 10},
491 .upper_right_corner_radius = FlutterSize{10, 10},
492 .lower_right_corner_radius = FlutterSize{10, 10},
493 .lower_left_corner_radius = FlutterSize{10, 10},
494 },
495 },
496 {
497 .type = kFlutterPlatformViewMutationTypeTransformation,
498 .transformation =
499 FlutterTransformation{
500 .scaleX = 1,
501 .transX = 100,
502 .scaleY = 1,
503 .transY = 50,
504 },
505 },
506 };
507
508 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations3);
509
510 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 55, 10, 10)));
511 EXPECT_TRUE(
512 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -5, 30, 20)));
513 EXPECT_EQ(mutatorView.pathClipViews.count, 2ul);
514
515 EXPECT_EQ(platformView.superview, mutatorView.platformViewContainer);
516 EXPECT_EQ(mutatorView.platformViewContainer.superview, mutatorView.pathClipViews[1]);
517 EXPECT_EQ(mutatorView.pathClipViews[1].superview, mutatorView.pathClipViews[0]);
518 EXPECT_EQ(mutatorView.pathClipViews[0].superview, mutatorView);
519
520 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations2);
521
522 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 60, 20, 10)));
523 EXPECT_TRUE(
524 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -10, 30, 20)));
525 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
526 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
527
528 EXPECT_EQ(platformView.superview, mutatorView.platformViewContainer);
529 EXPECT_EQ(mutatorView.platformViewContainer.superview, mutatorView.pathClipViews[0]);
530 EXPECT_EQ(mutatorView.pathClipViews[0].superview, mutatorView);
531
532 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
533
534 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
535 EXPECT_EQ(mutatorView.pathClipViews.count, 0ull);
536 }
537
538 TEST(FlutterMutatorViewTest, HitTestIgnoreRegion) {
539 NSView* platformView = [[NSView alloc] init];
540 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
541 ApplyFlutterLayer(mutatorView, FlutterSize{100, 100}, {});
542 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 10)], platformView);
543 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 10)], platformView);
544
545 [mutatorView resetHitTestRegion];
546 [mutatorView addHitTestIgnoreRegion:CGRectMake(0, 0, 50, 50)];
547 [mutatorView addHitTestIgnoreRegion:CGRectMake(50, 50, 50, 50)];
548
549 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 10)], nil);
550 EXPECT_EQ([mutatorView hitTest:NSMakePoint(49, 10)], nil);
551 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 49)], nil);
552 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 50)], nil);
553 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 10)], platformView);
554 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 50)], platformView);
555
556 [mutatorView resetHitTestRegion];
557 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 10)], platformView);
558 EXPECT_EQ([mutatorView hitTest:NSMakePoint(49, 10)], platformView);
559 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 49)], platformView);
560 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 50)], platformView);
561 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 10)], platformView);
562 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 50)], platformView);
563 }
564
565 TEST(FlutterMutatorViewTest, ReparentingPlatformView) {
566 NSView* platformView = [[NSView alloc] init];
567 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
568 ApplyFlutterLayer(mutatorView, FlutterSize{100, 100}, {});
569 EXPECT_TRUE(platformView.superview == mutatorView.platformViewContainer);
570 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 100, 100)));
571
572 // Reparent platform view and replace it with placeholder (mimicking WKWebKit going full screen)
573 NSView* newParent = [[NSView alloc] init];
574 [newParent addSubview:platformView];
575 platformView.frame = CGRectMake(10, 10, 200, 200);
576
577 NSView* placeholderView = [[NSView alloc] init];
578 [mutatorView.platformViewContainer addSubview:placeholderView];
579 ApplyFlutterLayer(mutatorView, FlutterSize{100, 100}, {});
580
581 // Platform view should not be touched but the replacement view should be properly positioned.
582 EXPECT_TRUE(platformView.superview == newParent);
583 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(10, 10, 200, 200)));
584 EXPECT_TRUE(CGRectEqualToRect(placeholderView.frame, CGRectMake(0, 0, 100, 100)));
585 }
586
587 @interface FlutterCursorCoordinatorTest : NSObject
588
589 @end
590
591 @implementation FlutterCursorCoordinatorTest
592 - (void)testCoordinatorEventWithinFlutterContent {
593 id flutterView = OCMClassMock([FlutterView class]);
594 FlutterCursorCoordinator* coordinator =
595 [[FlutterCursorCoordinator alloc] initWithFlutterView:flutterView];
596 {
597 id platformView = OCMClassMock([NSView class]);
598 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
599 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
600 OCMStub([mutatorView platformView]).andReturn(platformView);
601 CGPoint location = NSMakePoint(50, 50);
602 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
603 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
604 location:location
605 modifierFlags:0
606 timestamp:0
607 windowNumber:0
608 context:nil
609 eventNumber:0
610 clickCount:0
611 pressure:0];
612 [coordinator processMouseMoveEvent:event
613 forMutatorView:mutatorView
614 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
615 OCMVerify([flutterView cursorUpdate:event]);
616 }
617 {
618 id platformView = OCMClassMock([NSView class]);
619 // Make sure once event is handled the coordinator will not send cursorUpdate again.
620 OCMReject([flutterView cursorUpdate:[OCMArg any]]);
621 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
622 OCMStub([mutatorView platformView]).andReturn(platformView);
623 CGPoint location = NSMakePoint(50, 50);
624 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
625 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
626 location:location
627 modifierFlags:0
628 timestamp:0
629 windowNumber:0
630 context:nil
631 eventNumber:0
632 clickCount:0
633 pressure:0];
634 [coordinator processMouseMoveEvent:event
635 forMutatorView:mutatorView
636 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
637 }
638 EXPECT_TRUE(coordinator.cleanupScheduled);
639 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
640 EXPECT_FALSE(coordinator.cleanupScheduled);
641 }
642
643 - (void)testCoordinatorEventOutsideFlutterContent {
644 id flutterView = OCMClassMock([FlutterView class]);
645 OCMReject([flutterView cursorUpdate:[OCMArg any]]);
646 FlutterCursorCoordinator* coordinator =
647 [[FlutterCursorCoordinator alloc] initWithFlutterView:flutterView];
648 id platformViewWindow = OCMClassMock([NSWindow class]);
649 {
650 id platformView = OCMClassMock([NSView class]);
651 OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
652 OCMStub([platformView window]).andReturn(platformViewWindow);
653 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
654 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
655 OCMStub([mutatorView platformView]).andReturn(platformView);
656 CGPoint location = NSMakePoint(150, 150);
657 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
658 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
659 location:location
660 modifierFlags:0
661 timestamp:0
662 windowNumber:0
663 context:nil
664 eventNumber:0
665 clickCount:0
666 pressure:0];
667 [coordinator processMouseMoveEvent:event
668 forMutatorView:mutatorView
669 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
670 OCMVerify([platformViewWindow invalidateCursorRectsForView:platformView]);
671 }
672 {
673 // Make sure this is not called again for subsequent invocation during same run loop turn.
674 OCMReject([platformViewWindow invalidateCursorRectsForView:[OCMArg any]]);
675
676 id platformView = OCMClassMock([NSView class]);
677 OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
678 OCMStub([platformView window]).andReturn(platformViewWindow);
679 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
680 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
681 OCMStub([mutatorView platformView]).andReturn(platformView);
682 CGPoint location = NSMakePoint(150, 150);
683 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
684 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
685 location:location
686 modifierFlags:0
687 timestamp:0
688 windowNumber:0
689 context:nil
690 eventNumber:0
691 clickCount:0
692 pressure:0];
693 [coordinator processMouseMoveEvent:event
694 forMutatorView:mutatorView
695 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
696 }
697 EXPECT_TRUE(coordinator.cleanupScheduled);
698 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
699 EXPECT_FALSE(coordinator.cleanupScheduled);
700
701 // Check that invalidateCursorRectsForView is called again
702 platformViewWindow = OCMClassMock([NSWindow class]);
703 {
704 id platformView = OCMClassMock([NSView class]);
705 OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
706 OCMStub([platformView window]).andReturn(platformViewWindow);
707 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
708 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
709 OCMStub([mutatorView platformView]).andReturn(platformView);
710 CGPoint location = NSMakePoint(150, 150);
711 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
712 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
713 location:location
714 modifierFlags:0
715 timestamp:0
716 windowNumber:0
717 context:nil
718 eventNumber:0
719 clickCount:0
720 pressure:0];
721 [coordinator processMouseMoveEvent:event
722 forMutatorView:mutatorView
723 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
724 OCMVerify([platformViewWindow invalidateCursorRectsForView:platformView]);
725 }
726 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
727 }
728 @end
729
730 TEST(FlutterMutatorViewTest, CursorCoordinator) {
731 [[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventWithinFlutterContent];
732 [[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventOutsideFlutterContent];
733 }
FlutterMutatorView.h
kMaxErr
static constexpr float kMaxErr
Definition: FlutterMutatorViewTest.mm:20
TEST
TEST(FlutterMutatorViewTest, BasicFrameIsCorrect)
Definition: FlutterMutatorViewTest.mm:63
FlutterView.h
NSView+ClipsToBounds.h
FlutterMutatorView(Private)
Definition: FlutterMutatorViewTest.mm:13
FlutterMutatorView(Private)::platformViewContainer
NSView * platformViewContainer
Definition: FlutterMutatorViewTest.mm:16
FlutterMutatorView(Private)::pathClipViews
NSMutableArray< NSView * > * pathClipViews
Definition: FlutterMutatorViewTest.mm:15
flutter::PlatformViewLayer
Represents a platform view layer, including all mutations.
Definition: FlutterMutatorView.h:16
FlutterCursorCoordinator
Definition: FlutterMutatorView.h:46
-[FlutterCursorCoordinator processMouseMoveEvent:forMutatorView:overlayRegion:]
void processMouseMoveEvent:forMutatorView:overlayRegion:(nonnull NSEvent *event,[forMutatorView] nonnull FlutterMutatorView *view,[overlayRegion] const std::vector< CGRect > &region)
FlutterCursorCoordinator::cleanupScheduled
BOOL cleanupScheduled
Definition: FlutterMutatorView.h:54
FlutterCursorCoordinatorTest
Definition: FlutterMutatorViewTest.mm:588
FlutterMutatorView
Definition: FlutterMutatorView.h:64
FlutterMutatorView::platformView
NSView * platformView
Returns wrapped platform view.
Definition: FlutterMutatorView.h:72
-[FlutterMutatorView resetHitTestRegion]
void resetHitTestRegion()
Resets hit hit testing region for this mutator view.
Definition: FlutterMutatorView.mm:526
-[FlutterMutatorView applyFlutterLayer:]
void applyFlutterLayer:(nonnull const flutter::PlatformViewLayer *layer)
-[FlutterMutatorView addHitTestIgnoreRegion:]
void addHitTestIgnoreRegion:(CGRect region)
Definition: FlutterMutatorView.mm:530
FlutterView
Definition: FlutterView.h:36