瀏覽代碼

merge with master

Charlie Kindel 5 年之前
父節點
當前提交
2d5a9d0dbf

+ 51 - 0
.github/workflows/codeql-analysis.yml

@@ -0,0 +1,51 @@
+name: "Code scanning - action"
+
+on:
+  push:
+  pull_request:
+  schedule:
+    - cron: '0 4 * * 0'
+
+jobs:
+  CodeQL-Build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+      with:
+        # We must fetch at least the immediate parents so that if this is
+        # a pull request then we can checkout the head.
+        fetch-depth: 2
+
+    # If this run was triggered by a pull request event, then checkout
+    # the head of the pull request instead of the merge commit.
+    - run: git checkout HEAD^2
+      if: ${{ github.event_name == 'pull_request' }}
+      
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      # Override language selection by uncommenting this and choosing your languages
+      with:
+        languages: csharp
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v1
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1

+ 9 - 7
Example/demo.cs

@@ -235,14 +235,14 @@ static class Demo {
 	}
 	}
 
 
 	//
 	//
-	// Creates a nested editor
-	static void Editor (Toplevel top)
+	static void Editor ()
 	{
 	{
-		var tframe = top.Frame;
-		var ntop = new Toplevel (tframe);
+		var tframe = Application.Top.Frame;
+		Application.Top.RemoveAll ();
+		var ntop = Application.Top;
 		var menu = new MenuBar (new MenuBarItem [] {
 		var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
+				new MenuItem ("_Close", "", () => { if (Quit ()) {Application.RequestStop (); } }),
 			}),
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", null),
 				new MenuItem ("_Copy", "", null),
@@ -273,7 +273,9 @@ static class Demo {
 			text.Text = System.IO.File.ReadAllText (fname);
 			text.Text = System.IO.File.ReadAllText (fname);
 		win.Add (text);
 		win.Add (text);
 
 
-		Application.Run (ntop);
+		Application.Run (ntop, false);
+		Application.Top.RemoveAll ();
+		Main ();
 	}
 	}
 
 
 	static bool Quit ()
 	static bool Quit ()
@@ -576,7 +578,7 @@ static class Demo {
 
 
 		menu = new MenuBar (new MenuBarItem [] {
 		menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("Text _Editor Demo", "", () => { Editor (top); }),
+				new MenuItem ("Text _Editor Demo", "", () => { Editor (); }),
 				new MenuItem ("_New", "Creates new file", NewFile),
 				new MenuItem ("_New", "Creates new file", NewFile),
 				new MenuItem ("_Open", "", Open),
 				new MenuItem ("_Open", "", Open),
 				new MenuItem ("_Hex", "", () => ShowHex (top)),
 				new MenuItem ("_Hex", "", () => ShowHex (top)),

+ 2 - 3
FSharpExample/Program.fs

@@ -140,7 +140,7 @@ type Demo() = class end
         container.Add (login, loginText, password, passText,
         container.Add (login, loginText, password, passText,
             new FrameView (new Rect (3, 10, 25, 6), ustr "Options",
             new FrameView (new Rect (3, 10, 25, 6), ustr "Options",
                 [|new CheckBox (1, 0, ustr "Remember me");
                 [|new CheckBox (1, 0, ustr "Remember me");
-                new RadioGroup (1, 2, [|"_Personal"; "_Company"|])|]
+                new RadioGroup (1, 2, [|ustr "_Personal"; ustr "_Company"|])|]
                 ),
                 ),
             new ListView (new Rect(59, 6, 16, 4),
             new ListView (new Rect(59, 6, 16, 4),
                     [|"First row";
                     [|"First row";
@@ -434,8 +434,7 @@ type Demo() = class end
             new StatusItem(Key.F2, ustr "~F2~ Load", Action(Load));
             new StatusItem(Key.F2, ustr "~F2~ Load", Action(Load));
             new StatusItem(Key.F3, ustr "~F3~ Save", Action(Save));
             new StatusItem(Key.F3, ustr "~F3~ Save", Action(Save));
             new StatusItem(Key.ControlX, ustr "~^X~ Quit", fun () -> if (Quit ()) then top.Running <- false)
             new StatusItem(Key.ControlX, ustr "~^X~ Quit", fun () -> if (Quit ()) then top.Running <- false)
-            |],
-            Parent = null
+            |]
             )
             )
         win.Add (drag, dragText)
         win.Add (drag, dragText)
         let mutable bottom = new Label(ustr "This should go on the bottom of the same top-level!")
         let mutable bottom = new Label(ustr "This should go on the bottom of the same top-level!")

+ 1 - 0
README.md

@@ -1,4 +1,5 @@
 ![.NET Core](https://github.com/migueldeicaza/gui.cs/workflows/.NET%20Core/badge.svg?branch=master)
 ![.NET Core](https://github.com/migueldeicaza/gui.cs/workflows/.NET%20Core/badge.svg?branch=master)
+![Code scanning - action](https://github.com/migueldeicaza/gui.cs/workflows/Code%20scanning%20-%20action/badge.svg)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)
 [![License](https://img.shields.io/github/license/migueldeicaza/gui.cs.svg)](LICENSE)
 [![License](https://img.shields.io/github/license/migueldeicaza/gui.cs.svg)](LICENSE)

+ 2 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -39,14 +39,14 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		static bool sync = false;
 		static bool sync = false;
-		public override void AddRune (Rune rune)
+		public override void AddRune (Rune rune) 
 		{
 		{
 			if (Clip.Contains (ccol, crow)) {
 			if (Clip.Contains (ccol, crow)) {
 				if (needMove) {
 				if (needMove) {
 					Curses.move (crow, ccol);
 					Curses.move (crow, ccol);
 					needMove = false;
 					needMove = false;
 				}
 				}
-				Curses.addch ((int)(uint)rune);
+				Curses.addch ((int)(uint)MakePrintable(rune));
 			} else
 			} else
 				needMove = true;
 				needMove = true;
 			if (sync)
 			if (sync)

+ 1 - 0
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -90,6 +90,7 @@ namespace Terminal.Gui {
 		/// <param name="rune"></param>
 		/// <param name="rune"></param>
 		public override void AddRune (Rune rune)
 		public override void AddRune (Rune rune)
 		{
 		{
+			rune = MakePrintable (rune);
 			if (Clip.Contains (ccol, crow)) {
 			if (Clip.Contains (ccol, crow)) {
 				if (needMove) {
 				if (needMove) {
 					//MockConsole.CursorLeft = ccol;
 					//MockConsole.CursorLeft = ccol;

+ 1 - 0
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -70,6 +70,7 @@ namespace Terminal.Gui {
 
 
 		public override void AddRune (Rune rune)
 		public override void AddRune (Rune rune)
 		{
 		{
+			rune = MakePrintable (rune);
 			if (Clip.Contains (ccol, crow)) {
 			if (Clip.Contains (ccol, crow)) {
 				if (needMove) {
 				if (needMove) {
 					//Console.CursorLeft = ccol;
 					//Console.CursorLeft = ccol;

+ 21 - 8
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -364,7 +364,7 @@ namespace Terminal.Gui {
 		[DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
 		[DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
 		public static extern bool ReadConsoleInput (
 		public static extern bool ReadConsoleInput (
 			IntPtr hConsoleInput,
 			IntPtr hConsoleInput,
-			[Out] InputRecord [] lpBuffer,
+			IntPtr lpBuffer,
 			uint nLength,
 			uint nLength,
 			out uint lpNumberOfEventsRead);
 			out uint lpNumberOfEventsRead);
 
 
@@ -420,6 +420,24 @@ namespace Terminal.Gui {
 				return v;
 				return v;
 			}
 			}
 		}
 		}
+		
+		public InputRecord [] ReadConsoleInput ()
+		{
+			const int bufferSize = 1;
+			var pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
+			try {
+				ReadConsoleInput (InputHandle, pRecord, bufferSize,
+					out var numberEventsRead);
+
+				return numberEventsRead == 0
+					? null
+					: new [] {Marshal.PtrToStructure<InputRecord> (pRecord)};
+			} catch (Exception) {
+				return null;
+			} finally {
+				Marshal.FreeHGlobal (pRecord);
+			}
+		}
 
 
 #if false // See: https://github.com/migueldeicaza/gui.cs/issues/357
 #if false // See: https://github.com/migueldeicaza/gui.cs/issues/357
 		[StructLayout (LayoutKind.Sequential)]
 		[StructLayout (LayoutKind.Sequential)]
@@ -577,13 +595,7 @@ namespace Terminal.Gui {
 				waitForProbe.Wait ();
 				waitForProbe.Wait ();
 				waitForProbe.Reset ();
 				waitForProbe.Reset ();
 
 
-				uint numberEventsRead = 0;
-
-				WindowsConsole.ReadConsoleInput (winConsole.InputHandle, records, 1, out numberEventsRead);
-				if (numberEventsRead == 0)
-					result = null;
-				else
-					result = records;
+				result = winConsole.ReadConsoleInput ();
 
 
 				eventReady.Set ();
 				eventReady.Set ();
 			}
 			}
@@ -1182,6 +1194,7 @@ namespace Terminal.Gui {
 
 
 		public override void AddRune (Rune rune)
 		public override void AddRune (Rune rune)
 		{
 		{
+			rune = MakePrintable (rune);
 			var position = crow * Cols + ccol;
 			var position = crow * Cols + ccol;
 
 
 			if (Clip.Contains (ccol, crow)) {
 			if (Clip.Contains (ccol, crow)) {

+ 23 - 1
Terminal.Gui/Core/ConsoleDriver.cs

@@ -1,4 +1,4 @@
-//
+//
 // ConsoleDriver.cs: Definition for the Console Driver API
 // ConsoleDriver.cs: Definition for the Console Driver API
 //
 //
 // Authors:
 // Authors:
@@ -558,6 +558,28 @@ namespace Terminal.Gui {
 		/// <param name="rune">Rune to add.</param>
 		/// <param name="rune">Rune to add.</param>
 		public abstract void AddRune (Rune rune);
 		public abstract void AddRune (Rune rune);
 		/// <summary>
 		/// <summary>
+		/// Ensures a Rune is not a control character and can be displayed by translating characters below 0x20
+		/// to equivalent, printable, Unicode chars.
+		/// </summary>
+		/// <param name="c">Rune to translate</param>
+		/// <returns></returns>
+		public static Rune MakePrintable (Rune c)
+		{
+			if (c <= 0x1F) {
+				// ASCII (C0) control characters. 
+				return new Rune (c + 0x2400);
+			} else if (c >= 0x80 && c <= 0x9F) {
+				// C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1)
+				return new Rune (0x25a1); // U+25A1, WHITE SQUARE, □: 
+			} else if (Rune.ColumnWidth (c) > 1) {
+				// BUGBUG: Until we figure out how to fix #41. Note this still doesn't help when 
+				// an Emoji or other char doesn't represent it's width correctly
+				return new Rune (0x25a1); // U+25A1, WHITE SQUARE, □: 
+			} else {
+				return c;
+			}
+		}
+		/// <summary>
 		/// Adds the specified
 		/// Adds the specified
 		/// </summary>
 		/// </summary>
 		/// <param name="str">String.</param>
 		/// <param name="str">String.</param>

+ 40 - 17
Terminal.Gui/Core/PosDim.cs

@@ -279,7 +279,7 @@ namespace Terminal.Gui {
 			}
 			}
 			internal override int Anchor (int width)
 			internal override int Anchor (int width)
 			{
 			{
-				switch(side) {
+				switch (side) {
 				case 0: return Target.Frame.X;
 				case 0: return Target.Frame.X;
 				case 1: return Target.Frame.Y;
 				case 1: return Target.Frame.Y;
 				case 2: return Target.Frame.Right;
 				case 2: return Target.Frame.Right;
@@ -292,7 +292,7 @@ namespace Terminal.Gui {
 			public override string ToString ()
 			public override string ToString ()
 			{
 			{
 				string tside;
 				string tside;
-				switch(side) {
+				switch (side) {
 				case 0: tside = "x"; break;
 				case 0: tside = "x"; break;
 				case 1: tside = "y"; break;
 				case 1: tside = "y"; break;
 				case 2: tside = "right"; break;
 				case 2: tside = "right"; break;
@@ -308,42 +308,42 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-		public static Pos Left (View view) => new PosView (view, 0);
+		public static Pos Left (View view) => new PosCombine (true, new PosView (view, 0), new Pos.PosAbsolute (0));
 
 
 		/// <summary>
 		/// <summary>
 		/// Returns a <see cref="Pos"/> object tracks the Left (X) position of the specified <see cref="View"/>.
 		/// Returns a <see cref="Pos"/> object tracks the Left (X) position of the specified <see cref="View"/>.
 		/// </summary>
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-		public static Pos X (View view) => new PosView (view, 0);
+		public static Pos X (View view) => new PosCombine (true, new PosView (view, 0), new Pos.PosAbsolute (0));
 
 
 		/// <summary>
 		/// <summary>
 		/// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
 		/// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
 		/// </summary>
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-		public static Pos Top (View view) => new PosView (view, 1);
+		public static Pos Top (View view) => new PosCombine (true, new PosView (view, 1), new Pos.PosAbsolute (0));
 
 
 		/// <summary>
 		/// <summary>
 		/// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
 		/// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
 		/// </summary>
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-		public static Pos Y (View view) => new PosView (view, 1);
+		public static Pos Y (View view) => new PosCombine (true, new PosView (view, 1), new Pos.PosAbsolute (0));
 
 
 		/// <summary>
 		/// <summary>
 		/// Returns a <see cref="Pos"/> object tracks the Right (X+Width) coordinate of the specified <see cref="View"/>.
 		/// Returns a <see cref="Pos"/> object tracks the Right (X+Width) coordinate of the specified <see cref="View"/>.
 		/// </summary>
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-		public static Pos Right (View view) => new PosView (view, 2);
+		public static Pos Right (View view) => new PosCombine (true, new PosView (view, 2), new Pos.PosAbsolute (0));
 
 
 		/// <summary>
 		/// <summary>
 		/// Returns a <see cref="Pos"/> object tracks the Bottom (Y+Height) coordinate of the specified <see cref="View"/> 
 		/// Returns a <see cref="Pos"/> object tracks the Bottom (Y+Height) coordinate of the specified <see cref="View"/> 
 		/// </summary>
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-		public static Pos Bottom (View view) => new PosView (view, 3);
+		public static Pos Bottom (View view) => new PosCombine (true, new PosView (view, 3), new Pos.PosAbsolute (0));
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -366,12 +366,14 @@ namespace Terminal.Gui {
 			return 0;
 			return 0;
 		}
 		}
 
 
-		class DimFactor : Dim {
+		internal class DimFactor : Dim {
 			float factor;
 			float factor;
+			bool remaining;
 
 
-			public DimFactor (float n)
+			public DimFactor (float n, bool r = false)
 			{
 			{
-				this.factor = n;
+				factor = n;
+				remaining = r;
 			}
 			}
 
 
 			internal override int Anchor (int width)
 			internal override int Anchor (int width)
@@ -379,14 +381,19 @@ namespace Terminal.Gui {
 				return (int)(width * factor);
 				return (int)(width * factor);
 			}
 			}
 
 
+			public bool IsFromRemaining ()
+			{
+				return remaining;
+			}
+
 			public override string ToString ()
 			public override string ToString ()
 			{
 			{
-				return $"Dim.Factor({factor})";
+				return $"Dim.Factor(factor={factor}, remaining={remaining})";
 			}
 			}
 
 
 			public override int GetHashCode () => factor.GetHashCode ();
 			public override int GetHashCode () => factor.GetHashCode ();
 
 
-			public override bool Equals (object other) => other is DimFactor f && f.factor == factor;
+			public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
 
 
 		}
 		}
 
 
@@ -395,6 +402,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <returns>The percent <see cref="Dim"/> object.</returns>
 		/// <returns>The percent <see cref="Dim"/> object.</returns>
 		/// <param name="n">A value between 0 and 100 representing the percentage.</param>
 		/// <param name="n">A value between 0 and 100 representing the percentage.</param>
+		/// <param name="r">If <c>true</c> the Percent is computed based on the remaining space after the X/Y anchor positions. If <c>false</c> is computed based on the whole original space.</param>
 		/// <example>
 		/// <example>
 		/// This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, 
 		/// This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, 
 		/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
 		/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
@@ -407,12 +415,12 @@ namespace Terminal.Gui {
 		/// };
 		/// };
 		/// </code>
 		/// </code>
 		/// </example>
 		/// </example>
-		public static Dim Percent (float n)
+		public static Dim Percent (float n, bool r = false)
 		{
 		{
 			if (n < 0 || n > 100)
 			if (n < 0 || n > 100)
 				throw new ArgumentException ("Percent value must be between 0 and 100");
 				throw new ArgumentException ("Percent value must be between 0 and 100");
 
 
-			return new DimFactor (n / 100);
+			return new DimFactor (n / 100, r);
 		}
 		}
 
 
 		internal class DimAbsolute : Dim {
 		internal class DimAbsolute : Dim {
@@ -546,7 +554,7 @@ namespace Terminal.Gui {
 			public override string ToString ()
 			public override string ToString ()
 			{
 			{
 				string tside;
 				string tside;
-				switch(side) {
+				switch (side) {
 				case 0: tside = "Height"; break;
 				case 0: tside = "Height"; break;
 				case 1: tside = "Width"; break;
 				case 1: tside = "Width"; break;
 				default: tside = "unknown"; break;
 				default: tside = "unknown"; break;
@@ -556,13 +564,18 @@ namespace Terminal.Gui {
 
 
 			internal override int Anchor (int width)
 			internal override int Anchor (int width)
 			{
 			{
-				switch(side) {
+				switch (side) {
 				case 0: return Target.Frame.Height;
 				case 0: return Target.Frame.Height;
 				case 1: return Target.Frame.Width;
 				case 1: return Target.Frame.Width;
 				default:
 				default:
 					return 0;
 					return 0;
 				}
 				}
 			}
 			}
+
+			public override int GetHashCode () => Target.GetHashCode ();
+
+			public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
+
 		}
 		}
 		/// <summary>
 		/// <summary>
 		/// Returns a <see cref="Dim"/> object tracks the Width of the specified <see cref="View"/>.
 		/// Returns a <see cref="Dim"/> object tracks the Width of the specified <see cref="View"/>.
@@ -577,5 +590,15 @@ namespace Terminal.Gui {
 		/// <returns>The <see cref="Dim"/> of the other <see cref="View"/>.</returns>
 		/// <returns>The <see cref="Dim"/> of the other <see cref="View"/>.</returns>
 		/// <param name="view">The view that will be tracked.</param>
 		/// <param name="view">The view that will be tracked.</param>
 		public static Dim Height (View view) => new DimView (view, 0);
 		public static Dim Height (View view) => new DimView (view, 0);
+
+		/// <summary>Serves as the default hash function. </summary>
+		/// <returns>A hash code for the current object.</returns>
+		public override int GetHashCode () => GetHashCode ();
+
+		/// <summary>Determines whether the specified object is equal to the current object.</summary>
+		/// <param name="other">The object to compare with the current object. </param>
+		/// <returns>
+		///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
+		public override bool Equals (object other) => other is Dim abs && abs == this;
 	}
 	}
 }
 }

+ 22 - 24
Terminal.Gui/Core/Toplevel.cs

@@ -269,31 +269,28 @@ namespace Terminal.Gui {
 
 
 		internal void PositionToplevels ()
 		internal void PositionToplevels ()
 		{
 		{
-			if (this != Application.Top) {
-				EnsureVisibleBounds (this, Frame.X, Frame.Y, out int nx, out int ny);
-				if ((nx != Frame.X || ny != Frame.Y) && LayoutStyle != LayoutStyle.Computed) {
-					X = nx;
-					Y = ny;
+			foreach (var top in Subviews) {
+				if (top is Toplevel) {
+					PositionToplevel ((Toplevel)top);
 				}
 				}
-			} else {
-				foreach (var top in Subviews) {
-					if (top is Toplevel) {
-						EnsureVisibleBounds ((Toplevel)top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
-						if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle != LayoutStyle.Computed) {
-							top.X = nx;
-							top.Y = ny;
-						}
-						if (StatusBar != null) {
-							if (ny + top.Frame.Height > Driver.Rows - 1) {
-								if (top.Height is Dim.DimFill)
-									top.Height = Dim.Fill () - 1;
-							}
-							if (StatusBar.Frame.Y != Driver.Rows - 1) {
-								StatusBar.Y = Driver.Rows - 1;
-								SetNeedsDisplay ();
-							}
-						}
-					}
+			}
+		}
+
+		private void PositionToplevel (Toplevel top)
+		{
+			EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
+			if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle != LayoutStyle.Computed) {
+				top.X = nx;
+				top.Y = ny;
+			}
+			if (StatusBar != null) {
+				if (ny + top.Frame.Height > Driver.Rows - 1) {
+					if (top.Height is Dim.DimFill)
+						top.Height = Dim.Fill () - 1;
+				}
+				if (StatusBar.Frame.Y != Driver.Rows - 1) {
+					StatusBar.Y = Driver.Rows - 1;
+					SetNeedsDisplay ();
 				}
 				}
 			}
 			}
 		}
 		}
@@ -311,6 +308,7 @@ namespace Terminal.Gui {
 					// (the bounds passed to us).
 					// (the bounds passed to us).
 					Clear (bounds);
 					Clear (bounds);
 					Driver.SetAttribute (Colors.Base.Normal);
 					Driver.SetAttribute (Colors.Base.Normal);
+					PositionToplevels ();
 				}
 				}
 				foreach (var view in Subviews) {
 				foreach (var view in Subviews) {
 					if (view.Frame.IntersectsWith (bounds)) {
 					if (view.Frame.IntersectsWith (bounds)) {

+ 6 - 2
Terminal.Gui/Core/View.cs

@@ -1325,8 +1325,10 @@ namespace Terminal.Gui {
 					_x = x.Anchor (hostFrame.Width);
 					_x = x.Anchor (hostFrame.Width);
 				if (width == null)
 				if (width == null)
 					w = hostFrame.Width;
 					w = hostFrame.Width;
+				else if (width is Dim.DimFactor && !((Dim.DimFactor)width).IsFromRemaining ())
+					w = width.Anchor (hostFrame.Width);
 				else
 				else
-					w = width.Anchor (hostFrame.Width - _x);
+					w = Math.Max (width.Anchor (hostFrame.Width - _x), 0);
 			}
 			}
 
 
 			if (y is Pos.PosCenter) {
 			if (y is Pos.PosCenter) {
@@ -1342,8 +1344,10 @@ namespace Terminal.Gui {
 					_y = y.Anchor (hostFrame.Height);
 					_y = y.Anchor (hostFrame.Height);
 				if (height == null)
 				if (height == null)
 					h = hostFrame.Height;
 					h = hostFrame.Height;
+				else if (height is Dim.DimFactor && !((Dim.DimFactor)height).IsFromRemaining ())
+					h = height.Anchor (hostFrame.Height);
 				else
 				else
-					h = height.Anchor (hostFrame.Height - _y);
+					h = Math.Max (height.Anchor (hostFrame.Height - _y), 0);
 			}
 			}
 			Frame = new Rect (_x, _y, w, h);
 			Frame = new Rect (_x, _y, w, h);
 		}
 		}

+ 3 - 0
Terminal.Gui/Types/Point.cs

@@ -111,6 +111,9 @@ namespace Terminal.Gui
 
 
 		public static explicit operator Size (Point p)
 		public static explicit operator Size (Point p)
 		{
 		{
+			if (p.X < 0 || p.Y < 0)
+				throw new ArgumentException ("Either Width and Height must be greater or equal to 0.");
+
 			return new Size (p.X, p.Y);
 			return new Size (p.X, p.Y);
 		}
 		}
 
 

+ 29 - 8
Terminal.Gui/Types/Rect.cs

@@ -17,6 +17,9 @@ namespace Terminal.Gui
 	/// </summary>
 	/// </summary>
 	public struct Rect
 	public struct Rect
 	{
 	{
+		int width;
+		int height;
+
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the x-coordinate of the upper-left corner of this Rectangle structure.
 		/// Gets or sets the x-coordinate of the upper-left corner of this Rectangle structure.
 		/// </summary>
 		/// </summary>
@@ -29,12 +32,26 @@ namespace Terminal.Gui
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the width of this Rect structure.
 		/// Gets or sets the width of this Rect structure.
 		/// </summary>
 		/// </summary>
-		public int Width;
+		public int Width {
+			get { return width; }
+			set {
+				if (value < 0)
+					throw new ArgumentException ("Width must be greater or equal to 0.");
+				width = value;
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the height of this Rectangle structure.
 		/// Gets or sets the height of this Rectangle structure.
 		/// </summary>
 		/// </summary>
-		public int Height;
+		public int Height {
+			get { return height; }
+			set {
+				if (value < 0)
+					throw new ArgumentException ("Height must be greater or equal to 0.");
+				height = value;
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
 		///	Empty Shared Field
 		///	Empty Shared Field
@@ -209,8 +226,10 @@ namespace Terminal.Gui
 		{
 		{
 			X = location.X;
 			X = location.X;
 			Y = location.Y;
 			Y = location.Y;
-			Width = size.Width;
-			Height = size.Height;
+			width = size.Width;
+			height = size.Height;
+			Width = width;
+			Height = height;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -224,10 +243,12 @@ namespace Terminal.Gui
 
 
 		public Rect (int x, int y, int width, int height)
 		public Rect (int x, int y, int width, int height)
 		{
 		{
-			this.X = x;
-			this.Y = y;
-			this.Width = width;
-			this.Height = height;
+			X = x;
+			Y = y;
+			this.width = width;
+			this.height = height;
+			Width = this.width;
+			Height = this.height;
 		}
 		}
 
 
 
 

+ 7 - 0
Terminal.Gui/Types/Size.cs

@@ -121,6 +121,9 @@ namespace Terminal.Gui {
 
 
 		public Size (int width, int height)
 		public Size (int width, int height)
 		{
 		{
+			if (width < 0 || height < 0)
+				throw new ArgumentException ("Either Width and Height must be greater or equal to 0.");
+
 			this.width = width;
 			this.width = width;
 			this.height = height;
 			this.height = height;
 		}
 		}
@@ -152,6 +155,8 @@ namespace Terminal.Gui {
 				return width;
 				return width;
 			}
 			}
 			set {
 			set {
+				if (value < 0)
+					throw new ArgumentException ("Width must be greater or equal to 0.");
 				width = value;
 				width = value;
 			}
 			}
 		}
 		}
@@ -169,6 +174,8 @@ namespace Terminal.Gui {
 				return height;
 				return height;
 			}
 			}
 			set {
 			set {
+				if (value < 0)
+					throw new ArgumentException ("Height must be greater or equal to 0.");
 				height = value;
 				height = value;
 			}
 			}
 		}
 		}

+ 19 - 19
Terminal.Gui/Views/Button.cs

@@ -62,7 +62,7 @@ namespace Terminal.Gui {
 		///   The width of the <see cref="Button"/> is computed based on the
 		///   The width of the <see cref="Button"/> is computed based on the
 		///   text length. The height will always be 1.
 		///   text length. The height will always be 1.
 		/// </remarks>
 		/// </remarks>
-		public Button () : this (string.Empty) { }
+		public Button () : this (text: string.Empty, is_default: false) { }
 
 
 		/// <summary>
 		/// <summary>
 		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
 		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
@@ -78,11 +78,7 @@ namespace Terminal.Gui {
 		/// </param>
 		/// </param>
 		public Button (ustring text, bool is_default = false) : base ()
 		public Button (ustring text, bool is_default = false) : base ()
 		{
 		{
-			CanFocus = true;
-			Text = text ?? string.Empty;
-			this.IsDefault = is_default;
-			int w = SetWidthHeight (text, is_default);
-			Frame = new Rect (Frame.Location, new Size (w, 1));
+			Init (text, is_default);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -114,9 +110,26 @@ namespace Terminal.Gui {
 		public Button (int x, int y, ustring text, bool is_default)
 		public Button (int x, int y, ustring text, bool is_default)
 		    : base (new Rect (x, y, text.Length + 4 + (is_default ? 2 : 0), 1))
 		    : base (new Rect (x, y, text.Length + 4 + (is_default ? 2 : 0), 1))
 		{
 		{
+			Init (text, is_default);
+		}
+
+		Rune _leftBracket;
+		Rune _rightBracket;
+		Rune _leftDefault;
+		Rune _rightDefault;
+
+		void Init (ustring text, bool is_default)
+		{
+			_leftBracket = new Rune (Driver != null ? Driver.LeftBracket : '[');
+			_rightBracket = new Rune (Driver != null ? Driver.RightBracket : ']');
+			_leftDefault = new Rune (Driver != null ? Driver.LeftDefaultIndicator : '<');
+			_rightDefault = new Rune (Driver != null ? Driver.RightDefaultIndicator : '>');
+
 			CanFocus = true;
 			CanFocus = true;
 			Text = text ?? string.Empty;
 			Text = text ?? string.Empty;
 			this.IsDefault = is_default;
 			this.IsDefault = is_default;
+			int w = SetWidthHeight (text, is_default);
+			Frame = new Rect (Frame.Location, new Size (w, 1));
 		}
 		}
 
 
 		int SetWidthHeight (ustring text, bool is_default)
 		int SetWidthHeight (ustring text, bool is_default)
@@ -154,11 +167,6 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		Rune _leftBracket = new Rune (Driver != null ? Driver.LeftBracket : '[');
-		Rune _rightBracket = new Rune (Driver != null ? Driver.RightBracket : ']');
-		Rune _leftDefault = new Rune (Driver != null ? Driver.LeftDefaultIndicator : '<');
-		Rune _rightDefault = new Rune (Driver != null ? Driver.RightDefaultIndicator : '>');
-
 		internal void Update ()
 		internal void Update ()
 		{
 		{
 			if (IsDefault)
 			if (IsDefault)
@@ -166,14 +174,6 @@ namespace Terminal.Gui {
 			else
 			else
 				shown_text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 				shown_text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 
 
-			shown_text = shown_text
-				.Replace ("\f", "\u21a1")		// U+21A1 ↡ DOWNWARDS TWO HEADED ARROW
-				.Replace ("\n", "\u240a")		// U+240A (SYMBOL FOR LINE FEED, ␊)
-				.Replace ("\r", "\u240d")		// U+240D (SYMBOL FOR CARRIAGE RETURN, ␍)
-				.Replace ("\t", "\u2409")		// U+2409 ␉ SYMBOL FOR HORIZONTAL TABULATION
-				.Replace ("\v", "\u240b")		// U+240B ␋ SYMBOL FOR VERTICAL TABULATION
-				.TrimSpace ();
-
 			shown_text = GetTextFromHotKey (shown_text, '_', out hot_pos, out hot_key);
 			shown_text = GetTextFromHotKey (shown_text, '_', out hot_pos, out hot_key);
 
 
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();

+ 15 - 7
Terminal.Gui/Views/Label.cs

@@ -123,8 +123,6 @@ namespace Terminal.Gui {
 
 
 		static ustring ClipAndJustify (ustring str, int width, TextAlignment talign)
 		static ustring ClipAndJustify (ustring str, int width, TextAlignment talign)
 		{
 		{
-			// Get rid of any '\r' added by Windows
-			str = str.Replace ("\r", ustring.Empty);
 			int slen = str.RuneCount;
 			int slen = str.RuneCount;
 			if (slen > width) {
 			if (slen > width) {
 				var uints = str.ToRunes (width);
 				var uints = str.ToRunes (width);
@@ -166,12 +164,22 @@ namespace Terminal.Gui {
 			Recalc (text, lines, Frame.Width, textAlignment, Bounds.Height > 1);
 			Recalc (text, lines, Frame.Width, textAlignment, Bounds.Height > 1);
 		}
 		}
 
 
-		static ustring ReplaceNonPrintables (ustring str)
+		static ustring StripCRLF (ustring str)
 		{
 		{
 			var runes = new List<Rune> ();
 			var runes = new List<Rune> ();
 			foreach (var r in str.ToRunes ()) {
 			foreach (var r in str.ToRunes ()) {
-				if (r < 0x20) {
-					runes.Add (new Rune (r + 0x2400));         // U+25A1 □ WHITE SQUARE
+				if (r != '\r' && r != '\n') {
+					runes.Add (r);
+				}
+			}
+			return ustring.Make (runes); ;
+		}
+		static ustring ReplaceCRLFWithSpace (ustring str)
+		{
+			var runes = new List<Rune> ();
+			foreach (var r in str.ToRunes ()) {
+				if (r == '\r' || r == '\n') {
+					runes.Add (new Rune (' ')); // r + 0x2400));         // U+25A1 □ WHITE SQUARE
 				} else {
 				} else {
 					runes.Add (r);
 					runes.Add (r);
 				}
 				}
@@ -184,7 +192,7 @@ namespace Terminal.Gui {
 			int start = 0, end;
 			int start = 0, end;
 			var lines = new List<ustring> ();
 			var lines = new List<ustring> ();
 
 
-			text = ReplaceNonPrintables (text);
+			text = StripCRLF (text);
 
 
 			while ((end = start + margin) < text.Length) {
 			while ((end = start + margin) < text.Length) {
 				while (text [end] != ' ' && end > start)
 				while (text [end] != ' ' && end > start)
@@ -208,7 +216,7 @@ namespace Terminal.Gui {
 			lineResult.Clear ();
 			lineResult.Clear ();
 
 
 			if (wordWrap == false) {
 			if (wordWrap == false) {
-				textStr = ReplaceNonPrintables (textStr);
+				textStr = ReplaceCRLFWithSpace (textStr);
 				lineResult.Add (ClipAndJustify (textStr, width, talign));
 				lineResult.Add (ClipAndJustify (textStr, width, talign));
 				return;
 				return;
 			}
 			}

+ 1 - 1
Terminal.Gui/Views/ListView.cs

@@ -568,7 +568,7 @@ namespace Terminal.Gui {
 			for (int i = 0; i < byteLen;) {
 			for (int i = 0; i < byteLen;) {
 				(var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen);
 				(var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen);
 				var count = Rune.ColumnWidth (rune);
 				var count = Rune.ColumnWidth (rune);
-				if (used + count >= width)
+				if (used + count > width)
 					break;
 					break;
 				driver.AddRune (rune);
 				driver.AddRune (rune);
 				used += count;
 				used += count;

+ 8 - 4
UICatalog/Scenarios/AllViewsTester.cs

@@ -317,7 +317,7 @@ namespace UICatalog {
 
 
 		void UpdateTitle (View view)
 		void UpdateTitle (View view)
 		{
 		{
-			_hostPane.Title = $"{view.GetType().Name} - {view.X.ToString ()}, {view.Y.ToString ()}, {view.Width.ToString ()}, {view.Height.ToString ()}";
+			_hostPane.Title = $"{view.GetType ().Name} - {view.X.ToString ()}, {view.Y.ToString ()}, {view.Width.ToString ()}, {view.Height.ToString ()}";
 		}
 		}
 
 
 		List<Type> GetAllViewClassesCollection ()
 		List<Type> GetAllViewClassesCollection ()
@@ -363,13 +363,17 @@ namespace UICatalog {
 				}
 				}
 			}
 			}
 
 
-			if (view == null) return null;
-
 			// If the view supports a Title property, set it so we have something to look at
 			// If the view supports a Title property, set it so we have something to look at
-			if (view.GetType ().GetProperty ("Title") != null) {
+			if (view != null && view.GetType ().GetProperty ("Title") != null) {
 				view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { ustring.Make ("Test Title") });
 				view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { ustring.Make ("Test Title") });
 			}
 			}
 
 
+			// If the view supports a Source property, set it so we have something to look at
+			if (view != null && view.GetType ().GetProperty ("Source") != null) {
+				var source = new ListWrapper (new List<ustring> () { ustring.Make ("List Item #1"), ustring.Make ("List Item #2"), ustring.Make ("List Item #3")});
+				view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
+			}
+
 			// Set Settings
 			// Set Settings
 			_computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed;
 			_computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed;
 
 

+ 2 - 2
UICatalog/Scenarios/Buttons.cs

@@ -210,7 +210,7 @@ namespace UICatalog {
 			var moveHotKeyBtn = new Button (mhkb) {
 			var moveHotKeyBtn = new Button (mhkb) {
 				X = 2,
 				X = 2,
 				Y = Pos.Bottom (radioGroup) + 1,
 				Y = Pos.Bottom (radioGroup) + 1,
-				Width = mhkb.Length + 10,
+				Width = Dim.Width (computedFrame) - 2,
 				ColorScheme = Colors.TopLevel,
 				ColorScheme = Colors.TopLevel,
 			};
 			};
 			moveHotKeyBtn.Clicked = () => {
 			moveHotKeyBtn.Clicked = () => {
@@ -222,7 +222,7 @@ namespace UICatalog {
 			var moveUnicodeHotKeyBtn = new Button (muhkb) {
 			var moveUnicodeHotKeyBtn = new Button (muhkb) {
 				X = Pos.Left (absoluteFrame) + 1,
 				X = Pos.Left (absoluteFrame) + 1,
 				Y = Pos.Bottom (radioGroup) + 1,
 				Y = Pos.Bottom (radioGroup) + 1,
-				Width = muhkb.Length + 30,
+				Width = Dim.Width (absoluteFrame) - 2, // BUGBUG: Not always the width isn't calculated correctly.
 				ColorScheme = Colors.TopLevel,
 				ColorScheme = Colors.TopLevel,
 			};
 			};
 			moveUnicodeHotKeyBtn.Clicked = () => {
 			moveUnicodeHotKeyBtn.Clicked = () => {

+ 18 - 2
UICatalog/Scenarios/CharacterMap.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 using Terminal.Gui;
 using Terminal.Gui;
+using Rune = System.Rune;
 
 
 namespace UICatalog {
 namespace UICatalog {
 	/// <summary>
 	/// <summary>
@@ -37,6 +38,9 @@ namespace UICatalog {
 			}
 			}
 
 
 			var radioItems = new (ustring radioLabel, int start, int end) [] {
 			var radioItems = new (ustring radioLabel, int start, int end) [] {
+				CreateRadio("ASCII Control Characterss", 0x00, 0x1F),
+				CreateRadio("C0 Control Characters", 0x80, 0x9f),
+				CreateRadio("Hangul Jamo", 0x1100, 0x11ff),	// This is where wide chars tend to start
 				CreateRadio("Currency Symbols", 0x20A0, 0x20CF),
 				CreateRadio("Currency Symbols", 0x20A0, 0x20CF),
 				CreateRadio("Letterlike Symbols", 0x2100, 0x214F),
 				CreateRadio("Letterlike Symbols", 0x2100, 0x214F),
 				CreateRadio("Arrows", 0x2190, 0x21ff),
 				CreateRadio("Arrows", 0x2190, 0x21ff),
@@ -110,6 +114,15 @@ namespace UICatalog {
 #if true
 #if true
 		private void CharMap_DrawContent (Rect viewport)
 		private void CharMap_DrawContent (Rect viewport)
 		{
 		{
+			//Rune ReplaceNonPrintables (Rune c)
+			//{
+			//	if (c < 0x20) {
+			//		return new Rune (c + 0x2400);         // U+25A1 □ WHITE SQUARE
+			//	} else {
+			//		return c;
+			//	}
+			//}
+
 			for (int header = 0; header < 16; header++) {
 			for (int header = 0; header < 16; header++) {
 				Move (viewport.X + RowHeaderWidth + (header * 2), 0);
 				Move (viewport.X + RowHeaderWidth + (header * 2), 0);
 				Driver.AddStr ($" {header:x} ");
 				Driver.AddStr ($" {header:x} ");
@@ -120,9 +133,12 @@ namespace UICatalog {
 					var rowLabel = $"U+{val / 16:x4}x";
 					var rowLabel = $"U+{val / 16:x4}x";
 					Move (0, y + 1);
 					Move (0, y + 1);
 					Driver.AddStr (rowLabel);
 					Driver.AddStr (rowLabel);
+					var prevColWasWide = false;
 					for (int col = 0; col < 16; col++) {
 					for (int col = 0; col < 16; col++) {
-						Move (viewport.X + RowHeaderWidth + (col * 2), 0 + y + 1);
-						Driver.AddStr ($" {(char)((-viewport.Y + row) * 16 + col)}");
+						var rune = new Rune ((uint)((uint)(-viewport.Y + row) * 16 + col));
+						Move (viewport.X + RowHeaderWidth + (col * 2) + (prevColWasWide ? 0 : 1), 0 + y + 1);
+						Driver.AddRune (rune);
+						//prevColWasWide = Rune.ColumnWidth(rune) > 1;
 					}
 					}
 				}
 				}
 			}
 			}

+ 11 - 11
UICatalog/Scenarios/Keys.cs

@@ -7,11 +7,11 @@ namespace UICatalog {
 	[ScenarioCategory ("Input")]
 	[ScenarioCategory ("Input")]
 	class Keys : Scenario {
 	class Keys : Scenario {
 
 
-		static List<string> _processKeyList = new List<string> ();
-		static List<string> _processHotKeyList = new List<string> ();
-		static List<string> _processColdKeyList = new List<string> ();
-
 		class TestWindow : Window {
 		class TestWindow : Window {
+			public List<string> _processKeyList = new List<string> ();
+			public List<string> _processHotKeyList = new List<string> ();
+			public List<string> _processColdKeyList = new List<string> ();
+
 			public TestWindow (ustring title = null) : base (title)
 			public TestWindow (ustring title = null) : base (title)
 			{
 			{
 			}
 			}
@@ -112,7 +112,7 @@ namespace UICatalog {
 			var keyStrokeListView = new ListView (keyStrokelist) {
 			var keyStrokeListView = new ListView (keyStrokelist) {
 				X = 0,
 				X = 0,
 				Y = Pos.Top (keyLogLabel) + yOffset,
 				Y = Pos.Top (keyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (30),
 				Height = Dim.Fill (),
 				Height = Dim.Fill (),
 			};
 			};
 			keyStrokeListView.ColorScheme = Colors.TopLevel;
 			keyStrokeListView.ColorScheme = Colors.TopLevel;
@@ -127,10 +127,10 @@ namespace UICatalog {
 
 
 			maxLogEntry = $"{fakeKeyPress}".Length;
 			maxLogEntry = $"{fakeKeyPress}".Length;
 			yOffset = (Top == Application.Top ? 1 : 6);
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processKeyListView = new ListView (_processKeyList) {
+			var processKeyListView = new ListView (((TestWindow)Win)._processKeyList) {
 				X = Pos.Left (processKeyLogLabel),
 				X = Pos.Left (processKeyLogLabel),
 				Y = Pos.Top (processKeyLogLabel) + yOffset,
 				Y = Pos.Top (processKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent(30),
 				Height = Dim.Fill (),
 				Height = Dim.Fill (),
 			};
 			};
 			processKeyListView.ColorScheme = Colors.TopLevel;
 			processKeyListView.ColorScheme = Colors.TopLevel;
@@ -145,10 +145,10 @@ namespace UICatalog {
 			Win.Add (processHotKeyLogLabel);
 			Win.Add (processHotKeyLogLabel);
 
 
 			yOffset = (Top == Application.Top ? 1 : 6);
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processHotKeyListView = new ListView (_processHotKeyList) {
+			var processHotKeyListView = new ListView (((TestWindow)Win)._processHotKeyList) {
 				X = Pos.Left (processHotKeyLogLabel),
 				X = Pos.Left (processHotKeyLogLabel),
 				Y = Pos.Top (processHotKeyLogLabel) + yOffset,
 				Y = Pos.Top (processHotKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (20),
 				Height = Dim.Fill (),
 				Height = Dim.Fill (),
 			};
 			};
 			processHotKeyListView.ColorScheme = Colors.TopLevel;
 			processHotKeyListView.ColorScheme = Colors.TopLevel;
@@ -163,10 +163,10 @@ namespace UICatalog {
 			Win.Add (processColdKeyLogLabel);
 			Win.Add (processColdKeyLogLabel);
 
 
 			yOffset = (Top == Application.Top ? 1 : 6);
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processColdKeyListView = new ListView (_processColdKeyList) {
+			var processColdKeyListView = new ListView (((TestWindow)Win)._processColdKeyList) {
 				X = Pos.Left (processColdKeyLogLabel),
 				X = Pos.Left (processColdKeyLogLabel),
 				Y = Pos.Top (processColdKeyLogLabel) + yOffset,
 				Y = Pos.Top (processColdKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (20),
 				Height = Dim.Fill (),
 				Height = Dim.Fill (),
 			};
 			};
 
 

+ 2 - 2
UICatalog/Scenarios/ListsAndCombos.cs

@@ -42,14 +42,14 @@ namespace UICatalog.Scenarios {
 			var lbComboBox = new Label ("ComboBox") {
 			var lbComboBox = new Label ("ComboBox") {
 				ColorScheme = Colors.TopLevel,
 				ColorScheme = Colors.TopLevel,
 				X = Pos.Right (lbListView) + 1,
 				X = Pos.Right (lbListView) + 1,
-				Width = Dim.Percent(60)
+				Width = Dim.Percent(40)
 			};
 			};
 
 
 			var comboBox = new ComboBox () {
 			var comboBox = new ComboBox () {
 				X = Pos.Right (listview) + 1,
 				X = Pos.Right (listview) + 1,
 				Y = Pos.Bottom (lbListView) + 1,
 				Y = Pos.Bottom (lbListView) + 1,
 				Height = Dim.Fill (2),
 				Height = Dim.Fill (2),
-				Width = Dim.Percent(60)
+				Width = Dim.Percent(40)
 			};
 			};
 			comboBox.SetSource (items);
 			comboBox.SetSource (items);
 
 

+ 122 - 9
UnitTests/DimTests.cs

@@ -27,9 +27,37 @@ namespace Terminal.Gui {
 			int testVal = 5;
 			int testVal = 5;
 			dim = Dim.Sized (testVal);
 			dim = Dim.Sized (testVal);
 			Assert.Equal ($"Dim.Absolute({testVal})", dim.ToString ());
 			Assert.Equal ($"Dim.Absolute({testVal})", dim.ToString ());
+
+			testVal = -1;
+			dim = Dim.Sized (testVal);
+			Assert.Equal ($"Dim.Absolute({testVal})", dim.ToString ());
 		}
 		}
 
 
-		// TODO: Other Dim.Sized tests (e.g. Equal?)
+		[Fact]
+		public void Sized_Equals ()
+		{
+			int n1 = 0;
+			int n2 = 0;
+			var dim1 = Dim.Sized (n1);
+			var dim2 = Dim.Sized (n2);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = 1;
+			dim1 = Dim.Sized (n1);
+			dim2 = Dim.Sized (n2);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = -1;
+			dim1 = Dim.Sized (n1);
+			dim2 = Dim.Sized (n2);
+			Assert.Equal (dim1, dim2);
+
+			n1 = 0;
+			n2 = 1;
+			dim1 = Dim.Sized (n1);
+			dim2 = Dim.Sized (n2);
+			Assert.NotEqual (dim1, dim2);
+		}
 
 
 		[Fact]
 		[Fact]
 		public void Width_SetsValue ()
 		public void Width_SetsValue ()
@@ -47,7 +75,47 @@ namespace Terminal.Gui {
 			Assert.Equal ($"DimView(side=Width, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", dim.ToString ());
 			Assert.Equal ($"DimView(side=Width, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", dim.ToString ());
 		}
 		}
 
 
-		// TODO: Other Dim.Width tests (e.g. Equal?)
+		[Fact]
+		public void Width_Equals ()
+		{
+			var testRect1 = Rect.Empty;
+			var view1 = new View (testRect1);
+			var testRect2 = Rect.Empty;
+			var view2 = new View (testRect2);
+
+			var dim1 = Dim.Width (view1);
+			var dim2 = Dim.Width (view1);
+			// FIXED: Dim.Width should support Equals() and this should change to Equal.
+			Assert.Equal (dim1, dim2);
+
+			dim2 = Dim.Width (view2);
+			Assert.NotEqual (dim1, dim2);
+
+			testRect1 = new Rect (0, 1, 2, 3);
+			view1 = new View (testRect1);
+			testRect2 = new Rect (0, 1, 2, 3);
+			dim1 = Dim.Width (view1);
+			dim2 = Dim.Width (view1);
+			// FIXED: Dim.Width should support Equals() and this should change to Equal.
+			Assert.Equal (dim1, dim2);
+
+			Assert.Throws<ArgumentException> (() => new Rect (0, -1, -2, -3));
+			testRect1 = new Rect (0, -1, 2, 3);
+			view1 = new View (testRect1);
+			testRect2 = new Rect (0, -1, 2, 3);
+			dim1 = Dim.Width (view1);
+			dim2 = Dim.Width (view1);
+			// FIXED: Dim.Width should support Equals() and this should change to Equal.
+			Assert.Equal (dim1, dim2);
+
+			testRect1 = new Rect (0, -1, 2, 3);
+			view1 = new View (testRect1);
+			testRect2 = Rect.Empty;
+			view2 = new View (testRect2);
+			dim1 = Dim.Width (view1);
+			dim2 = Dim.Width (view2);
+			Assert.NotEqual (dim1, dim2);
+		}
 
 
 		[Fact]
 		[Fact]
 		public void Height_SetsValue ()
 		public void Height_SetsValue ()
@@ -72,7 +140,7 @@ namespace Terminal.Gui {
 		{
 		{
 			var testMargin = 0;
 			var testMargin = 0;
 			var dim = Dim.Fill ();
 			var dim = Dim.Fill ();
-			Assert.Equal ($"Dim.Fill(margin={testMargin})", dim.ToString());
+			Assert.Equal ($"Dim.Fill(margin={testMargin})", dim.ToString ());
 
 
 			testMargin = 0;
 			testMargin = 0;
 			dim = Dim.Fill (testMargin);
 			dim = Dim.Fill (testMargin);
@@ -85,7 +153,7 @@ namespace Terminal.Gui {
 
 
 
 
 		[Fact]
 		[Fact]
-		public void Fill_Equal()
+		public void Fill_Equal ()
 		{
 		{
 			var margin1 = 0;
 			var margin1 = 0;
 			var margin2 = 0;
 			var margin2 = 0;
@@ -99,19 +167,64 @@ namespace Terminal.Gui {
 		{
 		{
 			float f = 0;
 			float f = 0;
 			var dim = Dim.Percent (f);
 			var dim = Dim.Percent (f);
-			Assert.Equal ($"Dim.Factor({f/100:0.###})", dim.ToString ());
+			Assert.Equal ($"Dim.Factor(factor={f / 100:0.###}, remaining={false})", dim.ToString ());
 			f = 0.5F;
 			f = 0.5F;
 			dim = Dim.Percent (f);
 			dim = Dim.Percent (f);
-			Assert.Equal ($"Dim.Factor({f/100:0.###})", dim.ToString ());
+			Assert.Equal ($"Dim.Factor(factor={f / 100:0.###}, remaining={false})", dim.ToString ());
 			f = 100;
 			f = 100;
 			dim = Dim.Percent (f);
 			dim = Dim.Percent (f);
-			Assert.Equal ($"Dim.Factor({f/100:0.###})", dim.ToString ());
+			Assert.Equal ($"Dim.Factor(factor={f / 100:0.###}, remaining={false})", dim.ToString ());
 		}
 		}
 
 
-		// TODO: Other Dim.Percent tests (e.g. Equal?)
+		[Fact]
+		public void Percent_Equals ()
+		{
+			float n1 = 0;
+			float n2 = 0;
+			var dim1 = Dim.Percent (n1);
+			var dim2 = Dim.Percent (n2);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = 1;
+			dim1 = Dim.Percent (n1);
+			dim2 = Dim.Percent (n2);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = 0.5f;
+			dim1 = Dim.Percent (n1);
+			dim2 = Dim.Percent (n2);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = 100f;
+			dim1 = Dim.Percent (n1);
+			dim2 = Dim.Percent (n2);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = 0.3f;
+			dim1 = Dim.Percent (n1, true);
+			dim2 = Dim.Percent (n2, true);
+			Assert.Equal (dim1, dim2);
+
+			n1 = n2 = 0.3f;
+			dim1 = Dim.Percent (n1);
+			dim2 = Dim.Percent (n2, true);
+			Assert.NotEqual (dim1, dim2);
+
+			n1 = 0;
+			n2 = 1;
+			dim1 = Dim.Percent (n1);
+			dim2 = Dim.Percent (n2);
+			Assert.NotEqual (dim1, dim2);
+
+			n1 = 0.5f;
+			n2 = 1.5f;
+			dim1 = Dim.Percent (n1);
+			dim2 = Dim.Percent (n2);
+			Assert.NotEqual (dim1, dim2);
+		}
 
 
 		[Fact]
 		[Fact]
-		public void Percent_ThrowsOnIvalid()
+		public void Percent_ThrowsOnIvalid ()
 		{
 		{
 			var dim = Dim.Percent (0);
 			var dim = Dim.Percent (0);
 			Assert.Throws<ArgumentException> (() => dim = Dim.Percent (-1));
 			Assert.Throws<ArgumentException> (() => dim = Dim.Percent (-1));

+ 78 - 0
UnitTests/PointTests.cs

@@ -0,0 +1,78 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui {
+	public class PointTests {
+		[Fact]
+		public void Point_New ()
+		{
+			var point = new Point ();
+			Assert.True (point.IsEmpty);
+
+			point = new Point (new Size ());
+			Assert.True (point.IsEmpty);
+
+			point = new Point (1, 2);
+			Assert.False (point.IsEmpty);
+
+			point = new Point (-1, -2);
+			Assert.False (point.IsEmpty);
+		}
+
+		[Fact]
+		public void Point__SetsValue ()
+		{
+			var point = new Point () {
+				X = 0,
+				Y = 0
+			};
+			Assert.True (point.IsEmpty);
+
+			point = new Point () {
+				X = 1,
+				Y = 2
+			};
+			Assert.False (point.IsEmpty);
+
+			point = new Point () {
+				X = -1,
+				Y = -2
+			};
+			Assert.False (point.IsEmpty);
+		}
+
+		[Fact]
+		public void Point_Equals ()
+		{
+			var point1 = new Point ();
+			var point2 = new Point ();
+			Assert.Equal (point1, point2);
+
+			point1 = new Point (1, 2);
+			point2 = new Point (1, 2);
+			Assert.Equal (point1, point2);
+
+			point1 = new Point (1, 2);
+			point2 = new Point (0, 2);
+			Assert.NotEqual (point1, point2);
+
+			point1 = new Point (1, 2);
+			point2 = new Point (0, 3);
+			Assert.NotEqual (point1, point2);
+		}
+
+		[Fact]
+		public void Point_Size ()
+		{
+			var point = new Point (1, 2);
+			var size = (Size)point;
+			Assert.False (size.IsEmpty);
+
+			point = new Point (-1, 2);
+			Action action = () => size = (Size)point;
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+		}
+	}
+}

+ 232 - 51
UnitTests/PosTests.cs

@@ -1,8 +1,10 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
+using System.Data;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
 using Terminal.Gui;
 using Terminal.Gui;
 using Xunit;
 using Xunit;
 
 
@@ -46,6 +48,14 @@ namespace Terminal.Gui {
 			Assert.NotEqual (pos1, pos2);
 			Assert.NotEqual (pos1, pos2);
 		}
 		}
 
 
+		[Fact]
+		public void AnchorEnd_Negative_Throws ()
+		{
+			Pos pos;
+			var n = -1;
+			Assert.Throws<ArgumentException> (() => pos = Pos.AnchorEnd (n));
+		}
+
 		[Fact]
 		[Fact]
 		public void At_SetsValue ()
 		public void At_SetsValue ()
 		{
 		{
@@ -55,7 +65,8 @@ namespace Terminal.Gui {
 			pos = Pos.At (5);
 			pos = Pos.At (5);
 			Assert.Equal ("Pos.Absolute(5)", pos.ToString ());
 			Assert.Equal ("Pos.Absolute(5)", pos.ToString ());
 
 
-			//Assert.Throws<ArgumentException> (() => pos = Pos.At (-1));
+			pos = Pos.At (-1);
+			Assert.Equal ("Pos.Absolute(-1)", pos.ToString ());
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -69,79 +80,246 @@ namespace Terminal.Gui {
 			Assert.Equal (pos1, pos2);
 			Assert.Equal (pos1, pos2);
 		}
 		}
 
 
-		[Fact]
-		public void Left_SetsValue ()
+		[Fact] 
+		public void SetSide_Null_Throws ()
 		{
 		{
 			var pos = Pos.Left (null);
 			var pos = Pos.Left (null);
 			Assert.Throws<NullReferenceException> (() => pos.ToString ());
 			Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
 
-			var testVal = Rect.Empty;
-			pos = Pos.Left (new View ());
-			Assert.Equal ($"Pos.View(side=x, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			pos = Pos.X (null);
+			Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
 
-			pos = Pos.Left (new View (testVal));
-			Assert.Equal ($"Pos.View(side=x, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			pos = Pos.Top (null);
+			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+
+			pos = Pos.Y(null);
+			Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
 
-			testVal = new Rect (1, 2, 3, 4);
-			pos = Pos.Left (new View (testVal));
-			Assert.Equal ($"Pos.View(side=x, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			pos = Pos.Bottom (null);
+			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+
+			pos = Pos.Right (null);
+			Assert.Throws<NullReferenceException> (() => pos.ToString ());
 		}
 		}
 
 
 		// TODO: Test Left, Top, Right bottom Equal
 		// TODO: Test Left, Top, Right bottom Equal
 
 
+		/// <summary>
+		/// Tests Pos.Left, Pos.X, Pos.Top, Pos.Y, Pos.Right, and Pos.Bottom set operations
+		/// </summary>
 		[Fact]
 		[Fact]
-		public void Top_SetsValue ()
+		public void PosSide_SetsValue ()
 		{
 		{
-			var pos = Pos.Top (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
-
-			var testVal = Rect.Empty;
+			string side; // used in format string
+			var testRect = Rect.Empty;
+			var testInt = 0;
+			Pos pos; 
+
+			// Pos.Left
+			side = "x";
+			testInt = 0;
+			testRect = Rect.Empty;
+			pos = Pos.Left (new View ());
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			pos = Pos.Left (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testRect = new Rect (1, 2, 3, 4);
+			pos = Pos.Left (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.Left(win) + 0
+			pos = Pos.Left (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = 1;
+			// Pos.Left(win) +1
+			pos = Pos.Left (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = -1;
+			// Pos.Left(win) -1
+			pos = Pos.Left (new View (testRect)) - testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.X
+			side = "x";
+			testInt = 0;
+			testRect = Rect.Empty;
+			pos = Pos.X (new View ());
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			pos = Pos.X (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testRect = new Rect (1, 2, 3, 4);
+			pos = Pos.X (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.X(win) + 0
+			pos = Pos.X (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = 1;
+			// Pos.X(win) +1
+			pos = Pos.X (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = -1;
+			// Pos.X(win) -1
+			pos = Pos.X (new View (testRect)) - testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.Top
+			side = "y";
+			testInt = 0;
+			testRect = Rect.Empty;
 			pos = Pos.Top (new View ());
 			pos = Pos.Top (new View ());
-			Assert.Equal ($"Pos.View(side=y, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			pos = Pos.Top (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testRect = new Rect (1, 2, 3, 4);
+			pos = Pos.Top (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.Top(win) + 0
+			pos = Pos.Top (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = 1;
+			// Pos.Top(win) +1
+			pos = Pos.Top (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = -1;
+			// Pos.Top(win) -1
+			pos = Pos.Top (new View (testRect)) - testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.Y
+			side = "y";
+			testInt = 0;
+			testRect = Rect.Empty;
+			pos = Pos.Y (new View ());
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			pos = Pos.Y (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testRect = new Rect (1, 2, 3, 4);
+			pos = Pos.Y (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.Y(win) + 0
+			pos = Pos.Y (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = 1;
+			// Pos.Y(win) +1
+			pos = Pos.Y (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			testInt = -1;
+			// Pos.Y(win) -1
+			pos = Pos.Y (new View (testRect)) - testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
+
+			// Pos.Bottom
+			side = "bottom";
+			testRect = Rect.Empty;
+			testInt = 0;
+			pos = Pos.Bottom (new View ());
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
 
 
-			testVal = new Rect (1, 2, 3, 4);
-			pos = Pos.Top (new View (testVal));
-			Assert.Equal ($"Pos.View(side=y, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
-		}
+			pos = Pos.Bottom (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
 
 
-		[Fact]
-		public void Right_SetsValue ()
-		{
-			var pos = Pos.Right (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+			testRect = new Rect (1, 2, 3, 4);
+			pos = Pos.Bottom (new View (testRect));
+			Assert.Equal ($"Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}})){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
 
 
-			var testVal = Rect.Empty;
-			pos = Pos.Right (new View ());
-			Assert.Equal ($"Pos.View(side=right, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			// Pos.Bottom(win) + 0
+			pos = Pos.Bottom (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
 
 
-			testVal = Rect.Empty;
-			pos = Pos.Right (new View (testVal));
-			Assert.Equal ($"Pos.View(side=right, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			testInt = 1;
+			// Pos.Bottom(win) +1
+			pos = Pos.Bottom (new View (testRect)) + testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
 
 
-			testVal = new Rect (1, 2, 3, 4);
-			pos = Pos.Right (new View (testVal));
-			Assert.Equal ($"Pos.View(side=right, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			testInt = -1;
+			// Pos.Bottom(win) -1
+			pos = Pos.Bottom (new View (testRect)) - testInt;
+			Assert.Equal ($"Pos.Combine(Pos.Combine(Pos.View(side={side}, target=View()({{X={testRect.X},Y={testRect.Y},Width={testRect.Width},Height={testRect.Height}}}))+Pos.Absolute(0)){(testInt < 0 ? '-' : '+')}Pos.Absolute({testInt}))", pos.ToString ());
 		}
 		}
 
 
+		// See: https://github.com/migueldeicaza/gui.cs/issues/504
 		[Fact]
 		[Fact]
-		public void Bottom_SetsValue ()
+		public void LeftTopBottomRight_Win_ShouldNotThrow ()
 		{
 		{
-			var pos = Pos.Bottom (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
-
-			var testVal = Rect.Empty;
-			pos = Pos.Bottom (new View ());
-			Assert.Equal ($"Pos.View(side=bottom, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
-
-			testVal = Rect.Empty;
-			pos = Pos.Bottom (new View (testVal));
-			Assert.Equal ($"Pos.View(side=bottom, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
+			// Setup Fake driver
+			(Window win, Button button) setup ()
+			{
+				Application.Init (new FakeDriver (), new NetMainLoop (() => FakeConsole.ReadKey (true)));
+				Application.Iteration = () => {
+					Application.RequestStop ();
+				};
+				var win = new Window ("window") {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (),
+					Height = Dim.Fill (),
+				};
+				Application.Top.Add (win);
+
+				var button = new Button ("button") {
+					X = Pos.Center (),
+				};
+				win.Add (button);
+
+				return (win, button);
+			}
+
+			void cleanup ()
+			{
+				// Cleanup
+				Application.Shutdown ();
+			}
+
+			// Test cases:
+			var app = setup ();
+			app.button.Y = Pos.Left (app.win);
+			Application.Run ();
+			cleanup ();
+
+			app = setup ();
+			app.button.Y = Pos.X (app.win);
+			Application.Run ();
+			cleanup ();
+
+			app = setup ();
+			app.button.Y = Pos.Top (app.win);
+			Application.Run ();
+			cleanup ();
+
+			app = setup ();
+			app.button.Y = Pos.Y (app.win);
+			Application.Run ();
+			cleanup ();
+
+			app = setup ();
+			app.button.Y = Pos.Bottom (app.win);
+			Application.Run ();
+			cleanup ();
+
+			app = setup ();
+			app.button.Y = Pos.Right (app.win);
+			Application.Run ();
+			cleanup ();
 
 
-			testVal = new Rect (1, 2, 3, 4);
-			pos = Pos.Bottom (new View (testVal));
-			Assert.Equal ($"Pos.View(side=bottom, target=View()({{X={testVal.X},Y={testVal.Y},Width={testVal.Width},Height={testVal.Height}}}))", pos.ToString ());
-
-			//Assert.Throws<ArgumentException> (() => pos = Pos.Bottom (new View (new Rect (0, 0, -3, -4))));
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -186,6 +364,9 @@ namespace Terminal.Gui {
 			Assert.Throws<ArgumentException> (() => pos = Pos.Percent (1000001));
 			Assert.Throws<ArgumentException> (() => pos = Pos.Percent (1000001));
 		}
 		}
 
 
+		// TODO: Test PosCombine
+
+
 		// TODO: Test operators
 		// TODO: Test operators
 	}
 	}
 }
 }

+ 114 - 0
UnitTests/RectTests.cs

@@ -0,0 +1,114 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui {
+	public class RectTests {
+		[Fact]
+		public void Rect_New ()
+		{
+			var rect = new Rect ();
+			Assert.True (rect.IsEmpty);
+
+			rect = new Rect (new Point (), new Size ());
+			Assert.True (rect.IsEmpty);
+
+			rect = new Rect (1, 2, 3, 4);
+			Assert.False (rect.IsEmpty);
+
+			rect = new Rect (-1, -2, 3, 4);
+			Assert.False (rect.IsEmpty);
+
+			Action action = () => new Rect (1, 2, -3, 4);
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+			action = () => new Rect (1, 2, 3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Height must be greater or equal to 0.", ex.Message);
+
+			action = () => new Rect (1, 2, -3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+		}
+
+		[Fact]
+		public void Rect__SetsValue ()
+		{
+			var rect = new Rect () {
+				X = 0,
+				Y = 0
+			};
+			Assert.True (rect.IsEmpty);
+
+			rect = new Rect () {
+				X = -1,
+				Y = -2
+			};
+			Assert.False (rect.IsEmpty);
+
+			rect = new Rect () {
+				Width = 3,
+				Height = 4
+			};
+			Assert.False (rect.IsEmpty);
+
+			rect = new Rect () {
+				X = -1,
+				Y = -2,
+				Width = 3,
+				Height = 4
+			};
+			Assert.False (rect.IsEmpty);
+
+			Action action = () => {
+				rect = new Rect () {
+					X = -1,
+					Y = -2,
+					Width = -3,
+					Height = 4
+				};
+			};
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				rect = new Rect () {
+					X = -1,
+					Y = -2,
+					Width = 3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Height must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				rect = new Rect () {
+					X = -1,
+					Y = -2,
+					Width = -3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+		}
+
+		[Fact]
+		public void Rect_Equals ()
+		{
+			var rect1 = new Rect ();
+			var rect2 = new Rect ();
+			Assert.Equal (rect1, rect2);
+
+			rect1 = new Rect (1, 2, 3, 4);
+			rect2 = new Rect (1, 2, 3, 4);
+			Assert.Equal (rect1, rect2);
+
+			rect1 = new Rect (1, 2, 3, 4);
+			rect2 = new Rect (-1, 2, 3, 4);
+			Assert.NotEqual (rect1, rect2);
+		}
+	}
+}

+ 91 - 0
UnitTests/SizeTests.cs

@@ -0,0 +1,91 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui {
+	public class SizeTests {
+		[Fact]
+		public void Size_New ()
+		{
+			var size = new Size ();
+			Assert.True (size.IsEmpty);
+
+			size = new Size (new Point ());
+			Assert.True (size.IsEmpty);
+
+			size = new Size (3, 4);
+			Assert.False (size.IsEmpty);
+
+			Action action = () => new Size (-3, 4);
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+			action = () => new Size (3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+			action = () => new Size (-3, -4);
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Either Width and Height must be greater or equal to 0.", ex.Message);
+
+		}
+
+		[Fact]
+		public void Size__SetsValue ()
+		{
+			var size = new Size () {
+				Width = 0,
+				Height = 0
+			};
+			Assert.True (size.IsEmpty);
+
+			size = new Size () {
+				Width = 3,
+				Height = 4
+			};
+			Assert.False (size.IsEmpty);
+
+			Action action = () => {
+				size = new Size () {
+					Width = -3,
+					Height = 4
+				};
+			};
+			var ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				size = new Size () {
+					Width = 3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Height must be greater or equal to 0.", ex.Message);
+
+			action = () => {
+				size = new Size () {
+					Width = -3,
+					Height = -4
+				};
+			};
+			ex = Assert.Throws<ArgumentException> (action);
+			Assert.Equal ("Width must be greater or equal to 0.", ex.Message);
+		}
+
+		[Fact]
+		public void Size_Equals ()
+		{
+			var size1 = new Size ();
+			var size2 = new Size ();
+			Assert.Equal (size1, size2);
+
+			size1 = new Size (3, 4);
+			size2 = new Size (3, 4);
+			Assert.Equal (size1, size2);
+
+			size1 = new Size (3, 4);
+			size2 = new Size (4, 4);
+			Assert.NotEqual (size1, size2);
+		}
+	}
+}