Ver Fonte

merge with master

Charlie Kindel há 5 anos atrás
pai
commit
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 [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
+				new MenuItem ("_Close", "", () => { if (Quit ()) {Application.RequestStop (); } }),
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", null),
@@ -273,7 +273,9 @@ static class Demo {
 			text.Text = System.IO.File.ReadAllText (fname);
 		win.Add (text);
 
-		Application.Run (ntop);
+		Application.Run (ntop, false);
+		Application.Top.RemoveAll ();
+		Main ();
 	}
 
 	static bool Quit ()
@@ -576,7 +578,7 @@ static class Demo {
 
 		menu = new MenuBar (new MenuBarItem [] {
 			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 ("_Open", "", Open),
 				new MenuItem ("_Hex", "", () => ShowHex (top)),

+ 2 - 3
FSharpExample/Program.fs

@@ -140,7 +140,7 @@ type Demo() = class end
         container.Add (login, loginText, password, passText,
             new FrameView (new Rect (3, 10, 25, 6), ustr "Options",
                 [|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),
                     [|"First row";
@@ -434,8 +434,7 @@ type Demo() = class end
             new StatusItem(Key.F2, ustr "~F2~ Load", Action(Load));
             new StatusItem(Key.F3, ustr "~F3~ Save", Action(Save));
             new StatusItem(Key.ControlX, ustr "~^X~ Quit", fun () -> if (Quit ()) then top.Running <- false)
-            |],
-            Parent = null
+            |]
             )
         win.Add (drag, dragText)
         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)
+![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)
 [![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)

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

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

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

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

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

@@ -70,6 +70,7 @@ namespace Terminal.Gui {
 
 		public override void AddRune (Rune rune)
 		{
+			rune = MakePrintable (rune);
 			if (Clip.Contains (ccol, crow)) {
 				if (needMove) {
 					//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)]
 		public static extern bool ReadConsoleInput (
 			IntPtr hConsoleInput,
-			[Out] InputRecord [] lpBuffer,
+			IntPtr lpBuffer,
 			uint nLength,
 			out uint lpNumberOfEventsRead);
 
@@ -420,6 +420,24 @@ namespace Terminal.Gui {
 				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
 		[StructLayout (LayoutKind.Sequential)]
@@ -577,13 +595,7 @@ namespace Terminal.Gui {
 				waitForProbe.Wait ();
 				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 ();
 			}
@@ -1182,6 +1194,7 @@ namespace Terminal.Gui {
 
 		public override void AddRune (Rune rune)
 		{
+			rune = MakePrintable (rune);
 			var position = crow * Cols + ccol;
 
 			if (Clip.Contains (ccol, crow)) {

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

@@ -1,4 +1,4 @@
-//
+//
 // ConsoleDriver.cs: Definition for the Console Driver API
 //
 // Authors:
@@ -558,6 +558,28 @@ namespace Terminal.Gui {
 		/// <param name="rune">Rune to add.</param>
 		public abstract void AddRune (Rune rune);
 		/// <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
 		/// </summary>
 		/// <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)
 			{
-				switch(side) {
+				switch (side) {
 				case 0: return Target.Frame.X;
 				case 1: return Target.Frame.Y;
 				case 2: return Target.Frame.Right;
@@ -292,7 +292,7 @@ namespace Terminal.Gui {
 			public override string ToString ()
 			{
 				string tside;
-				switch(side) {
+				switch (side) {
 				case 0: tside = "x"; break;
 				case 1: tside = "y"; break;
 				case 2: tside = "right"; break;
@@ -308,42 +308,42 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <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>
 		/// Returns a <see cref="Pos"/> object tracks the Left (X) position of the specified <see cref="View"/>.
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <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>
 		/// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <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>
 		/// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <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>
 		/// Returns a <see cref="Pos"/> object tracks the Right (X+Width) coordinate of the specified <see cref="View"/>.
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <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>
 		/// Returns a <see cref="Pos"/> object tracks the Bottom (Y+Height) coordinate of the specified <see cref="View"/> 
 		/// </summary>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <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>
@@ -366,12 +366,14 @@ namespace Terminal.Gui {
 			return 0;
 		}
 
-		class DimFactor : Dim {
+		internal class DimFactor : Dim {
 			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)
@@ -379,14 +381,19 @@ namespace Terminal.Gui {
 				return (int)(width * factor);
 			}
 
+			public bool IsFromRemaining ()
+			{
+				return remaining;
+			}
+
 			public override string ToString ()
 			{
-				return $"Dim.Factor({factor})";
+				return $"Dim.Factor(factor={factor}, remaining={remaining})";
 			}
 
 			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>
 		/// <returns>The percent <see cref="Dim"/> object.</returns>
 		/// <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>
 		/// 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.
@@ -407,12 +415,12 @@ namespace Terminal.Gui {
 		/// };
 		/// </code>
 		/// </example>
-		public static Dim Percent (float n)
+		public static Dim Percent (float n, bool r = false)
 		{
 			if (n < 0 || n > 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 {
@@ -546,7 +554,7 @@ namespace Terminal.Gui {
 			public override string ToString ()
 			{
 				string tside;
-				switch(side) {
+				switch (side) {
 				case 0: tside = "Height"; break;
 				case 1: tside = "Width"; break;
 				default: tside = "unknown"; break;
@@ -556,13 +564,18 @@ namespace Terminal.Gui {
 
 			internal override int Anchor (int width)
 			{
-				switch(side) {
+				switch (side) {
 				case 0: return Target.Frame.Height;
 				case 1: return Target.Frame.Width;
 				default:
 					return 0;
 				}
 			}
+
+			public override int GetHashCode () => Target.GetHashCode ();
+
+			public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
+
 		}
 		/// <summary>
 		/// 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>
 		/// <param name="view">The view that will be tracked.</param>
 		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 ()
 		{
-			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).
 					Clear (bounds);
 					Driver.SetAttribute (Colors.Base.Normal);
+					PositionToplevels ();
 				}
 				foreach (var view in Subviews) {
 					if (view.Frame.IntersectsWith (bounds)) {

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

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

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

@@ -17,6 +17,9 @@ namespace Terminal.Gui
 	/// </summary>
 	public struct Rect
 	{
+		int width;
+		int height;
+
 		/// <summary>
 		/// Gets or sets the x-coordinate of the upper-left corner of this Rectangle structure.
 		/// </summary>
@@ -29,12 +32,26 @@ namespace Terminal.Gui
 		/// <summary>
 		/// Gets or sets the width of this Rect structure.
 		/// </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>
 		/// Gets or sets the height of this Rectangle structure.
 		/// </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>
 		///	Empty Shared Field
@@ -209,8 +226,10 @@ namespace Terminal.Gui
 		{
 			X = location.X;
 			Y = location.Y;
-			Width = size.Width;
-			Height = size.Height;
+			width = size.Width;
+			height = size.Height;
+			Width = width;
+			Height = height;
 		}
 
 		/// <summary>
@@ -224,10 +243,12 @@ namespace Terminal.Gui
 
 		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)
 		{
+			if (width < 0 || height < 0)
+				throw new ArgumentException ("Either Width and Height must be greater or equal to 0.");
+
 			this.width = width;
 			this.height = height;
 		}
@@ -152,6 +155,8 @@ namespace Terminal.Gui {
 				return width;
 			}
 			set {
+				if (value < 0)
+					throw new ArgumentException ("Width must be greater or equal to 0.");
 				width = value;
 			}
 		}
@@ -169,6 +174,8 @@ namespace Terminal.Gui {
 				return height;
 			}
 			set {
+				if (value < 0)
+					throw new ArgumentException ("Height must be greater or equal to 0.");
 				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
 		///   text length. The height will always be 1.
 		/// </remarks>
-		public Button () : this (string.Empty) { }
+		public Button () : this (text: string.Empty, is_default: false) { }
 
 		/// <summary>
 		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
@@ -78,11 +78,7 @@ namespace Terminal.Gui {
 		/// </param>
 		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>
@@ -114,9 +110,26 @@ namespace Terminal.Gui {
 		public Button (int x, int y, ustring text, bool is_default)
 		    : 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;
 			Text = text ?? string.Empty;
 			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)
@@ -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 ()
 		{
 			if (IsDefault)
@@ -166,14 +174,6 @@ namespace Terminal.Gui {
 			else
 				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);
 
 			SetNeedsDisplay ();

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

@@ -123,8 +123,6 @@ namespace Terminal.Gui {
 
 		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;
 			if (slen > width) {
 				var uints = str.ToRunes (width);
@@ -166,12 +164,22 @@ namespace Terminal.Gui {
 			Recalc (text, lines, Frame.Width, textAlignment, Bounds.Height > 1);
 		}
 
-		static ustring ReplaceNonPrintables (ustring str)
+		static ustring StripCRLF (ustring str)
 		{
 			var runes = new List<Rune> ();
 			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 {
 					runes.Add (r);
 				}
@@ -184,7 +192,7 @@ namespace Terminal.Gui {
 			int start = 0, end;
 			var lines = new List<ustring> ();
 
-			text = ReplaceNonPrintables (text);
+			text = StripCRLF (text);
 
 			while ((end = start + margin) < text.Length) {
 				while (text [end] != ' ' && end > start)
@@ -208,7 +216,7 @@ namespace Terminal.Gui {
 			lineResult.Clear ();
 
 			if (wordWrap == false) {
-				textStr = ReplaceNonPrintables (textStr);
+				textStr = ReplaceCRLFWithSpace (textStr);
 				lineResult.Add (ClipAndJustify (textStr, width, talign));
 				return;
 			}

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

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

+ 8 - 4
UICatalog/Scenarios/AllViewsTester.cs

@@ -317,7 +317,7 @@ namespace UICatalog {
 
 		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 ()
@@ -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 (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") });
 			}
 
+			// 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
 			_computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed;
 

+ 2 - 2
UICatalog/Scenarios/Buttons.cs

@@ -210,7 +210,7 @@ namespace UICatalog {
 			var moveHotKeyBtn = new Button (mhkb) {
 				X = 2,
 				Y = Pos.Bottom (radioGroup) + 1,
-				Width = mhkb.Length + 10,
+				Width = Dim.Width (computedFrame) - 2,
 				ColorScheme = Colors.TopLevel,
 			};
 			moveHotKeyBtn.Clicked = () => {
@@ -222,7 +222,7 @@ namespace UICatalog {
 			var moveUnicodeHotKeyBtn = new Button (muhkb) {
 				X = Pos.Left (absoluteFrame) + 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,
 			};
 			moveUnicodeHotKeyBtn.Clicked = () => {

+ 18 - 2
UICatalog/Scenarios/CharacterMap.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using Terminal.Gui;
+using Rune = System.Rune;
 
 namespace UICatalog {
 	/// <summary>
@@ -37,6 +38,9 @@ namespace UICatalog {
 			}
 
 			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("Letterlike Symbols", 0x2100, 0x214F),
 				CreateRadio("Arrows", 0x2190, 0x21ff),
@@ -110,6 +114,15 @@ namespace UICatalog {
 #if true
 		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++) {
 				Move (viewport.X + RowHeaderWidth + (header * 2), 0);
 				Driver.AddStr ($" {header:x} ");
@@ -120,9 +133,12 @@ namespace UICatalog {
 					var rowLabel = $"U+{val / 16:x4}x";
 					Move (0, y + 1);
 					Driver.AddStr (rowLabel);
+					var prevColWasWide = false;
 					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")]
 	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 {
+			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)
 			{
 			}
@@ -112,7 +112,7 @@ namespace UICatalog {
 			var keyStrokeListView = new ListView (keyStrokelist) {
 				X = 0,
 				Y = Pos.Top (keyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (30),
 				Height = Dim.Fill (),
 			};
 			keyStrokeListView.ColorScheme = Colors.TopLevel;
@@ -127,10 +127,10 @@ namespace UICatalog {
 
 			maxLogEntry = $"{fakeKeyPress}".Length;
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processKeyListView = new ListView (_processKeyList) {
+			var processKeyListView = new ListView (((TestWindow)Win)._processKeyList) {
 				X = Pos.Left (processKeyLogLabel),
 				Y = Pos.Top (processKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent(30),
 				Height = Dim.Fill (),
 			};
 			processKeyListView.ColorScheme = Colors.TopLevel;
@@ -145,10 +145,10 @@ namespace UICatalog {
 			Win.Add (processHotKeyLogLabel);
 
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processHotKeyListView = new ListView (_processHotKeyList) {
+			var processHotKeyListView = new ListView (((TestWindow)Win)._processHotKeyList) {
 				X = Pos.Left (processHotKeyLogLabel),
 				Y = Pos.Top (processHotKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (20),
 				Height = Dim.Fill (),
 			};
 			processHotKeyListView.ColorScheme = Colors.TopLevel;
@@ -163,10 +163,10 @@ namespace UICatalog {
 			Win.Add (processColdKeyLogLabel);
 
 			yOffset = (Top == Application.Top ? 1 : 6);
-			var processColdKeyListView = new ListView (_processColdKeyList) {
+			var processColdKeyListView = new ListView (((TestWindow)Win)._processColdKeyList) {
 				X = Pos.Left (processColdKeyLogLabel),
 				Y = Pos.Top (processColdKeyLogLabel) + yOffset,
-				Width = maxLogEntry,
+				Width = Dim.Percent (20),
 				Height = Dim.Fill (),
 			};
 

+ 2 - 2
UICatalog/Scenarios/ListsAndCombos.cs

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

+ 122 - 9
UnitTests/DimTests.cs

@@ -27,9 +27,37 @@ namespace Terminal.Gui {
 			int testVal = 5;
 			dim = Dim.Sized (testVal);
 			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]
 		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 ());
 		}
 
-		// 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]
 		public void Height_SetsValue ()
@@ -72,7 +140,7 @@ namespace Terminal.Gui {
 		{
 			var testMargin = 0;
 			var dim = Dim.Fill ();
-			Assert.Equal ($"Dim.Fill(margin={testMargin})", dim.ToString());
+			Assert.Equal ($"Dim.Fill(margin={testMargin})", dim.ToString ());
 
 			testMargin = 0;
 			dim = Dim.Fill (testMargin);
@@ -85,7 +153,7 @@ namespace Terminal.Gui {
 
 
 		[Fact]
-		public void Fill_Equal()
+		public void Fill_Equal ()
 		{
 			var margin1 = 0;
 			var margin2 = 0;
@@ -99,19 +167,64 @@ namespace Terminal.Gui {
 		{
 			float f = 0;
 			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;
 			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;
 			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]
-		public void Percent_ThrowsOnIvalid()
+		public void Percent_ThrowsOnIvalid ()
 		{
 			var dim = Dim.Percent (0);
 			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.Collections.Generic;
 using System.ComponentModel;
+using System.Data;
 using System.IO;
 using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
 using Terminal.Gui;
 using Xunit;
 
@@ -46,6 +48,14 @@ namespace Terminal.Gui {
 			Assert.NotEqual (pos1, pos2);
 		}
 
+		[Fact]
+		public void AnchorEnd_Negative_Throws ()
+		{
+			Pos pos;
+			var n = -1;
+			Assert.Throws<ArgumentException> (() => pos = Pos.AnchorEnd (n));
+		}
+
 		[Fact]
 		public void At_SetsValue ()
 		{
@@ -55,7 +65,8 @@ namespace Terminal.Gui {
 			pos = Pos.At (5);
 			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]
@@ -69,79 +80,246 @@ namespace Terminal.Gui {
 			Assert.Equal (pos1, pos2);
 		}
 
-		[Fact]
-		public void Left_SetsValue ()
+		[Fact] 
+		public void SetSide_Null_Throws ()
 		{
 			var pos = Pos.Left (null);
 			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
 
+		/// <summary>
+		/// Tests Pos.Left, Pos.X, Pos.Top, Pos.Y, Pos.Right, and Pos.Bottom set operations
+		/// </summary>
 		[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 ());
-			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]
-		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]
@@ -186,6 +364,9 @@ namespace Terminal.Gui {
 			Assert.Throws<ArgumentException> (() => pos = Pos.Percent (1000001));
 		}
 
+		// TODO: Test PosCombine
+
+
 		// 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);
+		}
+	}
+}