2
0

MenuBar.cs 59 KB

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