app_drag_icon_proxy_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // Copyright 2021 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. #include "ash/app_list/views/app_drag_icon_proxy.h"
  5. #include <memory>
  6. #include "ash/public/cpp/image_util.h"
  7. #include "ash/shell.h"
  8. #include "ash/test/ash_test_base.h"
  9. #include "base/test/bind.h"
  10. #include "ui/compositor/layer.h"
  11. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  12. #include "ui/compositor/scoped_layer_animation_settings.h"
  13. #include "ui/gfx/geometry/rect_conversions.h"
  14. #include "ui/gfx/geometry/rounded_corners_f.h"
  15. #include "ui/gfx/geometry/transform.h"
  16. #include "ui/views/widget/widget.h"
  17. namespace ash {
  18. class AppDragIconProxyTest : public AshTestBase {
  19. public:
  20. AppDragIconProxyTest() = default;
  21. AppDragIconProxyTest(const AppDragIconProxyTest&) = delete;
  22. AppDragIconProxyTest& operator=(const AppDragIconProxyTest&) = delete;
  23. ~AppDragIconProxyTest() override = default;
  24. gfx::Rect GetTargetAppDragIconImageBounds(AppDragIconProxy* drag_icon_proxy) {
  25. gfx::RectF layer_target_position(
  26. drag_icon_proxy->GetImageLayerForTesting()->GetTargetBounds());
  27. drag_icon_proxy->GetImageLayerForTesting()
  28. ->GetTargetTransform()
  29. .TransformRect(&layer_target_position);
  30. gfx::Rect widget_target_bounds =
  31. drag_icon_proxy->GetWidgetForTesting()->GetLayer()->GetTargetBounds();
  32. layer_target_position.set_origin(
  33. gfx::PointF(layer_target_position.x() + widget_target_bounds.x(),
  34. layer_target_position.y() + widget_target_bounds.y()));
  35. return gfx::ToNearestRect(layer_target_position);
  36. }
  37. };
  38. TEST_F(AppDragIconProxyTest, UpdatingLocationRespectsIconOffset) {
  39. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  40. Shell::GetPrimaryRootWindow(),
  41. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  42. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  43. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  44. /*scale_factor=*/1.0f,
  45. /*use_blurred_background=*/false);
  46. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  47. drag_icon_proxy->GetBoundsInScreen());
  48. drag_icon_proxy->UpdatePosition(gfx::Point(150, 200));
  49. EXPECT_EQ(gfx::Rect(gfx::Point(115, 155), gfx::Size(50, 50)),
  50. drag_icon_proxy->GetBoundsInScreen());
  51. drag_icon_proxy->UpdatePosition(gfx::Point(150, 250));
  52. EXPECT_EQ(gfx::Rect(gfx::Point(115, 205), gfx::Size(50, 50)),
  53. drag_icon_proxy->GetBoundsInScreen());
  54. drag_icon_proxy->UpdatePosition(gfx::Point(120, 270));
  55. EXPECT_EQ(gfx::Rect(gfx::Point(85, 225), gfx::Size(50, 50)),
  56. drag_icon_proxy->GetBoundsInScreen());
  57. }
  58. TEST_F(AppDragIconProxyTest, SecondaryDisplay) {
  59. UpdateDisplay("1000x800,1000x800");
  60. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  61. Shell::GetRootWindowForDisplayId(GetSecondaryDisplay().id()),
  62. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  63. /*pointer_location_in_screen=*/gfx::Point(1100, 200),
  64. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  65. /*scale_factor=*/1.0f,
  66. /*use_blurred_background=*/false);
  67. EXPECT_EQ(gfx::Rect(gfx::Point(1065, 155), gfx::Size(50, 50)),
  68. drag_icon_proxy->GetBoundsInScreen());
  69. drag_icon_proxy->UpdatePosition(gfx::Point(1150, 200));
  70. EXPECT_EQ(gfx::Rect(gfx::Point(1115, 155), gfx::Size(50, 50)),
  71. drag_icon_proxy->GetBoundsInScreen());
  72. drag_icon_proxy->UpdatePosition(gfx::Point(1150, 250));
  73. EXPECT_EQ(gfx::Rect(gfx::Point(1115, 205), gfx::Size(50, 50)),
  74. drag_icon_proxy->GetBoundsInScreen());
  75. drag_icon_proxy->UpdatePosition(gfx::Point(1120, 270));
  76. EXPECT_EQ(gfx::Rect(gfx::Point(1085, 225), gfx::Size(50, 50)),
  77. drag_icon_proxy->GetBoundsInScreen());
  78. }
  79. TEST_F(AppDragIconProxyTest, ScaledBounds) {
  80. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  81. Shell::GetPrimaryRootWindow(),
  82. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  83. /*pointer_location_in_screen=*/gfx::Point(200, 400),
  84. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  85. /*scale_factor=*/2.0f,
  86. /*use_blurred_background=*/false);
  87. EXPECT_EQ(gfx::Rect(gfx::Point(140, 330), gfx::Size(100, 100)),
  88. drag_icon_proxy->GetBoundsInScreen());
  89. drag_icon_proxy->UpdatePosition(gfx::Point(150, 300));
  90. EXPECT_EQ(gfx::Rect(gfx::Point(90, 230), gfx::Size(100, 100)),
  91. drag_icon_proxy->GetBoundsInScreen());
  92. }
  93. TEST_F(AppDragIconProxyTest, BlurSetsRoundedCorners) {
  94. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  95. Shell::GetPrimaryRootWindow(),
  96. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  97. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  98. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  99. /*scale_factor=*/1.0f,
  100. /*use_blurred_background=*/true);
  101. // The background should be circular.
  102. EXPECT_EQ(gfx::RoundedCornersF(25.0f).ToString(),
  103. drag_icon_proxy->GetImageLayerForTesting()
  104. ->rounded_corner_radii()
  105. .ToString());
  106. EXPECT_EQ(30.0f,
  107. drag_icon_proxy->GetImageLayerForTesting()->background_blur());
  108. // Test that background corner radii are scaled with the image.
  109. drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  110. Shell::GetPrimaryRootWindow(),
  111. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  112. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  113. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  114. /*scale_factor=*/2.0f,
  115. /*use_blurred_background=*/true);
  116. // The background should be circular.
  117. EXPECT_EQ(gfx::RoundedCornersF(50.0f).ToString(),
  118. drag_icon_proxy->GetImageLayerForTesting()
  119. ->rounded_corner_radii()
  120. .ToString());
  121. EXPECT_EQ(30.0f,
  122. drag_icon_proxy->GetImageLayerForTesting()->background_blur());
  123. }
  124. TEST_F(AppDragIconProxyTest, AnimateBoundsForClosure) {
  125. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  126. Shell::GetPrimaryRootWindow(),
  127. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  128. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  129. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  130. /*scale_factor=*/1.0f,
  131. /*use_blurred_background=*/false);
  132. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  133. drag_icon_proxy->GetBoundsInScreen());
  134. ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
  135. ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  136. base::RunLoop run_loop;
  137. gfx::Rect target_bounds = gfx::Rect(gfx::Point(50, 50), gfx::Size(10, 10));
  138. drag_icon_proxy->AnimateToBoundsAndCloseWidget(target_bounds,
  139. run_loop.QuitClosure());
  140. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  141. drag_icon_proxy->GetBoundsInScreen());
  142. EXPECT_EQ(target_bounds,
  143. GetTargetAppDragIconImageBounds(drag_icon_proxy.get()));
  144. // Verify the animation callback gets called.
  145. run_loop.Run();
  146. // The widget should be closed at this point - verify public methods do no
  147. // crash if used.
  148. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  149. drag_icon_proxy->UpdatePosition(gfx::Point(20, 20));
  150. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  151. drag_icon_proxy->SetOpacity(1.0f);
  152. }
  153. TEST_F(AppDragIconProxyTest, CloseAnimationCallbackCalledWithZeroAnimation) {
  154. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  155. Shell::GetPrimaryRootWindow(),
  156. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  157. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  158. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  159. /*scale_factor=*/1.0f,
  160. /*use_blurred_background=*/false);
  161. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  162. drag_icon_proxy->GetBoundsInScreen());
  163. ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
  164. ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
  165. bool animation_callback_called = false;
  166. gfx::Rect target_bounds = gfx::Rect(gfx::Point(50, 50), gfx::Size(10, 10));
  167. drag_icon_proxy->AnimateToBoundsAndCloseWidget(
  168. target_bounds, base::BindLambdaForTesting([&animation_callback_called]() {
  169. animation_callback_called = true;
  170. }));
  171. EXPECT_TRUE(animation_callback_called);
  172. // The widget should be closed at this point - verify public methods do no
  173. // crash if used.
  174. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  175. drag_icon_proxy->UpdatePosition(gfx::Point(20, 20));
  176. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  177. drag_icon_proxy->SetOpacity(1.0f);
  178. }
  179. // Tests the timing of animation callback if app drag icon is deleted while
  180. // another layer animation is in progress.
  181. TEST_F(AppDragIconProxyTest,
  182. CloseAnimationInterruptsAnotherLayerTransformAnimation) {
  183. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  184. Shell::GetPrimaryRootWindow(),
  185. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  186. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  187. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  188. /*scale_factor=*/1.0f,
  189. /*use_blurred_background=*/false);
  190. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  191. drag_icon_proxy->GetBoundsInScreen());
  192. ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
  193. ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  194. // Animate the drag image layer transform, so
  195. // `AnimateToBoundsAndCloseWidget()` interrupts an animation.
  196. {
  197. ui::ScopedLayerAnimationSettings animation_settings(
  198. drag_icon_proxy->GetImageLayerForTesting()->GetAnimator());
  199. animation_settings.SetTransitionDuration(base::Milliseconds(300));
  200. gfx::Transform transform;
  201. transform.Translate(100, 100);
  202. drag_icon_proxy->GetImageLayerForTesting()->SetTransform(transform);
  203. }
  204. base::RunLoop run_loop;
  205. bool animation_callback_called = false;
  206. gfx::Rect target_bounds = gfx::Rect(gfx::Point(50, 50), gfx::Size(10, 10));
  207. drag_icon_proxy->AnimateToBoundsAndCloseWidget(
  208. target_bounds,
  209. base::BindLambdaForTesting([&animation_callback_called, &run_loop]() {
  210. animation_callback_called = true;
  211. run_loop.Quit();
  212. }));
  213. // The animation callback should not yet been called, as the close animation
  214. // has not finished.
  215. ASSERT_FALSE(animation_callback_called);
  216. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  217. drag_icon_proxy->GetBoundsInScreen());
  218. EXPECT_EQ(target_bounds,
  219. GetTargetAppDragIconImageBounds(drag_icon_proxy.get()));
  220. // Wait for the layer animation to complete.
  221. run_loop.Run();
  222. // The widget should be closed at this point - verify public methods do no
  223. // crash if used.
  224. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  225. drag_icon_proxy->UpdatePosition(gfx::Point(20, 20));
  226. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  227. drag_icon_proxy->SetOpacity(1.0f);
  228. }
  229. // Verifies that "close" animation completion callback gets called if the proxy
  230. // is reset during the animation.
  231. TEST_F(AppDragIconProxyTest, ProxyResetDuringCloseAnimation) {
  232. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  233. Shell::GetPrimaryRootWindow(),
  234. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  235. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  236. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  237. /*scale_factor=*/1.0f,
  238. /*use_blurred_background=*/false);
  239. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  240. drag_icon_proxy->GetBoundsInScreen());
  241. ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
  242. ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  243. bool animation_callback_called = false;
  244. gfx::Rect target_bounds = gfx::Rect(gfx::Point(50, 50), gfx::Size(10, 10));
  245. drag_icon_proxy->AnimateToBoundsAndCloseWidget(
  246. target_bounds, base::BindLambdaForTesting([&animation_callback_called]() {
  247. animation_callback_called = true;
  248. }));
  249. EXPECT_FALSE(animation_callback_called);
  250. drag_icon_proxy.reset();
  251. EXPECT_TRUE(animation_callback_called);
  252. }
  253. // Tests that calls to `UpdatePosition()` are ignored if
  254. // `AnimateToBoundsAndCloseWidget()` has been called.
  255. TEST_F(AppDragIconProxyTest, UpdatePositionDuringCloseIsNoOp) {
  256. auto drag_icon_proxy = std::make_unique<AppDragIconProxy>(
  257. Shell::GetPrimaryRootWindow(),
  258. ash::image_util::CreateEmptyImage(gfx::Size(50, 50)),
  259. /*pointer_location_in_screen=*/gfx::Point(100, 200),
  260. /*pointer_offset_from_center=*/gfx::Vector2d(10, 20),
  261. /*scale_factor=*/1.0f,
  262. /*use_blurred_background=*/false);
  263. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  264. drag_icon_proxy->GetBoundsInScreen());
  265. ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
  266. ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  267. base::RunLoop run_loop;
  268. gfx::Rect target_bounds = gfx::Rect(gfx::Point(50, 50), gfx::Size(10, 10));
  269. drag_icon_proxy->AnimateToBoundsAndCloseWidget(target_bounds,
  270. run_loop.QuitClosure());
  271. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  272. drag_icon_proxy->GetBoundsInScreen());
  273. EXPECT_EQ(target_bounds,
  274. GetTargetAppDragIconImageBounds(drag_icon_proxy.get()));
  275. // Updating location should not work at this point.
  276. drag_icon_proxy->UpdatePosition(gfx::Point(200, 300));
  277. EXPECT_EQ(gfx::Rect(gfx::Point(65, 155), gfx::Size(50, 50)),
  278. drag_icon_proxy->GetBoundsInScreen());
  279. EXPECT_EQ(target_bounds,
  280. GetTargetAppDragIconImageBounds(drag_icon_proxy.get()));
  281. // Verify the animation callback gets called.
  282. run_loop.Run();
  283. // The widget should be closed at this point - verify public methods do no
  284. // crash if used.
  285. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  286. drag_icon_proxy->UpdatePosition(gfx::Point(20, 20));
  287. EXPECT_EQ(gfx::Rect(), drag_icon_proxy->GetBoundsInScreen());
  288. drag_icon_proxy->SetOpacity(1.0f);
  289. }
  290. } // namespace ash