diff --git a/Core.cs b/Core.cs
index 4c57f6f5b..39c1b129d 100644
--- a/Core.cs
+++ b/Core.cs
@@ -14,558 +14,612 @@ using System.Collections.Generic;
namespace Terminal {
- public class Responder {
- public virtual bool CanFocus { get; set; }
- public bool HasFocus { get; internal set; }
+ public class Responder {
+ public virtual bool CanFocus { get; set; }
+ public bool HasFocus { get; internal set; }
- // Key handling
- ///
- /// This method can be overwritten by view that
- /// want to provide accelerator functionality
- /// (Alt-key for example).
- ///
- ///
- ///
- /// Before keys are sent to the subview on the
- /// current view, all the views are
- /// processed and the key is passed to the widgets
- /// to allow some of them to process the keystroke
- /// as a hot-key.
- ///
- /// For example, if you implement a button that
- /// has a hotkey ok "o", you would catch the
- /// combination Alt-o here. If the event is
- /// caught, you must return true to stop the
- /// keystroke from being dispatched to other
- /// views.
- ///
- ///
+ // Key handling
+ ///
+ /// This method can be overwritten by view that
+ /// want to provide accelerator functionality
+ /// (Alt-key for example).
+ ///
+ ///
+ ///
+ /// Before keys are sent to the subview on the
+ /// current view, all the views are
+ /// processed and the key is passed to the widgets
+ /// to allow some of them to process the keystroke
+ /// as a hot-key.
+ ///
+ /// For example, if you implement a button that
+ /// has a hotkey ok "o", you would catch the
+ /// combination Alt-o here. If the event is
+ /// caught, you must return true to stop the
+ /// keystroke from being dispatched to other
+ /// views.
+ ///
+ ///
- public virtual bool ProcessHotKey (KeyEvent kb)
- {
- return false;
- }
+ public virtual bool ProcessHotKey (KeyEvent kb)
+ {
+ return false;
+ }
- ///
- /// If the view is focused, gives the view a
- /// chance to process the keystroke.
- ///
- ///
- ///
- /// Views can override this method if they are
- /// interested in processing the given keystroke.
- /// If they consume the keystroke, they must
- /// return true to stop the keystroke from being
- /// processed by other widgets or consumed by the
- /// widget engine. If they return false, the
- /// keystroke will be passed using the ProcessColdKey
- /// method to other views to process.
- ///
- ///
- public virtual bool ProcessKey (KeyEvent kb)
- {
- return false;
- }
+ ///
+ /// If the view is focused, gives the view a
+ /// chance to process the keystroke.
+ ///
+ ///
+ ///
+ /// Views can override this method if they are
+ /// interested in processing the given keystroke.
+ /// If they consume the keystroke, they must
+ /// return true to stop the keystroke from being
+ /// processed by other widgets or consumed by the
+ /// widget engine. If they return false, the
+ /// keystroke will be passed using the ProcessColdKey
+ /// method to other views to process.
+ ///
+ ///
+ public virtual bool ProcessKey (KeyEvent kb)
+ {
+ return false;
+ }
- ///
- /// This method can be overwritten by views that
- /// want to provide accelerator functionality
- /// (Alt-key for example), but without
- /// interefering with normal ProcessKey behavior.
- ///
- ///
- ///
- /// After keys are sent to the subviews on the
- /// current view, all the view are
- /// processed and the key is passed to the views
- /// to allow some of them to process the keystroke
- /// as a cold-key.
- ///
- /// This functionality is used, for example, by
- /// default buttons to act on the enter key.
- /// Processing this as a hot-key would prevent
- /// non-default buttons from consuming the enter
- /// keypress when they have the focus.
- ///
- ///
- public virtual bool ProcessColdKey (KeyEvent kb)
- {
- return false;
- }
+ ///
+ /// This method can be overwritten by views that
+ /// want to provide accelerator functionality
+ /// (Alt-key for example), but without
+ /// interefering with normal ProcessKey behavior.
+ ///
+ ///
+ ///
+ /// After keys are sent to the subviews on the
+ /// current view, all the view are
+ /// processed and the key is passed to the views
+ /// to allow some of them to process the keystroke
+ /// as a cold-key.
+ ///
+ /// This functionality is used, for example, by
+ /// default buttons to act on the enter key.
+ /// Processing this as a hot-key would prevent
+ /// non-default buttons from consuming the enter
+ /// keypress when they have the focus.
+ ///
+ ///
+ public virtual bool ProcessColdKey (KeyEvent kb)
+ {
+ return false;
+ }
- // Mouse events
- public virtual void MouseEvent (Event.Mouse me) { }
- }
+ // Mouse events
+ public virtual void MouseEvent (Event.Mouse me) { }
+ }
- public class View : Responder, IEnumerable {
- View container = null;
- View focused = null;
- public static ConsoleDriver Driver = Application.Driver;
- public static IList empty = new List (0).AsReadOnly ();
- List subviews;
- public IList Subviews => subviews == null ? empty : subviews.AsReadOnly ();
- internal bool NeedDisplay { get; private set; } = true;
+ public class View : Responder, IEnumerable {
+ string id = "";
+ View container = null;
+ View focused = null;
+ public static ConsoleDriver Driver = Application.Driver;
+ public static IList empty = new List (0).AsReadOnly ();
+ List subviews;
+ public IList Subviews => subviews == null ? empty : subviews.AsReadOnly ();
+ internal bool NeedDisplay { get; private set; } = true;
- // The frame for the object
- Rect frame;
+ // The frame for the object
+ Rect frame;
- // The frame for this view
- public Rect Frame {
- get => frame;
- set {
- frame = value;
- SetNeedsDisplay ();
- }
- }
+ public string Id {
+ get => id;
+ set {
+ id = value;
+ }
+ }
- public IEnumerator GetEnumerator ()
- {
- foreach (var v in subviews)
- yield return v;
- }
+ // The frame for this view
+ public Rect Frame {
+ get => frame;
+ set {
+ frame = value;
+ SetNeedsDisplay ();
+ }
+ }
- public Rect Bounds {
- get => new Rect (Point.Empty, Frame.Size);
- set {
- Frame = new Rect (frame.Location, value.Size);
- }
- }
+ public IEnumerator GetEnumerator ()
+ {
+ foreach (var v in subviews)
+ yield return v;
+ }
- public View SuperView => container;
+ public Rect Bounds {
+ get => new Rect (Point.Empty, Frame.Size);
+ set {
+ Frame = new Rect (frame.Location, value.Size);
+ }
+ }
- public View (Rect frame)
- {
- this.Frame = frame;
- CanFocus = false;
- }
+ public View SuperView => container;
- ///
- /// Invoke to flag that this view needs to be redisplayed, by any code
- /// that alters the state of the view.
- ///
- public void SetNeedsDisplay ()
- {
- NeedDisplay = true;
- if (container != null)
- container.SetNeedsDisplay ();
- }
+ public View (Rect frame)
+ {
+ this.Frame = frame;
+ CanFocus = false;
+ }
- ///
- /// Adds a subview to this view.
- ///
- ///
- ///
- public virtual void Add (View view)
- {
- if (view == null)
- return;
- if (subviews == null)
- subviews = new List ();
- subviews.Add (view);
- view.container = this;
- if (view.CanFocus)
- CanFocus = true;
- }
+ ///
+ /// Invoke to flag that this view needs to be redisplayed, by any code
+ /// that alters the state of the view.
+ ///
+ public void SetNeedsDisplay ()
+ {
+ NeedDisplay = true;
+ if (container != null)
+ container.ChildNeedsDisplay ();
+ }
- ///
- /// Removes all the widgets from this container.
- ///
- ///
- ///
- public virtual void RemoveAll ()
- {
- if (subviews == null)
- return;
+ ///
+ /// Sets the NeedsDisplay flag on this view and any child subviews.
+ ///
+ public void SetNeedsDisplayRecursive ()
+ {
+ NeedDisplay = true;
+ if (subviews == null)
+ return;
+ foreach (var view in subviews)
+ view.SetNeedsDisplayRecursive ();
+ if (container != null)
+ container.ChildNeedsDisplay ();
+ }
- while (subviews.Count > 0) {
- var view = subviews [0];
- Remove (view);
- subviews.RemoveAt (0);
- }
- }
+ internal bool childNeedsDisplay;
- ///
- /// Removes a widget from this container.
- ///
- ///
- ///
- public virtual void Remove (View view)
- {
- if (view == null)
- return;
+ public void ChildNeedsDisplay ()
+ {
+ childNeedsDisplay = true;
+ if (container != null)
+ container.ChildNeedsDisplay ();
+ }
- subviews.Remove (view);
- view.container = null;
+ ///
+ /// Adds a subview to this view.
+ ///
+ ///
+ ///
+ public virtual void Add (View view)
+ {
+ if (view == null)
+ return;
+ if (subviews == null)
+ subviews = new List ();
+ subviews.Add (view);
+ view.container = this;
+ if (view.CanFocus)
+ CanFocus = true;
+ }
- if (subviews.Count < 1)
- this.CanFocus = false;
- }
+ ///
+ /// Removes all the widgets from this container.
+ ///
+ ///
+ ///
+ public virtual void RemoveAll ()
+ {
+ if (subviews == null)
+ return;
- ///
- /// Clears the view region with the current color.
- ///
- ///
- ///
- /// This clears the entire region used by this view.
- ///
- ///
- public void Clear ()
- {
- var h = Frame.Height;
- var w = Frame.Width;
- for (int line = 0; line < h; line++) {
- Move (0, line);
- for (int col = 0; col < w; col++)
- Driver.AddCh (' ');
- }
- }
+ while (subviews.Count > 0) {
+ var view = subviews [0];
+ Remove (view);
+ subviews.RemoveAt (0);
+ }
+ }
- ///
- /// Converts the (col,row) position from the view into a screen (col,row). The values are clamped to (0..ScreenDim-1)
- ///
- /// View-based column.
- /// View-based row.
- /// Absolute column, display relative.
- /// Absolute row, display relative.
- internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
- {
- // Computes the real row, col relative to the screen.
- rrow = row + frame.Y;
- rcol = col + frame.X;
- var ccontainer = container;
- while (ccontainer != null) {
- rrow += ccontainer.frame.Y;
- rcol += ccontainer.frame.X;
- ccontainer = ccontainer.container;
- }
+ ///
+ /// Removes a widget from this container.
+ ///
+ ///
+ ///
+ public virtual void Remove (View view)
+ {
+ if (view == null)
+ return;
- // The following ensures that the cursor is always in the screen boundaries.
- if (clipped) {
- rrow = Math.Max (0, Math.Min (rrow, Driver.Rows - 1));
- rcol = Math.Max (0, Math.Min (rcol, Driver.Cols - 1));
- }
- }
+ subviews.Remove (view);
+ view.container = null;
- Rect RectToScreen (Rect rect)
- {
- ViewToScreen (rect.X, rect.Y, out var x, out var y, clipped: false);
- return new Rect (x, y, rect.Width, rect.Height);
- }
+ if (subviews.Count < 1)
+ this.CanFocus = false;
+ }
- Rect ScreenClip (Rect rect)
- {
- var x = rect.X < 0 ? 0 : rect.X;
- var y = rect.Y < 0 ? 0 : rect.Y;
- var w = rect.X + rect.Width >= Driver.Cols ? Driver.Cols - rect.X : rect.Width;
- var h = rect.Y + rect.Height >= Driver.Rows ? Driver.Rows - rect.Y : rect.Height;
+ ///
+ /// Clears the view region with the current color.
+ ///
+ ///
+ ///
+ /// This clears the entire region used by this view.
+ ///
+ ///
+ public void Clear ()
+ {
+ var h = Frame.Height;
+ var w = Frame.Width;
+ for (int line = 0; line < h; line++) {
+ Move (0, line);
+ for (int col = 0; col < w; col++)
+ Driver.AddCh (' ');
+ }
+ }
- return new Rect (x, y, w, h);
- }
+ ///
+ /// Converts the (col,row) position from the view into a screen (col,row). The values are clamped to (0..ScreenDim-1)
+ ///
+ /// View-based column.
+ /// View-based row.
+ /// Absolute column, display relative.
+ /// Absolute row, display relative.
+ internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
+ {
+ // Computes the real row, col relative to the screen.
+ rrow = row + frame.Y;
+ rcol = col + frame.X;
+ var ccontainer = container;
+ while (ccontainer != null) {
+ rrow += ccontainer.frame.Y;
+ rcol += ccontainer.frame.X;
+ ccontainer = ccontainer.container;
+ }
- ///
- /// Draws a frame in the current view, clipped by the boundary of this view
- ///
- /// Rectangular region for the frame to be drawn.
- /// If set to true it fill will the contents.
- public void DrawFrame (Rect rect, bool fill = false)
- {
- var scrRect = RectToScreen (rect);
- var savedClip = Driver.Clip;
- Driver.Clip = ScreenClip (RectToScreen (Bounds));
- Driver.DrawFrame (scrRect, fill);
- Driver.Clip = savedClip;
- }
+ // The following ensures that the cursor is always in the screen boundaries.
+ if (clipped) {
+ rrow = Math.Max (0, Math.Min (rrow, Driver.Rows - 1));
+ rcol = Math.Max (0, Math.Min (rcol, Driver.Cols - 1));
+ }
+ }
- ///
- /// This moves the cursor to the specified column and row in the view.
- ///
- /// The move.
- /// Col.
- /// Row.
- public void Move (int col, int row)
- {
- ViewToScreen (col, row, out var rcol, out var rrow);
- Driver.Move (rcol, rrow);
- }
+ // Converts a rectangle in view coordinates to screen coordinates.
+ Rect RectToScreen (Rect rect)
+ {
+ ViewToScreen (rect.X, rect.Y, out var x, out var y, clipped: false);
+ return new Rect (x, y, rect.Width, rect.Height);
+ }
- ///
- /// Positions the cursor in the right position based on the currently focused view in the chain.
- ///
- public virtual void PositionCursor ()
- {
- if (focused != null)
- focused.PositionCursor ();
- else
- Move (frame.X, frame.Y);
- }
+ // Clips a rectangle in screen coordinates to the dimensions currently available on the screen
+ Rect ScreenClip (Rect rect)
+ {
+ var x = rect.X < 0 ? 0 : rect.X;
+ var y = rect.Y < 0 ? 0 : rect.Y;
+ var w = rect.X + rect.Width >= Driver.Cols ? Driver.Cols - rect.X : rect.Width;
+ var h = rect.Y + rect.Height >= Driver.Rows ? Driver.Rows - rect.Y : rect.Height;
- ///
- /// Returns the currently focused view inside this view, or null if nothing is focused.
- ///
- /// The focused.
- public View Focused => focused;
+ return new Rect (x, y, w, h);
+ }
- ///
- /// Displays the specified character in the specified column and row.
- ///
- /// Col.
- /// Row.
- /// Ch.
- public void AddCh (int col, int row, int ch)
- {
- if (row < 0 || col < 0)
- return;
- if (row > frame.Height - 1 || col > frame.Width - 1)
- return;
- Move (col, row);
- Driver.AddCh (ch);
- }
+ ///
+ /// Draws a frame in the current view, clipped by the boundary of this view
+ ///
+ /// Rectangular region for the frame to be drawn.
+ /// If set to true it fill will the contents.
+ public void DrawFrame (Rect rect, bool fill = false)
+ {
+ var scrRect = RectToScreen (rect);
+ var savedClip = Driver.Clip;
+ Driver.Clip = ScreenClip (RectToScreen (Bounds));
+ Driver.DrawFrame (scrRect, fill);
+ Driver.Clip = savedClip;
+ }
- ///
- /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display.
- ///
- public virtual void Redraw (Rect region)
- {
- var clipRect = new Rect (Point.Empty, frame.Size);
+ ///
+ /// This moves the cursor to the specified column and row in the view.
+ ///
+ /// The move.
+ /// Col.
+ /// Row.
+ public void Move (int col, int row)
+ {
+ ViewToScreen (col, row, out var rcol, out var rrow);
+ Driver.Move (rcol, rrow);
+ }
- if (subviews != null) {
- foreach (var view in subviews) {
- if (view.NeedDisplay) {
- if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
+ ///
+ /// Positions the cursor in the right position based on the currently focused view in the chain.
+ ///
+ public virtual void PositionCursor ()
+ {
+ if (focused != null)
+ focused.PositionCursor ();
+ else
+ Move (frame.X, frame.Y);
+ }
- // TODO: optimize this by computing the intersection of region and view.Bounds
- view.Redraw (view.Bounds);
- }
- view.NeedDisplay = false;
- }
- }
- }
- NeedDisplay = false;
- }
+ ///
+ /// Returns the currently focused view inside this view, or null if nothing is focused.
+ ///
+ /// The focused.
+ public View Focused => focused;
- ///
- /// Focuses the specified sub-view.
- ///
- /// View.
- public void SetFocus (View view)
- {
- if (view == null)
- return;
- if (!view.CanFocus)
- return;
- if (focused == view)
- return;
+ ///
+ /// Displays the specified character in the specified column and row.
+ ///
+ /// Col.
+ /// Row.
+ /// Ch.
+ public void AddCh (int col, int row, int ch)
+ {
+ if (row < 0 || col < 0)
+ return;
+ if (row > frame.Height - 1 || col > frame.Width - 1)
+ return;
+ Move (col, row);
+ Driver.AddCh (ch);
+ }
- // Make sure that this view is a subview
- View c;
- for (c = view.container; c != null; c = c.container)
- if (c == this)
- break;
- if (c == null)
- throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+ ///
+ /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display.
+ ///
+ ///
+ /// The region argument is relative to the view itself.
+ ///
+ public virtual void Redraw (Rect region)
+ {
+ var clipRect = new Rect (Point.Empty, frame.Size);
- if (focused != null)
- focused.HasFocus = false;
- focused = view;
- view.HasFocus = true;
- if (view != null)
- view.EnsureFocus ();
- focused.PositionCursor ();
- }
+ if (subviews != null) {
+ foreach (var view in subviews) {
+ if (view.NeedDisplay || view.childNeedsDisplay) {
+ if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
- public override bool ProcessKey (KeyEvent kb)
- {
- if (Focused?.ProcessKey (kb) == true)
- return true;
+ // TODO: optimize this by computing the intersection of region and view.Bounds
+ view.Redraw (view.Bounds);
+ }
+ view.NeedDisplay = false;
+ view.childNeedsDisplay = false;
+ }
+ }
+ }
+ NeedDisplay = false;
+ childNeedsDisplay = false;
+ }
- return false;
- }
+ ///
+ /// Focuses the specified sub-view.
+ ///
+ /// View.
+ public void SetFocus (View view)
+ {
+ if (view == null)
+ return;
+ //Console.WriteLine ($"Request to focus {view}");
+ if (!view.CanFocus)
+ return;
+ if (focused == view)
+ return;
+
+ // Make sure that this view is a subview
+ View c;
+ for (c = view.container; c != null; c = c.container)
+ if (c == this)
+ break;
+ if (c == null)
+ throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+
+ if (focused != null)
+ focused.HasFocus = false;
+ focused = view;
+ view.HasFocus = true;
+ if (view != null)
+ view.EnsureFocus ();
+ focused.PositionCursor ();
+ }
+
+ public override bool ProcessKey (KeyEvent kb)
+ {
+ if (Focused?.ProcessKey (kb) == true)
+ return true;
+
+ return false;
+ }
- ///
- /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
- ///
- public void EnsureFocus ()
- {
- if (focused == null)
- FocusFirst ();
- }
+ ///
+ /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
+ ///
+ public void EnsureFocus ()
+ {
+ if (focused == null)
+ FocusFirst ();
+ }
- ///
- /// Focuses the first focusable subview if one exists.
- ///
- public void FocusFirst ()
- {
- if (subviews == null) {
- SuperView.SetFocus (this);
- return;
- }
-
- foreach (var view in subviews) {
- if (view.CanFocus) {
- SetFocus (view);
- return;
- }
- }
- }
+ ///
+ /// Focuses the first focusable subview if one exists.
+ ///
+ public void FocusFirst ()
+ {
+ if (subviews == null) {
+ SuperView.SetFocus (this);
+ return;
+ }
- ///
- /// Focuses the last focusable subview if one exists.
- ///
- public void FocusLast ()
- {
- if (subviews == null)
- return;
-
- for (int i = subviews.Count; i > 0;) {
- i--;
+ foreach (var view in subviews) {
+ if (view.CanFocus) {
+ SetFocus (view);
+ return;
+ }
+ }
+ }
- View v = subviews [i];
- if (v.CanFocus) {
- SetFocus (v);
- return;
- }
- }
- }
+ ///
+ /// Focuses the last focusable subview if one exists.
+ ///
+ public void FocusLast ()
+ {
+ if (subviews == null)
+ return;
- ///
- /// Focuses the previous view.
- ///
- /// true, if previous was focused, false otherwise.
- public bool FocusPrev ()
- {
- if (focused == null) {
- FocusLast ();
- return true;
- }
- int focused_idx = -1;
- for (int i = subviews.Count; i > 0;) {
- i--;
- View w = subviews [i];
+ for (int i = subviews.Count; i > 0;) {
+ i--;
- if (w.HasFocus) {
- if (w.FocusPrev ())
- return true;
- focused_idx = i;
- continue;
- }
- if (w.CanFocus && focused_idx != -1) {
- focused.HasFocus = false;
+ View v = subviews [i];
+ if (v.CanFocus) {
+ SetFocus (v);
+ return;
+ }
+ }
+ }
- if (w.CanFocus)
- w.FocusLast ();
+ ///
+ /// Focuses the previous view.
+ ///
+ /// true, if previous was focused, false otherwise.
+ public bool FocusPrev ()
+ {
+ if (focused == null) {
+ FocusLast ();
+ return true;
+ }
+ int focused_idx = -1;
+ for (int i = subviews.Count; i > 0;) {
+ i--;
+ View w = subviews [i];
- SetFocus (w);
- return true;
- }
- }
-
- if (focused != null) {
- focused.HasFocus = false;
- focused = null;
- }
- return false;
- }
+ if (w.HasFocus) {
+ if (w.FocusPrev ())
+ return true;
+ focused_idx = i;
+ continue;
+ }
+ if (w.CanFocus && focused_idx != -1) {
+ focused.HasFocus = false;
- ///
- /// Focuses the next view.
- ///
- /// true, if next was focused, false otherwise.
- public bool FocusNext ()
- {
- if (focused == null) {
- FocusFirst ();
- return focused != null;
- }
- int n = subviews.Count;
- int focused_idx = -1;
- for (int i = 0; i < n; i++) {
- View w = subviews [i];
+ if (w.CanFocus)
+ w.FocusLast ();
- if (w.HasFocus) {
- if (w.FocusNext ())
- return true;
- focused_idx = i;
- continue;
- }
- if (w.CanFocus && focused_idx != -1) {
- focused.HasFocus = false;
+ SetFocus (w);
+ return true;
+ }
+ }
- if (w != null && w.CanFocus)
- w.FocusFirst ();
+ if (focused != null) {
+ focused.HasFocus = false;
+ focused = null;
+ }
+ return false;
+ }
- SetFocus (w);
- return true;
- }
- }
- if (focused != null) {
- focused.HasFocus = false;
- focused = null;
- }
- return false;
- }
+ ///
+ /// Focuses the next view.
+ ///
+ /// true, if next was focused, false otherwise.
+ public bool FocusNext ()
+ {
+ if (subviews == null || subviews.Count == 0)
+ return false;
- public virtual void LayoutSubviews ()
- {
- }
- }
+ if (focused == null) {
+ FocusFirst ();
+ return focused != null;
+ }
+ int n = subviews.Count;
+ int focused_idx = -1;
+ for (int i = 0; i < n; i++) {
+ View w = subviews [i];
- ///
- /// Toplevel views can be modally executed.
- ///
- public class Toplevel : View {
- public bool Running;
+ if (w.HasFocus) {
+ if (w.FocusNext ())
+ return true;
+ focused_idx = i;
+ continue;
+ }
+ if (w.CanFocus && focused_idx != -1) {
+ focused.HasFocus = false;
- public Toplevel (Rect frame) : base (frame)
- {
- }
+ if (w != null && w.CanFocus)
+ w.FocusFirst ();
- public static Toplevel Create ()
- {
- return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
- }
+ SetFocus (w);
+ return true;
+ }
+ }
+ if (focused != null) {
+ focused.HasFocus = false;
+ focused = null;
+ }
+ return false;
+ }
- public override bool CanFocus {
- get => true;
- }
+ public virtual void LayoutSubviews ()
+ {
+ }
- public override bool ProcessKey (KeyEvent kb)
- {
- if (ProcessHotKey (kb))
- return true;
+ public override string ToString ()
+ {
+ return $"{GetType ().Name}({id})({Frame})";
+ }
+ }
- if (base.ProcessKey (kb))
- return true;
-
- // Process the key normally
- if (ProcessColdKey (kb))
- return true;
-
- switch (kb.Key) {
- case Key.ControlC:
- // TODO: stop current execution of this container
- break;
- case Key.ControlZ:
- // TODO: should suspend
- // console_csharp_send_sigtstp ();
- break;
- case Key.Tab:
- var old = Focused;
- if (!FocusNext ())
- FocusNext ();
- old?.SetNeedsDisplay ();
- Focused?.SetNeedsDisplay ();
- break;
- case Key.BackTab:
- old = Focused;
- if (!FocusPrev ())
- FocusPrev ();
- old?.SetNeedsDisplay ();
- Focused?.SetNeedsDisplay ();
- break;
- }
- return false;
- }
+ ///
+ /// Toplevel views can be modally executed.
+ ///
+ public class Toplevel : View {
+ public bool Running;
+
+ public Toplevel (Rect frame) : base (frame)
+ {
+ }
+
+ public static Toplevel Create ()
+ {
+ return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
+ }
+
+ public override bool CanFocus {
+ get => true;
+ }
+
+ public override bool ProcessKey (KeyEvent kb)
+ {
+ if (ProcessHotKey (kb))
+ return true;
+
+ if (base.ProcessKey (kb))
+ return true;
+
+ // Process the key normally
+ if (ProcessColdKey (kb))
+ return true;
+
+ switch (kb.Key) {
+ case Key.ControlC:
+ // TODO: stop current execution of this container
+ break;
+ case Key.ControlZ:
+ // TODO: should suspend
+ // console_csharp_send_sigtstp ();
+ break;
+ case Key.Tab:
+ var old = Focused;
+ if (!FocusNext ())
+ FocusNext ();
+ if (old != Focused) {
+ old?.SetNeedsDisplay ();
+ Focused?.SetNeedsDisplay ();
+ }
+ return true;
+ case Key.BackTab:
+ old = Focused;
+ if (!FocusPrev ())
+ FocusPrev ();
+ if (old != Focused) {
+ old?.SetNeedsDisplay ();
+ Focused?.SetNeedsDisplay ();
+ }
+ return true;
+ case Key.ControlL:
+ SetNeedsDisplayRecursive ();
+ return true;
+ }
+ return false;
+ }
#if false
public override void Redraw ()
@@ -576,248 +630,254 @@ namespace Terminal {
Driver.AddStr ("Line: " + i);
}
}
-#endif
- }
+#endif
+ }
- ///
- /// A toplevel view that draws a frame around its region
- ///
- public class Window : Toplevel, IEnumerable {
- View contentView;
- string title;
+ ///
+ /// A toplevel view that draws a frame around its region
+ ///
+ public class Window : Toplevel, IEnumerable {
+ View contentView;
+ string title;
- public string Title {
- get => title;
- set {
- title = value;
- SetNeedsDisplay ();
- }
- }
+ public string Title {
+ get => title;
+ set {
+ title = value;
+ SetNeedsDisplay ();
+ }
+ }
- public Window (Rect frame, string title = null) : base (frame)
- {
- this.Title = title;
- frame.Inflate (-1, -1);
- contentView = new View (frame);
- base.Add(contentView);
- }
+ class ContentView : View {
+ public ContentView (Rect frame) : base (frame) { }
+ }
- public new IEnumerator GetEnumerator ()
- {
- return contentView.GetEnumerator ();
- }
+ public Window (Rect frame, string title = null) : base (frame)
+ {
+ this.Title = title;
+ frame.Inflate (-1, -1);
+ contentView = new ContentView (frame);
+ base.Add (contentView);
+ }
- void DrawFrame ()
- {
- DrawFrame (new Rect(0, 0, Frame.Width, Frame.Height), true);
- }
+ public new IEnumerator GetEnumerator ()
+ {
+ return contentView.GetEnumerator ();
+ }
- public override void Add (View view)
- {
- contentView.Add (view);
- }
+ void DrawFrame ()
+ {
+ DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), true);
+ }
- public override void Redraw (Rect bounds)
- {
- Driver.SetAttribute (Colors.Base.Normal);
- DrawFrame ();
- if (HasFocus)
- Driver.SetAttribute (Colors.Dialog.Normal);
- var width = Frame.Width;
- if (Title != null && width > 4) {
- Move (1, 0);
- Driver.AddCh (' ');
- var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
- Driver.AddStr (str);
- Driver.AddCh (' ');
- }
- Driver.SetAttribute (Colors.Dialog.Normal);
- contentView.Redraw (contentView.Bounds);
- }
- }
+ public override void Add (View view)
+ {
+ contentView.Add (view);
+ }
- public class Application {
- public static ConsoleDriver Driver = new CursesDriver ();
- public static Toplevel Top { get; private set; }
- public static Mono.Terminal.MainLoop MainLoop { get; private set; }
+ public override void Redraw (Rect bounds)
+ {
+ Driver.SetAttribute (Colors.Base.Normal);
+ DrawFrame ();
+ if (HasFocus)
+ Driver.SetAttribute (Colors.Dialog.Normal);
+ var width = Frame.Width;
+ if (Title != null && width > 4) {
+ Move (1, 0);
+ Driver.AddCh (' ');
+ var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
+ Driver.AddStr (str);
+ Driver.AddCh (' ');
+ }
+ Driver.SetAttribute (Colors.Dialog.Normal);
+ contentView.Redraw (contentView.Bounds);
+ }
+ }
- static Stack toplevels = new Stack ();
- static Responder focus;
+ public class Application {
+ public static ConsoleDriver Driver = new CursesDriver ();
+ public static Toplevel Top { get; private set; }
+ public static Mono.Terminal.MainLoop MainLoop { get; private set; }
- ///
- /// This event is raised on each iteration of the
- /// main loop.
- ///
- ///
- /// See also
- ///
- static public event EventHandler Iteration;
+ static Stack toplevels = new Stack ();
+ static Responder focus;
- public static void MakeFirstResponder (Responder newResponder)
- {
- if (newResponder == null)
- throw new ArgumentNullException ();
+ ///
+ /// This event is raised on each iteration of the
+ /// main loop.
+ ///
+ ///
+ /// See also
+ ///
+ static public event EventHandler Iteration;
- throw new NotImplementedException ();
- }
+ public static void MakeFirstResponder (Responder newResponder)
+ {
+ if (newResponder == null)
+ throw new ArgumentNullException ();
- ///
- /// Initializes the Application
- ///
- public static void Init ()
- {
- if (Top != null)
- return;
+ throw new NotImplementedException ();
+ }
- Driver.Init (TerminalResized);
- MainLoop = new Mono.Terminal.MainLoop ();
- Top = Toplevel.Create ();
- focus = Top;
- }
+ ///
+ /// Initializes the Application
+ ///
+ public static void Init ()
+ {
+ if (Top != null)
+ return;
- public class RunState : IDisposable {
- internal RunState (Toplevel view)
- {
- Toplevel = view;
- }
- internal Toplevel Toplevel;
+ Driver.Init (TerminalResized);
+ MainLoop = new Mono.Terminal.MainLoop ();
+ Top = Toplevel.Create ();
+ focus = Top;
+ }
- public void Dispose ()
- {
- Dispose (true);
- GC.SuppressFinalize(this);
- }
+ public class RunState : IDisposable {
+ internal RunState (Toplevel view)
+ {
+ Toplevel = view;
+ }
+ internal Toplevel Toplevel;
- public virtual void Dispose (bool disposing)
- {
- if (Toplevel != null){
- Application.End (Toplevel);
- Toplevel = null;
- }
- }
- }
+ public void Dispose ()
+ {
+ Dispose (true);
+ GC.SuppressFinalize (this);
+ }
- static void KeyEvent (Key key)
- {
- }
+ public virtual void Dispose (bool disposing)
+ {
+ if (Toplevel != null) {
+ Application.End (Toplevel);
+ Toplevel = null;
+ }
+ }
+ }
- static public RunState Begin (Toplevel toplevel)
- {
- if (toplevel == null)
- throw new ArgumentNullException (nameof(toplevel));
- var rs = new RunState (toplevel);
+ static void KeyEvent (Key key)
+ {
+ }
- Init ();
- toplevels.Push (toplevel);
- Driver.PrepareToRun (MainLoop, toplevel);
- toplevel.LayoutSubviews ();
- toplevel.FocusFirst ();
- Redraw (toplevel);
- toplevel.PositionCursor ();
- Driver.Refresh ();
+ static public RunState Begin (Toplevel toplevel)
+ {
+ if (toplevel == null)
+ throw new ArgumentNullException (nameof (toplevel));
+ var rs = new RunState (toplevel);
- return rs;
- }
+ Init ();
+ toplevels.Push (toplevel);
+ Driver.PrepareToRun (MainLoop, toplevel);
+ toplevel.LayoutSubviews ();
+ toplevel.FocusFirst ();
+ Redraw (toplevel);
+ toplevel.PositionCursor ();
+ Driver.Refresh ();
- static public void End (RunState rs)
- {
- if (rs == null)
- throw new ArgumentNullException (nameof (rs));
- rs.Dispose ();
- }
+ return rs;
+ }
- static void Shutdown ()
- {
- Driver.End ();
- }
+ static public void End (RunState rs)
+ {
+ if (rs == null)
+ throw new ArgumentNullException (nameof (rs));
+ rs.Dispose ();
+ }
- static void Redraw (View view)
- {
- view.Redraw (view.Bounds);
- Driver.Refresh ();
- }
+ static void Shutdown ()
+ {
+ Driver.End ();
+ }
- static void Refresh (View view)
- {
- view.Redraw (view.Bounds);
- Driver.Refresh ();
- }
+ static void Redraw (View view)
+ {
+ view.Redraw (view.Bounds);
+ Driver.Refresh ();
+ }
- public static void Refresh ()
- {
- Driver.RedrawTop ();
- View last = null;
- foreach (var v in toplevels){
- v.Redraw (v.Bounds);
- last = v;
- }
- if (last != null)
- last.PositionCursor ();
- Driver.Refresh ();
- }
+ static void Refresh (View view)
+ {
+ view.Redraw (view.Bounds);
+ Driver.Refresh ();
+ }
- internal static void End (View view)
- {
- if (toplevels.Peek () != view)
- throw new ArgumentException ("The view that you end with must be balanced");
- toplevels.Pop ();
- if (toplevels.Count == 0)
- Shutdown ();
- else
- Refresh ();
- }
+ public static void Refresh ()
+ {
+ Driver.RedrawTop ();
+ View last = null;
+ foreach (var v in toplevels) {
+ v.Redraw (v.Bounds);
+ last = v;
+ }
+ if (last != null)
+ last.PositionCursor ();
+ Driver.Refresh ();
+ }
- ///
- /// Runs the main loop for the created dialog
- ///
- ///
- /// Use the wait parameter to control whether this is a
- /// blocking or non-blocking call.
- ///
- public static void RunLoop(RunState state, bool wait = true)
- {
- if (state == null)
- throw new ArgumentNullException(nameof(state));
- if (state.Toplevel == null)
- throw new ObjectDisposedException("state");
+ internal static void End (View view)
+ {
+ if (toplevels.Peek () != view)
+ throw new ArgumentException ("The view that you end with must be balanced");
+ toplevels.Pop ();
+ if (toplevels.Count == 0)
+ Shutdown ();
+ else
+ Refresh ();
+ }
- for (state.Toplevel.Running = true; state.Toplevel.Running;) {
- if (MainLoop.EventsPending(wait)){
- MainLoop.MainIteration();
- if (Iteration != null)
- Iteration(null, EventArgs.Empty);
- } else if (wait == false)
- return;
- if (state.Toplevel.NeedDisplay)
- state.Toplevel.Redraw (state.Toplevel.Bounds);
- }
- }
+ ///
+ /// Runs the main loop for the created dialog
+ ///
+ ///
+ /// Use the wait parameter to control whether this is a
+ /// blocking or non-blocking call.
+ ///
+ public static void RunLoop (RunState state, bool wait = true)
+ {
+ if (state == null)
+ throw new ArgumentNullException (nameof (state));
+ if (state.Toplevel == null)
+ throw new ObjectDisposedException ("state");
- public static void Run ()
- {
- Run (Top);
- }
+ for (state.Toplevel.Running = true; state.Toplevel.Running;) {
+ if (MainLoop.EventsPending (wait)) {
+ MainLoop.MainIteration ();
+ if (Iteration != null)
+ Iteration (null, EventArgs.Empty);
+ } else if (wait == false)
+ return;
+ if (state.Toplevel.NeedDisplay || state.Toplevel.childNeedsDisplay) {
+ state.Toplevel.Redraw (state.Toplevel.Bounds);
+ Driver.Refresh ();
+ }
+ }
+ }
- ///
- /// Runs the main loop on the given container.
- ///
- ///
- /// This method is used to start processing events
- /// for the main application, but it is also used to
- /// run modal dialog boxes.
- ///
- public static void Run (Toplevel view)
- {
- var runToken = Begin (view);
- RunLoop (runToken);
- End (runToken);
- }
+ public static void Run ()
+ {
+ Run (Top);
+ }
- static void TerminalResized ()
- {
- foreach (var t in toplevels) {
- t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
- }
- }
- }
+ ///
+ /// Runs the main loop on the given container.
+ ///
+ ///
+ /// This method is used to start processing events
+ /// for the main application, but it is also used to
+ /// run modal dialog boxes.
+ ///
+ public static void Run (Toplevel view)
+ {
+ var runToken = Begin (view);
+ RunLoop (runToken);
+ End (runToken);
+ }
+
+ static void TerminalResized ()
+ {
+ foreach (var t in toplevels) {
+ t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/Driver.cs b/Driver.cs
index 9c53cfc54..a8e5e4201 100644
--- a/Driver.cs
+++ b/Driver.cs
@@ -5,328 +5,328 @@ using Unix.Terminal;
namespace Terminal {
- ///
- /// Basic colors that can be used to set the foreground and background colors in console applications. These can only be
- ///
- public enum Color {
- Black,
- Blue,
- Green,
- Cyan,
- Red,
- Magenta,
- Brown,
- Gray,
- DarkGray,
- BrightBlue,
- BrightGreen,
- BrighCyan,
- BrightRed,
- BrightMagenta,
- BrightYellow,
- White
- }
+ ///
+ /// Basic colors that can be used to set the foreground and background colors in console applications. These can only be
+ ///
+ 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 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 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 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 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);
+ 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);
+ // 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);
+ // 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);
+ public abstract void DrawFrame (Rect region, bool fill);
- Rect clip;
- public Rect Clip {
- get => clip;
- set => this.clip = value;
- }
- }
+ Rect clip;
+ public Rect Clip {
+ get => clip;
+ set => this.clip = value;
+ }
+ }
- public class CursesDriver : ConsoleDriver {
- Action terminalResized;
+ public class CursesDriver : ConsoleDriver {
+ Action terminalResized;
- public override int Cols => Curses.Cols;
- public override int Rows => Curses.Lines;
+ 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;
+ // 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;
- }
- }
+ 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 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 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;
+ 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) };
- }
+ 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];
+ 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;
+ 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);
- }
+ v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
+ colorPairs [(int)foreground, (int)background] = v | 0x1000;
+ }
+ SetAttribute (v & 0xffff);
+ }
- Dictionary rawPairs = new Dictionary ();
- 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);
- }
+ Dictionary rawPairs = new Dictionary ();
+ 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;
- }
- }
+ 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;
- }
+ 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);
+ // 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));
- }
+ 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);
+ public override void PrepareToRun (MainLoop mainLoop, Responder handler)
+ {
+ Curses.timeout (-1);
- mainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
- ProcessInput (handler);
- return true;
- });
+ 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;
+ 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);
- }
+ 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;
+ 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 ();
+ 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.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.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.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;
- }
- }
- }
+ 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;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/Event.cs b/Event.cs
index b6a350f31..b00fad620 100644
--- a/Event.cs
+++ b/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);
diff --git a/TODO.md b/TODO.md
index 979667add..746ed849e 100644
--- a/TODO.md
+++ b/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
\ No newline at end of file
+# Make HasFocus implicitly call SetNeedsDisplay
diff --git a/Views/Label.cs b/Views/Label.cs
index 2ad2ffef3..ad15cabfa 100644
--- a/Views/Label.cs
+++ b/Views/Label.cs
@@ -4,177 +4,177 @@ using System.Collections.Generic;
using System.Linq;
namespace Terminal {
- public enum TextAlignment {
- Left, Right, Centered, Justified
- }
+ public enum TextAlignment {
+ Left, Right, Centered, Justified
+ }
- ///
- /// Label widget, displays a string at a given position, can include multiple lines.
- ///
- public class Label : View {
- List lines = new List ();
- bool recalcPending = true;
- string text;
- TextAlignment textAlignment;
+ ///
+ /// Label widget, displays a string at a given position, can include multiple lines.
+ ///
+ public class Label : View {
+ List lines = new List ();
+ bool recalcPending = true;
+ string text;
+ TextAlignment textAlignment;
- static Rect CalcRect (int x, int y, string s)
- {
- int mw = 0;
- int ml = 1;
+ 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);
- }
+ 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);
+ }
- ///
- /// 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.
- ///
- public Label (int x, int y, string text) : this (CalcRect (x, y, text), text)
- {
- }
+ ///
+ /// 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.
+ ///
+ public Label (int x, int y, string text) : this (CalcRect (x, y, text), text)
+ {
+ }
- ///
- /// Public constructor: creates a label at the given
- /// coordinate with the given string and uses the specified
- /// frame for the string.
- ///
- public Label (Rect rect, string text) : base (rect)
- {
- this.text = text;
- }
+ ///
+ /// Public constructor: creates a label at the given
+ /// coordinate with the given string and uses the specified
+ /// frame for the string.
+ ///
+ public Label (Rect rect, string text) : base (rect)
+ {
+ this.text = text;
+ }
- static char [] whitespace = new char [] { ' ', '\t' };
+ 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);
+ 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;
- }
- }
+ 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];
+ 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;
- }
- }
- recalcPending = false;
- }
+ 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);
+ public override void Redraw (Rect region)
+ {
+ if (recalcPending)
+ Recalc ();
- 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);
- }
- }
+ if (TextColor != -1)
+ Driver.SetAttribute (TextColor);
+ else
+ Driver.SetAttribute (Colors.Base.Normal);
- ///
- /// The text displayed by this widget.
- ///
- public virtual string Text {
- get => text;
- set {
- text = value;
- recalcPending = true;
- SetNeedsDisplay ();
- }
- }
+ 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);
+ }
+ }
- public TextAlignment TextAlignment {
- get => textAlignment;
- set {
- textAlignment = value;
- SetNeedsDisplay ();
- }
- }
+ ///
+ /// The text displayed by this widget.
+ ///
+ public virtual string Text {
+ get => text;
+ set {
+ text = value;
+ recalcPending = true;
+ SetNeedsDisplay ();
+ }
+ }
- ///
- /// The color used for the label
- ///
- Attribute textColor = -1;
- public Attribute TextColor {
- get => textColor;
- set {
- textColor = value;
- SetNeedsDisplay ();
- }
- }
- }
+ public TextAlignment TextAlignment {
+ get => textAlignment;
+ set {
+ textAlignment = value;
+ SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// The color used for the label
+ ///
+ Attribute textColor = -1;
+ public Attribute TextColor {
+ get => textColor;
+ set {
+ textColor = value;
+ SetNeedsDisplay ();
+ }
+ }
+ }
}
diff --git a/Views/TextField.cs b/Views/TextField.cs
index afc9d8112..1159030a0 100644
--- a/Views/TextField.cs
+++ b/Views/TextField.cs
@@ -3,299 +3,298 @@ using System.Collections.Generic;
using System.Linq;
namespace Terminal {
- ///
- /// Text data entry widget
- ///
- ///
- /// The Entry widget provides Emacs-like editing
- /// functionality, and mouse support.
- ///
- public class TextField : View {
- string text, kill;
- int first, point;
- bool used;
+ ///
+ /// Text data entry widget
+ ///
+ ///
+ /// The Entry widget provides Emacs-like editing
+ /// functionality, and mouse support.
+ ///
+ public class TextField : View {
+ string text, kill;
+ int first, point;
+ bool used;
- ///
- /// Changed event, raised when the text has clicked.
- ///
- ///
- /// Client code can hook up to this event, it is
- /// raised when the text in the entry changes.
- ///
- public event EventHandler Changed;
+ ///
+ /// Changed event, raised when the text has clicked.
+ ///
+ ///
+ /// Client code can hook up to this event, it is
+ /// raised when the text in the entry changes.
+ ///
+ public event EventHandler Changed;
- ///
- /// Public constructor.
- ///
- ///
- ///
- public TextField (int x, int y, int w, string s) : base (new Rect (x, y, w, 1))
- {
- if (s == null)
- s = "";
+ ///
+ /// Public constructor.
+ ///
+ ///
+ ///
+ 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;
- }
+ text = s;
+ point = s.Length;
+ first = point > w ? point - w : 0;
+ CanFocus = true;
+ Color = Colors.Dialog.Focus;
+ }
- ///
- /// Sets or gets the text in the entry.
- ///
- ///
- ///
- public string Text {
- get {
- return text;
- }
+ ///
+ /// Sets or gets the text in the entry.
+ ///
+ ///
+ ///
+ 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 ();
- }
- }
+ set {
+ text = value;
+ if (point > text.Length)
+ point = text.Length;
+ first = point > Frame.Width ? point - Frame.Width : 0;
+ SetNeedsDisplay ();
+ }
+ }
- ///
- /// Sets the secret property.
- ///
- ///
- /// This makes the text entry suitable for entering passwords.
- ///
- public bool Secret { get; set; }
+ ///
+ /// Sets the secret property.
+ ///
+ ///
+ /// This makes the text entry suitable for entering passwords.
+ ///
+ public bool Secret { get; set; }
- Attribute color;
- ///
- /// Sets the color attribute to use (includes foreground and background).
- ///
- /// The color.
- public Attribute Color {
- get => color;
- set {
- color = value;
- SetNeedsDisplay ();
- }
- }
+ Attribute color;
+ ///
+ /// Sets the color attribute to use (includes foreground and background).
+ ///
+ /// The color.
+ public Attribute Color {
+ get => color;
+ set {
+ color = value;
+ SetNeedsDisplay ();
+ }
+ }
- ///
- /// The current cursor position.
- ///
- public int CursorPosition { get { return point; } }
+ ///
+ /// The current cursor position.
+ ///
+ public int CursorPosition { get { return point; } }
- ///
- /// Sets the cursor position.
- ///
- public override void PositionCursor ()
- {
- Move (point - first, 0);
- }
+ ///
+ /// Sets the cursor position.
+ ///
+ public override void PositionCursor ()
+ {
+ Move (point - first, 0);
+ }
- public override void Redraw (Rect region)
- {
- Driver.SetAttribute (Color);
- Move (0, 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;
+ 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 ();
- }
+ 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 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);
- }
+ 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 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;
+ 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;
+ 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;
+ // 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.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.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.ControlE: // End
+ point = text.Length;
+ Adjust ();
+ break;
- case Key.CursorRight:
- case Key.ControlF:
- if (point == text.Length)
- break;
- point++;
- 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.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;
+ 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;
+ 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)'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;
+ 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;
+ 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;
- }
+ 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 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 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 WordBackward (int p)
+ {
+ if (p == 0)
+ return -1;
- int i = p - 1;
- if (i == 0)
- return 0;
+ 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 (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;
+ if (i != p)
+ return i;
- return -1;
- }
+ return -1;
+ }
#if false
public override void ProcessMouse (Curses.MouseEvent ev)
@@ -315,7 +314,7 @@ namespace Terminal {
SetNeedsDisplay ();
}
#endif
- }
+ }
}
diff --git a/demo.cs b/demo.cs
index d7a922ef9..724fd0ec2 100644
--- a/demo.cs
+++ b/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 ();
+ }
}
\ No newline at end of file