MenusV2.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. #nullable enable
  2. using System.Collections.ObjectModel;
  3. using Microsoft.Extensions.Logging;
  4. using Serilog;
  5. using Serilog.Core;
  6. using Serilog.Events;
  7. using Terminal.Gui;
  8. using ILogger = Microsoft.Extensions.Logging.ILogger;
  9. namespace UICatalog.Scenarios;
  10. [ScenarioMetadata ("MenusV2", "Illustrates MenuV2")]
  11. [ScenarioCategory ("Controls")]
  12. [ScenarioCategory ("Shortcuts")]
  13. public class MenusV2 : Scenario
  14. {
  15. public override void Main ()
  16. {
  17. Logging.Logger = CreateLogger ();
  18. Application.Init ();
  19. Toplevel app = new ();
  20. app.Title = GetQuitKeyAndName ();
  21. ObservableCollection<string> eventSource = new ();
  22. var eventLog = new ListView
  23. {
  24. Title = "Event Log",
  25. X = Pos.AnchorEnd (),
  26. Width = Dim.Auto (),
  27. Height = Dim.Fill (), // Make room for some wide things
  28. ColorScheme = Colors.ColorSchemes ["Toplevel"],
  29. Source = new ListWrapper<string> (eventSource)
  30. };
  31. eventLog.Border!.Thickness = new (0, 1, 0, 0);
  32. TargetView targetView = new ()
  33. {
  34. Id = "targetView",
  35. Title = "Target View",
  36. X = 5,
  37. Y = 5,
  38. Width = Dim.Fill (2)! - Dim.Width (eventLog),
  39. Height = Dim.Fill (2),
  40. BorderStyle = LineStyle.Dotted
  41. };
  42. app.Add (targetView);
  43. targetView.CommandNotBound += (o, args) =>
  44. {
  45. if (args.Cancel)
  46. {
  47. return;
  48. }
  49. Logging.Trace ($"targetView CommandNotBound: {args?.Context?.Command}");
  50. eventSource.Add ($"targetView CommandNotBound: {args?.Context?.Command}");
  51. eventLog.MoveDown ();
  52. };
  53. targetView.Accepting += (o, args) =>
  54. {
  55. if (args.Cancel)
  56. {
  57. return;
  58. }
  59. Logging.Trace ($"targetView Accepting: {args?.Context?.Source?.Title}");
  60. eventSource.Add ($"targetView Accepting: {args?.Context?.Source?.Title}: ");
  61. eventLog.MoveDown ();
  62. };
  63. targetView.FilePopoverMenu!.Accepted += (o, args) =>
  64. {
  65. if (args.Cancel)
  66. {
  67. return;
  68. }
  69. Logging.Trace ($"FilePopoverMenu Accepted: {args?.Context?.Source?.Text}");
  70. eventSource.Add ($"FilePopoverMenu Accepted: {args?.Context?.Source?.Text}: ");
  71. eventLog.MoveDown ();
  72. };
  73. app.Add (eventLog);
  74. Application.Run (app);
  75. app.Dispose ();
  76. Application.Shutdown ();
  77. }
  78. public class TargetView : View
  79. {
  80. internal PopoverMenu? FilePopoverMenu { get; }
  81. private CheckBox? _enableOverwriteCb;
  82. private CheckBox? _autoSaveCb;
  83. private CheckBox? _editModeCb;
  84. private RadioGroup? _mutuallyExclusiveOptionsRg;
  85. private ColorPicker? _menuBgColorCp;
  86. public TargetView ()
  87. {
  88. CanFocus = true;
  89. Text = "TargetView";
  90. BorderStyle = LineStyle.Dashed;
  91. AddCommand (
  92. Command.Context,
  93. ctx =>
  94. {
  95. FilePopoverMenu?.MakeVisible ();
  96. return true;
  97. });
  98. KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context);
  99. AddCommand (
  100. Command.Cancel,
  101. ctx =>
  102. {
  103. if (Application.Popover?.GetActivePopover () as PopoverMenu is { Visible: true } visiblePopover)
  104. {
  105. visiblePopover.Visible = false;
  106. }
  107. return true;
  108. });
  109. MouseBindings.ReplaceCommands (MouseFlags.Button1Clicked, Command.Cancel);
  110. Label lastCommandLabel = new ()
  111. {
  112. Title = "_Last Command:",
  113. X = 15,
  114. Y = 10,
  115. };
  116. View lastCommandText = new ()
  117. {
  118. X = Pos.Right (lastCommandLabel) + 1,
  119. Y = Pos.Top (lastCommandLabel),
  120. Height = Dim.Auto (),
  121. Width = Dim.Auto ()
  122. };
  123. Add (lastCommandLabel, lastCommandText);
  124. AddCommand (Command.New, HandleCommand);
  125. HotKeyBindings.Add (Key.F2, Command.New);
  126. AddCommand (Command.Open, HandleCommand);
  127. HotKeyBindings.Add (Key.F3, Command.Open);
  128. AddCommand (Command.Save, HandleCommand);
  129. HotKeyBindings.Add (Key.F4, Command.Save);
  130. AddCommand (Command.SaveAs, HandleCommand);
  131. HotKeyBindings.Add (Key.A.WithCtrl, Command.SaveAs);
  132. HotKeyBindings.Add (Key.W.WithCtrl, Command.EnableOverwrite);
  133. var fileMenu = new Menuv2
  134. {
  135. Id = "fileMenu"
  136. };
  137. ConfigureFileMenu (fileMenu);
  138. var optionsSubMenu = new Menuv2
  139. {
  140. Id = "optionsSubMenu",
  141. Visible = false
  142. };
  143. ConfigureOptionsSubMenu (optionsSubMenu);
  144. var optionsSubMenuItem = new MenuItemv2 (this, Command.NotBound, "O_ptions", "File options", optionsSubMenu);
  145. fileMenu.Add (optionsSubMenuItem);
  146. var detailsSubMenu = new Menuv2
  147. {
  148. Id = "detailsSubMenu",
  149. Visible = false
  150. };
  151. ConfigureDetialsSubMenu (detailsSubMenu);
  152. var detailsSubMenuItem = new MenuItemv2 (this, Command.NotBound, "_Details", "File details", detailsSubMenu);
  153. fileMenu.Add (detailsSubMenuItem);
  154. var moreDetailsSubMenu = new Menuv2
  155. {
  156. Id = "moreDetailsSubMenu",
  157. Visible = false
  158. };
  159. ConfigureMoreDetailsSubMenu (moreDetailsSubMenu);
  160. var moreDetailsSubMenuItem = new MenuItemv2 (this, Command.NotBound, "_More Details", "More details", moreDetailsSubMenu);
  161. detailsSubMenu.Add (moreDetailsSubMenuItem);
  162. FilePopoverMenu = new (fileMenu)
  163. {
  164. Id = "FilePopoverMenu"
  165. };
  166. MenuBarItemv2 fileMenuRootItem = new ("_File", FilePopoverMenu);
  167. AddCommand (Command.Cut, HandleCommand);
  168. HotKeyBindings.Add (Key.X.WithCtrl, Command.Cut);
  169. AddCommand (Command.Copy, HandleCommand);
  170. HotKeyBindings.Add (Key.C.WithCtrl, Command.Copy);
  171. AddCommand (Command.Paste, HandleCommand);
  172. HotKeyBindings.Add (Key.V.WithCtrl, Command.Paste);
  173. AddCommand (Command.SelectAll, HandleCommand);
  174. HotKeyBindings.Add (Key.T.WithCtrl, Command.SelectAll);
  175. Add (new MenuBarv2 (
  176. [
  177. fileMenuRootItem,
  178. new MenuBarItemv2 (
  179. "_Edit",
  180. [
  181. new MenuItemv2 (this, Command.Cut),
  182. new MenuItemv2 (this, Command.Copy),
  183. new MenuItemv2 (this, Command.Paste),
  184. new Line (),
  185. new MenuItemv2 (this, Command.SelectAll)
  186. ]
  187. ),
  188. new MenuBarItemv2 (this, Command.NotBound, "_Help")
  189. {
  190. Key = Key.F1,
  191. Action = () => { MessageBox.Query ("Help", "This is the help...", "_Ok"); }
  192. }
  193. ]
  194. )
  195. );
  196. Label lastAcceptedLabel = new ()
  197. {
  198. Title = "Last Accepted:",
  199. X = Pos.Left (lastCommandLabel),
  200. Y = Pos.Bottom (lastCommandLabel)
  201. };
  202. View lastAcceptedText = new ()
  203. {
  204. X = Pos.Right (lastAcceptedLabel) + 1,
  205. Y = Pos.Top (lastAcceptedLabel),
  206. Height = Dim.Auto (),
  207. Width = Dim.Auto ()
  208. };
  209. Add (lastAcceptedLabel, lastAcceptedText);
  210. CheckBox autoSaveStatusCb = new ()
  211. {
  212. Title = "AutoSave",
  213. X = Pos.Left (lastAcceptedLabel),
  214. Y = Pos.Bottom (lastAcceptedLabel)
  215. };
  216. autoSaveStatusCb.CheckedStateChanged += (sender, args) => { _autoSaveCb!.CheckedState = autoSaveStatusCb.CheckedState; };
  217. Add (autoSaveStatusCb);
  218. CheckBox enableOverwriteStatusCb = new ()
  219. {
  220. Title = "Enable Overwrite",
  221. X = Pos.Left (autoSaveStatusCb),
  222. Y = Pos.Bottom (autoSaveStatusCb)
  223. };
  224. enableOverwriteStatusCb.CheckedStateChanged += (sender, args) => { _enableOverwriteCb!.CheckedState = enableOverwriteStatusCb.CheckedState; };
  225. base.Add (enableOverwriteStatusCb);
  226. AddCommand (
  227. Command.EnableOverwrite,
  228. ctx =>
  229. {
  230. enableOverwriteStatusCb.CheckedState =
  231. enableOverwriteStatusCb.CheckedState == CheckState.UnChecked ? CheckState.Checked : CheckState.UnChecked;
  232. return HandleCommand (ctx);
  233. });
  234. CheckBox editModeStatusCb = new ()
  235. {
  236. Title = "EditMode (App binding)",
  237. X = Pos.Left (enableOverwriteStatusCb),
  238. Y = Pos.Bottom (enableOverwriteStatusCb)
  239. };
  240. editModeStatusCb.CheckedStateChanged += (sender, args) => { _editModeCb!.CheckedState = editModeStatusCb.CheckedState; };
  241. base.Add (editModeStatusCb);
  242. AddCommand (Command.Edit, ctx =>
  243. {
  244. editModeStatusCb.CheckedState =
  245. editModeStatusCb.CheckedState == CheckState.UnChecked ? CheckState.Checked : CheckState.UnChecked;
  246. return HandleCommand (ctx);
  247. });
  248. Application.KeyBindings.Add (Key.F9, this, Command.Edit);
  249. FilePopoverMenu!.Accepted += (o, args) =>
  250. {
  251. lastAcceptedText.Text = args?.Context?.Source?.Title!;
  252. if (args?.Context?.Source is MenuItemv2 mi && mi.CommandView == _autoSaveCb)
  253. {
  254. autoSaveStatusCb.CheckedState = _autoSaveCb.CheckedState;
  255. }
  256. };
  257. FilePopoverMenu!.VisibleChanged += (sender, args) =>
  258. {
  259. if (FilePopoverMenu!.Visible)
  260. {
  261. lastCommandText.Text = string.Empty;
  262. }
  263. };
  264. Add (
  265. new Button
  266. {
  267. Title = "_Button",
  268. X = Pos.Center (),
  269. Y = Pos.Center ()
  270. });
  271. autoSaveStatusCb.SetFocus ();
  272. return;
  273. // Add the commands supported by this View
  274. bool? HandleCommand (ICommandContext? ctx)
  275. {
  276. lastCommandText.Text = ctx?.Command!.ToString ()!;
  277. return true;
  278. }
  279. }
  280. private void ConfigureFileMenu (Menuv2 menu)
  281. {
  282. var newFile = new MenuItemv2
  283. {
  284. Command = Command.New,
  285. TargetView = this
  286. };
  287. var openFile = new MenuItemv2
  288. {
  289. Command = Command.Open,
  290. TargetView = this
  291. };
  292. var saveFile = new MenuItemv2
  293. {
  294. Command = Command.Save,
  295. TargetView = this
  296. };
  297. var saveFileAs = new MenuItemv2 (this, Command.SaveAs);
  298. menu.Add (newFile, openFile, saveFile, saveFileAs, new Line ());
  299. }
  300. private void ConfigureOptionsSubMenu (Menuv2 menu)
  301. {
  302. // This is an example of a menu item with a checkbox that is NOT
  303. // bound to a Command. The PopoverMenu will raise Accepted when Alt-U is pressed.
  304. // The checkbox state will automatically toggle each time Alt-U is pressed beacuse
  305. // the MenuItem actaully gets the key events.
  306. var autoSave = new MenuItemv2
  307. {
  308. Title = "_Auto Save",
  309. Text = "(no Command)",
  310. Key = Key.F10
  311. };
  312. autoSave.CommandView = _autoSaveCb = new ()
  313. {
  314. Title = autoSave.Title,
  315. HighlightStyle = HighlightStyle.None,
  316. CanFocus = false
  317. };
  318. // This is an example of a MenuItem with a checkbox that is bound to a command.
  319. // When the key bound to Command.EntableOverwrite is pressed, InvokeCommand will invoke it
  320. // on targetview, and thus the MenuItem will never see the key event.
  321. // Because of this, the check box will not automatically track the state.
  322. var enableOverwrite = new MenuItemv2
  323. {
  324. Title = "Enable _Overwrite",
  325. Text = "Overwrite",
  326. Command = Command.EnableOverwrite,
  327. TargetView = this
  328. };
  329. enableOverwrite.CommandView = _enableOverwriteCb = new ()
  330. {
  331. Title = enableOverwrite.Title,
  332. HighlightStyle = HighlightStyle.None,
  333. CanFocus = false
  334. };
  335. _enableOverwriteCb.Accepting += (sender, args) => args.Cancel = true;
  336. var mutuallyExclusiveOptions = new MenuItemv2
  337. {
  338. HelpText = "3 Mutually Exclusive Options",
  339. Key = Key.F7
  340. };
  341. mutuallyExclusiveOptions.CommandView = _mutuallyExclusiveOptionsRg = new RadioGroup ()
  342. {
  343. RadioLabels = [ "G_ood", "_Bad", "U_gly" ]
  344. };
  345. var menuBGColor = new MenuItemv2
  346. {
  347. HelpText = "Menu BG Color",
  348. Key = Key.F8,
  349. };
  350. menuBGColor.CommandView = _menuBgColorCp = new ColorPicker()
  351. {
  352. Width = 30
  353. };
  354. _menuBgColorCp.ColorChanged += (sender, args) =>
  355. {
  356. menu.ColorScheme = menu.ColorScheme with
  357. {
  358. Normal = new (menu.ColorScheme.Normal.Foreground, args.CurrentValue)
  359. };
  360. };
  361. menu.Add (autoSave, enableOverwrite, new Line (), mutuallyExclusiveOptions, new Line (), menuBGColor);
  362. }
  363. private void ConfigureDetialsSubMenu (Menuv2 menu)
  364. {
  365. var shortcut2 = new MenuItemv2
  366. {
  367. Title = "_Detail 1",
  368. Text = "Some detail #1"
  369. };
  370. var shortcut3 = new MenuItemv2
  371. {
  372. Title = "_Three",
  373. Text = "The 3rd item"
  374. };
  375. var editMode = new MenuItemv2
  376. {
  377. Title = "E_dit Mode",
  378. Text = "App binding to Command.Edit",
  379. Command = Command.Edit,
  380. };
  381. editMode.CommandView = _editModeCb = new CheckBox
  382. {
  383. Title = editMode.Title,
  384. HighlightStyle = HighlightStyle.None,
  385. CanFocus = false
  386. };
  387. // This ensures the checkbox state toggles when the hotkey of Title is pressed.
  388. //shortcut4.Accepting += (sender, args) => args.Cancel = true;
  389. menu.Add (shortcut2, shortcut3, new Line (), editMode);
  390. }
  391. private void ConfigureMoreDetailsSubMenu (Menuv2 menu)
  392. {
  393. var deeperDetail = new MenuItemv2
  394. {
  395. Title = "_Deeper Detail",
  396. Text = "Deeper Detail",
  397. Action = () => { MessageBox.Query ("Deeper Detail", "Lots of details", "_Ok"); }
  398. };
  399. var shortcut4 = new MenuItemv2
  400. {
  401. Title = "_Third",
  402. Text = "Below the line"
  403. };
  404. // This ensures the checkbox state toggles when the hotkey of Title is pressed.
  405. //shortcut4.Accepting += (sender, args) => args.Cancel = true;
  406. menu.Add (deeperDetail, new Line (), shortcut4);
  407. }
  408. }
  409. private const string LOGFILE_LOCATION = "./logs";
  410. private static readonly string _logFilePath = string.Empty;
  411. private static readonly LoggingLevelSwitch _logLevelSwitch = new ();
  412. private static ILogger CreateLogger ()
  413. {
  414. // Configure Serilog to write logs to a file
  415. _logLevelSwitch.MinimumLevel = LogEventLevel.Verbose;
  416. Log.Logger = new LoggerConfiguration ()
  417. .MinimumLevel.ControlledBy (_logLevelSwitch)
  418. .Enrich.FromLogContext () // Enables dynamic enrichment
  419. .WriteTo.Debug ()
  420. .WriteTo.File (
  421. _logFilePath,
  422. rollingInterval: RollingInterval.Day,
  423. outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
  424. .CreateLogger ();
  425. // Create a logger factory compatible with Microsoft.Extensions.Logging
  426. using ILoggerFactory loggerFactory = LoggerFactory.Create (
  427. builder =>
  428. {
  429. builder
  430. .AddSerilog (dispose: true) // Integrate Serilog with ILogger
  431. .SetMinimumLevel (LogLevel.Trace); // Set minimum log level
  432. });
  433. // Get an ILogger instance
  434. return loggerFactory.CreateLogger ("Global Logger");
  435. }
  436. }