diff --git a/FSharpExample/Program.fs b/FSharpExample/Program.fs index f6f4d2462..079a5dd9a 100644 --- a/FSharpExample/Program.fs +++ b/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!") diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 98faa32ac..4ba273444 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/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) diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 85faaf25f..11cd1c912 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -90,6 +90,7 @@ namespace Terminal.Gui { /// public override void AddRune (Rune rune) { + rune = MakePrintable (rune); if (Clip.Contains (ccol, crow)) { if (needMove) { //MockConsole.CursorLeft = ccol; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index c7317d30d..1fe8f01c7 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/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; diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 1cb899067..7e5dcfdf6 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1182,6 +1182,7 @@ namespace Terminal.Gui { public override void AddRune (Rune rune) { + rune = MakePrintable (rune); var position = crow * Cols + ccol; if (Clip.Contains (ccol, crow)) { diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index 77f61f1e2..dc709cd70 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/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 { /// Rune to add. public abstract void AddRune (Rune rune); /// + /// Ensures a Rune is not a control character and can be displayed by translating characters below 0x20 + /// to equivalent, printable, Unicode chars. + /// + /// Rune to translate + /// + 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; + } + } + /// /// Adds the specified /// /// String. diff --git a/Terminal.Gui/Core/PosDim.cs b/Terminal.Gui/Core/PosDim.cs index a3ea2bd18..8370ab3d7 100644 --- a/Terminal.Gui/Core/PosDim.cs +++ b/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 { /// /// The that depends on the other view. /// The that will be tracked. - 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)); /// /// Returns a object tracks the Left (X) position of the specified . /// /// The that depends on the other view. /// The that will be tracked. - 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)); /// /// Returns a object tracks the Top (Y) position of the specified . /// /// The that depends on the other view. /// The that will be tracked. - 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)); /// /// Returns a object tracks the Top (Y) position of the specified . /// /// The that depends on the other view. /// The that will be tracked. - 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)); /// /// Returns a object tracks the Right (X+Width) coordinate of the specified . /// /// The that depends on the other view. /// The that will be tracked. - 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)); /// /// Returns a object tracks the Bottom (Y+Height) coordinate of the specified /// /// The that depends on the other view. /// The that will be tracked. - 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)); } /// @@ -546,7 +546,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,7 +556,7 @@ 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: diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 36b848544..c542d3dee 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -62,7 +62,7 @@ namespace Terminal.Gui { /// The width of the is computed based on the /// text length. The height will always be 1. /// - public Button () : this (string.Empty) { } + public Button () : this (text: string.Empty, is_default: false) { } /// /// Initializes a new instance of using layout. @@ -78,11 +78,7 @@ namespace Terminal.Gui { /// 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); } /// @@ -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 (); diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 60d30ee83..a875c4658 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/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 { /// @@ -36,6 +37,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), @@ -57,7 +61,7 @@ namespace UICatalog { jumpList.Y = Pos.Bottom (label); jumpList.Width = Dim.Fill (); jumpList.SelectedItemChanged = (args) => { - charMap.Start = radioItems[args.SelectedItem].start; + charMap.Start = radioItems [args.SelectedItem].start; }; Win.Add (jumpList); @@ -105,6 +109,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} "); @@ -115,9 +128,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; } } } diff --git a/UnitTests/DimTests.cs b/UnitTests/DimTests.cs index 2b99510db..d77539cd6 100644 --- a/UnitTests/DimTests.cs +++ b/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,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 ()); } - // 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] public void Height_SetsValue () @@ -72,7 +139,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 +152,7 @@ namespace Terminal.Gui { [Fact] - public void Fill_Equal() + public void Fill_Equal () { var margin1 = 0; var margin2 = 0; @@ -99,19 +166,54 @@ namespace Terminal.Gui { { float f = 0; 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; dim = Dim.Percent (f); - Assert.Equal ($"Dim.Factor({f/100:0.###})", dim.ToString ()); + Assert.Equal ($"Dim.Factor({f / 100:0.###})", dim.ToString ()); f = 100; 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] - public void Percent_ThrowsOnIvalid() + public void Percent_ThrowsOnIvalid () { var dim = Dim.Percent (0); Assert.Throws (() => dim = Dim.Percent (-1)); diff --git a/UnitTests/PosTests.cs b/UnitTests/PosTests.cs index fec28c758..f91174400 100644 --- a/UnitTests/PosTests.cs +++ b/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 (() => 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 (() => 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 (() => 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 (() => 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 (() => 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.Y(null); + Assert.Throws (() => pos.ToString ()); + + pos = Pos.Bottom (null); + Assert.Throws (() => pos.ToString ()); + + pos = Pos.Right (null); + Assert.Throws (() => pos.ToString ()); } // TODO: Test Left, Top, Right bottom Equal + /// + /// Tests Pos.Left, Pos.X, Pos.Top, Pos.Y, Pos.Right, and Pos.Bottom set operations + /// [Fact] - public void Top_SetsValue () + public void PosSide_SetsValue () { - var pos = Pos.Top (null); - Assert.Throws (() => pos.ToString ()); + string side; // used in format string + var testRect = Rect.Empty; + var testInt = 0; + Pos pos; - var testVal = Rect.Empty; + // 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 ()); - 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.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 ()); - [Fact] - public void Right_SetsValue () - { - var pos = Pos.Right (null); - Assert.Throws (() => 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 ()); - 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.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 ()); - 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.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 ()); - 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.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 ()); - [Fact] - public void Bottom_SetsValue () - { - var pos = Pos.Bottom (null); - Assert.Throws (() => 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 ()); - var testVal = Rect.Empty; + 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.View(side=bottom, 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 ()); - 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 ()); + 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 ()); - 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 ()); + 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 ()); + + // 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 ()); + + 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 ()); + + 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 LeftTopBottomRight_Win_ShouldNotThrow () + { + // 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 (); - //Assert.Throws (() => pos = Pos.Bottom (new View (new Rect (0, 0, -3, -4)))); } [Fact] @@ -186,6 +364,9 @@ namespace Terminal.Gui { Assert.Throws (() => pos = Pos.Percent (1000001)); } + // TODO: Test PosCombine + + // TODO: Test operators } }