ScrollContentPresenterTests.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reactive.Linq;
  4. using Avalonia.Controls.Presenters;
  5. using Avalonia.Layout;
  6. using Avalonia.UnitTests;
  7. using Xunit;
  8. using Xunit.Sdk;
  9. namespace Avalonia.Controls.UnitTests.Presenters
  10. {
  11. public class ScrollContentPresenterTests
  12. {
  13. [Theory]
  14. [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Stretch, 10, 10, 80, 80)]
  15. [InlineData(HorizontalAlignment.Left, VerticalAlignment.Stretch, 10, 10, 16, 80)]
  16. [InlineData(HorizontalAlignment.Right, VerticalAlignment.Stretch, 74, 10, 16, 80)]
  17. [InlineData(HorizontalAlignment.Center, VerticalAlignment.Stretch, 42, 10, 16, 80)]
  18. [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Top, 10, 10, 80, 16)]
  19. [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Bottom, 10, 74, 80, 16)]
  20. [InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Center, 10, 42, 80, 16)]
  21. public void Alignment_And_Padding_Are_Applied_To_Child_Bounds(
  22. HorizontalAlignment h,
  23. VerticalAlignment v,
  24. double expectedX,
  25. double expectedY,
  26. double expectedWidth,
  27. double expectedHeight)
  28. {
  29. Border content;
  30. var target = new ScrollContentPresenter
  31. {
  32. Padding = new Thickness(10),
  33. Content = content = new Border
  34. {
  35. MinWidth = 16,
  36. MinHeight = 16,
  37. HorizontalAlignment = h,
  38. VerticalAlignment = v,
  39. },
  40. };
  41. target.UpdateChild();
  42. target.Measure(new Size(100, 100));
  43. target.Arrange(new Rect(0, 0, 100, 100));
  44. Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), content.Bounds);
  45. }
  46. [Fact]
  47. public void DesiredSize_Is_Content_Size_When_Smaller_Than_AvailableSize()
  48. {
  49. var target = new ScrollContentPresenter
  50. {
  51. Padding = new Thickness(10),
  52. Content = new Border
  53. {
  54. MinWidth = 16,
  55. MinHeight = 16,
  56. },
  57. };
  58. target.UpdateChild();
  59. target.Measure(new Size(100, 100));
  60. target.Arrange(new Rect(0, 0, 100, 100));
  61. Assert.Equal(new Size(16, 16), target.DesiredSize);
  62. }
  63. [Fact]
  64. public void DesiredSize_Is_AvailableSize_When_Content_Larger_Than_AvailableSize()
  65. {
  66. var target = new ScrollContentPresenter
  67. {
  68. Padding = new Thickness(10),
  69. Content = new Border
  70. {
  71. MinWidth = 160,
  72. MinHeight = 160,
  73. },
  74. };
  75. target.UpdateChild();
  76. target.Measure(new Size(100, 100));
  77. target.Arrange(new Rect(0, 0, 100, 100));
  78. Assert.Equal(new Size(100, 100), target.DesiredSize);
  79. }
  80. [Fact]
  81. public void Content_Can_Be_Larger_Than_Viewport()
  82. {
  83. TestControl content;
  84. var target = new ScrollContentPresenter
  85. {
  86. CanHorizontallyScroll = true,
  87. CanVerticallyScroll = true,
  88. Content = content = new TestControl(),
  89. };
  90. target.UpdateChild();
  91. target.Measure(new Size(100, 100));
  92. target.Arrange(new Rect(0, 0, 100, 100));
  93. Assert.Equal(new Rect(0, 0, 150, 150), content.Bounds);
  94. }
  95. [Fact]
  96. public void Content_Can_Be_Offset()
  97. {
  98. Border content;
  99. var target = new ScrollContentPresenter
  100. {
  101. CanHorizontallyScroll = true,
  102. CanVerticallyScroll = true,
  103. Content = content = new Border
  104. {
  105. Width = 150,
  106. Height = 150,
  107. },
  108. };
  109. target.UpdateChild();
  110. target.Measure(new Size(100, 100));
  111. target.Arrange(new Rect(0, 0, 100, 100));
  112. target.Offset = new Vector(25, 25);
  113. target.Measure(new Size(100, 100));
  114. target.Arrange(new Rect(0, 0, 100, 100));
  115. Assert.Equal(new Rect(-25, -25, 150, 150), content.Bounds);
  116. }
  117. [Fact]
  118. public void Measure_Should_Pass_Bounded_X_If_CannotScrollHorizontally()
  119. {
  120. var child = new TestControl();
  121. var target = new ScrollContentPresenter
  122. {
  123. CanVerticallyScroll = true,
  124. Content = child,
  125. };
  126. target.UpdateChild();
  127. target.Measure(new Size(100, 100));
  128. Assert.Equal(new Size(100, double.PositiveInfinity), child.AvailableSize);
  129. }
  130. [Fact]
  131. public void Measure_Should_Pass_Unbounded_X_If_CanScrollHorizontally()
  132. {
  133. var child = new TestControl();
  134. var target = new ScrollContentPresenter
  135. {
  136. CanHorizontallyScroll = true,
  137. CanVerticallyScroll = true,
  138. Content = child,
  139. };
  140. target.UpdateChild();
  141. target.Measure(new Size(100, 100));
  142. Assert.Equal(Size.Infinity, child.AvailableSize);
  143. }
  144. [Fact]
  145. public void Arrange_Should_Set_Viewport_And_Extent_In_That_Order()
  146. {
  147. var target = new ScrollContentPresenter
  148. {
  149. Content = new Border { Width = 40, Height = 50 }
  150. };
  151. var set = new List<string>();
  152. target.UpdateChild();
  153. target.Measure(new Size(100, 100));
  154. target.GetObservable(ScrollViewer.ViewportProperty).Skip(1).Subscribe(_ => set.Add("Viewport"));
  155. target.GetObservable(ScrollViewer.ExtentProperty).Skip(1).Subscribe(_ => set.Add("Extent"));
  156. target.Arrange(new Rect(0, 0, 100, 100));
  157. Assert.Equal(new[] { "Viewport", "Extent" }, set);
  158. }
  159. [Fact]
  160. public void Should_Correctly_Arrange_Child_Larger_Than_Viewport()
  161. {
  162. var child = new Canvas { MinWidth = 150, MinHeight = 150 };
  163. var target = new ScrollContentPresenter { Content = child, };
  164. target.UpdateChild();
  165. target.Measure(Size.Infinity);
  166. target.Arrange(new Rect(0, 0, 100, 100));
  167. Assert.Equal(new Size(150, 150), child.Bounds.Size);
  168. }
  169. [Fact]
  170. public void Arrange_Should_Constrain_Child_Width_When_CanHorizontallyScroll_False()
  171. {
  172. var child = new WrapPanel
  173. {
  174. Children =
  175. {
  176. new Border { Width = 40, Height = 50 },
  177. new Border { Width = 40, Height = 50 },
  178. new Border { Width = 40, Height = 50 },
  179. }
  180. };
  181. var target = new ScrollContentPresenter
  182. {
  183. Content = child,
  184. CanHorizontallyScroll = false,
  185. };
  186. target.UpdateChild();
  187. target.Measure(Size.Infinity);
  188. target.Arrange(new Rect(0, 0, 100, 100));
  189. Assert.Equal(100, child.Bounds.Width);
  190. }
  191. [Fact]
  192. public void Extent_Should_Include_Content_Margin()
  193. {
  194. var target = new ScrollContentPresenter
  195. {
  196. Content = new Border
  197. {
  198. Width = 100,
  199. Height = 100,
  200. Margin = new Thickness(5),
  201. }
  202. };
  203. target.UpdateChild();
  204. target.Measure(new Size(50, 50));
  205. target.Arrange(new Rect(0, 0, 50, 50));
  206. Assert.Equal(new Size(110, 110), target.Extent);
  207. }
  208. [Fact]
  209. public void Extent_Should_Include_Content_Margin_Scaled_With_Layout_Rounding()
  210. {
  211. var root = new TestRoot
  212. {
  213. LayoutScaling = 1.25,
  214. UseLayoutRounding = true
  215. };
  216. var target = new ScrollContentPresenter
  217. {
  218. HorizontalAlignment = HorizontalAlignment.Center,
  219. VerticalAlignment = VerticalAlignment.Center,
  220. Content = new Border
  221. {
  222. Width = 200,
  223. Height = 200,
  224. Margin = new Thickness(2)
  225. }
  226. };
  227. root.Child = target;
  228. target.UpdateChild();
  229. target.Measure(new Size(1000, 1000));
  230. target.Arrange(new Rect(0, 0, 1000, 1000));
  231. Assert.Equal(new Size(203.2, 203.2), target.Viewport);
  232. Assert.Equal(new Size(203.2, 203.2), target.Extent);
  233. }
  234. [Fact]
  235. public void Extent_Should_Be_Rounded_To_Viewport_When_Close()
  236. {
  237. var root = new TestRoot
  238. {
  239. LayoutScaling = 1.75,
  240. UseLayoutRounding = true
  241. };
  242. var target = new ScrollContentPresenter
  243. {
  244. HorizontalAlignment = HorizontalAlignment.Center,
  245. VerticalAlignment = VerticalAlignment.Center,
  246. Content = new Border
  247. {
  248. Width = 164.57142857142858,
  249. Height = 164.57142857142858,
  250. Margin = new Thickness(6)
  251. }
  252. };
  253. root.Child = target;
  254. target.UpdateChild();
  255. target.Measure(new Size(1000, 1000));
  256. target.Arrange(new Rect(0, 0, 1000, 1000));
  257. var nonRoundedVieViewport = target.Child!.Bounds.Size.Inflate(
  258. LayoutHelper.RoundLayoutThickness(target.Child.Margin, root.LayoutScaling));
  259. Assert.Equal(new Size(176.00000000000003, 176.00000000000003), nonRoundedVieViewport);
  260. Assert.Equal(new Size(176, 176), target.Viewport);
  261. Assert.Equal(new Size(176, 176), target.Extent);
  262. }
  263. [Fact]
  264. public void Extent_Width_Should_Be_Arrange_Width_When_CanScrollHorizontally_False()
  265. {
  266. var child = new WrapPanel
  267. {
  268. Children =
  269. {
  270. new Border { Width = 40, Height = 50 },
  271. new Border { Width = 40, Height = 50 },
  272. new Border { Width = 40, Height = 50 },
  273. }
  274. };
  275. var target = new ScrollContentPresenter
  276. {
  277. Content = child,
  278. CanHorizontallyScroll = false,
  279. };
  280. target.UpdateChild();
  281. target.Measure(Size.Infinity);
  282. target.Arrange(new Rect(0, 0, 100, 100));
  283. Assert.Equal(new Size(100, 100), target.Extent);
  284. }
  285. [Fact]
  286. public void Setting_Offset_Should_Invalidate_Arrange()
  287. {
  288. var target = new ScrollContentPresenter
  289. {
  290. Content = new Border { Width = 140, Height = 150 }
  291. };
  292. target.UpdateChild();
  293. target.Measure(new Size(100, 100));
  294. target.Arrange(new Rect(0, 0, 100, 100));
  295. target.Offset = new Vector(10, 100);
  296. Assert.True(target.IsMeasureValid);
  297. Assert.False(target.IsArrangeValid);
  298. }
  299. [Fact]
  300. public void BringDescendantIntoView_Should_Update_Offset()
  301. {
  302. var target = new ScrollContentPresenter
  303. {
  304. Width = 100,
  305. Height = 100,
  306. Content = new Border
  307. {
  308. Width = 200,
  309. Height = 200,
  310. }
  311. };
  312. target.UpdateChild();
  313. target.Measure(Size.Infinity);
  314. target.Arrange(new Rect(0, 0, 100, 100));
  315. target.BringDescendantIntoView(target.Child!, new Rect(200, 200, 0, 0));
  316. Assert.Equal(new Vector(100, 100), target.Offset);
  317. }
  318. [Fact]
  319. public void BringDescendantIntoView_Should_Handle_Child_Margin()
  320. {
  321. Border border;
  322. var target = new ScrollContentPresenter
  323. {
  324. CanHorizontallyScroll = true,
  325. CanVerticallyScroll = true,
  326. Width = 100,
  327. Height = 100,
  328. Content = new Decorator
  329. {
  330. Margin = new Thickness(50),
  331. Child = border = new Border
  332. {
  333. Width = 200,
  334. Height = 200,
  335. }
  336. }
  337. };
  338. target.UpdateChild();
  339. target.Measure(Size.Infinity);
  340. target.Arrange(new Rect(0, 0, 100, 100));
  341. target.BringDescendantIntoView(border, new Rect(200, 200, 0, 0));
  342. Assert.Equal(new Vector(150, 150), target.Offset);
  343. }
  344. [Fact]
  345. public void BringDescendantIntoView_Should_Not_Move_Child_If_Completely_In_View()
  346. {
  347. Border border = new Border
  348. {
  349. Width = 100,
  350. Height = 20
  351. };
  352. var content = new StackPanel()
  353. {
  354. Orientation = Orientation.Vertical,
  355. Width = 100,
  356. };
  357. for(int i = 0; i < 100; i++)
  358. {
  359. // border position will be (0,60)
  360. var child = i == 3 ? border : new Border
  361. {
  362. Width = 100,
  363. Height = 20,
  364. };
  365. content.Children.Add(child);
  366. }
  367. var target = new ScrollContentPresenter
  368. {
  369. CanHorizontallyScroll = true,
  370. CanVerticallyScroll = true,
  371. Width = 200,
  372. Height = 100,
  373. Content = new Decorator
  374. {
  375. Child = content
  376. }
  377. };
  378. target.UpdateChild();
  379. target.Measure(Size.Infinity);
  380. target.Arrange(new Rect(0, 0, 100, 100));
  381. target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
  382. Assert.Equal(new Vector(0, 0), target.Offset);
  383. }
  384. [Fact]
  385. public void BringDescendantIntoView_Should_Move_Child_At_Least_Partially_Above_Viewport()
  386. {
  387. Border border = new Border
  388. {
  389. Width = 100,
  390. Height = 20
  391. };
  392. var content = new StackPanel()
  393. {
  394. Orientation = Orientation.Vertical,
  395. Width = 100,
  396. };
  397. for(int i = 0; i < 100; i++)
  398. {
  399. // border position will be (0,60)
  400. var child = i == 3 ? border : new Border
  401. {
  402. Width = 100,
  403. Height = 20,
  404. };
  405. content.Children.Add(child);
  406. }
  407. var target = new ScrollContentPresenter
  408. {
  409. CanHorizontallyScroll = true,
  410. CanVerticallyScroll = true,
  411. Width = 200,
  412. Height = 100,
  413. Content = new Decorator
  414. {
  415. Child = content
  416. }
  417. };
  418. target.UpdateChild();
  419. target.Measure(Size.Infinity);
  420. target.Arrange(new Rect(0, 0, 100, 100));
  421. // move border to above the view port
  422. target.Offset = new Vector(0, 90);
  423. target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
  424. Assert.Equal(new Vector(0, 60), target.Offset);
  425. // move border to partially above the view port
  426. target.Offset = new Vector(0, 70);
  427. target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
  428. Assert.Equal(new Vector(0, 60), target.Offset);
  429. }
  430. [Fact]
  431. public void BringDescendantIntoView_Should_Not_Move_Child_If_Completely_Covers_Viewport()
  432. {
  433. Border border = new Border
  434. {
  435. Width = 100,
  436. Height = 200
  437. };
  438. var content = new StackPanel()
  439. {
  440. Orientation = Orientation.Vertical,
  441. Width = 100,
  442. };
  443. for (int i = 0; i < 100; i++)
  444. {
  445. // border position will be (0,60)
  446. var child = i == 3 ? border : new Border
  447. {
  448. Width = 100,
  449. Height = 20,
  450. };
  451. content.Children.Add(child);
  452. }
  453. var target = new ScrollContentPresenter
  454. {
  455. CanHorizontallyScroll = true,
  456. CanVerticallyScroll = true,
  457. Width = 200,
  458. Height = 100,
  459. Content = new Decorator
  460. {
  461. Child = content
  462. }
  463. };
  464. target.UpdateChild();
  465. target.Measure(Size.Infinity);
  466. target.Arrange(new Rect(0, 0, 100, 100));
  467. // move border such that it's partially above viewport and partially below viewport
  468. target.Offset = new Vector(0, 90);
  469. target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
  470. Assert.Equal(new Vector(0, 90), target.Offset);
  471. }
  472. [Fact]
  473. public void BringDescendantIntoView_Should_Move_Child_At_Least_Partially_Below_Viewport()
  474. {
  475. Border border = new Border
  476. {
  477. Width = 100,
  478. Height = 20
  479. };
  480. var content = new StackPanel()
  481. {
  482. Orientation = Orientation.Vertical,
  483. Width = 100,
  484. };
  485. for (int i = 0; i < 100; i++)
  486. {
  487. // border position will be (0,180)
  488. var child = i == 9 ? border : new Border
  489. {
  490. Width = 100,
  491. Height = 20,
  492. };
  493. content.Children.Add(child);
  494. }
  495. var target = new ScrollContentPresenter
  496. {
  497. CanHorizontallyScroll = true,
  498. CanVerticallyScroll = true,
  499. Width = 200,
  500. Height = 100,
  501. Content = new Decorator
  502. {
  503. Child = content
  504. }
  505. };
  506. target.UpdateChild();
  507. target.Measure(Size.Infinity);
  508. target.Arrange(new Rect(0, 0, 100, 100));
  509. // border is at (0, 180) and below the viewport
  510. target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
  511. Assert.Equal(new Vector(0, 100), target.Offset);
  512. // move border to partially below the view port
  513. target.Offset = new Vector(0, 90);
  514. target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
  515. }
  516. [Fact]
  517. public void Nested_Presenters_Should_Scroll_Outer_When_Content_Exceeds_Viewport()
  518. {
  519. ScrollContentPresenter innerPresenter;
  520. Border border;
  521. var outerPresenter = new ScrollContentPresenter
  522. {
  523. CanHorizontallyScroll = true,
  524. CanVerticallyScroll = true,
  525. Width = 100,
  526. Height = 100,
  527. Content = innerPresenter = new ScrollContentPresenter
  528. {
  529. CanHorizontallyScroll = true,
  530. CanVerticallyScroll = true,
  531. Width = 100,
  532. Height = 200,
  533. Content = border = new Border
  534. {
  535. Width = 200, // larger than viewport
  536. Height = 25,
  537. HorizontalAlignment = HorizontalAlignment.Left,
  538. VerticalAlignment = VerticalAlignment.Top,
  539. Margin = new Thickness(0, 120, 0, 0)
  540. }
  541. }
  542. };
  543. innerPresenter.UpdateChild();
  544. outerPresenter.UpdateChild();
  545. outerPresenter.Measure(new Size(100, 100));
  546. outerPresenter.Arrange(new Rect(0, 0, 100, 100));
  547. border.BringIntoView();
  548. Assert.Equal(new Vector(0, 45), outerPresenter.Offset);
  549. Assert.Equal(new Vector(0, 0), innerPresenter.Offset);
  550. }
  551. [Fact]
  552. public void Nested_Presenters_Should_Scroll_Outer_When_Viewports_Are_Close()
  553. {
  554. ScrollContentPresenter innerPresenter;
  555. Border border;
  556. var outerPresenter = new ScrollContentPresenter
  557. {
  558. CanHorizontallyScroll = true,
  559. CanVerticallyScroll = true,
  560. Width = 100,
  561. Height = 170.0568181818182,
  562. UseLayoutRounding = false,
  563. Content = innerPresenter = new ScrollContentPresenter
  564. {
  565. CanHorizontallyScroll = true,
  566. CanVerticallyScroll = true,
  567. Width = 100,
  568. Height = 493.2613636363636,
  569. UseLayoutRounding = false,
  570. Content = new StackPanel
  571. {
  572. Children =
  573. {
  574. new Border
  575. {
  576. Height = 455.31818181818187,
  577. UseLayoutRounding = false
  578. },
  579. (border = new Border {
  580. Width = 100,
  581. Height = 37.94318181818182,
  582. UseLayoutRounding = false
  583. })
  584. }
  585. }
  586. }
  587. };
  588. innerPresenter.UpdateChild();
  589. outerPresenter.UpdateChild();
  590. outerPresenter.Measure(new Size(100, 170.0568181818182));
  591. outerPresenter.Arrange(new Rect(0, 0, 100, 170.0568181818182));
  592. border.BringIntoView();
  593. Assert.Equal(new Vector(0, 323.20454545454544), outerPresenter.Offset);
  594. Assert.Equal(new Vector(0, 0), innerPresenter.Offset);
  595. }
  596. private class TestControl : Control
  597. {
  598. public Size AvailableSize { get; private set; }
  599. protected override Size MeasureOverride(Size availableSize)
  600. {
  601. AvailableSize = availableSize;
  602. return new Size(150, 150);
  603. }
  604. }
  605. }
  606. }