ViewMouse.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. using System.ComponentModel;
  2. using System.Diagnostics;
  3. namespace Terminal.Gui;
  4. public partial class View
  5. {
  6. /// <summary>
  7. /// Gets or sets whether the <see cref="View"/> will be highlighted visually while the mouse button is
  8. /// pressed.
  9. /// </summary>
  10. public bool HighlightOnPress { get; set; }
  11. /// <summary>Gets or sets whether the <see cref="View"/> wants continuous button pressed events.</summary>
  12. public virtual bool WantContinuousButtonPressed { get; set; }
  13. /// <summary>Gets or sets whether the <see cref="View"/> wants mouse position reports.</summary>
  14. /// <value><see langword="true"/> if mouse position reports are wanted; otherwise, <see langword="false"/>.</value>
  15. public virtual bool WantMousePositionReports { get; set; }
  16. /// <summary>
  17. /// Called when the mouse enters the View's <see cref="Bounds"/>. The view will now receive mouse events until the
  18. /// mouse leaves
  19. /// the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/> will be called.
  20. /// </summary>
  21. /// <remarks>
  22. /// The coordinates are relative to <see cref="View.Bounds"/>.
  23. /// </remarks>
  24. /// <param name="mouseEvent"></param>
  25. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  26. protected internal virtual bool OnMouseEnter (MouseEvent mouseEvent)
  27. {
  28. if (!Enabled)
  29. {
  30. return true;
  31. }
  32. if (!CanBeVisible (this))
  33. {
  34. return false;
  35. }
  36. var args = new MouseEventEventArgs (mouseEvent);
  37. MouseEnter?.Invoke (this, args);
  38. return args.Handled;
  39. }
  40. /// <summary>Event fired when the mouse moves into the View's <see cref="Bounds"/>.</summary>
  41. public event EventHandler<MouseEventEventArgs> MouseEnter;
  42. /// <summary>
  43. /// Called when the mouse has moved out of the View's <see cref="Bounds"/>. The view will no longer receive mouse
  44. /// events (until the
  45. /// mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
  46. /// </summary>
  47. /// <remarks>
  48. /// The coordinates are relative to <see cref="View.Bounds"/>.
  49. /// </remarks>
  50. /// <param name="mouseEvent"></param>
  51. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  52. protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent)
  53. {
  54. if (!Enabled)
  55. {
  56. return true;
  57. }
  58. if (!CanBeVisible (this))
  59. {
  60. return false;
  61. }
  62. var args = new MouseEventEventArgs (mouseEvent);
  63. MouseLeave?.Invoke (this, args);
  64. return args.Handled;
  65. }
  66. /// <summary>Event fired when the mouse leaves the View's <see cref="Bounds"/>.</summary>
  67. public event EventHandler<MouseEventEventArgs> MouseLeave;
  68. /// <summary>
  69. /// Processes a <see cref="MouseEvent"/>. This method is called by <see cref="Application.OnMouseEvent"/> when a mouse
  70. /// event occurs.
  71. /// </summary>
  72. /// <remarks>
  73. /// <para>
  74. /// A view must be both enabled and visible to receive mouse events.
  75. /// </para>
  76. /// <para>
  77. /// This method calls <see cref="OnMouseEvent"/> to process the event. If the event is not handled, and one of the
  78. /// mouse buttons was clicked, it calls <see cref="OnMouseClick"/> to process the click.
  79. /// </para>
  80. /// <para>
  81. /// If <see cref="HighlightOnPress"/> is <see langword="true"/>, the view will be highlighted when the mouse is
  82. /// pressed.
  83. /// See <see cref="EnableHighlight"/> and <see cref="DisableHighlight"/> for more information.
  84. /// </para>
  85. /// <para>
  86. /// If <see cref="WantContinuousButtonPressed"/> is <see langword="true"/>, the <see cref="OnMouseClick"/> event
  87. /// will be invoked repeatedly while the button is pressed.
  88. /// </para>
  89. /// </remarks>
  90. /// <param name="mouseEvent"></param>
  91. /// <returns><see langword="true"/> if the event was handled, <see langword="false"/> otherwise.</returns>
  92. public bool? NewMouseEvent (MouseEvent mouseEvent)
  93. {
  94. if (!Enabled)
  95. {
  96. // A disabled view should not eat mouse events
  97. return false;
  98. }
  99. if (!CanBeVisible (this))
  100. {
  101. return false;
  102. }
  103. if (OnMouseEvent (mouseEvent))
  104. {
  105. // Technically mouseEvent.Handled should already be true if implementers of OnMouseEvent
  106. // follow the rules. But we'll update it just in case.
  107. return mouseEvent.Handled = true;
  108. }
  109. if ((HighlightOnPress || WantContinuousButtonPressed) && Highlight (mouseEvent))
  110. {
  111. Debug.Assert (mouseEvent.Handled);
  112. return mouseEvent.Handled;
  113. }
  114. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
  115. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked)
  116. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)
  117. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked))
  118. {
  119. return OnMouseClick (new (mouseEvent));
  120. }
  121. return false;
  122. }
  123. /// <summary>
  124. /// Highlight the view when the mouse is pressed.
  125. /// </summary>
  126. /// <remarks>
  127. /// <para>
  128. /// Set <see cref="HighlightOnPress"/> to <see langword="true"/> to have the view highlighted when the mouse is
  129. /// pressed.
  130. /// </para>
  131. /// <para>
  132. /// Calls <see cref="OnEnablingHighlight"/> which fires the <see cref="EnablingHighlight"/> event.
  133. /// </para>
  134. /// <para>
  135. /// Calls <see cref="OnDisablingHighlight"/> which fires the <see cref="DisablingHighlight"/> event.
  136. /// </para>
  137. /// </remarks>
  138. /// <param name="mouseEvent"></param>
  139. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  140. private bool Highlight (MouseEvent mouseEvent)
  141. {
  142. if (Application.MouseGrabView == this
  143. && (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
  144. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked)
  145. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)
  146. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked)))
  147. {
  148. // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab
  149. Application.UngrabMouse ();
  150. DisableHighlight ();
  151. // If mouse is still in bounds, click
  152. if (!WantContinuousButtonPressed && Bounds.Contains (mouseEvent.X, mouseEvent.Y))
  153. {
  154. return OnMouseClick (new (mouseEvent));
  155. }
  156. return mouseEvent.Handled = true;
  157. }
  158. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
  159. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
  160. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)
  161. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed))
  162. {
  163. // The first time we get pressed event, grab the mouse and set focus
  164. if (Application.MouseGrabView != this)
  165. {
  166. Application.GrabMouse (this);
  167. if (CanFocus)
  168. {
  169. // Set the focus, but don't invoke Accept
  170. SetFocus ();
  171. }
  172. }
  173. if (Bounds.Contains (mouseEvent.X, mouseEvent.Y))
  174. {
  175. EnableHighlight ();
  176. }
  177. else
  178. {
  179. DisableHighlight ();
  180. }
  181. if (WantContinuousButtonPressed && Application.MouseGrabView == this)
  182. {
  183. // If this is not the first pressed event, click
  184. return OnMouseClick (new (mouseEvent));
  185. }
  186. return mouseEvent.Handled = true;
  187. }
  188. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released)
  189. || mouseEvent.Flags.HasFlag (MouseFlags.Button2Released)
  190. || mouseEvent.Flags.HasFlag (MouseFlags.Button3Released)
  191. || mouseEvent.Flags.HasFlag (MouseFlags.Button4Released))
  192. {
  193. if (Application.MouseGrabView == this)
  194. {
  195. DisableHighlight ();
  196. }
  197. return mouseEvent.Handled = true;
  198. }
  199. return mouseEvent.Handled;
  200. }
  201. [CanBeNull]
  202. private ColorScheme _savedHighlightColorScheme;
  203. /// <summary>
  204. /// Enables the highlight for the view. Called from OnMouseEvent.
  205. /// </summary>
  206. public void EnableHighlight ()
  207. {
  208. if (OnEnablingHighlight () == true)
  209. {
  210. return;
  211. }
  212. if (_savedHighlightColorScheme is null && ColorScheme is { })
  213. {
  214. _savedHighlightColorScheme ??= ColorScheme;
  215. if (CanFocus)
  216. {
  217. // TODO: Make the inverted color configurable
  218. var cs = new ColorScheme (ColorScheme)
  219. {
  220. // For Buttons etc...
  221. Focus = new (ColorScheme.Normal.Foreground, ColorScheme.Focus.Background),
  222. // For Adornments
  223. Normal = new (ColorScheme.Focus.Foreground, ColorScheme.Normal.Background)
  224. };
  225. ColorScheme = cs;
  226. }
  227. else
  228. {
  229. var cs = new ColorScheme (ColorScheme)
  230. {
  231. // For Buttons etc... that can't focus (like up/down).
  232. Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground)
  233. };
  234. ColorScheme = cs;
  235. }
  236. }
  237. }
  238. /// <summary>
  239. /// Fired when the view is highlighted. Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/>
  240. /// to implement a custom highlight scheme or prevent the view from being highlighted.
  241. /// </summary>
  242. public event EventHandler<CancelEventArgs> EnablingHighlight;
  243. /// <summary>
  244. /// Called when the view is to be highlighted.
  245. /// </summary>
  246. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  247. protected virtual bool? OnEnablingHighlight ()
  248. {
  249. CancelEventArgs args = new ();
  250. EnablingHighlight?.Invoke (this, args);
  251. return args.Cancel;
  252. }
  253. /// <summary>
  254. /// Disables the highlight for the view. Called from OnMouseEvent.
  255. /// </summary>
  256. public void DisableHighlight ()
  257. {
  258. if (OnDisablingHighlight () == true)
  259. {
  260. return;
  261. }
  262. // Unhighlight
  263. if (_savedHighlightColorScheme is { })
  264. {
  265. ColorScheme = _savedHighlightColorScheme;
  266. _savedHighlightColorScheme = null;
  267. }
  268. }
  269. /// <summary>
  270. /// Fired when the view is no longer to be highlighted. Set <see cref="CancelEventArgs.Cancel"/> to
  271. /// <see langword="true"/>
  272. /// to implement a custom highlight scheme or prevent the view from being highlighted.
  273. /// </summary>
  274. public event EventHandler<CancelEventArgs> DisablingHighlight;
  275. /// <summary>
  276. /// Called when the view is no longer to be highlighted.
  277. /// </summary>
  278. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  279. protected virtual bool? OnDisablingHighlight ()
  280. {
  281. CancelEventArgs args = new ();
  282. DisablingHighlight?.Invoke (this, args);
  283. return args.Cancel;
  284. }
  285. /// <summary>Called when a mouse event occurs within the view's <see cref="Bounds"/>.</summary>
  286. /// <remarks>
  287. /// <para>
  288. /// The coordinates are relative to <see cref="View.Bounds"/>.
  289. /// </para>
  290. /// </remarks>
  291. /// <param name="mouseEvent"></param>
  292. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  293. protected internal virtual bool OnMouseEvent (MouseEvent mouseEvent)
  294. {
  295. var args = new MouseEventEventArgs (mouseEvent);
  296. MouseEvent?.Invoke (this, args);
  297. return args.Handled;
  298. }
  299. /// <summary>Event fired when a mouse event occurs.</summary>
  300. /// <remarks>
  301. /// <para>
  302. /// The coordinates are relative to <see cref="View.Bounds"/>.
  303. /// </para>
  304. /// </remarks>
  305. public event EventHandler<MouseEventEventArgs> MouseEvent;
  306. /// <summary>Invokes the MouseClick event.</summary>
  307. /// <remarks>
  308. /// <para>
  309. /// Called when the mouse is either clicked or double-clicked. Check
  310. /// <see cref="MouseEvent.Flags"/> to see which button was clicked.
  311. /// </para>
  312. /// </remarks>
  313. /// <returns><see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
  314. protected bool OnMouseClick (MouseEventEventArgs args)
  315. {
  316. if (!Enabled)
  317. {
  318. // QUESTION: Is this right? Should a disabled view eat mouse clicks?
  319. args.Handled = true;
  320. return true;
  321. }
  322. MouseClick?.Invoke (this, args);
  323. if (args.Handled)
  324. {
  325. return true;
  326. }
  327. if (!HasFocus && CanFocus)
  328. {
  329. args.Handled = true;
  330. SetFocus ();
  331. }
  332. return args.Handled;
  333. }
  334. /// <summary>Event fired when a mouse click occurs.</summary>
  335. /// <remarks>
  336. /// <para>
  337. /// Fired when the mouse is either clicked or double-clicked. Check
  338. /// <see cref="MouseEvent.Flags"/> to see which button was clicked.
  339. /// </para>
  340. /// <para>
  341. /// The coordinates are relative to <see cref="View.Bounds"/>.
  342. /// </para>
  343. /// </remarks>
  344. public event EventHandler<MouseEventEventArgs> MouseClick;
  345. }