View.Mouse.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. #nullable enable
  2. using System.ComponentModel;
  3. namespace Terminal.Gui.ViewBase;
  4. public partial class View // Mouse APIs
  5. {
  6. /// <summary>Gets the mouse bindings for this view.</summary>
  7. public MouseBindings MouseBindings { get; internal set; } = null!;
  8. private void SetupMouse ()
  9. {
  10. MouseBindings = new ();
  11. // TODO: Should the default really work with any button or just button1?
  12. MouseBindings.Add (MouseFlags.Button1Clicked, Command.Select);
  13. MouseBindings.Add (MouseFlags.Button2Clicked, Command.Select);
  14. MouseBindings.Add (MouseFlags.Button3Clicked, Command.Select);
  15. MouseBindings.Add (MouseFlags.Button4Clicked, Command.Select);
  16. MouseBindings.Add (MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl, Command.Select);
  17. }
  18. /// <summary>
  19. /// Invokes the Commands bound to the MouseFlags specified by <paramref name="mouseEventArgs"/>.
  20. /// <para>See <see href="../docs/mouse.md">for an overview of Terminal.Gui mouse APIs.</see></para>
  21. /// </summary>
  22. /// <param name="mouseEventArgs">The mouse event passed.</param>
  23. /// <returns>
  24. /// <see langword="null"/> if no command was invoked; input processing should continue.
  25. /// <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input processing
  26. /// should continue.
  27. /// <see langword="true"/> if at least one command was invoked and handled (or cancelled); input processing should
  28. /// stop.
  29. /// </returns>
  30. protected bool? InvokeCommandsBoundToMouse (MouseEventArgs mouseEventArgs)
  31. {
  32. if (!MouseBindings.TryGet (mouseEventArgs.Flags, out MouseBinding binding))
  33. {
  34. return null;
  35. }
  36. binding.MouseEventArgs = mouseEventArgs;
  37. return InvokeCommands (binding.Commands, binding);
  38. }
  39. #region MouseEnterLeave
  40. /// <summary>
  41. /// Gets whether the mouse is currently hovering over the View's <see cref="Viewport"/>. Is <see langword="true"/> after
  42. /// <see cref="MouseEnter"/> has been raised, and before <see cref="MouseLeave"/> is raised.
  43. /// </summary>
  44. public bool MouseHovering { get; internal set; }
  45. /// <summary>
  46. /// INTERNAL Called by <see cref="Application.RaiseMouseEvent"/> when the mouse moves over the View's
  47. /// <see cref="Frame"/>.
  48. /// <see cref="MouseLeave"/> will
  49. /// be raised when the mouse is no longer over the <see cref="Frame"/>. If another View occludes this View, the
  50. /// that View will also receive MouseEnter/Leave events.
  51. /// </summary>
  52. /// <param name="eventArgs"></param>
  53. /// <returns>
  54. /// <see langword="true"/> if the event was canceled, <see langword="false"/> if not, <see langword="null"/> if the
  55. /// view is not visible. Cancelling the event
  56. /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
  57. /// </returns>
  58. internal bool? NewMouseEnterEvent (CancelEventArgs eventArgs)
  59. {
  60. // Pre-conditions
  61. if (!CanBeVisible (this))
  62. {
  63. return null;
  64. }
  65. // Cancellable event
  66. if (OnMouseEnter (eventArgs))
  67. {
  68. return true;
  69. }
  70. MouseEnter?.Invoke (this, eventArgs);
  71. if (eventArgs.Cancel)
  72. {
  73. return true;
  74. }
  75. MouseHovering = true;
  76. if (HighlightStyle != HighlightStyle.None)
  77. {
  78. SetNeedsDraw ();
  79. }
  80. return false;
  81. }
  82. /// <summary>
  83. /// Gets the <see cref="Drawing.Scheme"/> to use when the view is highlighted. The highlight colorscheme
  84. /// is based on the current <see cref="Drawing.Scheme"/>, using <see cref="Color.GetBrighterColor"/>.
  85. /// </summary>
  86. /// <remarks>The highlight scheme.</remarks>
  87. public Scheme GetHighlightScheme ()
  88. {
  89. Scheme cs = GetScheme ();
  90. return cs with
  91. {
  92. Normal = new (
  93. GetAttributeForRole (VisualRole.Normal).Foreground.GetBrighterColor (),
  94. GetAttributeForRole (VisualRole.Normal).Background,
  95. GetAttributeForRole (VisualRole.Normal).Style),
  96. HotNormal = new (
  97. GetAttributeForRole (VisualRole.HotNormal).Foreground.GetBrighterColor (),
  98. GetAttributeForRole (VisualRole.HotNormal).Background,
  99. GetAttributeForRole (VisualRole.HotNormal).Style),
  100. Focus = new (
  101. GetAttributeForRole (VisualRole.Focus).Foreground.GetBrighterColor (),
  102. GetAttributeForRole (VisualRole.Focus).Background,
  103. GetAttributeForRole (VisualRole.Focus).Style),
  104. HotFocus = new (
  105. GetAttributeForRole (VisualRole.HotFocus).Foreground.GetBrighterColor (),
  106. GetAttributeForRole (VisualRole.HotFocus).Background,
  107. GetAttributeForRole (VisualRole.HotFocus).Style)
  108. };
  109. }
  110. /// <summary>
  111. /// Called when the mouse moves over the View's <see cref="Frame"/> and no other non-SubView occludes it.
  112. /// <see cref="MouseLeave"/> will
  113. /// be raised when the mouse is no longer over the <see cref="Frame"/>.
  114. /// </summary>
  115. /// <remarks>
  116. /// <para>
  117. /// A view must be visible to receive Enter events (Leave events are always received).
  118. /// </para>
  119. /// <para>
  120. /// If the event is cancelled, the mouse event will not be propagated to other views and <see cref="MouseEnter"/>
  121. /// will not be raised.
  122. /// </para>
  123. /// <para>
  124. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  125. /// </para>
  126. /// <para>
  127. /// See <see cref="SetPressedHighlight"/> for more information.
  128. /// </para>
  129. /// </remarks>
  130. /// <param name="eventArgs"></param>
  131. /// <returns>
  132. /// <see langword="true"/> if the event was canceled, <see langword="false"/> if not. Cancelling the event
  133. /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
  134. /// </returns>
  135. protected virtual bool OnMouseEnter (CancelEventArgs eventArgs) { return false; }
  136. /// <summary>
  137. /// Raised when the mouse moves over the View's <see cref="Frame"/>. <see cref="MouseLeave"/> will
  138. /// be raised when the mouse is no longer over the <see cref="Frame"/>. If another View occludes this View, the
  139. /// that View will also receive MouseEnter/Leave events.
  140. /// </summary>
  141. /// <remarks>
  142. /// <para>
  143. /// A view must be visible to receive Enter events (Leave events are always received).
  144. /// </para>
  145. /// <para>
  146. /// If the event is cancelled, the mouse event will not be propagated to other views.
  147. /// </para>
  148. /// <para>
  149. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  150. /// </para>
  151. /// <para>
  152. /// Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/> if the event was canceled,
  153. /// <see langword="false"/> if not. Cancelling the event
  154. /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
  155. /// </para>
  156. /// <para>
  157. /// See <see cref="SetPressedHighlight"/> for more information.
  158. /// </para>
  159. /// </remarks>
  160. public event EventHandler<CancelEventArgs>? MouseEnter;
  161. /// <summary>
  162. /// INTERNAL Called by <see cref="Application.RaiseMouseEvent"/> when the mouse leaves <see cref="Frame"/>, or is
  163. /// occluded
  164. /// by another non-SubView.
  165. /// </summary>
  166. /// <remarks>
  167. /// <para>
  168. /// This method calls <see cref="OnMouseLeave"/> and raises the <see cref="MouseLeave"/> event.
  169. /// </para>
  170. /// <para>
  171. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  172. /// </para>
  173. /// <para>
  174. /// See <see cref="SetPressedHighlight"/> for more information.
  175. /// </para>
  176. /// </remarks>
  177. internal void NewMouseLeaveEvent ()
  178. {
  179. // Pre-conditions
  180. // Non-cancellable event
  181. OnMouseLeave ();
  182. MouseLeave?.Invoke (this, EventArgs.Empty);
  183. MouseHovering = false;
  184. if (HighlightStyle != HighlightStyle.None)
  185. {
  186. SetNeedsDraw ();
  187. }
  188. }
  189. /// <summary>
  190. /// Called when the mouse moves outside View's <see cref="Frame"/>, or is occluded by another non-SubView.
  191. /// </summary>
  192. /// <remarks>
  193. /// <para>
  194. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  195. /// </para>
  196. /// <para>
  197. /// See <see cref="SetPressedHighlight"/> for more information.
  198. /// </para>
  199. /// </remarks>
  200. protected virtual void OnMouseLeave () { }
  201. /// <summary>
  202. /// Raised when the mouse moves outside View's <see cref="Frame"/>, or is occluded by another non-SubView.
  203. /// </summary>
  204. /// <remarks>
  205. /// <para>
  206. /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's <see cref="Thickness"/>.
  207. /// </para>
  208. /// <para>
  209. /// See <see cref="SetPressedHighlight"/> for more information.
  210. /// </para>
  211. /// </remarks>
  212. public event EventHandler? MouseLeave;
  213. #endregion MouseEnterLeave
  214. #region Low Level Mouse Events
  215. /// <summary>Gets or sets whether the <see cref="View"/> wants continuous button pressed events.</summary>
  216. public virtual bool WantContinuousButtonPressed { get; set; }
  217. /// <summary>Gets or sets whether the <see cref="View"/> wants mouse position reports.</summary>
  218. /// <value><see langword="true"/> if mouse position reports are wanted; otherwise, <see langword="false"/>.</value>
  219. public bool WantMousePositionReports { get; set; }
  220. /// <summary>
  221. /// Processes a new <see cref="MouseEvent"/>. This method is called by <see cref="Application.RaiseMouseEvent"/> when a
  222. /// mouse
  223. /// event occurs.
  224. /// </summary>
  225. /// <remarks>
  226. /// <para>
  227. /// A view must be both enabled and visible to receive mouse events.
  228. /// </para>
  229. /// <para>
  230. /// This method raises <see cref="RaiseMouseEvent"/>/<see cref="MouseEvent"/>; if not handled, and one of the
  231. /// mouse buttons was clicked, the <see cref="RaiseMouseClickEvent"/>/<see cref="MouseClick"/> event will be raised
  232. /// </para>
  233. /// <para>
  234. /// See <see cref="SetPressedHighlight"/> for more information.
  235. /// </para>
  236. /// <para>
  237. /// If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, the <see cref="RaiseMouseEvent"/>/
  238. /// <see cref="MouseEvent"/> event
  239. /// will be raised on any new mouse event where <see cref="MouseEventArgs.Flags"/> indicates a button
  240. /// is pressed.
  241. /// </para>
  242. /// </remarks>
  243. /// <param name="mouseEvent"></param>
  244. /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
  245. public bool? NewMouseEvent (MouseEventArgs mouseEvent)
  246. {
  247. // Pre-conditions
  248. if (!Enabled)
  249. {
  250. // A disabled view should not eat mouse events
  251. return false;
  252. }
  253. if (!CanBeVisible (this))
  254. {
  255. return false;
  256. }
  257. if (!WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
  258. {
  259. return false;
  260. }
  261. // Cancellable event
  262. if (RaiseMouseEvent (mouseEvent) || mouseEvent.Handled)
  263. {
  264. return true;
  265. }
  266. // Post-Conditions
  267. if (HighlightStyle != HighlightStyle.None || WantContinuousButtonPressed)
  268. {
  269. if (WhenGrabbedHandlePressed (mouseEvent))
  270. {
  271. return mouseEvent.Handled;
  272. }
  273. if (WhenGrabbedHandleReleased (mouseEvent))
  274. {
  275. return mouseEvent.Handled;
  276. }
  277. if (WhenGrabbedHandleClicked (mouseEvent))
  278. {
  279. return mouseEvent.Handled;
  280. }
  281. }
  282. // We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and
  283. // it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked
  284. if (mouseEvent.IsSingleDoubleOrTripleClicked)
  285. {
  286. return RaiseMouseClickEvent (mouseEvent);
  287. }
  288. if (mouseEvent.IsWheel)
  289. {
  290. return RaiseMouseWheelEvent (mouseEvent);
  291. }
  292. return false;
  293. }
  294. /// <summary>
  295. /// Raises the <see cref="RaiseMouseEvent"/>/<see cref="MouseEvent"/> event.
  296. /// </summary>
  297. /// <param name="mouseEvent"></param>
  298. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  299. public bool RaiseMouseEvent (MouseEventArgs mouseEvent)
  300. {
  301. if (OnMouseEvent (mouseEvent) || mouseEvent.Handled)
  302. {
  303. return true;
  304. }
  305. MouseEvent?.Invoke (this, mouseEvent);
  306. return mouseEvent.Handled;
  307. }
  308. /// <summary>Called when a mouse event occurs within the view's <see cref="Viewport"/>.</summary>
  309. /// <remarks>
  310. /// <para>
  311. /// The coordinates are relative to <see cref="View.Viewport"/>.
  312. /// </para>
  313. /// </remarks>
  314. /// <param name="mouseEvent"></param>
  315. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  316. protected virtual bool OnMouseEvent (MouseEventArgs mouseEvent) { return false; }
  317. /// <summary>Raised when a mouse event occurs.</summary>
  318. /// <remarks>
  319. /// <para>
  320. /// The coordinates are relative to <see cref="View.Viewport"/>.
  321. /// </para>
  322. /// </remarks>
  323. public event EventHandler<MouseEventArgs>? MouseEvent;
  324. #endregion Low Level Mouse Events
  325. #region Mouse Pressed Events
  326. /// <summary>
  327. /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event
  328. /// (typically
  329. /// when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
  330. /// </summary>
  331. /// <remarks>
  332. /// Marked internal just to support unit tests
  333. /// </remarks>
  334. /// <param name="mouseEvent"></param>
  335. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  336. internal bool WhenGrabbedHandleReleased (MouseEventArgs mouseEvent)
  337. {
  338. mouseEvent.Handled = false;
  339. if (mouseEvent.IsReleased)
  340. {
  341. if (Application.MouseGrabView == this)
  342. {
  343. SetPressedHighlight (HighlightStyle.None);
  344. }
  345. return mouseEvent.Handled = true;
  346. }
  347. return false;
  348. }
  349. /// <summary>
  350. /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event
  351. /// (typically
  352. /// when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
  353. /// </summary>
  354. /// <remarks>
  355. /// <para>
  356. /// Marked internal just to support unit tests
  357. /// </para>
  358. /// </remarks>
  359. /// <param name="mouseEvent"></param>
  360. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  361. private bool WhenGrabbedHandlePressed (MouseEventArgs mouseEvent)
  362. {
  363. mouseEvent.Handled = false;
  364. if (mouseEvent.IsPressed)
  365. {
  366. // The first time we get pressed event, grab the mouse and set focus
  367. if (Application.MouseGrabView != this)
  368. {
  369. Application.GrabMouse (this);
  370. if (!HasFocus && CanFocus)
  371. {
  372. // Set the focus, but don't invoke Accept
  373. SetFocus ();
  374. }
  375. mouseEvent.Handled = true;
  376. }
  377. if (Viewport.Contains (mouseEvent.Position))
  378. {
  379. if (this is not Adornment
  380. && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None))
  381. {
  382. return true;
  383. }
  384. }
  385. else
  386. {
  387. if (this is not Adornment
  388. && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None))
  389. {
  390. return true;
  391. }
  392. }
  393. if (WantContinuousButtonPressed && Application.MouseGrabView == this)
  394. {
  395. return RaiseMouseClickEvent (mouseEvent);
  396. }
  397. return mouseEvent.Handled = true;
  398. }
  399. return false;
  400. }
  401. #endregion Mouse Pressed Events
  402. #region Mouse Click Events
  403. /// <summary>Raises the <see cref="OnMouseClick"/>/<see cref="MouseClick"/> event.</summary>
  404. /// <remarks>
  405. /// <para>
  406. /// Called when the mouse is either clicked or double-clicked.
  407. /// </para>
  408. /// <para>
  409. /// If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, will be invoked on every mouse event
  410. /// where
  411. /// the mouse button is pressed.
  412. /// </para>
  413. /// </remarks>
  414. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  415. protected bool RaiseMouseClickEvent (MouseEventArgs args)
  416. {
  417. // Pre-conditions
  418. if (!Enabled)
  419. {
  420. // QUESTION: Is this right? Should a disabled view eat mouse clicks?
  421. return args.Handled = false;
  422. }
  423. // Cancellable event
  424. if (OnMouseClick (args) || args.Handled)
  425. {
  426. return args.Handled;
  427. }
  428. MouseClick?.Invoke (this, args);
  429. if (args.Handled)
  430. {
  431. return true;
  432. }
  433. // Post-conditions
  434. // By default, this will raise Selecting/OnSelecting - Subclasses can override this via AddCommand (Command.Select ...).
  435. args.Handled = InvokeCommandsBoundToMouse (args) == true;
  436. return args.Handled;
  437. }
  438. /// <summary>
  439. /// Called when a mouse click occurs. Check <see cref="MouseEventArgs.Flags"/> to see which button was clicked.
  440. /// </summary>
  441. /// <remarks>
  442. /// <para>
  443. /// Called when the mouse is either clicked or double-clicked.
  444. /// </para>
  445. /// <para>
  446. /// If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, will be called on every mouse event
  447. /// where
  448. /// the mouse button is pressed.
  449. /// </para>
  450. /// </remarks>
  451. /// <param name="args"></param>
  452. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  453. protected virtual bool OnMouseClick (MouseEventArgs args) { return false; }
  454. /// <summary>Raised when a mouse click occurs.</summary>
  455. /// <remarks>
  456. /// <para>
  457. /// Raised when the mouse is either clicked or double-clicked.
  458. /// </para>
  459. /// <para>
  460. /// If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, will be raised on every mouse event
  461. /// where
  462. /// the mouse button is pressed.
  463. /// </para>
  464. /// </remarks>
  465. public event EventHandler<MouseEventArgs>? MouseClick;
  466. /// <summary>
  467. /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the click event
  468. /// (typically
  469. /// when <see cref="WantContinuousButtonPressed"/> or <see cref="HighlightStyle"/> are set).
  470. /// </summary>
  471. /// <remarks>
  472. /// Marked internal just to support unit tests
  473. /// </remarks>
  474. /// <param name="mouseEvent"></param>
  475. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  476. internal bool WhenGrabbedHandleClicked (MouseEventArgs mouseEvent)
  477. {
  478. mouseEvent.Handled = false;
  479. if (Application.MouseGrabView == this && mouseEvent.IsSingleClicked)
  480. {
  481. // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab
  482. Application.UngrabMouse ();
  483. if (SetPressedHighlight (HighlightStyle.None))
  484. {
  485. return true;
  486. }
  487. // If mouse is still in bounds, generate a click
  488. if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position))
  489. {
  490. return RaiseMouseClickEvent (mouseEvent);
  491. }
  492. return mouseEvent.Handled = true;
  493. }
  494. return false;
  495. }
  496. #endregion Mouse Clicked Events
  497. #region Mouse Wheel Events
  498. /// <summary>Raises the <see cref="OnMouseWheel"/>/<see cref="MouseWheel"/> event.</summary>
  499. /// <remarks>
  500. /// </remarks>
  501. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  502. protected bool RaiseMouseWheelEvent (MouseEventArgs args)
  503. {
  504. // Pre-conditions
  505. if (!Enabled)
  506. {
  507. // QUESTION: Is this right? Should a disabled view eat mouse?
  508. return args.Handled = false;
  509. }
  510. // Cancellable event
  511. if (OnMouseWheel (args) || args.Handled)
  512. {
  513. return args.Handled;
  514. }
  515. MouseWheel?.Invoke (this, args);
  516. if (args.Handled)
  517. {
  518. return true;
  519. }
  520. args.Handled = InvokeCommandsBoundToMouse (args) == true;
  521. return args.Handled;
  522. }
  523. /// <summary>
  524. /// Called when a mouse wheel event occurs. Check <see cref="MouseEventArgs.Flags"/> to see which wheel was moved was
  525. /// clicked.
  526. /// </summary>
  527. /// <remarks>
  528. /// </remarks>
  529. /// <param name="args"></param>
  530. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  531. protected virtual bool OnMouseWheel (MouseEventArgs args) { return false; }
  532. /// <summary>Raised when a mouse wheel event occurs.</summary>
  533. /// <remarks>
  534. /// </remarks>
  535. public event EventHandler<MouseEventArgs>? MouseWheel;
  536. #endregion Mouse Wheel Events
  537. #region Highlight Handling
  538. /// <summary>
  539. /// Gets or sets whether the <see cref="View"/> will be highlighted visually by mouse interaction.
  540. /// </summary>
  541. public HighlightStyle HighlightStyle { get; set; }
  542. /// <summary>
  543. /// INTERNAL Raises the <see cref="Highlight"/> event. Returns <see langword="true"/> if the event was handled,
  544. /// <see langword="false"/> otherwise.
  545. /// </summary>
  546. /// <param name="args"></param>
  547. /// <returns></returns>
  548. private bool RaiseHighlight (CancelEventArgs<HighlightStyle> args)
  549. {
  550. if (OnHighlight (args))
  551. {
  552. return true;
  553. }
  554. Highlight?.Invoke (this, args);
  555. return args.Cancel;
  556. }
  557. /// <summary>
  558. /// Called when the view is to be highlighted. The <see cref="HighlightStyle"/> passed in the event indicates the
  559. /// highlight style that will be applied. The view can modify the highlight style by setting the
  560. /// <see cref="CancelEventArgs{T}.NewValue"/> property.
  561. /// </summary>
  562. /// <param name="args">
  563. /// Set the <see cref="CancelEventArgs{T}.NewValue"/> property to <see langword="true"/>, to cancel, indicating custom
  564. /// highlighting.
  565. /// </param>
  566. /// <returns><see langword="true"/>, to cancel, indicating custom highlighting.</returns>
  567. protected virtual bool OnHighlight (CancelEventArgs<HighlightStyle> args) { return false; }
  568. /// <summary>
  569. /// Raised when the view is to be highlighted. The <see cref="HighlightStyle"/> passed in the event indicates the
  570. /// highlight style that will be applied. The view can modify the highlight style by setting the
  571. /// <see cref="CancelEventArgs{T}.NewValue"/> property.
  572. /// Set to <see langword="true"/>, to cancel, indicating custom highlighting.
  573. /// </summary>
  574. public event EventHandler<CancelEventArgs<HighlightStyle>>? Highlight;
  575. /// <summary>
  576. /// INTERNAL Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent.
  577. /// </summary>
  578. /// <remarks>
  579. /// <para>
  580. /// Set <see cref="HighlightStyle"/> to <see cref="HighlightStyle.Pressed"/> and/or
  581. /// <see cref="HighlightStyle.PressedOutside"/> to enable.
  582. /// </para>
  583. /// <para>
  584. /// Calls <see cref="OnHighlight"/> and raises the <see cref="Highlight"/> event.
  585. /// </para>
  586. /// <para>
  587. /// Marked internal just to support unit tests
  588. /// </para>
  589. /// </remarks>
  590. /// <returns><see langword="true"/>, if the Highlight event was handled, <see langword="false"/> otherwise.</returns>
  591. internal bool SetPressedHighlight (HighlightStyle newHighlightStyle)
  592. {
  593. // TODO: Make the highlight colors configurable
  594. if (!CanFocus)
  595. {
  596. return false;
  597. }
  598. HighlightStyle copy = HighlightStyle;
  599. CancelEventArgs<HighlightStyle> args = new (ref copy, ref newHighlightStyle);
  600. if (RaiseHighlight (args) || args.Cancel)
  601. {
  602. return true;
  603. }
  604. // For 3D Pressed Style - Note we don't care about canceling the event here
  605. Margin?.RaiseHighlight (args);
  606. return args.Cancel;
  607. }
  608. #endregion Highlight Handling
  609. private void DisposeMouse () { }
  610. }