Smarter StatusBar bottom tracking.

This commit is contained in:
Charlie Kindel
2020-04-15 18:25:04 -06:00
parent cd2c9e219d
commit c211dffb46
2 changed files with 219 additions and 200 deletions

View File

@@ -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<string> ();
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 ();
}

View File

@@ -6,149 +6,165 @@
//
// TODO:
// Add mouse support
// Uses internals of Application
using System;
using NStack;
using System;
using NStack;
namespace Terminal.Gui {
/// <summary>
/// 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.
/// </summary>
/// <summary>
/// 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.
/// </summary>
public class StatusItem {
/// <summary>
/// Initializes a new <see cref="T:Terminal.Gui.StatusItem"/>.
/// </summary>
/// <param name="shortcut">Shortcut to activate the item.</param>
/// <param name="title">Title for the statusbar item.</param>
/// <param name="action">Action to invoke when the staturbar item is activated.</param>
/// <summary>
/// Initializes a new <see cref="T:Terminal.Gui.StatusItem"/>.
/// </summary>
/// <param name="shortcut">Shortcut to activate the item.</param>
/// <param name="title">Title for the statusbar item.</param>
/// <param name="action">Action to invoke when the staturbar item is activated.</param>
public StatusItem (Key shortcut, ustring title, Action action)
{
Title = title ?? "";
Shortcut = shortcut;
Action = action;
{
Title = title ?? "";
Shortcut = shortcut;
Action = action;
}
/// <summary>
/// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
/// </summary>
/// <summary>
/// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
/// </summary>
public Key Shortcut { get; }
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
public ustring Title { get; }
/// <summary>
/// Gets or sets the action to be invoked when the statusbar item is triggered
/// </summary>
/// <value>Method to invoke.</value>
public Action Action { get; }
/// <summary>
/// Gets or sets the action to be invoked when the statusbar item is triggered
/// </summary>
/// <value>Method to invoke.</value>
public Action Action { get; }
};
/// <summary>
/// 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.
/// </summary>
/// <summary>
/// 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.
/// </summary>
public class StatusBar : View {
/// <summary>
/// The style supported by StatusBar
/// </summary>
/// <summary>
/// The style supported by StatusBar
/// </summary>
public enum StatusBarStyle {
/// <summary>
/// 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.
/// </summary>
SnapToConsoleBottom = 0,
Default = 0,
/// <summary>
/// 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.
/// </summary>
SnapToBottom = Default,
/// <summary>
/// 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.
/// </summary>
SnapToAppBottom = 1,
/// <summary>
/// The StatusBar will act identically to MenuBar, snapping to the first line of the
/// console window.
/// </summary>
SnapToTop = 1,
}
public StatusBarStyle Style { get; set; } = StatusBarStyle.Default;
public View Parent { get; set; }
/// <summary>
/// The StatusBar will act identically to MenuBar, snapping to the first line of the
/// console window.
/// </summary>
SnapToTop = 2,
}
public StatusItem [] Items { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="T:Terminal.Gui.StatusBar"/> class with the specified set of statusbar items.
/// It will be drawn in the lowest line of the terminal.
/// </summary>
/// <param name="items">A list of statusbar items.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Terminal.Gui.StatusBar"/> class with the specified set of statusbar items.
/// It will be drawn in the lowest line of the terminal.
/// </summary>
/// <param name="items">A list of statusbar items.</param>
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;
}
}
}