PopupTests.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  1. using System;
  2. using System.Collections.Specialized;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using Moq;
  6. using Avalonia.Controls.Presenters;
  7. using Avalonia.Controls.Primitives;
  8. using Avalonia.Controls.Primitives.PopupPositioning;
  9. using Avalonia.Controls.Templates;
  10. using Avalonia.Layout;
  11. using Avalonia.LogicalTree;
  12. using Avalonia.Platform;
  13. using Avalonia.Styling;
  14. using Avalonia.UnitTests;
  15. using Avalonia.VisualTree;
  16. using Xunit;
  17. using Avalonia.Input;
  18. using Avalonia.Rendering;
  19. using System.Threading.Tasks;
  20. using Avalonia.Threading;
  21. namespace Avalonia.Controls.UnitTests.Primitives
  22. {
  23. public class PopupTests
  24. {
  25. protected bool UsePopupHost;
  26. [Fact]
  27. public void Popup_Open_Without_Target_Should_Attach_Itself_Later()
  28. {
  29. using (CreateServices())
  30. {
  31. int openedEvent = 0;
  32. var target = new Popup();
  33. target.Opened += (s, a) => openedEvent++;
  34. target.IsOpen = true;
  35. var window = PreparedWindow(target);
  36. window.Show();
  37. Assert.Equal(1, openedEvent);
  38. }
  39. }
  40. [Fact]
  41. public void Popup_Without_TopLevel_Shouldnt_Call_Open()
  42. {
  43. int openedEvent = 0;
  44. var target = new Popup();
  45. target.Opened += (s, a) => openedEvent++;
  46. target.IsOpen = true;
  47. Assert.Equal(0, openedEvent);
  48. }
  49. [Fact]
  50. public void Opening_Popup_Shouldnt_Throw_When_Not_In_Visual_Tree()
  51. {
  52. var target = new Popup();
  53. target.IsOpen = true;
  54. }
  55. [Fact]
  56. public void Opening_Popup_Shouldnt_Throw_When_In_Tree_Without_TopLevel()
  57. {
  58. Control c = new Control();
  59. var target = new Popup();
  60. ((ISetLogicalParent)target).SetParent(c);
  61. target.IsOpen = true;
  62. }
  63. [Fact]
  64. public void Setting_Child_Should_Set_Child_Controls_LogicalParent()
  65. {
  66. var target = new Popup();
  67. var child = new Control();
  68. target.Child = child;
  69. Assert.Equal(child.Parent, target);
  70. Assert.Equal(((ILogical)child).LogicalParent, target);
  71. }
  72. [Fact]
  73. public void Clearing_Child_Should_Clear_Child_Controls_Parent()
  74. {
  75. var target = new Popup();
  76. var child = new Control();
  77. target.Child = child;
  78. target.Child = null;
  79. Assert.Null(child.Parent);
  80. Assert.Null(((ILogical)child).LogicalParent);
  81. }
  82. [Fact]
  83. public void Child_Control_Should_Appear_In_LogicalChildren()
  84. {
  85. var target = new Popup();
  86. var child = new Control();
  87. target.Child = child;
  88. Assert.Equal(new[] { child }, target.GetLogicalChildren());
  89. }
  90. [Fact]
  91. public void Clearing_Child_Should_Remove_From_LogicalChildren()
  92. {
  93. var target = new Popup();
  94. var child = new Control();
  95. target.Child = child;
  96. target.Child = null;
  97. Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList());
  98. }
  99. [Fact]
  100. public void Setting_Child_Should_Fire_LogicalChildren_CollectionChanged()
  101. {
  102. var target = new Popup();
  103. var child = new Control();
  104. var called = false;
  105. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  106. called = e.Action == NotifyCollectionChangedAction.Add;
  107. target.Child = child;
  108. Assert.True(called);
  109. }
  110. [Fact]
  111. public void Clearing_Child_Should_Fire_LogicalChildren_CollectionChanged()
  112. {
  113. var target = new Popup();
  114. var child = new Control();
  115. var called = false;
  116. target.Child = child;
  117. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  118. called = e.Action == NotifyCollectionChangedAction.Remove;
  119. target.Child = null;
  120. Assert.True(called);
  121. }
  122. [Fact]
  123. public void Changing_Child_Should_Fire_LogicalChildren_CollectionChanged()
  124. {
  125. var target = new Popup();
  126. var child1 = new Control();
  127. var child2 = new Control();
  128. var called = false;
  129. target.Child = child1;
  130. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
  131. target.Child = child2;
  132. Assert.True(called);
  133. }
  134. [Fact]
  135. public void Setting_Child_Should_Not_Set_Childs_VisualParent()
  136. {
  137. var target = new Popup();
  138. var child = new Control();
  139. target.Child = child;
  140. Assert.Null(((IVisual)child).VisualParent);
  141. }
  142. [Fact]
  143. public void PopupRoot_Should_Initially_Be_Null()
  144. {
  145. using (CreateServices())
  146. {
  147. var target = new Popup();
  148. Assert.Null(((Visual)target.Host));
  149. }
  150. }
  151. [Fact]
  152. public void PopupRoot_Should_Have_Popup_As_LogicalParent()
  153. {
  154. using (CreateServices())
  155. {
  156. var target = new Popup() {PlacementTarget = PreparedWindow()};
  157. target.Open();
  158. Assert.Equal(target, ((Visual)target.Host).Parent);
  159. Assert.Equal(target, ((Visual)target.Host).GetLogicalParent());
  160. }
  161. }
  162. [Fact]
  163. public void PopupRoot_Should_Be_Detached_From_Logical_Tree_When_Popup_Is_Detached()
  164. {
  165. using (CreateServices())
  166. {
  167. var target = new Popup() {PlacementMode = PlacementMode.Pointer};
  168. var root = PreparedWindow(target);
  169. target.Open();
  170. var popupRoot = (ILogical)((Visual)target.Host);
  171. Assert.True(popupRoot.IsAttachedToLogicalTree);
  172. root.Content = null;
  173. Assert.False(((ILogical)target).IsAttachedToLogicalTree);
  174. }
  175. }
  176. [Fact]
  177. public void Popup_Open_Should_Raise_Single_Opened_Event()
  178. {
  179. using (CreateServices())
  180. {
  181. var window = PreparedWindow();
  182. var target = new Popup() {PlacementMode = PlacementMode.Pointer};
  183. window.Content = target;
  184. int openedCount = 0;
  185. target.Opened += (sender, args) =>
  186. {
  187. openedCount++;
  188. };
  189. target.Open();
  190. Assert.Equal(1, openedCount);
  191. }
  192. }
  193. [Fact]
  194. public void Popup_Close_Should_Raise_Single_Closed_Event()
  195. {
  196. using (CreateServices())
  197. {
  198. var window = PreparedWindow();
  199. var target = new Popup() {PlacementMode = PlacementMode.Pointer};
  200. window.Content = target;
  201. window.ApplyTemplate();
  202. target.Open();
  203. int closedCount = 0;
  204. target.Closed += (sender, args) =>
  205. {
  206. closedCount++;
  207. };
  208. target.Close();
  209. Assert.Equal(1, closedCount);
  210. }
  211. }
  212. [Fact]
  213. public void Popup_Close_On_Closed_Popup_Should_Not_Raise_Closed_Event()
  214. {
  215. using (CreateServices())
  216. {
  217. var window = PreparedWindow();
  218. var target = new Popup() { PlacementMode = PlacementMode.Pointer };
  219. window.Content = target;
  220. window.ApplyTemplate();
  221. int closedCount = 0;
  222. target.Closed += (sender, args) =>
  223. {
  224. closedCount++;
  225. };
  226. target.Close();
  227. target.Close();
  228. target.Close();
  229. target.Close();
  230. Assert.Equal(0, closedCount);
  231. }
  232. }
  233. [Fact]
  234. public void ContentControl_With_Popup_In_Template_Should_Set_TemplatedParent()
  235. {
  236. // Test uses OverlayPopupHost default template
  237. using (CreateServices())
  238. {
  239. PopupContentControl target;
  240. var root = PreparedWindow(target = new PopupContentControl
  241. {
  242. Content = new Border(),
  243. Template = new FuncControlTemplate<PopupContentControl>(PopupContentControlTemplate),
  244. });
  245. root.Show();
  246. target.ApplyTemplate();
  247. var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
  248. popup.Open();
  249. var popupRoot = (Control)popup.Host;
  250. popupRoot.Measure(Size.Infinity);
  251. popupRoot.Arrange(new Rect(popupRoot.DesiredSize));
  252. var children = popupRoot.GetVisualDescendants().ToList();
  253. var types = children.Select(x => x.GetType().Name).ToList();
  254. if (UsePopupHost)
  255. {
  256. Assert.Equal(
  257. new[]
  258. {
  259. "LayoutTransformControl",
  260. "VisualLayerManager",
  261. "ContentPresenter",
  262. "ContentPresenter",
  263. "Border",
  264. },
  265. types);
  266. }
  267. else
  268. {
  269. Assert.Equal(
  270. new[]
  271. {
  272. "LayoutTransformControl",
  273. "Panel",
  274. "Border",
  275. "VisualLayerManager",
  276. "ContentPresenter",
  277. "ContentPresenter",
  278. "Border",
  279. },
  280. types);
  281. }
  282. var templatedParents = children
  283. .OfType<IControl>()
  284. .Select(x => x.TemplatedParent).ToList();
  285. if (UsePopupHost)
  286. {
  287. Assert.Equal(
  288. new object[]
  289. {
  290. popupRoot,
  291. popupRoot,
  292. popupRoot,
  293. target,
  294. null,
  295. },
  296. templatedParents);
  297. }
  298. else
  299. {
  300. Assert.Equal(
  301. new object[]
  302. {
  303. popupRoot,
  304. popupRoot,
  305. popupRoot,
  306. popupRoot,
  307. popupRoot,
  308. target,
  309. null,
  310. },
  311. templatedParents);
  312. }
  313. }
  314. }
  315. [Fact]
  316. public void ItemsControl_With_Popup_In_Template_Should_Set_TemplatedParent()
  317. {
  318. // Test uses OverlayPopupHost default template
  319. using (CreateServices())
  320. {
  321. PopupItemsControl target;
  322. var item = new Border();
  323. var root = PreparedWindow(target = new PopupItemsControl
  324. {
  325. Items = new[] { item },
  326. Template = new FuncControlTemplate<PopupItemsControl>(PopupItemsControlTemplate),
  327. }); ;
  328. root.Show();
  329. target.ApplyTemplate();
  330. var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
  331. popup.Open();
  332. var popupRoot = (Control)popup.Host;
  333. popupRoot.Measure(Size.Infinity);
  334. popupRoot.Arrange(new Rect(popupRoot.DesiredSize));
  335. var children = popupRoot.GetVisualDescendants().ToList();
  336. var types = children.Select(x => x.GetType().Name).ToList();
  337. if (UsePopupHost)
  338. {
  339. Assert.Equal(
  340. new[]
  341. {
  342. "LayoutTransformControl",
  343. "VisualLayerManager",
  344. "ContentPresenter",
  345. "ItemsPresenter",
  346. "StackPanel",
  347. "Border",
  348. },
  349. types);
  350. }
  351. else
  352. {
  353. Assert.Equal(
  354. new[]
  355. {
  356. "LayoutTransformControl",
  357. "Panel",
  358. "Border",
  359. "VisualLayerManager",
  360. "ContentPresenter",
  361. "ItemsPresenter",
  362. "StackPanel",
  363. "Border",
  364. },
  365. types);
  366. }
  367. var templatedParents = children
  368. .OfType<IControl>()
  369. .Select(x => x.TemplatedParent).ToList();
  370. if (UsePopupHost)
  371. {
  372. Assert.Equal(
  373. new object[]
  374. {
  375. popupRoot,
  376. popupRoot,
  377. popupRoot,
  378. target,
  379. target,
  380. null,
  381. },
  382. templatedParents);
  383. }
  384. else
  385. {
  386. Assert.Equal(
  387. new object[]
  388. {
  389. popupRoot,
  390. popupRoot,
  391. popupRoot,
  392. popupRoot,
  393. popupRoot,
  394. target,
  395. target,
  396. null,
  397. },
  398. templatedParents);
  399. }
  400. }
  401. }
  402. [Fact]
  403. public void Should_Not_Overwrite_TemplatedParent_Of_Item_In_ItemsControl_With_Popup_On_Second_Open()
  404. {
  405. // Test uses OverlayPopupHost default template
  406. using (CreateServices())
  407. {
  408. PopupItemsControl target;
  409. var item = new Border();
  410. var root = PreparedWindow(target = new PopupItemsControl
  411. {
  412. Items = new[] { item },
  413. Template = new FuncControlTemplate<PopupItemsControl>(PopupItemsControlTemplate),
  414. });
  415. root.Show();
  416. target.ApplyTemplate();
  417. var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
  418. popup.Open();
  419. var popupRoot = (Control)popup.Host;
  420. popupRoot.Measure(Size.Infinity);
  421. popupRoot.Arrange(new Rect(popupRoot.DesiredSize));
  422. Assert.Null(item.TemplatedParent);
  423. popup.Close();
  424. popup.Open();
  425. Assert.Null(item.TemplatedParent);
  426. }
  427. }
  428. [Fact]
  429. public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit()
  430. {
  431. using (CreateServices())
  432. {
  433. TestControl child;
  434. var popup = new Popup
  435. {
  436. Child = child = new TestControl(),
  437. DataContext = "foo",
  438. PlacementTarget = PreparedWindow()
  439. };
  440. var beginCalled = false;
  441. child.DataContextBeginUpdate += (s, e) => beginCalled = true;
  442. // Test for #1245. Here, the child's logical parent is the popup but it's not yet
  443. // attached to a visual tree because the popup hasn't been opened.
  444. Assert.Same(popup, ((ILogical)child).LogicalParent);
  445. Assert.Same(popup, child.InheritanceParent);
  446. Assert.Null(child.GetVisualRoot());
  447. popup.Open();
  448. // #1245 was caused by the fact that DataContextBeginUpdate was called on `target`
  449. // when the PopupRoot was created, even though PopupRoot isn't the
  450. // InheritanceParent of child.
  451. Assert.False(beginCalled);
  452. }
  453. }
  454. [Fact]
  455. public void Popup_Host_Type_Should_Match_Platform_Preference()
  456. {
  457. using (CreateServices())
  458. {
  459. var target = new Popup() {PlacementTarget = PreparedWindow()};
  460. target.Open();
  461. if (UsePopupHost)
  462. Assert.IsType<OverlayPopupHost>(target.Host);
  463. else
  464. Assert.IsType<PopupRoot>(target.Host);
  465. }
  466. }
  467. [Fact]
  468. public void OverlayDismissEventPassThrough_Should_Pass_Event_To_Window_Contents()
  469. {
  470. using (CreateServices())
  471. {
  472. var renderer = new Mock<IRenderer>();
  473. var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
  474. var windowImpl = Mock.Get(platform.CreateWindow());
  475. windowImpl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  476. var window = new Window(windowImpl.Object);
  477. window.ApplyTemplate();
  478. var target = new Popup()
  479. {
  480. PlacementTarget = window ,
  481. IsLightDismissEnabled = true,
  482. OverlayDismissEventPassThrough = true,
  483. };
  484. var raised = 0;
  485. var border = new Border();
  486. window.Content = border;
  487. renderer.Setup(x =>
  488. x.HitTestFirst(new Point(10, 15), window, It.IsAny<Func<IVisual, bool>>()))
  489. .Returns(border);
  490. border.PointerPressed += (s, e) =>
  491. {
  492. Assert.Same(border, e.Source);
  493. ++raised;
  494. };
  495. target.Open();
  496. Assert.True(target.IsOpen);
  497. var e = CreatePointerPressedEventArgs(window, new Point(10, 15));
  498. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  499. overlay.RaiseEvent(e);
  500. Assert.Equal(1, raised);
  501. Assert.False(target.IsOpen);
  502. }
  503. }
  504. [Fact]
  505. public void Focusable_Controls_In_Popup_Should_Get_Focus()
  506. {
  507. using (CreateServicesWithFocus())
  508. {
  509. var window = PreparedWindow();
  510. var tb = new TextBox();
  511. var b = new Button();
  512. var p = new Popup
  513. {
  514. PlacementTarget = window,
  515. Child = new StackPanel
  516. {
  517. Children =
  518. {
  519. tb,
  520. b
  521. }
  522. }
  523. };
  524. ((ISetLogicalParent)p).SetParent(p.PlacementTarget);
  525. window.Show();
  526. p.Open();
  527. if(p.Host is OverlayPopupHost host)
  528. {
  529. //Need to measure/arrange for visual children to show up
  530. //in OverlayPopupHost
  531. host.Measure(Size.Infinity);
  532. host.Arrange(new Rect(host.DesiredSize));
  533. }
  534. tb.Focus();
  535. Assert.True(FocusManager.Instance?.Current == tb);
  536. //Ensure focus remains in the popup
  537. var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
  538. Assert.True(nextFocus == b);
  539. p.Close();
  540. }
  541. }
  542. [Fact]
  543. public void Closing_Popup_Sets_Focus_On_PlacementTarget()
  544. {
  545. using (CreateServicesWithFocus())
  546. {
  547. var window = PreparedWindow();
  548. window.Focusable = true;
  549. var tb = new TextBox();
  550. var p = new Popup
  551. {
  552. PlacementTarget = window,
  553. Child = tb
  554. };
  555. ((ISetLogicalParent)p).SetParent(p.PlacementTarget);
  556. window.Show();
  557. p.Open();
  558. if (p.Host is OverlayPopupHost host)
  559. {
  560. //Need to measure/arrange for visual children to show up
  561. //in OverlayPopupHost
  562. host.Measure(Size.Infinity);
  563. host.Arrange(new Rect(host.DesiredSize));
  564. }
  565. tb.Focus();
  566. p.Close();
  567. var focus = FocusManager.Instance?.Current;
  568. Assert.True(focus == window);
  569. }
  570. }
  571. [Fact]
  572. public void Prog_Close_Popup_NoLightDismiss_Doesnt_Move_Focus_To_PlacementTarget()
  573. {
  574. using (CreateServicesWithFocus())
  575. {
  576. var window = PreparedWindow();
  577. var windowTB = new TextBox();
  578. window.Content = windowTB;
  579. var popupTB = new TextBox();
  580. var p = new Popup
  581. {
  582. PlacementTarget = window,
  583. IsLightDismissEnabled = false,
  584. Child = popupTB
  585. };
  586. ((ISetLogicalParent)p).SetParent(p.PlacementTarget);
  587. window.Show();
  588. p.Open();
  589. if (p.Host is OverlayPopupHost host)
  590. {
  591. //Need to measure/arrange for visual children to show up
  592. //in OverlayPopupHost
  593. host.Measure(Size.Infinity);
  594. host.Arrange(new Rect(host.DesiredSize));
  595. }
  596. popupTB.Focus();
  597. windowTB.Focus();
  598. var focus = FocusManager.Instance?.Current;
  599. Assert.True(focus == windowTB);
  600. p.Close();
  601. Assert.True(focus == windowTB);
  602. }
  603. }
  604. [Fact]
  605. public void Popup_Should_Not_Follow_Placement_Target_On_Window_Move_If_Pointer()
  606. {
  607. using (CreateServices())
  608. {
  609. var popup = new Popup
  610. {
  611. Width = 400,
  612. Height = 200,
  613. PlacementMode = PlacementMode.Pointer
  614. };
  615. var window = PreparedWindow(popup);
  616. window.Show();
  617. popup.Open();
  618. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  619. var raised = false;
  620. if (popup.Host is PopupRoot popupRoot)
  621. {
  622. popupRoot.PositionChanged += (_, args) =>
  623. {
  624. raised = true;
  625. };
  626. }
  627. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  628. {
  629. overlayPopupHost.PropertyChanged += (_, args) =>
  630. {
  631. if (args.Property == Canvas.TopProperty
  632. || args.Property == Canvas.LeftProperty)
  633. {
  634. raised = true;
  635. }
  636. };
  637. }
  638. window.Position = new PixelPoint(10, 10);
  639. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  640. Assert.False(raised);
  641. }
  642. }
  643. [Fact]
  644. public void Popup_Should_Follow_Placement_Target_On_Window_Resize()
  645. {
  646. using (CreateServices())
  647. {
  648. var placementTarget = new Panel()
  649. {
  650. Width = 10,
  651. Height = 10,
  652. HorizontalAlignment = HorizontalAlignment.Center,
  653. VerticalAlignment = VerticalAlignment.Center
  654. };
  655. var popup = new Popup()
  656. {
  657. PlacementTarget = placementTarget,
  658. PlacementMode = PlacementMode.Bottom,
  659. Width = 10,
  660. Height = 10
  661. };
  662. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  663. var window = PreparedWindow(placementTarget);
  664. window.Show();
  665. popup.Open();
  666. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  667. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  668. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  669. var raised = false;
  670. // Resizing the window to 700x500 must move the popup to (345,255) as this is the new
  671. // location of the placement target
  672. if (popup.Host is PopupRoot popupRoot)
  673. {
  674. popupRoot.PositionChanged += (_, args) =>
  675. {
  676. Assert.Equal(new PixelPoint(345, 255), args.Point);
  677. raised = true;
  678. };
  679. }
  680. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  681. {
  682. overlayPopupHost.PropertyChanged += (_, args) =>
  683. {
  684. if ((args.Property == Canvas.TopProperty
  685. || args.Property == Canvas.LeftProperty)
  686. && Canvas.GetLeft(overlayPopupHost) == 345
  687. && Canvas.GetTop(overlayPopupHost) == 255)
  688. {
  689. raised = true;
  690. }
  691. };
  692. }
  693. window.PlatformImpl?.Resize(new Size(700D, 500D), PlatformResizeReason.Unspecified);
  694. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  695. Assert.True(raised);
  696. }
  697. }
  698. [Fact]
  699. public void Popup_Should_Not_Follow_Placement_Target_On_Window_Resize_If_Pointer_If_Pointer()
  700. {
  701. using (CreateServices())
  702. {
  703. var placementTarget = new Panel()
  704. {
  705. Width = 10,
  706. Height = 10,
  707. HorizontalAlignment = HorizontalAlignment.Center,
  708. VerticalAlignment = VerticalAlignment.Center
  709. };
  710. var popup = new Popup()
  711. {
  712. PlacementTarget = placementTarget,
  713. PlacementMode = PlacementMode.Pointer,
  714. Width = 10,
  715. Height = 10
  716. };
  717. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  718. var window = PreparedWindow(placementTarget);
  719. window.Show();
  720. popup.Open();
  721. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  722. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  723. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  724. var raised = false;
  725. if (popup.Host is PopupRoot popupRoot)
  726. {
  727. popupRoot.PositionChanged += (_, args) =>
  728. {
  729. raised = true;
  730. };
  731. }
  732. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  733. {
  734. overlayPopupHost.PropertyChanged += (_, args) =>
  735. {
  736. if (args.Property == Canvas.TopProperty
  737. || args.Property == Canvas.LeftProperty)
  738. {
  739. raised = true;
  740. }
  741. };
  742. }
  743. window.PlatformImpl?.Resize(new Size(700D, 500D), PlatformResizeReason.Unspecified);
  744. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  745. Assert.False(raised);
  746. }
  747. }
  748. [Fact]
  749. public void Popup_Should_Follow_Placement_Target_On_Target_Moved()
  750. {
  751. using (CreateServices())
  752. {
  753. var placementTarget = new Panel()
  754. {
  755. Width = 10,
  756. Height = 10,
  757. HorizontalAlignment = HorizontalAlignment.Center,
  758. VerticalAlignment = VerticalAlignment.Center
  759. };
  760. var popup = new Popup()
  761. {
  762. PlacementTarget = placementTarget,
  763. PlacementMode = PlacementMode.Bottom,
  764. Width = 10,
  765. Height = 10
  766. };
  767. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  768. var window = PreparedWindow(placementTarget);
  769. window.Show();
  770. popup.Open();
  771. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  772. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  773. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  774. var raised = false;
  775. // Margin will move placement target
  776. if (popup.Host is PopupRoot popupRoot)
  777. {
  778. popupRoot.PositionChanged += (_, args) =>
  779. {
  780. Assert.Equal(new PixelPoint(400, 305), args.Point);
  781. raised = true;
  782. };
  783. }
  784. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  785. {
  786. overlayPopupHost.PropertyChanged += (_, args) =>
  787. {
  788. if ((args.Property == Canvas.TopProperty
  789. || args.Property == Canvas.LeftProperty)
  790. && Canvas.GetLeft(overlayPopupHost) == 400
  791. && Canvas.GetTop(overlayPopupHost) == 305)
  792. {
  793. raised = true;
  794. }
  795. };
  796. }
  797. placementTarget.Margin = new Thickness(10, 0, 0, 0);
  798. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  799. Assert.True(raised);
  800. }
  801. }
  802. [Fact]
  803. public void Popup_Should_Not_Follow_Placement_Target_On_Target_Moved_If_Pointer()
  804. {
  805. using (CreateServices())
  806. {
  807. var placementTarget = new Panel()
  808. {
  809. Width = 10,
  810. Height = 10,
  811. HorizontalAlignment = HorizontalAlignment.Center,
  812. VerticalAlignment = VerticalAlignment.Center
  813. };
  814. var popup = new Popup()
  815. {
  816. PlacementTarget = placementTarget,
  817. PlacementMode = PlacementMode.Pointer,
  818. Width = 10,
  819. Height = 10
  820. };
  821. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  822. var window = PreparedWindow(placementTarget);
  823. window.Show();
  824. popup.Open();
  825. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  826. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  827. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  828. var raised = false;
  829. if (popup.Host is PopupRoot popupRoot)
  830. {
  831. popupRoot.PositionChanged += (_, args) =>
  832. {
  833. raised = true;
  834. };
  835. }
  836. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  837. {
  838. overlayPopupHost.PropertyChanged += (_, args) =>
  839. {
  840. if (args.Property == Canvas.TopProperty
  841. || args.Property == Canvas.LeftProperty)
  842. {
  843. raised = true;
  844. }
  845. };
  846. }
  847. placementTarget.Margin = new Thickness(10, 0, 0, 0);
  848. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  849. Assert.False(raised);
  850. }
  851. }
  852. [Fact]
  853. public void Popup_Should_Follow_Popup_Root_Placement_Target()
  854. {
  855. // When the placement target of a popup is another popup (e.g. nested menu items), the child popup must
  856. // follow the parent popup if it moves (due to root window movement or resize)
  857. using (CreateServices())
  858. {
  859. // The child popup is placed directly over the parent popup for position testing
  860. var parentPopup = new Popup() { Width = 10, Height = 10 };
  861. var childPopup = new Popup() {
  862. Width = 20,
  863. Height = 20,
  864. PlacementTarget = parentPopup,
  865. PlacementMode = PlacementMode.AnchorAndGravity,
  866. PlacementAnchor = PopupAnchor.TopLeft,
  867. PlacementGravity = PopupGravity.BottomRight
  868. };
  869. ((ISetLogicalParent)childPopup).SetParent(childPopup.PlacementTarget);
  870. var window = PreparedWindow(parentPopup);
  871. window.Show();
  872. parentPopup.Open();
  873. childPopup.Open();
  874. if (childPopup.Host is PopupRoot popupRoot)
  875. {
  876. var raised = false;
  877. popupRoot.PositionChanged += (_, args) =>
  878. {
  879. // The parent's initial placement is (395,295) which is a 10x10 popup centered
  880. // in a 800x600 window. When the window is moved, the child's final placement is (405, 305)
  881. // which is the parent's placement moved 10 pixels left and down.
  882. Assert.Equal(new PixelPoint(405, 305), args.Point);
  883. raised = true;
  884. };
  885. window.Position = new PixelPoint(10, 10);
  886. Assert.True(raised);
  887. }
  888. }
  889. }
  890. private IDisposable CreateServices()
  891. {
  892. return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
  893. new MockWindowingPlatform(null,
  894. x =>
  895. {
  896. if(UsePopupHost)
  897. return null;
  898. return MockWindowingPlatform.CreatePopupMock(x).Object;
  899. })));
  900. }
  901. private IDisposable CreateServicesWithFocus()
  902. {
  903. return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
  904. new MockWindowingPlatform(null,
  905. x =>
  906. {
  907. if (UsePopupHost)
  908. return null;
  909. return MockWindowingPlatform.CreatePopupMock(x).Object;
  910. }),
  911. focusManager: new FocusManager(),
  912. keyboardDevice: () => new KeyboardDevice()));
  913. }
  914. private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p)
  915. {
  916. var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
  917. return new PointerPressedEventArgs(
  918. source,
  919. pointer,
  920. source,
  921. p,
  922. 0,
  923. new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
  924. KeyModifiers.None);
  925. }
  926. private Window PreparedWindow(object content = null)
  927. {
  928. var w = new Window { Content = content };
  929. w.ApplyTemplate();
  930. return w;
  931. }
  932. private static IControl PopupContentControlTemplate(PopupContentControl control, INameScope scope)
  933. {
  934. return new Popup
  935. {
  936. Name = "popup",
  937. PlacementTarget = control,
  938. Child = new ContentPresenter
  939. {
  940. [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
  941. }
  942. }.RegisterInNameScope(scope);
  943. }
  944. private static IControl PopupItemsControlTemplate(PopupItemsControl control, INameScope scope)
  945. {
  946. return new Popup
  947. {
  948. Name = "popup",
  949. PlacementTarget = control,
  950. Child = new ItemsPresenter
  951. {
  952. [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
  953. }
  954. }.RegisterInNameScope(scope);
  955. }
  956. private class PopupContentControl : ContentControl
  957. {
  958. }
  959. private class PopupItemsControl : ItemsControl
  960. {
  961. }
  962. private class TestControl : Decorator
  963. {
  964. public event EventHandler DataContextBeginUpdate;
  965. public new IAvaloniaObject InheritanceParent => base.InheritanceParent;
  966. protected override void OnDataContextBeginUpdate()
  967. {
  968. DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
  969. base.OnDataContextBeginUpdate();
  970. }
  971. }
  972. }
  973. public class PopupTestsWithPopupRoot : PopupTests
  974. {
  975. public PopupTestsWithPopupRoot()
  976. {
  977. UsePopupHost = true;
  978. }
  979. }
  980. }