UI catalog (#387)

* key down/up support

* line endings?

* line endings

* KeyDown/Up support

* line endings

* line endings

* Revert "Drop NuGet restore"

This reverts commit 5c7a0d05f0.

* Revert "Revert "Drop NuGet restore""

This reverts commit 2dc5fce865.

* updated demo

* defined styles

* Smarter StatusBar bottom tracking.

* Prepping for https://github.com/migueldeicaza/gui.cs/issues/376

* Oops.

* Fixed StatusBar 'snap to bottom'

* line endings

* Revert "Fixed StatusBar 'snap to bottom'"

This reverts commit 9a91c957e2.

* started UICatalog project

* Initial working POC.

* Fix newlines

* merge

* textalignment demo tweaks

* textalignment demo tweaks

* Unicode Menu Scenario

* not sure why this keeps changing

* re-added project to .sln file

* re-enabled status bar

* moved scenarios to dir

* building a dim and pos demo

* terminal.sln

* progress...barely

* fixed exit

* progress with some underlying fixes to Label

* added readme

* fixes build issue

* launch

* made default colors readable on Windows

* major UI Catalog upgrade

* added more demos and updated readme

* refactored and added more tests

* added ref to Issue #437

* added OnKeyUp support to Curses and Net drivers

* more tweaks - grab PR #438 first

* Added a OpenSelectedItem event to the ListView #429

* updates

* moved KeyUpHandler out of special ESC stuff

* more tweaks & improvements

* testing top window bug

* supported OpenSelectedItem

* lots of updates

* fixed regression, fixed #444

* better button scenario

* tweaks

* add Ready event to Toplevel

* dotfx .gitignroe

* ready for ready

* updated colors based on feedback; consolodated config code

* tweaked readme

* readme

* Added Editor demonstrating TextView

* Added Editor demonstrating TextView

* added hexeditor scenario

Co-authored-by: Miguel de Icaza <miguel@gnome.org>
Co-authored-by: BDisp <bd.bdisp@gmail.com>
This commit is contained in:
Charlie Kindel
2020-05-20 11:37:12 -06:00
committed by BDisp
parent a89a50b44f
commit 8afb0db831
31 changed files with 2259 additions and 78 deletions

View File

@@ -8,6 +8,7 @@
<RootNamespace>Designer</RootNamespace>
<AssemblyName>Designer</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">

View File

@@ -8,6 +8,7 @@
<RootNamespace>Terminal</RootNamespace>
<AssemblyName>Terminal</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />

611
Example/demo.cs.orig Normal file
View File

@@ -0,0 +1,611 @@
using Terminal.Gui;
using System;
using Mono.Terminal;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using NStack;
using System.Text;
static class Demo {
//class Box10x : View, IScrollView {
class Box10x : View {
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 < h; y++) {
Move (0, y);
Driver.AddStr (y.ToString ());
for (int x = 0; x < w - y.ToString ().Length; x++) {
//Driver.AddRune ((Rune)('0' + (x + y) % 10));
if (y.ToString ().Length < w)
Driver.AddStr (" ");
}
}
//Move (pos.X, pos.Y);
}
}
class Filler : View {
public Filler (Rect rect) : base (rect)
{
}
public override void Redraw (Rect region)
{
Driver.SetAttribute (ColorScheme.Focus);
var f = Frame;
for (int y = 0; y < f.Width; y++) {
Move (0, y);
for (int x = 0; x < f.Height; x++) {
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:
r = 'o';
break;
default:
r = 'O';
break;
}
Driver.AddRune (r);
}
}
}
}
static void ShowTextAlignments ()
{
<<<<<<< HEAD
var container = new Window ($"Show Text Alignments") {
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
container.OnKeyUp += (KeyEvent ke) => {
if (ke.Key == Key.Esc)
container.Running = false;
};
=======
var container = new Dialog (
"Text Alignments", 70, 20,
new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
>>>>>>> cb40c5c2491a559658481d20dd4b6a3343c0183f
int i = 0;
string txt = "Hello world, how are you doing today?";
container.Add (
<<<<<<< HEAD
new Label ($"{i+1}-{txt}") { TextAlignment = TextAlignment.Left, Y = 3, Width = Dim.Fill () },
new Label ($"{i+2}-{txt}") { TextAlignment = TextAlignment.Right, Y = 5, Width = Dim.Fill () },
new Label ($"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered, Y = 7, Width = Dim.Fill () },
new Label ($"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified, Y = 9, Width = Dim.Fill () }
=======
new Label (new Rect (0, 1, 50, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left },
new Label (new Rect (0, 3, 50, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right },
new Label (new Rect (0, 5, 50, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered },
new Label (new Rect (0, 7, 50, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified }
>>>>>>> cb40c5c2491a559658481d20dd4b6a3343c0183f
);
Application.Run (container);
}
static void ShowEntries (View container)
{
var scrollView = new ScrollView (new Rect (50, 10, 20, 8)) {
ContentSize = new Size (20, 50),
//ContentOffset = new Point (0, 0),
ShowVerticalScrollIndicator = true,
ShowHorizontalScrollIndicator = true
};
#if false
scrollView.Add (new Box10x (0, 0));
#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)) {
ContentSize = new Size (100, 100),
ShowVerticalScrollIndicator = true,
ShowHorizontalScrollIndicator = true
};
scrollView2.Add (new Box10x (0, 0));
var progress = new ProgressBar (new Rect (68, 1, 10, 1));
bool timer (MainLoop caller)
{
progress.Pulse ();
return true;
}
Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
// A little convoluted, this is because I am using this to test the
// layout based on referencing elements of another view:
var login = new Label ("Login: ") { X = 3, Y = 6 };
var password = new Label ("Password: ") {
X = Pos.Left (login),
Y = Pos.Bottom (login) + 1
};
var loginText = new TextField ("") {
X = Pos.Right (password),
Y = Pos.Top (login),
Width = 40
};
var passText = new TextField ("") {
Secret = true,
X = Pos.Left (loginText),
Y = Pos.Top (password),
Width = Dim.Width (loginText)
};
var tf = new Button (3, 19, "Ok");
// Add some content
container.Add (
login,
loginText,
password,
passText,
new FrameView (new Rect (3, 10, 25, 6), "Options"){
new CheckBox (1, 0, "Remember me"),
new RadioGroup (1, 2, new [] { "_Personal", "_Company" }),
},
new ListView (new Rect (59, 6, 16, 4), new string [] {
"First row",
"<>",
"This is a very long row that should overflow what is shown",
"4th",
"There is an empty slot on the second row",
"Whoa",
"This is so cool"
}),
scrollView,
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, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"),
menuKeysStyle,
menuAutoMouseNav
);
container.SendSubviewToBack (tf);
}
public static Label ml2;
static void NewFile ()
{
var d = new Dialog (
"New File", 50, 20,
new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
ml2 = new Label (1, 1, "Mouse Debug Line");
d.Add (ml2);
Application.Run (d);
}
//
// Creates a nested editor
static void Editor (Toplevel top)
{
var tframe = top.Frame;
var ntop = new Toplevel (tframe);
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", null),
new MenuItem ("C_ut", "", null),
new MenuItem ("_Paste", "", null)
}),
});
ntop.Add (menu);
string fname = null;
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") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
ntop.Add (win);
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);
Application.Run (ntop);
}
static bool Quit ()
{
var n = MessageBox.Query (50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No");
return n == 0;
}
static void Close ()
{
MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok");
}
// Watch what happens when I try to introduce a newline after the first open brace
// it introduces a new brace instead, and does not indent. Then watch me fight
// the editor as more oddities happen.
public static void Open ()
{
var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true };
Application.Run (d);
if (!d.Canceled)
MessageBox.Query (50, 7, "Selected File", string.Join (", ", d.FilePaths), "Ok");
}
public static void ShowHex (Toplevel top)
{
var tframe = top.Frame;
var ntop = new Toplevel (tframe);
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
}),
});
ntop.Add (menu);
var win = new Window ("/etc/passwd") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
ntop.Add (win);
var source = System.IO.File.OpenRead ("/etc/passwd");
var hex = new HexView (source) {
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
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
static void ListSelectionDemo (bool multiple)
{
var d = new Dialog ("Selection Demo", 60, 20,
new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
var animals = new List<string> () { "Alpaca", "Llama", "Lion", "Shark", "Goat" };
var msg = new Label ("Use space bar or control-t to toggle selection") {
X = 1,
Y = 1,
Width = Dim.Fill () - 1,
Height = 1
};
var list = new ListView (animals) {
X = 1,
Y = 3,
Width = Dim.Fill () - 4,
Height = Dim.Fill () - 4,
AllowsMarking = true,
AllowsMultipleSelection = multiple
};
d.Add (msg, list);
Application.Run (d);
var result = "";
for (int i = 0; i < animals.Count; i++) {
if (list.Source.IsMarked (i)) {
result += animals [i] + " ";
}
}
MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok");
}
#endregion
#region OnKeyDown / OnKeyUp Demo
private static void OnKeyDownUpDemo ()
{
var container = new Dialog (
"OnKeyDown & OnKeyUp demo", 80, 20,
new Button ("Close") { Clicked = () => { Application.RequestStop (); } }) {
Width = Dim.Fill (),
Height = Dim.Fill (),
};
var list = new List<string> ();
var listView = new ListView (list) {
X = 0,
Y = 0,
Width = Dim.Fill () - 1,
Height = Dim.Fill () - 2,
};
listView.ColorScheme = Colors.TopLevel;
container.Add (listView);
void KeyUpDown (KeyEvent keyEvent, string updown)
{
if ((keyEvent.Key & Key.CtrlMask) != 0) {
list.Add ($"Key{updown,-4}: Ctrl ");
} else if ((keyEvent.Key & Key.AltMask) != 0) {
list.Add ($"Key{updown,-4}: Alt ");
} else {
list.Add ($"Key{updown,-4}: {(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}");
}
listView.MoveDown ();
}
container.OnKeyDown += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Down");
container.OnKeyUp += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Up");
Application.Run (container);
}
#endregion
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;
//Open ();
#if true
int margin = 3;
var win = new Window ("Hello") {
X = 1,
Y = 1,
Width = Dim.Fill () - margin,
Height = Dim.Fill () - margin
};
#else
var tframe = top.Frame;
var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height - 1), "Hello");
#endif
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", "", 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 MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)),
new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)),
}),
new MenuBarItem ("A_ssorted", new MenuItem [] {
new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()),
new MenuItem ("_OnKeyDown/Up", "", () => OnKeyDownUpDemo ())
}),
new MenuBarItem ("_Test Menu and SubMenus", new MenuItem [] {
new MenuItem ("SubMenu1Item_1",
new MenuBarItem (new MenuItem[] {
new MenuItem ("SubMenu2Item_1",
new MenuBarItem (new MenuItem [] {
new MenuItem ("SubMenu3Item_1",
new MenuBarItem (new MenuItem [] { menuItems [2] })
)
})
)
})
)
}),
new MenuBarItem ("_About...", "Demonstrates top-level menu item", () => MessageBox.ErrorQuery (50, 7, "About Demo", "This is a demo app for gui.cs", "Ok")),
});
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: ");
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);
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", () => { if (Quit ()) top.Running = false; }),
}) {
Parent = null,
};
win.Add (drag, dragText);
#if true
// FIXED: This currently causes a stack overflow, because it is referencing a window that has not had its size allocated yet
var bottom = new Label ("This should go on the bottom of the same top-level!");
win.Add (bottom);
var bottom2 = new Label ("This should go on the bottom of another top-level!");
top.Add (bottom2);
Application.OnLoad = () => {
bottom.X = win.X;
bottom.Y = Pos.Bottom (win) - Pos.Top (win) - margin;
bottom2.X = Pos.Left (win);
bottom2.Y = Pos.Bottom (win);
};
#endif
top.Add (win);
//top.Add (menu);
top.Add (menu, statusBar);
Application.Run ();
}
}

View File

@@ -1066,7 +1066,7 @@ namespace Terminal.Gui {
}
/// <summary>
/// Invoked when a character key is pressed and occurs after the key down event.
/// Invoked when a character key is pressed and occurs after the key up event.
/// </summary>
public Action<KeyEvent> OnKeyPress;
@@ -1083,7 +1083,6 @@ namespace Terminal.Gui {
/// <inheritdoc cref="ProcessHotKey"/>
public override bool ProcessHotKey (KeyEvent keyEvent)
{
OnKeyPress?.Invoke (keyEvent);
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)
@@ -1095,7 +1094,6 @@ namespace Terminal.Gui {
/// <inheritdoc cref="ProcessColdKey"/>
public override bool ProcessColdKey (KeyEvent keyEvent)
{
OnKeyPress?.Invoke (keyEvent);
if (subviews == null || subviews.Count == 0)
return false;
foreach (var view in subviews)

View File

@@ -52,7 +52,7 @@ namespace Terminal.Gui {
if (sync)
Application.Driver.Refresh ();
ccol++;
var runeWidth = Rune.ColumnWidth(rune);
var runeWidth = Rune.ColumnWidth (rune);
if (runeWidth > 1) {
for (int i = 1; i < runeWidth; i++) {
ccol++;
@@ -192,7 +192,7 @@ namespace Terminal.Gui {
};
}
void ProcessInput (Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler)
void ProcessInput (Action<KeyEvent> keyHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
{
int wch;
var code = Curses.get_wch (out wch);
@@ -212,6 +212,7 @@ namespace Terminal.Gui {
return;
}
keyHandler (new KeyEvent (MapCursesKey (wch)));
keyUpHandler (new KeyEvent (MapCursesKey (wch)));
return;
}
@@ -219,7 +220,7 @@ namespace Terminal.Gui {
if (wch == 27) {
Curses.timeout (200);
code = Curses.get_wch (out wch);
code = Curses.get_wch (out int wch2);
if (code == Curses.KEY_CODE_YES)
keyHandler (new KeyEvent (Key.AltMask | MapCursesKey (wch)));
if (code == 0) {
@@ -227,23 +228,28 @@ namespace Terminal.Gui {
// The ESC-number handling, debatable.
// Simulates the AltMask itself by pressing Alt + Space.
if (wch == (int)Key.Space)
if (wch2 == (int)Key.Space)
key = new KeyEvent (Key.AltMask);
else if (wch - (int)Key.Space >= 'A' && wch - (int)Key.Space <= 'Z')
key = new KeyEvent ((Key)((uint)Key.AltMask + (wch - (int)Key.Space)));
else if (wch >= '1' && wch <= '9')
key = new KeyEvent ((Key)((int)Key.F1 + (wch - '0' - 1)));
else if (wch == '0')
else if (wch2 - (int)Key.Space >= 'A' && wch2 - (int)Key.Space <= 'Z')
key = new KeyEvent ((Key)((uint)Key.AltMask + (wch2 - (int)Key.Space)));
else if (wch2 >= '1' && wch <= '9')
key = new KeyEvent ((Key)((int)Key.F1 + (wch2 - '0' - 1)));
else if (wch2 == '0')
key = new KeyEvent (Key.F10);
else if (wch == 27)
key = new KeyEvent ((Key)wch);
else if (wch2 == 27)
key = new KeyEvent ((Key)wch2);
else
key = new KeyEvent (Key.AltMask | (Key)wch);
key = new KeyEvent (Key.AltMask | (Key)wch2);
keyHandler (key);
} else
} else {
keyHandler (new KeyEvent (Key.Esc));
} else
}
} else {
keyHandler (new KeyEvent ((Key)wch));
}
// Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above
// will not impact KeyUp.
keyUpHandler (new KeyEvent ((Key)wch));
}
public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
@@ -252,7 +258,7 @@ namespace Terminal.Gui {
Curses.timeout (-1);
(mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => {
ProcessInput (keyHandler, mouseHandler);
ProcessInput (keyHandler, keyUpHandler, mouseHandler);
return true;
});
@@ -314,7 +320,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.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);

View File

@@ -328,6 +328,7 @@ namespace Terminal.Gui {
if (map == (Key)0xffffffff)
return;
keyHandler (new KeyEvent (map));
keyUpHandler (new KeyEvent (map));
};
}

View File

@@ -440,15 +440,10 @@ 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 ();
SetupColorsAndBorders ();
cols = Console.WindowWidth;
rows = Console.WindowHeight;
WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
@@ -459,6 +454,54 @@ namespace Terminal.Gui {
Task.Run ((Action)WindowsInputHandler);
}
private void SetupColorsAndBorders ()
{
Colors.TopLevel = new ColorScheme ();
Colors.Base = new ColorScheme ();
Colors.Dialog = new ColorScheme ();
Colors.Menu = new ColorScheme ();
Colors.Error = 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.DarkBlue, ConsoleColor.DarkCyan);
Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue);
Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray);
Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray);
Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray);
Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
HLine = '\u2500';
VLine = '\u2502';
Stipple = '\u2592';
Diamond = '\u25c6';
ULCorner = '\u250C';
LLCorner = '\u2514';
URCorner = '\u2510';
LRCorner = '\u2518';
LeftTee = '\u251c';
RightTee = '\u2524';
TopTee = '\u22a4';
BottomTee = '\u22a5';
}
[StructLayout (LayoutKind.Sequential)]
public struct ConsoleKeyInfoEx {
public ConsoleKeyInfo consoleKeyInfo;
@@ -564,11 +607,24 @@ namespace Terminal.Gui {
case WindowsConsole.EventType.Key:
var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
if (map == (Key)0xffffffff) {
KeyEvent key = default;
KeyEvent key = new KeyEvent ();
// Shift = VK_SHIFT = 0x10
// Ctrl = VK_CONTROL = 0x11
// Alt = VK_MENU = 0x12
if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.CapslockOn)) {
inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.CapslockOn;
}
if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ScrolllockOn)) {
inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.ScrolllockOn;
}
if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.NumlockOn)) {
inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.NumlockOn;
}
switch (inputEvent.KeyEvent.dwControlKeyState) {
case WindowsConsole.ControlKeyState.RightAltPressed:
case WindowsConsole.ControlKeyState.RightAltPressed |
@@ -617,10 +673,10 @@ namespace Terminal.Gui {
keyUpHandler (key);
} else {
if (inputEvent.KeyEvent.bKeyDown) {
// Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event
keyDownHandler (new KeyEvent (map));
keyHandler (new KeyEvent (map));
} else {
// Key Up - Fire KeyDown Event and KeyStroke (ProcessKey) Event
keyHandler (new KeyEvent (map));
keyUpHandler (new KeyEvent (map));
}
}
@@ -918,6 +974,9 @@ namespace Terminal.Gui {
case ConsoleKey.OemComma:
case ConsoleKey.OemPlus:
case ConsoleKey.OemMinus:
if (keyInfo.KeyChar == 0)
return Key.Unknown;
return (Key)((uint)keyInfo.KeyChar);
}
@@ -971,48 +1030,10 @@ namespace Terminal.Gui {
public override void Init (Action terminalResized)
{
TerminalResized = terminalResized;
Colors.Base = new ColorScheme ();
Colors.Dialog = new ColorScheme ();
Colors.Menu = new ColorScheme ();
Colors.Error = new ColorScheme ();
HLine = '\u2500';
VLine = '\u2502';
Stipple = '\u2592';
Diamond = '\u25c6';
ULCorner = '\u250C';
LLCorner = '\u2514';
URCorner = '\u2510';
LRCorner = '\u2518';
LeftTee = '\u251c';
RightTee = '\u2524';
TopTee = '\u22a4';
BottomTee = '\u22a5';
Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue);
Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
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.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);
Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan);
Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red);
Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red);
Colors.Error.HotFocus = Colors.Error.HotNormal;
Console.Clear ();
SetupColorsAndBorders ();
}
void ResizeScreen ()
{
OutputBuffer = new WindowsConsole.CharInfo [Rows * Cols];

View File

@@ -45,7 +45,7 @@ namespace Terminal.Gui {
ControlSpace = 0,
/// <summary>
/// The key code for the user pressing Control-A
/// The key code for the user pressing Control-A
/// </summary>
ControlA = 1,
/// <summary>
@@ -288,8 +288,7 @@ namespace Terminal.Gui {
/// <summary>
/// Describes a keyboard event.
/// </summary>
public struct KeyEvent {
public class KeyEvent {
/// <summary>
/// Symb olid definition for the key.
/// </summary>
@@ -321,6 +320,10 @@ namespace Terminal.Gui {
//public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
public bool IsCtrl => (Key & Key.CtrlMask) != 0;
public KeyEvent ()
{
Key = Key.Unknown;
}
/// <summary>
/// Constructs a new KeyEvent from the provided Key value - can be a rune cast into a Key value
/// </summary>
@@ -328,6 +331,28 @@ namespace Terminal.Gui {
{
Key = k;
}
public override string ToString ()
{
string msg = "";
var key = this.Key;
if ((this.Key & Key.ShiftMask) != 0) {
msg += "Shift-";
}
if ((this.Key & Key.CtrlMask) != 0) {
msg += "Ctrl-";
}
if ((this.Key & Key.AltMask) != 0) {
msg += "Alt-";
}
if (string.IsNullOrEmpty (msg)) {
msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"{key}")}";
} else {
msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"")}";
}
return msg;
}
}
/// <summary>
@@ -486,7 +511,7 @@ namespace Terminal.Gui {
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.Gui.MouseEvent"/>.
/// </summary>
/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.Gui.MouseEvent"/>.</returns>
public override string ToString()
public override string ToString ()
{
return $"({X},{Y}:{Flags}";
}

View File

@@ -124,7 +124,7 @@ namespace Terminal.Gui {
for (int i = 0; i < spaces; i++)
s.Append (' ');
if (extras > 0) {
s.Append ('_');
//s.Append ('_');
extras--;
}
}
@@ -179,8 +179,11 @@ namespace Terminal.Gui {
int x;
switch (textAlignment) {
case TextAlignment.Left:
x = Frame.Left;
break;
case TextAlignment.Justified:
x = 0;
Recalc ();
x = Frame.Left;
break;
case TextAlignment.Right:
x = Frame.Right - str.Length;

View File

@@ -294,6 +294,11 @@ namespace Terminal.Gui {
/// </summary>
public event Action SelectedChanged;
/// <summary>
/// This event is raised on Enter key or Double Click to open the selected item.
/// </summary>
public event EventHandler OpenSelectedItem;
/// <summary>
/// Handles cursor movement for this view, passes all other events.
/// </summary>
@@ -325,6 +330,11 @@ namespace Terminal.Gui {
return true;
else
break;
case Key.Enter:
OpenSelectedItem?.Invoke (this, new EventArgs ());
break;
}
return base.ProcessKey (kb);
}
@@ -451,7 +461,7 @@ namespace Terminal.Gui {
///<inheritdoc cref="MouseEvent(Gui.MouseEvent)"/>
public override bool MouseEvent(MouseEvent me)
{
if (!me.Flags.HasFlag (MouseFlags.Button1Clicked))
if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
return false;
if (!HasFocus)
@@ -469,9 +479,10 @@ namespace Terminal.Gui {
SetNeedsDisplay ();
return true;
}
if (SelectedChanged != null)
SelectedChanged();
SelectedChanged?.Invoke ();
SetNeedsDisplay ();
if (me.Flags == MouseFlags.Button1DoubleClicked)
OpenSelectedItem?.Invoke (this, new EventArgs ());
return true;
}
}

View File

@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UICatalog", "UICatalog\UICatalog.csproj", "{88979F89-9A42-448F-AE3E-3060145F6375}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
@@ -25,6 +27,14 @@ Global
{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.Build.0 = Debug|x86
{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.ActiveCfg = Release|x86
{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.Build.0 = Release|x86
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.ActiveCfg = Debug|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.Build.0 = Debug|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.ActiveCfg = Release|Any CPU
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0

23
UICatalog/.editorconfig Normal file
View File

@@ -0,0 +1,23 @@
[*.cs]
indent_style = tab
indent_size = 8
tab_width = 8
csharp_new_line_before_open_brace = methods,local_functions
csharp_new_line_before_else = false
csharp_new_line_before_catch = false
csharp_new_line_before_finally = false
end_of_line = crlf
csharp_indent_case_contents = true
csharp_indent_switch_labels = false
csharp_indent_labels = flush_left
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_method_call_parameter_list_parentheses = false
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

9
UICatalog/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
###############
# folder #
###############
/**/DROP/
/**/TEMP/
/**/packages/
/**/bin/
/**/obj/
_site

271
UICatalog/Program.cs Normal file
View File

@@ -0,0 +1,271 @@
using NStack;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Terminal.Gui;
namespace UICatalog {
/// <summary>
/// Main program for the Terminal.gui UI Catalog app. This app provides a chooser that allows
/// for a calalog of UI demos, examples, and tests.
/// </summary>
class Program {
private static Toplevel _top;
private static MenuBar _menu;
private static int _nameColumnWidth;
private static Window _leftPane;
private static List<string> _categories;
private static ListView _categoryListView;
private static Window _rightPane;
private static List<Type> _scenarios;
private static ListView _scenarioListView;
private static StatusBar _statusBar;
private static Scenario _runningScenario = null;
static void Main (string [] args)
{
if (Debugger.IsAttached)
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
_scenarios = Scenario.GetDerivedClassesCollection ().ToList ();
if (args.Length > 0) {
var item = _scenarios.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals (args [0], StringComparison.OrdinalIgnoreCase));
_runningScenario = (Scenario)Activator.CreateInstance (_scenarios [item]);
Application.Init ();
_runningScenario.Init (Application.Top);
_runningScenario.Setup ();
_runningScenario.Run ();
_runningScenario = null;
return;
}
Scenario scenario = GetScenarioToRun ();
while (scenario != null) {
Application.Init ();
scenario.Init (Application.Top);
scenario.Setup ();
scenario.Run ();
scenario = GetScenarioToRun ();
}
}
/// <summary>
/// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs.
/// </summary>
private static void Setup ()
{
_menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Quit", "", () => Application.RequestStop() )
}),
new MenuBarItem ("_About...", "About this app", () => MessageBox.Query (0, 6, "About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok")),
});
_leftPane = new Window ("Categories") {
X = 0,
Y = 1, // for menu
Width = 25,
Height = Dim.Fill (),
CanFocus = false,
};
_categories = Scenario.GetAllCategories ();
_categoryListView = new ListView (_categories) {
X = 1,
Y = 0,
Width = Dim.Fill (0),
Height = Dim.Fill (2),
AllowsMarking = false,
CanFocus = true,
};
_categoryListView.OpenSelectedItem += (o, a) => {
_top.SetFocus (_rightPane);
};
_categoryListView.SelectedChanged += CategoryListView_SelectedChanged;
_leftPane.Add (_categoryListView);
_rightPane = new Window ("Scenarios") {
X = 25,
Y = 1, // for menu
Width = Dim.Fill (),
Height = Dim.Fill (),
CanFocus = false,
};
_nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length;
_scenarioListView = new ListView () {
X = 0,
Y = 0,
Width = Dim.Fill (0),
Height = Dim.Fill (0),
AllowsMarking = false,
CanFocus = true,
};
//_scenarioListView.OnKeyPress += (KeyEvent ke) => {
// if (_top.MostFocused == _scenarioListView && ke.Key == Key.Enter) {
// _scenarioListView_OpenSelectedItem (null, null);
// }
//};
_scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
_rightPane.Add (_scenarioListView);
_categoryListView.SelectedItem = 0;
CategoryListView_SelectedChanged ();
_statusBar = new StatusBar (new StatusItem [] {
//new StatusItem(Key.F1, "~F1~ Help", () => Help()),
new StatusItem(Key.ControlQ, "~CTRL-Q~ Quit", () => {
if (_runningScenario is null){
// This causes GetScenarioToRun to return null
_runningScenario = null;
Application.RequestStop();
} else {
_runningScenario.RequestStop();
}
}),
});
}
/// <summary>
/// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything.
/// </summary>
/// <returns></returns>
private static Scenario GetScenarioToRun ()
{
Application.Init ();
if (_menu == null) {
Setup ();
}
_top = Application.Top;
_top.OnKeyUp += KeyUpHandler;
_top.Add (_menu);
_top.Add (_leftPane);
_top.Add (_rightPane);
_top.Add (_statusBar);
// HACK: There is no other way to SetFocus before Application.Run. See Issue #445
#if false
if (_runningScenario != null)
Application.Iteration += Application_Iteration;
#else
_top.Ready += (o, a) => {
if (_runningScenario != null) {
_top.SetFocus (_rightPane);
_runningScenario = null;
}
};
#endif
Application.Run (_top);
return _runningScenario;
}
#if false
private static void Application_Iteration (object sender, EventArgs e)
{
Application.Iteration -= Application_Iteration;
_top.SetFocus (_rightPane);
}
#endif
private static void _scenarioListView_OpenSelectedItem (object sender, EventArgs e)
{
if (_runningScenario is null) {
var source = _scenarioListView.Source as ScenarioListDataSource;
_runningScenario = (Scenario)Activator.CreateInstance (source.Scenarios [_scenarioListView.SelectedItem]);
Application.RequestStop ();
}
}
internal class ScenarioListDataSource : IListDataSource {
public List<Type> Scenarios { get; set; }
public bool IsMarked (int item) => false;// Scenarios [item].IsMarked;
public int Count => Scenarios.Count;
public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
{
container.Move (col, line);
// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
}
public void SetMark (int item, bool value)
{
}
// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
{
int used = 0;
int index = 0;
while (index < ustr.Length) {
(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
var count = Rune.ColumnWidth (rune);
if (used + count >= width) break;
driver.AddRune (rune);
used += count;
index += size;
}
while (used < width) {
driver.AddRune (' ');
used++;
}
}
}
/// <summary>
/// When Scenarios are running we need to override the behavior of the Menu
/// and Statusbar to enable Scenarios that use those (or related key input)
/// to not be impacted. Same as for tabs.
/// </summary>
/// <param name="ke"></param>
private static void KeyUpHandler (KeyEvent ke)
{
if (_runningScenario != null) {
//switch (ke.Key) {
//case Key.Esc:
// //_runningScenario.RequestStop ();
// break;
//case Key.Enter:
// break;
//}
} else if (ke.Key == Key.Tab || ke.Key == Key.BackTab) {
// BUGBUG: Work around Issue #434 by implementing our own TAB navigation
if (_top.MostFocused == _categoryListView)
_top.SetFocus (_rightPane);
else
_top.SetFocus (_leftPane);
}
}
private static void CategoryListView_SelectedChanged ()
{
var item = _categories [_categoryListView.SelectedItem];
List<Type> newlist;
if (item.Equals ("All")) {
newlist = _scenarios;
} else {
newlist = _scenarios.Where (t => Scenario.ScenarioCategory.GetCategories (t).Contains (item)).ToList ();
}
_scenarioListView.Source = new ScenarioListDataSource (newlist);
_scenarioListView.SelectedItem = 0;
}
}
}

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"UICatalog": {
"commandName": "Project",
"commandLineArgs": "HexEditor"
}
}
}

122
UICatalog/README.md Normal file
View File

@@ -0,0 +1,122 @@
# Terminal.Gui UI Catalog
UI Catalog is a comprehensive sample library for Terminal.Gui. It attempts to satisfy the following goals:
1. Be an easy to use showcase for Terminal.Gui concepts and features.
2. Provide sample code that illustrates how to properly implement said concepts & features.
3. Make it easy for contributors to add additional samples in a structured way.
![screenshot](screenshot.png)
## Motivation
The original `demo.cs` sample app for Terminal.Gui is neither good to showcase, nor does it explain different concepts. In addition, because it is built on a single source file, it has proven to cause friction when multiple contributors are simultaneously working on different aspects of Terminal.Gui. See [Issue #368](https://github.com/migueldeicaza/Terminal.Gui/issues/368) for more background.
## How To Use
`Program.cs` is the main app and provides a UI for selecting and running **Scenarios**. Each **Scenario* is implemented as a class derived from `Scenario` and `Program.cs` uses reflection to dynamically build the UI.
**Scenarios** are tagged with categories using the `[ScenarioCategory]` attribute. The left pane of the main screen lists the categories. Clicking on a category shows all the scenarios in that category.
**Scenarios** can be run either from the **UICatalog.exe** app UI or by being specified on the command line:
```
UICatalog.exe <Scenario Name>
```
e.g.
```
UICatalog.exe Buttons
```
When a **Scenario** is run, it runs as though it were a standalone `Terminal.Gui` app. However, scaffolding is provided (in the `Scenario` base class) that (optionally) takes care of `Terminal.Gui` initialization.
## Contributing by Adding Scenarios
To add a new **Scenario** simply:
1. Create a new `.cs` file in the `Scenarios` directory that derives from `Scenario`.
2. Add a `[ScenarioMetaData]` attribute to the class specifying the scenario's name and description.
3. Add one or more `[ScenarioCategory]` attributes to the class specifying which categories the sceanrio belongs to. If you don't specify a category the sceanrio will show up in "All".
4. Implement the `Setup` override which will be called when a user selects the scenario to run.
5. Optionally, implement the `Init` and/or `Run` overrides to provide a custom implementation.
The sample below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
```csharp
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
[ScenarioCategory ("Controls")]
class MyScenario : Scenario {
public override void Setup ()
{
// Put your scenario code here, e.g.
Win.Add (new Button ("Press me!") {
X = Pos.Center (),
Y = Pos.Center (),
Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
});
}
}
}
```
`Scenario` provides a `Toplevel` and `Window` the provides a canvas for the Scenario to operate. The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key.
![screenshot](generic_screenshot.png)
To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply set the `Top` and `Window` properties as appropriate, as seen in the `UnicodeInMenu` scenario:
```csharp
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")]
[ScenarioCategory ("Text")]
[ScenarioCategory ("Controls")]
class UnicodeInMenu : Scenario {
public override void Setup ()
{
Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_Файл", new MenuItem [] {
new MenuItem ("_Создать", "Creates new file", null),
new MenuItem ("_Открыть", "", null),
new MenuItem ("Со_хранить", "", null),
new MenuItem ("_Выход", "", () => Application.RequestStop() )
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", null),
new MenuItem ("C_ut", "", null),
new MenuItem ("_Paste", "", null)
})
});
Top.Add (menu);
Win = new Window ($"Scenario: {GetName ()}") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Top.Add (Win);
}
}
}
```
For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` assigns `Application.Top` to `Top` and creates `Win`. The `base.Run` simply calls `Application.Run(Top)`.
## Contribution Guidelines
- Provide a terse, descriptive name for `Scenarios`. Keep them short; the `ListView` that displays them dynamically sizes the column width and long names will make it hard for people to use.
- Provide a clear description.
- Comment `Scenario` code to describe to others why it's a useful `Scenario`.
- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Try to minimize the number of new categories created.
- Use the `Bug Rero` Category for `Scnarios` that reproduce bugs.
- Include the Github Issue # in the Description.
- Once the bug has been fixed in `master` submit another PR to remove the `Scenario` (or modify it to provide a good regression test).
- Tag bugs or suggestions for `UI Catalog` in the main `Terminal.Gui` Github Issues with "UICatalog: ".

181
UICatalog/Scenario.cs Normal file
View File

@@ -0,0 +1,181 @@
using NStack;
using System;
using System.Collections.Generic;
using System.Linq;
using Terminal.Gui;
namespace UICatalog {
/// <summary>
/// Base class for each demo/scenario. To define a new sceanrio simply
///
/// 1) declare a class derived from Scenario,
/// 2) Set Name and Description as appropriate using [ScenarioMetadata] attribute
/// 3) Set one or more categories with the [ScenarioCategory] attribute
/// 4) Implement Setup.
/// 5) Optionally, implement Run.
///
/// The Main program uses reflection to find all sceanarios and adds them to the
/// ListViews. Press ENTER to run the selected sceanrio. Press CTRL-Q to exit it.
/// </summary>
public class Scenario {
/// <summary>
/// The Top level for the Scenario. This should be set to `Application.Top` in most cases.
/// </summary>
public Toplevel Top { get; set; }
/// <summary>
/// </summary>
public Window Win { get; set; }
/// <summary>
/// Helper that provides the default Window implementation with a frame and
/// label showing the name of the Scenario and logic to exit back to
/// the Scenario picker UI.
/// Override Init to provide any `Toplevel` behavior needed.
/// </summary>
/// <param name="top"></param>
public virtual void Init(Toplevel top)
{
Top = top;
Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Top.Add (Win);
}
[System.AttributeUsage (System.AttributeTargets.Class)]
public class ScenarioMetadata : System.Attribute {
/// <summary>
/// Scenario Name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Scenario Description
/// </summary>
public string Description { get; set; }
public ScenarioMetadata (string Name, string Description)
{
this.Name = Name;
this.Description = Description;
}
/// <summary>
/// Static helper function to get the Scenario Name given a Type
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public static string GetName (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Name;
/// <summary>
/// Static helper function to get the Scenario Description given a Type
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public static string GetDescription (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Description;
}
/// <summary>
/// Helper to get the Scenario Name
/// </summary>
/// <returns></returns>
public string GetName () => ScenarioMetadata.GetName (this.GetType ());
/// <summary>
/// Helper to get the Scenario Descripiton
/// </summary>
/// <returns></returns>
public string GetDescription () => ScenarioMetadata.GetDescription (this.GetType ());
[System.AttributeUsage (System.AttributeTargets.Class, AllowMultiple = true)]
public class ScenarioCategory : System.Attribute {
/// <summary>
/// Category Name
/// </summary>
public string Name { get; set; }
public ScenarioCategory (string Name) => this.Name = Name;
/// <summary>
/// Static helper function to get the Scenario Name given a Type
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public static string GetName (Type t) => ((ScenarioCategory)System.Attribute.GetCustomAttributes (t) [0]).Name;
/// <summary>
/// Static helper function to get the Scenario Categories given a Type
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public static List<string> GetCategories (Type t) => System.Attribute.GetCustomAttributes (t)
.ToList ()
.Where (a => a is ScenarioCategory)
.Select (a => ((ScenarioCategory)a).Name)
.ToList ();
}
/// <summary>
/// Helper function to get the Categories of a Scenario
/// </summary>
/// <returns></returns>
public List<string> GetCategories () => ScenarioCategory.GetCategories (this.GetType ());
public override string ToString () => $"{GetName (),-30}{GetDescription ()}";
/// <summary>
/// Override this to implement the Scenario setup logic (create controls, etc...).
/// </summary>
public virtual void Setup ()
{
}
/// <summary>
/// Runs the scenario. Override to start the scearnio using a Top level different than `Top`.
/// </summary>
public virtual void Run ()
{
Application.Run (Top);
}
/// <summary>
/// Stops the scenario. Override to implement shutdown behavior for the Scenario.
/// </summary>
public virtual void RequestStop ()
{
Application.RequestStop ();
}
/// <summary>
/// Returns a list of all Categories set by all of the scenarios defined in the project.
/// </summary>
internal static List<string> GetAllCategories ()
{
List<string> categories = new List<string> () { "All" };
foreach (Type type in typeof (Scenario).Assembly.GetTypes ()
.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
List<System.Attribute> attrs = System.Attribute.GetCustomAttributes (type).ToList ();
categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList ();
}
return categories;
}
/// <summary>
/// Returns an instance of each Scenario defined in the project.
/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
/// </summary>
internal static List<Type> GetDerivedClassesCollection ()
{
List<Type> objects = new List<Type> ();
foreach (Type type in typeof (Scenario).Assembly.GetTypes ()
.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
objects.Add (type);
}
return objects;
}
}
}

View File

@@ -0,0 +1,105 @@
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Layout")]
class Buttons : Scenario {
public override void Setup ()
{
// Add a label & text field so we can demo IsDefault
var editLabel = new Label ("TextField (to demo IsDefault):") {
X = 0,
Y = 0,
};
Win.Add (editLabel);
var edit = new TextField ("") {
X = Pos.Right (editLabel) + 1,
Y = Pos.Top (editLabel),
Width = Dim.Fill (2),
};
Win.Add (edit);
// This is the default button (IsDefault = true); if user presses ENTER in the TextField
// the scenario will quit
var defaultButton = new Button ("Quit") {
X = Pos.Center (),
// BUGBUG: Throws an exception
//Y= Pos.Bottom(Win),
Y = 20,
IsDefault = true,
Clicked = () => Application.RequestStop (),
};
Win.Add (defaultButton);
var y = 2;
var button = new Button (10, y, "Base Color") {
ColorScheme = Colors.Base,
Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
};
Win.Add (button);
y += 2;
Win.Add (new Button (10, y, "Error Color") {
ColorScheme = Colors.Error,
Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
});
y += 2;
Win.Add (new Button (10, y, "Dialog Color") {
ColorScheme = Colors.Dialog,
Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
});
y += 2;
Win.Add (new Button (10, y, "Menu Color") {
ColorScheme = Colors.Menu,
Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
});
y += 2;
Win.Add (new Button (10, y, "TopLevel Color") {
ColorScheme = Colors.TopLevel,
Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
});
y += 2;
Win.Add (new Button (10, y, "A super long button that will probably expose a bug in clipping or wrapping of text. Will it?") {
Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
});
y += 2;
// Note the 'N' in 'Newline' will be the hotkey
Win.Add (new Button (10, y, "a Newline\nin the button") {
Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
});
y += 2;
// BUGBUG: Buttons don't support specifying hotkeys with _?!?
Win.Add (button = new Button (10, y, "Te_xt Changer") {
});
button.Clicked = () => button.Text += $"{y++}";
Win.Add (new Button ("Lets see if this will move as \"Text Changer\" grows") {
X = Pos.Right(button) + 10,
Y = y,
});
y += 2;
Win.Add (new Button (10, y, "Delete") {
ColorScheme = Colors.Error,
Clicked = () => Win.Remove (button)
});
y += 2;
Win.Add (new Button (10, y, "Change Default") {
Clicked = () => {
defaultButton.IsDefault = !defaultButton.IsDefault;
button.IsDefault = !button.IsDefault;
},
});
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Terminal.Gui;
namespace UICatalog {
/// <summary>
/// This Scenario demonstrates how to use Termina.gui's Dim and Pos Layout System.
/// [x] - Using Dim.Fill to fill a window
/// [x] - Using Dim.Fill and Dim.Pos to automatically align controls based on an initial control
/// [ ] - ...
/// </summary>
[ScenarioMetadata (Name: "DimAndPosLayout", Description: "Demonstrates using the Dim and Pos Layout System")]
[ScenarioCategory ("Layout")]
class DimAndPosLayout : Scenario {
public override void Setup ()
{
Top.LayoutStyle = LayoutStyle.Computed;
// Demonstrate using Dim to create a ruler that always measures the top-level window's width
// BUGBUG: Dim.Fill returns too big a value sometimes.
//const string rule = "|123456789";
//var labelRuler = new Label ("ruler") {
// X = 0,
// Y = 0,
// Width = Dim.Fill (1), // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does.
// ColorScheme = Colors.Error
//};
//Application.OnResized += () => {
// labelRuler.Text = rule.Repeat ((int)Math.Ceiling((double)(labelRuler.Bounds.Width) / (double)rule.Length))[0..(labelRuler.Bounds.Width)];
//};
//win.Add (labelRuler);
// Demonstrate using Dim to create a window that fills the parent with a margin
int margin = 20;
var subWin = new Window ($"Sub Windoww with {margin} character margin") {
X = margin,
Y = 2,
Width = Dim.Fill (margin),
Height = Dim.Fill ()
};
Win.Add (subWin);
int i = 1;
string txt = "Hello world, how are you doing today";
var labelList = new List<Label> ();
labelList.Add (new Label ($"Label:"));
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
subWin.Add (labelList.ToArray ());
//subWin.LayoutSubviews ();
}
public override void Run ()
{
base.Run ();
}
}
public static class StringExtensions {
public static string Repeat (this string instr, int n)
{
if (n <= 0) {
return null;
}
if (string.IsNullOrEmpty (instr) || n == 1) {
return instr;
}
return new StringBuilder (instr.Length * n)
.Insert (0, instr, n)
.ToString ();
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Text;
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Editor", Description: "A Terminal.Gui Text Editor via TextView")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Text")]
class Editor : Scenario {
private string _fileName = "demo.txt";
private TextView _textView;
private bool _saved = true;
public override void Init (Toplevel top)
{
Top = top;
}
public override void Setup ()
{
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_New", "", () => New()),
new MenuItem ("_Open", "", () => Open()),
new MenuItem ("_Save", "", () => Save()),
null,
new MenuItem ("_Quit", "", () => Quit()),
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", () => Copy()),
new MenuItem ("C_ut", "", () => Cut()),
new MenuItem ("_Paste", "", () => Paste())
}),
});
Top.Add (menu);
var statusBar = new StatusBar (new StatusItem [] {
new StatusItem(Key.F2, "~F2~ Open", () => Open()),
new StatusItem(Key.F3, "~F3~ Save", () => Save()),
new StatusItem(Key.ControlQ, "~^Q~ Quit", () => Quit()),
});
Top.Add (statusBar);
CreateDemoFile (_fileName);
Win = new Window (_fileName ?? "Untitled") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Top.Add (Win);
_textView = new TextView () {
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill (),
};
LoadFile ();
Win.Add (_textView);
}
private void New ()
{
Win.Title = _fileName = "Untitled";
throw new NotImplementedException ();
}
private void LoadFile ()
{
if (!_saved) {
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
}
if (_fileName != null) {
// BUGBUG: #452 TextView.LoadFile keeps file open and provides no way of closing it
//_textView.LoadFile(_fileName);
_textView.Text = System.IO.File.ReadAllText (_fileName);
Win.Title = _fileName;
_saved = true;
}
}
private void Paste ()
{
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
}
private void Cut ()
{
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
}
private void Copy ()
{
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
//if (_textView != null && _textView.SelectedLength != 0) {
// _textView.Copy ();
//}
}
private void Open ()
{
var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = false };
Application.Run (d);
if (!d.Canceled) {
_fileName = d.FilePaths [0];
LoadFile ();
}
}
private void Save ()
{
if (_fileName != null) {
// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
// As a result files saved on Windows and then read back will show invalid chars.
System.IO.File.WriteAllText (_fileName, _textView.Text.ToString());
_saved = true;
}
}
private void Quit ()
{
Application.RequestStop ();
}
private void CreateDemoFile(string fileName)
{
var sb = new StringBuilder ();
// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
sb.Append ("Hello world.\n");
sb.Append ("This is a test of the Emergency Broadcast System.\n");
var sw = System.IO.File.CreateText (fileName);
sw.Write (sb.ToString ());
sw.Close ();
}
public override void Run ()
{
Application.Run (Top);
}
}
}

View File

@@ -0,0 +1,17 @@
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
[ScenarioCategory ("Controls")]
class MyScenario : Scenario {
public override void Setup ()
{
// Put your scenario code here, e.g.
Win.Add (new Button ("Press me!") {
X = Pos.Center (),
Y = Pos.Center (),
Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
});
}
}
}

View File

@@ -0,0 +1,154 @@
using System;
using System.IO;
using System.Text;
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "HexEditor", Description: "A Terminal.Gui binary (hex) editor via HexView")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Text")]
class HexEditor : Scenario {
private string _fileName = "demo.bin";
private HexView _hexView;
private bool _saved = true;
public override void Init (Toplevel top)
{
Top = top;
}
public override void Setup ()
{
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_New", "", () => New()),
new MenuItem ("_Open", "", () => Open()),
new MenuItem ("_Save", "", () => Save()),
null,
new MenuItem ("_Quit", "", () => Quit()),
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", () => Copy()),
new MenuItem ("C_ut", "", () => Cut()),
new MenuItem ("_Paste", "", () => Paste())
}),
});
Top.Add (menu);
var statusBar = new StatusBar (new StatusItem [] {
//new StatusItem(Key.Enter, "~ENTER~ ApplyEdits", () => { _hexView.ApplyEdits(); }),
new StatusItem(Key.F2, "~F2~ Open", () => Open()),
new StatusItem(Key.F3, "~F3~ Save", () => Save()),
new StatusItem(Key.ControlQ, "~^Q~ Quit", () => Quit()),
});
Top.Add (statusBar);
CreateDemoFile (_fileName);
Win = new Window (_fileName ?? "Untitled") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Top.Add (Win);
_hexView = new HexView (LoadFile()) {
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill (),
};
Win.Add (_hexView);
}
private void New ()
{
Win.Title = _fileName = "Untitled";
throw new NotImplementedException ();
}
private Stream LoadFile ()
{
MemoryStream stream = null;
if (!_saved) {
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
}
if (_fileName != null) {
var bin = System.IO.File.ReadAllBytes (_fileName);
stream = new MemoryStream (bin);
Win.Title = _fileName;
_saved = true;
}
return stream;
}
private void Paste ()
{
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
}
private void Cut ()
{
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
}
private void Copy ()
{
MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
//if (_textView != null && _textView.SelectedLength != 0) {
// _textView.Copy ();
//}
}
private void Open ()
{
var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = false };
Application.Run (d);
if (!d.Canceled) {
_fileName = d.FilePaths [0];
_hexView.Source = LoadFile ();
_hexView.DisplayStart = 0;
}
}
private void Save ()
{
if (_fileName != null) {
using (FileStream fs = new FileStream (_fileName, FileMode.OpenOrCreate)) {
_hexView.ApplyEdits ();
_hexView.Source.CopyTo (fs);
fs.Flush ();
}
_saved = true;
}
}
private void Quit ()
{
Application.RequestStop ();
}
private void CreateDemoFile(string fileName)
{
var sb = new StringBuilder ();
// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
sb.Append ("Hello world.\n");
sb.Append ("This is a test of the Emergency Broadcast System.\n");
var sw = System.IO.File.CreateText (fileName);
sw.Write (sb.ToString ());
sw.Close ();
}
public override void Run ()
{
Application.Run (Top);
}
}
}

181
UICatalog/Scenarios/Keys.cs Normal file
View File

@@ -0,0 +1,181 @@
using NStack;
using System.Collections.Generic;
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Keys", Description: "Shows how to handle keyboard input")]
[ScenarioCategory ("Input")]
class Keys : Scenario {
static List<string> _processKeyList = new List<string> ();
static List<string> _processHotKeyList = new List<string> ();
static List<string> _processColdKeyList = new List<string> ();
class TestWindow : Window {
public TestWindow (ustring title = null) : base (title)
{
}
public TestWindow (Rect frame, ustring title = null) : base (frame, title)
{
}
public TestWindow (ustring title = null, int padding = 0) : base (title, padding)
{
}
public TestWindow (Rect frame, ustring title = null, int padding = 0) : base (frame, title, padding)
{
}
public override bool ProcessKey (KeyEvent keyEvent)
{
_processKeyList.Add (keyEvent.ToString ());
return base.ProcessKey (keyEvent);
}
public override bool ProcessHotKey (KeyEvent keyEvent)
{
_processHotKeyList.Add (keyEvent.ToString ());
return base.ProcessHotKey (keyEvent);
}
public override bool ProcessColdKey (KeyEvent keyEvent)
{
_processColdKeyList.Add (keyEvent.ToString ());
return base.ProcessHotKey (keyEvent);
}
}
public override void Init (Toplevel top)
{
Top = top;
Win = new TestWindow ($"CTRL-Q to Close - Scenario: {GetName ()}") {
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Top.Add (Win);
}
public override void Setup ()
{
// Type text here: ______
var editLabel = new Label ("Type text here:") {
X = 0,
Y = 0,
};
Win.Add (editLabel);
var edit = new TextField ("") {
X = Pos.Right (editLabel) + 1,
Y = Pos.Top (editLabel),
Width = Dim.Fill (2),
};
Win.Add (edit);
// Last KeyPress: ______
var keyPressedLabel = new Label ("Last KeyPress:") {
X = Pos.Left (editLabel),
Y = Pos.Top (editLabel) + 2,
};
Win.Add (keyPressedLabel);
// BUGBUG: Label is not positioning right with Pos, so using TextField instead
var labelKeypress = new TextField ("") {
X = Pos.Right (keyPressedLabel) + 1,
Y = Pos.Top (keyPressedLabel),
Width = 20,
//TextAlignment = Terminal.Gui.TextAlignment.Left,
ColorScheme = Colors.Error,
};
Win.Add (labelKeypress);
Win.OnKeyPress += (KeyEvent keyEvent) => labelKeypress.Text = keyEvent.ToString ();
// Key stroke log:
var keyLogLabel = new Label ("Key stroke log:") {
X = Pos.Left (editLabel),
Y = Pos.Top (editLabel) + 4,
};
Win.Add (keyLogLabel);
var yOffset = (Top == Application.Top ? 1 : 6);
var keyStrokelist = new List<string> ();
var keyStrokeListView = new ListView (keyStrokelist) {
X = 0,
Y = Pos.Top (keyLogLabel) + yOffset,
Width = 25,
Height = Dim.Fill (),
};
keyStrokeListView.ColorScheme = Colors.TopLevel;
Win.Add (keyStrokeListView);
void KeyDownPressUp (KeyEvent keyEvent, string updown)
{
var msg = $"Key{updown,-5}: {keyEvent.ToString ()}";
keyStrokelist.Add (msg);
keyStrokeListView.MoveDown ();
}
Win.OnKeyDown += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Down");
Win.OnKeyPress += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Press");
Win.OnKeyUp += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Up");
// ProcessKey log:
// BUGBUG: Label is not positioning right with Pos, so using TextField instead
var processKeyLogLabel = new Label ("ProcessKey log:") {
X = Pos.Right (keyStrokeListView) + 1,
Y = Pos.Top (editLabel) + 4,
};
Win.Add (processKeyLogLabel);
yOffset = (Top == Application.Top ? 1 : 6);
var processKeyListView = new ListView (_processKeyList) {
X = Pos.Left (processKeyLogLabel),
Y = Pos.Top (processKeyLogLabel) + yOffset,
Width = 25,
Height = Dim.Fill (),
};
processKeyListView.ColorScheme = Colors.TopLevel;
Win.Add (processKeyListView);
// ProcessHotKey log:
// BUGBUG: Label is not positioning right with Pos, so using TextField instead
var processHotKeyLogLabel = new Label ("ProcessHotKey log:") {
X = Pos.Right (processKeyListView) + 1,
Y = Pos.Top (editLabel) + 4,
};
Win.Add (processHotKeyLogLabel);
yOffset = (Top == Application.Top ? 1 : 6);
var processHotKeyListView = new ListView (_processHotKeyList) {
X = Pos.Left (processHotKeyLogLabel),
Y = Pos.Top (processHotKeyLogLabel) + yOffset,
Width = 25,
Height = Dim.Fill (),
};
processHotKeyListView.ColorScheme = Colors.TopLevel;
Win.Add (processHotKeyListView);
// ProcessColdKey log:
// BUGBUG: Label is not positioning right with Pos, so using TextField instead
var processColdKeyLogLabel = new Label ("ProcessColdKey log:") {
X = Pos.Right (processHotKeyListView) + 1,
Y = Pos.Top (editLabel) + 4,
};
Win.Add (processColdKeyLogLabel);
yOffset = (Top == Application.Top ? 1 : 6);
var processColdKeyListView = new ListView (_processColdKeyList) {
X = Pos.Left (processColdKeyLogLabel),
Y = Pos.Top (processColdKeyLogLabel) + yOffset,
Width = 25,
Height = Dim.Fill (),
};
processColdKeyListView.ColorScheme = Colors.TopLevel;
Win.Add (processColdKeyListView);
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "MessageBoxes", Description: "Demonstrates how to use MessageBoxes")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Dialogs")]
[ScenarioCategory ("Bug Repro")]
class MessageBoxes : Scenario {
public override void Setup ()
{
Top = new Toplevel ();
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Quit", "", () => Application.RequestStop() )
}),
new MenuBarItem ("_Simple Query...", "A simple query message box", () => MessageBox.Query (0, 6, "MessageBox.Query", "Minimum size was specified", "Ok")),
new MenuBarItem ("_Error Query...", "A error query message box", () => MessageBox.ErrorQuery (0, 6, "MessageBox.Query", "Minimum size was specified", "Ok")),
// BUGBUG: Illustrates MessageBoxes do not deal with long text gracefully. Issue #432
new MenuBarItem ("_Long Text...", "Demo long text", () => MessageBox.Query (0, 6, "About UI Catalog", "This is a very long title. It is longer than the width of the screen. Will it Wrap? I bet it will not wrap", "Ok")),
});
Top.Add (menu);
Win = new Window ($"Scenario: {GetName ()}") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Top.Add (Win);
}
public override void Run ()
{
Application.Run (Top);
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Mouse", Description: "Demonstrates how to capture mouse events")]
[ScenarioCategory ("Input")]
class Mouse : Scenario {
public override void Setup () {
Label ml;
int count = 0;
ml = new Label (new Rect (1, 1, 50, 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 (1, 2, "Se iniciará el análisis");
Win.Add (test);
Win.Add (ml);
// I have no idea what this was intended to show off in demo.c
var drag = new Label ("Drag: ") { X = 1, Y = 4 };
var dragText = new TextField ("") {
X = Pos.Right (drag),
Y = Pos.Top (drag),
Width = 40
};
Win.Add (drag, dragText);
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Linq;
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Text Alignment", Description: "Demonstrates text alignment")]
[ScenarioCategory ("Text")]
class TextAlignment : Scenario {
public override void Setup ()
{
int i = 1;
string txt = "Hello world, how are you doing today";
var labelList = new List<Label> ();
labelList.Add (new Label ($"Label:"));
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
txt += "\nSecond line";
labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (1), Height = 4, X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
Win.Add (labelList.ToArray ());
Win.LayoutSubviews ();
}
}
}

View File

@@ -0,0 +1,40 @@
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "TopLevelNoWindowBug", Description: "Illustrates that not having a Window causes MenuBar to misbehave. #437")]
[ScenarioCategory ("Bug Repro")]
class TopLevelNoWindowBug : Scenario {
public override void Run ()
{
var ntop = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_Файл", new MenuItem [] {
new MenuItem ("_Создать", "Creates new file", null),
new MenuItem ("_Открыть", "", null),
new MenuItem ("Со_хранить", "", null),
new MenuItem ("_Выход", "", () => ntop.Running = false )
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", null),
new MenuItem ("C_ut", "", null),
new MenuItem ("_Paste", "", null)
})
});
ntop.Add (menu);
// BUGBUG: #437 This being commmented out causes menu to mis-behave
var win = new Window ($"Scenario: {GetName ()}") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
ntop.Add (win);
Application.Run (ntop);
}
}
}

View File

@@ -0,0 +1,35 @@
using Terminal.Gui;
namespace UICatalog {
[ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")]
[ScenarioCategory ("Text")]
[ScenarioCategory ("Controls")]
class UnicodeInMenu : Scenario {
public override void Setup ()
{
Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_Файл", new MenuItem [] {
new MenuItem ("_Создать", "Creates new file", null),
new MenuItem ("_Открыть", "", null),
new MenuItem ("Со_хранить", "", null),
new MenuItem ("_Выход", "", () => Application.RequestStop() )
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", null),
new MenuItem ("C_ut", "", null),
new MenuItem ("_Paste", "", null)
})
});
Top.Add (menu);
Win = new Window ($"Scenario: {GetName ()}") {
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Top.Add (Win);
}
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<StartupObject>UICatalog.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
UICatalog/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB