From c211dffb4687a7e1c5cd0b7bf869c0e6dd0e4065 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 15 Apr 2020 18:25:04 -0600 Subject: [PATCH] Smarter StatusBar bottom tracking. --- Example/demo.cs | 153 +++++++++--------- Terminal.Gui/Views/StatusBar.cs | 266 +++++++++++++++++--------------- 2 files changed, 219 insertions(+), 200 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 5abd08dc1..73dfdf201 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -7,8 +7,8 @@ using System.Globalization; using System.Reflection; using NStack; -static class Demo { - //class Box10x : View, IScrollView { +static class Demo { + //class Box10x : View, IScrollView { class Box10x : View { int w = 40; int h = 50; @@ -30,20 +30,20 @@ static class Demo { } public override void Redraw (Rect region) - { - //Point pos = new Point (region.X, region.Y); + { + //Point pos = new Point (region.X, region.Y); Driver.SetAttribute (ColorScheme.Focus); for (int y = 0; y < h; y++) { Move (0, y); Driver.AddStr (y.ToString ()); - for (int x = 0; x < w - y.ToString ().Length; x++) { - //Driver.AddRune ((Rune)('0' + (x + y) % 10)); + for (int x = 0; x < w - y.ToString ().Length; x++) { + //Driver.AddRune ((Rune)('0' + (x + y) % 10)); if (y.ToString ().Length < w) Driver.AddStr (" "); } - } - //Move (pos.X, pos.Y); + } + //Move (pos.X, pos.Y); } } @@ -93,10 +93,10 @@ static class Demo { int i = 0; string txt = "Hello world, how are you doing today"; container.Add ( - new Label (new Rect (0, 1, 40, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left }, - new Label (new Rect (0, 3, 40, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right }, - new Label (new Rect (0, 5, 40, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered }, - new Label (new Rect (0, 7, 40, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified } + new Label (new Rect (0, 1, 40, 3), $"{i + 1}-{txt}") { TextAlignment = TextAlignment.Left }, + new Label (new Rect (0, 3, 40, 3), $"{i + 2}-{txt}") { TextAlignment = TextAlignment.Right }, + new Label (new Rect (0, 5, 40, 3), $"{i + 3}-{txt}") { TextAlignment = TextAlignment.Centered }, + new Label (new Rect (0, 7, 40, 3), $"{i + 4}-{txt}") { TextAlignment = TextAlignment.Justified } ); Application.Run (container); @@ -105,18 +105,18 @@ static class Demo { static void ShowEntries (View container) { var scrollView = new ScrollView (new Rect (50, 10, 20, 8)) { - ContentSize = new Size (20, 50), - //ContentOffset = new Point (0, 0), + ContentSize = new Size (20, 50), + //ContentOffset = new Point (0, 0), ShowVerticalScrollIndicator = true, ShowHorizontalScrollIndicator = true - }; + }; #if false scrollView.Add (new Box10x (0, 0)); #else - scrollView.Add (new Filler (new Rect (0, 0, 40, 40))); + scrollView.Add (new Filler (new Rect (0, 0, 40, 40))); #endif - - // This is just to debug the visuals of the scrollview when small + + // This is just to debug the visuals of the scrollview when small var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) { ContentSize = new Size (100, 100), ShowVerticalScrollIndicator = true, @@ -130,12 +130,12 @@ static class Demo { return true; } - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer); - - - // A little convoluted, this is because I am using this to test the - // layout based on referencing elements of another view: - + Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer); + + + // A little convoluted, this is because I am using this to test the + // layout based on referencing elements of another view: + var login = new Label ("Login: ") { X = 3, Y = 6 }; var password = new Label ("Password: ") { X = Pos.Left (login), @@ -154,8 +154,8 @@ static class Demo { Width = Dim.Width (loginText) }; - var tf = new Button (3, 19, "Ok"); - // Add some content + var tf = new Button (3, 19, "Ok"); + // Add some content container.Add ( login, loginText, @@ -202,10 +202,10 @@ static class Demo { ml2 = new Label (1, 1, "Mouse Debug Line"); d.Add (ml2); Application.Run (d); - } - - // - // Creates a nested editor + } + + // + // Creates a nested editor static void Editor (Toplevel top) { var tframe = top.Frame; @@ -255,12 +255,12 @@ static class Demo { static void Close () { MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok"); - } - - // Watch what happens when I try to introduce a newline after the first open brace - // it introduces a new brace instead, and does not indent. Then watch me fight - // the editor as more oddities happen. - + } + + // Watch what happens when I try to introduce a newline after the first open brace + // it introduces a new brace instead, and does not indent. Then watch me fight + // the editor as more oddities happen. + public static void Open () { var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true }; @@ -368,10 +368,10 @@ static class Demo { static void Help () { MessageBox.Query (50, 7, "Help", "This is a small help\nBe kind.", "Ok"); - } - + } + #region Selection Demo - + static void ListSelectionDemo (bool multiple) { var d = new Dialog ("Selection Demo", 60, 20, @@ -404,20 +404,19 @@ static class Demo { } } MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok"); - } + } #endregion - - + + #region OnKeyDown / OnKeyUp Demo private static void OnKeyDownUpDemo () { var container = new Dialog ( - "OnKeyDown & OnKeyUp demo", 80, 20, - new Button ("Close") { Clicked = () => { Application.RequestStop (); } }) { - Width = Dim.Fill (), + "OnKeyDown & OnKeyUp demo", 0, 0) { + Width = Dim.Fill () , Height = Dim.Fill (), - }; - + }; + var list = new List (); var listView = new ListView (list) { X = 0, @@ -431,23 +430,24 @@ static class Demo { void KeyUpDown (KeyEvent keyEvent, string updown) { if ((keyEvent.Key & Key.CtrlMask) != 0) { - list.Add ($"Key{updown, -4}: Ctrl "); + list.Add ($"Key{updown,-4}: Ctrl "); } else if ((keyEvent.Key & Key.AltMask) != 0) { - list.Add ($"Key{updown, -4}: Alt "); + list.Add ($"Key{updown,-4}: Alt "); } else { - list.Add ($"Key{updown, -4}: {(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}"); + list.Add ($"Key{updown,-4}: {(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}"); } listView.MoveDown (); } container.OnKeyDown += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Down"); - container.OnKeyUp += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Up"); + container.OnKeyUp += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Up"); + Application.Run (container); - } - - -#endregion - + } + + + #endregion + public static Label ml; public static MenuBar menu; public static CheckBox menuKeysStyle; @@ -455,23 +455,23 @@ static class Demo { static void Main () { if (Debugger.IsAttached) - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); - - //Application.UseSystemConsole = true; + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); + + //Application.UseSystemConsole = true; Console.WindowHeight = 35; Application.Init (); - var top = Application.Top; - - //Open (); + var top = Application.Top; + + //Open (); #if true var win = new Window ("Hello") { X = 1, Y = 1, Width = Dim.Fill (), Height = Dim.Fill () - 1 - }; + }; #else var tframe = top.Frame; @@ -561,27 +561,30 @@ static class Demo { new StatusItem(Key.F2, "~F2~ Load", null), new StatusItem(Key.F3, "~F3~ Save", null), new StatusItem(Key.ControlX, "~^X~ Quit", () => { if (Quit ()) top.Running = false; }), - }); - - win.Add (drag, dragText); -#if true - // This currently causes a stack overflow, because it is referencing a window that has not had its size allocated yet + }) { + Style = StatusBar.StatusBarStyle.SnapToBottom, + Parent = null, + }; + win.Add (drag, dragText); +#if false + // This currently causes a stack overflow, because it is referencing a window that has not had its size allocated yet + var bottom = new Label ("This should go on the bottom!"); win.Add (bottom); - Application.OnResized = () => { + Application.OnResized += () => { bottom.X = Pos.Left (win); bottom.Y = Pos.Bottom (win); - }; + }; #endif - - - top.Add (win); - //top.Add (menu); + + + top.Add (win); + //top.Add (menu); top.Add (menu, statusBar, ml); - OnKeyDownUpDemo (); + //OnKeyDownUpDemo (); Application.Run (); } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index f9121c056..5d5145a89 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -6,149 +6,165 @@ // // TODO: // Add mouse support -// Uses internals of Application -using System; -using NStack; - +using System; +using NStack; + namespace Terminal.Gui { - /// - /// A statusbar item has a title, a shortcut aka hotkey, and an action to execute on activation. - /// Such an item is ment to be as part of the global hotkeys of the application, which are available in the current context of the screen. - /// The colour of the text will be changed after each ~. Having an statusbar item with a text of `~F1~ Help` will draw *F1* as shortcut and - /// *Help* as standard text. - /// + /// + /// A statusbar item has a title, a shortcut aka hotkey, and an action to execute on activation. + /// Such an item is ment to be as part of the global hotkeys of the application, which are available in the current context of the screen. + /// The colour of the text will be changed after each ~. Having an statusbar item with a text of `~F1~ Help` will draw *F1* as shortcut and + /// *Help* as standard text. + /// public class StatusItem { - /// - /// Initializes a new . - /// - /// Shortcut to activate the item. - /// Title for the statusbar item. - /// Action to invoke when the staturbar item is activated. + /// + /// Initializes a new . + /// + /// Shortcut to activate the item. + /// Title for the statusbar item. + /// Action to invoke when the staturbar item is activated. public StatusItem (Key shortcut, ustring title, Action action) - { - Title = title ?? ""; - Shortcut = shortcut; - Action = action; + { + Title = title ?? ""; + Shortcut = shortcut; + Action = action; } - /// - /// This is the global setting that can be used as a global shortcut to invoke the action on the menu. - /// + /// + /// This is the global setting that can be used as a global shortcut to invoke the action on the menu. + /// public Key Shortcut { get; } - /// - /// Gets or sets the title. - /// - /// The title. + /// + /// Gets or sets the title. + /// + /// The title. public ustring Title { get; } - /// - /// Gets or sets the action to be invoked when the statusbar item is triggered - /// - /// Method to invoke. - public Action Action { get; } + /// + /// Gets or sets the action to be invoked when the statusbar item is triggered + /// + /// Method to invoke. + public Action Action { get; } }; - /// - /// A statusbar for your application. - /// The statusbar should be context sensitive. This means, if the main menu and an open text editor are visible, the items probably shown will - /// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help. - /// So for each context must be a new instance of a statusbar. - /// + /// + /// A statusbar for your application. + /// The statusbar should be context sensitive. This means, if the main menu and an open text editor are visible, the items probably shown will + /// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help. + /// So for each context must be a new instance of a statusbar. + /// public class StatusBar : View { - /// - /// The style supported by StatusBar - /// + /// + /// The style supported by StatusBar + /// public enum StatusBarStyle { - /// - /// The StatusBar will snap at the the bottom line of the console window. - /// If the console window is made larger while the app is runing, the StatusBar - /// will continue to snap to the bottom. If the console window is subsequently - /// made shorter, the status bar will be invisible. This is the default. - /// - SnapToConsoleBottom = 0, + Default = 0, + /// + /// The StatusBar will snap at the the bottom line of the Parent view. + /// If the console window is made larger while the app is runing, the StatusBar + /// will continue to snap to the bottom line of the Parent, staying visible. + /// On consoles that support resizing of console apps (e.g. Windows Terminal and ConEmu), + /// if the console window is subsequently made shorter, the status bar will remain visible + /// as the Parent view resizes. If Parent is null, the StatusBar will snap to the bottom line + /// of the console window. + /// This is the default. + /// + SnapToBottom = Default, - /// - /// The StatusBar will snap at the the bottom line of the Application.Top view. - /// If the console window is made larger while the app is runing, the StatusBar - /// will continue to snap to the bottom line of Application.Top, staying visible. - /// If the console window is subsequently made shorter, the status bar will remain visible - /// at the last visible line. - /// - SnapToAppBottom = 1, + /// + /// The StatusBar will act identically to MenuBar, snapping to the first line of the + /// console window. + /// + SnapToTop = 1, + } + + public StatusBarStyle Style { get; set; } = StatusBarStyle.Default; + + public View Parent { get; set; } - /// - /// The StatusBar will act identically to MenuBar, snapping to the first line of the - /// console window. - /// - SnapToTop = 2, - } - public StatusItem [] Items { get; set; } - /// - /// Initializes a new instance of the class with the specified set of statusbar items. - /// It will be drawn in the lowest line of the terminal. - /// - /// A list of statusbar items. - public StatusBar (StatusItem [] items) : base () - { - X = 0; - Y = Application.Driver.Rows - 1; // TODO: using internals of Application - Width = Dim.Fill (); - Height = 1; - Items = items; - CanFocus = false; - ColorScheme = Colors.Menu; - } - - Attribute ToggleScheme (Attribute scheme) - { - var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal; - Driver.SetAttribute (result); - return result; - } - + /// + /// Initializes a new instance of the class with the specified set of statusbar items. + /// It will be drawn in the lowest line of the terminal. + /// + /// A list of statusbar items. + public StatusBar (StatusItem [] items) : base () + { + Width = Dim.Fill (); + Height = 1; + Items = items; + CanFocus = false; + ColorScheme = Colors.Menu; + + Application.OnResized += () => { + X = 0; + Height = 1; + + switch (Style) { + case StatusBarStyle.SnapToBottom: + if (Parent == null) { + Y = Application.Driver.Rows - 1; // TODO: using internals of Application + } else { + Y = Pos.Bottom (Parent); + } + break; + case StatusBarStyle.SnapToTop: + X = 0; + Y = 0; + break; + } + }; + } + + Attribute ToggleScheme (Attribute scheme) + { + var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal; + Driver.SetAttribute (result); + return result; + } + public override void Redraw (Rect region) - { - if (Frame.Y != Driver.Rows - 1) { - Frame = new Rect (Frame.X, Driver.Rows - 1, Frame.Width, Frame.Height); - Y = Driver.Rows - 1; - SetNeedsDisplay (); - } - - Move (0, 0); - Driver.SetAttribute (ColorScheme.Normal); - for (int i = 0; i < Frame.Width; i++) - Driver.AddRune (' '); - - Move (1, 0); - var scheme = ColorScheme.Normal; - Driver.SetAttribute (scheme); - for (int i = 0; i < Items.Length; i++) { - var title = Items [i].Title; - for (int n = 0; n < title.Length; n++) { - if (title [n] == '~') { - scheme = ToggleScheme (scheme); - continue; - } - Driver.AddRune (title [n]); - } - Driver.AddRune (' '); - } - } - - public override bool ProcessHotKey (KeyEvent kb) - { - foreach (var item in Items) { - if (kb.Key == item.Shortcut) { - if (item.Action != null) item.Action (); - return true; - } - } - return false; - } - } + { + //if (Frame.Y != Driver.Rows - 1) { + // Frame = new Rect (Frame.X, Driver.Rows - 1, Frame.Width, Frame.Height); + // Y = Driver.Rows - 1; + // SetNeedsDisplay (); + //} + + Move (0, 0); + Driver.SetAttribute (ColorScheme.Normal); + for (int i = 0; i < Frame.Width; i++) + Driver.AddRune (' '); + + Move (1, 0); + var scheme = ColorScheme.Normal; + Driver.SetAttribute (scheme); + for (int i = 0; i < Items.Length; i++) { + var title = Items [i].Title; + for (int n = 0; n < title.Length; n++) { + if (title [n] == '~') { + scheme = ToggleScheme (scheme); + continue; + } + Driver.AddRune (title [n]); + } + Driver.AddRune (' '); + } + } + + public override bool ProcessHotKey (KeyEvent kb) + { + foreach (var item in Items) { + if (kb.Key == item.Shortcut) { + if (item.Action != null) item.Action (); + return true; + } + } + return false; + } + } } \ No newline at end of file