MenuBar.cs 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856
  1. #nullable enable
  2. namespace Terminal.Gui;
  3. /// <summary>
  4. /// <para>Provides a menu bar that spans the top of a <see cref="Toplevel"/> View with drop-down and cascading menus.</para>
  5. /// <para>
  6. /// By default, any sub-sub-menus (sub-menus of the <see cref="MenuItem"/>s added to <see cref="MenuBarItem"/>s)
  7. /// are displayed in a cascading manner, where each sub-sub-menu pops out of the sub-menu frame (either to the
  8. /// right or left, depending on where the sub-menu is relative to the edge of the screen). By setting
  9. /// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all
  10. /// sub-sub-menus are drawn within a single frame below the MenuBar.
  11. /// </para>
  12. /// </summary>
  13. /// <remarks>
  14. /// <para>
  15. /// The <see cref="MenuBar"/> appears on the first row of the <see cref="Toplevel"/> SuperView and uses the full
  16. /// width.
  17. /// </para>
  18. /// <para>See also: <see cref="ContextMenu"/></para>
  19. /// <para>The <see cref="MenuBar"/> provides global hot keys for the application. See <see cref="MenuItem.HotKey"/>.</para>
  20. /// <para>
  21. /// When the menu is created key bindings for each menu item and its sub-menu items are added for each menu
  22. /// item's hot key (both alone AND with AltMask) and shortcut, if defined.
  23. /// </para>
  24. /// <para>
  25. /// If a key press matches any of the menu item's hot keys or shortcuts, the menu item's action is invoked or
  26. /// sub-menu opened.
  27. /// </para>
  28. /// <para>
  29. /// * If the menu bar is not open * Any shortcut defined within the menu will be invoked * Only hot keys defined
  30. /// for the menu bar items will be invoked, and only if Alt is pressed too. * If the menu bar is open * Un-shifted
  31. /// hot keys defined for the menu bar items will be invoked, only if the menu they belong to is open (the menu bar
  32. /// item's text is visible). * Alt-shifted hot keys defined for the menu bar items will be invoked, only if the
  33. /// menu they belong to is open (the menu bar item's text is visible). * If there is a visible hot key that
  34. /// duplicates a shortcut (e.g. _File and Alt-F), the hot key wins.
  35. /// </para>
  36. /// </remarks>
  37. public class MenuBar : View, IDesignable
  38. {
  39. // Spaces before the Title
  40. private static readonly int _leftPadding = 1;
  41. // Spaces after the submenu Title, before Help
  42. private static readonly int _parensAroundHelp = 3;
  43. // Spaces after the Title
  44. private static readonly int _rightPadding = 1;
  45. // The column where the MenuBar starts
  46. private static readonly int _xOrigin = 0;
  47. internal bool _isMenuClosing;
  48. internal bool _isMenuOpening;
  49. internal Menu? _openMenu;
  50. internal List<Menu>? _openSubMenu;
  51. internal int _selected;
  52. internal int _selectedSub;
  53. private bool _initialCanFocus;
  54. private bool _isCleaning;
  55. private View? _lastFocused;
  56. private Menu? _ocm;
  57. private View? _previousFocused;
  58. private bool _reopen;
  59. private bool _useSubMenusSingleFrame;
  60. /// <summary>Initializes a new instance of the <see cref="MenuBar"/>.</summary>
  61. public MenuBar ()
  62. {
  63. TabStop = TabBehavior.NoStop;
  64. X = 0;
  65. Y = 0;
  66. Width = Dim.Fill ();
  67. Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
  68. Menus = [];
  69. //CanFocus = true;
  70. _selected = -1;
  71. _selectedSub = -1;
  72. // ReSharper disable once VirtualMemberCallInConstructor
  73. ColorScheme = Colors.ColorSchemes ["Menu"];
  74. // ReSharper disable once VirtualMemberCallInConstructor
  75. WantMousePositionReports = true;
  76. IsMenuOpen = false;
  77. SuperViewChanged += MenuBar_SuperViewChanged;
  78. // Things this view knows how to do
  79. AddCommand (
  80. Command.Left,
  81. () =>
  82. {
  83. MoveLeft ();
  84. return true;
  85. }
  86. );
  87. AddCommand (
  88. Command.Right,
  89. () =>
  90. {
  91. MoveRight ();
  92. return true;
  93. }
  94. );
  95. AddCommand (
  96. Command.Cancel,
  97. () =>
  98. {
  99. if (IsMenuOpen)
  100. {
  101. CloseMenuBar ();
  102. return true;
  103. }
  104. return false;
  105. }
  106. );
  107. AddCommand (
  108. Command.Accept,
  109. (ctx) =>
  110. {
  111. if (Menus.Length > 0)
  112. {
  113. ProcessMenu (_selected, Menus [_selected]);
  114. }
  115. return RaiseAccepting (ctx);
  116. }
  117. );
  118. AddCommand (Command.Toggle, ctx =>
  119. {
  120. CloseOtherOpenedMenuBar ();
  121. if (ctx is not CommandContext<KeyBinding> keyCommandContext)
  122. {
  123. return false;
  124. }
  125. return Select (Menus.IndexOf (keyCommandContext.Binding.Data));
  126. });
  127. AddCommand (Command.Select, ctx =>
  128. {
  129. if (ctx is not CommandContext<KeyBinding> keyCommandContext)
  130. {
  131. return false ;
  132. }
  133. if (keyCommandContext.Binding.Data is MouseEventArgs)
  134. {
  135. // HACK: Work around the fact that View.MouseClick always invokes Select
  136. return false;
  137. }
  138. var res = Run ((keyCommandContext.Binding.Data as MenuItem)?.Action!);
  139. CloseAllMenus ();
  140. return res;
  141. });
  142. // Default key bindings for this view
  143. KeyBindings.Add (Key.CursorLeft, Command.Left);
  144. KeyBindings.Add (Key.CursorRight, Command.Right);
  145. KeyBindings.Add (Key.Esc, Command.Cancel);
  146. KeyBindings.Add (Key.CursorDown, Command.Accept);
  147. KeyBinding keyBinding = new ([Command.Toggle], this, data: -1); // -1 indicates Key was used
  148. HotKeyBindings.Add (Key, keyBinding);
  149. // TODO: Why do we have two keybindings for opening the menu? Ctrl-Space and Key?
  150. HotKeyBindings.Add (Key.Space.WithCtrl, keyBinding);
  151. // This is needed for macOS because Key.Space.WithCtrl doesn't work
  152. HotKeyBindings.Add (Key.Space.WithAlt, keyBinding);
  153. // TODO: Figure out how to make Alt work (on Windows)
  154. //KeyBindings.Add (Key.WithAlt, keyBinding);
  155. }
  156. /// <summary><see langword="true"/> if the menu is open; otherwise <see langword="true"/>.</summary>
  157. public bool IsMenuOpen { get; protected set; }
  158. /// <summary>Gets the view that was last focused before opening the menu.</summary>
  159. public View? LastFocused { get; private set; }
  160. /// <summary>
  161. /// Gets or sets the array of <see cref="MenuBarItem"/>s for the menu. Only set this after the
  162. /// <see cref="MenuBar"/> is visible.
  163. /// </summary>
  164. /// <value>The menu array.</value>
  165. public MenuBarItem [] Menus
  166. {
  167. get => _menus;
  168. set
  169. {
  170. _menus = value;
  171. if (Menus is [])
  172. {
  173. return;
  174. }
  175. // TODO: Hotkeys should not work for sub-menus if they are not visible!
  176. for (var i = 0; i < Menus.Length; i++)
  177. {
  178. MenuBarItem menuBarItem = Menus [i];
  179. if (menuBarItem.HotKey != Key.Empty)
  180. {
  181. HotKeyBindings.Remove (menuBarItem.HotKey!);
  182. KeyBinding keyBinding = new ([Command.Toggle], this, menuBarItem);
  183. HotKeyBindings.Add (menuBarItem.HotKey!, keyBinding);
  184. HotKeyBindings.Remove (menuBarItem.HotKey!.WithAlt);
  185. keyBinding = new ([Command.Toggle], this, data: menuBarItem);
  186. HotKeyBindings.Add (menuBarItem.HotKey.WithAlt, keyBinding);
  187. }
  188. if (menuBarItem.ShortcutKey != Key.Empty)
  189. {
  190. // Technically this will never run because MenuBarItems don't have shortcuts
  191. // unless the IsTopLevel is true
  192. HotKeyBindings.Remove (menuBarItem.ShortcutKey!);
  193. KeyBinding keyBinding = new ([Command.Select], this, data: menuBarItem);
  194. HotKeyBindings.Add (menuBarItem.ShortcutKey!, keyBinding);
  195. }
  196. menuBarItem.AddShortcutKeyBindings (this);
  197. }
  198. }
  199. }
  200. /// <summary>
  201. /// The default <see cref="LineStyle"/> for <see cref="Menus"/>'s border. The default is
  202. /// <see cref="LineStyle.Single"/>.
  203. /// </summary>
  204. public LineStyle MenusBorderStyle { get; set; } = LineStyle.Single;
  205. /// <summary>
  206. /// Gets or sets if the sub-menus must be displayed in a single or multiple frames.
  207. /// <para>
  208. /// By default, any sub-sub-menus (sub-menus of the main <see cref="MenuItem"/>s) are displayed in a cascading
  209. /// manner, where each sub-sub-menu pops out of the sub-menu frame (either to the right or left, depending on where
  210. /// the sub-menu is relative to the edge of the screen). By setting <see cref="UseSubMenusSingleFrame"/> to
  211. /// <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are drawn within a single
  212. /// frame below the MenuBar.
  213. /// </para>
  214. /// </summary>
  215. public bool UseSubMenusSingleFrame
  216. {
  217. get => _useSubMenusSingleFrame;
  218. set
  219. {
  220. _useSubMenusSingleFrame = value;
  221. if (value && UseKeysUpDownAsKeysLeftRight)
  222. {
  223. _useKeysUpDownAsKeysLeftRight = false;
  224. SetNeedsDraw ();
  225. }
  226. }
  227. }
  228. /// <inheritdoc/>
  229. public override bool Visible
  230. {
  231. get => base.Visible;
  232. set
  233. {
  234. base.Visible = value;
  235. if (!value)
  236. {
  237. CloseAllMenus ();
  238. }
  239. }
  240. }
  241. internal Menu? OpenCurrentMenu
  242. {
  243. get => _ocm;
  244. set
  245. {
  246. if (_ocm != value)
  247. {
  248. _ocm = value!;
  249. if (_ocm is { _currentChild: > -1 })
  250. {
  251. OnMenuOpened ();
  252. }
  253. }
  254. }
  255. }
  256. /// <summary>Closes the Menu programmatically if open and not canceled (as though F9 were pressed).</summary>
  257. public bool CloseMenu (bool ignoreUseSubMenusSingleFrame = false) { return CloseMenu (false, false, ignoreUseSubMenusSingleFrame); }
  258. /// <summary>Raised when all the menu is closed.</summary>
  259. public event EventHandler? MenuAllClosed;
  260. /// <summary>Raised when a menu is closing passing <see cref="MenuClosingEventArgs"/>.</summary>
  261. public event EventHandler<MenuClosingEventArgs>? MenuClosing;
  262. /// <summary>Raised when a menu is opened.</summary>
  263. public event EventHandler<MenuOpenedEventArgs>? MenuOpened;
  264. /// <summary>Raised as a menu is opening.</summary>
  265. public event EventHandler<MenuOpeningEventArgs>? MenuOpening;
  266. /// <inheritdoc/>
  267. protected override bool OnDrawingContent ()
  268. {
  269. var pos = 0;
  270. for (var i = 0; i < Menus.Length; i++)
  271. {
  272. MenuBarItem menu = Menus [i];
  273. Move (pos, 0);
  274. Attribute hotColor, normalColor;
  275. if (i == _selected && IsMenuOpen)
  276. {
  277. hotColor = i == _selected ? ColorScheme!.HotFocus : GetHotNormalColor ();
  278. normalColor = i == _selected ? GetFocusColor () : GetNormalColor ();
  279. }
  280. else
  281. {
  282. hotColor = GetHotNormalColor ();
  283. normalColor = GetNormalColor ();
  284. }
  285. // Note Help on MenuBar is drawn with parens around it
  286. DrawHotString (
  287. string.IsNullOrEmpty (menu.Help) ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ",
  288. hotColor,
  289. normalColor
  290. );
  291. pos += _leftPadding
  292. + menu.TitleLength
  293. + (menu.Help.GetColumns () > 0
  294. ? _leftPadding + menu.Help.GetColumns () + _parensAroundHelp
  295. : 0)
  296. + _rightPadding;
  297. }
  298. //PositionCursor ();
  299. return true;
  300. }
  301. /// <summary>Virtual method that will invoke the <see cref="MenuAllClosed"/>.</summary>
  302. public virtual void OnMenuAllClosed () { MenuAllClosed?.Invoke (this, EventArgs.Empty); }
  303. /// <summary>Virtual method that will invoke the <see cref="MenuClosing"/>.</summary>
  304. /// <param name="currentMenu">The current menu to be closed.</param>
  305. /// <param name="reopen">Whether the current menu will be reopened.</param>
  306. /// <param name="isSubMenu">Whether is a sub-menu or not.</param>
  307. public virtual MenuClosingEventArgs OnMenuClosing (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
  308. {
  309. var ev = new MenuClosingEventArgs (currentMenu, reopen, isSubMenu);
  310. MenuClosing?.Invoke (this, ev);
  311. return ev;
  312. }
  313. /// <summary>Virtual method that will invoke the <see cref="MenuOpened"/> event if it's defined.</summary>
  314. public virtual void OnMenuOpened ()
  315. {
  316. MenuItem? mi = null;
  317. MenuBarItem? parent;
  318. if (OpenCurrentMenu?.BarItems?.Children is { Length: > 0 }
  319. && OpenCurrentMenu?._currentChild > -1)
  320. {
  321. parent = OpenCurrentMenu.BarItems;
  322. mi = parent.Children [OpenCurrentMenu._currentChild];
  323. }
  324. else if (OpenCurrentMenu!.BarItems!.IsTopLevel)
  325. {
  326. parent = null;
  327. mi = OpenCurrentMenu.BarItems;
  328. }
  329. else
  330. {
  331. parent = _openMenu?.BarItems;
  332. if (OpenCurrentMenu?._currentChild > -1)
  333. {
  334. mi = parent?.Children?.Length > 0 ? parent.Children [_openMenu!._currentChild] : null;
  335. }
  336. }
  337. MenuOpened?.Invoke (this, new (parent, mi));
  338. }
  339. /// <summary>Virtual method that will invoke the <see cref="MenuOpening"/> event if it's defined.</summary>
  340. /// <param name="currentMenu">The current menu to be replaced.</param>
  341. /// <returns>Returns the <see cref="MenuOpeningEventArgs"/></returns>
  342. public virtual MenuOpeningEventArgs OnMenuOpening (MenuBarItem currentMenu)
  343. {
  344. var ev = new MenuOpeningEventArgs (currentMenu);
  345. MenuOpening?.Invoke (this, ev);
  346. return ev;
  347. }
  348. /// <summary>Opens the Menu programatically, as though the F9 key were pressed.</summary>
  349. public void OpenMenu ()
  350. {
  351. MenuBar? mbar = GetMouseGrabViewInstance (this);
  352. mbar?.CleanUp ();
  353. CloseOtherOpenedMenuBar ();
  354. if (!Enabled || _openMenu is { })
  355. {
  356. return;
  357. }
  358. _selected = 0;
  359. SetNeedsDraw ();
  360. _previousFocused = (SuperView is null ? Application.Top?.Focused : SuperView.Focused)!;
  361. OpenMenu (_selected);
  362. if (!SelectEnabledItem (
  363. OpenCurrentMenu?.BarItems?.Children,
  364. OpenCurrentMenu!._currentChild,
  365. out OpenCurrentMenu._currentChild
  366. )
  367. && !CloseMenu ())
  368. {
  369. return;
  370. }
  371. if (!OpenCurrentMenu.CheckSubMenu ())
  372. {
  373. return;
  374. }
  375. if (_isContextMenuLoading)
  376. {
  377. Application.GrabMouse (_openMenu);
  378. _isContextMenuLoading = false;
  379. }
  380. else
  381. {
  382. Application.GrabMouse (this);
  383. }
  384. }
  385. /// <inheritdoc/>
  386. public override Point? PositionCursor ()
  387. {
  388. if (_selected == -1 && HasFocus && Menus.Length > 0)
  389. {
  390. _selected = 0;
  391. }
  392. var pos = 0;
  393. for (var i = 0; i < Menus.Length; i++)
  394. {
  395. if (i == _selected)
  396. {
  397. pos++;
  398. Move (pos + 1, 0);
  399. return null; // Don't show the cursor
  400. }
  401. pos += _leftPadding
  402. + Menus [i].TitleLength
  403. + (Menus [i].Help.GetColumns () > 0
  404. ? Menus [i].Help.GetColumns () + _parensAroundHelp
  405. : 0)
  406. + _rightPadding;
  407. }
  408. return null; // Don't show the cursor
  409. }
  410. // Activates the menu, handles either first focus, or activating an entry when it was already active
  411. // For mouse events.
  412. internal void Activate (int idx, int sIdx = -1, MenuBarItem? subMenu = null!)
  413. {
  414. _selected = idx;
  415. _selectedSub = sIdx;
  416. if (_openMenu is null)
  417. {
  418. _previousFocused = (SuperView is null ? Application.Top?.Focused ?? null : SuperView.Focused)!;
  419. }
  420. OpenMenu (idx, sIdx, subMenu);
  421. SetNeedsDraw ();
  422. }
  423. internal void CleanUp ()
  424. {
  425. if (_isCleaning)
  426. {
  427. return;
  428. }
  429. _isCleaning = true;
  430. if (_openMenu is { })
  431. {
  432. CloseAllMenus ();
  433. }
  434. _openedByAltKey = false;
  435. IsMenuOpen = false;
  436. _selected = -1;
  437. CanFocus = _initialCanFocus;
  438. if (_lastFocused is { })
  439. {
  440. _lastFocused.SetFocus ();
  441. }
  442. SetNeedsDraw ();
  443. if (Application.MouseGrabView is { } && Application.MouseGrabView is MenuBar && Application.MouseGrabView != this)
  444. {
  445. var menuBar = Application.MouseGrabView as MenuBar;
  446. if (menuBar!.IsMenuOpen)
  447. {
  448. menuBar.CleanUp ();
  449. }
  450. }
  451. Application.UngrabMouse ();
  452. _isCleaning = false;
  453. }
  454. internal void CloseAllMenus ()
  455. {
  456. if (!_isMenuOpening && !_isMenuClosing)
  457. {
  458. if (_openSubMenu is { } && !CloseMenu (false, true, true))
  459. {
  460. return;
  461. }
  462. if (!CloseMenu ())
  463. {
  464. return;
  465. }
  466. if (LastFocused is { } && LastFocused != this)
  467. {
  468. _selected = -1;
  469. }
  470. Application.UngrabMouse ();
  471. }
  472. if (OpenCurrentMenu is { })
  473. {
  474. OpenCurrentMenu = null;
  475. }
  476. IsMenuOpen = false;
  477. _openedByAltKey = false;
  478. OnMenuAllClosed ();
  479. CloseOtherOpenedMenuBar ();
  480. }
  481. private void CloseOtherOpenedMenuBar ()
  482. {
  483. if (SuperView is { })
  484. {
  485. // Close others menu bar opened
  486. Menu? menu = SuperView.SubViews.FirstOrDefault (v => v is Menu m && m.Host != this && m.Host.IsMenuOpen) as Menu;
  487. menu?.Host.CleanUp ();
  488. }
  489. }
  490. internal bool CloseMenu (bool reopen, bool isSubMenu, bool ignoreUseSubMenusSingleFrame = false)
  491. {
  492. MenuBarItem? mbi = isSubMenu ? OpenCurrentMenu!.BarItems : _openMenu?.BarItems;
  493. if (UseSubMenusSingleFrame && mbi is { } && !ignoreUseSubMenusSingleFrame && mbi.Parent is { })
  494. {
  495. return false;
  496. }
  497. _isMenuClosing = true;
  498. _reopen = reopen;
  499. MenuClosingEventArgs args = OnMenuClosing (mbi!, reopen, isSubMenu);
  500. if (args.Cancel)
  501. {
  502. _isMenuClosing = false;
  503. if (args.CurrentMenu.Parent is { } && _openMenu is { })
  504. {
  505. _openMenu._currentChild =
  506. ((MenuBarItem)args.CurrentMenu.Parent).Children.IndexOf (args.CurrentMenu);
  507. }
  508. return false;
  509. }
  510. switch (isSubMenu)
  511. {
  512. case false:
  513. if (_openMenu is { })
  514. {
  515. SuperView?.Remove (_openMenu);
  516. }
  517. SetNeedsDraw ();
  518. if (_previousFocused is Menu && _openMenu is { } && _previousFocused.ToString () != OpenCurrentMenu!.ToString ())
  519. {
  520. _previousFocused.SetFocus ();
  521. }
  522. if (Application.MouseGrabView == _openMenu)
  523. {
  524. Application.UngrabMouse();
  525. }
  526. _openMenu?.Dispose ();
  527. _openMenu = null;
  528. if (_lastFocused is Menu or MenuBar)
  529. {
  530. _lastFocused = null;
  531. }
  532. LastFocused = _lastFocused;
  533. _lastFocused = null;
  534. if (LastFocused is { CanFocus: true })
  535. {
  536. if (!reopen)
  537. {
  538. _selected = -1;
  539. }
  540. if (_openSubMenu is { })
  541. {
  542. _openSubMenu = null;
  543. }
  544. if (OpenCurrentMenu is { })
  545. {
  546. SuperView?.Remove (OpenCurrentMenu);
  547. if (Application.MouseGrabView == OpenCurrentMenu)
  548. {
  549. Application.UngrabMouse ();
  550. }
  551. OpenCurrentMenu.Dispose ();
  552. OpenCurrentMenu = null;
  553. }
  554. LastFocused.SetFocus ();
  555. }
  556. else if (_openSubMenu is null || _openSubMenu.Count == 0)
  557. {
  558. CloseAllMenus ();
  559. }
  560. else
  561. {
  562. SetFocus ();
  563. }
  564. IsMenuOpen = false;
  565. break;
  566. case true:
  567. _selectedSub = -1;
  568. SetNeedsDraw ();
  569. RemoveAllOpensSubMenus ();
  570. OpenCurrentMenu!._previousSubFocused!.SetFocus ();
  571. _openSubMenu = null;
  572. IsMenuOpen = true;
  573. break;
  574. }
  575. _reopen = false;
  576. _isMenuClosing = false;
  577. return true;
  578. }
  579. /// <summary>Gets the superview location offset relative to the <see cref="IConsoleDriver"/> location.</summary>
  580. /// <returns>The location offset.</returns>
  581. internal Point GetScreenOffset ()
  582. {
  583. // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
  584. if (Driver is null)
  585. {
  586. return Point.Empty;
  587. }
  588. Rectangle superViewFrame = SuperView?.Frame ?? Application.Screen;
  589. View? sv = SuperView ?? Application.Top;
  590. if (sv is null)
  591. {
  592. // Support Unit Tests
  593. return Point.Empty;
  594. }
  595. Point viewportOffset = sv.GetViewportOffsetFromFrame ();
  596. return new (
  597. superViewFrame.X - sv.Frame.X - viewportOffset.X,
  598. superViewFrame.Y - sv.Frame.Y - viewportOffset.Y
  599. );
  600. }
  601. internal void NextMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
  602. {
  603. switch (isSubMenu)
  604. {
  605. case false:
  606. if (_selected == -1)
  607. {
  608. _selected = 0;
  609. }
  610. else if (_selected + 1 == Menus.Length)
  611. {
  612. _selected = 0;
  613. }
  614. else
  615. {
  616. _selected++;
  617. }
  618. if (_selected > -1 && !CloseMenu (true, ignoreUseSubMenusSingleFrame))
  619. {
  620. return;
  621. }
  622. if (_selected == -1)
  623. {
  624. return;
  625. }
  626. OpenMenu (_selected);
  627. SelectEnabledItem (
  628. OpenCurrentMenu?.BarItems?.Children,
  629. OpenCurrentMenu!._currentChild,
  630. out OpenCurrentMenu._currentChild
  631. );
  632. break;
  633. case true:
  634. if (UseKeysUpDownAsKeysLeftRight)
  635. {
  636. if (CloseMenu (false, true, ignoreUseSubMenusSingleFrame))
  637. {
  638. NextMenu (false, ignoreUseSubMenusSingleFrame);
  639. }
  640. }
  641. else
  642. {
  643. MenuBarItem? subMenu = OpenCurrentMenu!._currentChild > -1 && OpenCurrentMenu.BarItems?.Children!.Length > 0
  644. ? OpenCurrentMenu.BarItems.SubMenu (
  645. OpenCurrentMenu.BarItems.Children? [OpenCurrentMenu._currentChild]!
  646. )
  647. : null;
  648. if ((_selectedSub == -1 || _openSubMenu is null || _openSubMenu?.Count - 1 == _selectedSub) && subMenu is null)
  649. {
  650. if (_openSubMenu is { } && !CloseMenu (false, true))
  651. {
  652. return;
  653. }
  654. NextMenu (false, ignoreUseSubMenusSingleFrame);
  655. }
  656. else if (subMenu != null
  657. || (OpenCurrentMenu._currentChild > -1
  658. && !OpenCurrentMenu.BarItems!
  659. .Children! [OpenCurrentMenu._currentChild]!
  660. .IsFromSubMenu))
  661. {
  662. _selectedSub++;
  663. OpenCurrentMenu.CheckSubMenu ();
  664. }
  665. else
  666. {
  667. if (CloseMenu (false, true, ignoreUseSubMenusSingleFrame))
  668. {
  669. NextMenu (false, ignoreUseSubMenusSingleFrame);
  670. }
  671. return;
  672. }
  673. SetNeedsDraw ();
  674. if (UseKeysUpDownAsKeysLeftRight)
  675. {
  676. OpenCurrentMenu.CheckSubMenu ();
  677. }
  678. }
  679. break;
  680. }
  681. }
  682. internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null!)
  683. {
  684. _isMenuOpening = true;
  685. MenuOpeningEventArgs newMenu = OnMenuOpening (Menus [index]);
  686. if (newMenu.Cancel)
  687. {
  688. _isMenuOpening = false;
  689. return;
  690. }
  691. if (newMenu.NewMenuBarItem is { })
  692. {
  693. Menus [index] = newMenu.NewMenuBarItem;
  694. }
  695. var pos = 0;
  696. switch (subMenu)
  697. {
  698. case null:
  699. // Open a submenu below a MenuBar
  700. _lastFocused ??= SuperView is null ? Application.Top?.MostFocused : SuperView.MostFocused;
  701. if (_openSubMenu is { } && !CloseMenu (false, true))
  702. {
  703. return;
  704. }
  705. if (_openMenu is { })
  706. {
  707. SuperView?.Remove (_openMenu);
  708. if (Application.MouseGrabView == _openMenu)
  709. {
  710. Application.UngrabMouse ();
  711. }
  712. _openMenu.Dispose ();
  713. _openMenu = null;
  714. }
  715. // This positions the submenu horizontally aligned with the first character of the
  716. // text belonging to the menu
  717. for (var i = 0; i < index; i++)
  718. {
  719. pos += Menus [i].TitleLength + (Menus [i].Help.GetColumns () > 0 ? Menus [i].Help.GetColumns () + 2 : 0) + _leftPadding + _rightPadding;
  720. }
  721. _openMenu = new ()
  722. {
  723. Host = this,
  724. X = Frame.X + pos,
  725. Y = Frame.Y + 1,
  726. BarItems = Menus [index],
  727. Parent = null
  728. };
  729. OpenCurrentMenu = _openMenu;
  730. OpenCurrentMenu._previousSubFocused = _openMenu;
  731. if (SuperView is { })
  732. {
  733. SuperView.Add (_openMenu);
  734. // _openMenu.SetRelativeLayout (Application.Screen.Size);
  735. }
  736. else
  737. {
  738. _openMenu.BeginInit ();
  739. _openMenu.EndInit ();
  740. }
  741. _openMenu.SetFocus ();
  742. break;
  743. default:
  744. // Opens a submenu next to another submenu (openSubMenu)
  745. if (_openSubMenu is null)
  746. {
  747. _openSubMenu = new ();
  748. }
  749. if (sIndex > -1)
  750. {
  751. RemoveSubMenu (sIndex);
  752. }
  753. else
  754. {
  755. Menu? last = _openSubMenu.Count > 0 ? _openSubMenu.Last () : _openMenu;
  756. if (!UseSubMenusSingleFrame)
  757. {
  758. OpenCurrentMenu = new ()
  759. {
  760. Host = this,
  761. X = last!.Frame.Left + last.Frame.Width,
  762. Y = last.Frame.Top + last._currentChild + 1,
  763. BarItems = subMenu,
  764. Parent = last
  765. };
  766. }
  767. else
  768. {
  769. Menu? first = _openSubMenu.Count > 0 ? _openSubMenu.First () : _openMenu;
  770. // 2 is for the parent and the separator
  771. MenuItem? [] mbi = new MenuItem [2 + subMenu.Children!.Length];
  772. mbi [0] = new () { Title = subMenu.Title, Parent = subMenu };
  773. mbi [1] = null;
  774. for (var j = 0; j < subMenu.Children.Length; j++)
  775. {
  776. mbi [j + 2] = subMenu.Children [j];
  777. }
  778. var newSubMenu = new MenuBarItem (mbi!) { Parent = subMenu };
  779. OpenCurrentMenu = new ()
  780. {
  781. Host = this, X = first!.Frame.Left, Y = first.Frame.Top, BarItems = newSubMenu
  782. };
  783. last!.Visible = false;
  784. Application.GrabMouse (OpenCurrentMenu);
  785. }
  786. OpenCurrentMenu._previousSubFocused = last._previousSubFocused;
  787. _openSubMenu.Add (OpenCurrentMenu);
  788. SuperView?.Add (OpenCurrentMenu);
  789. if (!OpenCurrentMenu.IsInitialized)
  790. {
  791. // Supports unit tests
  792. OpenCurrentMenu.BeginInit ();
  793. OpenCurrentMenu.EndInit ();
  794. }
  795. }
  796. _selectedSub = _openSubMenu.Count - 1;
  797. if (_selectedSub > -1
  798. && SelectEnabledItem (
  799. OpenCurrentMenu!.BarItems!.Children,
  800. OpenCurrentMenu._currentChild,
  801. out OpenCurrentMenu._currentChild
  802. ))
  803. {
  804. OpenCurrentMenu.SetFocus ();
  805. }
  806. break;
  807. }
  808. _isMenuOpening = false;
  809. IsMenuOpen = true;
  810. }
  811. internal void PreviousMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
  812. {
  813. switch (isSubMenu)
  814. {
  815. case false:
  816. if (_selected <= 0)
  817. {
  818. _selected = Menus.Length - 1;
  819. }
  820. else
  821. {
  822. _selected--;
  823. }
  824. if (_selected > -1 && !CloseMenu (true, false, ignoreUseSubMenusSingleFrame))
  825. {
  826. return;
  827. }
  828. if (_selected == -1)
  829. {
  830. return;
  831. }
  832. OpenMenu (_selected);
  833. if (!SelectEnabledItem (
  834. OpenCurrentMenu?.BarItems?.Children,
  835. OpenCurrentMenu!._currentChild,
  836. out OpenCurrentMenu._currentChild,
  837. false
  838. ))
  839. {
  840. OpenCurrentMenu._currentChild = 0;
  841. }
  842. break;
  843. case true:
  844. if (_selectedSub > -1)
  845. {
  846. _selectedSub--;
  847. RemoveSubMenu (_selectedSub, ignoreUseSubMenusSingleFrame);
  848. SetNeedsDraw ();
  849. }
  850. else
  851. {
  852. PreviousMenu ();
  853. }
  854. break;
  855. }
  856. }
  857. internal void RemoveAllOpensSubMenus ()
  858. {
  859. if (_openSubMenu is { })
  860. {
  861. foreach (Menu item in _openSubMenu)
  862. {
  863. SuperView?.Remove (item);
  864. if (Application.MouseGrabView == item)
  865. {
  866. Application.UngrabMouse ();
  867. }
  868. item.Dispose ();
  869. }
  870. }
  871. }
  872. internal bool Run (Action? action)
  873. {
  874. if (action is null)
  875. {
  876. return false;
  877. }
  878. Application.AddIdle (
  879. () =>
  880. {
  881. action ();
  882. return false;
  883. }
  884. );
  885. return true;
  886. }
  887. internal bool SelectEnabledItem (
  888. MenuItem? []? children,
  889. int current,
  890. out int newCurrent,
  891. bool forward = true
  892. )
  893. {
  894. if (children is null)
  895. {
  896. newCurrent = -1;
  897. return true;
  898. }
  899. IEnumerable<MenuItem?> childMenuItems = forward ? children : children.Reverse ();
  900. int count;
  901. IEnumerable<MenuItem?> menuItems = childMenuItems as MenuItem [] ?? childMenuItems.ToArray ();
  902. if (forward)
  903. {
  904. count = -1;
  905. }
  906. else
  907. {
  908. count = menuItems.Count ();
  909. }
  910. foreach (MenuItem? child in menuItems)
  911. {
  912. if (forward)
  913. {
  914. if (++count < current)
  915. {
  916. continue;
  917. }
  918. }
  919. else
  920. {
  921. if (--count > current)
  922. {
  923. continue;
  924. }
  925. }
  926. // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
  927. if (child is null || !child.IsEnabled ())
  928. {
  929. if (forward)
  930. {
  931. current++;
  932. }
  933. else
  934. {
  935. current--;
  936. }
  937. }
  938. else
  939. {
  940. newCurrent = current;
  941. return true;
  942. }
  943. }
  944. newCurrent = -1;
  945. return false;
  946. }
  947. /// <summary>Called when an item is selected; Runs the action.</summary>
  948. /// <param name="item"></param>
  949. internal bool SelectItem (MenuItem? item)
  950. {
  951. if (item?.Action is null)
  952. {
  953. return false;
  954. }
  955. Application.UngrabMouse ();
  956. CloseAllMenus ();
  957. Application.LayoutAndDraw (true);
  958. _openedByAltKey = true;
  959. return Run (item.Action);
  960. }
  961. private void CloseMenuBar ()
  962. {
  963. if (!CloseMenu ())
  964. {
  965. return;
  966. }
  967. if (_openedByAltKey)
  968. {
  969. _openedByAltKey = false;
  970. LastFocused?.SetFocus ();
  971. }
  972. SetNeedsDraw ();
  973. }
  974. private Point GetLocationOffset ()
  975. {
  976. if (MenusBorderStyle != LineStyle.None)
  977. {
  978. return new (0, 1);
  979. }
  980. return new (-2, 0);
  981. }
  982. private void MenuBar_SuperViewChanged (object? sender, SuperViewChangedEventArgs _)
  983. {
  984. _initialCanFocus = CanFocus;
  985. SuperViewChanged -= MenuBar_SuperViewChanged;
  986. }
  987. private void MoveLeft ()
  988. {
  989. _selected--;
  990. if (_selected < 0)
  991. {
  992. _selected = Menus.Length - 1;
  993. }
  994. OpenMenu (_selected);
  995. SetNeedsDraw ();
  996. }
  997. private void MoveRight ()
  998. {
  999. _selected = (_selected + 1) % Menus.Length;
  1000. OpenMenu (_selected);
  1001. SetNeedsDraw ();
  1002. }
  1003. private bool ProcessMenu (int i, MenuBarItem mi)
  1004. {
  1005. if (_selected < 0 && IsMenuOpen)
  1006. {
  1007. return false;
  1008. }
  1009. if (mi.IsTopLevel)
  1010. {
  1011. Point screen = ViewportToScreen (new Point (0, i));
  1012. var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi };
  1013. menu.Run (mi.Action);
  1014. if (Application.MouseGrabView == menu)
  1015. {
  1016. Application.UngrabMouse ();
  1017. }
  1018. menu.Dispose ();
  1019. }
  1020. else
  1021. {
  1022. Application.GrabMouse (this);
  1023. _selected = i;
  1024. OpenMenu (i);
  1025. if (!SelectEnabledItem (
  1026. OpenCurrentMenu?.BarItems?.Children,
  1027. OpenCurrentMenu!._currentChild,
  1028. out OpenCurrentMenu._currentChild
  1029. )
  1030. && !CloseMenu ())
  1031. {
  1032. return true;
  1033. }
  1034. if (!OpenCurrentMenu.CheckSubMenu ())
  1035. {
  1036. return true;
  1037. }
  1038. }
  1039. SetNeedsDraw ();
  1040. return true;
  1041. }
  1042. private void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false)
  1043. {
  1044. if (_openSubMenu == null
  1045. || (UseSubMenusSingleFrame
  1046. && !ignoreUseSubMenusSingleFrame
  1047. && _openSubMenu.Count == 0))
  1048. {
  1049. return;
  1050. }
  1051. for (int i = _openSubMenu.Count - 1; i > index; i--)
  1052. {
  1053. _isMenuClosing = true;
  1054. Menu? menu;
  1055. if (_openSubMenu!.Count - 1 > 0)
  1056. {
  1057. menu = _openSubMenu [i - 1];
  1058. }
  1059. else
  1060. {
  1061. menu = _openMenu;
  1062. }
  1063. if (!menu!.Visible)
  1064. {
  1065. menu.Visible = true;
  1066. }
  1067. OpenCurrentMenu = menu;
  1068. OpenCurrentMenu.SetFocus ();
  1069. if (_openSubMenu is { })
  1070. {
  1071. menu = _openSubMenu [i];
  1072. SuperView!.Remove (menu);
  1073. _openSubMenu.Remove (menu);
  1074. if (Application.MouseGrabView == menu)
  1075. {
  1076. Application.GrabMouse (this);
  1077. }
  1078. menu.Dispose ();
  1079. }
  1080. RemoveSubMenu (i, ignoreUseSubMenusSingleFrame);
  1081. }
  1082. if (_openSubMenu!.Count > 0)
  1083. {
  1084. OpenCurrentMenu = _openSubMenu.Last ();
  1085. }
  1086. _isMenuClosing = false;
  1087. }
  1088. #region Keyboard handling
  1089. private Key _key = Key.F9;
  1090. /// <summary>
  1091. /// The <see cref="Key"/> used to activate or close the menu bar by keyboard. The default is <see cref="Key.F9"/>
  1092. /// .
  1093. /// </summary>
  1094. /// <remarks>
  1095. /// <para>
  1096. /// If the user presses any <see cref="MenuItem.HotKey"/>s defined in the <see cref="MenuBarItem"/>s, the menu
  1097. /// bar will be activated and the sub-menu will be opened.
  1098. /// </para>
  1099. /// <para><see cref="Key.Esc"/> will close the menu bar and any open sub-menus.</para>
  1100. /// </remarks>
  1101. public Key Key
  1102. {
  1103. get => _key;
  1104. set
  1105. {
  1106. if (_key == value)
  1107. {
  1108. return;
  1109. }
  1110. HotKeyBindings.Remove (_key);
  1111. KeyBinding keyBinding = new ([Command.Toggle], this, data: -1); // -1 indicates Key was used
  1112. HotKeyBindings.Add (value, keyBinding);
  1113. _key = value;
  1114. }
  1115. }
  1116. private bool _useKeysUpDownAsKeysLeftRight;
  1117. /// <summary>Used for change the navigation key style.</summary>
  1118. public bool UseKeysUpDownAsKeysLeftRight
  1119. {
  1120. get => _useKeysUpDownAsKeysLeftRight;
  1121. set
  1122. {
  1123. _useKeysUpDownAsKeysLeftRight = value;
  1124. if (value && UseSubMenusSingleFrame)
  1125. {
  1126. UseSubMenusSingleFrame = false;
  1127. SetNeedsDraw ();
  1128. }
  1129. }
  1130. }
  1131. /// <summary>The specifier character for the hot keys.</summary>
  1132. public new static Rune HotKeySpecifier => (Rune)'_';
  1133. // TODO: This doesn't actually work. Figure out why.
  1134. private bool _openedByAltKey;
  1135. /// <summary>
  1136. /// Called when a key bound to Command.Select is pressed. Either activates the menu item or runs it, depending on
  1137. /// whether it has a sub-menu. If the menu is open, it will close the menu bar.
  1138. /// </summary>
  1139. /// <param name="index">The index of the menu bar item to select. -1 if the selection was via <see cref="Key"/>.</param>
  1140. /// <returns></returns>
  1141. private bool Select (int index)
  1142. {
  1143. if (!IsInitialized || !Visible)
  1144. {
  1145. return true;
  1146. }
  1147. // If the menubar is open and the menu that's open is 'index' then close it. Otherwise activate it.
  1148. if (IsMenuOpen)
  1149. {
  1150. if (index == -1)
  1151. {
  1152. CloseAllMenus ();
  1153. return true;
  1154. }
  1155. // Find the index of the open submenu and close the menu if it matches
  1156. for (var i = 0; i < Menus.Length; i++)
  1157. {
  1158. MenuBarItem open = Menus [i];
  1159. if (open == OpenCurrentMenu!.BarItems && i == index)
  1160. {
  1161. CloseAllMenus ();
  1162. return true;
  1163. }
  1164. }
  1165. }
  1166. if (index == -1)
  1167. {
  1168. OpenMenu ();
  1169. }
  1170. else if (Menus [index].IsTopLevel)
  1171. {
  1172. Run (Menus [index].Action);
  1173. }
  1174. else
  1175. {
  1176. Activate (index);
  1177. }
  1178. return true;
  1179. }
  1180. #endregion Keyboard handling
  1181. #region Mouse Handling
  1182. internal void LostFocus (View view)
  1183. {
  1184. if (view is not MenuBar && view is not Menu && !_isCleaning && !_reopen)
  1185. {
  1186. CleanUp ();
  1187. }
  1188. }
  1189. /// <inheritdoc/>
  1190. protected override bool OnMouseEvent (MouseEventArgs me)
  1191. {
  1192. if (!_handled && !HandleGrabView (me, this))
  1193. {
  1194. return false;
  1195. }
  1196. _handled = false;
  1197. if (me.Flags == MouseFlags.Button1Pressed
  1198. || me.Flags == MouseFlags.Button1DoubleClicked
  1199. || me.Flags == MouseFlags.Button1TripleClicked
  1200. || me.Flags == MouseFlags.Button1Clicked
  1201. || (me.Flags == MouseFlags.ReportMousePosition && _selected > -1)
  1202. || (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _selected > -1))
  1203. {
  1204. int pos = _xOrigin;
  1205. Point locationOffset = default;
  1206. if (SuperView is { })
  1207. {
  1208. locationOffset.X += SuperView.Border.Thickness.Left;
  1209. locationOffset.Y += SuperView.Border.Thickness.Top;
  1210. }
  1211. int cx = me.Position.X - locationOffset.X;
  1212. for (var i = 0; i < Menus.Length; i++)
  1213. {
  1214. if (cx >= pos && cx < pos + _leftPadding + Menus [i].TitleLength + Menus [i].Help.GetColumns () + _rightPadding)
  1215. {
  1216. if (me.Flags == MouseFlags.Button1Clicked)
  1217. {
  1218. if (Menus [i].IsTopLevel)
  1219. {
  1220. Point screen = ViewportToScreen (new Point (0, i));
  1221. var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] };
  1222. menu.Run (Menus [i].Action);
  1223. if (Application.MouseGrabView == menu)
  1224. {
  1225. Application.UngrabMouse ();
  1226. }
  1227. menu.Dispose ();
  1228. }
  1229. else if (!IsMenuOpen)
  1230. {
  1231. Activate (i);
  1232. }
  1233. }
  1234. else if (me.Flags.HasFlag (MouseFlags.Button1Pressed)
  1235. || me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
  1236. || me.Flags.HasFlag (MouseFlags.Button1TripleClicked))
  1237. {
  1238. if (IsMenuOpen && !Menus [i].IsTopLevel)
  1239. {
  1240. CloseAllMenus ();
  1241. }
  1242. else if (!Menus [i].IsTopLevel)
  1243. {
  1244. Activate (i);
  1245. }
  1246. }
  1247. else if (_selected != i
  1248. && _selected > -1
  1249. && (me.Flags == MouseFlags.ReportMousePosition
  1250. || (me.Flags is MouseFlags.Button1Pressed && me.Flags == MouseFlags.ReportMousePosition)))
  1251. {
  1252. if (IsMenuOpen)
  1253. {
  1254. if (!CloseMenu (true, false))
  1255. {
  1256. return me.Handled = true;
  1257. }
  1258. Activate (i);
  1259. }
  1260. }
  1261. else if (IsMenuOpen)
  1262. {
  1263. if (!UseSubMenusSingleFrame
  1264. || (UseSubMenusSingleFrame
  1265. && OpenCurrentMenu is { BarItems.Parent: { } }
  1266. && OpenCurrentMenu.BarItems.Parent.Parent != Menus [i]))
  1267. {
  1268. Activate (i);
  1269. }
  1270. }
  1271. return me.Handled = true;
  1272. }
  1273. if (i == Menus.Length - 1 && me.Flags == MouseFlags.Button1Clicked)
  1274. {
  1275. if (IsMenuOpen && !Menus [i].IsTopLevel)
  1276. {
  1277. CloseAllMenus ();
  1278. return me.Handled = true;
  1279. }
  1280. }
  1281. pos += _leftPadding + Menus [i].TitleLength + _rightPadding;
  1282. }
  1283. }
  1284. return false;
  1285. }
  1286. internal bool _handled;
  1287. internal bool _isContextMenuLoading;
  1288. private MenuBarItem [] _menus = [];
  1289. internal bool HandleGrabView (MouseEventArgs me, View current)
  1290. {
  1291. if (Application.MouseGrabView is { })
  1292. {
  1293. if (me.View is MenuBar or Menu)
  1294. {
  1295. MenuBar? mbar = GetMouseGrabViewInstance (me.View);
  1296. if (mbar is { })
  1297. {
  1298. if (me.Flags == MouseFlags.Button1Clicked)
  1299. {
  1300. mbar.CleanUp ();
  1301. Application.GrabMouse (me.View);
  1302. }
  1303. else
  1304. {
  1305. _handled = false;
  1306. return false;
  1307. }
  1308. }
  1309. if (Application.MouseGrabView != me.View)
  1310. {
  1311. View v = me.View;
  1312. Application.GrabMouse (v);
  1313. return true;
  1314. }
  1315. if (me.View != current)
  1316. {
  1317. View v = me.View;
  1318. Application.GrabMouse (v);
  1319. MouseEventArgs nme;
  1320. if (me.Position.Y > -1)
  1321. {
  1322. Point frameLoc = v.ScreenToFrame (me.Position);
  1323. nme = new ()
  1324. {
  1325. Position = frameLoc,
  1326. Flags = me.Flags,
  1327. View = v
  1328. };
  1329. }
  1330. else
  1331. {
  1332. nme = new ()
  1333. {
  1334. Position = new (me.Position.X + current.Frame.X, me.Position.Y + current.Frame.Y),
  1335. Flags = me.Flags, View = v
  1336. };
  1337. }
  1338. v.NewMouseEvent (nme);
  1339. return false;
  1340. }
  1341. }
  1342. else if (!(me.View is MenuBar || me.View is Menu)
  1343. && me.Flags != MouseFlags.ReportMousePosition
  1344. && me.Flags != 0)
  1345. {
  1346. Application.UngrabMouse ();
  1347. if (IsMenuOpen)
  1348. {
  1349. CloseAllMenus ();
  1350. }
  1351. _handled = false;
  1352. return false;
  1353. }
  1354. else
  1355. {
  1356. _handled = false;
  1357. return false;
  1358. }
  1359. }
  1360. else if (!IsMenuOpen
  1361. && (me.Flags == MouseFlags.Button1Pressed
  1362. || me.Flags == MouseFlags.Button1DoubleClicked
  1363. || me.Flags == MouseFlags.Button1TripleClicked
  1364. || me.Flags.HasFlag (
  1365. MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
  1366. )))
  1367. {
  1368. Application.GrabMouse (current);
  1369. }
  1370. else if (IsMenuOpen && (me.View is MenuBar || me.View is Menu))
  1371. {
  1372. Application.GrabMouse (me.View);
  1373. }
  1374. else
  1375. {
  1376. _handled = false;
  1377. return false;
  1378. }
  1379. _handled = true;
  1380. return true;
  1381. }
  1382. private MenuBar? GetMouseGrabViewInstance (View? view)
  1383. {
  1384. if (view is null || Application.MouseGrabView is null)
  1385. {
  1386. return null;
  1387. }
  1388. MenuBar? hostView = null;
  1389. if (view is MenuBar)
  1390. {
  1391. hostView = (MenuBar)view;
  1392. }
  1393. else if (view is Menu)
  1394. {
  1395. hostView = ((Menu)view).Host;
  1396. }
  1397. View grabView = Application.MouseGrabView;
  1398. MenuBar? hostGrabView = null;
  1399. if (grabView is MenuBar bar)
  1400. {
  1401. hostGrabView = bar;
  1402. }
  1403. else if (grabView is Menu menu)
  1404. {
  1405. hostGrabView = menu.Host;
  1406. }
  1407. return hostView != hostGrabView ? hostGrabView : null;
  1408. }
  1409. #endregion Mouse Handling
  1410. /// <inheritdoc />
  1411. public bool EnableForDesign<TContext> (ref readonly TContext context) where TContext : notnull
  1412. {
  1413. if (context is not Func<string, bool> actionFn)
  1414. {
  1415. actionFn = (_) => true;
  1416. }
  1417. Menus =
  1418. [
  1419. new MenuBarItem (
  1420. "_File",
  1421. new MenuItem []
  1422. {
  1423. new (
  1424. "_New",
  1425. "",
  1426. () => actionFn ("New"),
  1427. null,
  1428. null,
  1429. KeyCode.CtrlMask | KeyCode.N
  1430. ),
  1431. new (
  1432. "_Open",
  1433. "",
  1434. () => actionFn ("Open"),
  1435. null,
  1436. null,
  1437. KeyCode.CtrlMask | KeyCode.O
  1438. ),
  1439. new (
  1440. "_Save",
  1441. "",
  1442. () => actionFn ("Save"),
  1443. null,
  1444. null,
  1445. KeyCode.CtrlMask | KeyCode.S
  1446. ),
  1447. #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
  1448. null,
  1449. #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
  1450. // Don't use Application.Quit so we can disambiguate between quitting and closing the toplevel
  1451. new (
  1452. "_Quit",
  1453. "",
  1454. () => actionFn ("Quit"),
  1455. null,
  1456. null,
  1457. KeyCode.CtrlMask | KeyCode.Q
  1458. )
  1459. }
  1460. ),
  1461. new MenuBarItem (
  1462. "_Edit",
  1463. new MenuItem []
  1464. {
  1465. new (
  1466. "_Copy",
  1467. "",
  1468. () => actionFn ("Copy"),
  1469. null,
  1470. null,
  1471. KeyCode.CtrlMask | KeyCode.C
  1472. ),
  1473. new (
  1474. "C_ut",
  1475. "",
  1476. () => actionFn ("Cut"),
  1477. null,
  1478. null,
  1479. KeyCode.CtrlMask | KeyCode.X
  1480. ),
  1481. new (
  1482. "_Paste",
  1483. "",
  1484. () => actionFn ("Paste"),
  1485. null,
  1486. null,
  1487. KeyCode.CtrlMask | KeyCode.V
  1488. ),
  1489. new MenuBarItem (
  1490. "_Find and Replace",
  1491. new MenuItem []
  1492. {
  1493. new (
  1494. "F_ind",
  1495. "",
  1496. () => actionFn ("Find"),
  1497. null,
  1498. null,
  1499. KeyCode.CtrlMask | KeyCode.F
  1500. ),
  1501. new (
  1502. "_Replace",
  1503. "",
  1504. () => actionFn ("Replace"),
  1505. null,
  1506. null,
  1507. KeyCode.CtrlMask | KeyCode.H
  1508. ),
  1509. new MenuBarItem (
  1510. "_3rd Level",
  1511. new MenuItem []
  1512. {
  1513. new (
  1514. "_1st",
  1515. "",
  1516. () => actionFn (
  1517. "1"
  1518. ),
  1519. null,
  1520. null,
  1521. KeyCode.F1
  1522. ),
  1523. new (
  1524. "_2nd",
  1525. "",
  1526. () => actionFn (
  1527. "2"
  1528. ),
  1529. null,
  1530. null,
  1531. KeyCode.F2
  1532. )
  1533. }
  1534. ),
  1535. new MenuBarItem (
  1536. "_4th Level",
  1537. new MenuItem []
  1538. {
  1539. new (
  1540. "_5th",
  1541. "",
  1542. () => actionFn (
  1543. "5"
  1544. ),
  1545. null,
  1546. null,
  1547. KeyCode.CtrlMask
  1548. | KeyCode.D5
  1549. ),
  1550. new (
  1551. "_6th",
  1552. "",
  1553. () => actionFn (
  1554. "6"
  1555. ),
  1556. null,
  1557. null,
  1558. KeyCode.CtrlMask
  1559. | KeyCode.D6
  1560. )
  1561. }
  1562. )
  1563. }
  1564. ),
  1565. new (
  1566. "_Select All",
  1567. "",
  1568. () => actionFn ("Select All"),
  1569. null,
  1570. null,
  1571. KeyCode.CtrlMask
  1572. | KeyCode.ShiftMask
  1573. | KeyCode.S
  1574. )
  1575. }
  1576. ),
  1577. new MenuBarItem ("_About", "Top-Level", () => actionFn ("About"))
  1578. ];
  1579. return true;
  1580. }
  1581. }