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