浏览代码

Merge branch 'master' of tig:migueldeicaza/gui.cs

Charlie Kindel 5 年之前
父节点
当前提交
dd6d54e919

+ 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!")

+ 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;

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

@@ -1182,6 +1182,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>

+ 10 - 10
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>
@@ -546,7 +546,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,7 +556,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.Height;
 				case 0: return Target.Frame.Height;
 				case 1: return Target.Frame.Width;
 				case 1: return Target.Frame.Width;
 				default:
 				default:

+ 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 ();

+ 19 - 3
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>
@@ -36,6 +37,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),
@@ -57,7 +61,7 @@ namespace UICatalog {
 			jumpList.Y = Pos.Bottom (label);
 			jumpList.Y = Pos.Bottom (label);
 			jumpList.Width = Dim.Fill ();
 			jumpList.Width = Dim.Fill ();
 			jumpList.SelectedItemChanged = (args) => {
 			jumpList.SelectedItemChanged = (args) => {
-				charMap.Start = radioItems[args.SelectedItem].start;
+				charMap.Start = radioItems [args.SelectedItem].start;
 			};
 			};
 
 
 			Win.Add (jumpList);
 			Win.Add (jumpList);
@@ -105,6 +109,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} ");
@@ -115,9 +128,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;
 					}
 					}
 				}
 				}
 			}
 			}

+ 111 - 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,46 @@ 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);
+			// BUGBUG: Dim.Width should support Equals() and this should change to Euqal.
+			Assert.NotEqual (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);
+			// BUGBUG: Dim.Width should support Equals() and this should change to Euqal.
+			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);
+			// BUGBUG: Dim.Width should support Equals() and this should change to Euqal.
+			Assert.NotEqual (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 +139,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 +152,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 +166,54 @@ 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({f / 100:0.###})", 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({f / 100:0.###})", 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({f / 100:0.###})", 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 = 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));

+ 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
 	}
 	}
 }
 }