Explorar o código

Start event handling

Miguel de Icaza %!s(int64=7) %!d(string=hai) anos
pai
achega
afb6fbf300
Modificáronse 6 ficheiros con 643 adicións e 28 borrados
  1. 146 12
      Core.cs
  2. 85 14
      Driver.cs
  3. 87 1
      Event.cs
  4. 1 0
      Terminal.csproj
  5. 321 0
      Views/TextField.cs
  6. 3 1
      demo.cs

+ 146 - 12
Core.cs

@@ -3,6 +3,8 @@
 // Pending:
 //   - Check for NeedDisplay on the hierarchy and repaint
 //   - Layout support
+//   - "Colors" type or "Attributes" type?
+//   - What to surface as "BackgroundCOlor" when clearing a window, an attribute or colors?
 //
 // Optimziations
 //   - Add rendering limitation to the exposed area
@@ -17,7 +19,79 @@ namespace Terminal {
         public bool HasFocus { get; internal set; }
 
         // Key handling
-        public virtual void KeyDown (Event.Key kb) { }
+        /// <summary>
+        ///   This method can be overwritten by view that
+        ///     want to provide accelerator functionality
+        ///     (Alt-key for example).
+        /// </summary>
+        /// <remarks>
+        ///   <para>
+        ///     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. </para>
+        ///  <para>
+        ///     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.
+        ///  </para>
+        /// </remarks>
+
+        public virtual bool ProcessHotKey (KeyEvent kb)
+        {
+            return false;
+        }
+
+        /// <summary>
+        ///   If the view is focused, gives the view a
+        ///   chance to process the keystroke. 
+        /// </summary>
+        /// <remarks>
+        ///   <para>
+        ///     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.
+        ///   </para>
+        /// </remarks>
+        public virtual bool ProcessKey (KeyEvent kb) 
+        { 
+            return false; 
+        }
+
+        /// <summary>
+        ///   This method can be overwritten by views that
+        ///     want to provide accelerator functionality
+        ///     (Alt-key for example), but without
+        ///     interefering with normal ProcessKey behavior.
+        /// </summary>
+        /// <remarks>
+        ///   <para>
+        ///     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. </para>
+        ///  <para>
+        ///    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.
+        ///  </para>
+        /// </remarks>
+        public virtual bool ProcessColdKey (KeyEvent kb)
+        {
+            return false;
+        }
 
         // Mouse events
         public virtual void MouseEvent (Event.Mouse me) { }
@@ -57,6 +131,8 @@ namespace Terminal {
             }
         }
 
+        public View SuperView => container;
+
         public View (Rect frame)
         {
             this.Frame = frame;
@@ -223,6 +299,12 @@ namespace Terminal {
                 Move (frame.X, frame.Y);
         }
 
+        /// <summary>
+        /// Returns the currently focused view inside this view, or null if nothing is focused.
+        /// </summary>
+        /// <value>The focused.</value>
+        public View Focused => focused;
+
         /// <summary>
         /// Displays the specified character in the specified column and row.
         /// </summary>
@@ -305,6 +387,9 @@ namespace Terminal {
         /// </summary>
         public void FocusFirst ()
         {
+            if (subviews == null)
+                return;
+            
             foreach (var view in subviews) {
                 if (view.CanFocus) {
                     SetFocus (view);
@@ -318,6 +403,9 @@ namespace Terminal {
         /// </summary>
         public void FocusLast ()
         {
+            if (subviews == null)
+                return;
+                        
             for (int i = subviews.Count; i > 0;) {
                 i--;
 
@@ -360,6 +448,7 @@ namespace Terminal {
                     return true;
                 }
             }
+        
             if (focused != null) {
                 focused.HasFocus = false;
                 focused = null;
@@ -425,6 +514,48 @@ namespace Terminal {
             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;
+
+            // Process the key normally
+            if (Focused?.ProcessKey (kb) == true)
+                return true;
+
+            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;
+        }
+
 #if false
         public override void Redraw ()
         {
@@ -460,7 +591,7 @@ namespace Terminal {
             base.Add(contentView);
         }
 
-        public IEnumerator GetEnumerator ()
+        public new IEnumerator GetEnumerator ()
         {
             return contentView.GetEnumerator ();
         }
@@ -527,16 +658,10 @@ namespace Terminal {
             if (Top != null)
                 return;
 
-            Driver.Init ();
+            Driver.Init (TerminalResized);
             MainLoop = new Mono.Terminal.MainLoop ();
             Top = Toplevel.Create ();  
             focus = Top;
-
-            MainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
-                //ProcessChar ();
-
-				return true;
-			});
         }
 
         public class RunState : IDisposable {
@@ -561,6 +686,10 @@ namespace Terminal {
             }
         }
 
+        static void KeyEvent (Key key)
+        {
+        }
+
         static public RunState Begin (Toplevel toplevel)
         {
             if (toplevel == null)
@@ -568,10 +697,8 @@ namespace Terminal {
             var rs = new RunState (toplevel);
 
             Init ();
-            Driver.PrepareToRun ();
-
             toplevels.Push (toplevel);
-
+            Driver.PrepareToRun (MainLoop, toplevel);
             toplevel.LayoutSubviews ();
             toplevel.FocusFirst ();
             Redraw (toplevel);
@@ -674,5 +801,12 @@ namespace Terminal {
             RunLoop (runToken);
             End (runToken);
         }
+
+        static void TerminalResized ()
+        {
+            foreach (var t in toplevels) {
+                t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
+            }
+        }
     }
 }

+ 85 - 14
Driver.cs

@@ -1,10 +1,14 @@
 using System;
 using System.Collections.Generic;
+using Mono.Terminal;
 using Unix.Terminal;
 
 namespace Terminal {
-    public enum Color
-    {
+
+    /// <summary>
+    /// Basic colors that can be used to set the foreground and background colors in console applications.  These can only be
+    /// </summary>
+    public enum Color {
         Black,
         Blue,
         Green,
@@ -49,13 +53,13 @@ namespace Terminal {
     }
 
     public abstract class ConsoleDriver {
-        public abstract int Cols {get;}
-        public abstract int Rows {get;}
-        public abstract void Init ();
+        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 ();
+        public abstract void PrepareToRun (MainLoop mainLoop, Responder target);
         public abstract void Refresh ();
         public abstract void End ();
         public abstract void RedrawTop ();
@@ -78,6 +82,8 @@ namespace Terminal {
     }
 
     public class CursesDriver : ConsoleDriver {
+        Action terminalResized;
+
         public override int Cols => Curses.Cols;
         public override int Rows => Curses.Lines;
 
@@ -115,12 +121,12 @@ namespace Terminal {
         {
             // 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);
+                AddCh ((int)c);
         }
 
-        public override void Refresh() => Curses.refresh ();
-        public override void End() => Curses.endwin ();
-        public override void RedrawTop() => window.redrawwin ();
+        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;
 
@@ -135,7 +141,7 @@ namespace Terminal {
 
         public override void SetColors (ConsoleColor foreground, ConsoleColor background)
         {
-            int f = (short) foreground;
+            int f = (short)foreground;
             int b = (short)background;
             var v = colorPairs [f, b];
             if ((v & 0x10000) == 0) {
@@ -152,16 +158,80 @@ namespace Terminal {
         Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
         public override void SetColors (short foreColorId, short backgroundColorId)
         {
-            int key = (((ushort)foreColorId << 16)) | (ushort) backgroundColorId;
+            int key = (((ushort)foreColorId << 16)) | (ushort)backgroundColorId;
             if (!rawPairs.TryGetValue (key, out var v)) {
                 v = MakeColor (foreColorId, backgroundColorId);
                 rawPairs [key] = v;
             }
             SetAttribute (v);
         }
-        public override void PrepareToRun()
+
+        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)
+        {
+            var code = Curses.getch ();
+            if ((code == -1) || (code == Curses.KeyResize)) {
+                if (Curses.CheckWinChange ()) {
+                    terminalResized ();
+                }
+            }
+            if (code == Curses.KeyMouse) {
+                // TODO
+                // Curses.MouseEvent ev;
+                // Curses.getmouse (out ev);
+                // handler.HandleMouse ();
+                return;
+            }
+
+            // ESC+letter is Alt-Letter.
+            if (code == 27) {
+                Curses.timeout (100);
+                int k = Curses.getch ();
+                if (k != Curses.ERR && k != 27) {
+                    var mapped = MapCursesKey (k) | Key.AltMask;
+                    handler.ProcessKey (new KeyEvent (mapped));
+                } 
+            } else {
+                    handler.ProcessKey (new KeyEvent (MapCursesKey (code)));
+            }
+        }
+
+        public override void PrepareToRun (MainLoop mainLoop, Responder handler)
         {
             Curses.timeout (-1);
+
+            mainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
+                ProcessInput (handler);
+                return true;
+            });
+
         }
 
         public override void DrawFrame (Rect region, bool fill)
@@ -192,7 +262,7 @@ namespace Terminal {
             AddCh (Curses.ACS_LRCORNER);
         }
 
-        public override void Init()
+        public override void Init(Action terminalResized)
         {
             if (window != null)
                 return;
@@ -205,6 +275,7 @@ namespace Terminal {
             Curses.raw ();
             Curses.noecho ();
             Curses.Window.Standard.keypad (true);
+            this.terminalResized = terminalResized;
         
             Colors.Base = new ColorScheme ();
             Colors.Dialog = new ColorScheme ();

+ 87 - 1
Event.cs

@@ -1,9 +1,95 @@
 namespace Terminal {
 
+    /// <summary>
+    /// The Key enumeration contains special encoding for some keys, but can also
+    /// encode all the unicode values that can be passed.   
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///   If the SpecialMask is set, then the value is that of the special mask,
+    ///   otherwise, the value is the one of the lower bits (as extracted by CharMask)
+    /// </para>
+    /// <para>
+    ///   Control keys are the values between 1 and 26 corresponding to Control-A to Control-Z
+    /// </para>
+    /// </remarks>
+    public enum Key : uint {
+        CharMask = 0xfffff,
+        SpecialMask = 0xfff00000,
+        ControlA = 1,
+        ControlB,
+        ControlC,
+        ControlD,
+        ControlE,
+        ControlF,
+        ControlG,
+        ControlH,
+        ControlI,
+        Tab = ControlI,
+        ControlJ,
+        ControlK,
+        ControlL,
+        ControlM,
+        ControlN,
+        ControlO,
+        ControlP,
+        ControlQ,
+        ControlR,
+        ControlS,
+        ControlT,
+        ControlU,
+        ControlV,
+        ControlW,
+        ControlX,
+        ControlY,
+        ControlZ,
+        Esc = 27,
+        Space = 32,
+        Delete = 127,
+
+        AltMask = 0x80000000,
+
+        Backspace = 0x100000,
+        CursorUp,
+        CursorDown,
+        CursorLeft,
+        CursorRight,
+        PageUp,
+        PageDown,
+        Home,
+        End,
+        DeleteChar,
+        InsertChar,
+        F1,
+        F2,
+        F3,
+        F4,
+        F5,
+        F6,
+        F7,
+        F8,
+        F9,
+        F10,
+        BackTab,
+        Unknown
+    }
+
+    public struct KeyEvent {
+        public Key Key;
+        public int KeyValue => (int)KeyValue;
+        public bool IsAlt => (Key & Key.AltMask) != 0;
+        public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
+
+        public KeyEvent (Key k)
+        {
+            Key = k;
+        }
+    }
+
     public class Event {
         public class Key : Event {
             public int Code { get; private set; }
-
+            public bool Alt { get; private set; }
             public Key (int code)
             {
                 Code = code;

+ 1 - 0
Terminal.csproj

@@ -40,6 +40,7 @@
     <Compile Include="Types\Size.cs" />
     <Compile Include="demo.cs" />
     <Compile Include="Views\Label.cs" />
+    <Compile Include="Views\TextField.cs" />
   </ItemGroup>
   <ItemGroup>
    <Reference Include="mono-curses.dll">

+ 321 - 0
Views/TextField.cs

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

+ 3 - 1
demo.cs

@@ -9,7 +9,9 @@ class Demo {
             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 (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, "")
         };
         top.Add (win);
         Application.Run ();