mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Add menus
This commit is contained in:
19
Core.cs
19
Core.cs
@@ -196,6 +196,7 @@ namespace Terminal {
|
||||
view.container = this;
|
||||
if (view.CanFocus)
|
||||
CanFocus = true;
|
||||
SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
public void Add (params View [] views)
|
||||
@@ -233,11 +234,18 @@ namespace Terminal {
|
||||
if (view == null)
|
||||
return;
|
||||
|
||||
SetNeedsDisplay ();
|
||||
var touched = view.Frame;
|
||||
subviews.Remove (view);
|
||||
view.container = null;
|
||||
|
||||
if (subviews.Count < 1)
|
||||
this.CanFocus = false;
|
||||
|
||||
foreach (var v in subviews) {
|
||||
if (v.Frame.IntersectsWith (touched))
|
||||
view.SetNeedsDisplay ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -375,6 +383,17 @@ namespace Terminal {
|
||||
/// <value>The focused.</value>
|
||||
public View Focused => focused;
|
||||
|
||||
public View MostFocused {
|
||||
get {
|
||||
if (Focused == null)
|
||||
return null;
|
||||
var most = Focused.MostFocused;
|
||||
if (most != null)
|
||||
return most;
|
||||
return Focused;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the specified character in the specified column and row.
|
||||
/// </summary>
|
||||
|
||||
45
Driver.cs
45
Driver.cs
@@ -27,6 +27,14 @@ namespace Terminal {
|
||||
White
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attributes are used as elements that contain both a foreground and a background or platform specific features
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attributes are needed to map colors to terminal capabilities that might lack colors, on color
|
||||
/// scenarios, they encode both the foreground and the background color and are used in the ColorScheme
|
||||
/// class to define color schemes that can be used in your application.
|
||||
/// </remarks>
|
||||
public struct Attribute {
|
||||
internal int value;
|
||||
public Attribute (int v)
|
||||
@@ -38,13 +46,14 @@ namespace Terminal {
|
||||
public static implicit operator Attribute (int v) => new Attribute (v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Color scheme definitions
|
||||
/// </summary>
|
||||
public class ColorScheme {
|
||||
public Attribute Normal;
|
||||
public Attribute Focus;
|
||||
public Attribute HotNormal;
|
||||
public Attribute HotFocus;
|
||||
public Attribute Marked => HotNormal;
|
||||
public Attribute MarkedSelected => HotFocus;
|
||||
}
|
||||
|
||||
public static class Colors {
|
||||
@@ -230,15 +239,28 @@ namespace Terminal {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter.
|
||||
// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
|
||||
if (wch == 27) {
|
||||
Curses.timeout (100);
|
||||
|
||||
code = Curses.get_wch (out wch);
|
||||
if (code == Curses.KEY_CODE_YES)
|
||||
handler.ProcessKey (new KeyEvent (Key.AltMask | MapCursesKey (wch)));
|
||||
if (code == 0)
|
||||
handler.ProcessKey (new KeyEvent (Key.AltMask | (Key)wch));
|
||||
if (code == 0) {
|
||||
KeyEvent key;
|
||||
|
||||
// The ESC-number handling, debatable.
|
||||
if (wch >= '1' && wch <= '9')
|
||||
key = new KeyEvent ((Key)((int)Key.F1 + (wch - '0' - 1)));
|
||||
else if (wch == '0')
|
||||
key = new KeyEvent (Key.F10);
|
||||
else if (wch == 27)
|
||||
key = new KeyEvent ((Key)wch);
|
||||
else
|
||||
key = new KeyEvent (Key.AltMask | (Key)wch);
|
||||
handler.ProcessKey (key);
|
||||
} else
|
||||
handler.ProcessKey (new KeyEvent (Key.Esc));
|
||||
} else
|
||||
handler.ProcessKey (new KeyEvent ((Key)wch));
|
||||
}
|
||||
@@ -310,10 +332,17 @@ namespace Terminal {
|
||||
Colors.Base.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
|
||||
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);
|
||||
Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
|
||||
Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
|
||||
Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
|
||||
|
||||
// Focused,
|
||||
// Selected, Hot: Yellow on Black
|
||||
// Selected, text: white on black
|
||||
// Unselected, hot: yellow on cyan
|
||||
// unselected, text: same as unfocused
|
||||
Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
|
||||
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.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
|
||||
Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
|
||||
Colors.Dialog.HotNormal = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_WHITE);
|
||||
|
||||
28
README.md
Normal file
28
README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Gui.cs - Terminal UI toolkit for .NET
|
||||
|
||||
This is a simple UI toolkit for .NET.
|
||||
|
||||
# Input Handling
|
||||
|
||||
The input handling of gui.cs is similar in some ways to Emacs and the
|
||||
Midnight Commander, so you can expect some of the special key
|
||||
combinations to be active.
|
||||
|
||||
The key ESC can act as an Alt modifier (or Meta in Emacs parlance), to
|
||||
allow input on terminals that do not have an alt key. So to produce
|
||||
the sequence Alt-F, you can press either Alt-F, or ESC folowed by the key F.
|
||||
|
||||
To enter the key ESC, you can either press ESC and wait 100
|
||||
milliseconds, or you can press ESC twice.
|
||||
|
||||
ESC-0, and ESC_1 through ESC-9 have a special meaning, they map to
|
||||
F10, and F1 to F9 respectively.
|
||||
|
||||
# Driver model
|
||||
|
||||
Currently gui.cs is built on top of curses, but the console driver has
|
||||
been abstracted, an implementation that uses `System.Console` is
|
||||
possible, but would have to emulate some of the behavior of curses,
|
||||
namely that operations are performed on the buffer, and the Refresh
|
||||
call reflects the contents of an internal buffer into the screen and
|
||||
position the cursor in the last set position at the end.
|
||||
213
Views/Menu.cs
213
Views/Menu.cs
@@ -1,4 +1,13 @@
|
||||
using System;
|
||||
//
|
||||
// Authors:
|
||||
// Miguel de Icaza (miguel@gnome.org)
|
||||
//
|
||||
// TODO:
|
||||
// Add accelerator support (ShortCut in MenuItem)
|
||||
// Add mouse support
|
||||
// Allow menus inside menus
|
||||
|
||||
using System;
|
||||
namespace Terminal {
|
||||
|
||||
/// <summary>
|
||||
@@ -10,12 +19,31 @@ namespace Terminal {
|
||||
Title = title ?? "";
|
||||
Help = help ?? "";
|
||||
Action = action;
|
||||
Width = Title.Length + Help.Length + 1;
|
||||
bool nextIsHot = false;
|
||||
foreach (var x in title) {
|
||||
if (x == '_')
|
||||
nextIsHot = true;
|
||||
else {
|
||||
if (nextIsHot) {
|
||||
HotKey = 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 char HotKey;
|
||||
public Key ShortCut;
|
||||
|
||||
public string Title { get; set; }
|
||||
public string Help { get; set; }
|
||||
public Action Action { get; set; }
|
||||
public int Width { get; set; }
|
||||
internal int Width => Title.Length + Help.Length + 1 + 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,6 +61,93 @@ namespace Terminal {
|
||||
public int Current { get; set; }
|
||||
}
|
||||
|
||||
class Menu : View {
|
||||
MenuBarItem barItems;
|
||||
MenuBar host;
|
||||
|
||||
static Rect MakeFrame (int x, int y, MenuItem [] items)
|
||||
{
|
||||
int maxW = 0;
|
||||
|
||||
foreach (var item in items) {
|
||||
var l = item.Width;
|
||||
maxW = Math.Max (l, maxW);
|
||||
}
|
||||
|
||||
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;
|
||||
CanFocus = true;
|
||||
}
|
||||
|
||||
public override void Redraw (Rect region)
|
||||
{
|
||||
Driver.SetAttribute (Colors.Menu.Normal);
|
||||
DrawFrame (region, 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 == barItems.Current ? Colors.Menu.Focus : Colors.Menu.Normal);
|
||||
for (int p = 0; p < Frame.Width-2; p++)
|
||||
if (item == null)
|
||||
Driver.AddSpecial (SpecialChar.HLine);
|
||||
else
|
||||
Driver.AddCh (' ');
|
||||
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
Move (2, i + 1);
|
||||
DrawHotString (item.Title,
|
||||
i == barItems.Current ? Colors.Menu.HotFocus : Colors.Menu.HotNormal,
|
||||
i == barItems.Current ? Colors.Menu.Focus : Colors.Menu.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 + barItems.Current);
|
||||
}
|
||||
|
||||
public override bool ProcessKey (KeyEvent kb)
|
||||
{
|
||||
switch (kb.Key) {
|
||||
case Key.CursorUp:
|
||||
barItems.Current--;
|
||||
if (barItems.Current < 0)
|
||||
barItems.Current = barItems.Children.Length - 1;
|
||||
SetNeedsDisplay ();
|
||||
break;
|
||||
case Key.CursorDown:
|
||||
barItems.Current++;
|
||||
if (barItems.Current == barItems.Children.Length)
|
||||
barItems.Current = 0;
|
||||
SetNeedsDisplay ();
|
||||
break;
|
||||
case Key.CursorLeft:
|
||||
host.PreviousMenu ();
|
||||
break;
|
||||
case Key.CursorRight:
|
||||
host.NextMenu ();
|
||||
break;
|
||||
case Key.Esc:
|
||||
host.CloseMenu ();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A menu bar for your application.
|
||||
/// </summary>
|
||||
@@ -40,6 +155,7 @@ namespace Terminal {
|
||||
public MenuBarItem [] Menus { get; set; }
|
||||
int selected;
|
||||
Action action;
|
||||
bool opened;
|
||||
|
||||
public MenuBar (MenuBarItem [] menus) : base (new Rect (0, 0, Application.Driver.Cols, 1))
|
||||
{
|
||||
@@ -87,30 +203,7 @@ namespace Terminal {
|
||||
}
|
||||
max += 4;
|
||||
DrawFrame (new Rect (col, line, max, menu.Children.Length + 2), true);
|
||||
for (int i = 0; i < menu.Children.Length; i++) {
|
||||
var item = menu.Children [i];
|
||||
|
||||
Move (line + 1 + i, col + 1);
|
||||
Driver.SetAttribute (item == null ? Colors.Base.Focus : i == menu.Current ? Colors.Menu.MarkedSelected : Colors.Menu.Marked);
|
||||
for (int p = 0; p < max - 2; p++)
|
||||
if (item == null)
|
||||
Driver.AddSpecial (SpecialChar.HLine);
|
||||
else
|
||||
Driver.AddCh (' ');
|
||||
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
Move (line + 1 + i, col + 2);
|
||||
DrawHotString (item.Title,
|
||||
i == menu.Current ? Colors.Menu.HotFocus: Colors.Menu.HotNormal,
|
||||
i == menu.Current ? Colors.Menu.MarkedSelected : Colors.Menu.Marked);
|
||||
|
||||
// The help string
|
||||
var l = item.Help.Length;
|
||||
Move (col + max - l - 2, line + 1 + i);
|
||||
Driver.AddStr (item.Help);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Redraw (Rect region)
|
||||
@@ -121,26 +214,24 @@ namespace Terminal {
|
||||
Driver.AddCh (' ');
|
||||
|
||||
Move (1, 0);
|
||||
int pos = 0;
|
||||
int pos = 1;
|
||||
|
||||
for (int i = 0; i < Menus.Length; i++) {
|
||||
var menu = Menus [i];
|
||||
if (i == selected) {
|
||||
DrawMenu (i, pos, 1);
|
||||
Driver.SetAttribute (Colors.Menu.MarkedSelected);
|
||||
} else
|
||||
Driver.SetAttribute (Colors.Menu.Focus);
|
||||
|
||||
}
|
||||
Move (pos, 0);
|
||||
Driver.AddCh (' ');
|
||||
Driver.AddStr(menu.Title);
|
||||
Driver.AddCh (' ');
|
||||
if (HasFocus && i == selected)
|
||||
Driver.SetAttribute (Colors.Menu.MarkedSelected);
|
||||
else
|
||||
Driver.SetAttribute (Colors.Menu.Marked);
|
||||
Driver.AddStr (" ");
|
||||
|
||||
pos += menu.Title.Length + 4;
|
||||
Attribute hotColor, normalColor;
|
||||
if (opened){
|
||||
hotColor = i == selected ? Colors.Menu.HotFocus : Colors.Menu.HotNormal;
|
||||
normalColor = i == selected ? Colors.Menu.Focus : Colors.Menu.Normal;
|
||||
} else {
|
||||
hotColor = Colors.Base.Focus;
|
||||
normalColor = Colors.Base.Focus;
|
||||
}
|
||||
DrawHotString (" " + menu.Title + " " + " ", hotColor, normalColor);
|
||||
pos += menu.Title.Length + 3;
|
||||
}
|
||||
PositionCursor ();
|
||||
}
|
||||
@@ -166,6 +257,46 @@ namespace Terminal {
|
||||
action = item.Action;
|
||||
}
|
||||
|
||||
Menu openMenu;
|
||||
View focusedWhenOpened;
|
||||
|
||||
void OpenMenu ()
|
||||
{
|
||||
if (openMenu != null)
|
||||
return;
|
||||
|
||||
focusedWhenOpened = SuperView.MostFocused;
|
||||
openMenu = new Menu (this, 0, 1, Menus [0]);
|
||||
// Save most deeply focused chain
|
||||
SuperView.Add (openMenu);
|
||||
SuperView.SetFocus (openMenu);
|
||||
}
|
||||
|
||||
internal void CloseMenu ()
|
||||
{
|
||||
SetNeedsDisplay ();
|
||||
SuperView.Remove (openMenu);
|
||||
focusedWhenOpened.SuperView.SetFocus (focusedWhenOpened);
|
||||
openMenu = null;
|
||||
}
|
||||
|
||||
internal void PreviousMenu ()
|
||||
{
|
||||
}
|
||||
|
||||
internal void NextMenu ()
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ProcessHotKey (KeyEvent kb)
|
||||
{
|
||||
if (kb.Key == Key.F9) {
|
||||
OpenMenu ();
|
||||
return true;
|
||||
}
|
||||
return base.ProcessHotKey (kb);
|
||||
}
|
||||
|
||||
public override bool ProcessKey (KeyEvent kb)
|
||||
{
|
||||
switch (kb.Key) {
|
||||
|
||||
21
demo.cs
21
demo.cs
@@ -19,7 +19,8 @@ class Demo {
|
||||
new TextField (14, 4, 40, "") { Secret = true },
|
||||
new CheckBox (3, 6, "Remember me"),
|
||||
new Button (3, 8, "Ok"),
|
||||
new Button (10, 8, "Cancel")
|
||||
new Button (10, 8, "Cancel"),
|
||||
new Label (3, 18, "Press ESC and 9 to activate the menubar")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,16 +32,16 @@ class Demo {
|
||||
|
||||
var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height-1), "Hello");
|
||||
var menu = new MenuBar (new MenuBarItem [] {
|
||||
new MenuBarItem ("File", new MenuItem [] {
|
||||
new MenuItem ("New", "", null),
|
||||
new MenuItem ("Open", "", null),
|
||||
new MenuItem ("Close", "", null),
|
||||
new MenuItem ("Quit", "", null)
|
||||
new MenuBarItem ("_File", new MenuItem [] {
|
||||
new MenuItem ("_New", "", null),
|
||||
new MenuItem ("_Open", "", null),
|
||||
new MenuItem ("_Close", "", null),
|
||||
new MenuItem ("_Quit", "", null)
|
||||
}),
|
||||
new MenuBarItem ("Edit", new MenuItem [] {
|
||||
new MenuItem ("Copy", "", null),
|
||||
new MenuItem ("Cut", "", null),
|
||||
new MenuItem ("Paste", "", null)
|
||||
new MenuBarItem ("_Edit", new MenuItem [] {
|
||||
new MenuItem ("_Copy", "", null),
|
||||
new MenuItem ("C_ut", "", null),
|
||||
new MenuItem ("_Paste", "", null)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user