PopupTests.cs 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446
  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 Popup_Should_Clear_Keyboard_Focus_From_Children_When_Closed()
  560. {
  561. using (CreateServicesWithFocus())
  562. {
  563. var winButton = new Button();
  564. var window = PreparedWindow(new Panel { Children = { winButton }});
  565. var border1 = new Border();
  566. var border2 = new Border();
  567. var button = new Button();
  568. border1.Child = border2;
  569. border2.Child = button;
  570. var popup = new Popup
  571. {
  572. PlacementTarget = window,
  573. Child = new StackPanel
  574. {
  575. Children =
  576. {
  577. border1
  578. }
  579. }
  580. };
  581. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  582. window.Show();
  583. winButton.Focus();
  584. popup.Open();
  585. button.Focus();
  586. var inputRoot = Assert.IsAssignableFrom<IInputRoot>(popup.Host);
  587. var focusManager = inputRoot.FocusManager!;
  588. Assert.Same(button, focusManager.GetFocusedElement());
  589. border1.Child = null;
  590. winButton.Focus();
  591. Assert.False(border2.IsKeyboardFocusWithin);
  592. }
  593. }
  594. [Fact]
  595. public void Closing_Popup_Sets_Focus_On_PlacementTarget()
  596. {
  597. using (CreateServicesWithFocus())
  598. {
  599. var window = PreparedWindow();
  600. window.Focusable = true;
  601. var tb = new TextBox();
  602. var p = new Popup
  603. {
  604. PlacementTarget = window,
  605. Child = tb
  606. };
  607. window.Content = p;
  608. window.Show();
  609. window.Focus();
  610. p.Open();
  611. if (p.Host is OverlayPopupHost host)
  612. {
  613. //Need to measure/arrange for visual children to show up
  614. //in OverlayPopupHost
  615. host.Measure(Size.Infinity);
  616. host.Arrange(new Rect(host.DesiredSize));
  617. }
  618. tb.Focus();
  619. p.Close();
  620. var focusManager = window.FocusManager;
  621. var focus = focusManager.GetFocusedElement();
  622. Assert.Same(window, focus);
  623. }
  624. }
  625. [Fact]
  626. public void Prog_Close_Popup_NoLightDismiss_Doesnt_Move_Focus_To_PlacementTarget()
  627. {
  628. using (CreateServicesWithFocus())
  629. {
  630. var window = PreparedWindow();
  631. var windowTB = new TextBox();
  632. window.Content = windowTB;
  633. var popupTB = new TextBox();
  634. var p = new Popup
  635. {
  636. PlacementTarget = window,
  637. IsLightDismissEnabled = false,
  638. Child = popupTB
  639. };
  640. ((ISetLogicalParent)p).SetParent(p.PlacementTarget);
  641. window.Show();
  642. p.Open();
  643. if (p.Host is OverlayPopupHost host)
  644. {
  645. //Need to measure/arrange for visual children to show up
  646. //in OverlayPopupHost
  647. host.Measure(Size.Infinity);
  648. host.Arrange(new Rect(host.DesiredSize));
  649. }
  650. popupTB.Focus();
  651. windowTB.Focus();
  652. var focusManager = window.FocusManager;
  653. var focus = focusManager.GetFocusedElement();
  654. Assert.True(focus == windowTB);
  655. p.Close();
  656. Assert.True(focus == windowTB);
  657. }
  658. }
  659. [Fact]
  660. public void Popup_Should_Not_Follow_Placement_Target_On_Window_Move_If_Pointer()
  661. {
  662. using (CreateServices())
  663. {
  664. var popup = new Popup
  665. {
  666. Width = 400,
  667. Height = 200,
  668. Placement = PlacementMode.Pointer
  669. };
  670. var window = PreparedWindow(popup);
  671. window.Show();
  672. popup.Open();
  673. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  674. var raised = false;
  675. if (popup.Host is PopupRoot popupRoot)
  676. {
  677. popupRoot.PositionChanged += (_, args) =>
  678. {
  679. raised = true;
  680. };
  681. }
  682. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  683. {
  684. overlayPopupHost.PropertyChanged += (_, args) =>
  685. {
  686. if (args.Property == Canvas.TopProperty
  687. || args.Property == Canvas.LeftProperty)
  688. {
  689. raised = true;
  690. }
  691. };
  692. }
  693. window.Position = new PixelPoint(10, 10);
  694. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  695. Assert.False(raised);
  696. }
  697. }
  698. [Fact]
  699. public void Popup_Should_Follow_Placement_Target_On_Window_Resize()
  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. Placement = PlacementMode.Bottom,
  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.AfterRender);
  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. // Resizing the window to 700x500 must move the popup to (345,255) as this is the new
  726. // location of the placement target
  727. if (popup.Host is PopupRoot popupRoot)
  728. {
  729. popupRoot.PositionChanged += (_, args) =>
  730. {
  731. Assert.Equal(new PixelPoint(345, 255), args.Point);
  732. raised = true;
  733. };
  734. }
  735. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  736. {
  737. overlayPopupHost.PropertyChanged += (_, args) =>
  738. {
  739. if ((args.Property == Canvas.TopProperty
  740. || args.Property == Canvas.LeftProperty)
  741. && Canvas.GetLeft(overlayPopupHost) == 345
  742. && Canvas.GetTop(overlayPopupHost) == 255)
  743. {
  744. raised = true;
  745. }
  746. };
  747. }
  748. window.PlatformImpl?.Resize(new Size(700D, 500D), WindowResizeReason.Unspecified);
  749. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  750. Assert.True(raised);
  751. }
  752. }
  753. [Fact]
  754. public void Popup_Should_Not_Follow_Placement_Target_On_Window_Resize_If_Pointer_If_Pointer()
  755. {
  756. using (CreateServices())
  757. {
  758. var placementTarget = new Panel()
  759. {
  760. Width = 10,
  761. Height = 10,
  762. HorizontalAlignment = HorizontalAlignment.Center,
  763. VerticalAlignment = VerticalAlignment.Center
  764. };
  765. var popup = new Popup()
  766. {
  767. PlacementTarget = placementTarget,
  768. Placement = PlacementMode.Pointer,
  769. Width = 10,
  770. Height = 10
  771. };
  772. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  773. var window = PreparedWindow(placementTarget);
  774. window.Show();
  775. popup.Open();
  776. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  777. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  778. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  779. var raised = false;
  780. if (popup.Host is PopupRoot popupRoot)
  781. {
  782. popupRoot.PositionChanged += (_, args) =>
  783. {
  784. raised = true;
  785. };
  786. }
  787. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  788. {
  789. overlayPopupHost.PropertyChanged += (_, args) =>
  790. {
  791. if (args.Property == Canvas.TopProperty
  792. || args.Property == Canvas.LeftProperty)
  793. {
  794. raised = true;
  795. }
  796. };
  797. }
  798. window.PlatformImpl?.Resize(new Size(700D, 500D), WindowResizeReason.Unspecified);
  799. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  800. Assert.False(raised);
  801. }
  802. }
  803. [Fact]
  804. public void Popup_Should_Follow_Placement_Target_On_Target_Moved()
  805. {
  806. using (CreateServices())
  807. {
  808. var placementTarget = new Panel()
  809. {
  810. Width = 10,
  811. Height = 10,
  812. HorizontalAlignment = HorizontalAlignment.Center,
  813. VerticalAlignment = VerticalAlignment.Center
  814. };
  815. var popup = new Popup()
  816. {
  817. PlacementTarget = placementTarget,
  818. Placement = PlacementMode.Bottom,
  819. Width = 10,
  820. Height = 10
  821. };
  822. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  823. var window = PreparedWindow(placementTarget);
  824. window.Show();
  825. popup.Open();
  826. Dispatcher.UIThread.RunJobs();
  827. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  828. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  829. var raised = false;
  830. // Margin will move placement target
  831. if (popup.Host is PopupRoot popupRoot)
  832. {
  833. popupRoot.PositionChanged += (_, args) =>
  834. {
  835. Assert.Equal(new PixelPoint(400, 305), args.Point);
  836. raised = true;
  837. };
  838. }
  839. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  840. {
  841. overlayPopupHost.PropertyChanged += (_, args) =>
  842. {
  843. if ((args.Property == Canvas.TopProperty
  844. || args.Property == Canvas.LeftProperty)
  845. && Canvas.GetLeft(overlayPopupHost) == 400
  846. && Canvas.GetTop(overlayPopupHost) == 305)
  847. {
  848. raised = true;
  849. }
  850. };
  851. }
  852. placementTarget.Margin = new Thickness(10, 0, 0, 0);
  853. Dispatcher.UIThread.RunJobs();
  854. Assert.True(raised);
  855. }
  856. }
  857. [Fact]
  858. public void Popup_Should_Not_Follow_Placement_Target_On_Target_Moved_If_Pointer()
  859. {
  860. using (CreateServices())
  861. {
  862. var placementTarget = new Panel()
  863. {
  864. Width = 10,
  865. Height = 10,
  866. HorizontalAlignment = HorizontalAlignment.Center,
  867. VerticalAlignment = VerticalAlignment.Center
  868. };
  869. var popup = new Popup()
  870. {
  871. PlacementTarget = placementTarget,
  872. Placement = PlacementMode.Pointer,
  873. Width = 10,
  874. Height = 10
  875. };
  876. ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget);
  877. var window = PreparedWindow(placementTarget);
  878. window.Show();
  879. popup.Open();
  880. Dispatcher.UIThread.RunJobs();
  881. // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window
  882. Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10));
  883. var raised = false;
  884. if (popup.Host is PopupRoot popupRoot)
  885. {
  886. popupRoot.PositionChanged += (_, args) =>
  887. {
  888. raised = true;
  889. };
  890. }
  891. else if (popup.Host is OverlayPopupHost overlayPopupHost)
  892. {
  893. overlayPopupHost.PropertyChanged += (_, args) =>
  894. {
  895. if (args.Property == Canvas.TopProperty
  896. || args.Property == Canvas.LeftProperty)
  897. {
  898. raised = true;
  899. }
  900. };
  901. }
  902. placementTarget.Margin = new Thickness(10, 0, 0, 0);
  903. Dispatcher.UIThread.RunJobs();
  904. Assert.False(raised);
  905. }
  906. }
  907. [Fact]
  908. public void Popup_Should_Follow_Popup_Root_Placement_Target()
  909. {
  910. // When the placement target of a popup is another popup (e.g. nested menu items), the child popup must
  911. // follow the parent popup if it moves (due to root window movement or resize)
  912. using (CreateServices())
  913. {
  914. // The child popup is placed directly over the parent popup for position testing
  915. var parentPopup = new Popup() { Width = 10, Height = 10 };
  916. var childPopup = new Popup() {
  917. Width = 20,
  918. Height = 20,
  919. PlacementTarget = parentPopup,
  920. Placement = PlacementMode.AnchorAndGravity,
  921. PlacementAnchor = PopupAnchor.TopLeft,
  922. PlacementGravity = PopupGravity.BottomRight
  923. };
  924. ((ISetLogicalParent)childPopup).SetParent(childPopup.PlacementTarget);
  925. var window = PreparedWindow(parentPopup);
  926. window.Show();
  927. parentPopup.Open();
  928. childPopup.Open();
  929. if (childPopup.Host is PopupRoot popupRoot)
  930. {
  931. var raised = false;
  932. popupRoot.PositionChanged += (_, args) =>
  933. {
  934. // The parent's initial placement is (395,295) which is a 10x10 popup centered
  935. // in a 800x600 window. When the window is moved, the child's final placement is (405, 305)
  936. // which is the parent's placement moved 10 pixels left and down.
  937. Assert.Equal(new PixelPoint(405, 305), args.Point);
  938. raised = true;
  939. };
  940. window.Position = new PixelPoint(10, 10);
  941. Assert.True(raised);
  942. }
  943. }
  944. }
  945. [Fact]
  946. public void Events_Should_Be_Routed_To_Popup_Parent()
  947. {
  948. using (CreateServices())
  949. {
  950. var popupContent = new Border();
  951. var popup = new Popup { Child = popupContent };
  952. var popupParent = new Border { Child = popup };
  953. var root = PreparedWindow(popupParent);
  954. var raised = 0;
  955. root.LayoutManager.ExecuteInitialLayoutPass();
  956. popup.Open();
  957. root.LayoutManager.ExecuteLayoutPass();
  958. var ev = new RoutedEventArgs(Button.ClickEvent);
  959. popupParent.AddHandler(Button.ClickEvent, (s, e) => ++raised);
  960. popupContent.RaiseEvent(ev);
  961. Assert.Equal(1, raised);
  962. }
  963. }
  964. [Fact]
  965. public void GetPosition_On_Control_In_Popup_Called_From_Parent_Should_Return_Valid_Coordinates()
  966. {
  967. // This test only applies when using a PopupRoot host and not an overlay popup.
  968. if (UsePopupHost)
  969. return;
  970. using (CreateServices())
  971. {
  972. var popupContent = new Border() { Height = 100, Width = 100, Background = Brushes.Red };
  973. var popup = new Popup { Child = popupContent, HorizontalOffset = 40, VerticalOffset = 40, Placement = PlacementMode.AnchorAndGravity,
  974. PlacementAnchor = PopupAnchor.TopLeft, PlacementGravity = PopupGravity.BottomRight};
  975. var popupParent = new Border { Child = popup };
  976. var root = PreparedWindow(popupParent);
  977. popup.Open();
  978. // Verify that the popup is positioned at 40,40 as descibed by the Horizontal/
  979. // VerticalOffset: 10,10 becomes 50,50 in screen coordinates.
  980. Assert.Equal(new PixelPoint(50, 50), popupContent.PointToScreen(new Point(10, 10)));
  981. // The popup parent is positioned at 0,0 in screen coordinates so client and
  982. // screen coordinates are the same.
  983. Assert.Equal(new PixelPoint(10, 10), popupParent.PointToScreen(new Point(10, 10)));
  984. // The event will be raised on the popup content at 50,50 (90,90 in screen coordinates)
  985. var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
  986. var ev = new PointerPressedEventArgs(
  987. popupContent,
  988. pointer,
  989. popupContent.VisualRoot as PopupRoot,
  990. new Point(50 , 50),
  991. 0,
  992. new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
  993. KeyModifiers.None);
  994. var contentRaised = 0;
  995. var parentRaised = 0;
  996. // The event is raised on the popup content in popup coordinates.
  997. popupContent.AddHandler(Button.PointerPressedEvent, (s, e) =>
  998. {
  999. ++contentRaised;
  1000. Assert.Equal(new Point(50, 50), e.GetPosition(popupContent));
  1001. });
  1002. // The event is raised on the parent in root coordinates (which in this case are
  1003. // the same as screen coordinates).
  1004. popupParent.AddHandler(Button.PointerPressedEvent, (s, e) =>
  1005. {
  1006. ++parentRaised;
  1007. Assert.Equal(new Point(90, 90), e.GetPosition(popupParent));
  1008. });
  1009. popupContent.RaiseEvent(ev);
  1010. Assert.Equal(1, contentRaised);
  1011. Assert.Equal(1, parentRaised);
  1012. }
  1013. }
  1014. [Fact]
  1015. public void Popup_Attached_To_Adorner_Respects_Adorner_Position()
  1016. {
  1017. using (CreateServices())
  1018. {
  1019. var popupTarget = new Border() { Height = 30, Background = Brushes.Red, [DockPanel.DockProperty] = Dock.Top };
  1020. var popupContent = new Border() { Height = 30, Width = 50, Background = Brushes.Yellow };
  1021. var popup = new Popup
  1022. {
  1023. Child = popupContent,
  1024. Placement = PlacementMode.AnchorAndGravity,
  1025. PlacementTarget = popupTarget,
  1026. PlacementAnchor = PopupAnchor.BottomRight,
  1027. PlacementGravity = PopupGravity.BottomRight
  1028. };
  1029. var adorner = new DockPanel() { Children = { popupTarget, popup },
  1030. HorizontalAlignment = HorizontalAlignment.Left,
  1031. Width = 40,
  1032. Margin = new Thickness(50, 5, 0, 0) };
  1033. var adorned = new Border() {
  1034. Width = 100,
  1035. Height = 100,
  1036. Background = Brushes.Blue,
  1037. [Canvas.LeftProperty] = 20,
  1038. [Canvas.TopProperty] = 40
  1039. };
  1040. var windowContent = new Canvas();
  1041. windowContent.Children.Add(adorned);
  1042. var root = PreparedWindow(windowContent);
  1043. var adornerLayer = AdornerLayer.GetAdornerLayer(adorned);
  1044. adornerLayer.Children.Add(adorner);
  1045. AdornerLayer.SetAdornedElement(adorner, adorned);
  1046. root.LayoutManager.ExecuteInitialLayoutPass();
  1047. popup.Open();
  1048. Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender);
  1049. // X: Adorned Canvas.Left + Adorner Margin Left + Adorner Width
  1050. // Y: Adorned Canvas.Top + Adorner Margin Top + Adorner Height
  1051. Assert.Equal(new PixelPoint(110, 75), popupContent.PointToScreen(new Point(0, 0)));
  1052. }
  1053. }
  1054. [Fact]
  1055. public void Custom_Placement_Callback_Is_Executed()
  1056. {
  1057. using (CreateServices())
  1058. {
  1059. var callbackExecuted = 0;
  1060. var popupContent = new Border { Width = 100, Height = 100 };
  1061. var popup = new Popup
  1062. {
  1063. Child = popupContent,
  1064. Placement = PlacementMode.Custom,
  1065. HorizontalOffset = 42,
  1066. VerticalOffset = 21
  1067. };
  1068. var popupParent = new Border { Child = popup };
  1069. var root = PreparedWindow(popupParent);
  1070. popup.CustomPopupPlacementCallback = (parameters) =>
  1071. {
  1072. Assert.Equal(popupContent.Width, parameters.PopupSize.Width);
  1073. Assert.Equal(popupContent.Height, parameters.PopupSize.Height);
  1074. Assert.Equal(root.Width, parameters.AnchorRectangle.Width);
  1075. Assert.Equal(root.Height, parameters.AnchorRectangle.Height);
  1076. Assert.Equal(popup.HorizontalOffset, parameters.Offset.X);
  1077. Assert.Equal(popup.VerticalOffset, parameters.Offset.Y);
  1078. callbackExecuted++;
  1079. parameters.Anchor = PopupAnchor.Top;
  1080. parameters.Gravity = PopupGravity.Bottom;
  1081. };
  1082. root.LayoutManager.ExecuteInitialLayoutPass();
  1083. popup.Open();
  1084. root.LayoutManager.ExecuteLayoutPass();
  1085. Assert.Equal(1, callbackExecuted);
  1086. }
  1087. }
  1088. private static PopupRoot CreateRoot(TopLevel popupParent, IPopupImpl impl = null)
  1089. {
  1090. impl ??= popupParent.PlatformImpl.CreatePopup();
  1091. var result = new PopupRoot(popupParent, impl)
  1092. {
  1093. Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
  1094. new ContentPresenter
  1095. {
  1096. Name = "PART_ContentPresenter",
  1097. [!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty],
  1098. }.RegisterInNameScope(scope)),
  1099. };
  1100. result.ApplyTemplate();
  1101. return result;
  1102. }
  1103. [Fact]
  1104. public void Popup_Open_With_Correct_IsUsingOverlayLayer_And_Disabled_OverlayLayer()
  1105. {
  1106. using (CreateServices())
  1107. {
  1108. var target = new Popup();
  1109. target.IsOpen = true;
  1110. target.ShouldUseOverlayLayer = false;
  1111. var window = PreparedWindow(target);
  1112. window.Show();
  1113. Assert.Equal(UsePopupHost, target.IsUsingOverlayLayer);
  1114. }
  1115. }
  1116. [Fact]
  1117. public void Popup_Open_With_Correct_IsUsingOverlayLayer_And_Enabled_OverlayLayer()
  1118. {
  1119. using (CreateServices())
  1120. {
  1121. var target = new Popup();
  1122. target.IsOpen = true;
  1123. target.ShouldUseOverlayLayer = true;
  1124. var window = PreparedWindow(target);
  1125. window.Show();
  1126. Assert.Equal(true, target.IsUsingOverlayLayer);
  1127. }
  1128. }
  1129. private IDisposable CreateServices()
  1130. {
  1131. return UnitTestApplication.Start(TestServices.StyledWindow.With(
  1132. windowingPlatform: CreateMockWindowingPlatform()));
  1133. }
  1134. private IDisposable CreateServicesWithFocus()
  1135. {
  1136. return UnitTestApplication.Start(TestServices.StyledWindow.With(
  1137. windowingPlatform: CreateMockWindowingPlatform(),
  1138. keyboardDevice: () => new KeyboardDevice(),
  1139. keyboardNavigation: () => new KeyboardNavigationHandler()));
  1140. }
  1141. private static PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p)
  1142. {
  1143. var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
  1144. return new PointerPressedEventArgs(
  1145. source,
  1146. pointer,
  1147. source,
  1148. p,
  1149. 0,
  1150. new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
  1151. KeyModifiers.None);
  1152. }
  1153. private MockWindowingPlatform CreateMockWindowingPlatform()
  1154. {
  1155. return new MockWindowingPlatform(() =>
  1156. {
  1157. var mock = MockWindowingPlatform.CreateWindowMock();
  1158. mock.Setup(x => x.CreatePopup()).Returns(() =>
  1159. {
  1160. if (UsePopupHost)
  1161. return null;
  1162. return MockWindowingPlatform.CreatePopupMock(mock.Object).Object;
  1163. });
  1164. return mock.Object;
  1165. }, null);
  1166. }
  1167. private static Window PreparedWindow(object content = null)
  1168. {
  1169. var w = new Window { Content = content };
  1170. w.Show();
  1171. w.ApplyStyling();
  1172. w.ApplyTemplate();
  1173. return w;
  1174. }
  1175. private static Control PopupContentControlTemplate(PopupContentControl control, INameScope scope)
  1176. {
  1177. return new Popup
  1178. {
  1179. Name = "popup",
  1180. PlacementTarget = control,
  1181. Child = new ContentPresenter
  1182. {
  1183. [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
  1184. }
  1185. }.RegisterInNameScope(scope);
  1186. }
  1187. private static Control PopupItemsControlTemplate(PopupItemsControl control, INameScope scope)
  1188. {
  1189. return new Popup
  1190. {
  1191. Name = "popup",
  1192. PlacementTarget = control,
  1193. Child = new ItemsPresenter(),
  1194. }.RegisterInNameScope(scope);
  1195. }
  1196. private class PopupContentControl : ContentControl
  1197. {
  1198. }
  1199. private class PopupItemsControl : ItemsControl
  1200. {
  1201. }
  1202. private class TestControl : Decorator
  1203. {
  1204. public event EventHandler DataContextBeginUpdate;
  1205. public new AvaloniaObject InheritanceParent => base.InheritanceParent;
  1206. protected override void OnDataContextBeginUpdate()
  1207. {
  1208. DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
  1209. base.OnDataContextBeginUpdate();
  1210. }
  1211. }
  1212. }
  1213. public class PopupTestsWithPopupRoot : PopupTests
  1214. {
  1215. public PopupTestsWithPopupRoot()
  1216. {
  1217. UsePopupHost = true;
  1218. }
  1219. }
  1220. }