LayoutManagerTests.cs 19 KB

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