View.Mouse.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. #nullable enable
  2. using System.ComponentModel;
  3. namespace Terminal.Gui;
  4. public partial class View // Mouse APIs
  5. {
  6. private ColorScheme? _savedHighlightColorScheme;
  7. /// <summary>
  8. /// Fired when the view is highlighted. Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/>
  9. /// to implement a custom highlight scheme or prevent the view from being highlighted.
  10. /// </summary>
  11. public event EventHandler<CancelEventArgs<HighlightStyle>>? Highlight;
  12. /// <summary>
  13. /// Gets or sets whether the <see cref="View"/> will be highlighted visually while the mouse button is
  14. /// pressed.
  15. /// </summary>
  16. public HighlightStyle HighlightStyle { get; set; }
  17. /// <summary>Event fired when a mouse click occurs.</summary>
  18. /// <remarks>
  19. /// <para>
  20. /// Fired when the mouse is either clicked or double-clicked. Check
  21. /// <see cref="MouseEvent.Flags"/> to see which button was clicked.
  22. /// </para>
  23. /// <para>
  24. /// The coordinates are relative to <see cref="View.Viewport"/>.
  25. /// </para>
  26. /// </remarks>
  27. public event EventHandler<MouseEventEventArgs>? MouseClick;
  28. /// <summary>Event fired when a mouse event occurs.</summary>
  29. /// <remarks>
  30. /// <para>
  31. /// The coordinates are relative to <see cref="View.Viewport"/>.
  32. /// </para>
  33. /// </remarks>
  34. public event EventHandler<MouseEventEventArgs>? MouseEvent;
  35. /// <summary>Event fired when the mouse leaves the View's <see cref="Viewport"/>.</summary>
  36. public event EventHandler<MouseEventEventArgs>? MouseLeave;
  37. /// <summary>
  38. /// Processes a <see cref="MouseEvent"/>. This method is called by <see cref="Application.OnMouseEvent"/> when a mouse
  39. /// event occurs.
  40. /// </summary>
  41. /// <remarks>
  42. /// <para>
  43. /// A view must be both enabled and visible to receive mouse events.
  44. /// </para>
  45. /// <para>
  46. /// This method calls <see cref="OnMouseEvent"/> to process the event. If the event is not handled, and one of the
  47. /// mouse buttons was clicked, it calls <see cref="OnMouseClick"/> to process the click.
  48. /// </para>
  49. /// <para>
  50. /// See <see cref="SetHighlight"/> for more information.
  51. /// </para>
  52. /// <para>
  53. /// If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, the <see cref="OnMouseClick"/> event
  54. /// will be invoked repeatedly while the button is pressed.
  55. /// </para>
  56. /// </remarks>
  57. /// <param name="mouseEvent"></param>
  58. /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
  59. public bool? NewMouseEvent (MouseEvent mouseEvent)
  60. {
  61. if (!Enabled)
  62. {
  63. // A disabled view should not eat mouse events
  64. return false;
  65. }
  66. if (!CanBeVisible (this))
  67. {
  68. return false;
  69. }
  70. if (!WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
  71. {
  72. return false;
  73. }
  74. if (OnMouseEvent (mouseEvent))
  75. {
  76. // Technically mouseEvent.Handled should already be true if implementers of OnMouseEvent
  77. // follow the rules. But we'll update it just in case.
  78. return mouseEvent.Handled = true;
  79. }
  80. if (HighlightStyle != HighlightStyle.None || (WantContinuousButtonPressed && WantMousePositionReports))
  81. {
  82. if (HandlePressed (mouseEvent))
  83. {
  84. return mouseEvent.Handled;
  85. }
  86. if (HandleReleased (mouseEvent))
  87. {
  88. return mouseEvent.Handled;
  89. }
  90. if (HandleClicked (mouseEvent))
  91. {
  92. return mouseEvent.Handled;
  93. }
  94. }
  95. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
  96. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked)
  97. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)
  98. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked)
  99. || mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
  100. || mouseEvent.Flags.HasFlag (MouseFlags.Button2DoubleClicked)
  101. || mouseEvent.Flags.HasFlag (MouseFlags.Button3DoubleClicked)
  102. || mouseEvent.Flags.HasFlag (MouseFlags.Button4DoubleClicked)
  103. || mouseEvent.Flags.HasFlag (MouseFlags.Button1TripleClicked)
  104. || mouseEvent.Flags.HasFlag (MouseFlags.Button2TripleClicked)
  105. || mouseEvent.Flags.HasFlag (MouseFlags.Button3TripleClicked)
  106. || mouseEvent.Flags.HasFlag (MouseFlags.Button4TripleClicked)
  107. )
  108. {
  109. // If it's a click, and we didn't handle it, then we'll call OnMouseClick
  110. // We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and
  111. // it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked
  112. return OnMouseClick (new (mouseEvent));
  113. }
  114. return false;
  115. }
  116. /// <summary>Gets or sets whether the <see cref="View"/> wants continuous button pressed events.</summary>
  117. public virtual bool WantContinuousButtonPressed { get; set; }
  118. /// <summary>Gets or sets whether the <see cref="View"/> wants mouse position reports.</summary>
  119. /// <value><see langword="true"/> if mouse position reports are wanted; otherwise, <see langword="false"/>.</value>
  120. public virtual bool WantMousePositionReports { get; set; }
  121. #region MouseEnterLeave
  122. /// <summary>
  123. /// INTERNAL Called by <see cref="Application.OnMouseEvent"/> when the mouse moves over the View's <see cref="Frame"/>.
  124. /// <see cref="MouseLeave"/> will
  125. /// be raised when the mouse is no longer over the <see cref="Frame"/>. If another View occludes this View, the
  126. /// that View will also receive MouseEnter/Leave events.
  127. /// </summary>
  128. /// <param name="eventArgs"></param>
  129. /// <returns>
  130. /// <see langword="true"/> if the event was canceled, <see langword="false"/> if not, <see langword="null"/> if the
  131. /// view is not visible. Cancelling the event
  132. /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
  133. /// </returns>
  134. internal bool? NewMouseEnterEvent (CancelEventArgs eventArgs)
  135. {
  136. if (!CanBeVisible (this))
  137. {
  138. return null;
  139. }
  140. if (OnMouseEnter (eventArgs))
  141. {
  142. return true;
  143. }
  144. MouseEnter?.Invoke (this, eventArgs);
  145. #if HOVER
  146. if (HighlightStyle.HasFlag(HighlightStyle.Hover))
  147. {
  148. if (SetHighlight (HighlightStyle.Hover))
  149. {
  150. return true;
  151. }
  152. }
  153. #endif
  154. return eventArgs.Cancel;
  155. }
  156. /// <summary>
  157. /// Called when the mouse moves over the View's <see cref="Frame"/> and no other non-Subview occludes it. <see cref="MouseLeave"/> will
  158. /// be raised when the mouse is no longer over the <see cref="Frame"/>.
  159. /// </summary>
  160. /// <remarks>
  161. /// <para>
  162. /// A view must be visible to receive Enter events (Leave events are always received).
  163. /// </para>
  164. /// <para>
  165. /// If the event is cancelled, the mouse event will not be propagated to other views and <see cref="MouseEnter"/>
  166. /// will not be raised.
  167. /// </para>
  168. /// <para>
  169. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  170. /// </para>
  171. /// <para>
  172. /// See <see cref="SetHighlight"/> for more information.
  173. /// </para>
  174. /// </remarks>
  175. /// <param name="eventArgs"></param>
  176. /// <returns>
  177. /// <see langword="true"/> if the event was canceled, <see langword="false"/> if not. Cancelling the event
  178. /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
  179. /// </returns>
  180. protected virtual bool OnMouseEnter (CancelEventArgs eventArgs) { return false; }
  181. /// <summary>
  182. /// Raised when the mouse moves over the View's <see cref="Frame"/>. <see cref="MouseLeave"/> will
  183. /// be raised when the mouse is no longer over the <see cref="Frame"/>. If another View occludes this View, the
  184. /// that View will also receive MouseEnter/Leave events.
  185. /// </summary>
  186. /// <remarks>
  187. /// <para>
  188. /// A view must be visible to receive Enter events (Leave events are always received).
  189. /// </para>
  190. /// <para>
  191. /// If the event is cancelled, the mouse event will not be propagated to other views.
  192. /// </para>
  193. /// <para>
  194. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  195. /// </para>
  196. /// <para>
  197. /// Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/> if the event was canceled,
  198. /// <see langword="false"/> if not. Cancelling the event
  199. /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
  200. /// </para>
  201. /// <para>
  202. /// See <see cref="SetHighlight"/> for more information.
  203. /// </para>
  204. /// </remarks>
  205. public event EventHandler<CancelEventArgs>? MouseEnter;
  206. #endregion MouseEnterLeave
  207. /// <summary>Called when a mouse event occurs within the view's <see cref="Viewport"/>.</summary>
  208. /// <remarks>
  209. /// <para>
  210. /// The coordinates are relative to <see cref="View.Viewport"/>.
  211. /// </para>
  212. /// </remarks>
  213. /// <param name="mouseEvent"></param>
  214. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  215. protected internal virtual bool OnMouseEvent (MouseEvent mouseEvent)
  216. {
  217. var args = new MouseEventEventArgs (mouseEvent);
  218. MouseEvent?.Invoke (this, args);
  219. return args.Handled;
  220. }
  221. /// <summary>
  222. /// Called by <see cref="NewMouseEvent"/> when a mouse leaves <see cref="Viewport"/>. The view will
  223. /// no longer receive mouse events.
  224. /// </summary>
  225. /// <remarks>
  226. /// <para>
  227. /// Override this method or subscribe to <see cref="MouseEnter"/> to change the default leave behavior.
  228. /// </para>
  229. /// <para>
  230. /// The coordinates are relative to <see cref="View.Viewport"/>.
  231. /// </para>
  232. /// </remarks>
  233. /// <param name="mouseEvent"></param>
  234. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  235. protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) { return false; }
  236. /// <summary>
  237. /// Called when the view is to be highlighted.
  238. /// </summary>
  239. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  240. protected virtual bool? OnHighlight (CancelEventArgs<HighlightStyle> args)
  241. {
  242. Highlight?.Invoke (this, args);
  243. if (args.Cancel)
  244. {
  245. return true;
  246. }
  247. Margin?.Highlight?.Invoke (this, args);
  248. //args = new (highlight);
  249. //Border?.Highlight?.Invoke (this, args);
  250. //args = new (highlight);
  251. //Padding?.Highlight?.Invoke (this, args);
  252. return args.Cancel;
  253. }
  254. /// <summary>Invokes the MouseClick event.</summary>
  255. /// <remarks>
  256. /// <para>
  257. /// Called when the mouse is either clicked or double-clicked. Check
  258. /// <see cref="MouseEvent.Flags"/> to see which button was clicked.
  259. /// </para>
  260. /// </remarks>
  261. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  262. protected bool OnMouseClick (MouseEventEventArgs args)
  263. {
  264. if (!Enabled)
  265. {
  266. // QUESTION: Is this right? Should a disabled view eat mouse clicks?
  267. return args.Handled = false;
  268. }
  269. MouseClick?.Invoke (this, args);
  270. if (args.Handled)
  271. {
  272. return true;
  273. }
  274. if (!HasFocus && CanFocus)
  275. {
  276. args.Handled = true;
  277. SetFocus ();
  278. }
  279. return args.Handled;
  280. }
  281. /// <summary>
  282. /// For cases where the view is grabbed and the mouse is clicked, this method handles the click event (typically
  283. /// when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
  284. /// </summary>
  285. /// <remarks>
  286. /// Marked internal just to support unit tests
  287. /// </remarks>
  288. /// <param name="mouseEvent"></param>
  289. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  290. internal bool HandleClicked (MouseEvent mouseEvent)
  291. {
  292. if (Application.MouseGrabView == this
  293. && (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
  294. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked)
  295. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)
  296. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked)))
  297. {
  298. // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab
  299. Application.UngrabMouse ();
  300. if (SetHighlight (HighlightStyle.None))
  301. {
  302. return true;
  303. }
  304. // If mouse is still in bounds, click
  305. if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.Position))
  306. {
  307. return OnMouseClick (new (mouseEvent));
  308. }
  309. return mouseEvent.Handled = true;
  310. }
  311. return false;
  312. }
  313. /// <summary>
  314. /// For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
  315. /// when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
  316. /// </summary>
  317. /// <remarks>
  318. /// Marked internal just to support unit tests
  319. /// </remarks>
  320. /// <param name="mouseEvent"></param>
  321. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  322. internal bool HandleReleased (MouseEvent mouseEvent)
  323. {
  324. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released)
  325. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Released)
  326. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Released)
  327. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Released))
  328. {
  329. if (Application.MouseGrabView == this)
  330. {
  331. SetHighlight (HighlightStyle.None);
  332. }
  333. return mouseEvent.Handled = true;
  334. }
  335. return false;
  336. }
  337. /// <summary>
  338. /// INTERNAL Called by <see cref="Application.OnMouseEvent"/> when the mouse leaves <see cref="Frame"/>.
  339. /// </summary>
  340. /// <remarks>
  341. /// <para>
  342. /// A view must be visible to receive Enter events (Leave events are always received).
  343. /// </para>
  344. /// <para>
  345. /// This method calls <see cref="OnMouseLeave"/> to raise the <see cref="MouseLeave"/> event.
  346. /// </para>
  347. /// <para>
  348. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  349. /// </para>
  350. /// <para>
  351. /// See <see cref="SetHighlight"/> for more information.
  352. /// </para>
  353. /// </remarks>
  354. /// <param name="mouseEvent"></param>
  355. /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise. </returns>
  356. internal bool? NewMouseLeaveEvent (MouseEvent mouseEvent)
  357. {
  358. if (OnMouseLeave (mouseEvent))
  359. {
  360. return true;
  361. }
  362. var args = new MouseEventEventArgs (mouseEvent);
  363. MouseLeave?.Invoke (this, args);
  364. #if HOVER
  365. if (HighlightStyle.HasFlag (HighlightStyle.Hover))
  366. {
  367. SetHighlight (HighlightStyle.None);
  368. }
  369. #endif
  370. return args.Handled;
  371. }
  372. /// <summary>
  373. /// Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent.
  374. /// </summary>
  375. /// <remarks>
  376. /// <para>
  377. /// Set <see cref="HighlightStyle"/> to have the view highlighted based on the mouse.
  378. /// </para>
  379. /// <para>
  380. /// Calls <see cref="OnHighlight"/> which fires the <see cref="Highlight"/> event.
  381. /// </para>
  382. /// <para>
  383. /// Marked internal just to support unit tests
  384. /// </para>
  385. /// </remarks>
  386. /// <returns><see langword="true"/>, if the Highlight event was handled, <see langword="false"/> otherwise.</returns>
  387. internal bool SetHighlight (HighlightStyle newHighlightStyle)
  388. {
  389. // TODO: Make the highlight colors configurable
  390. if (!CanFocus)
  391. {
  392. return false;
  393. }
  394. // Enable override via virtual method and/or event
  395. HighlightStyle copy = HighlightStyle;
  396. CancelEventArgs<HighlightStyle> args = new (ref copy, ref newHighlightStyle);
  397. if (OnHighlight (args) == true)
  398. {
  399. return true;
  400. }
  401. #if HOVER
  402. if (style.HasFlag (HighlightStyle.Hover))
  403. {
  404. if (_savedHighlightColorScheme is null && ColorScheme is { })
  405. {
  406. _savedHighlightColorScheme ??= ColorScheme;
  407. var cs = new ColorScheme (ColorScheme)
  408. {
  409. Normal = GetFocusColor (),
  410. HotNormal = ColorScheme.HotFocus
  411. };
  412. ColorScheme = cs;
  413. }
  414. return true;
  415. }
  416. #endif
  417. if (args.NewValue.HasFlag (HighlightStyle.Pressed) || args.NewValue.HasFlag (HighlightStyle.PressedOutside))
  418. {
  419. if (_savedHighlightColorScheme is null && ColorScheme is { })
  420. {
  421. _savedHighlightColorScheme ??= ColorScheme;
  422. if (CanFocus)
  423. {
  424. var cs = new ColorScheme (ColorScheme)
  425. {
  426. // Highlight the foreground focus color
  427. Focus = new (ColorScheme.Focus.Foreground.GetHighlightColor (), ColorScheme.Focus.Background.GetHighlightColor ())
  428. };
  429. ColorScheme = cs;
  430. }
  431. else
  432. {
  433. var cs = new ColorScheme (ColorScheme)
  434. {
  435. // Invert Focus color foreground/background. We can do this because we know the view is not going to be focused.
  436. Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground)
  437. };
  438. ColorScheme = cs;
  439. }
  440. }
  441. // Return false since we don't want to eat the event
  442. return false;
  443. }
  444. if (args.NewValue == HighlightStyle.None)
  445. {
  446. // Unhighlight
  447. if (_savedHighlightColorScheme is { })
  448. {
  449. ColorScheme = _savedHighlightColorScheme;
  450. _savedHighlightColorScheme = null;
  451. }
  452. }
  453. return false;
  454. }
  455. /// <summary>
  456. /// For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
  457. /// when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
  458. /// </summary>
  459. /// <remarks>
  460. /// <para>
  461. /// Marked internal just to support unit tests
  462. /// </para>
  463. /// </remarks>
  464. /// <param name="mouseEvent"></param>
  465. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  466. private bool HandlePressed (MouseEvent mouseEvent)
  467. {
  468. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
  469. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
  470. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)
  471. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed))
  472. {
  473. // The first time we get pressed event, grab the mouse and set focus
  474. if (Application.MouseGrabView != this)
  475. {
  476. Application.GrabMouse (this);
  477. if (!HasFocus && CanFocus)
  478. {
  479. // Set the focus, but don't invoke Accept
  480. SetFocus ();
  481. }
  482. mouseEvent.Handled = true;
  483. }
  484. if (Viewport.Contains (mouseEvent.Position))
  485. {
  486. if (this is not Adornment
  487. && SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None))
  488. {
  489. return true;
  490. }
  491. }
  492. else
  493. {
  494. if (this is not Adornment
  495. && SetHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None))
  496. {
  497. return true;
  498. }
  499. }
  500. if (WantContinuousButtonPressed && Application.MouseGrabView == this)
  501. {
  502. // If this is not the first pressed event, click
  503. return OnMouseClick (new (mouseEvent));
  504. }
  505. return mouseEvent.Handled = true;
  506. }
  507. return false;
  508. }
  509. /// <summary>
  510. /// INTERNAL: Gets the Views that are under the mouse at <paramref name="location"/>, including Adornments.
  511. /// </summary>
  512. /// <param name="location"></param>
  513. /// <returns></returns>
  514. internal static List<View?> GetViewsUnderMouse (in Point location)
  515. {
  516. List<View?> viewsUnderMouse = new ();
  517. View? start = Application.Current ?? Application.Top;
  518. Point currentLocation = location;
  519. while (start is { Visible: true } && start.Contains (currentLocation))
  520. {
  521. viewsUnderMouse.Add (start);
  522. Adornment? found = null;
  523. if (start.Margin.Contains (currentLocation))
  524. {
  525. found = start.Margin;
  526. }
  527. else if (start.Border.Contains (currentLocation))
  528. {
  529. found = start.Border;
  530. }
  531. else if (start.Padding.Contains (currentLocation))
  532. {
  533. found = start.Padding;
  534. }
  535. Point viewportOffset = start.GetViewportOffsetFromFrame ();
  536. if (found is { })
  537. {
  538. start = found;
  539. viewsUnderMouse.Add (start);
  540. viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
  541. }
  542. int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
  543. int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y);
  544. View? subview = null;
  545. for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
  546. {
  547. if (start.InternalSubviews [i].Visible
  548. && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
  549. {
  550. subview = start.InternalSubviews [i];
  551. currentLocation.X = startOffsetX + start.Viewport.X;
  552. currentLocation.Y = startOffsetY + start.Viewport.Y;
  553. // start is the deepest subview under the mouse; stop searching the subviews
  554. break;
  555. }
  556. }
  557. if (subview is null)
  558. {
  559. // No subview was found that's under the mouse, so we're done
  560. return viewsUnderMouse;
  561. }
  562. // We found a subview of start that's under the mouse, continue...
  563. start = subview;
  564. }
  565. return viewsUnderMouse;
  566. }
  567. }