Browse Source

Some work on display flags

Miguel de Icaza 7 years ago
parent
commit
59f73e587a
7 changed files with 1417 additions and 1360 deletions
  1. 606 552
      Core.cs
  2. 324 324
      Driver.cs
  3. 1 1
      Event.cs
  4. 5 1
      TODO.md
  5. 172 172
      Views/Label.cs
  6. 293 294
      Views/TextField.cs
  7. 16 16
      demo.cs

File diff suppressed because it is too large
+ 606 - 552
Core.cs


+ 324 - 324
Driver.cs

@@ -5,328 +5,328 @@ using Unix.Terminal;
 
 namespace Terminal {
 
-    /// <summary>
-    /// Basic colors that can be used to set the foreground and background colors in console applications.  These can only be
-    /// </summary>
-    public enum Color {
-        Black,
-        Blue,
-        Green,
-        Cyan,
-        Red,
-        Magenta,
-        Brown,
-        Gray,
-        DarkGray,
-        BrightBlue,
-        BrightGreen,
-        BrighCyan,
-        BrightRed,
-        BrightMagenta,
-        BrightYellow,
-        White
-    }
-
-    public struct Attribute {
-        internal int value;
-        public Attribute (int v)
-        {
-            value = v;
-        }
-
-        public static implicit operator int (Attribute c) => c.value;
-        public static implicit operator Attribute (int v) => new Attribute (v);
-    }
-
-    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 {
-        public static ColorScheme Base, Dialog, Menu, Error;
-
-    }
-
-    public abstract class ConsoleDriver {
-        public abstract int Cols { get; }
-        public abstract int Rows { get; }
-        public abstract void Init (Action terminalResized);
-        public abstract void Move (int col, int row);
-        public abstract void AddCh (int ch);
-        public abstract void AddStr (string str);
-        public abstract void PrepareToRun (MainLoop mainLoop, Responder target);
-        public abstract void Refresh ();
-        public abstract void End ();
-        public abstract void RedrawTop ();
-        public abstract void SetAttribute (Attribute c);
-
-        // Set Colors from limit sets of colors
-        public abstract void SetColors (ConsoleColor foreground, ConsoleColor background);
-
-        // Advanced uses - set colors to any pre-set pairs, you would need to init_color
-        // that independently with the R, G, B values.
-        public abstract void SetColors (short foreColorId, short backgroundColorId);
-
-        public abstract void DrawFrame (Rect region, bool fill);
-
-        Rect clip;
-        public Rect Clip {
-            get => clip;
-            set => this.clip = value;
-        }
-    }
-
-    public class CursesDriver : ConsoleDriver {
-        Action terminalResized;
-
-        public override int Cols => Curses.Cols;
-        public override int Rows => Curses.Lines;
-
-        // Current row, and current col, tracked by Move/AddCh only
-        int ccol, crow;
-        bool needMove;
-        public override void Move (int col, int row)
-        {
-            ccol = col;
-            crow = row;
-
-            if (Clip.Contains (col, row)) {
-                Curses.move (row, col);
-                needMove = false;
-            } else {
-                Curses.move (Clip.Y, Clip.X);
-                needMove = true;
-            }
-        }
-
-        public override void AddCh (int ch)
-        {
-            if (Clip.Contains (ccol, crow)) {
-                if (needMove) {
-                    Curses.move (crow, ccol);
-                    needMove = false;
-                }
-                Curses.addch (ch);
-            } else
-                needMove = true;
-            ccol++;
-        }
-
-        public override void AddStr (string str)
-        {
-            // TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
-            foreach (var c in str)
-                AddCh ((int)c);
-        }
-
-        public override void Refresh () => Curses.refresh ();
-        public override void End () => Curses.endwin ();
-        public override void RedrawTop () => window.redrawwin ();
-        public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
-        public Curses.Window window;
-
-        static short last_color_pair = 16;
-        static Attribute MakeColor (short f, short b)
-        {
-            Curses.InitColorPair (++last_color_pair, f, b);
-            return new Attribute () { value = Curses.ColorPair (last_color_pair) };
-        }
-
-        int [,] colorPairs = new int [16, 16];
-
-        public override void SetColors (ConsoleColor foreground, ConsoleColor background)
-        {
-            int f = (short)foreground;
-            int b = (short)background;
-            var v = colorPairs [f, b];
-            if ((v & 0x10000) == 0) {
-                b = b & 0x7;
-                bool bold = (f & 0x8) != 0;
-                f = f & 0x7;
-
-                v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
-                colorPairs [(int)foreground, (int)background] = v | 0x1000;
-            }
-            SetAttribute (v & 0xffff);
-        }
-
-        Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
-        public override void SetColors (short foreColorId, short backgroundColorId)
-        {
-            int key = (((ushort)foreColorId << 16)) | (ushort)backgroundColorId;
-            if (!rawPairs.TryGetValue (key, out var v)) {
-                v = MakeColor (foreColorId, backgroundColorId);
-                rawPairs [key] = v;
-            }
-            SetAttribute (v);
-        }
-
-        static Key MapCursesKey (int cursesKey)
-        {
-            switch (cursesKey) {
-            case Curses.KeyF1: return Key.F1;
-            case Curses.KeyF2: return Key.F2;
-            case Curses.KeyF3: return Key.F3;
-            case Curses.KeyF4: return Key.F4;
-            case Curses.KeyF5: return Key.F5;
-            case Curses.KeyF6: return Key.F6;
-            case Curses.KeyF7: return Key.F7;
-            case Curses.KeyF8: return Key.F8;
-            case Curses.KeyF9: return Key.F9;
-            case Curses.KeyF10: return Key.F10;
-            case Curses.KeyUp: return Key.CursorUp;
-            case Curses.KeyDown: return Key.CursorDown;
-            case Curses.KeyLeft: return Key.CursorLeft;
-            case Curses.KeyRight: return Key.CursorRight;
-            case Curses.KeyHome: return Key.Home;
-            case Curses.KeyEnd: return Key.End;
-            case Curses.KeyNPage: return Key.PageDown;
-            case Curses.KeyPPage: return Key.PageUp;
-            case Curses.KeyDeleteChar: return Key.DeleteChar;
-            case Curses.KeyInsertChar: return Key.InsertChar;
-            case Curses.KeyBackTab: return Key.BackTab;
-            default: return Key.Unknown;
-            }
-        }
-
-        void ProcessInput (Responder handler)
-        {
-            int wch;
-            var code = Curses.get_wch (out wch);
-            if (code == Curses.KEY_CODE_YES) {
-                if (wch == Curses.KeyResize) {
-                    if (Curses.CheckWinChange ()) {
-                        terminalResized ();
-                        return;
-                    }
-                }
-                if (code == Curses.KeyMouse) {
-                    // TODO
-                    // Curses.MouseEvent ev;
-                    // Curses.getmouse (out ev);
-                    // handler.HandleMouse ();
-                    return;
-                }
-                handler.ProcessKey (new KeyEvent (MapCursesKey (wch)));
-                return;
-            }
-
-            // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter.
-            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));
-            } else 
-                handler.ProcessKey (new KeyEvent ((Key)wch));
-        }
-
-        public override void PrepareToRun (MainLoop mainLoop, Responder handler)
-        {
-            Curses.timeout (-1);
-
-            mainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
-                ProcessInput (handler);
-                return true;
-            });
-
-        }
-
-        public override void DrawFrame (Rect region, bool fill)
-        {
-            int width = region.Width;
-            int height = region.Height;
-            int b;
-
-            Move (region.X, region.Y);
-            AddCh (Curses.ACS_ULCORNER);
-            for (b = 0; b < width - 2; b++)
-                AddCh (Curses.ACS_HLINE);
-            AddCh (Curses.ACS_URCORNER);
-            for (b = 1; b < height - 1; b++) {
-                Move (region.X, region.Y + b);
-                AddCh (Curses.ACS_VLINE);
-                if (fill) {
-                    for (int x = 1; x < width - 1; x++)
-                        AddCh (' ');
-                } else
-                    Move (region.X + width - 1, region.Y + b);
-                AddCh (Curses.ACS_VLINE);
-            }
-            Move (region.X, region.Y + height - 1);
-            AddCh (Curses.ACS_LLCORNER);
-            for (b = 0; b < width - 2; b++)
-                AddCh (Curses.ACS_HLINE);
-            AddCh (Curses.ACS_LRCORNER);
-        }
-
-        public override void Init(Action terminalResized)
-        {
-            if (window != null)
-                return;
-
-            try {
-                window = Curses.initscr ();
-            } catch (Exception e){
-                Console.WriteLine ("Curses failed to initialize, the exception is: " + e);
-            }
-            Curses.raw ();
-            Curses.noecho ();
-            Curses.Window.Standard.keypad (true);
-            this.terminalResized = terminalResized;
-        
-            Colors.Base = new ColorScheme ();
-            Colors.Dialog = new ColorScheme ();
-            Colors.Menu = new ColorScheme ();
-            Colors.Error = new ColorScheme ();
-            Clip = new Rect (0, 0, Cols, Rows);
-            if (Curses.HasColors){
-                Curses.StartColor ();
-                Curses.UseDefaultColors ();
-
-                Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE);
-                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);
-                Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
-                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);
-                Colors.Dialog.HotFocus  = MakeColor (Curses.COLOR_BLUE,  Curses.COLOR_CYAN);
-
-                Colors.Error.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED);
-                Colors.Error.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
-                Colors.Error.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_RED);
-                Colors.Error.HotFocus = Colors.Error.HotNormal;
-            } else {
-                Colors.Base.Normal = Curses.A_NORMAL;
-                Colors.Base.Focus = Curses.A_REVERSE;
-                Colors.Base.HotNormal = Curses.A_BOLD;
-                Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
-                Colors.Menu.Normal = Curses.A_REVERSE;
-                Colors.Menu.Focus = Curses.A_NORMAL;
-                Colors.Menu.HotNormal = Curses.A_BOLD;
-                Colors.Menu.HotFocus = Curses.A_NORMAL;
-                Colors.Dialog.Normal    = Curses.A_REVERSE;
-                Colors.Dialog.Focus     = Curses.A_NORMAL;
-                Colors.Dialog.HotNormal = Curses.A_BOLD;
-                Colors.Dialog.HotFocus  = Curses.A_NORMAL;
-                Colors.Error.Normal = Curses.A_BOLD;
-                Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
-                Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
-                Colors.Error.HotFocus = Curses.A_REVERSE;
-            }
-        }
-    }
+	/// <summary>
+	/// Basic colors that can be used to set the foreground and background colors in console applications.  These can only be
+	/// </summary>
+	public enum Color {
+		Black,
+		Blue,
+		Green,
+		Cyan,
+		Red,
+		Magenta,
+		Brown,
+		Gray,
+		DarkGray,
+		BrightBlue,
+		BrightGreen,
+		BrighCyan,
+		BrightRed,
+		BrightMagenta,
+		BrightYellow,
+		White
+	}
+
+	public struct Attribute {
+		internal int value;
+		public Attribute (int v)
+		{
+			value = v;
+		}
+
+		public static implicit operator int (Attribute c) => c.value;
+		public static implicit operator Attribute (int v) => new Attribute (v);
+	}
+
+	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 {
+		public static ColorScheme Base, Dialog, Menu, Error;
+
+	}
+
+	public abstract class ConsoleDriver {
+		public abstract int Cols { get; }
+		public abstract int Rows { get; }
+		public abstract void Init (Action terminalResized);
+		public abstract void Move (int col, int row);
+		public abstract void AddCh (int ch);
+		public abstract void AddStr (string str);
+		public abstract void PrepareToRun (MainLoop mainLoop, Responder target);
+		public abstract void Refresh ();
+		public abstract void End ();
+		public abstract void RedrawTop ();
+		public abstract void SetAttribute (Attribute c);
+
+		// Set Colors from limit sets of colors
+		public abstract void SetColors (ConsoleColor foreground, ConsoleColor background);
+
+		// Advanced uses - set colors to any pre-set pairs, you would need to init_color
+		// that independently with the R, G, B values.
+		public abstract void SetColors (short foreColorId, short backgroundColorId);
+
+		public abstract void DrawFrame (Rect region, bool fill);
+
+		Rect clip;
+		public Rect Clip {
+			get => clip;
+			set => this.clip = value;
+		}
+	}
+
+	public class CursesDriver : ConsoleDriver {
+		Action terminalResized;
+
+		public override int Cols => Curses.Cols;
+		public override int Rows => Curses.Lines;
+
+		// Current row, and current col, tracked by Move/AddCh only
+		int ccol, crow;
+		bool needMove;
+		public override void Move (int col, int row)
+		{
+			ccol = col;
+			crow = row;
+
+			if (Clip.Contains (col, row)) {
+				Curses.move (row, col);
+				needMove = false;
+			} else {
+				Curses.move (Clip.Y, Clip.X);
+				needMove = true;
+			}
+		}
+
+		public override void AddCh (int ch)
+		{
+			if (Clip.Contains (ccol, crow)) {
+				if (needMove) {
+					Curses.move (crow, ccol);
+					needMove = false;
+				}
+				Curses.addch (ch);
+			} else
+				needMove = true;
+			ccol++;
+		}
+
+		public override void AddStr (string str)
+		{
+			// TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
+			foreach (var c in str)
+				AddCh ((int)c);
+		}
+
+		public override void Refresh () => Curses.refresh ();
+		public override void End () => Curses.endwin ();
+		public override void RedrawTop () => window.redrawwin ();
+		public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
+		public Curses.Window window;
+
+		static short last_color_pair = 16;
+		static Attribute MakeColor (short f, short b)
+		{
+			Curses.InitColorPair (++last_color_pair, f, b);
+			return new Attribute () { value = Curses.ColorPair (last_color_pair) };
+		}
+
+		int [,] colorPairs = new int [16, 16];
+
+		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
+		{
+			int f = (short)foreground;
+			int b = (short)background;
+			var v = colorPairs [f, b];
+			if ((v & 0x10000) == 0) {
+				b = b & 0x7;
+				bool bold = (f & 0x8) != 0;
+				f = f & 0x7;
+
+				v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
+				colorPairs [(int)foreground, (int)background] = v | 0x1000;
+			}
+			SetAttribute (v & 0xffff);
+		}
+
+		Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
+		public override void SetColors (short foreColorId, short backgroundColorId)
+		{
+			int key = (((ushort)foreColorId << 16)) | (ushort)backgroundColorId;
+			if (!rawPairs.TryGetValue (key, out var v)) {
+				v = MakeColor (foreColorId, backgroundColorId);
+				rawPairs [key] = v;
+			}
+			SetAttribute (v);
+		}
+
+		static Key MapCursesKey (int cursesKey)
+		{
+			switch (cursesKey) {
+			case Curses.KeyF1: return Key.F1;
+			case Curses.KeyF2: return Key.F2;
+			case Curses.KeyF3: return Key.F3;
+			case Curses.KeyF4: return Key.F4;
+			case Curses.KeyF5: return Key.F5;
+			case Curses.KeyF6: return Key.F6;
+			case Curses.KeyF7: return Key.F7;
+			case Curses.KeyF8: return Key.F8;
+			case Curses.KeyF9: return Key.F9;
+			case Curses.KeyF10: return Key.F10;
+			case Curses.KeyUp: return Key.CursorUp;
+			case Curses.KeyDown: return Key.CursorDown;
+			case Curses.KeyLeft: return Key.CursorLeft;
+			case Curses.KeyRight: return Key.CursorRight;
+			case Curses.KeyHome: return Key.Home;
+			case Curses.KeyEnd: return Key.End;
+			case Curses.KeyNPage: return Key.PageDown;
+			case Curses.KeyPPage: return Key.PageUp;
+			case Curses.KeyDeleteChar: return Key.DeleteChar;
+			case Curses.KeyInsertChar: return Key.InsertChar;
+			case Curses.KeyBackTab: return Key.BackTab;
+			default: return Key.Unknown;
+			}
+		}
+
+		void ProcessInput (Responder handler)
+		{
+			int wch;
+			var code = Curses.get_wch (out wch);
+			if (code == Curses.KEY_CODE_YES) {
+				if (wch == Curses.KeyResize) {
+					if (Curses.CheckWinChange ()) {
+						terminalResized ();
+						return;
+					}
+				}
+				if (code == Curses.KeyMouse) {
+					// TODO
+					// Curses.MouseEvent ev;
+					// Curses.getmouse (out ev);
+					// handler.HandleMouse ();
+					return;
+				}
+				handler.ProcessKey (new KeyEvent (MapCursesKey (wch)));
+				return;
+			}
+
+			// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter.
+			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));
+			} else
+				handler.ProcessKey (new KeyEvent ((Key)wch));
+		}
+
+		public override void PrepareToRun (MainLoop mainLoop, Responder handler)
+		{
+			Curses.timeout (-1);
+
+			mainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
+				ProcessInput (handler);
+				return true;
+			});
+
+		}
+
+		public override void DrawFrame (Rect region, bool fill)
+		{
+			int width = region.Width;
+			int height = region.Height;
+			int b;
+
+			Move (region.X, region.Y);
+			AddCh (Curses.ACS_ULCORNER);
+			for (b = 0; b < width - 2; b++)
+				AddCh (Curses.ACS_HLINE);
+			AddCh (Curses.ACS_URCORNER);
+			for (b = 1; b < height - 1; b++) {
+				Move (region.X, region.Y + b);
+				AddCh (Curses.ACS_VLINE);
+				if (fill) {
+					for (int x = 1; x < width - 1; x++)
+						AddCh (' ');
+				} else
+					Move (region.X + width - 1, region.Y + b);
+				AddCh (Curses.ACS_VLINE);
+			}
+			Move (region.X, region.Y + height - 1);
+			AddCh (Curses.ACS_LLCORNER);
+			for (b = 0; b < width - 2; b++)
+				AddCh (Curses.ACS_HLINE);
+			AddCh (Curses.ACS_LRCORNER);
+		}
+
+		public override void Init (Action terminalResized)
+		{
+			if (window != null)
+				return;
+
+			try {
+				window = Curses.initscr ();
+			} catch (Exception e) {
+				Console.WriteLine ("Curses failed to initialize, the exception is: " + e);
+			}
+			Curses.raw ();
+			Curses.noecho ();
+			Curses.Window.Standard.keypad (true);
+			this.terminalResized = terminalResized;
+
+			Colors.Base = new ColorScheme ();
+			Colors.Dialog = new ColorScheme ();
+			Colors.Menu = new ColorScheme ();
+			Colors.Error = new ColorScheme ();
+			Clip = new Rect (0, 0, Cols, Rows);
+			if (Curses.HasColors) {
+				Curses.StartColor ();
+				Curses.UseDefaultColors ();
+
+				Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE);
+				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);
+				Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
+				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);
+				Colors.Dialog.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_CYAN);
+
+				Colors.Error.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED);
+				Colors.Error.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
+				Colors.Error.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_RED);
+				Colors.Error.HotFocus = Colors.Error.HotNormal;
+			} else {
+				Colors.Base.Normal = Curses.A_NORMAL;
+				Colors.Base.Focus = Curses.A_REVERSE;
+				Colors.Base.HotNormal = Curses.A_BOLD;
+				Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
+				Colors.Menu.Normal = Curses.A_REVERSE;
+				Colors.Menu.Focus = Curses.A_NORMAL;
+				Colors.Menu.HotNormal = Curses.A_BOLD;
+				Colors.Menu.HotFocus = Curses.A_NORMAL;
+				Colors.Dialog.Normal = Curses.A_REVERSE;
+				Colors.Dialog.Focus = Curses.A_NORMAL;
+				Colors.Dialog.HotNormal = Curses.A_BOLD;
+				Colors.Dialog.HotFocus = Curses.A_NORMAL;
+				Colors.Error.Normal = Curses.A_BOLD;
+				Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
+				Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
+				Colors.Error.HotFocus = Curses.A_REVERSE;
+			}
+		}
+	}
 }

+ 1 - 1
Event.cs

@@ -76,7 +76,7 @@ namespace Terminal {
 
     public struct KeyEvent {
         public Key Key;
-        public int KeyValue => (int)KeyValue;
+        public int KeyValue => (int)Key;
         public bool IsAlt => (Key & Key.AltMask) != 0;
         public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
 

+ 5 - 1
TODO.md

@@ -58,6 +58,10 @@ model, but needs revisiting in the new model.
 On the demo, press tab twice, instead of selecting Ok, the first tab
 does nothing, the second tab clears the screen.
 
+	=> Explanation: the Window gets a NeedsDisplay, so it displays
+	   tiself, but the contentView does not have NeedsDisplay
+	   set recursively, so it does not render any of the subviews
+
 # Merge Responder into View
 
-# Make HasFocus implicitly call SetNeedsDisplay
+# Make HasFocus implicitly call SetNeedsDisplay

+ 172 - 172
Views/Label.cs

@@ -4,177 +4,177 @@ using System.Collections.Generic;
 using System.Linq;
 
 namespace Terminal {
-    public enum TextAlignment {
-        Left, Right, Centered, Justified
-    }
-
-    /// <summary>
-    ///   Label widget, displays a string at a given position, can include multiple lines.
-    /// </summary>
-    public class Label : View {
-        List<string> lines = new List<string> ();
-        bool recalcPending = true;
-        string text;
-        TextAlignment textAlignment;
-
-        static Rect CalcRect (int x, int y, string s)
-        {
-            int mw = 0;
-            int ml = 1;
-
-            int cols = 0;
-            foreach (var c in s) {
-                if (c == '\n'){
-                    ml++;
-                    if (cols > mw)
-                        mw = cols;
-                    cols = 0;
-                } else
-                    cols++;
-            }
-            return new Rect (x, y, cols, ml);
-        }
-
-        /// <summary>
-        ///   Public constructor: creates a label at the given
-        ///   coordinate with the given string, computes the bounding box
-        ///   based on the size of the string, assumes that the string contains
-        ///   newlines for multiple lines, no special breaking rules are used.
-        /// </summary>
-        public Label (int x, int y, string text) : this (CalcRect (x, y, text), text)
-        {
-        }
-
-        /// <summary>
-        ///   Public constructor: creates a label at the given
-        ///   coordinate with the given string and uses the specified
-        ///   frame for the string.
-        /// </summary>
-        public Label (Rect rect, string text) : base (rect)
-        {
-            this.text = text;
-        }
-
-        static char [] whitespace = new char [] { ' ', '\t' };
-
-        string ClipAndJustify (string str)
-        {
-            int slen = str.Length;
-            if (slen > Frame.Width)
-                return str.Substring (0, Frame.Width);
-            else {
-                if (textAlignment == TextAlignment.Justified) {
-                    var words = str.Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
-                    int textCount = words.Sum ((arg) => arg.Length);
-
-                    var spaces = (Frame.Width - textCount) / (words.Length - 1);
-                    var extras = (Frame.Width - textCount) % words.Length;
-                    var s = new System.Text.StringBuilder ();
-                    //s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
-                    for (int w = 0; w < words.Length; w++) {
-                        var x = words [w];
-                        s.Append (x);
-                        if (w + 1 < words.Length)
-                            for (int i = 0; i < spaces; i++)
-                                s.Append (' ');
-                        if (extras > 0) {
-                            s.Append ('_');
-                            extras--;
-                        }
-                    }
-                    return s.ToString ();
-                }
-                return str;
-            }
-        }
-
-        void Recalc ()
-        {
-            lines.Clear ();
-            if (text.IndexOf ('\n') == -1) {
-                lines.Add (ClipAndJustify (text));
-                return;
-            }
-            int textLen = text.Length;
-            int lp = 0;
-            for (int i = 0; i < textLen; i++) {
-                char c = text [i];
-
-                if (c == '\n') {
-                    lines.Add (ClipAndJustify (text.Substring (lp, i - lp)));
-                    lp = i + 1;
-                }
-            }
-            recalcPending = false;
-        }
-
-        public override void Redraw (Rect region)
-        {
-            if (recalcPending)
-                Recalc ();
-            
-            if (TextColor != -1)
-                Driver.SetAttribute (TextColor);
-            else
-                Driver.SetAttribute(Colors.Base.Normal);
-
-            Clear ();
-            Move (Frame.X, Frame.Y);
-            for (int line = 0; line < lines.Count; line++) {
-                if (line < region.Top || line >= region.Bottom)
-                    continue;
-                var str = lines [line];
-                int x;
-                switch (textAlignment) {
-                case TextAlignment.Left:
-                case TextAlignment.Justified:
-                    x = 0;
-                    break;
-                case TextAlignment.Right:
-                    x = Frame.Right - str.Length;
-                    break;
-                case TextAlignment.Centered:
-                    x = Frame.Left + (Frame.Width - str.Length) / 2;
-                    break;
-                default:
-                    throw new ArgumentOutOfRangeException ();
-                }
-                Move (x, line);
-                Driver.AddStr (str);
-            }
-        }
-
-        /// <summary>
-        ///   The text displayed by this widget.
-        /// </summary>
-        public virtual string Text {
-            get => text;
-            set {
-                text = value;
-                recalcPending = true;
-                SetNeedsDisplay ();
-            }
-        }
-
-        public TextAlignment TextAlignment {
-            get => textAlignment;
-            set {
-                textAlignment = value;
-                SetNeedsDisplay ();
-            }
-        }
-
-        /// <summary>
-        ///   The color used for the label
-        /// </summary>        
-        Attribute textColor = -1;
-        public Attribute TextColor {
-            get => textColor;
-            set {
-                textColor = value;
-                SetNeedsDisplay ();
-            }
-        }
-    }
+	public enum TextAlignment {
+		Left, Right, Centered, Justified
+	}
+
+	/// <summary>
+	///   Label widget, displays a string at a given position, can include multiple lines.
+	/// </summary>
+	public class Label : View {
+		List<string> lines = new List<string> ();
+		bool recalcPending = true;
+		string text;
+		TextAlignment textAlignment;
+
+		static Rect CalcRect (int x, int y, string s)
+		{
+			int mw = 0;
+			int ml = 1;
+
+			int cols = 0;
+			foreach (var c in s) {
+				if (c == '\n') {
+					ml++;
+					if (cols > mw)
+						mw = cols;
+					cols = 0;
+				} else
+					cols++;
+			}
+			return new Rect (x, y, cols, ml);
+		}
+
+		/// <summary>
+		///   Public constructor: creates a label at the given
+		///   coordinate with the given string, computes the bounding box
+		///   based on the size of the string, assumes that the string contains
+		///   newlines for multiple lines, no special breaking rules are used.
+		/// </summary>
+		public Label (int x, int y, string text) : this (CalcRect (x, y, text), text)
+		{
+		}
+
+		/// <summary>
+		///   Public constructor: creates a label at the given
+		///   coordinate with the given string and uses the specified
+		///   frame for the string.
+		/// </summary>
+		public Label (Rect rect, string text) : base (rect)
+		{
+			this.text = text;
+		}
+
+		static char [] whitespace = new char [] { ' ', '\t' };
+
+		string ClipAndJustify (string str)
+		{
+			int slen = str.Length;
+			if (slen > Frame.Width)
+				return str.Substring (0, Frame.Width);
+			else {
+				if (textAlignment == TextAlignment.Justified) {
+					var words = str.Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
+					int textCount = words.Sum ((arg) => arg.Length);
+
+					var spaces = (Frame.Width - textCount) / (words.Length - 1);
+					var extras = (Frame.Width - textCount) % words.Length;
+					var s = new System.Text.StringBuilder ();
+					//s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
+					for (int w = 0; w < words.Length; w++) {
+						var x = words [w];
+						s.Append (x);
+						if (w + 1 < words.Length)
+							for (int i = 0; i < spaces; i++)
+								s.Append (' ');
+						if (extras > 0) {
+							s.Append ('_');
+							extras--;
+						}
+					}
+					return s.ToString ();
+				}
+				return str;
+			}
+		}
+
+		void Recalc ()
+		{
+			recalcPending = false;
+			lines.Clear ();
+			if (text.IndexOf ('\n') == -1) {
+				lines.Add (ClipAndJustify (text));
+				return;
+			}
+			int textLen = text.Length;
+			int lp = 0;
+			for (int i = 0; i < textLen; i++) {
+				char c = text [i];
+
+				if (c == '\n') {
+					lines.Add (ClipAndJustify (text.Substring (lp, i - lp)));
+					lp = i + 1;
+				}
+			}
+		}
+
+		public override void Redraw (Rect region)
+		{
+			if (recalcPending)
+				Recalc ();
+
+			if (TextColor != -1)
+				Driver.SetAttribute (TextColor);
+			else
+				Driver.SetAttribute (Colors.Base.Normal);
+
+			Clear ();
+			Move (Frame.X, Frame.Y);
+			for (int line = 0; line < lines.Count; line++) {
+				if (line < region.Top || line >= region.Bottom)
+					continue;
+				var str = lines [line];
+				int x;
+				switch (textAlignment) {
+				case TextAlignment.Left:
+				case TextAlignment.Justified:
+					x = 0;
+					break;
+				case TextAlignment.Right:
+					x = Frame.Right - str.Length;
+					break;
+				case TextAlignment.Centered:
+					x = Frame.Left + (Frame.Width - str.Length) / 2;
+					break;
+				default:
+					throw new ArgumentOutOfRangeException ();
+				}
+				Move (x, line);
+				Driver.AddStr (str);
+			}
+		}
+
+		/// <summary>
+		///   The text displayed by this widget.
+		/// </summary>
+		public virtual string Text {
+			get => text;
+			set {
+				text = value;
+				recalcPending = true;
+				SetNeedsDisplay ();
+			}
+		}
+
+		public TextAlignment TextAlignment {
+			get => textAlignment;
+			set {
+				textAlignment = value;
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		///   The color used for the label
+		/// </summary>        
+		Attribute textColor = -1;
+		public Attribute TextColor {
+			get => textColor;
+			set {
+				textColor = value;
+				SetNeedsDisplay ();
+			}
+		}
+	}
 
 }

+ 293 - 294
Views/TextField.cs

@@ -3,299 +3,298 @@ using System.Collections.Generic;
 using System.Linq;
 
 namespace Terminal {
-    /// <summary>
-    ///   Text data entry widget
-    /// </summary>
-    /// <remarks>
-    ///   The Entry widget provides Emacs-like editing
-    ///   functionality,  and mouse support.
-    /// </remarks>
-    public class TextField : View {
-        string text, kill;
-        int first, point;
-        bool used;
-
-        /// <summary>
-        ///   Changed event, raised when the text has clicked.
-        /// </summary>
-        /// <remarks>
-        ///   Client code can hook up to this event, it is
-        ///   raised when the text in the entry changes.
-        /// </remarks>
-        public event EventHandler Changed;
-
-        /// <summary>
-        ///   Public constructor.
-        /// </summary>
-        /// <remarks>
-        /// </remarks>
-        public TextField (int x, int y, int w, string s) : base (new Rect (x, y, w, 1))
-        {
-            if (s == null)
-                s = "";
-
-            text = s;
-            point = s.Length;
-            first = point > w ? point - w : 0;
-            CanFocus = true;
-            Color = Colors.Dialog.Focus;
-        }
-
-        /// <summary>
-        ///   Sets or gets the text in the entry.
-        /// </summary>
-        /// <remarks>
-        /// </remarks>
-        public string Text {
-            get {
-                return text;
-            }
-
-            set {
-                text = value;
-                if (point > text.Length)
-                    point = text.Length;
-                first = point > Frame.Width ? point - Frame.Width : 0;
-                SetNeedsDisplay ();
-            }
-        }
-
-        /// <summary>
-        ///   Sets the secret property.
-        /// </summary>
-        /// <remarks>
-        ///   This makes the text entry suitable for entering passwords. 
-        /// </remarks>
-        public bool Secret { get; set; }
-
-        Attribute color;
-        /// <summary>
-        /// Sets the color attribute to use (includes foreground and background).
-        /// </summary>
-        /// <value>The color.</value>
-        public Attribute Color {
-            get => color;
-            set {
-                color = value;
-                SetNeedsDisplay ();
-            }
-        }
-
-        /// <summary>
-        ///    The current cursor position.
-        /// </summary>
-        public int CursorPosition { get { return point; } }
-
-        /// <summary>
-        ///   Sets the cursor position.
-        /// </summary>
-        public override void PositionCursor ()
-        {
-            Move (point - first, 0);
-        }
-
-        public override void Redraw (Rect region)
-        {
-            Driver.SetAttribute (Color);
-            Move (0, 0);
-
-            for (int i = 0; i < Frame.Width; i++) {
-                int p = first + i;
-
-                if (p < text.Length) {
-                    Driver.AddCh (Secret ? '*' : text [p]);
-                } else
-                    Driver.AddCh (' ');
-            }
-            PositionCursor ();
-        }
-
-        void Adjust ()
-        {
-            if (point < first)
-                first = point;
-            else if (first + point >= Frame.Width)
-                first = point - (Frame.Width / 3);
-            Redraw (Bounds);
-            Driver.Refresh ();
-        }
-
-        void SetText (string new_text)
-        {
-            text = new_text;
-            if (Changed != null)
-                Changed (this, EventArgs.Empty);
-        }
-
-        public override bool CanFocus {
-            get => true;
-            set { base.CanFocus = value; }
-        }
-
-        public override bool ProcessKey (KeyEvent kb)
-        {
-            switch (kb.Key) {
-            case Key.Delete:
-            case Key.Backspace:
-                if (point == 0)
-                    return true;
-
-                SetText (text.Substring (0, point - 1) + text.Substring (point));
-                point--;
-                Adjust ();
-                break;
-
-                // Home, C-A
-            case Key.Home:
-            case Key.ControlA:
-                point = 0;
-                Adjust ();
-                break;
-
-            case Key.CursorLeft:
-            case Key.ControlB:
-                if (point > 0) {
-                    point--;
-                    Adjust ();
-                }
-                break;
-
-            case Key.ControlD: // Delete
-                if (point == text.Length)
-                    break;
-                SetText (text.Substring (0, point) + text.Substring (point + 1));
-                Adjust ();
-                break;
-
-            case Key.ControlE: // End
-                point = text.Length;
-                Adjust ();
-                break;
-
-            case Key.CursorRight:
-            case Key.ControlF:
-                if (point == text.Length)
-                    break;
-                point++;
-                Adjust ();
-                break;
-
-            case Key.ControlK: // kill-to-end
-                kill = text.Substring (point);
-                SetText (text.Substring (0, point));
-                Adjust ();
-                break;
-
-            case Key.ControlY: // Control-y, yank
-                if (kill == null)
-                    return true;
-
-                if (point == text.Length) {
-                    SetText (text + kill);
-                    point = text.Length;
-                } else {
-                    SetText (text.Substring (0, point) + kill + text.Substring (point));
-                    point += kill.Length;
-                }
-                Adjust ();
-                break;
-
-            case (Key)((int)'b' + Key.AltMask):
-                int bw = WordBackward (point);
-                if (bw != -1)
-                    point = bw;
-                Adjust ();
-                break;
-
-            case (Key)((int)'f' + Key.AltMask):
-                int fw = WordForward (point);
-                if (fw != -1)
-                    point = fw;
-                Adjust ();
-                break;
-
-            default:
-                // Ignore other control characters.
-                if (kb.Key < Key.Space || kb.Key > Key.CharMask)
-                    return false;
-
-                if (used) {
-                    if (point == text.Length) {
-                        SetText (text + (char)kb.Key);
-                    } else {
-                        SetText (text.Substring (0, point) + (char)kb.Key + text.Substring (point));
-                    }
-                    point++;
-                } else {
-                    SetText ("" + (char)kb.Key);
-                    first = 0;
-                    point = 1;
-                }
-                used = true;
-                Adjust ();
-                return true;
-            }
-            used = true;
-            return true;
-        }
-
-        int WordForward (int p)
-        {
-            if (p >= text.Length)
-                return -1;
-
-            int i = p;
-            if (Char.IsPunctuation (text [p]) || Char.IsWhiteSpace (text [p])) {
-                for (; i < text.Length; i++) {
-                    if (Char.IsLetterOrDigit (text [i]))
-                        break;
-                }
-                for (; i < text.Length; i++) {
-                    if (!Char.IsLetterOrDigit (text [i]))
-                        break;
-                }
-            } else {
-                for (; i < text.Length; i++) {
-                    if (!Char.IsLetterOrDigit (text [i]))
-                        break;
-                }
-            }
-            if (i != p)
-                return i;
-            return -1;
-        }
-
-        int WordBackward (int p)
-        {
-            if (p == 0)
-                return -1;
-
-            int i = p - 1;
-            if (i == 0)
-                return 0;
-
-            if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text [i])) {
-                for (; i >= 0; i--) {
-                    if (Char.IsLetterOrDigit (text [i]))
-                        break;
-                }
-                for (; i >= 0; i--) {
-                    if (!Char.IsLetterOrDigit (text [i]))
-                        break;
-                }
-            } else {
-                for (; i >= 0; i--) {
-                    if (!Char.IsLetterOrDigit (text [i]))
-                        break;
-                }
-            }
-            i++;
-
-            if (i != p)
-                return i;
-
-            return -1;
-        }
+	/// <summary>
+	///   Text data entry widget
+	/// </summary>
+	/// <remarks>
+	///   The Entry widget provides Emacs-like editing
+	///   functionality,  and mouse support.
+	/// </remarks>
+	public class TextField : View {
+		string text, kill;
+		int first, point;
+		bool used;
+
+		/// <summary>
+		///   Changed event, raised when the text has clicked.
+		/// </summary>
+		/// <remarks>
+		///   Client code can hook up to this event, it is
+		///   raised when the text in the entry changes.
+		/// </remarks>
+		public event EventHandler Changed;
+
+		/// <summary>
+		///   Public constructor.
+		/// </summary>
+		/// <remarks>
+		/// </remarks>
+		public TextField (int x, int y, int w, string s) : base (new Rect (x, y, w, 1))
+		{
+			if (s == null)
+				s = "";
+
+			text = s;
+			point = s.Length;
+			first = point > w ? point - w : 0;
+			CanFocus = true;
+			Color = Colors.Dialog.Focus;
+		}
+
+		/// <summary>
+		///   Sets or gets the text in the entry.
+		/// </summary>
+		/// <remarks>
+		/// </remarks>
+		public string Text {
+			get {
+				return text;
+			}
+
+			set {
+				text = value;
+				if (point > text.Length)
+					point = text.Length;
+				first = point > Frame.Width ? point - Frame.Width : 0;
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		///   Sets the secret property.
+		/// </summary>
+		/// <remarks>
+		///   This makes the text entry suitable for entering passwords. 
+		/// </remarks>
+		public bool Secret { get; set; }
+
+		Attribute color;
+		/// <summary>
+		/// Sets the color attribute to use (includes foreground and background).
+		/// </summary>
+		/// <value>The color.</value>
+		public Attribute Color {
+			get => color;
+			set {
+				color = value;
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		///    The current cursor position.
+		/// </summary>
+		public int CursorPosition { get { return point; } }
+
+		/// <summary>
+		///   Sets the cursor position.
+		/// </summary>
+		public override void PositionCursor ()
+		{
+			Move (point - first, 0);
+		}
+
+		public override void Redraw (Rect region)
+		{
+			Driver.SetAttribute (Color);
+			Move (0, 0);
+
+			for (int i = 0; i < Frame.Width; i++) {
+				int p = first + i;
+
+				if (p < text.Length) {
+					Driver.AddCh (Secret ? '*' : text [p]);
+				} else
+					Driver.AddCh ('_');
+			}
+			PositionCursor ();
+		}
+
+		void Adjust ()
+		{
+			if (point < first)
+				first = point;
+			else if (first + point >= Frame.Width)
+				first = point - (Frame.Width / 3);
+			SetNeedsDisplay ();
+		}
+
+		void SetText (string new_text)
+		{
+			text = new_text;
+			if (Changed != null)
+				Changed (this, EventArgs.Empty);
+		}
+
+		public override bool CanFocus {
+			get => true;
+			set { base.CanFocus = value; }
+		}
+
+		public override bool ProcessKey (KeyEvent kb)
+		{
+			switch (kb.Key) {
+			case Key.Delete:
+			case Key.Backspace:
+				if (point == 0)
+					return true;
+
+				SetText (text.Substring (0, point - 1) + text.Substring (point));
+				point--;
+				Adjust ();
+				break;
+
+			// Home, C-A
+			case Key.Home:
+			case Key.ControlA:
+				point = 0;
+				Adjust ();
+				break;
+
+			case Key.CursorLeft:
+			case Key.ControlB:
+				if (point > 0) {
+					point--;
+					Adjust ();
+				}
+				break;
+
+			case Key.ControlD: // Delete
+				if (point == text.Length)
+					break;
+				SetText (text.Substring (0, point) + text.Substring (point + 1));
+				Adjust ();
+				break;
+
+			case Key.ControlE: // End
+				point = text.Length;
+				Adjust ();
+				break;
+
+			case Key.CursorRight:
+			case Key.ControlF:
+				if (point == text.Length)
+					break;
+				point++;
+				Adjust ();
+				break;
+
+			case Key.ControlK: // kill-to-end
+				kill = text.Substring (point);
+				SetText (text.Substring (0, point));
+				Adjust ();
+				break;
+
+			case Key.ControlY: // Control-y, yank
+				if (kill == null)
+					return true;
+
+				if (point == text.Length) {
+					SetText (text + kill);
+					point = text.Length;
+				} else {
+					SetText (text.Substring (0, point) + kill + text.Substring (point));
+					point += kill.Length;
+				}
+				Adjust ();
+				break;
+
+			case (Key)((int)'b' + Key.AltMask):
+				int bw = WordBackward (point);
+				if (bw != -1)
+					point = bw;
+				Adjust ();
+				break;
+
+			case (Key)((int)'f' + Key.AltMask):
+				int fw = WordForward (point);
+				if (fw != -1)
+					point = fw;
+				Adjust ();
+				break;
+
+			default:
+				// Ignore other control characters.
+				if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+					return false;
+
+				if (used) {
+					if (point == text.Length) {
+						SetText (text + (char)kb.Key);
+					} else {
+						SetText (text.Substring (0, point) + (char)kb.Key + text.Substring (point));
+					}
+					point++;
+				} else {
+					SetText ("" + (char)kb.Key);
+					first = 0;
+					point = 1;
+				}
+				used = true;
+				Adjust ();
+				return true;
+			}
+			used = true;
+			return true;
+		}
+
+		int WordForward (int p)
+		{
+			if (p >= text.Length)
+				return -1;
+
+			int i = p;
+			if (Char.IsPunctuation (text [p]) || Char.IsWhiteSpace (text [p])) {
+				for (; i < text.Length; i++) {
+					if (Char.IsLetterOrDigit (text [i]))
+						break;
+				}
+				for (; i < text.Length; i++) {
+					if (!Char.IsLetterOrDigit (text [i]))
+						break;
+				}
+			} else {
+				for (; i < text.Length; i++) {
+					if (!Char.IsLetterOrDigit (text [i]))
+						break;
+				}
+			}
+			if (i != p)
+				return i;
+			return -1;
+		}
+
+		int WordBackward (int p)
+		{
+			if (p == 0)
+				return -1;
+
+			int i = p - 1;
+			if (i == 0)
+				return 0;
+
+			if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text [i])) {
+				for (; i >= 0; i--) {
+					if (Char.IsLetterOrDigit (text [i]))
+						break;
+				}
+				for (; i >= 0; i--) {
+					if (!Char.IsLetterOrDigit (text [i]))
+						break;
+				}
+			} else {
+				for (; i >= 0; i--) {
+					if (!Char.IsLetterOrDigit (text [i]))
+						break;
+				}
+			}
+			i++;
+
+			if (i != p)
+				return i;
+
+			return -1;
+		}
 
 #if false
         public override void ProcessMouse (Curses.MouseEvent ev)
@@ -315,7 +314,7 @@ namespace Terminal {
             SetNeedsDisplay ();
         }
 #endif
-    }
+	}
 
 
 }

+ 16 - 16
demo.cs

@@ -1,20 +1,20 @@
 using Terminal;
 
 class Demo {
-    static void Main ()
-    {
-        Application.Init ();
-        var top = Application.Top;
-        var win = new Window (new Rect (0, 0, 80, 24), "Hello") {
-            new Label (new Rect (0, 0, 40, 3), "1-Hello world, how are you doing today") { TextAlignment = TextAlignment.Left },
-            new Label (new Rect (0, 4, 40, 3), "2-Hello world, how are you doing today") { TextAlignment = TextAlignment.Right},
-            new Label (new Rect (0, 8, 40, 3), "3-Hello world, how are you doing today") { TextAlignment = TextAlignment.Centered },
-            new Label (new Rect (0, 12, 40, 3), "4-Hello world, how are you doing today") { TextAlignment = TextAlignment.Justified},
-            new Label (3, 14, "Login: "),
-            new TextField (10, 14, 40, ""),
-            new Button (3, 16, "Ok")
-        };
-        top.Add (win);
-        Application.Run ();
-    }
+	static void Main ()
+	{
+		Application.Init ();
+		var top = Application.Top;
+		var win = new Window (new Rect (0, 0, 80, 24), "Hello") {
+			new Label (new Rect (0, 0, 40, 3), "1-Hello world, how are you doing today") { TextAlignment = TextAlignment.Left },
+			new Label (new Rect (0, 4, 40, 3), "2-Hello world, how are you doing today") { TextAlignment = TextAlignment.Right},
+			new Label (new Rect (0, 8, 40, 3), "3-Hello world, how are you doing today") { TextAlignment = TextAlignment.Centered },
+			new Label (new Rect (0, 12, 40, 3), "4-Hello world, how are you doing today") { TextAlignment = TextAlignment.Justified},
+			//new Button (3, 16, "Ok"),
+			new Label (3, 14, "Login: "),
+			new TextField (10, 14, 40, ""),
+		};
+		top.Add (win);
+		Application.Run ();
+	}
 }

Some files were not shown because too many files changed in this diff