diff --git a/.editorconfig b/.editorconfig
index 3ad2ee46d..576839082 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -17,3 +17,6 @@ csharp_preserve_single_line_blocks = true
dotnet_style_require_accessibility_modifiers = never
csharp_style_var_when_type_is_apparent = true
csharp_prefer_braces = false
+csharp_space_before_open_square_brackets = true
+csharp_space_between_method_call_name_and_opening_parenthesis = true
+csharp_space_between_method_declaration_name_and_open_parenthesis = true
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f10938059..c4865c0ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ obj
*.userprefs
*~
packages
-.vs
\ No newline at end of file
+.vs
+*.csproj.user
diff --git a/Designer/Designer.csproj b/Designer/Designer.csproj
index e87bd0a6b..545c2b0b9 100644
--- a/Designer/Designer.csproj
+++ b/Designer/Designer.csproj
@@ -7,7 +7,7 @@
Exe
Designer
Designer
- v4.6.1
+ v4.7.2
@@ -30,13 +30,10 @@
x86
+
+ ..\packages\NStack.Core.0.14.0\lib\netstandard2.0\NStack.dll
+
-
- ..\packages\NStack.Core.0.11.0\lib\netstandard1.5\NStack.dll
-
-
- ..\packages\NStack.Core.0.11.0\lib\netstandard1.5\NStack.dll
-
@@ -48,8 +45,7 @@
-
-
+
\ No newline at end of file
diff --git a/Designer/Program.cs b/Designer/Program.cs
index d3dac91a4..a7d6a17f8 100644
--- a/Designer/Program.cs
+++ b/Designer/Program.cs
@@ -1,5 +1,6 @@
using System;
using Terminal.Gui;
+using Attribute = Terminal.Gui.Attribute;
namespace Designer {
class Surface : Window {
@@ -9,12 +10,18 @@ namespace Designer {
}
class MainClass {
+ static void Close ()
+ {
+ MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok");
+ }
+
public static void Main (string [] args)
{
Application.Init ();
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
+ new MenuItem ("_Close", "", () => Close ()),
new MenuItem ("_Quit", "", () => { Application.RequestStop (); })
}),
new MenuBarItem ("_Edit", new MenuItem [] {
@@ -37,8 +44,27 @@ namespace Designer {
Height = Dim.Fill ()
};
- //Application.Top.Add (menu);
- Application.Top.Add (login, password);
+ var loginText = new TextField("") {
+ X = Pos.Right(password),
+ Y = Pos.Top(login),
+ Width = 40,
+ ColorScheme = new ColorScheme() {
+ Focus = Attribute.Make(Color.BrightYellow, Color.DarkGray),
+ Normal = Attribute.Make(Color.Green, Color.BrightYellow),
+ HotFocus = Attribute.Make(Color.BrightBlue, Color.Brown),
+ HotNormal = Attribute.Make(Color.Red, Color.BrightRed),
+ },
+ };
+
+ var passText = new TextField ("") {
+ Secret = true,
+ X = Pos.Left (loginText),
+ Y = Pos.Top (password),
+ Width = Dim.Width (loginText)
+ };
+
+ surface.Add (login, password, loginText, passText);
+ Application.Top.Add (menu, surface);
Application.Run ();
}
}
diff --git a/Designer/app.config b/Designer/app.config
deleted file mode 100644
index 3dbff35f4..000000000
--- a/Designer/app.config
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/Designer/packages.config b/Designer/packages.config
index e18eee05a..40bce897a 100644
--- a/Designer/packages.config
+++ b/Designer/packages.config
@@ -1,4 +1,4 @@
-
+
-
-
+
+
\ No newline at end of file
diff --git a/Example/Example.csproj b/Example/Example.csproj
index 3f6523667..dff1d63ad 100644
--- a/Example/Example.csproj
+++ b/Example/Example.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -7,7 +7,10 @@
Exe
Terminal
Terminal
- v4.6.1
+ v4.7.2
+
+
+
true
@@ -30,14 +33,10 @@
x86
+
+ ..\packages\NStack.Core.0.14.0\lib\netstandard2.0\NStack.dll
+
-
- ..\packages\NStack.Core.0.11.0\lib\netstandard1.5\NStack.dll
- False
-
-
- ..\packages\NStack.Core.0.11.0\lib\netstandard1.5\NStack.dll
-
@@ -52,4 +51,4 @@
-
+
\ No newline at end of file
diff --git a/Example/demo.cs b/Example/demo.cs
index 3cdf6a6f4..4d1a3a265 100644
--- a/Example/demo.cs
+++ b/Example/demo.cs
@@ -1,28 +1,49 @@
using Terminal.Gui;
using System;
using Mono.Terminal;
-using System.Collections;
using System.Collections.Generic;
-
+using System.Diagnostics;
+using System.Globalization;
+using System.Reflection;
+using NStack;
static class Demo {
+ //class Box10x : View, IScrollView {
class Box10x : View {
- public Box10x (int x, int y) : base (new Rect (x, y, 10, 10))
+ int w = 40;
+ int h = 50;
+
+ public bool WantCursorPosition { get; set; } = false;
+
+ public Box10x (int x, int y) : base (new Rect (x, y, 20, 10))
{
}
+ public Size GetContentSize ()
+ {
+ return new Size (w, h);
+ }
+
+ public void SetCursorPosition (Point pos)
+ {
+ throw new NotImplementedException ();
+ }
+
public override void Redraw (Rect region)
{
+ //Point pos = new Point (region.X, region.Y);
Driver.SetAttribute (ColorScheme.Focus);
- for (int y = 0; y < 10; y++) {
+ for (int y = 0; y < h; y++) {
Move (0, y);
- for (int x = 0; x < 10; x++) {
-
- Driver.AddRune ((Rune)('0' + (x + y) % 10));
+ Driver.AddStr (y.ToString ());
+ 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);
}
}
@@ -42,6 +63,9 @@ static class Demo {
Rune r;
switch (x % 3) {
case 0:
+ Driver.AddRune (y.ToString ().ToCharArray (0, 1) [0]);
+ if (y > 9)
+ Driver.AddRune (y.ToString ().ToCharArray (1, 1) [0]);
r = '.';
break;
case 1:
@@ -60,24 +84,30 @@ static class Demo {
static void ShowTextAlignments (View container)
{
+ int i = 0;
+ string txt = "Hello world, how are you doing today";
container.Add (
- new Label (new Rect (0, 0, 40, 3), "1-Hello world, how are you doing today") { TextAlignment = TextAlignment.Left },
- new Label (new Rect (0, 4, 40, 3), "2-Hello world, how are you doing today") { TextAlignment = TextAlignment.Right },
- new Label (new Rect (0, 8, 40, 3), "3-Hello world, how are you doing today") { TextAlignment = TextAlignment.Centered },
- new Label (new Rect (0, 12, 40, 3), "4-Hello world, how are you doing today") { TextAlignment = TextAlignment.Justified });
+ new FrameView (new Rect (75, 3, txt.Length + 6, 20), "Text Alignments") {
+ new Label(new Rect(0, 1, 40, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left },
+ new Label(new Rect(0, 5, 40, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right },
+ new Label(new Rect(0, 9, 40, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered },
+ new Label(new Rect(0, 13, 40, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified }
+ });
}
static void ShowEntries (View container)
{
var scrollView = new ScrollView (new Rect (50, 10, 20, 8)) {
- ContentSize = new Size (100, 100),
- ContentOffset = new Point (-1, -1),
+ ContentSize = new Size (20, 50),
+ //ContentOffset = new Point (0, 0),
ShowVerticalScrollIndicator = true,
ShowHorizontalScrollIndicator = true
};
-
+#if false
scrollView.Add (new Box10x (0, 0));
- //scrollView.Add (new Filler (new Rect (0, 0, 40, 40)));
+#else
+ scrollView.Add (new Filler (new Rect (0, 0, 40, 40)));
+#endif
// This is just to debug the visuals of the scrollview when small
var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) {
@@ -93,7 +123,7 @@ static class Demo {
return true;
}
- //Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
+ Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
// A little convoluted, this is because I am using this to test the
@@ -117,6 +147,7 @@ static class Demo {
Width = Dim.Width (loginText)
};
+ var tf = new Button (3, 19, "Ok");
// Add some content
container.Add (
login,
@@ -127,7 +158,7 @@ static class Demo {
new CheckBox (1, 0, "Remember me"),
new RadioGroup (1, 2, new [] { "_Personal", "_Company" }),
},
- new ListView (new Rect (60, 6, 16, 4), new string [] {
+ new ListView (new Rect (59, 6, 16, 4), new string [] {
"First row",
"<>",
"This is a very long row that should overflow what is shown",
@@ -137,16 +168,20 @@ static class Demo {
"This is so cool"
}),
scrollView,
- //scrollView2,
- new Button (3, 19, "Ok"),
+ scrollView2,
+ tf,
new Button (10, 19, "Cancel"),
new TimeField (3, 20, DateTime.Now),
new TimeField (23, 20, DateTime.Now, true),
+ new DateField (3, 22, DateTime.Now),
+ new DateField (23, 22, DateTime.Now, true),
progress,
- new Label (3, 22, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar")
+ new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"),
+ menuKeysStyle,
+ menuAutoMouseNav
);
-
+ container.SendSubviewToBack (tf);
}
public static Label ml2;
@@ -162,12 +197,13 @@ static class Demo {
Application.Run (d);
}
- //
+ //
// Creates a nested editor
- static void Editor(Toplevel top) {
+ static void Editor (Toplevel top)
+ {
var tframe = top.Frame;
- var ntop = new Toplevel(tframe);
- var menu = new MenuBar(new MenuBarItem[] {
+ var ntop = new Toplevel (tframe);
+ var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
}),
@@ -177,25 +213,25 @@ static class Demo {
new MenuItem ("_Paste", "", null)
}),
});
- ntop.Add(menu);
+ ntop.Add (menu);
string fname = null;
- foreach (var s in new[] { "/etc/passwd", "c:\\windows\\win.ini" })
- if (System.IO.File.Exists(s)) {
+ foreach (var s in new [] { "/etc/passwd", "c:\\windows\\win.ini" })
+ if (System.IO.File.Exists (s)) {
fname = s;
break;
}
- var win = new Window(fname ?? "Untitled") {
+ var win = new Window (fname ?? "Untitled") {
X = 0,
Y = 1,
- Width = Dim.Fill(),
- Height = Dim.Fill()
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
};
- ntop.Add(win);
+ ntop.Add (win);
+
+ var text = new TextView (new Rect (0, 0, tframe.Width - 2, tframe.Height - 3));
- var text = new TextView(new Rect(0, 0, tframe.Width - 2, tframe.Height - 3));
-
if (fname != null)
text.Text = System.IO.File.ReadAllText (fname);
win.Add (text);
@@ -211,7 +247,7 @@ static class Demo {
static void Close ()
{
- MessageBox.ErrorQuery (50, 5, "Error", "There is nothing to close", "Ok");
+ 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
@@ -220,10 +256,11 @@ static class Demo {
public static void Open ()
{
- var d = new OpenDialog ("Open", "Open a file");
+ var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true };
Application.Run (d);
- MessageBox.Query (50, 7, "Selected File", string.Join (", ", d.FilePaths), "Ok");
+ if (!d.Canceled)
+ MessageBox.Query (50, 7, "Selected File", string.Join (", ", d.FilePaths), "Ok");
}
public static void ShowHex (Toplevel top)
@@ -254,7 +291,76 @@ static class Demo {
};
win.Add (hex);
Application.Run (ntop);
-
+
+ }
+
+ public class MenuItemDetails : MenuItem {
+ ustring title;
+ string help;
+ Action action;
+
+ public MenuItemDetails (ustring title, string help, Action action) : base (title, help, action)
+ {
+ this.title = title;
+ this.help = help;
+ this.action = action;
+ }
+
+ public static MenuItemDetails Instance (MenuItem mi)
+ {
+ return (MenuItemDetails)mi.GetMenuItem ();
+ }
+ }
+
+ public delegate MenuItem MenuItemDelegate (MenuItemDetails menuItem);
+
+ public static void ShowMenuItem (MenuItem mi)
+ {
+ BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
+ MethodInfo minfo = typeof (MenuItemDetails).GetMethod ("Instance", flags);
+ MenuItemDelegate mid = (MenuItemDelegate)Delegate.CreateDelegate (typeof (MenuItemDelegate), minfo);
+ MessageBox.Query (70, 7, mi.Title.ToString (),
+ $"{mi.Title.ToString ()} selected. Is from submenu: {mi.GetMenuBarItem ()}", "Ok");
+ }
+
+ static void MenuKeysStyle_Toggled (object sender, EventArgs e)
+ {
+ menu.UseKeysUpDownAsKeysLeftRight = menuKeysStyle.Checked;
+ }
+
+ static void MenuAutoMouseNav_Toggled (object sender, EventArgs e)
+ {
+ menu.WantMousePositionReports = menuAutoMouseNav.Checked;
+ }
+
+
+ static void Copy ()
+ {
+ TextField textField = menu.LastFocused as TextField;
+ if (textField != null && textField.SelectedLength != 0) {
+ textField.Copy ();
+ }
+ }
+
+ static void Cut ()
+ {
+ TextField textField = menu.LastFocused as TextField;
+ if (textField != null && textField.SelectedLength != 0) {
+ textField.Cut ();
+ }
+ }
+
+ static void Paste ()
+ {
+ TextField textField = menu.LastFocused as TextField;
+ if (textField != null) {
+ textField.Paste ();
+ }
+ }
+
+ static void Help ()
+ {
+ MessageBox.Query (50, 7, "Help", "This is a small help\nBe kind.", "Ok");
}
#region Selection Demo
@@ -278,7 +384,8 @@ static class Demo {
Y = 3,
Width = Dim.Fill () - 4,
Height = Dim.Fill () - 4,
- AllowsMarking = true
+ AllowsMarking = true,
+ AllowsMultipleSelection = false
};
d.Add (msg, list);
Application.Run (d);
@@ -295,60 +402,120 @@ static class Demo {
public static Label ml;
+ public static MenuBar menu;
+ public static CheckBox menuKeysStyle;
+ public static CheckBox menuAutoMouseNav;
static void Main ()
{
+ if (Debugger.IsAttached)
+ CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+
//Application.UseSystemConsole = true;
Application.Init ();
-
+
var top = Application.Top;
-
- var tframe = top.Frame;
+
//Open ();
#if true
var win = new Window ("Hello") {
X = 0,
- Y = 1,
+ Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
#else
+ var tframe = top.Frame;
+
var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height - 1), "Hello");
#endif
- var menu = new MenuBar (new MenuBarItem [] {
+ MenuItemDetails [] menuItems = {
+ new MenuItemDetails ("F_ind", "", null),
+ new MenuItemDetails ("_Replace", "", null),
+ new MenuItemDetails ("_Item1", "", null),
+ new MenuItemDetails ("_Not From Sub Menu", "", null)
+ };
+
+ menuItems [0].Action = () => ShowMenuItem (menuItems [0]);
+ menuItems [1].Action = () => ShowMenuItem (menuItems [1]);
+ menuItems [2].Action = () => ShowMenuItem (menuItems [2]);
+ menuItems [3].Action = () => ShowMenuItem (menuItems [3]);
+
+ menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("Text Editor Demo", "", () => { Editor (top); }),
new MenuItem ("_New", "Creates new file", NewFile),
new MenuItem ("_Open", "", Open),
new MenuItem ("_Hex", "", () => ShowHex (top)),
new MenuItem ("_Close", "", () => Close ()),
+ new MenuItem ("_Disabled", "", () => { }, () => false),
+ null,
new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
}),
new MenuBarItem ("_Edit", new MenuItem [] {
- new MenuItem ("_Copy", "", null),
- new MenuItem ("C_ut", "", null),
- new MenuItem ("_Paste", "", null)
+ new MenuItem ("_Copy", "", Copy),
+ new MenuItem ("C_ut", "", Cut),
+ new MenuItem ("_Paste", "", Paste),
+ new MenuItem ("_Find and Replace",
+ new MenuBarItem (new MenuItem[] {menuItems [0], menuItems [1] })),
+ menuItems[3]
}),
- new MenuBarItem ("_List Demos", new MenuItem [] {
+ new MenuBarItem ("_List Demos", new MenuItem [] {
new MenuItem ("Select Items", "", ListSelectionDemo),
}),
+ new MenuBarItem ("Test Menu and SubMenus", new MenuItem [] {
+ new MenuItem ("SubMenu1Item1",
+ new MenuBarItem (new MenuItem[] {
+ new MenuItem ("SubMenu2Item1",
+ new MenuBarItem (new MenuItem [] {
+ new MenuItem ("SubMenu3Item1",
+ new MenuBarItem (new MenuItem [] { menuItems [2] })
+ )
+ })
+ )
+ })
+ )
+ }),
});
+ menuKeysStyle = new CheckBox (3, 25, "UseKeysUpDownAsKeysLeftRight", true);
+ menuKeysStyle.Toggled += MenuKeysStyle_Toggled;
+ menuAutoMouseNav = new CheckBox (40, 25, "UseMenuAutoNavigation", true);
+ menuAutoMouseNav.Toggled += MenuAutoMouseNav_Toggled;
+
ShowEntries (win);
int count = 0;
- ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
+ ml = new Label (new Rect (3, 18, 47, 1), "Mouse: ");
Application.RootMouseEvent += delegate (MouseEvent me) {
-
+ ml.TextColor = Colors.TopLevel.Normal;
ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}";
};
-
+
var test = new Label (3, 18, "Se iniciará el análisis");
win.Add (test);
win.Add (ml);
-
- // ShowTextAlignments (win);
+
+ ShowTextAlignments (win);
+
+ var drag = new Label ("Drag: ") { X = 70, Y = 24 };
+ var dragText = new TextField ("") {
+ X = Pos.Right (drag),
+ Y = Pos.Top (drag),
+ Width = 40
+ };
+
+ var statusBar = new StatusBar (new StatusItem [] {
+ new StatusItem(Key.F1, "~F1~ Help", () => Help()),
+ new StatusItem(Key.F2, "~F2~ Load", null),
+ new StatusItem(Key.F3, "~F3~ Save", null),
+ new StatusItem(Key.ControlX, "~^X~ Quit", () => Quit()),
+ });
+
+ win.Add (drag, dragText);
+
top.Add (win);
- top.Add (menu);
+ //top.Add (menu);
+ top.Add (menu, statusBar, ml);
Application.Run ();
}
-}
\ No newline at end of file
+}
diff --git a/Example/packages.config b/Example/packages.config
index e18eee05a..40bce897a 100644
--- a/Example/packages.config
+++ b/Example/packages.config
@@ -1,4 +1,4 @@
-
+
-
-
+
+
\ No newline at end of file
diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs
index bc222f3a2..e89130556 100644
--- a/Terminal.Gui/Core.cs
+++ b/Terminal.Gui/Core.cs
@@ -134,8 +134,8 @@ namespace Terminal.Gui {
///
/// Determines the LayoutStyle for a view, if Absolute, during LayoutSubviews, the
- /// value from the Frame will be used, if the value is Computer, then the Frame
- /// will be updated from the X, Y Pos objets and the Width and Heigh Dim objects.
+ /// value from the Frame will be used, if the value is Computer, then the Frame
+ /// will be updated from the X, Y Pos objects and the Width and Height Dim objects.
///
public enum LayoutStyle {
///
@@ -229,6 +229,16 @@ namespace Terminal.Gui {
View focused = null;
Direction focusDirection;
+ ///
+ /// Event fired when the view get focus.
+ ///
+ public event EventHandler OnEnter;
+
+ ///
+ /// Event fired when the view lost focus.
+ ///
+ public event EventHandler OnLeave;
+
internal Direction FocusDirection {
get => SuperView?.FocusDirection ?? focusDirection;
set {
@@ -430,7 +440,7 @@ namespace Terminal.Gui {
SetNeedsDisplay (Bounds);
}
- bool layoutNeeded = true;
+ internal bool layoutNeeded = true;
internal void SetNeedsLayout ()
{
@@ -439,7 +449,7 @@ namespace Terminal.Gui {
layoutNeeded = true;
if (SuperView == null)
return;
- SuperView.layoutNeeded = true;
+ SuperView.SetNeedsLayout ();
}
///
@@ -649,14 +659,14 @@ namespace Terminal.Gui {
}
///
- /// Clears the specfied rectangular region with the current color
+ /// Clears the specified rectangular region with the current color
///
public void Clear (Rect r)
{
var h = r.Height;
var w = r.Width;
- for (int line = 0; line < h; line++) {
- Move (0, line);
+ for (int line = r.Y; line < r.Y + h; line++) {
+ Driver.Move (r.X, line);
for (int col = 0; col < w; col++)
Driver.AddRune (' ');
}
@@ -826,11 +836,16 @@ namespace Terminal.Gui {
}
internal set {
if (base.HasFocus != value)
+ if (value == true)
+ OnEnter?.Invoke (this, new EventArgs ());
+ else
+ OnLeave?.Invoke (this, new EventArgs ());
SetNeedsDisplay ();
base.HasFocus = value;
// Remove focus down the chain of subviews if focus is removed
if (value == false && focused != null) {
+ OnLeave?.Invoke (focused, new EventArgs ());
focused.HasFocus = false;
focused = null;
}
@@ -919,7 +934,9 @@ namespace Terminal.Gui {
if (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay) {
if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
- // TODO: optimize this by computing the intersection of region and view.Bounds
+ // FIXED: optimize this by computing the intersection of region and view.Bounds
+ if (view.layoutNeeded)
+ view.LayoutSubviews ();
view.Redraw (view.Bounds);
}
view.NeedDisplay = Rect.Empty;
@@ -1310,7 +1327,7 @@ namespace Terminal.Gui {
/// Frame.
public Toplevel (Rect frame) : base (frame)
{
- ColorScheme = Colors.Base;
+ Initialize ();
}
///
@@ -1318,11 +1335,16 @@ namespace Terminal.Gui {
///
public Toplevel () : base ()
{
- ColorScheme = Colors.Base;
+ Initialize ();
Width = Dim.Fill ();
Height = Dim.Fill ();
}
+ void Initialize ()
+ {
+ ColorScheme = Colors.Base;
+ }
+
///
/// Convenience factory method that creates a new toplevel with the current terminal dimensions.
///
@@ -1343,6 +1365,16 @@ namespace Terminal.Gui {
///
public bool Modal { get; set; }
+ ///
+ /// Check id current toplevel has menu bar
+ ///
+ public bool HasMenuBar { get; set; }
+
+ ///
+ /// Check id current toplevel has status bar
+ ///
+ public bool HasStatusBar { get; set; }
+
public override bool ProcessKey (KeyEvent keyEvent)
{
if (base.ProcessKey (keyEvent))
@@ -1392,6 +1424,103 @@ namespace Terminal.Gui {
return false;
}
+ public override void Add (View view)
+ {
+ if (this == Application.Top) {
+ if (view is MenuBar)
+ HasMenuBar = true;
+ if (view is StatusBar)
+ HasStatusBar = true;
+ }
+ base.Add (view);
+ }
+
+ public override void Remove (View view)
+ {
+ if (this == Application.Top) {
+ if (view is MenuBar)
+ HasMenuBar = true;
+ if (view is StatusBar)
+ HasStatusBar = true;
+ }
+ base.Remove (view);
+ }
+
+ public override void RemoveAll ()
+ {
+ if (this == Application.Top) {
+ HasMenuBar = false;
+ HasStatusBar = false;
+ }
+ base.RemoveAll ();
+ }
+
+ internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny)
+ {
+ nx = Math.Max (x, 0);
+ nx = nx + top.Frame.Width > Driver.Cols ? Math.Max(Driver.Cols - top.Frame.Width, 0) : nx;
+ bool m, s;
+ if (SuperView == null)
+ m = Application.Top.HasMenuBar;
+ else
+ m = ((Toplevel)SuperView).HasMenuBar;
+ int l = m ? 1 : 0;
+ ny = Math.Max (y, l);
+ if (SuperView == null)
+ s = Application.Top.HasStatusBar;
+ else
+ s = ((Toplevel)SuperView).HasStatusBar;
+ l = s ? Driver.Rows - 1 : Driver.Rows;
+ ny = Math.Min (ny, l);
+ ny = ny + top.Frame.Height > l ? Math.Max(l - top.Frame.Height, m ? 1 : 0) : ny;
+ }
+
+ internal void PositionToplevels ()
+ {
+ if (this != Application.Top) {
+ EnsureVisibleBounds (this, Frame.X, Frame.Y, out int nx, out int ny);
+ if (nx != Frame.X || ny != Frame.Y) {
+ X = nx;
+ Y = ny;
+ }
+ } else {
+ foreach (var top in Subviews) {
+ if (top is Toplevel) {
+ EnsureVisibleBounds ((Toplevel)top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
+ if (nx != top.Frame.X || ny != top.Frame.Y) {
+ top.X = nx;
+ top.Y = ny;
+ }
+ if (HasStatusBar && ny + top.Frame.Height > Driver.Rows - 1) {
+ if (top.Height is Dim.DimFill)
+ top.Height = Dim.Fill () - 1;
+ }
+ }
+ }
+ }
+ }
+
+ public override void Redraw (Rect region)
+ {
+ if (this == Application.Top) {
+ if (!NeedDisplay.IsEmpty) {
+ Driver.SetAttribute (Colors.TopLevel.Normal);
+ Clear (region);
+ Driver.SetAttribute (Colors.Base.Normal);
+ }
+ foreach (var view in Subviews) {
+ if (view.Frame.IntersectsWith (region)) {
+ //view.SetNeedsLayout ();
+ view.SetNeedsDisplay (view.Bounds);
+ }
+ }
+
+ ClearNeedsDisplay ();
+ }
+
+ base.Redraw (base.Bounds);
+ }
+
///
/// This method is invoked by Application.Begin as part of the Application.Run after
/// the views have been laid out, and before the views are drawn for the first time.
@@ -1572,48 +1701,48 @@ namespace Terminal.Gui {
ClearNeedsDisplay ();
}
-#if true
- //
- // It does not look like the event is raised on clicked-drag
+ //
+ // FIXED:It does not look like the event is raised on clicked-drag
// need to figure that out.
//
- Point? dragPosition;
- public override bool MouseEvent(MouseEvent mouseEvent)
+ internal static Point? dragPosition;
+ Point start;
+ public override bool MouseEvent (MouseEvent mouseEvent)
{
- // The code is currently disabled, because the
- // Driver.UncookMouse does not seem to have an effect if there is
+ // FIXED:The code is currently disabled, because the
+ // Driver.UncookMouse does not seem to have an effect if there is
// a pending mouse event activated.
- if (true)
- return false;
-
- if ((mouseEvent.Flags == MouseFlags.Button1Pressed|| mouseEvent.Flags == MouseFlags.Button4Pressed)){
-
+
+ int nx, ny;
+ if ((mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
+ mouseEvent.Flags == MouseFlags.Button4Pressed)) {
if (dragPosition.HasValue) {
- var dx = mouseEvent.X - dragPosition.Value.X;
- var dy = mouseEvent.Y - dragPosition.Value.Y;
+ if (SuperView == null) {
+ Application.Top.SetNeedsDisplay (Frame);
+ Application.Top.Redraw (Frame);
+ } else {
+ SuperView.SetNeedsDisplay (Frame);
+ }
+ EnsureVisibleBounds (this, mouseEvent.X + mouseEvent.OfX - start.X,
+ mouseEvent.Y + mouseEvent.OfY, out nx, out ny);
- var nx = Frame.X + dx;
- var ny = Frame.Y + dy;
- if (nx < 0)
- nx = 0;
- if (ny < 0)
- ny = 0;
-
- //Demo.ml2.Text = $"{dx},{dy}";
- dragPosition = new Point (mouseEvent.X, mouseEvent.Y);
-
- // TODO: optimize, only SetNeedsDisplay on the before/after regions.
- if (SuperView == null)
- Application.Refresh ();
- else
- SuperView.SetNeedsDisplay ();
+ dragPosition = new Point (nx, ny);
Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
+ X = nx;
+ Y = ny;
+ //Demo.ml2.Text = $"{dx},{dy}";
+
+ // FIXED: optimize, only SetNeedsDisplay on the before/after regions.
SetNeedsDisplay ();
return true;
} else {
// Only start grabbing if the user clicks on the title bar.
if (mouseEvent.Y == 0) {
- dragPosition = new Point (mouseEvent.X, mouseEvent.Y);
+ start = new Point (mouseEvent.X, mouseEvent.Y);
+ dragPosition = new Point ();
+ nx = mouseEvent.X - mouseEvent.OfX;
+ ny = mouseEvent.Y - mouseEvent.OfY;
+ dragPosition = new Point (nx, ny);
Application.GrabMouse (this);
}
@@ -1622,18 +1751,16 @@ namespace Terminal.Gui {
}
}
- if (mouseEvent.Flags == MouseFlags.Button1Released) {
+ if (mouseEvent.Flags == MouseFlags.Button1Released && dragPosition.HasValue) {
Application.UngrabMouse ();
Driver.UncookMouse ();
-
dragPosition = null;
- //Driver.StopReportingMouseMoves ();
}
//Demo.ml.Text = me.ToString ();
return false;
}
-#endif
+
}
///
@@ -1721,6 +1848,7 @@ namespace Terminal.Gui {
d (state);
return false;
});
+ mainLoop.Driver.Wakeup ();
}
public override void Send (SendOrPostCallback d, object state)
@@ -1741,7 +1869,7 @@ namespace Terminal.Gui {
///
public static void Init () => Init (() => Toplevel.Create ());
- static bool _initialized = false;
+ internal static bool _initialized = false;
///
/// Initializes the Application
@@ -1749,7 +1877,6 @@ namespace Terminal.Gui {
static void Init (Func topLevelFactory)
{
if (_initialized) return;
- _initialized = true;
var p = Environment.OSVersion.Platform;
Mono.Terminal.IMainLoopDriver mainLoopDriver;
@@ -1770,6 +1897,7 @@ namespace Terminal.Gui {
SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
Top = topLevelFactory ();
Current = Top;
+ _initialized = true;
}
///
@@ -1867,7 +1995,7 @@ namespace Terminal.Gui {
return start;
}
- static View mouseGrabView;
+ internal static View mouseGrabView;
///
/// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called.
@@ -1898,20 +2026,22 @@ namespace Terminal.Gui {
static void ProcessMouseEvent (MouseEvent me)
{
+ var view = FindDeepestView (Current, me.X, me.Y, out int rx, out int ry);
RootMouseEvent?.Invoke (me);
if (mouseGrabView != null) {
var newxy = mouseGrabView.ScreenToView (me.X, me.Y);
var nme = new MouseEvent () {
X = newxy.X,
Y = newxy.Y,
- Flags = me.Flags
+ Flags = me.Flags,
+ OfX = me.X - newxy.X,
+ OfY = me.Y - newxy.Y,
+ View = view
};
- mouseGrabView.MouseEvent (me);
+ mouseGrabView.MouseEvent (nme);
return;
}
- int rx, ry;
- var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry);
if (view != null) {
if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition)
return;
@@ -1919,7 +2049,10 @@ namespace Terminal.Gui {
var nme = new MouseEvent () {
X = rx,
Y = ry,
- Flags = me.Flags
+ Flags = me.Flags,
+ OfX = rx,
+ OfY = ry,
+ View = view
};
// Should we bubbled up the event, if it is not handled?
view.MouseEvent (nme);
@@ -1969,7 +2102,7 @@ namespace Terminal.Gui {
}
///
- /// Building block API: completes the exection of a Toplevel that was started with Begin.
+ /// Building block API: completes the execution of a Toplevel that was started with Begin.
///
/// The runstate returned by the method.
static public void End (RunState runState)
@@ -1980,6 +2113,9 @@ namespace Terminal.Gui {
runState.Dispose ();
}
+ ///
+ /// Finalize the driver.
+ ///
public static void Shutdown ()
{
Driver.End ();
@@ -2047,8 +2183,7 @@ namespace Terminal.Gui {
for (state.Toplevel.Running = true; state.Toplevel.Running;) {
if (MainLoop.EventsPending (wait)) {
MainLoop.MainIteration ();
- if (Iteration != null)
- Iteration (null, EventArgs.Empty);
+ Iteration?.Invoke (null, EventArgs.Empty);
} else if (wait == false)
return;
if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
@@ -2135,6 +2270,7 @@ namespace Terminal.Gui {
var full = new Rect (0, 0, Driver.Cols, Driver.Rows);
Driver.Clip = full;
foreach (var t in toplevels) {
+ t.PositionToplevels ();
t.RelativeLayout (full);
t.LayoutSubviews ();
}
diff --git a/Terminal.Gui/Dialogs/FileDialog.cs b/Terminal.Gui/Dialogs/FileDialog.cs
index c00f2b49d..069b8eea4 100644
--- a/Terminal.Gui/Dialogs/FileDialog.cs
+++ b/Terminal.Gui/Dialogs/FileDialog.cs
@@ -23,11 +23,13 @@ namespace Terminal.Gui {
internal bool canChooseFiles = true;
internal bool canChooseDirectories = false;
internal bool allowsMultipleSelection = false;
+ FileDialog host;
- public DirListView ()
+ public DirListView (FileDialog host)
{
infos = new List<(string,bool,bool)> ();
CanFocus = true;
+ this.host = host;
}
bool IsAllowed (FileSystemInfo fsi)
@@ -71,6 +73,84 @@ namespace Terminal.Gui {
Move (0, selected - top);
}
+ int lastSelected;
+ bool shiftOnWheel;
+ public override bool MouseEvent (MouseEvent me)
+ {
+ if ((me.Flags & (MouseFlags.Button1Clicked | MouseFlags.Button1DoubleClicked |
+ MouseFlags.WheeledUp | MouseFlags.WheeledDown)) == 0)
+ return false;
+
+ if (!HasFocus)
+ SuperView.SetFocus (this);
+
+ if (infos == null)
+ return false;
+
+ if (me.Y + top >= infos.Count)
+ return true;
+
+ int lastSelectedCopy = shiftOnWheel ? lastSelected : selected;
+
+ switch (me.Flags) {
+ case MouseFlags.Button1Clicked:
+ SetSelected (me);
+ SelectionChanged ();
+ SetNeedsDisplay ();
+ break;
+ case MouseFlags.Button1DoubleClicked:
+ SetSelected (me);
+ if (ExecuteSelection ()) {
+ host.canceled = false;
+ Application.RequestStop ();
+ }
+ return true;
+ case MouseFlags.Button1Clicked | MouseFlags.ButtonShift:
+ SetSelected (me);
+ if (shiftOnWheel)
+ lastSelected = lastSelectedCopy;
+ shiftOnWheel = false;
+ PerformMultipleSelection (lastSelected);
+ return true;
+ case MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl:
+ SetSelected (me);
+ PerformMultipleSelection ();
+ return true;
+ case MouseFlags.WheeledUp:
+ SetSelected (me);
+ selected = lastSelected;
+ MoveUp ();
+ return true;
+ case MouseFlags.WheeledDown:
+ SetSelected (me);
+ selected = lastSelected;
+ MoveDown ();
+ return true;
+ case MouseFlags.WheeledUp | MouseFlags.ButtonShift:
+ SetSelected (me);
+ selected = lastSelected;
+ lastSelected = lastSelectedCopy;
+ shiftOnWheel = true;
+ MoveUp ();
+ return true;
+ case MouseFlags.WheeledDown | MouseFlags.ButtonShift:
+ SetSelected (me);
+ selected = lastSelected;
+ lastSelected = lastSelectedCopy;
+ shiftOnWheel = true;
+ MoveDown ();
+ return true;
+ }
+
+ return true;
+ }
+
+ void SetSelected (MouseEvent me)
+ {
+ lastSelected = selected;
+ selected = top + me.Y;
+ }
+
void DrawString (int line, string str)
{
var f = Frame;
@@ -91,7 +171,7 @@ namespace Terminal.Gui {
}
for (; used < width; used++) {
Driver.AddRune (' ');
- }
+ }
}
public override void Redraw (Rect region)
@@ -123,7 +203,7 @@ namespace Terminal.Gui {
if (allowsMultipleSelection)
Driver.AddRune (fi.Item3 ? '*' : ' ');
-
+
if (fi.Item2)
Driver.AddRune ('/');
else
@@ -138,35 +218,38 @@ namespace Terminal.Gui {
void SelectionChanged ()
{
+ if (FilePaths.Count > 0)
+ FileChanged?.Invoke (string.Join (", ", GetFilesName (FilePaths)));
+ else
+ FileChanged?.Invoke (infos [selected].Item2 ? "" : Path.GetFileName (infos [selected].Item1));
if (SelectedChanged != null) {
var sel = infos [selected];
SelectedChanged ((sel.Item1, sel.Item2));
}
}
+ List GetFilesName (IReadOnlyList files)
+ {
+ List filesName = new List ();
+
+ foreach (var file in files) {
+ filesName.Add (Path.GetFileName (file));
+ }
+
+ return filesName;
+ }
+
public override bool ProcessKey (KeyEvent keyEvent)
{
switch (keyEvent.Key) {
case Key.CursorUp:
case Key.ControlP:
- if (selected > 0) {
- selected--;
- if (selected < top)
- top = selected;
- SelectionChanged ();
- SetNeedsDisplay ();
- }
+ MoveUp ();
return true;
case Key.CursorDown:
case Key.ControlN:
- if (selected + 1 < infos.Count) {
- selected++;
- if (selected >= top + Frame.Height)
- top++;
- SelectionChanged ();
- SetNeedsDisplay ();
- }
+ MoveDown ();
return true;
case Key.ControlV:
@@ -187,22 +270,10 @@ namespace Terminal.Gui {
return true;
case Key.Enter:
- var isDir = infos [selected].Item2;
-
- if (isDir) {
- Directory = Path.GetFullPath (Path.Combine (Path.GetFullPath (Directory.ToString ()), infos [selected].Item1));
- if (DirectoryChanged != null)
- DirectoryChanged (Directory);
- } else {
- if (FileChanged != null)
- FileChanged (infos [selected].Item1);
- if (canChooseFiles) {
- // Let the OK handler take it over
- return false;
- }
- // No files allowed, do not let the default handler take it.
- }
- return true;
+ if (ExecuteSelection ())
+ return false;
+ else
+ return true;
case Key.PageUp:
n = (selected - Frame.Height);
@@ -217,21 +288,97 @@ namespace Terminal.Gui {
return true;
case Key.Space:
- case Key.ControlT:
- if (allowsMultipleSelection) {
- if ((canChooseFiles && infos [selected].Item2 == false) ||
- (canChooseDirectories && infos [selected].Item2 &&
- infos [selected].Item1 != "..")){
- infos [selected] = (infos [selected].Item1, infos [selected].Item2, !infos [selected].Item3);
- SelectionChanged ();
- SetNeedsDisplay ();
- }
- }
+ case Key.ControlT:
+ PerformMultipleSelection ();
+ return true;
+
+ case Key.Home:
+ MoveFirst ();
+ return true;
+
+ case Key.End:
+ MoveLast ();
return true;
}
return base.ProcessKey (keyEvent);
}
+ void MoveLast ()
+ {
+ selected = infos.Count - 1;
+ top = infos.Count () - 1;
+ SelectionChanged ();
+ SetNeedsDisplay ();
+ }
+
+ void MoveFirst ()
+ {
+ selected = 0;
+ top = 0;
+ SelectionChanged ();
+ SetNeedsDisplay ();
+ }
+
+ void MoveDown ()
+ {
+ if (selected + 1 < infos.Count) {
+ selected++;
+ if (selected >= top + Frame.Height)
+ top++;
+ SelectionChanged ();
+ SetNeedsDisplay ();
+ }
+ }
+
+ void MoveUp ()
+ {
+ if (selected > 0) {
+ selected--;
+ if (selected < top)
+ top = selected;
+ SelectionChanged ();
+ SetNeedsDisplay ();
+ }
+ }
+
+ internal bool ExecuteSelection ()
+ {
+ var isDir = infos [selected].Item2;
+
+ if (isDir) {
+ Directory = Path.GetFullPath (Path.Combine (Path.GetFullPath (Directory.ToString ()), infos [selected].Item1));
+ DirectoryChanged?.Invoke (Directory);
+ } else {
+ FileChanged?.Invoke (infos [selected].Item1);
+ if (canChooseFiles) {
+ // Ensures that at least one file is selected.
+ if (FilePaths.Count == 0)
+ PerformMultipleSelection ();
+ // Let the OK handler take it over
+ return true;
+ }
+ // No files allowed, do not let the default handler take it.
+ }
+ return false;
+ }
+
+ void PerformMultipleSelection (int? firstSelected = null)
+ {
+ if (allowsMultipleSelection) {
+ int first = Math.Min (firstSelected ?? selected, selected);
+ int last = Math.Max (selected, firstSelected ?? selected);
+ for (int i = first; i <= last; i++) {
+ if ((canChooseFiles && infos [i].Item2 == false) ||
+ (canChooseDirectories && infos [i].Item2 &&
+ infos [i].Item1 != "..")) {
+ infos [i] = (infos [i].Item1, infos [i].Item2, !infos [i].Item3);
+ }
+ }
+ SelectionChanged ();
+ SetNeedsDisplay ();
+ }
+ }
+
string [] allowedFileTypes;
public string [] AllowedFileTypes {
get => allowedFileTypes;
@@ -278,6 +425,13 @@ namespace Terminal.Gui {
TextField dirEntry, nameEntry;
internal DirListView dirListView;
+ ///
+ /// Constructor for the OpenDialog and the SaveDialog.
+ ///
+ /// The title.
+ /// The prompt.
+ /// The name field label.
+ /// The message.
public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message) : base (title, Driver.Cols - 20, Driver.Rows - 5, null)
{
this.message = new Label (Rect.Empty, "MESSAGE" + message);
@@ -306,18 +460,16 @@ namespace Terminal.Gui {
};
Add (this.nameFieldLabel, nameEntry);
- dirListView = new DirListView () {
+ dirListView = new DirListView (this) {
X = 1,
Y = 3 + msgLines + 2,
- Width = Dim.Fill () - 2,
+ Width = Dim.Fill () - 3,
Height = Dim.Fill () - 2,
};
DirectoryPath = Path.GetFullPath (Environment.CurrentDirectory);
Add (dirListView);
dirListView.DirectoryChanged = (dir) => dirEntry.Text = dir;
- dirListView.FileChanged = (file) => {
- nameEntry.Text = file;
- };
+ dirListView.FileChanged = (file) => nameEntry.Text = file;
this.cancel = new Button ("Cancel");
this.cancel.Clicked += () => {
@@ -330,6 +482,7 @@ namespace Terminal.Gui {
IsDefault = true,
};
this.prompt.Clicked += () => {
+ dirListView.ExecuteSelection ();
canceled = false;
Application.RequestStop ();
};
@@ -430,6 +583,11 @@ namespace Terminal.Gui {
nameEntry.Text = Path.GetFileName(value.ToString());
}
}
+
+ ///
+ /// Check if the dialog was or not canceled.
+ ///
+ public bool Canceled { get => canceled; }
}
///
@@ -445,6 +603,11 @@ namespace Terminal.Gui {
///
///
public class SaveDialog : FileDialog {
+ ///
+ /// Constructor of the save dialog.
+ ///
+ /// The title.
+ /// The message.
public SaveDialog (ustring title, ustring message) : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message)
{
}
@@ -482,6 +645,11 @@ namespace Terminal.Gui {
///
///
public class OpenDialog : FileDialog {
+ ///
+ /// Constructor of the Open Dialog.
+ ///
+ ///
+ ///
public OpenDialog (ustring title, ustring message) : base (title, prompt: "Open", nameFieldLabel: "Open", message: message)
{
}
diff --git a/Terminal.Gui/Drivers/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs
index 7ab4139f5..19cdc4e48 100644
--- a/Terminal.Gui/Drivers/ConsoleDriver.cs
+++ b/Terminal.Gui/Drivers/ConsoleDriver.cs
@@ -6,6 +6,7 @@
//
using System;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Mono.Terminal;
using NStack;
@@ -93,14 +94,32 @@ namespace Terminal.Gui {
///
public struct Attribute {
internal int value;
+ internal Color foreground;
+ internal Color background;
///
/// Initializes a new instance of the struct.
///
/// Value.
- public Attribute (int value)
+ /// Foreground
+ /// Background
+ public Attribute (int value, Color foreground = new Color(), Color background = new Color())
{
this.value = value;
+ this.foreground = foreground;
+ this.background = background;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Foreground
+ /// Background
+ public Attribute (Color foreground = new Color (), Color background = new Color ())
+ {
+ this.value = value = ((int)foreground | (int)background << 4);
+ this.foreground = foreground;
+ this.background = background;
}
///
@@ -137,50 +156,207 @@ namespace Terminal.Gui {
/// views contained inside.
///
public class ColorScheme {
+ Attribute _normal;
+ Attribute _focus;
+ Attribute _hotNormal;
+ Attribute _hotFocus;
+ Attribute _disabled;
+ internal string caller = "";
+
///
/// The default color for text, when the view is not focused.
///
- public Attribute Normal;
+ public Attribute Normal { get { return _normal; } set { _normal = SetAttribute (value); } }
+
///
/// The color for text when the view has the focus.
///
- public Attribute Focus;
+ public Attribute Focus { get { return _focus; } set { _focus = SetAttribute (value); } }
///
/// The color for the hotkey when a view is not focused
///
- public Attribute HotNormal;
+ public Attribute HotNormal { get { return _hotNormal; } set { _hotNormal = SetAttribute (value); } }
///
/// The color for the hotkey when the view is focused.
///
- public Attribute HotFocus;
+ public Attribute HotFocus { get { return _hotFocus; } set { _hotFocus = SetAttribute (value); } }
+
+ ///
+ /// The default color for text, when the view is disabled.
+ ///
+ public Attribute Disabled { get { return _disabled; } set { _disabled = SetAttribute (value); } }
+
+ bool preparingScheme = false;
+
+ Attribute SetAttribute (Attribute attribute, [CallerMemberName]string callerMemberName = null)
+ {
+ if (!Application._initialized && !preparingScheme)
+ return attribute;
+
+ if (preparingScheme)
+ return attribute;
+
+ preparingScheme = true;
+ switch (caller) {
+ case "TopLevel":
+ switch (callerMemberName) {
+ case "Normal":
+ HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+ break;
+ case "Focus":
+ HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+ break;
+ case "HotNormal":
+ HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
+ break;
+ case "HotFocus":
+ HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
+ if (Focus.foreground != attribute.background)
+ Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+ break;
+ }
+ break;
+
+ case "Base":
+ switch (callerMemberName) {
+ case "Normal":
+ HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+ break;
+ case "Focus":
+ HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+ break;
+ case "HotNormal":
+ HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
+ Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+ break;
+ case "HotFocus":
+ HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
+ if (Focus.foreground != attribute.background)
+ Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+ break;
+ }
+ break;
+
+ case "Menu":
+ switch (callerMemberName) {
+ case "Normal":
+ if (Focus.background != attribute.background)
+ Focus = Application.Driver.MakeAttribute (attribute.foreground, Focus.background);
+ HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+ Disabled = Application.Driver.MakeAttribute (Disabled.foreground, attribute.background);
+ break;
+ case "Focus":
+ Normal = Application.Driver.MakeAttribute (attribute.foreground, Normal.background);
+ HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+ break;
+ case "HotNormal":
+ if (Focus.background != attribute.background)
+ HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
+ Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+ Disabled = Application.Driver.MakeAttribute (Disabled.foreground, attribute.background);
+ break;
+ case "HotFocus":
+ HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
+ if (Focus.foreground != attribute.background)
+ Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+ break;
+ case "Disabled":
+ if (Focus.background != attribute.background)
+ HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
+ Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+ HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+ break;
+
+ }
+ break;
+
+ case "Dialog":
+ switch (callerMemberName) {
+ case "Normal":
+ if (Focus.background != attribute.background)
+ Focus = Application.Driver.MakeAttribute (attribute.foreground, Focus.background);
+ HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+ break;
+ case "Focus":
+ Normal = Application.Driver.MakeAttribute (attribute.foreground, Normal.background);
+ HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+ break;
+ case "HotNormal":
+ if (Focus.background != attribute.background)
+ HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
+ if (Normal.foreground != attribute.background)
+ Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+ break;
+ case "HotFocus":
+ HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
+ if (Focus.foreground != attribute.background)
+ Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+ break;
+ }
+ break;
+
+ case "Error":
+ switch (callerMemberName) {
+ case "Normal":
+ HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+ HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+ break;
+ case "HotNormal":
+ case "HotFocus":
+ HotFocus = Application.Driver.MakeAttribute (attribute.foreground, attribute.background);
+ Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+ break;
+ }
+ break;
+
+ }
+ preparingScheme = false;
+ return attribute;
+ }
}
///
/// The default ColorSchemes for the application.
///
public static class Colors {
+ static ColorScheme _toplevel;
+ static ColorScheme _base;
+ static ColorScheme _dialog;
+ static ColorScheme _menu;
+ static ColorScheme _error;
+
+ ///
+ /// The application toplevel color scheme, for the default toplevel views.
+ ///
+ public static ColorScheme TopLevel { get { return _toplevel; } set { _toplevel = SetColorScheme (value); } }
+
///
/// The base color scheme, for the default toplevel views.
///
- public static ColorScheme Base;
-
+ public static ColorScheme Base { get { return _base; } set { _base = SetColorScheme (value); } }
+
///
/// The dialog color scheme, for standard popup dialog boxes
///
- public static ColorScheme Dialog;
+ public static ColorScheme Dialog { get { return _dialog; } set { _dialog = SetColorScheme (value); } }
///
/// The menu bar color
///
- public static ColorScheme Menu;
+ public static ColorScheme Menu { get { return _menu; } set { _menu = SetColorScheme (value); } }
///
/// The color scheme for showing errors.
///
- public static ColorScheme Error;
+ public static ColorScheme Error { get { return _error; } set { _error = SetColorScheme (value); } }
+ static ColorScheme SetColorScheme (ColorScheme colorScheme, [CallerMemberName]string callerMemberName = null)
+ {
+ colorScheme.caller = callerMemberName;
+ return colorScheme;
+ }
}
///
@@ -238,7 +414,7 @@ namespace Terminal.Gui {
RightTee,
///
- /// Top tee
+ /// Top tee
///
TopTee,
@@ -253,6 +429,9 @@ namespace Terminal.Gui {
/// ConsoleDriver is an abstract class that defines the requirements for a console driver. One implementation if the CursesDriver, and another one uses the .NET Console one.
///
public abstract class ConsoleDriver {
+ ///
+ /// The handler fired when the terminal is resized.
+ ///
protected Action TerminalResized;
///
@@ -280,10 +459,16 @@ namespace Terminal.Gui {
/// Rune to add.
public abstract void AddRune (Rune rune);
///
- /// Adds the specified
+ /// Adds the specified
///
/// String.
public abstract void AddStr (ustring str);
+ ///
+ /// Prepare the driver and set the key and mouse events handlers.
+ ///
+ ///
+ ///
+ ///
public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler);
///
@@ -312,19 +497,27 @@ namespace Terminal.Gui {
/// C.
public abstract void SetAttribute (Attribute c);
- // Set Colors from limit sets of colors
+ ///
+ /// Set Colors from limit sets of colors.
+ ///
+ /// Foreground.
+ /// Background.
public abstract void SetColors (ConsoleColor foreground, ConsoleColor background);
// Advanced uses - set colors to any pre-set pairs, you would need to init_color
// that independently with the R, G, B values.
///
- /// Advanced uses - set colors to any pre-set pairs, you would need to init_color
+ /// Advanced uses - set colors to any pre-set pairs, you would need to init_color
/// that independently with the R, G, B values.
///
/// Foreground color identifier.
/// Background color identifier.
public abstract void SetColors (short foregroundColorId, short backgroundColorId);
+ ///
+ /// Set the handler when the terminal is resized.
+ ///
+ ///
public void SetTerminalResized(Action terminalResized)
{
TerminalResized = terminalResized;
@@ -388,7 +581,7 @@ namespace Terminal.Gui {
for (int l = 0; l < padding; l++)
for (b = 0; b < width; b++)
AddRune (' ');
- }
+ }
}
@@ -408,7 +601,14 @@ namespace Terminal.Gui {
set => this.clip = value;
}
+ ///
+ /// Start of mouse moves.
+ ///
public abstract void StartReportingMouseMoves ();
+
+ ///
+ /// Stop reporting mouses moves.
+ ///
public abstract void StopReportingMouseMoves ();
///
@@ -472,7 +672,7 @@ namespace Terminal.Gui {
public Rune RightTee;
///
- /// Top tee
+ /// Top tee
///
public Rune TopTee;
@@ -481,6 +681,12 @@ namespace Terminal.Gui {
///
public Rune BottomTee;
+ ///
+ /// Make the attribute for the foreground and background colors.
+ ///
+ /// Foreground.
+ /// Background.
+ ///
public abstract Attribute MakeAttribute (Color fore, Color back);
}
}
diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs
index 6697579b0..73966586e 100644
--- a/Terminal.Gui/Drivers/CursesDriver.cs
+++ b/Terminal.Gui/Drivers/CursesDriver.cs
@@ -255,7 +255,7 @@ namespace Terminal.Gui {
Colors.Base.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLUE);
Colors.Base.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
- // Focused,
+ // Focused,
// Selected, Hot: Yellow on Black
// Selected, text: white on black
// Unselected, hot: yellow on cyan
@@ -264,6 +264,7 @@ namespace Terminal.Gui {
Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
+ Colors.Menu.Disabled = MakeColor(Curses.COLOR_WHITE, Curses.COLOR_CYAN);
Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
@@ -410,7 +411,7 @@ namespace Terminal.Gui {
suspendSignal = 18;
break;
case "Linux":
- // TODO: should fetch the machine name and
+ // TODO: should fetch the machine name and
// if it is MIPS return 24
suspendSignal = 20;
break;
diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs
index 0487d0d7b..0b5217910 100644
--- a/Terminal.Gui/Drivers/NetDriver.cs
+++ b/Terminal.Gui/Drivers/NetDriver.cs
@@ -135,7 +135,7 @@ namespace Terminal.Gui {
Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
- // Focused,
+ // Focused,
// Selected, Hot: Yellow on Black
// Selected, text: white on black
// Unselected, hot: yellow on cyan
@@ -144,6 +144,7 @@ namespace Terminal.Gui {
Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
+ Colors.Menu.Disabled = MakeColor(ConsoleColor.DarkGray, ConsoleColor.Cyan);
Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
@@ -348,7 +349,7 @@ namespace Terminal.Gui {
}
//
- // These are for the .NET driver, but running natively on Windows, wont run
+ // These are for the .NET driver, but running natively on Windows, wont run
// on the Mono emulation
//
diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs
index 0074181c7..43f227458 100644
--- a/Terminal.Gui/Drivers/WindowsDriver.cs
+++ b/Terminal.Gui/Drivers/WindowsDriver.cs
@@ -1,5 +1,5 @@
//
-// WindowsDriver.cs: Windows specific driver
+// WindowsDriver.cs: Windows specific driver
//
// Authors:
// Miguel de Icaza (miguel@gnome.org)
@@ -13,10 +13,10 @@
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
-//
+//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
-//
+//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -107,7 +107,7 @@ namespace Terminal.Gui {
ScreenBuffer = IntPtr.Zero;
}
- private bool ContinueListeningForConsoleEvents = true;
+ bool ContinueListeningForConsoleEvents = true;
public uint ConsoleMode {
get {
@@ -151,7 +151,8 @@ namespace Terminal.Gui {
Button3Pressed = 8,
Button4Pressed = 16,
RightmostButtonPressed = 2,
-
+ WheeledUp = unchecked((int)0x780000),
+ WheeledDown = unchecked((int)0xFF880000),
}
[Flags]
@@ -436,10 +437,17 @@ namespace Terminal.Gui {
public WindowsDriver ()
{
+ Colors.TopLevel = new ColorScheme ();
+
+ Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
+ Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
+ Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
+ Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.DarkCyan);
+
winConsole = new WindowsConsole ();
cols = Console.WindowWidth;
- rows = Console.WindowHeight - 1;
+ rows = Console.WindowHeight;
WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
ResizeScreen ();
@@ -519,7 +527,6 @@ namespace Terminal.Gui {
} finally {
eventReady.Reset();
}
- Debug.WriteLine("Events ready");
if (!tokenSource.IsCancellationRequested)
return result != null;
@@ -561,7 +568,7 @@ namespace Terminal.Gui {
case WindowsConsole.EventType.WindowBufferSize:
cols = inputEvent.WindowBufferSizeEvent.size.X;
- rows = inputEvent.WindowBufferSizeEvent.size.Y - 1;
+ rows = inputEvent.WindowBufferSizeEvent.size.Y;
ResizeScreen ();
UpdateOffScreen ();
TerminalResized?.Invoke();
@@ -570,22 +577,34 @@ namespace Terminal.Gui {
result = null;
}
- private WindowsConsole.ButtonState? LastMouseButtonPressed = null;
+ WindowsConsole.ButtonState? LastMouseButtonPressed = null;
+ bool IsButtonReleased = false;
+ bool IsButtonDoubleClicked = false;
- private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
+ MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
{
MouseFlags mouseFlag = MouseFlags.AllEvents;
+ if (IsButtonDoubleClicked) {
+ Task.Run (async () => {
+ await Task.Delay (300);
+ _ = new Action (() => IsButtonDoubleClicked = false);
+ });
+ }
+
// The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button.
// This will tell when a mouse button is pressed. When the button is released this event will
// be fired with it's bit set to 0. So when the button is up ButtonState will be 0.
// To map to the correct driver events we save the last pressed mouse button so we can
// map to the correct clicked event.
- if (LastMouseButtonPressed != null && mouseEvent.ButtonState != 0) {
+ if ((LastMouseButtonPressed != null || IsButtonReleased) && mouseEvent.ButtonState != 0) {
LastMouseButtonPressed = null;
+ IsButtonReleased = false;
}
- if (mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null) {
+ if ((mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null && !IsButtonDoubleClicked) ||
+ (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
+ mouseEvent.ButtonState != 0 && !IsButtonDoubleClicked)) {
switch (mouseEvent.ButtonState) {
case WindowsConsole.ButtonState.Button1Pressed:
mouseFlag = MouseFlags.Button1Pressed;
@@ -595,12 +614,32 @@ namespace Terminal.Gui {
mouseFlag = MouseFlags.Button2Pressed;
break;
- case WindowsConsole.ButtonState.Button3Pressed:
- mouseFlag = MouseFlags.Button3Pressed;
+ case WindowsConsole.ButtonState.RightmostButtonPressed:
+ mouseFlag = MouseFlags.Button4Pressed;
break;
}
+
+ if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved)
+ mouseFlag |= MouseFlags.ReportMousePosition;
LastMouseButtonPressed = mouseEvent.ButtonState;
- } else if (mouseEvent.EventFlags == 0 && LastMouseButtonPressed != null) {
+ } else if (mouseEvent.EventFlags == 0 && LastMouseButtonPressed != null && !IsButtonReleased &&
+ !IsButtonDoubleClicked) {
+ switch (LastMouseButtonPressed) {
+ case WindowsConsole.ButtonState.Button1Pressed:
+ mouseFlag = MouseFlags.Button1Released;
+ break;
+
+ case WindowsConsole.ButtonState.Button2Pressed:
+ mouseFlag = MouseFlags.Button2Released;
+ break;
+
+ case WindowsConsole.ButtonState.RightmostButtonPressed:
+ mouseFlag = MouseFlags.Button4Released;
+ break;
+ }
+ IsButtonReleased = true;
+ } else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) &&
+ IsButtonReleased) {
switch (LastMouseButtonPressed) {
case WindowsConsole.ButtonState.Button1Pressed:
mouseFlag = MouseFlags.Button1Clicked;
@@ -610,15 +649,59 @@ namespace Terminal.Gui {
mouseFlag = MouseFlags.Button2Clicked;
break;
- case WindowsConsole.ButtonState.Button3Pressed:
- mouseFlag = MouseFlags.Button3Clicked;
+ case WindowsConsole.ButtonState.RightmostButtonPressed:
+ mouseFlag = MouseFlags.Button4Clicked;
break;
}
LastMouseButtonPressed = null;
+ IsButtonReleased = false;
+ } else if (mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.DoubleClick)) {
+ switch (mouseEvent.ButtonState) {
+ case WindowsConsole.ButtonState.Button1Pressed:
+ mouseFlag = MouseFlags.Button1DoubleClicked;
+ break;
+
+ case WindowsConsole.ButtonState.Button2Pressed:
+ mouseFlag = MouseFlags.Button2DoubleClicked;
+ break;
+
+ case WindowsConsole.ButtonState.RightmostButtonPressed:
+ mouseFlag = MouseFlags.Button4DoubleClicked;
+ break;
+ }
+ IsButtonDoubleClicked = true;
+ } else if (mouseEvent.EventFlags == 0 && mouseEvent.ButtonState != 0 && IsButtonDoubleClicked) {
+ switch (mouseEvent.ButtonState) {
+ case WindowsConsole.ButtonState.Button1Pressed:
+ mouseFlag = MouseFlags.Button1TripleClicked;
+ break;
+
+ case WindowsConsole.ButtonState.Button2Pressed:
+ mouseFlag = MouseFlags.Button2TripleClicked;
+ break;
+
+ case WindowsConsole.ButtonState.RightmostButtonPressed:
+ mouseFlag = MouseFlags.Button4TripleClicked;
+ break;
+ }
+ IsButtonDoubleClicked = false;
+ } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseWheeled) {
+ switch (mouseEvent.ButtonState) {
+ case WindowsConsole.ButtonState.WheeledUp:
+ mouseFlag = MouseFlags.WheeledUp;
+ break;
+
+ case WindowsConsole.ButtonState.WheeledDown:
+ mouseFlag = MouseFlags.WheeledDown;
+ break;
+ }
+
} else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) {
mouseFlag = MouseFlags.ReportMousePosition;
}
+ mouseFlag = SetControlKeyStates (mouseEvent, mouseFlag);
+
return new MouseEvent () {
X = mouseEvent.MousePosition.X,
Y = mouseEvent.MousePosition.Y,
@@ -626,6 +709,21 @@ namespace Terminal.Gui {
};
}
+ static MouseFlags SetControlKeyStates (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag)
+ {
+ if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed) ||
+ mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftControlPressed))
+ mouseFlag |= MouseFlags.ButtonCtrl;
+
+ if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ShiftPressed))
+ mouseFlag |= MouseFlags.ButtonShift;
+
+ if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightAltPressed) ||
+ mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftAltPressed))
+ mouseFlag |= MouseFlags.ButtonAlt;
+ return mouseFlag;
+ }
+
public ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
{
var state = keyEvent.dwControlKeyState;
@@ -676,23 +774,23 @@ namespace Terminal.Gui {
case ConsoleKey.NumPad0:
return keyInfoEx.NumLock ? (Key)(uint)'0' : Key.InsertChar;
case ConsoleKey.NumPad1:
- return keyInfoEx.NumLock ? (Key)(uint)'1' : Key.End;
+ return keyInfoEx.NumLock ? (Key)(uint)'1' : Key.End;
case ConsoleKey.NumPad2:
- return keyInfoEx.NumLock ? (Key)(uint)'2' : Key.CursorDown;
+ return keyInfoEx.NumLock ? (Key)(uint)'2' : Key.CursorDown;
case ConsoleKey.NumPad3:
return keyInfoEx.NumLock ? (Key)(uint)'3' : Key.PageDown;
case ConsoleKey.NumPad4:
- return keyInfoEx.NumLock ? (Key)(uint)'4' : Key.CursorLeft;
+ return keyInfoEx.NumLock ? (Key)(uint)'4' : Key.CursorLeft;
case ConsoleKey.NumPad5:
- return keyInfoEx.NumLock ? (Key)(uint)'5' : (Key)((uint)keyInfo.KeyChar);
+ return keyInfoEx.NumLock ? (Key)(uint)'5' : (Key)((uint)keyInfo.KeyChar);
case ConsoleKey.NumPad6:
- return keyInfoEx.NumLock ? (Key)(uint)'6' : Key.CursorRight;
+ return keyInfoEx.NumLock ? (Key)(uint)'6' : Key.CursorRight;
case ConsoleKey.NumPad7:
- return keyInfoEx.NumLock ? (Key)(uint)'7' : Key.Home;
+ return keyInfoEx.NumLock ? (Key)(uint)'7' : Key.Home;
case ConsoleKey.NumPad8:
- return keyInfoEx.NumLock ? (Key)(uint)'8' : Key.CursorUp;
+ return keyInfoEx.NumLock ? (Key)(uint)'8' : Key.CursorUp;
case ConsoleKey.NumPad9:
- return keyInfoEx.NumLock ? (Key)(uint)'9' : Key.PageUp;
+ return keyInfoEx.NumLock ? (Key)(uint)'9' : Key.PageUp;
case ConsoleKey.Oem1:
case ConsoleKey.Oem2:
@@ -708,33 +806,39 @@ namespace Terminal.Gui {
case ConsoleKey.OemPlus:
case ConsoleKey.OemMinus:
return (Key)((uint)keyInfo.KeyChar);
- }
+ }
- var key = keyInfo.Key;
- var alphaBase = ((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock)) ? 'A' : 'a';
+ var key = keyInfo.Key;
+ var alphaBase = ((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock)) ? 'A' : 'a';
- if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
- var delta = key - ConsoleKey.A;
- if (keyInfo.Modifiers == ConsoleModifiers.Control)
- return (Key)((uint)Key.ControlA + delta);
- if (keyInfo.Modifiers == ConsoleModifiers.Alt)
- return (Key)(((uint)Key.AltMask) | ((uint)'A' + delta));
- return (Key)((uint)alphaBase + delta);
- }
- if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
- var delta = key - ConsoleKey.D0;
- if (keyInfo.Modifiers == ConsoleModifiers.Alt)
- return (Key)(((uint)Key.AltMask) | ((uint)'0' + delta));
+ if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
+ var delta = key - ConsoleKey.A;
+ if (keyInfo.Modifiers == ConsoleModifiers.Control)
+ return (Key)((uint)Key.ControlA + delta);
+ if (keyInfo.Modifiers == ConsoleModifiers.Alt)
+ return (Key)(((uint)Key.AltMask) | ((uint)'A' + delta));
+ if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+ if (keyInfo.KeyChar == 0)
+ return (Key)(((uint)Key.AltMask) + ((uint)Key.ControlA + delta));
+ else
+ return (Key)((uint)keyInfo.KeyChar);
+ }
+ return (Key)((uint)alphaBase + delta);
+ }
+ if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
+ var delta = key - ConsoleKey.D0;
+ if (keyInfo.Modifiers == ConsoleModifiers.Alt)
+ return (Key)(((uint)Key.AltMask) | ((uint)'0' + delta));
- return (Key)((uint)keyInfo.KeyChar);
- }
- if (key >= ConsoleKey.F1 && key <= ConsoleKey.F10) {
- var delta = key - ConsoleKey.F1;
+ return (Key)((uint)keyInfo.KeyChar);
+ }
+ if (key >= ConsoleKey.F1 && key <= ConsoleKey.F10) {
+ var delta = key - ConsoleKey.F1;
- return (Key)((int)Key.F1 + delta);
+ return (Key)((int)Key.F1 + delta);
+ }
+ return (Key)(0xffffffff);
}
- return (Key)(0xffffffff);
- }
public override void Init (Action terminalResized)
{
@@ -763,10 +867,11 @@ namespace Terminal.Gui {
Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
- Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
+ Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
- Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
+ Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
+ Colors.Menu.Disabled = MakeColor(ConsoleColor.DarkGray, ConsoleColor.Cyan);
Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
@@ -797,7 +902,7 @@ namespace Terminal.Gui {
for (int row = 0; row < rows; row++)
for (int col = 0; col < cols; col++) {
int position = row * cols + col;
- OutputBuffer [position].Attributes = (ushort)MakeColor (ConsoleColor.White, ConsoleColor.Blue);
+ OutputBuffer [position].Attributes = (ushort)Colors.TopLevel.Normal;
OutputBuffer [position].Char.UnicodeChar = ' ';
}
}
@@ -843,11 +948,13 @@ namespace Terminal.Gui {
currentAttribute = c.value;
}
- private Attribute MakeColor (ConsoleColor f, ConsoleColor b)
+ Attribute MakeColor (ConsoleColor f, ConsoleColor b)
{
// Encode the colors into the int value.
return new Attribute () {
- value = ((int)f | (int)b << 4)
+ value = ((int)f | (int)b << 4),
+ foreground = (Color)f,
+ background = (Color)b
};
}
@@ -881,7 +988,7 @@ namespace Terminal.Gui {
{
if (damageRegion.Left == -1)
return;
-
+
var bufferCoords = new WindowsConsole.Coord (){
X = (short)Clip.Width,
Y = (short)Clip.Height
@@ -908,6 +1015,7 @@ namespace Terminal.Gui {
};
winConsole.SetCursorPosition(position);
}
+
public override void End ()
{
winConsole.Cleanup();
@@ -945,5 +1053,4 @@ namespace Terminal.Gui {
}
-
}
diff --git a/Terminal.Gui/Event.cs b/Terminal.Gui/Event.cs
index 8556a370b..aea8d9780 100644
--- a/Terminal.Gui/Event.cs
+++ b/Terminal.Gui/Event.cs
@@ -336,7 +336,7 @@ namespace Terminal.Gui {
///
Button1DoubleClicked = unchecked((int)0x8),
///
- /// The first mouse button was tripple-clicked.
+ /// The first mouse button was triple-clicked.
///
Button1TripleClicked = unchecked((int)0x10),
///
@@ -356,9 +356,9 @@ namespace Terminal.Gui {
///
Button2DoubleClicked = unchecked((int)0x200),
///
- /// The second mouse button was tripple-clicked.
+ /// The second mouse button was triple-clicked.
///
- Button2TrippleClicked = unchecked((int)0x400),
+ Button2TripleClicked = unchecked((int)0x400),
///
/// The third mouse button was pressed.
///
@@ -376,7 +376,7 @@ namespace Terminal.Gui {
///
Button3DoubleClicked = unchecked((int)0x8000),
///
- /// The third mouse button was tripple-clicked.
+ /// The third mouse button was triple-clicked.
///
Button3TripleClicked = unchecked((int)0x10000),
///
@@ -396,15 +396,15 @@ namespace Terminal.Gui {
///
Button4DoubleClicked = unchecked((int)0x200000),
///
- /// The fourth button was tripple-clicked.
+ /// The fourth button was triple-clicked.
///
Button4TripleClicked = unchecked((int)0x400000),
///
- /// The fourth button was pressed.
+ /// Flag: the shift key was pressed when the mouse button took place.
///
ButtonShift = unchecked((int)0x2000000),
///
- /// Flag: the shift key was pressed when the mouse button took place.
+ /// Flag: the ctrl key was pressed when the mouse button took place.
///
ButtonCtrl = unchecked((int)0x1000000),
///
@@ -416,6 +416,14 @@ namespace Terminal.Gui {
///
ReportMousePosition = unchecked((int)0x8000000),
///
+ /// Vertical button wheeled up.
+ ///
+ WheeledUp = unchecked((int)0x10000000),
+ ///
+ /// Vertical button wheeled up.
+ ///
+ WheeledDown = unchecked((int)0x20000000),
+ ///
/// Mask that captures all the events.
///
AllEvents = unchecked((int)0x7ffffff),
@@ -440,6 +448,21 @@ namespace Terminal.Gui {
///
public MouseFlags Flags;
+ ///
+ /// The offset X (column) location for the mouse event.
+ ///
+ public int OfX;
+
+ ///
+ /// The offset Y (column) location for the mouse event.
+ ///
+ public int OfY;
+
+ ///
+ /// The current view at the location for the mouse event.
+ ///
+ public View View;
+
///
/// Returns a that represents the current .
///
diff --git a/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs b/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs
index 1fce883e9..09882dbfd 100644
--- a/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs
+++ b/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs
@@ -172,7 +172,7 @@ namespace Mono.Terminal.Internal {
///
/// Loads library in a platform specific way.
///
- private static IntPtr PlatformSpecificLoadLibrary (string libraryPath)
+ static IntPtr PlatformSpecificLoadLibrary (string libraryPath)
{
if (IsWindows) {
return Windows.LoadLibrary (libraryPath);
@@ -192,7 +192,7 @@ namespace Mono.Terminal.Internal {
throw new InvalidOperationException ("Unsupported platform.");
}
- private static string FirstValidLibraryPath (string [] libraryPathAlternatives)
+ static string FirstValidLibraryPath (string [] libraryPathAlternatives)
{
foreach (var path in libraryPathAlternatives) {
if (File.Exists (path)) {
@@ -204,7 +204,7 @@ namespace Mono.Terminal.Internal {
string.Join (",", libraryPathAlternatives)));
}
- private static class Windows
+ static class Windows
{
[DllImport ("kernel32.dll")]
internal static extern IntPtr LoadLibrary (string filename);
@@ -213,7 +213,7 @@ namespace Mono.Terminal.Internal {
internal static extern IntPtr GetProcAddress (IntPtr hModule, string procName);
}
- private static class Linux
+ static class Linux
{
[DllImport ("libdl.so")]
internal static extern IntPtr dlopen (string filename, int flags);
@@ -222,7 +222,7 @@ namespace Mono.Terminal.Internal {
internal static extern IntPtr dlsym (IntPtr handle, string symbol);
}
- private static class MacOSX
+ static class MacOSX
{
[DllImport ("libSystem.dylib")]
internal static extern IntPtr dlopen (string filename, int flags);
@@ -238,7 +238,7 @@ namespace Mono.Terminal.Internal {
/// dlopen and dlsym from the current process as on Linux
/// Mono sure is linked against these symbols.
///
- private static class Mono
+ static class Mono
{
[DllImport ("__Internal")]
internal static extern IntPtr dlopen (string filename, int flags);
@@ -252,7 +252,7 @@ namespace Mono.Terminal.Internal {
/// dlopen and dlsym from the "libcoreclr.so",
/// to avoid the dependency on libc-dev Linux.
///
- private static class CoreCLR
+ static class CoreCLR
{
[DllImport ("libcoreclr.so")]
internal static extern IntPtr dlopen (string filename, int flags);
diff --git a/Terminal.Gui/MonoCurses/mainloop.cs b/Terminal.Gui/MonoCurses/mainloop.cs
index 280c4dfee..f2927f8e0 100644
--- a/Terminal.Gui/MonoCurses/mainloop.cs
+++ b/Terminal.Gui/MonoCurses/mainloop.cs
@@ -354,10 +354,9 @@ namespace Mono.Terminal {
///
public Func AddIdle (Func idleHandler)
{
- lock (idleHandlers) {
+ lock (idleHandlers)
idleHandlers.Add (idleHandler);
- driver.Wakeup ();
- }
+
return idleHandler;
}
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index fa2ce8b07..559d3c249 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -1,6 +1,6 @@
- net461;netstandard2.0
+ net472;netstandard2.0
Terminal.Gui
Terminal.Gui
bin\Release\Terminal.Gui.xml
@@ -57,23 +57,18 @@
-
-
-
+
-
+
diff --git a/Terminal.Gui/Types/PosDim.cs b/Terminal.Gui/Types/PosDim.cs
index bf7fdf16c..1510d63ec 100644
--- a/Terminal.Gui/Types/PosDim.cs
+++ b/Terminal.Gui/Types/PosDim.cs
@@ -187,8 +187,16 @@ namespace Terminal.Gui {
else
return la - ra;
}
+
+ public override string ToString ()
+ {
+ return $"{((PosView)left).Target.ToString ()},{right.ToString ()}";
+ }
+
}
+ static PosCombine posCombine;
+
///
/// Adds a to a , yielding a new .
///
@@ -197,7 +205,10 @@ namespace Terminal.Gui {
/// The that is the sum of the values of left and right.
public static Pos operator + (Pos left, Pos right)
{
- return new PosCombine (true, left, right);
+ PosCombine newPos = new PosCombine (true, left, right);
+ if (posCombine?.ToString () != newPos.ToString ())
+ ((PosView)left).Target.SetNeedsLayout ();
+ return posCombine = newPos;
}
///
@@ -208,7 +219,10 @@ namespace Terminal.Gui {
/// The that is the left minus right.
public static Pos operator - (Pos left, Pos right)
{
- return new PosCombine (false, left, right);
+ PosCombine newPos = new PosCombine (false, left, right);
+ if (posCombine?.ToString () != newPos.ToString ())
+ ((PosView)left).Target.SetNeedsLayout ();
+ return posCombine = newPos;
}
internal class PosView : Pos {
@@ -276,6 +290,7 @@ namespace Terminal.Gui {
}
///
+ /// Dim properties of a view to control the position.
///
///
///
@@ -331,7 +346,7 @@ namespace Terminal.Gui {
return new DimFactor (n / 100);
}
- class DimAbsolute : Dim {
+ internal class DimAbsolute : Dim {
int n;
public DimAbsolute (int n) { this.n = n; }
@@ -351,7 +366,7 @@ namespace Terminal.Gui {
}
- class DimFill : Dim {
+ internal class DimFill : Dim {
int margin;
public DimFill (int margin) { this.margin = margin; }
diff --git a/Terminal.Gui/Types/Rect.cs b/Terminal.Gui/Types/Rect.cs
index d5165907c..7b6f1ef4d 100644
--- a/Terminal.Gui/Types/Rect.cs
+++ b/Terminal.Gui/Types/Rect.cs
@@ -43,7 +43,7 @@ namespace Terminal.Gui
///
/// An uninitialized Rectangle Structure.
///
-
+
public static readonly Rect Empty;
///
@@ -54,7 +54,7 @@ namespace Terminal.Gui
/// Produces a Rectangle structure from left, top, right
/// and bottom coordinates.
///
-
+
public static Rect FromLTRB (int left, int top,
int right, int bottom)
{
@@ -70,7 +70,7 @@ namespace Terminal.Gui
/// Produces a new Rectangle by inflating an existing
/// Rectangle by the specified coordinate values.
///
-
+
public static Rect Inflate (Rect rect, int x, int y)
{
Rect r = new Rect (rect.Location, rect.Size);
@@ -85,7 +85,7 @@ namespace Terminal.Gui
///
/// Inflates the Rectangle by a specified width and height.
///
-
+
public void Inflate (int width, int height)
{
Inflate (new Size (width, height));
@@ -98,7 +98,7 @@ namespace Terminal.Gui
///
/// Inflates the Rectangle by a specified Size.
///
-
+
public void Inflate (Size size)
{
X -= size.Width;
@@ -115,7 +115,7 @@ namespace Terminal.Gui
/// Produces a new Rectangle by intersecting 2 existing
/// Rectangles. Returns null if there is no intersection.
///
-
+
public static Rect Intersect (Rect a, Rect b)
{
// MS.NET returns a non-empty rectangle if the two rectangles
@@ -138,7 +138,7 @@ namespace Terminal.Gui
/// Replaces the Rectangle with the intersection of itself
/// and another Rectangle.
///
-
+
public void Intersect (Rect rect)
{
this = Rect.Intersect (this, rect);
@@ -152,7 +152,7 @@ namespace Terminal.Gui
/// Produces a new Rectangle from the union of 2 existing
/// Rectangles.
///
-
+
public static Rect Union (Rect a, Rect b)
{
return FromLTRB (Math.Min (a.Left, b.Left),
@@ -176,7 +176,7 @@ namespace Terminal.Gui
return ((left.Location == right.Location) &&
(left.Size == right.Size));
}
-
+
///
/// Inequality Operator
///
@@ -192,7 +192,6 @@ namespace Terminal.Gui
return ((left.Location != right.Location) ||
(left.Size != right.Size));
}
-
// -----------------------
// Public Constructors
@@ -205,7 +204,7 @@ namespace Terminal.Gui
///
/// Creates a Rectangle from Point and Size values.
///
-
+
public Rect (Point location, Size size)
{
X = location.X;
@@ -222,7 +221,7 @@ namespace Terminal.Gui
/// Creates a Rectangle from a specified x,y location and
/// width and height values.
///
-
+
public Rect (int x, int y, int width, int height)
{
this.X = x;
@@ -268,7 +267,7 @@ namespace Terminal.Gui
/// The X coordinate of the left edge of the Rectangle.
/// Read only.
///
-
+
public int Left {
get {
return X;
@@ -282,7 +281,7 @@ namespace Terminal.Gui
///
/// The Location of the top-left corner of the Rectangle.
///
-
+
public Point Location {
get {
return new Point (X, Y);
@@ -301,7 +300,7 @@ namespace Terminal.Gui
/// The X coordinate of the right edge of the Rectangle.
/// Read only.
///
-
+
public int Right {
get {
return X + Width;
@@ -315,7 +314,7 @@ namespace Terminal.Gui
///
/// The Size of the Rectangle.
///
-
+
public Size Size {
get {
return new Size (Width, Height);
@@ -334,7 +333,7 @@ namespace Terminal.Gui
/// The Y coordinate of the top edge of the Rectangle.
/// Read only.
///
-
+
public int Top {
get {
return Y;
@@ -348,7 +347,7 @@ namespace Terminal.Gui
///
/// Checks if an x,y coordinate lies within this Rectangle.
///
-
+
public bool Contains (int x, int y)
{
return ((x >= Left) && (x < Right) &&
@@ -362,7 +361,7 @@ namespace Terminal.Gui
///
/// Checks if a Point lies within this Rectangle.
///
-
+
public bool Contains (Point pt)
{
return Contains (pt.X, pt.Y);
@@ -376,7 +375,7 @@ namespace Terminal.Gui
/// Checks if a Rectangle lies entirely within this
/// Rectangle.
///
-
+
public bool Contains (Rect rect)
{
return (rect == Intersect (this, rect));
@@ -389,7 +388,7 @@ namespace Terminal.Gui
///
/// Checks equivalence of this Rectangle and another object.
///
-
+
public override bool Equals (object obj)
{
if (!(obj is Rect))
@@ -405,7 +404,7 @@ namespace Terminal.Gui
///
/// Calculates a hashing value.
///
-
+
public override int GetHashCode ()
{
return (Height + Width) ^ X + Y;
@@ -418,14 +417,14 @@ namespace Terminal.Gui
///
/// Checks if a Rectangle intersects with this one.
///
-
+
public bool IntersectsWith (Rect rect)
{
return !((Left >= rect.Right) || (Right <= rect.Left) ||
(Top >= rect.Bottom) || (Bottom <= rect.Top));
}
- private bool IntersectsWithInclusive (Rect r)
+ bool IntersectsWithInclusive (Rect r)
{
return !((Left > r.Right) || (Right < r.Left) ||
(Top > r.Bottom) || (Bottom < r.Top));
@@ -444,7 +443,7 @@ namespace Terminal.Gui
this.X += x;
this.Y += y;
}
-
+
///
/// Offset Method
///
@@ -458,7 +457,7 @@ namespace Terminal.Gui
X += pos.X;
Y += pos.Y;
}
-
+
///
/// ToString Method
///
@@ -466,7 +465,7 @@ namespace Terminal.Gui
///
/// Formats the Rectangle as a string in (x,y,w,h) notation.
///
-
+
public override string ToString ()
{
return String.Format ("{{X={0},Y={1},Width={2},Height={3}}}",
diff --git a/Terminal.Gui/Types/Size.cs b/Terminal.Gui/Types/Size.cs
index ff6493fa3..0a7bc204c 100644
--- a/Terminal.Gui/Types/Size.cs
+++ b/Terminal.Gui/Types/Size.cs
@@ -15,8 +15,8 @@ namespace Terminal.Gui {
/// Stores an ordered pair of integers, which specify a Height and Width.
///
public struct Size
- {
- private int width, height;
+ {
+ int width, height;
///
/// Gets a Size structure that has a Height and Width value of 0.
@@ -36,7 +36,7 @@ namespace Terminal.Gui {
return new Size (sz1.Width + sz2.Width,
sz1.Height + sz2.Height);
}
-
+
///
/// Equality Operator
///
@@ -52,7 +52,7 @@ namespace Terminal.Gui {
return ((sz1.Width == sz2.Width) &&
(sz1.Height == sz2.Height));
}
-
+
///
/// Inequality Operator
///
@@ -68,7 +68,7 @@ namespace Terminal.Gui {
return ((sz1.Width != sz2.Width) ||
(sz1.Height != sz2.Height));
}
-
+
///
/// Subtraction Operator
///
@@ -82,7 +82,7 @@ namespace Terminal.Gui {
return new Size (sz1.Width - sz2.Width,
sz1.Height - sz2.Height);
}
-
+
///
/// Size to Point Conversion
///
@@ -104,7 +104,7 @@ namespace Terminal.Gui {
///
/// Creates a Size from a Point value.
///
-
+
public Size (Point pt)
{
width = pt.X;
@@ -118,7 +118,7 @@ namespace Terminal.Gui {
///
/// Creates a Size from specified dimensions.
///
-
+
public Size (int width, int height)
{
this.width = width;
@@ -132,7 +132,7 @@ namespace Terminal.Gui {
///
/// Indicates if both Width and Height are zero.
///
-
+
public bool IsEmpty {
get {
return ((width == 0) && (height == 0));
@@ -146,7 +146,7 @@ namespace Terminal.Gui {
///
/// The Width coordinate of the Size.
///
-
+
public int Width {
get {
return width;
@@ -163,7 +163,7 @@ namespace Terminal.Gui {
///
/// The Height coordinate of the Size.
///
-
+
public int Height {
get {
return height;
@@ -180,7 +180,7 @@ namespace Terminal.Gui {
///
/// Checks equivalence of this Size and another object.
///
-
+
public override bool Equals (object obj)
{
if (!(obj is Size))
@@ -196,7 +196,7 @@ namespace Terminal.Gui {
///
/// Calculates a hashing value.
///
-
+
public override int GetHashCode ()
{
return width^height;
@@ -209,7 +209,7 @@ namespace Terminal.Gui {
///
/// Formats the Size as a string in coordinate notation.
///
-
+
public override string ToString ()
{
return String.Format ("{{Width={0}, Height={1}}}", width, height);
@@ -227,7 +227,13 @@ namespace Terminal.Gui {
sz1.Height + sz2.Height);
}
-
+
+ ///
+ /// Subtracts the width and height of one Size structure to the width and height of another Size structure.
+ ///
+ /// The subtract.
+ /// The first Size structure to subtract.
+ /// The second Size structure to subtract.
public static Size Subtract (Size sz1, Size sz2)
{
return new Size (sz1.Width - sz2.Width,
diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs
index 7304000e6..f816a98f3 100644
--- a/Terminal.Gui/Views/Button.cs
+++ b/Terminal.Gui/Views/Button.cs
@@ -68,10 +68,16 @@ namespace Terminal.Gui {
CanFocus = true;
this.IsDefault = is_default;
Text = text;
+ int w = SetWidthHeight (text, is_default);
+ Frame = new Rect (0, 0, w, 1);
+ }
+
+ int SetWidthHeight (ustring text, bool is_default)
+ {
int w = text.Length + 4 + (is_default ? 2 : 0);
Width = w;
Height = 1;
- Frame = new Rect (0, 0, w, 1);
+ return w;
}
///
@@ -96,6 +102,9 @@ namespace Terminal.Gui {
}
set {
+ if (text?.Length != value?.Length) {
+ SetWidthHeight (value, is_default);
+ }
text = value;
Update ();
}
@@ -166,8 +175,7 @@ namespace Terminal.Gui {
{
if (Char.ToUpper ((char)key.KeyValue) == hot_key) {
this.SuperView.SetFocus (this);
- if (Clicked != null)
- Clicked ();
+ Clicked?.Invoke ();
return true;
}
return false;
diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs
new file mode 100644
index 000000000..1f1915a10
--- /dev/null
+++ b/Terminal.Gui/Views/DateField.cs
@@ -0,0 +1,241 @@
+//
+// DateField.cs: text entry for date
+//
+// Author: Barry Nolte
+//
+// Licensed under the MIT license
+//
+using System;
+using System.Globalization;
+using System.Linq;
+using NStack;
+
+namespace Terminal.Gui {
+
+ ///
+ /// Date edit widget
+ ///
+ ///
+ /// This widget provides date editing functionality, and mouse support.
+ ///
+ public class DateField : TextField {
+ bool isShort;
+
+ int longFieldLen = 10;
+ int shortFieldLen = 8;
+ int FieldLen { get { return isShort ? shortFieldLen : longFieldLen; } }
+ string sepChar;
+ string longFormat;
+ string shortFormat;
+ string Format { get { return isShort ? shortFormat : longFormat; } }
+
+
+ ///
+ /// Public constructor that creates a date edit field at an absolute position and fixed size.
+ ///
+ /// The x coordinate.
+ /// The y coordinate.
+ /// Initial date contents.
+ /// If true, shows only two digits for the year.
+ public DateField(int x, int y, DateTime date, bool isShort = false) : base(x, y, isShort ? 10 : 12, "")
+ {
+ CultureInfo cultureInfo = CultureInfo.CurrentCulture;
+ sepChar = cultureInfo.DateTimeFormat.DateSeparator;
+ longFormat = $" {cultureInfo.DateTimeFormat.ShortDatePattern}";
+ shortFormat = GetShortFormat(longFormat);
+ this.isShort = isShort;
+ CursorPosition = 1;
+ Date = date;
+ Changed += DateField_Changed;
+ }
+
+ void DateField_Changed(object sender, ustring e)
+ {
+ if (!DateTime.TryParseExact(Text.ToString(), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
+ Text = e;
+ }
+
+ string GetShortFormat(string lf)
+ {
+ return lf.Replace("yyyy", "yy");
+ }
+
+ ///
+ /// Gets or sets the date in the widget.
+ ///
+ ///
+ ///
+ public DateTime Date {
+ get {
+ if (!DateTime.TryParseExact(Text.ToString(), Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result)) return new DateTime();
+ return result;
+ }
+ set {
+ this.Text = value.ToString(Format);
+ }
+ }
+
+ bool SetText(Rune key)
+ {
+ var text = TextModel.ToRunes(Text);
+ var newText = text.GetRange(0, CursorPosition);
+ newText.Add(key);
+ if (CursorPosition < FieldLen)
+ newText = newText.Concat(text.GetRange(CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList();
+ return SetText(ustring.Make(newText));
+ }
+
+ bool SetText(ustring text)
+ {
+ ustring[] vals = text.Split(ustring.Make(sepChar));
+ ustring[] frm = ustring.Make(Format).Split(ustring.Make(sepChar));
+ bool isValidDate = true;
+ int idx = GetFormatIndex(frm, "y");
+ int year = Int32.Parse(vals[idx].ToString());
+ int month;
+ int day;
+ idx = GetFormatIndex(frm, "M");
+ if (Int32.Parse(vals[idx].ToString()) < 1) {
+ isValidDate = false;
+ month = 1;
+ vals[idx] = "1";
+ } else if (Int32.Parse(vals[idx].ToString()) > 12) {
+ isValidDate = false;
+ month = 12;
+ vals[idx] = "12";
+ } else
+ month = Int32.Parse(vals[idx].ToString());
+ idx = GetFormatIndex(frm, "d");
+ if (Int32.Parse(vals[idx].ToString()) < 1) {
+ isValidDate = false;
+ day = 1;
+ vals[idx] = "1";
+ } else if (Int32.Parse(vals[idx].ToString()) > 31) {
+ isValidDate = false;
+ day = DateTime.DaysInMonth(year, month);
+ vals[idx] = day.ToString();
+ } else
+ day = Int32.Parse(vals[idx].ToString());
+ string date = GetData(month, day, year, frm);
+ Text = date;
+
+ if (!DateTime.TryParseExact(date, Format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
+ !isValidDate)
+ return false;
+ return true;
+ }
+
+ string GetData(int month, int day, int year, ustring[] fm)
+ {
+ string data = " ";
+ for (int i = 0; i < fm.Length; i++) {
+ if (fm[i].Contains("M"))
+ data += $"{month,2:00}";
+ else if (fm[i].Contains("d"))
+ data += $"{day,2:00}";
+ else
+ data += isShort ? $"{year,2:00}" : $"{year,4:0000}";
+ if (i < 2)
+ data += $"{sepChar}";
+ }
+ return data;
+ }
+
+ int GetFormatIndex(ustring[] fm, string t)
+ {
+ int idx = -1;
+ for (int i = 0; i < fm.Length; i++) {
+ if (fm[i].Contains(t)) {
+ idx = i;
+ break;
+ }
+ }
+ return idx;
+ }
+
+ void IncCursorPosition()
+ {
+ if (CursorPosition == FieldLen)
+ return;
+ if (Text[++CursorPosition] == sepChar.ToCharArray()[0])
+ CursorPosition++;
+ }
+
+ void DecCursorPosition()
+ {
+ if (CursorPosition == 1)
+ return;
+ if (Text[--CursorPosition] == sepChar.ToCharArray()[0])
+ CursorPosition--;
+ }
+
+ void AdjCursorPosition()
+ {
+ if (Text[CursorPosition] == sepChar.ToCharArray()[0])
+ CursorPosition++;
+ }
+
+ public override bool ProcessKey(KeyEvent kb)
+ {
+ switch (kb.Key) {
+ case Key.DeleteChar:
+ case Key.ControlD:
+ SetText('0');
+ break;
+
+ case Key.Delete:
+ case Key.Backspace:
+ SetText('0');
+ DecCursorPosition();
+ break;
+
+ // Home, C-A
+ case Key.Home:
+ case Key.ControlA:
+ CursorPosition = 1;
+ break;
+
+ case Key.CursorLeft:
+ case Key.ControlB:
+ DecCursorPosition();
+ break;
+
+ case Key.End:
+ case Key.ControlE: // End
+ CursorPosition = FieldLen;
+ break;
+
+ case Key.CursorRight:
+ case Key.ControlF:
+ IncCursorPosition();
+ break;
+
+ default:
+ // Ignore non-numeric characters.
+ if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9'))
+ return false;
+ if (SetText(TextModel.ToRunes(ustring.Make((uint)kb.Key)).First()))
+ IncCursorPosition();
+ return true;
+ }
+ return true;
+ }
+
+ public override bool MouseEvent(MouseEvent ev)
+ {
+ if (!ev.Flags.HasFlag(MouseFlags.Button1Clicked))
+ return false;
+ if (!HasFocus)
+ SuperView.SetFocus(this);
+
+ var point = ev.X;
+ if (point > FieldLen)
+ point = FieldLen;
+ if (point < 1)
+ point = 1;
+ CursorPosition = point;
+ AdjCursorPosition();
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs
index 22866f382..f87d941ee 100644
--- a/Terminal.Gui/Views/Label.cs
+++ b/Terminal.Gui/Views/Label.cs
@@ -248,7 +248,7 @@ namespace Terminal.Gui {
Attribute textColor = -1;
///
/// The color used for the label
- ///
+ ///
public Attribute TextColor {
get => textColor;
set {
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index 71ea3a709..41d4bc135 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -20,6 +20,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using NStack;
namespace Terminal.Gui {
@@ -123,6 +125,21 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Sets the source to an IList value asynchronously, if you want to set a full IListDataSource, use the Source property.
+ ///
+ /// An item implementing the IList interface.
+ public Task SetSourceAsync (IList source)
+ {
+ return Task.Factory.StartNew (() => {
+ if (source == null)
+ Source = null;
+ else
+ Source = MakeWrapper (source);
+ return source;
+ }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
bool allowsMarking;
///
/// Gets or sets a value indicating whether this allows items to be marked.
@@ -142,6 +159,8 @@ namespace Terminal.Gui {
}
}
+ public bool AllowsMultipleSelection { get; set; } = true;
+
///
/// Gets or sets the item that is displayed at the top of the listview
///
@@ -305,8 +324,21 @@ namespace Terminal.Gui {
return base.ProcessKey (kb);
}
+ public virtual bool AllowsAll ()
+ {
+ if (!allowsMarking)
+ return false;
+ if (!AllowsMultipleSelection) {
+ for (int i = 0; i < Source.Count; i++) {
+ if (Source.IsMarked (i) && i != selected)
+ return false;
+ }
+ }
+ return true;
+ }
+
public virtual bool MarkUnmarkRow(){
- if (allowsMarking){
+ if (AllowsAll ()) {
Source.SetMark(SelectedItem, !Source.IsMarked(SelectedItem));
SetNeedsDisplay();
return true;
@@ -400,7 +432,7 @@ namespace Terminal.Gui {
return true;
selected = top + me.Y;
- if (allowsMarking) {
+ if (AllowsAll ()) {
Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem));
SetNeedsDisplay ();
return true;
diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs
index ee120eb5f..f44c9b21c 100644
--- a/Terminal.Gui/Views/Menu.cs
+++ b/Terminal.Gui/Views/Menu.cs
@@ -1,515 +1,993 @@
-//
-// Menu.cs: application menus and submenus
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
-// TODO:
-// Add accelerator support, but should also support chords (ShortCut in MenuItem)
-// Allow menus inside menus
-
-using System;
-using NStack;
-using System.Linq;
-
-namespace Terminal.Gui {
-
- ///
- /// A menu item has a title, an associated help text, and an action to execute on activation.
- ///
- public class MenuItem {
-
- ///
- /// Initializes a new .
- ///
- /// Title for the menu item.
- /// Help text to display.
- /// Action to invoke when the menu item is activated.
- public MenuItem (ustring title, string help, Action action)
- {
- Title = title ?? "";
- Help = help ?? "";
- Action = action;
- bool nextIsHot = false;
- foreach (var x in Title) {
- if (x == '_')
- nextIsHot = true;
- else {
- if (nextIsHot) {
- HotKey = Char.ToUpper ((char)x);
- break;
- }
- nextIsHot = false;
- }
- }
- }
-
- //
- //
-
- ///
- /// The hotkey is used when the menu is active, the shortcut can be triggered when the menu is not active.
- /// For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry
- /// if the ShortCut is set to "Control-N", this would be a global hotkey that would trigger as well
- ///
- public Rune HotKey;
-
- ///
- /// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
- ///
- public Key ShortCut;
-
- ///
- /// Gets or sets the title.
- ///
- /// The title.
- public ustring Title { get; set; }
-
- ///
- /// Gets or sets the help text for the menu item.
- ///
- /// The help text.
- public ustring Help { get; set; }
-
- ///
- /// Gets or sets the action to be invoked when the menu is triggered
- ///
- /// Method to invoke.
- public Action Action { get; set; }
- internal int Width => Title.Length + Help.Length + 1 + 2;
- }
-
- ///
- /// A menu bar item contains other menu items.
- ///
- public class MenuBarItem {
- public MenuBarItem (ustring title, MenuItem [] children)
- {
- SetTitle (title ?? "");
- Children = children;
- }
-
- void SetTitle (ustring title)
- {
- if (title == null)
- title = "";
- Title = title;
- int len = 0;
- foreach (var ch in Title) {
- if (ch == '_')
- continue;
- len++;
- }
- TitleLength = len;
- }
-
- ///
- /// Gets or sets the title to display.
- ///
- /// The title.
- public ustring Title { get; set; }
-
- ///
- /// Gets or sets the children for this MenuBarItem
- ///
- /// The children.
- public MenuItem [] Children { get; set; }
- internal int TitleLength { get; private set; }
- }
-
- class Menu : View {
- MenuBarItem barItems;
- MenuBar host;
- int current;
-
- static Rect MakeFrame (int x, int y, MenuItem [] items)
- {
- int maxW = items.Max(z=>z?.Width) ?? 0;
-
- return new Rect (x, y, maxW + 2, items.Length + 2);
- }
-
- public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
- {
- this.barItems = barItems;
- this.host = host;
- current = -1;
- for (int i = 0; i < barItems.Children.Length; i++) {
- if (barItems.Children[i] != null) {
- current = i;
- break;
- }
- }
- ColorScheme = Colors.Menu;
- CanFocus = true;
- }
-
- public override void Redraw (Rect region)
- {
- Driver.SetAttribute (ColorScheme.Normal);
- DrawFrame (region, padding: 0, fill: true);
-
- for (int i = 0; i < barItems.Children.Length; i++){
- var item = barItems.Children [i];
- Move (1, i+1);
- Driver.SetAttribute (item == null ? Colors.Base.Focus : i == current ? ColorScheme.Focus : ColorScheme.Normal);
- for (int p = 0; p < Frame.Width-2; p++)
- if (item == null)
- Driver.AddRune (Driver.HLine);
- else
- Driver.AddRune (' ');
-
- if (item == null)
- continue;
-
- Move (2, i + 1);
- DrawHotString (item.Title,
- i == current? ColorScheme.HotFocus : ColorScheme.HotNormal,
- i == current ? ColorScheme.Focus : ColorScheme.Normal);
-
- // The help string
- var l = item.Help.Length;
- Move (Frame.Width - l - 2, 1 + i);
- Driver.AddStr (item.Help);
- }
- }
-
- public override void PositionCursor ()
- {
- Move (2, 1 + current);
- }
-
- void Run (Action action)
- {
- if (action == null)
- return;
-
- Application.MainLoop.AddIdle (() => {
- action ();
- return false;
- });
- }
-
- public override bool ProcessKey (KeyEvent kb)
- {
- switch (kb.Key) {
- case Key.CursorUp:
- if (current == -1)
- break;
- do {
- current--;
- if (current < 0)
- current = barItems.Children.Length - 1;
- } while (barItems.Children [current] == null);
- SetNeedsDisplay ();
- break;
- case Key.CursorDown:
- do {
- current++;
- if (current == barItems.Children.Length)
- current = 0;
- } while (barItems.Children [current] == null);
- SetNeedsDisplay ();
- break;
- case Key.CursorLeft:
- host.PreviousMenu ();
- break;
- case Key.CursorRight:
- host.NextMenu ();
- break;
- case Key.Esc:
- host.CloseMenu ();
- break;
- case Key.Enter:
- host.CloseMenu ();
- Run (barItems.Children [current].Action);
- break;
- default:
- // TODO: rune-ify
- if (Char.IsLetterOrDigit ((char)kb.KeyValue)) {
- var x = Char.ToUpper ((char)kb.KeyValue);
-
- foreach (var item in barItems.Children) {
- if (item.HotKey == x) {
- host.CloseMenu ();
- Run (item.Action);
- return true;
- }
- }
- }
- break;
- }
- return true;
- }
-
- public override bool MouseEvent(MouseEvent me)
- {
- if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1Released) {
- if (me.Y < 1)
- return true;
- var item = me.Y - 1;
- if (item >= barItems.Children.Length)
- return true;
- host.CloseMenu ();
- Run (barItems.Children [item].Action);
- return true;
- }
- if (me.Flags == MouseFlags.Button1Pressed) {
- if (me.Y < 1)
- return true;
- if (me.Y - 1 >= barItems.Children.Length)
- return true;
- current = me.Y - 1;
- SetNeedsDisplay ();
- return true;
- }
- return false;
- }
- }
-
- ///
- /// A menu bar for your application.
- ///
- public class MenuBar : View {
- ///
- /// The menus that were defined when the menubar was created. This can be updated if the menu is not currently visible.
- ///
- /// The menu array.
- public MenuBarItem [] Menus { get; set; }
- int selected;
- Action action;
-
-
- ///
- /// Initializes a new instance of the class with the specified set of toplevel menu items.
- ///
- /// Individual menu items, if one of those contains a null, then a separator is drawn.
- public MenuBar (MenuBarItem [] menus) : base ()
- {
- X = 0;
- Y = 0;
- Width = Dim.Fill ();
- Height = 1;
- Menus = menus;
- CanFocus = false;
- selected = -1;
- ColorScheme = Colors.Menu;
- }
-
- public override void Redraw (Rect region)
- {
- Move (0, 0);
- Driver.SetAttribute (Colors.Base.Focus);
- for (int i = 0; i < Frame.Width; i++)
- Driver.AddRune (' ');
-
- Move (1, 0);
- int pos = 1;
-
- for (int i = 0; i < Menus.Length; i++) {
- var menu = Menus [i];
- Move (pos, 0);
- Attribute hotColor, normalColor;
- if (i == selected){
- hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
- normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
- } else {
- hotColor = Colors.Base.Focus;
- normalColor = Colors.Base.Focus;
- }
- DrawHotString (" " + menu.Title + " " + " ", hotColor, normalColor);
- pos += menu.TitleLength+ 3;
- }
- PositionCursor ();
- }
-
- public override void PositionCursor ()
- {
- int pos = 0;
- for (int i = 0; i < Menus.Length; i++) {
- if (i == selected) {
- pos++;
- Move (pos, 0);
- return;
- } else {
- pos += Menus [i].TitleLength + 4;
- }
- }
- Move (0, 0);
- }
-
- void Selected (MenuItem item)
- {
- // TODO: Running = false;
- action = item.Action;
- }
-
- public event EventHandler OnOpenMenu;
- Menu openMenu;
- View previousFocused;
-
- void OpenMenu (int index)
- {
- OnOpenMenu?.Invoke(this, null);
- if (openMenu != null)
- SuperView.Remove (openMenu);
-
- int pos = 0;
- for (int i = 0; i < index; i++)
- pos += Menus [i].Title.Length + 3;
-
- openMenu = new Menu (this, pos, 1, Menus [index]);
-
- SuperView.Add (openMenu);
- SuperView.SetFocus (openMenu);
- }
-
- // Starts the menu from a hotkey
- void StartMenu ()
- {
- if (openMenu != null)
- return;
- selected = 0;
- SetNeedsDisplay ();
-
- previousFocused = SuperView.Focused;
- OpenMenu (selected);
- }
-
- // Activates the menu, handles either first focus, or activating an entry when it was already active
- // For mouse events.
- void Activate (int idx)
- {
- selected = idx;
- if (openMenu == null)
- previousFocused = SuperView.Focused;
-
- OpenMenu (idx);
- SetNeedsDisplay ();
- }
-
- internal void CloseMenu ()
- {
- selected = -1;
- SetNeedsDisplay ();
- SuperView.Remove (openMenu);
- previousFocused?.SuperView?.SetFocus (previousFocused);
- openMenu = null;
- }
-
- internal void PreviousMenu ()
- {
- if (selected <= 0)
- selected = Menus.Length - 1;
- else
- selected--;
-
- OpenMenu (selected);
- }
-
- internal void NextMenu ()
- {
- if (selected == -1)
- selected = 0;
- else if (selected + 1 == Menus.Length)
- selected = 0;
- else
- selected++;
- OpenMenu (selected);
- }
-
- internal bool FindAndOpenMenuByHotkey(KeyEvent kb)
- {
- int pos = 0;
+//
+// Menu.cs: application menus and submenus
+//
+// Authors:
+// Miguel de Icaza (miguel@gnome.org)
+//
+// TODO:
+// Add accelerator support, but should also support chords (ShortCut in MenuItem)
+// Allow menus inside menus
+
+using System;
+using NStack;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace Terminal.Gui {
+
+ ///
+ /// A menu item has a title, an associated help text, and an action to execute on activation.
+ ///
+ public class MenuItem {
+
+ ///
+ /// Initializes a new .
+ ///
+ /// Title for the menu item.
+ /// Help text to display.
+ /// Action to invoke when the menu item is activated.
+ /// Function to determine if the action can currently be executred.
+ public MenuItem (ustring title, string help, Action action, Func canExecute = null)
+ {
+ Title = title ?? "";
+ Help = help ?? "";
+ Action = action;
+ CanExecute = canExecute;
+ bool nextIsHot = false;
+ foreach (var x in Title) {
+ if (x == '_')
+ nextIsHot = true;
+ else {
+ if (nextIsHot) {
+ HotKey = Char.ToUpper ((char)x);
+ break;
+ }
+ nextIsHot = false;
+ }
+ }
+ }
+
+ ///
+ /// Initializes a new .
+ ///
+ /// Title for the menu item.
+ /// The menu sub-menu.
+ public MenuItem (ustring title, MenuBarItem subMenu) : this (title, "", null)
+ {
+ SubMenu = subMenu;
+ IsFromSubMenu = true;
+ }
+
+ //
+ //
+
+ ///
+ /// The hotkey is used when the menu is active, the shortcut can be triggered when the menu is not active.
+ /// For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry
+ /// if the ShortCut is set to "Control-N", this would be a global hotkey that would trigger as well
+ ///
+ public Rune HotKey;
+
+ ///
+ /// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
+ ///
+ public Key ShortCut;
+
+ ///
+ /// Gets or sets the title.
+ ///
+ /// The title.
+ public ustring Title { get; set; }
+
+ ///
+ /// Gets or sets the help text for the menu item.
+ ///
+ /// The help text.
+ public ustring Help { get; set; }
+
+ ///
+ /// Gets or sets the action to be invoked when the menu is triggered
+ ///
+ /// Method to invoke.
+ public Action Action { get; set; }
+
+ ///
+ /// Gets or sets the action to be invoked if the menu can be triggered
+ ///
+ /// Function to determine if action is ready to be executed.
+ public Func CanExecute { get; set; }
+
+ ///
+ /// Shortcut to check if the menu item is enabled
+ ///
+ public bool IsEnabled ()
+ {
+ return CanExecute == null ? true : CanExecute ();
+ }
+
+ internal int Width => Title.Length + Help.Length + 1 + 2;
+
+ ///
+ /// Gets or sets the parent for this MenuBarItem
+ ///
+ /// The parent.
+ internal MenuBarItem SubMenu { get; set; }
+ internal bool IsFromSubMenu { get; set; }
+
+ ///
+ /// Merely a debugging aid to see the interaction with main
+ ///
+ public MenuItem GetMenuItem ()
+ {
+ return this;
+ }
+
+ ///
+ /// Merely a debugging aid to see the interaction with main
+ ///
+ public bool GetMenuBarItem ()
+ {
+ return IsFromSubMenu;
+ }
+ }
+
+ ///
+ /// A menu bar item contains other menu items.
+ ///
+ public class MenuBarItem {
+ ///
+ /// Initializes a new .
+ ///
+ /// Title for the menu item.
+ /// The items in the current menu.
+ public MenuBarItem (ustring title, MenuItem [] children)
+ {
+ SetTitle (title ?? "");
+ Children = children;
+ }
+
+ ///
+ /// Initializes a new .
+ ///
+ /// The items in the current menu.
+ public MenuBarItem (MenuItem[] children) : this (new string (' ', GetMaxTitleLength (children)), children)
+ {
+ }
+
+ static int GetMaxTitleLength (MenuItem[] children)
+ {
+ int maxLength = 0;
+ foreach (var item in children) {
+ int len = GetMenuBarItemLength (item.Title);
+ if (len > maxLength)
+ maxLength = len;
+ item.IsFromSubMenu = true;
+ }
+
+ return maxLength;
+ }
+
+ void SetTitle (ustring title)
+ {
+ if (title == null)
+ title = "";
+ Title = title;
+ TitleLength = GetMenuBarItemLength(Title);
+ }
+
+ static int GetMenuBarItemLength(ustring title)
+ {
+ int len = 0;
+ foreach (var ch in title) {
+ if (ch == '_')
+ continue;
+ len++;
+ }
+
+ return len;
+ }
+
+ ///
+ /// Gets or sets the title to display.
+ ///
+ /// The title.
+ public ustring Title { get; set; }
+
+ ///
+ /// Gets or sets the children for this MenuBarItem
+ ///
+ /// The children.
+ public MenuItem [] Children { get; set; }
+ internal int TitleLength { get; private set; }
+ }
+
+ class Menu : View {
+ internal MenuBarItem barItems;
+ MenuBar host;
+ internal int current;
+ internal View previousSubFocused;
+
+ static Rect MakeFrame (int x, int y, MenuItem [] items)
+ {
+ int maxW = items.Max(z => z?.Width) ?? 0;
+
+ return new Rect (x, y, maxW + 2, items.Length + 2);
+ }
+
+ public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
+ {
+ this.barItems = barItems;
+ this.host = host;
+ current = -1;
+ for (int i = 0; i < barItems.Children.Length; i++) {
+ if (barItems.Children[i] != null) {
+ current = i;
+ break;
+ }
+ }
+ ColorScheme = Colors.Menu;
+ CanFocus = true;
+ WantMousePositionReports = host.WantMousePositionReports;
+ }
+
+ internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
+ {
+ if (item != null) {
+ if (index == current) return ColorScheme.Focus;
+ if (!item.IsEnabled ()) return ColorScheme.Disabled;
+ }
+ return ColorScheme.Normal;
+ }
+
+ public override void Redraw (Rect region)
+ {
+ Driver.SetAttribute (ColorScheme.Normal);
+ DrawFrame (region, padding: 0, fill: true);
+
+ for (int i = 0; i < barItems.Children.Length; i++) {
+ var item = barItems.Children [i];
+ Driver.SetAttribute (item == null ? ColorScheme.Normal : i == current ? ColorScheme.Focus : ColorScheme.Normal);
+ if (item == null) {
+ Move (0, i + 1);
+ Driver.AddRune (Driver.LeftTee);
+ } else
+ Move (1, i + 1);
+
+ Driver.SetAttribute (DetermineColorSchemeFor (item, i));
+ for (int p = 0; p < Frame.Width - 2; p++)
+ if (item == null)
+ Driver.AddRune (Driver.HLine);
+ else if (p == Frame.Width - 3 && barItems.Children [i].SubMenu != null)
+ Driver.AddRune ('>');
+ else
+ Driver.AddRune (' ');
+
+ if (item == null) {
+ Move (Frame.Right - 1, i + 1);
+ Driver.AddRune (Driver.RightTee);
+ continue;
+ }
+
+ Move (2, i + 1);
+ if (!item.IsEnabled ())
+ DrawHotString (item.Title, ColorScheme.Disabled, ColorScheme.Disabled);
+ else
+ DrawHotString (item.Title,
+ i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+ i == current ? ColorScheme.Focus : ColorScheme.Normal);
+
+ // The help string
+ var l = item.Help.Length;
+ Move (Frame.Width - l - 2, 1 + i);
+ Driver.AddStr (item.Help);
+ }
+ PositionCursor ();
+ }
+
+ public override void PositionCursor ()
+ {
+ if (!host.isMenuClosed)
+ Move (2, 1 + current);
+ else
+ host.PositionCursor ();
+ }
+
+ void Run (Action action)
+ {
+ if (action == null)
+ return;
+
+ Application.UngrabMouse ();
+ host.CloseAllMenus ();
+ Application.Refresh ();
+
+ Application.MainLoop.AddIdle (() => {
+ action ();
+ return false;
+ });
+ }
+
+ public override bool ProcessKey (KeyEvent kb)
+ {
+ bool disabled;
+ switch (kb.Key) {
+ case Key.CursorUp:
+ if (current == -1)
+ break;
+ do {
+ disabled = false;
+ current--;
+ if (host.UseKeysUpDownAsKeysLeftRight) {
+ if (current == -1 && barItems.Children [current + 1].IsFromSubMenu && host.selectedSub > -1) {
+ current++;
+ host.PreviousMenu (true);
+ break;
+ }
+ }
+ if (current < 0)
+ current = barItems.Children.Length - 1;
+ var item = barItems.Children [current];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ } while (barItems.Children [current] == null || disabled);
+ SetNeedsDisplay ();
+ break;
+ case Key.CursorDown:
+ do {
+ current++;
+ disabled = false;
+ if (current == barItems.Children.Length)
+ current = 0;
+ var item = barItems.Children [current];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ if (host.UseKeysUpDownAsKeysLeftRight && barItems.Children [current]?.SubMenu != null &&
+ !disabled && !host.isMenuClosed) {
+ CheckSubMenu ();
+ break;
+ }
+ if (host.isMenuClosed)
+ host.OpenMenu (host.selected);
+ } while (barItems.Children [current] == null || disabled);
+ SetNeedsDisplay ();
+ break;
+ case Key.CursorLeft:
+ host.PreviousMenu (true);
+ break;
+ case Key.CursorRight:
+ host.NextMenu (barItems.Children [current].IsFromSubMenu ? true : false);
+ break;
+ case Key.Esc:
+ Application.UngrabMouse ();
+ host.CloseAllMenus ();
+ break;
+ case Key.Enter:
+ CheckSubMenu ();
+ Run (barItems.Children [current].Action);
+ break;
+ default:
+ // TODO: rune-ify
+ if (Char.IsLetterOrDigit ((char)kb.KeyValue)) {
+ var x = Char.ToUpper ((char)kb.KeyValue);
+
+ foreach (var item in barItems.Children) {
+ if (item == null) continue;
+ if (item.IsEnabled () && item.HotKey == x) {
+ host.CloseMenu ();
+ Run (item.Action);
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ return true;
+ }
+
+ public override bool MouseEvent(MouseEvent me)
+ {
+ if (!host.handled && !host.HandleGrabView (me, this)) {
+ return false;
+ }
+ host.handled = false;
+ bool disabled;
+ if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1Released) {
+ disabled = false;
+ if (me.Y < 1)
+ return true;
+ var meY = me.Y - 1;
+ if (meY >= barItems.Children.Length)
+ return true;
+ var item = barItems.Children [meY];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ if (item != null && !disabled)
+ Run (barItems.Children [meY].Action);
+ return true;
+ } else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.ReportMousePosition) {
+ disabled = false;
+ if (me.Y < 1)
+ return true;
+ if (me.Y - 1 >= barItems.Children.Length)
+ return true;
+ var item = barItems.Children [me.Y - 1];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ if (item != null && !disabled)
+ current = me.Y - 1;
+ HasFocus = true;
+ SetNeedsDisplay ();
+ CheckSubMenu ();
+ return true;
+ }
+ return false;
+ }
+
+ internal void CheckSubMenu ()
+ {
+ if (barItems.Children [current] == null)
+ return;
+ var subMenu = barItems.Children [current].SubMenu;
+ if (subMenu != null) {
+ int pos = -1;
+ if (host.openSubMenu != null)
+ pos = host.openSubMenu.FindIndex (o => o?.barItems == subMenu);
+ host.Activate (host.selected, pos, subMenu);
+ } else if (host.openSubMenu != null && !barItems.Children [current].IsFromSubMenu)
+ host.CloseMenu (false, true);
+ }
+
+ int GetSubMenuIndex (MenuBarItem subMenu)
+ {
+ int pos = -1;
+ if (this != null && Subviews.Count > 0) {
+ Menu v = null;
+ foreach (var menu in Subviews) {
+ if (((Menu)menu).barItems == subMenu)
+ v = (Menu)menu;
+ }
+ if (v != null)
+ pos = Subviews.IndexOf (v);
+ }
+
+ return pos;
+ }
+ }
+
+
+
+ ///
+ /// A menu bar for your application.
+ ///
+ public class MenuBar : View {
+ ///
+ /// The menus that were defined when the menubar was created. This can be updated if the menu is not currently visible.
+ ///
+ /// The menu array.
+ public MenuBarItem [] Menus { get; set; }
+ internal int selected;
+ internal int selectedSub;
+ Action action;
+
+ ///
+ /// Used for change the navigation key style.
+ ///
+ public bool UseKeysUpDownAsKeysLeftRight { get; set; } = true;
+
+ ///
+ /// Initializes a new instance of the class with the specified set of toplevel menu items.
+ ///
+ /// Individual menu items, if one of those contains a null, then a separator is drawn.
+ public MenuBar (MenuBarItem [] menus) : base ()
+ {
+ X = 0;
+ Y = 0;
+ Width = Dim.Fill ();
+ Height = 1;
+ Menus = menus;
+ CanFocus = false;
+ selected = -1;
+ selectedSub = -1;
+ ColorScheme = Colors.Menu;
+ WantMousePositionReports = true;
+ isMenuClosed = true;
+ }
+
+ public override void Redraw (Rect region)
+ {
+ Move (0, 0);
+ Driver.SetAttribute (Colors.Menu.Normal);
+ for (int i = 0; i < Frame.Width; i++)
+ Driver.AddRune (' ');
+
+ Move (1, 0);
+ int pos = 1;
+
+ for (int i = 0; i < Menus.Length; i++) {
+ var menu = Menus [i];
+ Move (pos, 0);
+ Attribute hotColor, normalColor;
+ if (i == selected) {
+ hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
+ normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
+ } else {
+ hotColor = Colors.Base.Focus;
+ normalColor = Colors.Base.Focus;
+ }
+ DrawHotString (" " + menu.Title + " " + " ", hotColor, normalColor);
+ pos += menu.TitleLength + 3;
+ }
+ PositionCursor ();
+ }
+
+ public override void PositionCursor ()
+ {
+ int pos = 0;
+ for (int i = 0; i < Menus.Length; i++) {
+ if (i == selected) {
+ pos++;
+ if (!isMenuClosed)
+ Move (pos, 0);
+ else
+ Move (pos + 1, 0);
+ return;
+ } else {
+ if (!isMenuClosed)
+ pos += Menus [i].TitleLength + 4;
+ else
+ pos += 2 + Menus [i].TitleLength + 1;
+ }
+ }
+ Move (0, 0);
+ }
+
+ void Selected (MenuItem item)
+ {
+ // TODO: Running = false;
+ action = item.Action;
+ }
+
+ public event EventHandler OnOpenMenu;
+ internal Menu openMenu;
+ Menu openCurrentMenu;
+ internal List