Add menus

This commit is contained in:
Miguel de Icaza
2018-01-05 22:09:15 -05:00
parent da0a4e7d87
commit 60823d06dd
5 changed files with 267 additions and 59 deletions

19
Core.cs
View File

@@ -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>

View File

@@ -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
View 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.

View File

@@ -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
View File

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