Make it into a library

This commit is contained in:
Miguel de Icaza
2018-01-17 16:02:27 -05:00
parent 8159f641a2
commit 152126473a
25 changed files with 1162 additions and 33 deletions

View File

@@ -4,16 +4,74 @@ This is a simple UI toolkit for .NET.
It is an updated version of
[gui.cs](https://github.com/mono/mono-curses/blob/master/gui.cs) that
I wrote for [mono-curses](https://github.com/mono/mono-curses)
I wrote for [mono-curses](https://github.com/mono/mono-curses).
The toolkit contains various controls (labesl, text entry, buttons,
radio buttons, checkboxes, dialog boxes, windows, menus) for building
text user interfaces, a main loop, is designed to work on Curses and
the [Windows
Console](https://github.com/migueldeicaza/gui.cs/issues/27), works
well on both color and monochrome terminals and has mouse support on
terminal emulators that support it.
# API Documentation
Go to the [API documentation](https://migueldeicaza.github.io/gui.cs/api/Terminal.html) for details.
# Sample Usage
```
using Terminal.Gui;
class Demo {
static void Main ()
{
Application.Init ();
var top = Application.Top;
// Creates the top-level window to show
var win = new Window (new Rect (0, 1, top.Frame.Width, top.Frame.Height-1), "MyApp");
top.Add (win);
// Creates a menubar, the item "New" has a help menu.
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_New", "Creates new file", NewFile),
new MenuItem ("_Close", "", () => Close ()),
new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", null),
new MenuItem ("C_ut", "", null),
new MenuItem ("_Paste", "", null)
})
});
top.Add (menu);
// Add some controls
win.Add (
new Label (3, 2, "Login: "),
new TextField (14, 2, 40, ""),
new Label (3, 4, "Password: "),
new TextField (14, 4, 40, "") { Secret = true },
new CheckBox (3, 6, "Remember me"),
new RadioGroup (3, 8, new [] { "_Personal", "_Company" }),
new Button (3, 14, "Ok"),
new Button (10, 14, "Cancel"),
new Label (3, 18, "Press ESC and 9 to activate the menubar"));
ShowEntries (win);
Application.Run ();
}
}
```
This shows a UI like this:
# Running and Building
To run this, you will need a peer checkout of mono-ncurses to this
directory. I should make a NuGet package at some point.
Open the solution and run the sample program.
# Input Handling

View File

@@ -0,0 +1,13 @@
This directory contains a copy of the MonoCurses binding from:
http://github.com/mono/mono-curses
The source code has been exported in a way that the MonoCurses
API is kept private and does not surface to the user, this is
done with the command:
```
make publish-to-gui
```
In the MonoCurses package

View File

@@ -0,0 +1,382 @@
//
// binding.cs.in: Core binding for curses.
//
// Authors:
// Miguel de Icaza (miguel.de.icaza@gmail.com)
//
// Copyright (C) 2007 Novell (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Unix.Terminal {
internal partial class Curses {
[StructLayout (LayoutKind.Sequential)]
internal struct MouseEvent {
public short ID;
public int X, Y, Z;
public Event ButtonState;
}
#region Screen initialization
[DllImport ("ncurses", EntryPoint="initscr")]
extern static internal IntPtr real_initscr ();
static int lines, cols;
static Window main_window;
static IntPtr curses_handle, curscr_ptr, lines_ptr, cols_ptr;
static void FindNCurses ()
{
if (File.Exists ("/usr/lib/libncurses.dylib"))
curses_handle = dlopen ("libncurses.dylib", 0);
else
curses_handle = dlopen ("libncurses.so", 0);
if (curses_handle == IntPtr.Zero)
throw new Exception ("Could not dlopen ncurses");
stdscr = read_static_ptr ("stdscr");
curscr_ptr = get_ptr ("curscr");
lines_ptr = get_ptr ("LINES");
cols_ptr = get_ptr ("COLS");
}
static public Window initscr ()
{
FindNCurses ();
main_window = new Window (real_initscr ());
try {
console_sharp_get_dims (out lines, out cols);
} catch (DllNotFoundException){
endwin ();
Console.Error.WriteLine ("Unable to find the @MONO_CURSES@ native library\n" +
"this is different than the managed mono-curses.dll\n\n" +
"Typically you need to install to a LD_LIBRARY_PATH directory\n" +
"or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig");
Environment.Exit (1);
}
return main_window;
}
public static int Lines {
get {
return lines;
}
}
public static int Cols {
get {
return cols;
}
}
//
// Returns true if the window changed since the last invocation, as a
// side effect, the Lines and Cols properties are updated
//
public static bool CheckWinChange ()
{
int l, c;
console_sharp_get_dims (out l, out c);
if (l != lines || c != cols){
lines = l;
cols = c;
return true;
}
return false;
}
[DllImport ("ncurses")]
extern static public int endwin ();
[DllImport ("ncurses")]
extern static public bool isendwin ();
//
// Screen operations are flagged as internal, as we need to
// catch all changes so we can update newscr, curscr, stdscr
//
[DllImport ("ncurses")]
extern static public IntPtr internal_newterm (string type, IntPtr file_outfd, IntPtr file_infd);
[DllImport ("ncurses")]
extern static public IntPtr internal_set_term (IntPtr newscreen);
[DllImport ("ncurses")]
extern static internal void internal_delscreen (IntPtr sp);
#endregion
#region Input Options
[DllImport ("ncurses")]
extern static public int cbreak ();
[DllImport ("ncurses")]
extern static public int nocbreak ();
[DllImport ("ncurses")]
extern static public int echo ();
[DllImport ("ncurses")]
extern static public int noecho ();
[DllImport ("ncurses")]
extern static public int halfdelay (int t);
[DllImport ("ncurses")]
extern static public int raw ();
[DllImport ("ncurses")]
extern static public int noraw ();
[DllImport ("ncurses")]
extern static public void noqiflush ();
[DllImport ("ncurses")]
extern static public void qiflush ();
[DllImport ("ncurses")]
extern static public int typeahead (IntPtr fd);
[DllImport ("ncurses")]
extern static public int timeout (int delay);
//
// Internal, as they are exposed in Window
//
[DllImport ("ncurses")]
extern static internal int wtimeout (IntPtr win, int delay);
[DllImport ("ncurses")]
extern static internal int notimeout (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern static internal int keypad (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern static internal int meta (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern static internal int intrflush (IntPtr win, bool bf);
#endregion
#region Output Options
[DllImport ("ncurses")]
extern internal static int clearok (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern internal static int idlok (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern internal static void idcok (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern internal static void immedok (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern internal static int leaveok (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern internal static int wsetscrreg (IntPtr win, int top, int bot);
[DllImport ("ncurses")]
extern internal static int scrollok (IntPtr win, bool bf);
[DllImport ("ncurses")]
extern public static int nl();
[DllImport ("ncurses")]
extern public static int nonl();
[DllImport ("ncurses")]
extern public static int setscrreg (int top, int bot);
#endregion
#region refresh functions
[DllImport ("ncurses")]
extern public static int refresh ();
[DllImport ("ncurses")]
extern public static int doupdate();
[DllImport ("ncurses")]
extern internal static int wrefresh (IntPtr win);
[DllImport ("ncurses")]
extern internal static int redrawwin (IntPtr win);
[DllImport ("ncurses")]
extern internal static int wredrawwin (IntPtr win, int beg_line, int num_lines);
[DllImport ("ncurses")]
extern internal static int wnoutrefresh (IntPtr win);
#endregion
#region Output
[DllImport ("ncurses")]
extern public static int move (int line, int col);
[DllImport ("ncurses", EntryPoint="addch")]
extern internal static int _addch (int ch);
[DllImport ("ncurses")]
extern public static int addstr (string s);
public static int addstr (string format, params object [] args)
{
var s = string.Format (format, args);
return addstr (s);
}
static char [] r = new char [1];
//
// Have to wrap the native addch, as it can not
// display unicode characters, we have to use addstr
// for that. but we need addch to render special ACS
// characters
//
public static int addch (int ch)
{
if (ch < 127 || ch > 0xffff )
return _addch (ch);
char c = (char) ch;
return addstr (new String (c, 1));
}
[DllImport ("ncurses")]
extern internal static int wmove (IntPtr win, int line, int col);
[DllImport ("ncurses")]
extern internal static int waddch (IntPtr win, int ch);
#endregion
#region Attributes
[DllImport ("ncurses")]
extern public static int attron (int attrs);
[DllImport ("ncurses")]
extern public static int attroff (int attrs);
[DllImport ("ncurses")]
extern public static int attrset (int attrs);
#endregion
#region Input
[DllImport ("ncurses")]
extern public static int getch ();
[DllImport ("ncurses")]
extern public static int get_wch (out int sequence);
[DllImport ("ncurses")]
extern public static int ungetch (int ch);
[DllImport ("ncurses")]
extern public static int mvgetch (int y, int x);
#endregion
#region Colors
[DllImport ("ncurses")]
extern internal static bool has_colors ();
public static bool HasColors => has_colors ();
[DllImport ("ncurses")]
extern internal static int start_color ();
public static int StartColor () => start_color ();
[DllImport ("ncurses")]
extern internal static int init_pair (short pair, short f, short b);
public static int InitColorPair (short pair, short foreground, short background) => init_pair (pair, foreground, background);
[DllImport ("ncurses")]
extern internal static int use_default_colors ();
public static int UseDefaultColors () => use_default_colors ();
[DllImport ("ncurses")]
extern internal static int COLOR_PAIRS();
public static int ColorPairs => COLOR_PAIRS();
#endregion
[DllImport ("libc")]
extern static IntPtr dlopen (string file, int mode);
[DllImport ("libc")]
extern static IntPtr dlsym (IntPtr handle, string symbol);
static IntPtr stdscr;
static IntPtr get_ptr (string key)
{
var ptr = dlsym (curses_handle, key);
if (ptr == IntPtr.Zero)
throw new Exception ("Could not load the key " + key);
return ptr;
}
internal static IntPtr read_static_ptr (string key)
{
var ptr = get_ptr (key);
return Marshal.ReadIntPtr (ptr);
}
internal static IntPtr console_sharp_get_stdscr () => stdscr;
#region Helpers
internal static IntPtr console_sharp_get_curscr ()
{
return Marshal.ReadIntPtr (curscr_ptr);
}
internal static void console_sharp_get_dims (out int lines, out int cols)
{
lines = Marshal.ReadInt32 (lines_ptr);
cols = Marshal.ReadInt32 (cols_ptr);
}
[DllImport ("ncurses", EntryPoint="mousemask")]
extern static IntPtr call_mousemask (IntPtr newmask, out IntPtr oldmask);
public static Event mousemask (Event newmask, out Event oldmask)
{
IntPtr e;
var ret = (Event) call_mousemask ((IntPtr) newmask, out e);
oldmask = (Event) e;
return ret;
}
[DllImport ("ncurses")]
public extern static uint getmouse (out MouseEvent ev);
[DllImport ("ncurses")]
public extern static uint ungetmouse (ref MouseEvent ev);
#endregion
// We encode ESC + char (what Alt-char generates) as 0x2000 + char
public const int KeyAlt = 0x2000;
static public int IsAlt (int key)
{
if ((key & KeyAlt) != 0)
return key & ~KeyAlt;
return 0;
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* This file is autogenerated by the attrib.c program, do not edit
*/
using System;
namespace Unix.Terminal {
internal partial class Curses {
public const int A_NORMAL = unchecked((int)0x0);
public const int A_STANDOUT = unchecked((int)0x10000);
public const int A_UNDERLINE = unchecked((int)0x20000);
public const int A_REVERSE = unchecked((int)0x40000);
public const int A_BLINK = unchecked((int)0x80000);
public const int A_DIM = unchecked((int)0x100000);
public const int A_BOLD = unchecked((int)0x200000);
public const int A_PROTECT = unchecked((int)0x1000000);
public const int A_INVIS = unchecked((int)0x800000);
public const int ACS_LLCORNER = unchecked((int)0x40006d);
public const int ACS_LRCORNER = unchecked((int)0x40006a);
public const int ACS_HLINE = unchecked((int)0x400071);
public const int ACS_ULCORNER = unchecked((int)0x40006c);
public const int ACS_URCORNER = unchecked((int)0x40006b);
public const int ACS_VLINE = unchecked((int)0x400078);
public const int COLOR_BLACK = unchecked((int)0x0);
public const int COLOR_RED = unchecked((int)0x1);
public const int COLOR_GREEN = unchecked((int)0x2);
public const int COLOR_YELLOW = unchecked((int)0x3);
public const int COLOR_BLUE = unchecked((int)0x4);
public const int COLOR_MAGENTA = unchecked((int)0x5);
public const int COLOR_CYAN = unchecked((int)0x6);
public const int COLOR_WHITE = unchecked((int)0x7);
public const int KEY_CODE_YES = unchecked((int)0x100);
internal enum Event : long {
Button1Pressed = unchecked((int)0x2),
Button1Released = unchecked((int)0x1),
Button1Clicked = unchecked((int)0x4),
Button1DoubleClicked = unchecked((int)0x8),
Button1TripleClicked = unchecked((int)0x10),
Button2Pressed = unchecked((int)0x80),
Button2Released = unchecked((int)0x40),
Button2Clicked = unchecked((int)0x100),
Button2DoubleClicked = unchecked((int)0x200),
Button2TrippleClicked = unchecked((int)0x400),
Button3Pressed = unchecked((int)0x2000),
Button3Released = unchecked((int)0x1000),
Button3Clicked = unchecked((int)0x4000),
Button3DoubleClicked = unchecked((int)0x8000),
Button3TripleClicked = unchecked((int)0x10000),
Button4Pressed = unchecked((int)0x80000),
Button4Released = unchecked((int)0x40000),
Button4Clicked = unchecked((int)0x100000),
Button4DoubleClicked = unchecked((int)0x200000),
Button4TripleClicked = unchecked((int)0x400000),
ButtonShift = unchecked((int)0x2000000),
ButtonCtrl = unchecked((int)0x1000000),
ButtonAlt = unchecked((int)0x4000000),
ReportMousePosition = unchecked((int)0x8000000),
AllEvents = unchecked((int)0x7ffffff),
}
public const int ERR = unchecked((int)0xffffffff);
public const int KeyBackspace = unchecked((int)0x107);
public const int KeyUp = unchecked((int)0x103);
public const int KeyDown = unchecked((int)0x102);
public const int KeyLeft = unchecked((int)0x104);
public const int KeyRight = unchecked((int)0x105);
public const int KeyNPage = unchecked((int)0x152);
public const int KeyPPage = unchecked((int)0x153);
public const int KeyHome = unchecked((int)0x106);
public const int KeyMouse = unchecked((int)0x199);
public const int KeyEnd = unchecked((int)0x168);
public const int KeyDeleteChar = unchecked((int)0x14a);
public const int KeyInsertChar = unchecked((int)0x14b);
public const int KeyBackTab = unchecked((int)0x161);
public const int KeyF1 = unchecked((int)0x109);
public const int KeyF2 = unchecked((int)0x10a);
public const int KeyF3 = unchecked((int)0x10b);
public const int KeyF4 = unchecked((int)0x10c);
public const int KeyF5 = unchecked((int)0x10d);
public const int KeyF6 = unchecked((int)0x10e);
public const int KeyF7 = unchecked((int)0x10f);
public const int KeyF8 = unchecked((int)0x110);
public const int KeyF9 = unchecked((int)0x111);
public const int KeyF10 = unchecked((int)0x112);
public const int KeyResize = unchecked((int)0x19a);
static public int ColorPair(int n){
return 0 + n * 256;
}
}
}

View File

@@ -0,0 +1,172 @@
//
// handles.cs: OO wrappers for some curses objects
//
// Authors:
// Miguel de Icaza (miguel.de.icaza@gmail.com)
//
// Copyright (C) 2007 Novell (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Runtime.InteropServices;
namespace Unix.Terminal {
internal partial class Curses {
internal class Window {
public readonly IntPtr Handle;
static Window curscr;
static Window stdscr;
static Window ()
{
Curses.initscr ();
stdscr = new Window (Curses.console_sharp_get_stdscr ());
curscr = new Window (Curses.console_sharp_get_curscr ());
}
internal Window (IntPtr handle)
{
Handle = handle;
}
static public Window Standard {
get {
return stdscr;
}
}
static public Window Current {
get {
return curscr;
}
}
public int wtimeout (int delay)
{
return Curses.wtimeout (Handle, delay);
}
public int notimeout (bool bf)
{
return Curses.notimeout (Handle, bf);
}
public int keypad (bool bf)
{
return Curses.keypad (Handle, bf);
}
public int meta (bool bf)
{
return Curses.meta (Handle, bf);
}
public int intrflush (bool bf)
{
return Curses.intrflush (Handle, bf);
}
public int clearok (bool bf)
{
return Curses.clearok (Handle, bf);
}
public int idlok (bool bf)
{
return Curses.idlok (Handle, bf);
}
public void idcok (bool bf)
{
Curses.idcok (Handle, bf);
}
public void immedok (bool bf)
{
Curses.immedok (Handle, bf);
}
public int leaveok (bool bf)
{
return Curses.leaveok (Handle, bf);
}
public int setscrreg (int top, int bot)
{
return Curses.wsetscrreg (Handle, top, bot);
}
public int scrollok (bool bf)
{
return Curses.scrollok (Handle, bf);
}
public int wrefresh ()
{
return Curses.wrefresh (Handle);
}
public int redrawwin ()
{
return Curses.redrawwin (Handle);
}
public int wredrawwin (int beg_line, int num_lines)
{
return Curses.wredrawwin (Handle, beg_line, num_lines);
}
public int wnoutrefresh ()
{
return Curses.wnoutrefresh (Handle);
}
public int move (int line, int col)
{
return Curses.wmove (Handle, line, col);
}
public int addch (char ch)
{
return Curses.waddch (Handle, ch);
}
public int refresh ()
{
return Curses.wrefresh (Handle);
}
}
// Currently unused, to do later
internal class Screen {
public readonly IntPtr Handle;
internal Screen (IntPtr handle)
{
Handle = handle;
}
}
}
}

View File

@@ -0,0 +1,362 @@
//
// mainloop.cs: Simple managed mainloop implementation.
//
// Authors:
// Miguel de Icaza (miguel.de.icaza@gmail.com)
//
// Copyright (C) 2011 Novell (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Mono.Unix.Native;
using System.Collections.Generic;
using System;
using System.Runtime.InteropServices;
namespace Mono.Terminal {
/// <summary>
/// Simple main loop implementation that can be used to monitor
/// file descriptor, run timers and idle handlers.
/// </summary>
public class MainLoop {
/// <summary>
/// Condition on which to wake up from file descriptor activity
/// </summary>
[Flags]
public enum Condition {
/// <summary>
/// There is data to read
/// </summary>
PollIn = 1,
/// <summary>
/// Writing to the specified descriptor will not block
/// </summary>
PollOut = 2,
/// <summary>
/// There is urgent data to read
/// </summary>
PollPri = 4,
/// <summary>
/// Error condition on output
/// </summary>
PollErr = 8,
/// <summary>
/// Hang-up on output
/// </summary>
PollHup = 16,
/// <summary>
/// File descriptor is not open.
/// </summary>
PollNval = 32
}
class Watch {
public int File;
public Condition Condition;
public Func<MainLoop,bool> Callback;
}
class Timeout {
public TimeSpan Span;
public Func<MainLoop,bool> Callback;
}
Dictionary <int, Watch> descriptorWatchers = new Dictionary<int,Watch>();
SortedList <double, Timeout> timeouts = new SortedList<double,Timeout> ();
List<Func<bool>> idleHandlers = new List<Func<bool>> ();
Pollfd [] pollmap;
bool poll_dirty = true;
int [] wakeupPipes = new int [2];
static IntPtr ignore = Marshal.AllocHGlobal (1);
/// <summary>
/// Default constructor
/// </summary>
public MainLoop ()
{
Syscall.pipe (wakeupPipes);
AddWatch (wakeupPipes [0], Condition.PollIn, ml => {
Syscall.read (wakeupPipes [0], ignore, 1);
return true;
});
}
void Wakeup ()
{
Syscall.write (wakeupPipes [1], ignore, 1);
}
/// <summary>
/// Runs @action on the thread that is processing events
/// </summary>
public void Invoke (Action action)
{
AddIdle (()=> {
action ();
return false;
});
Wakeup ();
}
/// <summary>
/// Executes the specified @idleHandler on the idle loop. The return value is a token to remove it.
/// </summary>
public Func<bool> AddIdle (Func<bool> idleHandler)
{
lock (idleHandlers)
idleHandlers.Add (idleHandler);
return idleHandler;
}
/// <summary>
/// Removes the specified idleHandler from processing.
/// </summary>
public void RemoveIdle (Func<bool> idleHandler)
{
lock (idleHandler)
idleHandlers.Remove (idleHandler);
}
/// <summary>
/// Watches a file descriptor for activity.
/// </summary>
/// <remarks>
/// When the condition is met, the provided callback
/// is invoked. If the callback returns false, the
/// watch is automatically removed.
///
/// The return value is a token that represents this watch, you can
/// use this token to remove the watch by calling RemoveWatch.
/// </remarks>
public object AddWatch (int fileDescriptor, Condition condition, Func<MainLoop,bool> callback)
{
if (callback == null)
throw new ArgumentNullException ("callback");
var watch = new Watch () { Condition = condition, Callback = callback, File = fileDescriptor };
descriptorWatchers [fileDescriptor] = watch;
poll_dirty = true;
return watch;
}
/// <summary>
/// Removes an active watch from the mainloop.
/// </summary>
/// <remarks>
/// The token parameter is the value returned from AddWatch
/// </remarks>
public void RemoveWatch (object token)
{
var watch = token as Watch;
if (watch == null)
return;
descriptorWatchers.Remove (watch.File);
}
void AddTimeout (TimeSpan time, Timeout timeout)
{
timeouts.Add ((DateTime.UtcNow + time).Ticks, timeout);
}
/// <summary>
/// Adds a timeout to the mainloop.
/// </summary>
/// <remarks>
/// When time time specified passes, the callback will be invoked.
/// If the callback returns true, the timeout will be reset, repeating
/// the invocation. If it returns false, the timeout will stop.
///
/// The returned value is a token that can be used to stop the timeout
/// by calling RemoveTimeout.
/// </remarks>
public object AddTimeout (TimeSpan time, Func<MainLoop,bool> callback)
{
if (callback == null)
throw new ArgumentNullException ("callback");
var timeout = new Timeout () {
Span = time,
Callback = callback
};
AddTimeout (time, timeout);
return timeout;
}
/// <summary>
/// Removes a previously scheduled timeout
/// </summary>
/// <remarks>
/// The token parameter is the value returned by AddTimeout.
/// </remarks>
public void RemoveTimeout (object token)
{
var idx = timeouts.IndexOfValue (token as Timeout);
if (idx == -1)
return;
timeouts.RemoveAt (idx);
}
static PollEvents MapCondition (Condition condition)
{
PollEvents ret = 0;
if ((condition & Condition.PollIn) != 0)
ret |= PollEvents.POLLIN;
if ((condition & Condition.PollOut) != 0)
ret |= PollEvents.POLLOUT;
if ((condition & Condition.PollPri) != 0)
ret |= PollEvents.POLLPRI;
if ((condition & Condition.PollErr) != 0)
ret |= PollEvents.POLLERR;
if ((condition & Condition.PollHup) != 0)
ret |= PollEvents.POLLHUP;
if ((condition & Condition.PollNval) != 0)
ret |= PollEvents.POLLNVAL;
return ret;
}
void UpdatePollMap ()
{
if (!poll_dirty)
return;
poll_dirty = false;
pollmap = new Pollfd [descriptorWatchers.Count];
int i = 0;
foreach (var fd in descriptorWatchers.Keys){
pollmap [i].fd = fd;
pollmap [i].events = MapCondition (descriptorWatchers [fd].Condition);
}
}
void RunTimers ()
{
long now = DateTime.UtcNow.Ticks;
var copy = timeouts;
timeouts = new SortedList<double,Timeout> ();
foreach (var k in copy.Keys){
if (k >= now)
break;
var timeout = copy [k];
if (timeout.Callback (this))
AddTimeout (timeout.Span, timeout);
}
}
void RunIdle ()
{
List<Func<bool>> iterate;
lock (idleHandlers){
iterate = idleHandlers;
idleHandlers = new List<Func<bool>> ();
}
foreach (var idle in iterate){
if (idle ())
lock (idleHandlers)
idleHandlers.Add (idle);
}
}
bool running;
/// <summary>
/// Stops the mainloop.
/// </summary>
public void Stop ()
{
running = false;
Wakeup ();
}
/// <summary>
/// Determines whether there are pending events to be processed.
/// </summary>
/// <remarks>
/// You can use this method if you want to probe if events are pending.
/// Typically used if you need to flush the input queue while still
/// running some of your own code in your main thread.
/// </remarks>
public bool EventsPending (bool wait = false)
{
long now = DateTime.UtcNow.Ticks;
int pollTimeout, n;
if (timeouts.Count > 0)
pollTimeout = (int) ((timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
else
pollTimeout = -1;
if (!wait)
pollTimeout = 0;
UpdatePollMap ();
n = Syscall.poll (pollmap, (uint) pollmap.Length, pollTimeout);
int ic;
lock (idleHandlers)
ic = idleHandlers.Count;
return n > 0 || timeouts.Count > 0 && ((timeouts.Keys [0] - DateTime.UtcNow.Ticks) < 0) || ic > 0;
}
/// <summary>
/// Runs one iteration of timers and file watches
/// </summary>
/// <remarks>
/// You use this to process all pending events (timers, idle handlers and file watches).
///
/// You can use it like this:
/// while (main.EvensPending ()) MainIteration ();
/// </remarks>
public void MainIteration ()
{
if (timeouts.Count > 0)
RunTimers ();
foreach (var p in pollmap){
Watch watch;
if (p.revents == 0)
continue;
if (!descriptorWatchers.TryGetValue (p.fd, out watch))
continue;
if (!watch.Callback (this))
descriptorWatchers.Remove (p.fd);
}
if (idleHandlers.Count > 0)
RunIdle ();
}
/// <summary>
/// Runs the mainloop.
/// </summary>
public void Run ()
{
bool prev = running;
running = true;
while (running){
EventsPending (true);
MainIteration ();
}
running = prev;
}
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>Terminal.Gui</RootNamespace>
<AssemblyName>Terminal.Gui</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Mono.Posix" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
<Folder Include="Types\" />
<Folder Include="Views\" />
<Folder Include="MonoCurses\" />
</ItemGroup>
<ItemGroup>
<Compile Include="Types\Point.cs" />
<Compile Include="Types\Rect.cs" />
<Compile Include="Types\Size.cs" />
<Compile Include="Views\Button.cs" />
<Compile Include="Views\Checkbox.cs" />
<Compile Include="Views\Dialog.cs" />
<Compile Include="Views\Label.cs" />
<Compile Include="Views\Menu.cs" />
<Compile Include="Views\MessageBox.cs" />
<Compile Include="Views\RadioGroup.cs" />
<Compile Include="Views\ScrollView.cs" />
<Compile Include="Views\TextField.cs" />
<Compile Include="Core.cs" />
<Compile Include="Driver.cs" />
<Compile Include="Event.cs" />
<Compile Include="MonoCurses\binding.cs" />
<Compile Include="MonoCurses\constants.cs" />
<Compile Include="MonoCurses\handles.cs" />
<Compile Include="MonoCurses\mainloop.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -33,30 +33,13 @@
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Core.cs" />
<Compile Include="Driver.cs" />
<Compile Include="Event.cs" />
<Compile Include="Types\Point.cs" />
<Compile Include="Types\Rect.cs" />
<Compile Include="Types\Size.cs" />
<Compile Include="demo.cs" />
<Compile Include="Views\Label.cs" />
<Compile Include="Views\TextField.cs" />
<Compile Include="Views\Button.cs" />
<Compile Include="Views\Checkbox.cs" />
<Compile Include="Views\Menu.cs" />
<Compile Include="Views\ScrollView.cs" />
<Compile Include="Views\Dialog.cs" />
<Compile Include="Views\RadioGroup.cs" />
<Compile Include="Views\MessageBox.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="mono-curses.dll">
<HintPath>$(MSBuildProjectDirectory)/../mono-curses/mono-curses.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Views\" />
<ProjectReference Include="Terminal.Gui\Terminal.Gui.csproj">
<Project>{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}</Project>
<Name>Terminal.Gui</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal", "Terminal.csproj", "{B0A602CD-E176-449D-8663-64238D54F857}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
@@ -13,6 +15,10 @@ Global
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.Build.0 = Debug|x86
{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.ActiveCfg = Release|x86
{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.Build.0 = Release|x86
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.ActiveCfg = Debug|Any CPU
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.Build.0 = Debug|Any CPU
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.ActiveCfg = Release|Any CPU
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0

18
demo.cs
View File

@@ -13,15 +13,15 @@ class Demo {
static void ShowEntries (View container)
{
container.Add (
new Label (3, 2, "Login: "),
new TextField (14, 2, 40, ""),
new Label (3, 4, "Password: "),
new TextField (14, 4, 40, "") { Secret = true },
new CheckBox (3, 6, "Remember me"),
new RadioGroup (3, 8, new [] { "_Personal", "_Company" }),
new Button (3, 14, "Ok"),
new Button (10, 14, "Cancel"),
new Label (3, 18, "Press ESC and 9 to activate the menubar")
new Label (3, 6, "Login: "),
new TextField (14, 6, 40, ""),
new Label (3, 8, "Password: "),
new TextField (14, 8, 40, "") { Secret = true },
new CheckBox (3, 10, "Remember me"),
new RadioGroup (3, 12, new [] { "_Personal", "_Company" }),
new Button (3, 18, "Ok"),
new Button (10, 18, "Cancel"),
new Label (3, 22, "Press ESC and 9 to activate the menubar")
);
}