LayoutManagerTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Avalonia.Controls;
  4. using Avalonia.Layout;
  5. using Avalonia.Threading;
  6. using Xunit;
  7. namespace Avalonia.Base.UnitTests.Layout
  8. {
  9. public class LayoutManagerTests
  10. {
  11. [Fact]
  12. public void Measures_And_Arranges_InvalidateMeasured_Control()
  13. {
  14. var control = new LayoutTestControl();
  15. var root = new LayoutTestRoot { Child = control };
  16. root.LayoutManager.ExecuteInitialLayoutPass();
  17. control.Measured = control.Arranged = false;
  18. control.InvalidateMeasure();
  19. root.LayoutManager.ExecuteLayoutPass();
  20. Assert.True(control.Measured);
  21. Assert.True(control.Arranged);
  22. }
  23. [Fact]
  24. public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_TopLevel_Is_Not_Visible()
  25. {
  26. var control = new LayoutTestControl();
  27. var root = new LayoutTestRoot { Child = control, IsVisible = false };
  28. root.LayoutManager.ExecuteInitialLayoutPass();
  29. control.Measured = control.Arranged = false;
  30. control.InvalidateMeasure();
  31. root.LayoutManager.ExecuteLayoutPass();
  32. Assert.False(control.Measured);
  33. Assert.False(control.Arranged);
  34. }
  35. [Fact]
  36. public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_Ancestor_Is_Not_Visible()
  37. {
  38. var control = new LayoutTestControl();
  39. var parent = new Decorator { Child = control };
  40. var root = new LayoutTestRoot { Child = parent };
  41. root.LayoutManager.ExecuteInitialLayoutPass();
  42. control.Measured = control.Arranged = false;
  43. parent.IsVisible = false;
  44. control.InvalidateMeasure();
  45. root.LayoutManager.ExecuteLayoutPass();
  46. Assert.False(control.Measured);
  47. Assert.False(control.Arranged);
  48. }
  49. [Fact]
  50. public void Lays_Out_Descendents_That_Were_Invalidated_While_Ancestor_Was_Not_Visible()
  51. {
  52. // Issue #11076
  53. var control = new LayoutTestControl();
  54. var parent = new Decorator { Child = control };
  55. var grandparent = new Decorator { Child = parent };
  56. var root = new LayoutTestRoot { Child = grandparent };
  57. root.LayoutManager.ExecuteInitialLayoutPass();
  58. grandparent.IsVisible = false;
  59. control.InvalidateMeasure();
  60. root.LayoutManager.ExecuteInitialLayoutPass();
  61. grandparent.IsVisible = true;
  62. root.LayoutManager.ExecuteLayoutPass();
  63. Assert.True(control.IsMeasureValid);
  64. Assert.True(control.IsArrangeValid);
  65. }
  66. [Fact]
  67. public void Arranges_InvalidateArranged_Control()
  68. {
  69. var control = new LayoutTestControl();
  70. var root = new LayoutTestRoot { Child = control };
  71. root.LayoutManager.ExecuteInitialLayoutPass();
  72. control.Measured = control.Arranged = false;
  73. control.InvalidateArrange();
  74. root.LayoutManager.ExecuteLayoutPass();
  75. Assert.False(control.Measured);
  76. Assert.True(control.Arranged);
  77. }
  78. [Fact]
  79. public void Measures_Parent_Of_Newly_Added_Control()
  80. {
  81. var control = new LayoutTestControl();
  82. var root = new LayoutTestRoot();
  83. root.LayoutManager.ExecuteInitialLayoutPass();
  84. root.Child = control;
  85. root.Measured = root.Arranged = false;
  86. root.LayoutManager.ExecuteLayoutPass();
  87. Assert.True(root.Measured);
  88. Assert.True(root.Arranged);
  89. Assert.True(control.Measured);
  90. Assert.True(control.Arranged);
  91. }
  92. [Fact]
  93. public void Measures_In_Correct_Order()
  94. {
  95. LayoutTestControl control1;
  96. LayoutTestControl control2;
  97. var root = new LayoutTestRoot
  98. {
  99. Child = control1 = new LayoutTestControl
  100. {
  101. Child = control2 = new LayoutTestControl(),
  102. }
  103. };
  104. var order = new List<Layoutable>();
  105. Size MeasureOverride(Layoutable control, Size size)
  106. {
  107. order.Add(control);
  108. return new Size(10, 10);
  109. }
  110. root.DoMeasureOverride = MeasureOverride;
  111. control1.DoMeasureOverride = MeasureOverride;
  112. control2.DoMeasureOverride = MeasureOverride;
  113. root.LayoutManager.ExecuteInitialLayoutPass();
  114. control2.InvalidateMeasure();
  115. control1.InvalidateMeasure();
  116. root.InvalidateMeasure();
  117. order.Clear();
  118. root.LayoutManager.ExecuteLayoutPass();
  119. Assert.Equal(new Layoutable[] { root, control1, control2 }, order);
  120. }
  121. [Fact]
  122. public void Measures_Root_And_Grandparent_In_Correct_Order()
  123. {
  124. LayoutTestControl control1;
  125. LayoutTestControl control2;
  126. var root = new LayoutTestRoot
  127. {
  128. Child = control1 = new LayoutTestControl
  129. {
  130. Child = control2 = new LayoutTestControl(),
  131. }
  132. };
  133. var order = new List<Layoutable>();
  134. Size MeasureOverride(Layoutable control, Size size)
  135. {
  136. order.Add(control);
  137. return new Size(10, 10);
  138. }
  139. root.DoMeasureOverride = MeasureOverride;
  140. control1.DoMeasureOverride = MeasureOverride;
  141. control2.DoMeasureOverride = MeasureOverride;
  142. root.LayoutManager.ExecuteInitialLayoutPass();
  143. control2.InvalidateMeasure();
  144. root.InvalidateMeasure();
  145. order.Clear();
  146. root.LayoutManager.ExecuteLayoutPass();
  147. Assert.Equal(new Layoutable[] { root, control2 }, order);
  148. }
  149. [Fact]
  150. public void Doesnt_Measure_Non_Invalidated_Root()
  151. {
  152. var control = new LayoutTestControl();
  153. var root = new LayoutTestRoot { Child = control };
  154. root.LayoutManager.ExecuteInitialLayoutPass();
  155. root.Measured = root.Arranged = false;
  156. control.Measured = control.Arranged = false;
  157. control.InvalidateMeasure();
  158. root.LayoutManager.ExecuteLayoutPass();
  159. Assert.False(root.Measured);
  160. Assert.False(root.Arranged);
  161. Assert.True(control.Measured);
  162. Assert.True(control.Arranged);
  163. }
  164. [Fact]
  165. public void Doesnt_Measure_Removed_Control()
  166. {
  167. var control = new LayoutTestControl();
  168. var root = new LayoutTestRoot { Child = control };
  169. root.LayoutManager.ExecuteInitialLayoutPass();
  170. control.Measured = control.Arranged = false;
  171. control.InvalidateMeasure();
  172. root.Child = null;
  173. root.LayoutManager.ExecuteLayoutPass();
  174. Assert.False(control.Measured);
  175. Assert.False(control.Arranged);
  176. }
  177. [Fact]
  178. public void Measures_Root_With_Infinity()
  179. {
  180. var root = new LayoutTestRoot();
  181. var availableSize = default(Size);
  182. // Should not measure with this size.
  183. root.MaxClientSize = new Size(123, 456);
  184. root.DoMeasureOverride = (_, s) =>
  185. {
  186. availableSize = s;
  187. return new Size(100, 100);
  188. };
  189. root.LayoutManager.ExecuteInitialLayoutPass();
  190. Assert.Equal(Size.Infinity, availableSize);
  191. }
  192. [Fact]
  193. public void Arranges_Root_With_DesiredSize()
  194. {
  195. var root = new LayoutTestRoot
  196. {
  197. Width = 100,
  198. Height = 100,
  199. };
  200. var arrangeSize = default(Size);
  201. root.DoArrangeOverride = (_, s) =>
  202. {
  203. arrangeSize = s;
  204. return s;
  205. };
  206. root.LayoutManager.ExecuteInitialLayoutPass();
  207. Assert.Equal(new Size(100, 100), arrangeSize);
  208. root.Width = 120;
  209. root.LayoutManager.ExecuteLayoutPass();
  210. Assert.Equal(new Size(120, 100), arrangeSize);
  211. }
  212. [Fact]
  213. public void Invalidating_Child_Remeasures_Parent()
  214. {
  215. Border border;
  216. StackPanel panel;
  217. var root = new LayoutTestRoot
  218. {
  219. Child = panel = new StackPanel
  220. {
  221. Children =
  222. {
  223. (border = new Border())
  224. }
  225. }
  226. };
  227. root.LayoutManager.ExecuteInitialLayoutPass();
  228. Assert.Equal(new Size(0, 0), root.DesiredSize);
  229. border.Width = 100;
  230. border.Height = 100;
  231. root.LayoutManager.ExecuteLayoutPass();
  232. Assert.Equal(new Size(100, 100), panel.DesiredSize);
  233. }
  234. [Fact]
  235. public void LayoutManager_Should_Prevent_Infinite_Loop_On_Measure()
  236. {
  237. var control = new LayoutTestControl();
  238. var root = new LayoutTestRoot { Child = control };
  239. root.LayoutManager.ExecuteInitialLayoutPass();
  240. control.Measured = false;
  241. int cnt = 0;
  242. int maxcnt = 100;
  243. control.DoMeasureOverride = (l, s) =>
  244. {
  245. //emulate a problem in the logic of a control that triggers
  246. //invalidate measure during measure
  247. //it can lead to an infinite loop in layoutmanager
  248. if (++cnt < maxcnt)
  249. {
  250. control.InvalidateMeasure();
  251. }
  252. return new Size(100, 100);
  253. };
  254. control.InvalidateMeasure();
  255. root.LayoutManager.ExecuteLayoutPass();
  256. Assert.True(cnt < 100);
  257. }
  258. [Fact]
  259. public void LayoutManager_Should_Prevent_Infinite_Loop_On_Arrange()
  260. {
  261. var control = new LayoutTestControl();
  262. var root = new LayoutTestRoot { Child = control };
  263. root.LayoutManager.ExecuteInitialLayoutPass();
  264. control.Arranged = false;
  265. int cnt = 0;
  266. int maxcnt = 100;
  267. control.DoArrangeOverride = (l, s) =>
  268. {
  269. //emulate a problem in the logic of a control that triggers
  270. //invalidate measure during arrange
  271. //it can lead to infinity loop in layoutmanager
  272. if (++cnt < maxcnt)
  273. {
  274. control.InvalidateArrange();
  275. }
  276. return new Size(100, 100);
  277. };
  278. control.InvalidateArrange();
  279. root.LayoutManager.ExecuteLayoutPass();
  280. Assert.True(cnt < 100);
  281. }
  282. [Fact]
  283. public void LayoutManager_Should_Properly_Arrange_Visuals_Even_When_There_Are_Issues_With_Previous_Arranged()
  284. {
  285. var nonArrageableTargets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
  286. var targets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
  287. StackPanel panel;
  288. var root = new LayoutTestRoot
  289. {
  290. Child = panel = new StackPanel()
  291. };
  292. panel.Children.AddRange(nonArrageableTargets);
  293. panel.Children.AddRange(targets);
  294. root.LayoutManager.ExecuteInitialLayoutPass();
  295. foreach (var c in panel.Children.OfType<LayoutTestControl>())
  296. {
  297. c.Measured = c.Arranged = false;
  298. c.InvalidateMeasure();
  299. }
  300. foreach (var c in nonArrageableTargets)
  301. {
  302. c.DoArrangeOverride = (l, s) =>
  303. {
  304. //emulate a problem in the logic of a control that triggers
  305. //invalidate measure during arrange
  306. c.InvalidateMeasure();
  307. return new Size(100, 100);
  308. };
  309. }
  310. root.LayoutManager.ExecuteLayoutPass();
  311. //altough nonArrageableTargets has rubbish logic and can't be measured/arranged properly
  312. //layoutmanager should process properly other visuals
  313. Assert.All(targets, c => Assert.True(c.Arranged));
  314. }
  315. [Fact]
  316. public void LayoutManager_Should_Recover_From_Infinite_Loop_On_Measure()
  317. {
  318. // Test for issue #3041.
  319. var control = new LayoutTestControl();
  320. var root = new LayoutTestRoot { Child = control };
  321. root.LayoutManager.ExecuteInitialLayoutPass();
  322. control.Measured = false;
  323. control.DoMeasureOverride = (l, s) =>
  324. {
  325. control.InvalidateMeasure();
  326. return new Size(100, 100);
  327. };
  328. control.InvalidateMeasure();
  329. root.LayoutManager.ExecuteLayoutPass();
  330. // This is the important part: running a second layout pass in which we exceed the maximum
  331. // retries causes LayoutQueue<T>.Info.Count to exceed _maxEnqueueCountPerLoop.
  332. root.LayoutManager.ExecuteLayoutPass();
  333. control.Measured = false;
  334. control.DoMeasureOverride = null;
  335. root.LayoutManager.ExecuteLayoutPass();
  336. Assert.True(control.Measured);
  337. Assert.True(control.IsMeasureValid);
  338. }
  339. [Fact]
  340. public void Calling_ExecuteLayoutPass_From_ExecuteInitialLayoutPass_Does_Not_Break_Measure()
  341. {
  342. // Test for issue #3550.
  343. var control = new LayoutTestControl();
  344. var root = new LayoutTestRoot { Child = control };
  345. var count = 0;
  346. root.LayoutManager.ExecuteInitialLayoutPass();
  347. control.Measured = false;
  348. control.DoMeasureOverride = (l, s) =>
  349. {
  350. if (count++ == 0)
  351. {
  352. control.InvalidateMeasure();
  353. root.LayoutManager.ExecuteLayoutPass();
  354. return new Size(100, 100);
  355. }
  356. else
  357. {
  358. return new Size(200, 200);
  359. }
  360. };
  361. root.InvalidateMeasure();
  362. control.InvalidateMeasure();
  363. root.LayoutManager.ExecuteInitialLayoutPass();
  364. Assert.Equal(new Size(200, 200), control.Bounds.Size);
  365. Assert.Equal(new Size(200, 200), control.DesiredSize);
  366. }
  367. [Fact]
  368. public void LayoutManager_Execute_Layout_Pass_Should_Clear_Queued_LayoutPasses()
  369. {
  370. var control = new LayoutTestControl();
  371. var root = new LayoutTestRoot { Child = control };
  372. int layoutCount = 0;
  373. root.LayoutUpdated += (_, _) => layoutCount++;
  374. root.LayoutManager.InvalidateArrange(control);
  375. root.LayoutManager.ExecuteInitialLayoutPass();
  376. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  377. Assert.Equal(1, layoutCount);
  378. }
  379. [Fact]
  380. public void Child_Can_Invalidate_Parent_Measure_During_Arrange()
  381. {
  382. // Issue #11015.
  383. //
  384. // - Child invalidates parent measure in arrange pass
  385. // - Parent is added to measure & arrange queues
  386. // - Arrange pass dequeues parent
  387. // - Measure is not valid so parent is not arranged
  388. // - Parent is measured
  389. // - Parent has been dequeued from arrange queue so no arrange is performed
  390. var child = new LayoutTestControl();
  391. var parent = new LayoutTestControl { Child = child };
  392. var root = new LayoutTestRoot { Child = parent };
  393. root.LayoutManager.ExecuteInitialLayoutPass();
  394. child.DoArrangeOverride = (_, s) =>
  395. {
  396. parent.InvalidateMeasure();
  397. return s;
  398. };
  399. child.InvalidateMeasure();
  400. parent.InvalidateMeasure();
  401. root.LayoutManager.ExecuteLayoutPass();
  402. Assert.True(child.IsMeasureValid);
  403. Assert.True(child.IsArrangeValid);
  404. Assert.True(parent.IsMeasureValid);
  405. Assert.True(parent.IsArrangeValid);
  406. }
  407. }
  408. }