PopupTests.cs 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402
  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. using Avalonia.Interactivity;
  22. using Avalonia.Media;
  23. namespace Avalonia.Controls.UnitTests.Primitives
  24. {
  25. public class PopupTests : ScopedTestBase
  26. {
  27. protected bool UsePopupHost;
  28. [Fact]
  29. public void Popup_Open_Without_Target_Should_Attach_Itself_Later()
  30. {
  31. using (CreateServices())
  32. {
  33. int openedEvent = 0;
  34. var target = new Popup();
  35. target.Opened += (s, a) => openedEvent++;
  36. target.IsOpen = true;
  37. var window = PreparedWindow(target);
  38. window.Show();
  39. Assert.Equal(1, openedEvent);
  40. }
  41. }
  42. [Fact]
  43. public void Popup_Without_TopLevel_Shouldnt_Call_Open()
  44. {
  45. int openedEvent = 0;
  46. var target = new Popup();
  47. target.Opened += (s, a) => openedEvent++;
  48. target.IsOpen = true;
  49. Assert.Equal(0, openedEvent);
  50. }
  51. [Fact]
  52. public void Opening_Popup_Shouldnt_Throw_When_Not_In_Visual_Tree()
  53. {
  54. var target = new Popup();
  55. target.IsOpen = true;
  56. }
  57. [Fact]
  58. public void Opening_Popup_Shouldnt_Throw_When_In_Tree_Without_TopLevel()
  59. {
  60. Control c = new Control();
  61. var target = new Popup();
  62. ((ISetLogicalParent)target).SetParent(c);
  63. target.IsOpen = true;
  64. }
  65. [Fact]
  66. public void Setting_Child_Should_Set_Child_Controls_LogicalParent()
  67. {
  68. var target = new Popup();
  69. var child = new Control();
  70. target.Child = child;
  71. Assert.Equal(child.Parent, target);
  72. Assert.Equal(((ILogical)child).LogicalParent, target);
  73. }
  74. [Fact]
  75. public void Clearing_Child_Should_Clear_Child_Controls_Parent()
  76. {
  77. var target = new Popup();
  78. var child = new Control();
  79. target.Child = child;
  80. target.Child = null;
  81. Assert.Null(child.Parent);
  82. Assert.Null(((ILogical)child).LogicalParent);
  83. }
  84. [Fact]
  85. public void Child_Control_Should_Appear_In_LogicalChildren()
  86. {
  87. var target = new Popup();
  88. var child = new Control();
  89. target.Child = child;
  90. Assert.Equal(new[] { child }, target.GetLogicalChildren());
  91. }
  92. [Fact]
  93. public void Clearing_Child_Should_Remove_From_LogicalChildren()
  94. {
  95. var target = new Popup();
  96. var child = new Control();
  97. target.Child = child;
  98. target.Child = null;
  99. Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList());
  100. }
  101. [Fact]
  102. public void Setting_Child_Should_Fire_LogicalChildren_CollectionChanged()
  103. {
  104. var target = new Popup();
  105. var child = new Control();
  106. var called = false;
  107. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  108. called = e.Action == NotifyCollectionChangedAction.Add;
  109. target.Child = child;
  110. Assert.True(called);
  111. }
  112. [Fact]
  113. public void Clearing_Child_Should_Fire_LogicalChildren_CollectionChanged()
  114. {
  115. var target = new Popup();
  116. var child = new Control();
  117. var called = false;
  118. target.Child = child;
  119. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  120. called = e.Action == NotifyCollectionChangedAction.Remove;
  121. target.Child = null;
  122. Assert.True(called);
  123. }
  124. [Fact]
  125. public void Changing_Child_Should_Fire_LogicalChildren_CollectionChanged()
  126. {
  127. var target = new Popup();
  128. var child1 = new Control();
  129. var child2 = new Control();
  130. var called = false;
  131. target.Child = child1;
  132. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
  133. target.Child = child2;
  134. Assert.True(called);
  135. }
  136. [Fact]
  137. public void Setting_Child_Should_Not_Set_Childs_VisualParent()
  138. {
  139. var target = new Popup();
  140. var child = new Control();
  141. target.Child = child;
  142. Assert.Null(((Visual)child).VisualParent);
  143. }
  144. [Fact]
  145. public void PopupRoot_Should_Initially_Be_Null()
  146. {
  147. using (CreateServices())
  148. {
  149. var target = new Popup();
  150. Assert.Null(((Visual)target.Host));
  151. }
  152. }
  153. [Fact]
  154. public void PopupRoot_Should_Have_Popup_As_LogicalParent()
  155. {
  156. using (CreateServices())
  157. {
  158. var target = new Popup() {PlacementTarget = PreparedWindow()};
  159. target.Open();
  160. Assert.Equal(target, ((Visual)target.Host).Parent);
  161. Assert.Equal(target, ((Visual)target.Host).GetLogicalParent());
  162. }
  163. }
  164. [Fact]
  165. public void PopupRoot_Should_Be_Detached_From_Logical_Tree_When_Popup_Is_Detached()
  166. {
  167. using (CreateServices())
  168. {
  169. var target = new Popup() {Placement = PlacementMode.Pointer};
  170. var root = PreparedWindow(target);
  171. target.Open();
  172. var popupRoot = (ILogical)((Visual)target.Host);
  173. Assert.True(popupRoot.IsAttachedToLogicalTree);
  174. root.Content = null;
  175. Assert.False(((ILogical)target).IsAttachedToLogicalTree);
  176. }
  177. }
  178. [Fact]
  179. public void Should_Close_When_Control_Detaches()
  180. {
  181. using (CreateServices())
  182. {
  183. var button = new Button();
  184. var target = new Popup() {Placement = PlacementMode.Pointer, PlacementTarget = button};
  185. var root = PreparedWindow(button);
  186. target.Open();
  187. Assert.True(target.IsOpen);
  188. root.Content = null;
  189. Assert.False(target.IsOpen);
  190. }
  191. }
  192. [Fact]
  193. public void Popup_Open_Should_Raise_Single_Opened_Event()
  194. {
  195. using (CreateServices())
  196. {
  197. var window = PreparedWindow();
  198. var target = new Popup() {Placement = PlacementMode.Pointer};
  199. window.Content = target;
  200. int openedCount = 0;
  201. target.Opened += (sender, args) =>
  202. {
  203. openedCount++;
  204. };
  205. target.Open();
  206. Assert.Equal(1, openedCount);
  207. }
  208. }
  209. [Fact]
  210. public void Popup_Close_Should_Raise_Single_Closed_Event()
  211. {
  212. using (CreateServices())
  213. {
  214. var window = PreparedWindow();
  215. var target = new Popup() {Placement = PlacementMode.Pointer};
  216. window.Content = target;
  217. window.ApplyTemplate();
  218. target.Open();
  219. int closedCount = 0;
  220. target.Closed += (sender, args) =>
  221. {
  222. closedCount++;
  223. };
  224. target.Close();
  225. Assert.Equal(1, closedCount);
  226. }
  227. }
  228. [Fact]
  229. public void Popup_Close_On_Closed_Popup_Should_Not_Raise_Closed_Event()
  230. {
  231. using (CreateServices())
  232. {
  233. var window = PreparedWindow();
  234. var target = new Popup() { Placement = PlacementMode.Pointer };
  235. window.Content = target;
  236. window.ApplyTemplate();
  237. int closedCount = 0;
  238. target.Closed += (sender, args) =>
  239. {
  240. closedCount++;
  241. };
  242. target.Close();
  243. target.Close();
  244. target.Close();
  245. target.Close();
  246. Assert.Equal(0, closedCount);
  247. }
  248. }
  249. [Fact]
  250. public void ContentControl_With_Popup_In_Template_Should_Set_TemplatedParent()
  251. {
  252. // Test uses OverlayPopupHost default template
  253. using (CreateServices())
  254. {
  255. PopupContentControl target;
  256. var root = PreparedWindow(target = new PopupContentControl
  257. {
  258. Content = new Border(),
  259. Template = new FuncControlTemplate<PopupContentControl>(PopupContentControlTemplate),
  260. });
  261. root.Show();
  262. target.ApplyTemplate();
  263. var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
  264. popup.Open();
  265. var popupRoot = (Control)popup.Host;
  266. popupRoot.Measure(Size.Infinity);
  267. popupRoot.Arrange(new Rect(popupRoot.DesiredSize));
  268. var children = popupRoot.GetVisualDescendants().ToList();
  269. var types = children.Select(x => x.GetType().Name).ToList();
  270. if (UsePopupHost)
  271. {
  272. Assert.Equal(
  273. new[]
  274. {
  275. "LayoutTransformControl",
  276. "VisualLayerManager",
  277. "ContentPresenter",
  278. "ContentPresenter",
  279. "Border",
  280. },
  281. types);
  282. }
  283. else
  284. {
  285. Assert.Equal(
  286. new[]
  287. {
  288. "LayoutTransformControl",
  289. "Panel",
  290. "Border",
  291. "VisualLayerManager",
  292. "ContentPresenter",
  293. "ContentPresenter",
  294. "Border",
  295. },
  296. types);
  297. }
  298. var templatedParents = children
  299. .OfType<Control>()
  300. .Select(x => x.TemplatedParent).ToList();
  301. if (UsePopupHost)
  302. {
  303. Assert.Equal(
  304. new object[]
  305. {
  306. popupRoot,
  307. popupRoot,
  308. popupRoot,
  309. target,
  310. null,
  311. },
  312. templatedParents);
  313. }
  314. else
  315. {
  316. Assert.Equal(
  317. new object[]
  318. {
  319. popupRoot,
  320. popupRoot,
  321. popupRoot,
  322. popupRoot,
  323. popupRoot,
  324. target,
  325. null,
  326. },
  327. templatedParents);
  328. }
  329. }
  330. }
  331. [Fact]
  332. public void ItemsControl_With_Popup_In_Template_Should_Set_TemplatedParent()
  333. {
  334. // Test uses OverlayPopupHost default template
  335. using (CreateServices())
  336. {
  337. PopupItemsControl target;
  338. var item = new Border();
  339. var root = PreparedWindow(target = new PopupItemsControl
  340. {
  341. Items = { item },
  342. Template = new FuncControlTemplate<PopupItemsControl>(PopupItemsControlTemplate),
  343. }); ;
  344. root.Show();
  345. target.ApplyTemplate();
  346. var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
  347. popup.Open();
  348. var popupRoot = (Control)popup.Host;
  349. popupRoot.Measure(Size.Infinity);
  350. popupRoot.Arrange(new Rect(popupRoot.DesiredSize));
  351. var children = popupRoot.GetVisualDescendants().ToList();
  352. var types = children.Select(x => x.GetType().Name).ToList();
  353. if (UsePopupHost)
  354. {
  355. Assert.Equal(
  356. new[]
  357. {
  358. "LayoutTransformControl",
  359. "VisualLayerManager",
  360. "ContentPresenter",
  361. "ItemsPresenter",
  362. "StackPanel",
  363. "Border",
  364. },
  365. types);
  366. }
  367. else
  368. {
  369. Assert.Equal(
  370. new[]
  371. {
  372. "LayoutTransformControl",
  373. "Panel",
  374. "Border",
  375. "VisualLayerManager",
  376. "ContentPresenter",
  377. "ItemsPresenter",
  378. "StackPanel",
  379. "Border",
  380. },
  381. types);
  382. }
  383. var templatedParents = children
  384. .OfType<Control>()
  385. .Select(x => x.TemplatedParent).ToList();
  386. if (UsePopupHost)
  387. {
  388. Assert.Equal(
  389. new object[]
  390. {
  391. popupRoot,
  392. popupRoot,
  393. popupRoot,
  394. target,
  395. target,
  396. null,
  397. },
  398. templatedParents);
  399. }
  400. else
  401. {
  402. Assert.Equal(
  403. new object[]
  404. {
  405. popupRoot,
  406. popupRoot,
  407. popupRoot,
  408. popupRoot,
  409. popupRoot,
  410. target,
  411. target,
  412. null,
  413. },
  414. templatedParents);
  415. }
  416. }
  417. }
  418. [Fact]
  419. public void Should_Not_Overwrite_TemplatedParent_Of_Item_In_ItemsControl_With_Popup_On_Second_Open()
  420. {
  421. // Test uses OverlayPopupHost default template
  422. using (CreateServices())
  423. {
  424. PopupItemsControl target;
  425. var item = new Border();
  426. var root = PreparedWindow(target = new PopupItemsControl
  427. {
  428. Items = { item },
  429. Template = new FuncControlTemplate<PopupItemsControl>(PopupItemsControlTemplate),
  430. });
  431. root.Show();
  432. target.ApplyTemplate();
  433. var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
  434. popup.Open();
  435. var popupRoot = (Control)popup.Host;
  436. popupRoot.Measure(Size.Infinity);
  437. popupRoot.Arrange(new Rect(popupRoot.DesiredSize));
  438. Assert.Null(item.TemplatedParent);
  439. popup.Close();
  440. popup.Open();
  441. Assert.Null(item.TemplatedParent);
  442. }
  443. }
  444. [Fact]
  445. public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit()
  446. {
  447. using (CreateServices())
  448. {
  449. TestControl child;
  450. var popup = new Popup
  451. {
  452. Child = child = new TestControl(),
  453. DataContext = "foo",
  454. PlacementTarget = PreparedWindow()
  455. };
  456. var beginCalled = false;
  457. child.DataContextBeginUpdate += (s, e) => beginCalled = true;
  458. // Test for #1245. Here, the child's logical parent is the popup but it's not yet
  459. // attached to a visual tree because the popup hasn't been opened.
  460. Assert.Same(popup, ((ILogical)child).LogicalParent);
  461. Assert.Same(popup, child.InheritanceParent);
  462. Assert.Null(child.GetVisualRoot());
  463. popup.Open();
  464. // #1245 was caused by the fact that DataContextBeginUpdate was called on `target`
  465. // when the PopupRoot was created, even though PopupRoot isn't the
  466. // InheritanceParent of child.
  467. Assert.False(beginCalled);
  468. }
  469. }
  470. [Fact]
  471. public void Popup_Host_Type_Should_Match_Platform_Preference()
  472. {
  473. using (CreateServices())
  474. {
  475. var target = new Popup() {PlacementTarget = PreparedWindow()};
  476. target.Open();
  477. if (UsePopupHost)
  478. Assert.IsType<OverlayPopupHost>(target.Host);
  479. else
  480. Assert.IsType<PopupRoot>(target.Host);
  481. }
  482. }
  483. [Fact]
  484. public void OverlayDismissEventPassThrough_Should_Pass_Event_To_Window_Contents()
  485. {
  486. using (CreateServices())
  487. {
  488. var compositor = RendererMocks.CreateDummyCompositor();
  489. var platform = AvaloniaLocator.Current.GetRequiredService<IWindowingPlatform>();
  490. var windowImpl = Mock.Get(platform.CreateWindow());
  491. windowImpl.Setup(x => x.Compositor).Returns(compositor);
  492. var hitTester = new Mock<IHitTester>();
  493. var window = new Window(windowImpl.Object)
  494. {
  495. HitTesterOverride = hitTester.Object
  496. };
  497. window.ApplyStyling();
  498. window.ApplyTemplate();
  499. var target = new Popup()
  500. {
  501. PlacementTarget = window ,
  502. IsLightDismissEnabled = true,
  503. OverlayDismissEventPassThrough = true,
  504. };
  505. var raised = 0;
  506. var border = new Border();
  507. window.Content = border;
  508. hitTester.Setup(x =>
  509. x.HitTestFirst(new Point(10, 15), window, It.IsAny<Func<Visual, bool>>()))
  510. .Returns(border);
  511. border.PointerPressed += (s, e) =>
  512. {
  513. Assert.Same(border, e.Source);
  514. ++raised;
  515. };
  516. target.Open();
  517. Assert.True(target.IsOpen);
  518. var e = CreatePointerPressedEventArgs(window, new Point(10, 15));
  519. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  520. overlay.RaiseEvent(e);
  521. Assert.Equal(1, raised);
  522. Assert.False(target.IsOpen);
  523. }
  524. }
  525. [Fact]
  526. public void Focusable_Controls_In_Popup_Should_Get_Focus()
  527. {
  528. using (CreateServicesWithFocus())
  529. {
  530. var window = PreparedWindow(new Panel { Children = { new Slider() }});
  531. var textBox = new TextBox();
  532. var button = new Button();
  533. var popup = new Popup
  534. {
  535. PlacementTarget = window,
  536. Child = new StackPanel
  537. {
  538. Children =
  539. {
  540. textBox,
  541. button
  542. }
  543. }
  544. };
  545. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  546. window.Show();
  547. popup.Open();
  548. button.Focus();
  549. var inputRoot = Assert.IsAssignableFrom<IInputRoot>(popup.Host);
  550. var focusManager = inputRoot.FocusManager!;
  551. Assert.Same(button, focusManager.GetFocusedElement());
  552. //Ensure focus remains in the popup
  553. inputRoot.KeyboardNavigationHandler!.Move(focusManager.GetFocusedElement()!, NavigationDirection.Next);
  554. Assert.Same(textBox, focusManager.GetFocusedElement());
  555. popup.Close();
  556. }
  557. }
  558. [Fact]
  559. public void Closing_Popup_Sets_Focus_On_PlacementTarget()
  560. {
  561. using (CreateServicesWithFocus())
  562. {
  563. var window = PreparedWindow();
  564. window.Focusable = true;
  565. var tb = new TextBox();
  566. var p = new Popup
  567. {
  568. PlacementTarget = window,
  569. Child = tb
  570. };
  571. window.Content = p;
  572. window.Show();
  573. window.Focus();
  574. p.Open();
  575. if (p.Host is OverlayPopupHost host)
  576. {
  577. //Need to measure/arrange for visual children to show up
  578. //in OverlayPopupHost
  579. host.Measure(Size.Infinity);
  580. host.Arrange(new Rect(host.DesiredSize));
  581. }
  582. tb.Focus();
  583. p.Close();
  584. var focusManager = window.FocusManager;
  585. var focus = focusManager.GetFocusedElement();
  586. Assert.Same(window, focus);
  587. }
  588. }
  589. [Fact]
  590. public void Prog_Close_Popup_NoLightDismiss_Doesnt_Move_Focus_To_PlacementTarget()
  591. {
  592. using (CreateServicesWithFocus())
  593. {
  594. var window = PreparedWindow();
  595. var windowTB = new TextBox();
  596. window.Content = windowTB;
  597. var popupTB = new TextBox();
  598. var p = new Popup
  599. {
  600. PlacementTarget = window,
  601. IsLightDismissEnabled = false,
  602. Child = popupTB
  603. };
  604. ((ISetLogicalParent)p).SetParent(p.PlacementTarget);
  605. window.Show();
  606. p.Open();
  607. if (p.Host is OverlayPopupHost host)
  608. {
  609. //Need to measure/arrange for visual children to show up
  610. //in OverlayPopupHost
  611. host.Measure(Size.Infinity);
  612. host.Arrange(new Rect(host.DesiredSize));
  613. }
  614. popupTB.Focus();
  615. windowTB.Focus();
  616. var focusManager = window.FocusManager;
  617. var focus = focusManager.GetFocusedElement();
  618. Assert.True(focus == windowTB);
  619. p.Close();
  620. Assert.True(focus == windowTB);
  621. }
  622. }
  623. [Fact]
  624. public void Popup_Should_Not_Follow_Placement_Target_On_Window_Move_If_Pointer()
  625. {
  626. using (CreateServices())
  627. {
  628. var popup = new Popup
  629. {
  630. Width = 400,
  631. Height = 200,
  632. Placement = PlacementMode.Pointer
  633. };
  634. var window = PreparedWindow(popup);
  635. window.Show();
  636. popup.Open();
  637. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  638. var raised = false;
  639. if (popup.Host is PopupRoot popupRoot)
  640. {
  641. popupRoot.PositionChanged += (_, args) =>
  642. {
  643. raised = true;
  644. };
  645. }
  646. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  647. {
  648. overlayPopupHost.PropertyChanged += (_, args) =>
  649. {
  650. if (args.Property == Canvas.TopProperty
  651. || args.Property == Canvas.LeftProperty)
  652. {
  653. raised = true;
  654. }
  655. };
  656. }
  657. window.Position = new PixelPoint(10, 10);
  658. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  659. Assert.False(raised);
  660. }
  661. }
  662. [Fact]
  663. public void Popup_Should_Follow_Placement_Target_On_Window_Resize()
  664. {
  665. using (CreateServices())
  666. {
  667. var placementTarget = new Panel()
  668. {
  669. Width = 10,
  670. Height = 10,
  671. HorizontalAlignment = HorizontalAlignment.Center,
  672. VerticalAlignment = VerticalAlignment.Center
  673. };
  674. var popup = new Popup()
  675. {
  676. PlacementTarget = placementTarget,
  677. Placement = PlacementMode.Bottom,
  678. Width = 10,
  679. Height = 10
  680. };
  681. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  682. var window = PreparedWindow(placementTarget);
  683. window.Show();
  684. popup.Open();
  685. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  686. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  687. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  688. var raised = false;
  689. // Resizing the window to 700x500 must move the popup to (345,255) as this is the new
  690. // location of the placement target
  691. if (popup.Host is PopupRoot popupRoot)
  692. {
  693. popupRoot.PositionChanged += (_, args) =>
  694. {
  695. Assert.Equal(new PixelPoint(345, 255), args.Point);
  696. raised = true;
  697. };
  698. }
  699. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  700. {
  701. overlayPopupHost.PropertyChanged += (_, args) =>
  702. {
  703. if ((args.Property == Canvas.TopProperty
  704. || args.Property == Canvas.LeftProperty)
  705. && Canvas.GetLeft(overlayPopupHost) == 345
  706. && Canvas.GetTop(overlayPopupHost) == 255)
  707. {
  708. raised = true;
  709. }
  710. };
  711. }
  712. window.PlatformImpl?.Resize(new Size(700D, 500D), WindowResizeReason.Unspecified);
  713. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  714. Assert.True(raised);
  715. }
  716. }
  717. [Fact]
  718. public void Popup_Should_Not_Follow_Placement_Target_On_Window_Resize_If_Pointer_If_Pointer()
  719. {
  720. using (CreateServices())
  721. {
  722. var placementTarget = new Panel()
  723. {
  724. Width = 10,
  725. Height = 10,
  726. HorizontalAlignment = HorizontalAlignment.Center,
  727. VerticalAlignment = VerticalAlignment.Center
  728. };
  729. var popup = new Popup()
  730. {
  731. PlacementTarget = placementTarget,
  732. Placement = PlacementMode.Pointer,
  733. Width = 10,
  734. Height = 10
  735. };
  736. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  737. var window = PreparedWindow(placementTarget);
  738. window.Show();
  739. popup.Open();
  740. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  741. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  742. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  743. var raised = false;
  744. if (popup.Host is PopupRoot popupRoot)
  745. {
  746. popupRoot.PositionChanged += (_, args) =>
  747. {
  748. raised = true;
  749. };
  750. }
  751. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  752. {
  753. overlayPopupHost.PropertyChanged += (_, args) =>
  754. {
  755. if (args.Property == Canvas.TopProperty
  756. || args.Property == Canvas.LeftProperty)
  757. {
  758. raised = true;
  759. }
  760. };
  761. }
  762. window.PlatformImpl?.Resize(new Size(700D, 500D), WindowResizeReason.Unspecified);
  763. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  764. Assert.False(raised);
  765. }
  766. }
  767. [Fact]
  768. public void Popup_Should_Follow_Placement_Target_On_Target_Moved()
  769. {
  770. using (CreateServices())
  771. {
  772. var placementTarget = new Panel()
  773. {
  774. Width = 10,
  775. Height = 10,
  776. HorizontalAlignment = HorizontalAlignment.Center,
  777. VerticalAlignment = VerticalAlignment.Center
  778. };
  779. var popup = new Popup()
  780. {
  781. PlacementTarget = placementTarget,
  782. Placement = PlacementMode.Bottom,
  783. Width = 10,
  784. Height = 10
  785. };
  786. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  787. var window = PreparedWindow(placementTarget);
  788. window.Show();
  789. popup.Open();
  790. Dispatcher.UIThread.RunJobs();
  791. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  792. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  793. var raised = false;
  794. // Margin will move placement target
  795. if (popup.Host is PopupRoot popupRoot)
  796. {
  797. popupRoot.PositionChanged += (_, args) =>
  798. {
  799. Assert.Equal(new PixelPoint(400, 305), args.Point);
  800. raised = true;
  801. };
  802. }
  803. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  804. {
  805. overlayPopupHost.PropertyChanged += (_, args) =>
  806. {
  807. if ((args.Property == Canvas.TopProperty
  808. || args.Property == Canvas.LeftProperty)
  809. && Canvas.GetLeft(overlayPopupHost) == 400
  810. && Canvas.GetTop(overlayPopupHost) == 305)
  811. {
  812. raised = true;
  813. }
  814. };
  815. }
  816. placementTarget.Margin = new Thickness(10, 0, 0, 0);
  817. Dispatcher.UIThread.RunJobs();
  818. Assert.True(raised);
  819. }
  820. }
  821. [Fact]
  822. public void Popup_Should_Not_Follow_Placement_Target_On_Target_Moved_If_Pointer()
  823. {
  824. using (CreateServices())
  825. {
  826. var placementTarget = new Panel()
  827. {
  828. Width = 10,
  829. Height = 10,
  830. HorizontalAlignment = HorizontalAlignment.Center,
  831. VerticalAlignment = VerticalAlignment.Center
  832. };
  833. var popup = new Popup()
  834. {
  835. PlacementTarget = placementTarget,
  836. Placement = PlacementMode.Pointer,
  837. Width = 10,
  838. Height = 10
  839. };
  840. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  841. var window = PreparedWindow(placementTarget);
  842. window.Show();
  843. popup.Open();
  844. Dispatcher.UIThread.RunJobs();
  845. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  846. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  847. var raised = false;
  848. if (popup.Host is PopupRoot popupRoot)
  849. {
  850. popupRoot.PositionChanged += (_, args) =>
  851. {
  852. raised = true;
  853. };
  854. }
  855. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  856. {
  857. overlayPopupHost.PropertyChanged += (_, args) =>
  858. {
  859. if (args.Property == Canvas.TopProperty
  860. || args.Property == Canvas.LeftProperty)
  861. {
  862. raised = true;
  863. }
  864. };
  865. }
  866. placementTarget.Margin = new Thickness(10, 0, 0, 0);
  867. Dispatcher.UIThread.RunJobs();
  868. Assert.False(raised);
  869. }
  870. }
  871. [Fact]
  872. public void Popup_Should_Follow_Popup_Root_Placement_Target()
  873. {
  874. // When the placement target of a popup is another popup (e.g. nested menu items), the child popup must
  875. // follow the parent popup if it moves (due to root window movement or resize)
  876. using (CreateServices())
  877. {
  878. // The child popup is placed directly over the parent popup for position testing
  879. var parentPopup = new Popup() { Width = 10, Height = 10 };
  880. var childPopup = new Popup() {
  881. Width = 20,
  882. Height = 20,
  883. PlacementTarget = parentPopup,
  884. Placement = PlacementMode.AnchorAndGravity,
  885. PlacementAnchor = PopupAnchor.TopLeft,
  886. PlacementGravity = PopupGravity.BottomRight
  887. };
  888. ((ISetLogicalParent)childPopup).SetParent(childPopup.PlacementTarget);
  889. var window = PreparedWindow(parentPopup);
  890. window.Show();
  891. parentPopup.Open();
  892. childPopup.Open();
  893. if (childPopup.Host is PopupRoot popupRoot)
  894. {
  895. var raised = false;
  896. popupRoot.PositionChanged += (_, args) =>
  897. {
  898. // The parent's initial placement is (395,295) which is a 10x10 popup centered
  899. // in a 800x600 window. When the window is moved, the child's final placement is (405, 305)
  900. // which is the parent's placement moved 10 pixels left and down.
  901. Assert.Equal(new PixelPoint(405, 305), args.Point);
  902. raised = true;
  903. };
  904. window.Position = new PixelPoint(10, 10);
  905. Assert.True(raised);
  906. }
  907. }
  908. }
  909. [Fact]
  910. public void Events_Should_Be_Routed_To_Popup_Parent()
  911. {
  912. using (CreateServices())
  913. {
  914. var popupContent = new Border();
  915. var popup = new Popup { Child = popupContent };
  916. var popupParent = new Border { Child = popup };
  917. var root = PreparedWindow(popupParent);
  918. var raised = 0;
  919. root.LayoutManager.ExecuteInitialLayoutPass();
  920. popup.Open();
  921. root.LayoutManager.ExecuteLayoutPass();
  922. var ev = new RoutedEventArgs(Button.ClickEvent);
  923. popupParent.AddHandler(Button.ClickEvent, (s, e) => ++raised);
  924. popupContent.RaiseEvent(ev);
  925. Assert.Equal(1, raised);
  926. }
  927. }
  928. [Fact]
  929. public void GetPosition_On_Control_In_Popup_Called_From_Parent_Should_Return_Valid_Coordinates()
  930. {
  931. // This test only applies when using a PopupRoot host and not an overlay popup.
  932. if (UsePopupHost)
  933. return;
  934. using (CreateServices())
  935. {
  936. var popupContent = new Border() { Height = 100, Width = 100, Background = Brushes.Red };
  937. var popup = new Popup { Child = popupContent, HorizontalOffset = 40, VerticalOffset = 40, Placement = PlacementMode.AnchorAndGravity,
  938. PlacementAnchor = PopupAnchor.TopLeft, PlacementGravity = PopupGravity.BottomRight};
  939. var popupParent = new Border { Child = popup };
  940. var root = PreparedWindow(popupParent);
  941. popup.Open();
  942. // Verify that the popup is positioned at 40,40 as descibed by the Horizontal/
  943. // VerticalOffset: 10,10 becomes 50,50 in screen coordinates.
  944. Assert.Equal(new PixelPoint(50, 50), popupContent.PointToScreen(new Point(10, 10)));
  945. // The popup parent is positioned at 0,0 in screen coordinates so client and
  946. // screen coordinates are the same.
  947. Assert.Equal(new PixelPoint(10, 10), popupParent.PointToScreen(new Point(10, 10)));
  948. // The event will be raised on the popup content at 50,50 (90,90 in screen coordinates)
  949. var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
  950. var ev = new PointerPressedEventArgs(
  951. popupContent,
  952. pointer,
  953. popupContent.VisualRoot as PopupRoot,
  954. new Point(50 , 50),
  955. 0,
  956. new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
  957. KeyModifiers.None);
  958. var contentRaised = 0;
  959. var parentRaised = 0;
  960. // The event is raised on the popup content in popup coordinates.
  961. popupContent.AddHandler(Button.PointerPressedEvent, (s, e) =>
  962. {
  963. ++contentRaised;
  964. Assert.Equal(new Point(50, 50), e.GetPosition(popupContent));
  965. });
  966. // The event is raised on the parent in root coordinates (which in this case are
  967. // the same as screen coordinates).
  968. popupParent.AddHandler(Button.PointerPressedEvent, (s, e) =>
  969. {
  970. ++parentRaised;
  971. Assert.Equal(new Point(90, 90), e.GetPosition(popupParent));
  972. });
  973. popupContent.RaiseEvent(ev);
  974. Assert.Equal(1, contentRaised);
  975. Assert.Equal(1, parentRaised);
  976. }
  977. }
  978. [Fact]
  979. public void Popup_Attached_To_Adorner_Respects_Adorner_Position()
  980. {
  981. using (CreateServices())
  982. {
  983. var popupTarget = new Border() { Height = 30, Background = Brushes.Red, [DockPanel.DockProperty] = Dock.Top };
  984. var popupContent = new Border() { Height = 30, Width = 50, Background = Brushes.Yellow };
  985. var popup = new Popup
  986. {
  987. Child = popupContent,
  988. Placement = PlacementMode.AnchorAndGravity,
  989. PlacementTarget = popupTarget,
  990. PlacementAnchor = PopupAnchor.BottomRight,
  991. PlacementGravity = PopupGravity.BottomRight
  992. };
  993. var adorner = new DockPanel() { Children = { popupTarget, popup },
  994. HorizontalAlignment = HorizontalAlignment.Left,
  995. Width = 40,
  996. Margin = new Thickness(50, 5, 0, 0) };
  997. var adorned = new Border() {
  998. Width = 100,
  999. Height = 100,
  1000. Background = Brushes.Blue,
  1001. [Canvas.LeftProperty] = 20,
  1002. [Canvas.TopProperty] = 40
  1003. };
  1004. var windowContent = new Canvas();
  1005. windowContent.Children.Add(adorned);
  1006. var root = PreparedWindow(windowContent);
  1007. var adornerLayer = AdornerLayer.GetAdornerLayer(adorned);
  1008. adornerLayer.Children.Add(adorner);
  1009. AdornerLayer.SetAdornedElement(adorner, adorned);
  1010. root.LayoutManager.ExecuteInitialLayoutPass();
  1011. popup.Open();
  1012. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  1013. // X: Adorned Canvas.Left + Adorner Margin Left + Adorner Width
  1014. // Y: Adorned Canvas.Top + Adorner Margin Top + Adorner Height
  1015. Assert.Equal(new PixelPoint(110, 75), popupContent.PointToScreen(new Point(0, 0)));
  1016. }
  1017. }
  1018. [Fact]
  1019. public void Custom_Placement_Callback_Is_Executed()
  1020. {
  1021. using (CreateServices())
  1022. {
  1023. var callbackExecuted = 0;
  1024. var popupContent = new Border { Width = 100, Height = 100 };
  1025. var popup = new Popup
  1026. {
  1027. Child = popupContent,
  1028. Placement = PlacementMode.Custom,
  1029. HorizontalOffset = 42,
  1030. VerticalOffset = 21
  1031. };
  1032. var popupParent = new Border { Child = popup };
  1033. var root = PreparedWindow(popupParent);
  1034. popup.CustomPopupPlacementCallback = (parameters) =>
  1035. {
  1036. Assert.Equal(popupContent.Width, parameters.PopupSize.Width);
  1037. Assert.Equal(popupContent.Height, parameters.PopupSize.Height);
  1038. Assert.Equal(root.Width, parameters.AnchorRectangle.Width);
  1039. Assert.Equal(root.Height, parameters.AnchorRectangle.Height);
  1040. Assert.Equal(popup.HorizontalOffset, parameters.Offset.X);
  1041. Assert.Equal(popup.VerticalOffset, parameters.Offset.Y);
  1042. callbackExecuted++;
  1043. parameters.Anchor = PopupAnchor.Top;
  1044. parameters.Gravity = PopupGravity.Bottom;
  1045. };
  1046. root.LayoutManager.ExecuteInitialLayoutPass();
  1047. popup.Open();
  1048. root.LayoutManager.ExecuteLayoutPass();
  1049. Assert.Equal(1, callbackExecuted);
  1050. }
  1051. }
  1052. private static PopupRoot CreateRoot(TopLevel popupParent, IPopupImpl impl = null)
  1053. {
  1054. impl ??= popupParent.PlatformImpl.CreatePopup();
  1055. var result = new PopupRoot(popupParent, impl)
  1056. {
  1057. Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
  1058. new ContentPresenter
  1059. {
  1060. Name = "PART_ContentPresenter",
  1061. [!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty],
  1062. }.RegisterInNameScope(scope)),
  1063. };
  1064. result.ApplyTemplate();
  1065. return result;
  1066. }
  1067. [Fact]
  1068. public void Popup_Open_With_Correct_IsUsingOverlayLayer_And_Disabled_OverlayLayer()
  1069. {
  1070. using (CreateServices())
  1071. {
  1072. var target = new Popup();
  1073. target.IsOpen = true;
  1074. target.ShouldUseOverlayLayer = false;
  1075. var window = PreparedWindow(target);
  1076. window.Show();
  1077. Assert.Equal(UsePopupHost, target.IsUsingOverlayLayer);
  1078. }
  1079. }
  1080. [Fact]
  1081. public void Popup_Open_With_Correct_IsUsingOverlayLayer_And_Enabled_OverlayLayer()
  1082. {
  1083. using (CreateServices())
  1084. {
  1085. var target = new Popup();
  1086. target.IsOpen = true;
  1087. target.ShouldUseOverlayLayer = true;
  1088. var window = PreparedWindow(target);
  1089. window.Show();
  1090. Assert.Equal(true, target.IsUsingOverlayLayer);
  1091. }
  1092. }
  1093. private IDisposable CreateServices()
  1094. {
  1095. return UnitTestApplication.Start(TestServices.StyledWindow.With(
  1096. windowingPlatform: CreateMockWindowingPlatform()));
  1097. }
  1098. private IDisposable CreateServicesWithFocus()
  1099. {
  1100. return UnitTestApplication.Start(TestServices.StyledWindow.With(
  1101. windowingPlatform: CreateMockWindowingPlatform(),
  1102. focusManager: new FocusManager(),
  1103. keyboardDevice: () => new KeyboardDevice(),
  1104. keyboardNavigation: () => new KeyboardNavigationHandler()));
  1105. }
  1106. private static PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p)
  1107. {
  1108. var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
  1109. return new PointerPressedEventArgs(
  1110. source,
  1111. pointer,
  1112. source,
  1113. p,
  1114. 0,
  1115. new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
  1116. KeyModifiers.None);
  1117. }
  1118. private MockWindowingPlatform CreateMockWindowingPlatform()
  1119. {
  1120. return new MockWindowingPlatform(() =>
  1121. {
  1122. var mock = MockWindowingPlatform.CreateWindowMock();
  1123. mock.Setup(x => x.CreatePopup()).Returns(() =>
  1124. {
  1125. if (UsePopupHost)
  1126. return null;
  1127. return MockWindowingPlatform.CreatePopupMock(mock.Object).Object;
  1128. });
  1129. return mock.Object;
  1130. }, null);
  1131. }
  1132. private static Window PreparedWindow(object content = null)
  1133. {
  1134. var w = new Window { Content = content };
  1135. w.Show();
  1136. w.ApplyStyling();
  1137. w.ApplyTemplate();
  1138. return w;
  1139. }
  1140. private static Control PopupContentControlTemplate(PopupContentControl control, INameScope scope)
  1141. {
  1142. return new Popup
  1143. {
  1144. Name = "popup",
  1145. PlacementTarget = control,
  1146. Child = new ContentPresenter
  1147. {
  1148. [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
  1149. }
  1150. }.RegisterInNameScope(scope);
  1151. }
  1152. private static Control PopupItemsControlTemplate(PopupItemsControl control, INameScope scope)
  1153. {
  1154. return new Popup
  1155. {
  1156. Name = "popup",
  1157. PlacementTarget = control,
  1158. Child = new ItemsPresenter(),
  1159. }.RegisterInNameScope(scope);
  1160. }
  1161. private class PopupContentControl : ContentControl
  1162. {
  1163. }
  1164. private class PopupItemsControl : ItemsControl
  1165. {
  1166. }
  1167. private class TestControl : Decorator
  1168. {
  1169. public event EventHandler DataContextBeginUpdate;
  1170. public new AvaloniaObject InheritanceParent => base.InheritanceParent;
  1171. protected override void OnDataContextBeginUpdate()
  1172. {
  1173. DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
  1174. base.OnDataContextBeginUpdate();
  1175. }
  1176. }
  1177. }
  1178. public class PopupTestsWithPopupRoot : PopupTests
  1179. {
  1180. public PopupTestsWithPopupRoot()
  1181. {
  1182. UsePopupHost = true;
  1183. }
  1184. }
  1185. }