MenuBar.cs 59 KB

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