diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index d3cc209cc..d15e62390 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -6,6 +6,7 @@ // using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using NStack; @@ -111,11 +112,11 @@ namespace Terminal.Gui { public override void UpdateScreen () => window.redrawwin (); - int currentAttribute; + Attribute currentAttribute; public override void SetAttribute (Attribute c) { - currentAttribute = c.Value; + currentAttribute = c; Curses.attrset (currentAttribute); } @@ -1118,6 +1119,28 @@ namespace Terminal.Gui { } keyHandler (new KeyEvent (k, MapKeyModifiers (k))); } + + public override bool GetColors (int value, out Color foreground, out Color background) + { + bool hasColor = false; + foreground = default; + background = default; + int back = -1; + IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) + .OfType () + .Select (s => (int)s); + if (values.Contains ((value >> 12) & 0xffff)) { + hasColor = true; + back = (value >> 12) & 0xffff; + background = MapCursesColor (back); + } + if (values.Contains ((value - (back << 12)) >> 8)) { + hasColor = true; + foreground = MapCursesColor ((value - (back << 12)) >> 8); + } + return hasColor; + + } } internal static class Platform { diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index ba68cf3ef..6bede9a16 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -266,23 +266,6 @@ namespace Terminal.Gui { FakeConsole.CursorLeft = savedCol; } - public override void UpdateCursor () - { - // - } - - public override void StartReportingMouseMoves () - { - } - - public override void StopReportingMouseMoves () - { - } - - public override void Suspend () - { - } - Attribute currentAttribute; public override void SetAttribute (Attribute c) { @@ -568,7 +551,43 @@ namespace Terminal.Gui { } catch (IndexOutOfRangeException) { } } + public override bool GetColors (int value, out Color foreground, out Color background) + { + bool hasColor = false; + foreground = default; + background = default; + IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) + .OfType () + .Select (s => (int)s); + if (values.Contains (value & 0xffff)) { + hasColor = true; + background = (Color)(ConsoleColor)(value & 0xffff); + } + if (values.Contains ((value >> 16) & 0xffff)) { + hasColor = true; + foreground = (Color)(ConsoleColor)((value >> 16) & 0xffff); + } + return hasColor; + } + #region Unused + public override void UpdateCursor () + { + // + } + + public override void StartReportingMouseMoves () + { + } + + public override void StopReportingMouseMoves () + { + } + + public override void Suspend () + { + } + public override void SetColors (ConsoleColor foreground, ConsoleColor background) { } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 332038a6f..3aa4f76d4 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1497,10 +1497,10 @@ namespace Terminal.Gui { { } - int currentAttribute; + Attribute currentAttribute; public override void SetAttribute (Attribute c) { - currentAttribute = c.Value; + currentAttribute = c; } Key MapKey (ConsoleKeyInfo keyInfo) @@ -1798,23 +1798,6 @@ namespace Terminal.Gui { return currentAttribute; } - #region Unused - public override void SetColors (ConsoleColor foreground, ConsoleColor background) - { - } - - public override void SetColors (short foregroundColorId, short backgroundColorId) - { - } - - public override void CookMouse () - { - } - - public override void UncookMouse () - { - } - /// public override bool GetCursorVisibility (out CursorVisibility visibility) { @@ -1851,6 +1834,42 @@ namespace Terminal.Gui { ProcessInput (input); } catch (OverflowException) { } } + + public override bool GetColors (int value, out Color foreground, out Color background) + { + bool hasColor = false; + foreground = default; + background = default; + IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) + .OfType () + .Select (s => (int)s); + if (values.Contains (value & 0xffff)) { + hasColor = true; + background = (Color)(ConsoleColor)(value & 0xffff); + } + if (values.Contains ((value >> 16) & 0xffff)) { + hasColor = true; + foreground = (Color)(ConsoleColor)((value >> 16) & 0xffff); + } + return hasColor; + } + + #region Unused + public override void SetColors (ConsoleColor foreground, ConsoleColor background) + { + } + + public override void SetColors (short foregroundColorId, short backgroundColorId) + { + } + + public override void CookMouse () + { + } + + public override void UncookMouse () + { + } #endregion // diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 12b01ceae..022bf1e73 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -27,7 +27,9 @@ // using NStack; using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -1387,11 +1389,11 @@ namespace Terminal.Gui { AddRune (rune); } - int currentAttribute; + Attribute currentAttribute; public override void SetAttribute (Attribute c) { - currentAttribute = c.Value; + currentAttribute = c; } Attribute MakeColor (ConsoleColor f, ConsoleColor b) @@ -1492,35 +1494,6 @@ namespace Terminal.Gui { return winConsole.EnsureCursorVisibility (); } - #region Unused - public override void SetColors (ConsoleColor foreground, ConsoleColor background) - { - } - - public override void SetColors (short foregroundColorId, short backgroundColorId) - { - } - - public override void Suspend () - { - } - - public override void StartReportingMouseMoves () - { - } - - public override void StopReportingMouseMoves () - { - } - - public override void UncookMouse () - { - } - - public override void CookMouse () - { - } - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) { WindowsConsole.InputRecord input = new WindowsConsole.InputRecord (); @@ -1573,6 +1546,54 @@ namespace Terminal.Gui { ProcessInput (input); } } + + public override bool GetColors (int value, out Color foreground, out Color background) + { + bool hasColor = false; + foreground = default; + background = default; + IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) + .OfType () + .Select (s => (int)s); + if (values.Contains ((value >> 4) & 0xffff)) { + hasColor = true; + background = (Color)(ConsoleColor)((value >> 4) & 0xffff); + } + if (values.Contains (value - ((int)background << 4))) { + hasColor = true; + foreground = (Color)(ConsoleColor)(value - ((int)background << 4)); + } + return hasColor; + } + + #region Unused + public override void SetColors (ConsoleColor foreground, ConsoleColor background) + { + } + + public override void SetColors (short foregroundColorId, short backgroundColorId) + { + } + + public override void Suspend () + { + } + + public override void StartReportingMouseMoves () + { + } + + public override void StopReportingMouseMoves () + { + } + + public override void UncookMouse () + { + } + + public override void CookMouse () + { + } #endregion } diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index c12af219a..cfc270db2 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -528,7 +528,9 @@ namespace Terminal.Gui { wantContinuousButtonPressedView = view; else wantContinuousButtonPressedView = null; - + if (view != null) { + me.View = view; + } RootMouseEvent?.Invoke (me); if (mouseGrabView != null) { var newxy = mouseGrabView.ScreenToView (me.X, me.Y); diff --git a/Terminal.Gui/Core/Border.cs b/Terminal.Gui/Core/Border.cs new file mode 100644 index 000000000..a3848c54b --- /dev/null +++ b/Terminal.Gui/Core/Border.cs @@ -0,0 +1,853 @@ +using System; + +namespace Terminal.Gui { + /// + /// Specifies the border style for a and to be used by the class. + /// + public enum BorderStyle { + /// + /// No border is drawn. + /// + None, + /// + /// The border is drawn with a single line limits. + /// + Single, + /// + /// The border is drawn with a double line limits. + /// + Double + } + + /// + /// Describes the thickness of a frame around a rectangle. Four values describe + /// the , , , and sides + /// of the rectangle, respectively. + /// + public struct Thickness { + /// + /// Gets or sets the width, in integers, of the left side of the bounding rectangle. + /// + public int Left; + /// + /// Gets or sets the width, in integers, of the upper side of the bounding rectangle. + /// + public int Top; + /// + /// Gets or sets the width, in integers, of the right side of the bounding rectangle. + /// + public int Right; + /// + /// Gets or sets the width, in integers, of the lower side of the bounding rectangle. + /// + public int Bottom; + + /// + /// Initializes a new instance of the structure that has the + /// specified uniform length on each side. + /// + /// + public Thickness (int length) + { + if (length < 0) { + throw new ArgumentException ("Invalid value for this property."); + } + + Left = Top = Right = Bottom = length; + } + + /// + /// Initializes a new instance of the structure that has specific + /// lengths (supplied as a ) applied to each side of the rectangle. + /// + /// + /// + /// + /// + public Thickness (int left, int top, int right, int bottom) + { + if (left < 0 || top < 0 || right < 0 || bottom < 0) { + throw new ArgumentException ("Invalid value for this property."); + } + + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + } + + /// + /// Draws a border, background, or both around another element. + /// + public class Border { + private int marginFrame => DrawMarginFrame ? 1 : 0; + + /// + /// A sealed derived class to implement feature. + /// This is only a wrapper to get borders on a toplevel and is recommended using another + /// derived, like where is possible to have borders with or without + /// border line or spacing around. + /// + public sealed class ToplevelContainer : Toplevel { + /// + public override Border Border { + get => base.Border; + set { + if (base.Border != null && base.Border.Child != null && value.Child == null) { + value.Child = base.Border.Child; + } + base.Border = value; + if (value == null) { + return; + } + Rect frame; + if (Border.Child != null && (Border.Child.Width is Dim || Border.Child.Height is Dim)) { + frame = Rect.Empty; + } else { + frame = Frame; + } + AdjustContentView (frame); + + Border.BorderChanged += Border_BorderChanged; + } + } + + void Border_BorderChanged (Border border) + { + Rect frame; + if (Border.Child != null && (Border.Child.Width is Dim || Border.Child.Height is Dim)) { + frame = Rect.Empty; + } else { + frame = Frame; + } + AdjustContentView (frame); + } + + /// + /// Initializes with default null values. + /// + public ToplevelContainer () : this (null, null) { } + + /// + /// Initializes a with a + /// + /// The border. + /// The title. + public ToplevelContainer (Border border, string title = null) + { + Initialize (Rect.Empty, border, title); + } + + /// + /// Initializes a with a + /// + /// The frame. + /// The border. + /// The title. + public ToplevelContainer (Rect frame, Border border, string title = null) : base (frame) + { + Initialize (frame, border, title); + } + + private void Initialize (Rect frame, Border border, string title = null) + { + ColorScheme = Colors.TopLevel; + Text = title ?? ""; + if (border == null) { + Border = new Border () { + BorderStyle = BorderStyle.Single, + BorderBrush = ColorScheme.Normal.Background + }; + } else { + Border = border; + } + } + + void AdjustContentView (Rect frame) + { + var borderLength = Border.DrawMarginFrame ? 1 : 0; + var sumPadding = Border.GetSumThickness (); + var wb = new Size (); + if (frame == Rect.Empty) { + wb.Width = borderLength + sumPadding.Right; + wb.Height = borderLength + sumPadding.Bottom; + if (Border.Child == null) { + Border.Child = new ChildContentView (this) { + X = borderLength + sumPadding.Left, + Y = borderLength + sumPadding.Top, + Width = Dim.Fill (wb.Width), + Height = Dim.Fill (wb.Height) + }; + } else { + Border.Child.X = borderLength + sumPadding.Left; + Border.Child.Y = borderLength + sumPadding.Top; + Border.Child.Width = Dim.Fill (wb.Width); + Border.Child.Height = Dim.Fill (wb.Height); + } + } else { + wb.Width = (2 * borderLength) + sumPadding.Right + sumPadding.Left; + wb.Height = (2 * borderLength) + sumPadding.Bottom + sumPadding.Top; + var cFrame = new Rect (borderLength + sumPadding.Left, borderLength + sumPadding.Top, frame.Width - wb.Width, frame.Height - wb.Height); + if (Border.Child == null) { + Border.Child = new ChildContentView (cFrame, this); + } else { + Border.Child.Frame = cFrame; + } + } + base.Add (Border.Child); + Border.ChildContainer = this; + } + + /// + public override void Add (View view) + { + Border.Child.Add (view); + if (view.CanFocus) { + CanFocus = true; + } + AddMenuStatusBar (view); + } + + /// + public override void Remove (View view) + { + if (view == null) { + return; + } + + SetNeedsDisplay (); + var touched = view.Frame; + Border.Child.Remove (view); + + if (Border.Child.InternalSubviews.Count < 1) { + CanFocus = false; + } + RemoveMenuStatusBar (view); + } + + /// + public override void RemoveAll () + { + Border.Child.RemoveAll (); + } + + /// + public override void Redraw (Rect bounds) + { + if (!NeedDisplay.IsEmpty) { + Driver.SetAttribute (GetNormalColor ()); + Border.DrawContent (); + } + var savedClip = Border.Child.ClipToBounds (); + Border.Child.Redraw (Border.Child.Bounds); + Driver.Clip = savedClip; + + ClearLayoutNeeded (); + ClearNeedsDisplay (); + + if (Border.BorderStyle != BorderStyle.None) { + Driver.SetAttribute (GetNormalColor ()); + Border.DrawTitle (this, this.Frame); + } + + // Checks if there are any SuperView view which intersect with this window. + if (SuperView != null) { + SuperView.SetNeedsLayout (); + SuperView.SetNeedsDisplay (); + } + } + } + + private class ChildContentView : View { + View instance; + + public ChildContentView (Rect frame, View instance) : base (frame) + { + this.instance = instance; + } + public ChildContentView (View instance) + { + this.instance = instance; + } + + public override bool MouseEvent (MouseEvent mouseEvent) + { + return instance.MouseEvent (mouseEvent); + } + } + + /// + /// Event to be invoked when any border property change. + /// + public event Action BorderChanged; + + private BorderStyle borderStyle; + private bool drawMarginFrame; + private Thickness borderThickness; + private Thickness padding; + + /// + /// Specifies the for a view. + /// + public BorderStyle BorderStyle { + get => borderStyle; + set { + if (value != BorderStyle.None && !drawMarginFrame) { + // Ensures drawn the border lines. + drawMarginFrame = true; + } + borderStyle = value; + OnBorderChanged (); + } + } + + /// + /// Gets or sets if a margin frame is drawn around the regardless the + /// + public bool DrawMarginFrame { + get => drawMarginFrame; + set { + if (borderStyle != BorderStyle.None + && (!value || !drawMarginFrame)) { + // Ensures drawn the border lines. + drawMarginFrame = true; + } else { + drawMarginFrame = value; + } + OnBorderChanged (); + } + } + + /// + /// Gets or sets the relative of a . + /// + public Thickness BorderThickness { + get => borderThickness; + set { + borderThickness = value; + OnBorderChanged (); + } + } + + /// + /// Gets or sets the that draws the outer border color. + /// + public Color BorderBrush { get; set; } + + /// + /// Gets or sets the that fills the area between the bounds of a . + /// + public Color Background { get; set; } + + /// + /// Gets or sets a value that describes the amount of space between a + /// and its child element. + /// + public Thickness Padding { + get => padding; + set { + padding = value; + OnBorderChanged (); + } + } + + /// + /// Gets the rendered width of this element. + /// + public int ActualWidth { + get { + if (Parent?.Border == null) { + return Child?.Frame.Width + (2 * marginFrame) + Padding.Right + + BorderThickness.Right + Padding.Left + BorderThickness.Left ?? 0; + } + return Parent.Frame.Width; + } + } + /// + /// Gets the rendered height of this element. + /// + public int ActualHeight { + get { + if (Parent?.Border == null) { + return Child?.Frame.Height + (2 * marginFrame) + Padding.Bottom + + BorderThickness.Bottom + Padding.Top + BorderThickness.Top ?? 0; + } + return Parent.Frame.Height; + } + } + + /// + /// Gets or sets the single child element of a . + /// + public View Child { get; set; } + + /// + /// Gets the parent parent if any. + /// + public View Parent { get => Child?.SuperView; } + + /// + /// Gets or private sets by the + /// + public ToplevelContainer ChildContainer { get; private set; } + + /// + /// Gets or sets the 3D effect around the . + /// + public bool Effect3D { get; set; } + + /// + /// Get or sets the offset start position for the + /// + public Point Effect3DOffset { get; set; } = new Point (1, 1); + + /// + /// Gets or sets the color for the + /// + public Color Effect3DBrush { get; set; } = Color.DarkGray; + + /// + /// Calculate the sum of the and the + /// + /// The total of the + public Thickness GetSumThickness () + { + return new Thickness () { + Left = Padding.Left + BorderThickness.Left, + Top = Padding.Top + BorderThickness.Top, + Right = Padding.Right + BorderThickness.Right, + Bottom = Padding.Bottom + BorderThickness.Bottom + }; + } + + /// + /// Drawn the more the + /// more the and the . + /// + public void DrawContent () + { + if (Parent?.Border != null) { + DrawParentBorder (Parent.ViewToScreen (new Rect (0, 0, Parent.Frame.Width, Parent.Frame.Height))); + } else { + DrawChildBorder (Child.ViewToScreen (new Rect (0, 0, Child.Frame.Width, Child.Frame.Height))); + } + } + + /// + /// Same as but drawing full frames for all borders. + /// + public void DrawFullContent () + { + var borderThickness = BorderThickness; + var padding = Padding; + var marginFrame = DrawMarginFrame ? 1 : 0; + var driver = Application.Driver; + Rect scrRect; + if (Parent?.Border != null) { + scrRect = Parent.ViewToScreen (new Rect (0, 0, Parent.Frame.Width, Parent.Frame.Height)); + } else { + scrRect = Child.ViewToScreen (new Rect (0, 0, Child.Frame.Width, Child.Frame.Height)); + } + Rect borderRect; + if (Parent?.Border != null) { + borderRect = scrRect; + } else { + borderRect = new Rect () { + X = scrRect.X - marginFrame - padding.Left - borderThickness.Left, + Y = scrRect.Y - marginFrame - padding.Top - borderThickness.Top, + Width = ActualWidth, + Height = ActualHeight + }; + } + var savedAttribute = driver.GetAttribute (); + + // Draw 3D effects + if (Effect3D) { + driver.SetAttribute (new Attribute (Effect3DBrush)); + var effectBorder = new Rect () { + X = borderRect.X + Effect3DOffset.X, + Y = borderRect.Y + Effect3DOffset.Y, + Width = ActualWidth, + Height = ActualHeight + }; + Child.Clear (effectBorder); + } + + // Draw border thickness + driver.SetAttribute (new Attribute (BorderBrush)); + Child.Clear (borderRect); + + borderRect = new Rect () { + X = borderRect.X + borderThickness.Left, + Y = borderRect.Y + borderThickness.Top, + Width = Math.Max (borderRect.Width - borderThickness.Right - borderThickness.Left, 0), + Height = Math.Max (borderRect.Height - borderThickness.Bottom - borderThickness.Top, 0) + }; + if (borderRect != scrRect) { + // Draw padding + driver.SetAttribute (new Attribute (Background)); + Child.Clear (borderRect); + } + + driver.SetAttribute (savedAttribute); + + // Draw margin frame + if (Parent?.Border != null) { + var sumPadding = GetSumThickness (); + borderRect = new Rect () { + X = scrRect.X + sumPadding.Left, + Y = scrRect.Y + sumPadding.Top, + Width = Math.Max (scrRect.Width - sumPadding.Right - sumPadding.Left, 0), + Height = Math.Max (scrRect.Height - sumPadding.Bottom - sumPadding.Top, 0) + }; + } else { + borderRect = new Rect () { + X = borderRect.X + padding.Left, + Y = borderRect.Y + padding.Top, + Width = Math.Max (borderRect.Width - padding.Right - padding.Left, 0), + Height = Math.Max (borderRect.Height - padding.Bottom - padding.Top, 0) + }; + } + if (borderRect.Width > 0 && borderRect.Height > 0) { + driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this); + } + } + + private void DrawChildBorder (Rect frame) + { + var drawMarginFrame = DrawMarginFrame ? 1 : 0; + var sumThickness = GetSumThickness (); + var padding = Padding; + var effect3DOffset = Effect3DOffset; + var driver = Application.Driver; + + var savedAttribute = driver.GetAttribute (); + + driver.SetAttribute (new Attribute (BorderBrush)); + + // Draw the upper BorderThickness + for (int r = frame.Y - drawMarginFrame - sumThickness.Top; + r < frame.Y - drawMarginFrame - padding.Top; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left; + c < frame.Right + drawMarginFrame + sumThickness.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the left BorderThickness + for (int r = frame.Y - drawMarginFrame - padding.Top; + r < frame.Bottom + drawMarginFrame + padding.Bottom; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left; + c < frame.X - drawMarginFrame - padding.Left; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the right BorderThickness + for (int r = frame.Y - drawMarginFrame - padding.Top; + r < frame.Bottom + drawMarginFrame + padding.Bottom; r++) { + for (int c = frame.Right + drawMarginFrame + padding.Right; + c < frame.Right + drawMarginFrame + sumThickness.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the lower BorderThickness + for (int r = frame.Bottom + drawMarginFrame + padding.Bottom; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left; + c < frame.Right + drawMarginFrame + sumThickness.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + driver.SetAttribute (new Attribute (Background)); + + // Draw the upper Padding + for (int r = frame.Y - drawMarginFrame - padding.Top; + r < frame.Y - drawMarginFrame; r++) { + for (int c = frame.X - drawMarginFrame - padding.Left; + c < frame.Right + drawMarginFrame + padding.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the left Padding + for (int r = frame.Y - drawMarginFrame; + r < frame.Bottom + drawMarginFrame; r++) { + for (int c = frame.X - drawMarginFrame - padding.Left; + c < frame.X - drawMarginFrame; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the right Padding + for (int r = frame.Y - drawMarginFrame; + r < frame.Bottom + drawMarginFrame; r++) { + for (int c = frame.Right + drawMarginFrame; + c < frame.Right + drawMarginFrame + padding.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the lower Padding + for (int r = frame.Bottom + drawMarginFrame; + r < frame.Bottom + drawMarginFrame + padding.Bottom; r++) { + for (int c = frame.X - drawMarginFrame - padding.Left; + c < frame.Right + drawMarginFrame + padding.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + driver.SetAttribute (savedAttribute); + + // Draw the MarginFrame + var rect = new Rect () { + X = frame.X - drawMarginFrame, + Y = frame.Y - drawMarginFrame, + Width = frame.Width + (2 * drawMarginFrame), + Height = frame.Height + (2 * drawMarginFrame) + }; + if (rect.Width > 0 && rect.Height > 0) { + driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this); + } + + if (Effect3D) { + driver.SetAttribute (new Attribute (Effect3DBrush)); + + // Draw the upper Effect3D + for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y; + r < frame.Y - drawMarginFrame - sumThickness.Top; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X; + c < frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the left Effect3D + for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X; + c < frame.X - drawMarginFrame - sumThickness.Left; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the right Effect3D + for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.Right + drawMarginFrame + sumThickness.Right; + c < frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the lower Effect3D + for (int r = frame.Bottom + drawMarginFrame + sumThickness.Bottom; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X; + c < frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + } + } + + private void DrawParentBorder (Rect frame) + { + var drawMarginFrame = DrawMarginFrame ? 1 : 0; + var sumThickness = GetSumThickness (); + var borderThickness = BorderThickness; + var effect3DOffset = Effect3DOffset; + var driver = Application.Driver; + + var savedAttribute = driver.GetAttribute (); + + driver.SetAttribute (new Attribute (BorderBrush)); + + // Draw the upper BorderThickness + for (int r = frame.Y; + r < Math.Min (frame.Y + borderThickness.Top, frame.Bottom); r++) { + for (int c = frame.X; + c < frame.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the left BorderThickness + for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom); + r < frame.Bottom - borderThickness.Bottom; r++) { + for (int c = frame.X; + c < Math.Min (frame.X + borderThickness.Left, frame.Right); c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the right BorderThickness + for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom); + r < frame.Bottom - borderThickness.Bottom; r++) { + for (int c = Math.Max (frame.Right - borderThickness.Right, frame.X); + c < frame.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the lower BorderThickness + for (int r = Math.Max (frame.Bottom - borderThickness.Bottom, frame.Y); + r < frame.Bottom; r++) { + for (int c = frame.X; + c < frame.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + driver.SetAttribute (new Attribute (Background)); + + // Draw the upper Padding + for (int r = frame.Y + borderThickness.Top; + r < Math.Min (frame.Y + sumThickness.Top, frame.Bottom - borderThickness.Bottom); r++) { + for (int c = frame.X + borderThickness.Left; + c < frame.Right - borderThickness.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the left Padding + for (int r = frame.Y + sumThickness.Top; + r < frame.Bottom - sumThickness.Bottom; r++) { + for (int c = frame.X + borderThickness.Left; + c < Math.Min (frame.X + sumThickness.Left, frame.Right - borderThickness.Right); c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the right Padding + for (int r = frame.Y + sumThickness.Top; + r < frame.Bottom - sumThickness.Bottom; r++) { + for (int c = Math.Max (frame.Right - sumThickness.Right, frame.X + sumThickness.Left); + c < Math.Max (frame.Right - borderThickness.Right, frame.X + sumThickness.Left); c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the lower Padding + for (int r = Math.Max (frame.Bottom - sumThickness.Bottom, frame.Y + borderThickness.Top); + r < frame.Bottom - borderThickness.Bottom; r++) { + for (int c = frame.X + borderThickness.Left; + c < frame.Right - borderThickness.Right; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + driver.SetAttribute (savedAttribute); + + // Draw the MarginFrame + var rect = new Rect () { + X = frame.X + sumThickness.Left, + Y = frame.Y + sumThickness.Top, + Width = Math.Max (frame.Width - sumThickness.Right - sumThickness.Left, 0), + Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0) + }; + if (rect.Width > 0 && rect.Height > 0) { + driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this); + } + + if (Effect3D) { + driver.SetAttribute (new Attribute (Effect3DBrush)); + + // Draw the upper Effect3D + for (int r = frame.Y + effect3DOffset.Y; + r < frame.Y; r++) { + for (int c = frame.X + effect3DOffset.X; + c < frame.Right + effect3DOffset.X; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the left Effect3D + for (int r = frame.Y + effect3DOffset.Y; + r < frame.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X + effect3DOffset.X; + c < frame.X; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the right Effect3D + for (int r = frame.Y + effect3DOffset.Y; + r < frame.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.Right; + c < frame.Right + effect3DOffset.X; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + + // Draw the lower Effect3D + for (int r = frame.Bottom; + r < frame.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X + effect3DOffset.X; + c < frame.Right + effect3DOffset.X; c++) { + + AddRuneAt (driver, c, r, ' '); + } + } + } + } + + private void AddRuneAt (ConsoleDriver driver, int col, int row, Rune ch) + { + driver.Move (col, row); + driver.AddRune (ch); + } + + /// + /// Drawn the view text from a . + /// + public void DrawTitle (View view, Rect rect) + { + var driver = Application.Driver; + if (BorderStyle != BorderStyle.None) { + driver.SetAttribute (view.GetNormalColor ()); + if (view.HasFocus) { + driver.SetAttribute (view.ColorScheme.HotNormal); + } + var padding = GetSumThickness (); + driver.DrawWindowTitle (rect, view.Text, + padding.Left, padding.Top, padding.Right, padding.Bottom); + } + driver.SetAttribute (view.GetNormalColor ()); + } + + /// + /// Invoke the event. + /// + public virtual void OnBorderChanged () + { + BorderChanged?.Invoke (this); + } + } +} diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index 6967eb962..8d424ef79 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/Terminal.Gui/Core/ConsoleDriver.cs @@ -104,13 +104,31 @@ namespace Terminal.Gui { /// public Color Background { get; } + /// + /// Initializes a new instance of the struct with only the value passed to + /// and trying to get the colors if defined. + /// + /// Value. + public Attribute (int value) + { + Color foreground = default; + Color background = default; + + if (Application.Driver != null) { + Application.Driver.GetColors (value, out foreground, out background); + } + Value = value; + Foreground = foreground; + Background = background; + } + /// /// Initializes a new instance of the struct. /// /// Value. /// Foreground /// Background - public Attribute (int value, Color foreground = new Color (), Color background = new Color ()) + public Attribute (int value, Color foreground, Color background) { Value = value; Foreground = foreground; @@ -129,6 +147,13 @@ namespace Terminal.Gui { Background = background; } + /// + /// Initializes a new instance of the struct + /// with the same colors for the foreground and background. + /// + /// The color. + public Attribute (Color color) : this (color, color) { } + /// /// Implicit conversion from an to the underlying Int32 representation /// @@ -751,6 +776,15 @@ namespace Terminal.Gui { /// Background color identifier. public abstract void SetColors (short foregroundColorId, short backgroundColorId); + /// + /// Gets the foreground and background colors based on the value. + /// + /// The value. + /// The foreground. + /// The background. + /// + public abstract bool GetColors (int value, out Color foreground, out Color background); + /// /// Allows sending keys without typing on a keyboard. /// @@ -803,12 +837,12 @@ namespace Terminal.Gui { /// Off = 0b_0000_0000, /// - /// When enabled, will draw a + /// When enabled, will draw a /// ruler in the frame for any side with a padding value greater than 0. /// FrameRuler = 0b_0000_0001, /// - /// When Enabled, will use + /// When Enabled, will use /// 'L', 'R', 'T', and 'B' for padding instead of ' '. /// FramePadding = 0b_0000_0010, @@ -829,7 +863,9 @@ namespace Terminal.Gui { /// Number of rows to pad on the bottom (if 0 the border will not appear on the bottom). /// If set to true and any padding dimension is > 0 the border will be drawn. /// If set to true it will clear the content area (the area inside the padding) with the current color, otherwise the content area will be left untouched. - public virtual void DrawWindowFrame (Rect region, int paddingLeft = 0, int paddingTop = 0, int paddingRight = 0, int paddingBottom = 0, bool border = true, bool fill = false) + /// The to be used if defined. + public virtual void DrawWindowFrame (Rect region, int paddingLeft = 0, int paddingTop = 0, int paddingRight = 0, + int paddingBottom = 0, bool border = true, bool fill = false, Border borderContent = null) { char clearChar = ' '; char leftChar = clearChar; @@ -866,15 +902,17 @@ namespace Terminal.Gui { // ftop is location of top frame line int ftop = region.Y + paddingTop - 1; - // fbottom is locaiton of bottom frame line + // fbottom is location of bottom frame line int fbottom = ftop + fheight + 1; - Rune hLine = border ? HLine : clearChar; - Rune vLine = border ? VLine : clearChar; - Rune uRCorner = border ? URCorner : clearChar; - Rune uLCorner = border ? ULCorner : clearChar; - Rune lLCorner = border ? LLCorner : clearChar; - Rune lRCorner = border ? LRCorner : clearChar; + var borderStyle = borderContent == null ? BorderStyle.Single : borderContent.BorderStyle; + + Rune hLine = border ? (borderStyle == BorderStyle.Single ? HLine : HDLine) : clearChar; + Rune vLine = border ? (borderStyle == BorderStyle.Single ? VLine : VDLine) : clearChar; + Rune uRCorner = border ? (borderStyle == BorderStyle.Single ? URCorner : URDCorner) : clearChar; + Rune uLCorner = border ? (borderStyle == BorderStyle.Single ? ULCorner : ULDCorner) : clearChar; + Rune lLCorner = border ? (borderStyle == BorderStyle.Single ? LLCorner : LLDCorner) : clearChar; + Rune lRCorner = border ? (borderStyle == BorderStyle.Single ? LRCorner : LRDCorner) : clearChar; // Outside top if (paddingTop > 1) { @@ -985,7 +1023,7 @@ namespace Terminal.Gui { /// Screen relative region where the frame will be drawn. /// Padding to add on the sides. /// If set to true it will clear the contents with the current color, otherwise the contents will be left untouched. - /// This API has been superseded by . + /// This API has been superseded by . /// This API is equivalent to calling DrawWindowFrame(Rect, p - 1, p - 1, p - 1, p - 1). In other words, /// A padding value of 0 means there is actually a one cell border. /// @@ -1164,6 +1202,36 @@ namespace Terminal.Gui { /// public Rune ContinuousMeterSegment = '\u2588'; + /// + /// Horizontal double line character. + /// + public Rune HDLine = '\u2550'; + + /// + /// Vertical double line character. + /// + public Rune VDLine = '\u2551'; + + /// + /// Upper left double corner + /// + public Rune ULDCorner = '\u2554'; + + /// + /// Lower left double corner + /// + public Rune LLDCorner = '\u255a'; + + /// + /// Upper right double corner + /// + public Rune URDCorner = '\u2557'; + + /// + /// Lower right double corner + /// + public Rune LRDCorner = '\u255d'; + /// /// Make the attribute for the foreground and background colors. /// diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index c8e433036..ee4217837 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -663,6 +663,7 @@ namespace Terminal.Gui { nx = mouseEvent.X - mouseEvent.OfX; ny = mouseEvent.Y - mouseEvent.OfY; dragPosition = new Point (nx, ny); + SuperView?.BringSubviewToFront (this); Application.GrabMouse (this); } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 9944559cc..e94b1fa81 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -664,9 +664,10 @@ namespace Terminal.Gui { /// /// Location. /// text to initialize the property with. - public View (Rect rect, ustring text) + /// The . + public View (Rect rect, ustring text, Border border = null) { - Initialize (text, rect, LayoutStyle.Absolute); + Initialize (text, rect, LayoutStyle.Absolute, TextDirection.LeftRight_TopBottom, border); } /// @@ -684,19 +685,22 @@ namespace Terminal.Gui { /// /// text to initialize the property with. /// The text direction. - public View (ustring text, TextDirection direction = TextDirection.LeftRight_TopBottom) + /// The . + public View (ustring text, TextDirection direction = TextDirection.LeftRight_TopBottom, Border border = null) { - Initialize (text, Rect.Empty, LayoutStyle.Computed, direction); + Initialize (text, Rect.Empty, LayoutStyle.Computed, direction, border); } void Initialize (ustring text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed, - TextDirection direction = TextDirection.LeftRight_TopBottom) + TextDirection direction = TextDirection.LeftRight_TopBottom, Border border = null) { textFormatter = new TextFormatter (); TextDirection = direction; - + Border = border; + if (Border != null) { + Border.Child = this; + } shortcutHelper = new ShortcutHelper (); - CanFocus = false; TabIndex = -1; TabStop = false; @@ -1361,10 +1365,15 @@ namespace Terminal.Gui { var clipRect = new Rect (Point.Empty, frame.Size); + //if (ColorScheme != null && !(this is Toplevel)) { if (ColorScheme != null) { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); } + if (Border != null) { + Border.DrawContent (); + } + if (!ustring.IsNullOrEmpty (Text) || (this is Label && !AutoSize)) { Clear (); // Draw any Text @@ -2167,6 +2176,19 @@ namespace Terminal.Gui { } } + Border border; + + /// + public virtual Border Border { + get => border; + set { + if (border != value) { + border = value; + SetNeedsDisplay (); + } + } + } + /// /// Pretty prints the View /// @@ -2494,7 +2516,7 @@ namespace Terminal.Gui { /// /// if is /// or if is - protected Attribute GetNormalColor () + public Attribute GetNormalColor () { return Enabled ? ColorScheme.Normal : ColorScheme.Disabled; } diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs index 26a389275..5395ef016 100644 --- a/Terminal.Gui/Core/Window.cs +++ b/Terminal.Gui/Core/Window.cs @@ -23,7 +23,6 @@ namespace Terminal.Gui { public class Window : Toplevel { View contentView; ustring title; - int padding; /// /// The title to be displayed for this window. @@ -37,14 +36,62 @@ namespace Terminal.Gui { } } + /// + public override Border Border { + get => base.Border; + set { + if (base.Border != null && base.Border.Child != null && value.Child == null) { + value.Child = base.Border.Child; + } + base.Border = value; + if (value == null) { + return; + } + Rect frame; + if (contentView != null && (contentView.Width is Dim || contentView.Height is Dim)) { + frame = Rect.Empty; + } else { + frame = Frame; + } + AdjustContentView (frame); + + Border.BorderChanged += Border_BorderChanged; + } + } + + void Border_BorderChanged (Border border) + { + Rect frame; + if (contentView != null && (contentView.Width is Dim || contentView.Height is Dim)) { + frame = Rect.Empty; + } else { + frame = Frame; + } + AdjustContentView (frame); + } + + /// /// ContentView is an internal implementation detail of Window. It is used to host Views added with . /// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds /// are actually deflated due to the border. /// class ContentView : View { - public ContentView (Rect frame) : base (frame) { } - public ContentView () : base () { } + Window instance; + + public ContentView (Rect frame, Window instance) : base (frame) + { + this.instance = instance; + } + public ContentView (Window instance) : base () + { + this.instance = instance; + } + + public override bool MouseEvent (MouseEvent mouseEvent) + { + return instance.MouseEvent (mouseEvent); + } } /// @@ -56,7 +103,7 @@ namespace Terminal.Gui { /// This constructor initializes a Window with a of . Use constructors /// that do not take Rect parameters to initialize a Window with . /// - public Window (Rect frame, ustring title = null) : this (frame, title, padding: 0) + public Window (Rect frame, ustring title = null) : this (frame, title, padding: 0, border: null) { } @@ -68,7 +115,7 @@ namespace Terminal.Gui { /// This constructor initializes a View with a of . /// Use , , , and properties to dynamically control the size and location of the view. /// - public Window (ustring title = null) : this (title, padding: 0) + public Window (ustring title = null) : this (title, padding: 0, border: null) { } @@ -82,52 +129,82 @@ namespace Terminal.Gui { /// and an optional title. /// /// Superview-relative rectangle specifying the location and size - /// Number of characters to use for padding of the drawn frame. /// Title + /// Number of characters to use for padding of the drawn frame. + /// The . /// /// This constructor initializes a Window with a of . Use constructors /// that do not take Rect parameters to initialize a Window with of /// - public Window (Rect frame, ustring title = null, int padding = 0) : base (frame) + public Window (Rect frame, ustring title = null, int padding = 0, Border border = null) : base (frame) { - Initialize (title, frame, padding); + Initialize (title, frame, padding, border); } /// /// Initializes a new instance of the using positioning, /// and an optional title. /// - /// Number of characters to use for padding of the drawn frame. /// Title. + /// Number of characters to use for padding of the drawn frame. + /// The . /// /// This constructor initializes a View with a of . /// Use , , , and properties to dynamically control the size and location of the view. /// - public Window (ustring title = null, int padding = 0) : base () + public Window (ustring title = null, int padding = 0, Border border = null) : base () { - Initialize (title, Rect.Empty, padding); + Initialize (title, Rect.Empty, padding, border); } - void Initialize (ustring title, Rect frame, int padding = 0) + void Initialize (ustring title, Rect frame, int padding = 0, Border border = null) { ColorScheme = Colors.Base; Title = title; - int wb; - if (frame == Rect.Empty) { - wb = 1 + padding; - contentView = new ContentView () { - X = wb, - Y = wb, - Width = Dim.Fill (wb), - Height = Dim.Fill (wb) + if (border == null) { + Border = new Border () { + BorderStyle = BorderStyle.Single, + Padding = new Thickness (padding), + BorderBrush = ColorScheme.Normal.Background }; } else { - wb = 2 * (1 + padding); - var cFrame = new Rect (1 + padding, 1 + padding, frame.Width - wb, frame.Height - wb); - contentView = new ContentView (cFrame); + Border = border; + } + } + + void AdjustContentView (Rect frame) + { + var borderLength = Border.DrawMarginFrame ? 1 : 0; + var sumPadding = Border.GetSumThickness (); + var wb = new Size (); + if (frame == Rect.Empty) { + wb.Width = borderLength + sumPadding.Right; + wb.Height = borderLength + sumPadding.Bottom; + if (contentView == null) { + contentView = new ContentView (this) { + X = borderLength + sumPadding.Left, + Y = borderLength + sumPadding.Top, + Width = Dim.Fill (wb.Width), + Height = Dim.Fill (wb.Height) + }; + } else { + contentView.X = borderLength + sumPadding.Left; + contentView.Y = borderLength + sumPadding.Top; + contentView.Width = Dim.Fill (wb.Width); + contentView.Height = Dim.Fill (wb.Height); + } + } else { + wb.Width = (2 * borderLength) + sumPadding.Right + sumPadding.Left; + wb.Height = (2 * borderLength) + sumPadding.Bottom + sumPadding.Top; + var cFrame = new Rect (borderLength + sumPadding.Left, borderLength + sumPadding.Top, frame.Width - wb.Width, frame.Height - wb.Height); + if (contentView == null) { + contentView = new ContentView (cFrame, this); + } else { + contentView.Frame = cFrame; + } } - this.padding = padding; base.Add (contentView); + Border.Child = contentView; } ///// @@ -176,16 +253,18 @@ namespace Terminal.Gui { /// public override void Redraw (Rect bounds) { - //var padding = 0; + var padding = Border.GetSumThickness (); var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height)); + //var borderLength = Border.DrawMarginFrame ? 1 : 0; // BUGBUG: Why do we draw the frame twice? This call is here to clear the content area, I think. Why not just clear that area? if (!NeedDisplay.IsEmpty) { Driver.SetAttribute (GetNormalColor ()); - Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true); + //Driver.DrawWindowFrame (scrRect, padding.Left + borderLength, padding.Top + borderLength, padding.Right + borderLength, padding.Bottom + borderLength, + // Border.BorderStyle != BorderStyle.None, fill: true, Border); + Border.DrawContent (); } - - var savedClip = ClipToBounds (); + var savedClip = contentView.ClipToBounds (); // Redraw our contentView // TODO: smartly constrict contentView.Bounds to just be what intersects with the 'bounds' we were passed @@ -194,12 +273,14 @@ namespace Terminal.Gui { ClearLayoutNeeded (); ClearNeedsDisplay (); - Driver.SetAttribute (GetNormalColor ()); - Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false); - - if (HasFocus) - Driver.SetAttribute (ColorScheme.HotNormal); - Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding); + if (Border.BorderStyle != BorderStyle.None) { + Driver.SetAttribute (GetNormalColor ()); + //Driver.DrawWindowFrame (scrRect, padding.Left + borderLength, padding.Top + borderLength, padding.Right + borderLength, padding.Bottom + borderLength, + // Border.BorderStyle != BorderStyle.None, fill: true, Border.BorderStyle); + if (HasFocus) + Driver.SetAttribute (ColorScheme.HotNormal); + Driver.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom); + } Driver.SetAttribute (GetNormalColor ()); // Checks if there are any SuperView view which intersect with this window. diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index b19411f25..8a3937142 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -34,6 +34,40 @@ namespace Terminal.Gui { } } + /// + public override Border Border { + get => base.Border; + set { + if (base.Border != null && base.Border.Child != null && value.Child == null) { + value.Child = base.Border.Child; + } + base.Border = value; + if (value == null) { + return; + } + Rect frame; + if (contentView != null && (contentView.Width is Dim || contentView.Height is Dim)) { + frame = Rect.Empty; + } else { + frame = Frame; + } + AdjustContentView (frame); + + Border.BorderChanged += Border_BorderChanged; + } + } + + void Border_BorderChanged (Border border) + { + Rect frame; + if (contentView != null && (contentView.Width is Dim || contentView.Height is Dim)) { + frame = Rect.Empty; + } else { + frame = Frame; + } + AdjustContentView (frame); + } + /// /// ContentView is an internal implementation detail of Window. It is used to host Views added with . /// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds @@ -49,30 +83,22 @@ namespace Terminal.Gui { /// /// Frame. /// Title. - public FrameView (Rect frame, ustring title = null) : base (frame) - { - var cFrame = new Rect (1, 1, Math.Max (frame.Width - 2, 0), Math.Max (frame.Height - 2, 0)); - Initialize (title, cFrame); - } - - /// - /// Initializes a new instance of the class using layout. - /// - /// Frame. - /// Title. /// Views. - public FrameView (Rect frame, ustring title, View [] views) : this (frame, title) + /// The . + public FrameView (Rect frame, ustring title = null, View [] views = null, Border border = null) : base (frame) { - Initialize (title, frame, views); + //var cFrame = new Rect (1, 1, Math.Max (frame.Width - 2, 0), Math.Max (frame.Height - 2, 0)); + Initialize (frame, title, views, border); } /// /// Initializes a new instance of the class using layout. /// /// Title. - public FrameView (ustring title) + /// The . + public FrameView (ustring title, Border border = null) { - Initialize (title, Rect.Empty); + Initialize (Rect.Empty, title, null, border); } /// @@ -80,19 +106,49 @@ namespace Terminal.Gui { /// public FrameView () : this (title: string.Empty) { } - void Initialize (ustring title, Rect frame, View [] views = null) + void Initialize (Rect frame, ustring title, View [] views = null, Border border = null) { this.title = title; - if (frame == Rect.Empty) { - const int wb = 1; - contentView = new ContentView () { - X = wb, - Y = wb, - Width = Dim.Fill (wb), - Height = Dim.Fill (wb) + if (border == null) { + Border = new Border () { + BorderStyle = BorderStyle.Single }; } else { - contentView = new ContentView (frame); + Border = border; + } + AdjustContentView (frame, views); + } + + void AdjustContentView (Rect frame, View [] views = null) + { + var borderLength = Border.DrawMarginFrame ? 1 : 0; + var sumPadding = Border.GetSumThickness (); + var wb = new Size (); + if (frame == Rect.Empty) { + wb.Width = borderLength + sumPadding.Right; + wb.Height = borderLength + sumPadding.Bottom; + if (contentView == null) { + contentView = new ContentView () { + X = borderLength + sumPadding.Left, + Y = borderLength + sumPadding.Top, + Width = Dim.Fill (wb.Width), + Height = Dim.Fill (wb.Height) + }; + } else { + contentView.X = borderLength + sumPadding.Left; + contentView.Y = borderLength + sumPadding.Top; + contentView.Width = Dim.Fill (wb.Width); + contentView.Height = Dim.Fill (wb.Height); + } + } else { + wb.Width = (2 * borderLength) + sumPadding.Right + sumPadding.Left; + wb.Height = (2 * borderLength) + sumPadding.Bottom + sumPadding.Top; + var cFrame = new Rect (borderLength + sumPadding.Left, borderLength + sumPadding.Top, frame.Width - wb.Width, frame.Height - wb.Height); + if (contentView == null) { + contentView = new ContentView (cFrame); + } else { + contentView.Frame = cFrame; + } } if (views != null) { foreach (var view in views) { @@ -103,6 +159,7 @@ namespace Terminal.Gui { base.Add (contentView); contentView.Text = base.Text; } + Border.Child = contentView; } void DrawFrame () @@ -153,25 +210,28 @@ namespace Terminal.Gui { /// public override void Redraw (Rect bounds) { - var padding = 0; + var padding = Border.GetSumThickness (); var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height)); if (!NeedDisplay.IsEmpty) { Driver.SetAttribute (GetNormalColor ()); - Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true); + //Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true); + Border.DrawContent (); } - var savedClip = ClipToBounds (); + var savedClip = contentView.ClipToBounds (); contentView.Redraw (contentView.Bounds); Driver.Clip = savedClip; ClearNeedsDisplay (); - Driver.SetAttribute (GetNormalColor ()); - Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false); - - if (HasFocus) - Driver.SetAttribute (ColorScheme.HotNormal); - Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding); + if (Border.BorderStyle != BorderStyle.None) { + Driver.SetAttribute (GetNormalColor ()); + //Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false); + if (HasFocus) + Driver.SetAttribute (ColorScheme.HotNormal); + //Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding); + Driver.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom); + } Driver.SetAttribute (GetNormalColor ()); } diff --git a/Terminal.Gui/Views/PanelView.cs b/Terminal.Gui/Views/PanelView.cs new file mode 100644 index 000000000..edd747cd4 --- /dev/null +++ b/Terminal.Gui/Views/PanelView.cs @@ -0,0 +1,219 @@ +using System; + +namespace Terminal.Gui { + /// + /// A container for single that will allow to drawn in + /// two ways. If the borders and the child will be accommodated in the available + /// panel size, otherwise the panel will be resized based on the child and borders thickness sizes. + /// + public class PanelView : View { + ChildContentView childContentView; + + private class ChildContentView : View { } + + private class SavedPosDim { + public Pos X; + public Pos Y; + public Dim Width; + public Dim Height; + } + + private SavedPosDim savedPanel; + private SavedPosDim savedChild; + + private View child; + private bool usePanelFrame; + + /// + /// Initializes a panel with a null child. + /// + public PanelView () : this (null) { } + + /// + /// Initializes a panel with a valid child. + /// + /// + public PanelView (View child) + { + childContentView = new ChildContentView (); + base.Add (childContentView); + CanFocus = false; + Child = child; + if (child != null) { + Visible = child.Visible; + } + } + + /// + /// Gets or sets if the panel size will used, otherwise the child size. + /// + public bool UsePanelFrame { + get => usePanelFrame; + set { + usePanelFrame = value; + AdjustContainer (); + } + } + + /// + /// The child that will use this panel. + /// + public View Child { + get => child; + set { + if (child != null && value == null) { + childContentView.Remove (child); + child = value; + return; + } + child = value; + savedChild = new SavedPosDim () { + X = child?.X, + Y = child?.Y, + Width = child?.Width, + Height = child?.Height + }; + if (child == null) { + Visible = false; + return; + } + child.X = 0; + child.Y = 0; + AdjustContainer (); + if (child?.Border != null) { + child.Border.BorderChanged += Border_BorderChanged; + Border = child.Border; + Border.Child = childContentView; + } else { + if (Border == null) { + Border = new Border (); + } + Border.BorderChanged += Border_BorderChanged; + Border.Child = childContentView; + } + if (!child.IsInitialized) { + child.Initialized += Child_Initialized; + } + childContentView.Add (Child); + } + } + + private void Child_Initialized (object sender, EventArgs e) + { + savedPanel = new SavedPosDim () { + X = X, + Y = Y, + Width = Width, + Height = Height + }; + AdjustContainer (); + Child.Initialized -= Child_Initialized; + } + + private void Border_BorderChanged (Border obj) + { + AdjustContainer (); + } + + private void AdjustContainer () + { + if (Child?.IsInitialized == true) { + var borderLength = Child.Border != null + ? Child.Border.DrawMarginFrame ? 1 : 0 + : 0; + var sumPadding = Child.Border != null + ? Child.Border.GetSumThickness () + : new Thickness (); + if (!UsePanelFrame) { + X = savedChild.X; + childContentView.X = borderLength + sumPadding.Left; + Y = savedChild.Y; + childContentView.Y = borderLength + sumPadding.Top; + if (savedChild.Width is Dim.DimFill) { + var margin = -savedChild.Width.Anchor (0); + Width = Dim.Fill (margin); + childContentView.Width = Dim.Fill (margin + borderLength + sumPadding.Right); + } else { + Width = savedChild.Width + (2 * borderLength) + sumPadding.Right + sumPadding.Left; + childContentView.Width = Dim.Fill (borderLength + sumPadding.Right); + } + if (savedChild.Height is Dim.DimFill) { + var margin = -savedChild.Height.Anchor (0); + Height = Dim.Fill (margin); + childContentView.Height = Dim.Fill (margin + borderLength + sumPadding.Bottom); + } else { + Height = savedChild.Height + (2 * borderLength) + sumPadding.Bottom + sumPadding.Top; + childContentView.Height = Dim.Fill (borderLength + sumPadding.Bottom); + } + } else { + X = savedPanel.X; + childContentView.X = borderLength + sumPadding.Left; + Y = savedPanel.Y; + childContentView.Y = borderLength + sumPadding.Top; + Width = savedPanel.Width; + Height = savedPanel.Height; + if (Width is Dim.DimFill) { + var margin = -savedPanel.Width.Anchor (0); + childContentView.Width = Dim.Fill (margin + borderLength + sumPadding.Right); + } else { + childContentView.Width = Dim.Fill (borderLength + sumPadding.Right); + } + if (Height is Dim.DimFill) { + var margin = -savedPanel.Height.Anchor (0); + childContentView.Height = Dim.Fill (margin + borderLength + sumPadding.Bottom); + } else { + childContentView.Height = Dim.Fill (borderLength + sumPadding.Bottom); + } + } + Visible = Child.Visible; + } else { + Visible = false; + } + } + + /// + public override void Add (View view) + { + if (Child != null) { + Child = null; + } + Child = view; + } + + /// + public override void Remove (View view) + { + if (view == childContentView) { + base.Remove (view); + return; + } + childContentView.Remove (view); + if (Child != null) { + Child = null; + } + } + + /// + public override void RemoveAll () + { + if (Child != null) { + Child = null; + } + } + + /// + public override void Redraw (Rect bounds) + { + if (!NeedDisplay.IsEmpty) { + Driver.SetAttribute (Child.GetNormalColor ()); + Border.DrawContent (); + } + var savedClip = childContentView.ClipToBounds (); + childContentView.Redraw (childContentView.Bounds); + Driver.Clip = savedClip; + + ClearLayoutNeeded (); + ClearNeedsDisplay (); + } + } +} \ No newline at end of file diff --git a/Terminal.Gui/Windows/Dialog.cs b/Terminal.Gui/Windows/Dialog.cs index b75b7c173..b51e5a8ae 100644 --- a/Terminal.Gui/Windows/Dialog.cs +++ b/Terminal.Gui/Windows/Dialog.cs @@ -54,6 +54,7 @@ namespace Terminal.Gui { ColorScheme = Colors.Dialog; Modal = true; + Border.Effect3D = true; if (buttons != null) { foreach (var b in buttons) { diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs index 62b49ff89..aa3328c4b 100644 --- a/UICatalog/Scenarios/BasicColors.cs +++ b/UICatalog/Scenarios/BasicColors.cs @@ -43,6 +43,56 @@ namespace UICatalog { x = 30; y++; } + + Win.Add (new Label ("Mouse over to get the view color:") { + X = Pos.AnchorEnd (35) + }); + Win.Add (new Label ("Foreground:") { + X = Pos.AnchorEnd (34), + Y = 2 + }); + + var lblForeground = new Label () { + X = Pos.AnchorEnd (20), + Y = 2 + }; + Win.Add (lblForeground); + + var viewForeground = new View (" ") { + X = Pos.AnchorEnd (2), + Y = 2, + ColorScheme = new ColorScheme () + }; + Win.Add (viewForeground); + + Win.Add (new Label ("Background:") { + X = Pos.AnchorEnd (34), + Y = 4 + }); + + var lblBackground = new Label () { + X = Pos.AnchorEnd (20), + Y = 4 + }; + Win.Add (lblBackground); + + var viewBackground = new View (" ") { + X = Pos.AnchorEnd (2), + Y = 4, + ColorScheme = new ColorScheme () + }; + Win.Add (viewBackground); + + Application.RootMouseEvent = (e) => { + if (e.View != null) { + var colorValue = e.View.GetNormalColor ().Value; + Application.Driver.GetColors (colorValue, out Color fore, out Color back); + lblForeground.Text = fore.ToString (); + viewForeground.ColorScheme.Normal = new Attribute (fore, fore); + lblBackground.Text = back.ToString (); + viewBackground.ColorScheme.Normal = new Attribute (back, back); + } + }; } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/Borders.cs b/UICatalog/Scenarios/Borders.cs new file mode 100644 index 000000000..e5f6ebe08 --- /dev/null +++ b/UICatalog/Scenarios/Borders.cs @@ -0,0 +1,466 @@ +using System; +using System.Globalization; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Borders with/without PanelView", Description: "Demonstrate with/without PanelView borders manipulation.")] + [ScenarioCategory ("Border")] + class Borders : Scenario { + public override void Setup () + { + var borderStyle = BorderStyle.Single; + var drawMarginFrame = true; + var borderThickness = new Thickness (2); + var borderBrush = Color.Red; + var padding = new Thickness (2); + var background = Color.BrightGreen; + var effect3D = true; + + var smartPanel = new PanelView () { + X = Pos.Center () - 38, + Y = Pos.Center () - 3, + Width = 24, + Height = 13 + }; + smartPanel.Add (new Label () { // Or smartPanel.Child = + X = Pos.Center () - 38, + Y = Pos.Center () - 3, + Width = 24, + Height = 13, + Border = new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }, + ColorScheme = Colors.TopLevel, + Text = "This is a test\nwith a \nPanelView", + TextAlignment = TextAlignment.Centered + }); + + // Can be initialized this way too. + + //var smartPanel = new PanelView (new Label () { + // X = Pos.Center () - 38, + // Y = Pos.Center () - 3, + // Width = 24, + // Height = 13, + // Border = new Border () { + // BorderStyle = borderStyle, + // DrawMarginFrame = drawMarginFrame, + // BorderThickness = borderThickness, + // BorderBrush = borderBrush, + // Padding = padding, + // Background = background, + // Effect3D = effect3D + // }, + // ColorScheme = Colors.TopLevel, + // Text = "This is a test\nwith a \nPanelView", + // TextAlignment = TextAlignment.Centered + //}) { + // X = Pos.Center () - 38, + // Y = Pos.Center () - 3, + // Width = 24, + // Height = 13 + //}; + + var smartView = new Label () { + X = Pos.Center () + 10, + Y = Pos.Center () + 2, + Border = new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }, + ColorScheme = Colors.TopLevel, + Text = "This is a test\nwithout a \nPanelView", + TextAlignment = TextAlignment.Centered + }; + smartView.Border.Child = smartView; + + Win.Add (new Label ("Padding:") { + X = Pos.Center () - 23, + }); + + var paddingTopEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 1, + Width = 5 + }; + paddingTopEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.Padding = new Thickness (smartPanel.Child.Border.Padding.Left, + int.Parse (e.NewText.ToString ()), smartPanel.Child.Border.Padding.Right, + smartPanel.Child.Border.Padding.Bottom); + + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingTopEdit.Text = $"{smartView.Border.Padding.Top}"; + + Win.Add (paddingTopEdit); + + var paddingLeftEdit = new TextField ("") { + X = Pos.Center () - 30, + Y = 2, + Width = 5 + }; + paddingLeftEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.Padding = new Thickness (int.Parse (e.NewText.ToString ()), + smartPanel.Child.Border.Padding.Top, smartPanel.Child.Border.Padding.Right, + smartPanel.Child.Border.Padding.Bottom); + + smartView.Border.Padding = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingLeftEdit.Text = $"{smartView.Border.Padding.Left}"; + Win.Add (paddingLeftEdit); + + var paddingRightEdit = new TextField ("") { + X = Pos.Center () - 15, + Y = 2, + Width = 5 + }; + paddingRightEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.Padding = new Thickness (smartPanel.Child.Border.Padding.Left, + smartPanel.Child.Border.Padding.Top, int.Parse (e.NewText.ToString ()), + smartPanel.Child.Border.Padding.Bottom); + + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingRightEdit.Text = $"{smartView.Border.Padding.Right}"; + Win.Add (paddingRightEdit); + + var paddingBottomEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 3, + Width = 5 + }; + paddingBottomEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.Padding = new Thickness (smartPanel.Child.Border.Padding.Left, + smartPanel.Child.Border.Padding.Top, smartPanel.Child.Border.Padding.Right, + int.Parse (e.NewText.ToString ())); + + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingBottomEdit.Text = $"{smartView.Border.Padding.Bottom}"; + Win.Add (paddingBottomEdit); + + var replacePadding = new Button ("Replace all based on top") { + X = Pos.Center () - 35, + Y = 5 + }; + replacePadding.Clicked += () => { + smartPanel.Child.Border.Padding = new Thickness (smartPanel.Child.Border.Padding.Top); + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Top); + if (paddingTopEdit.Text.IsEmpty) { + paddingTopEdit.Text = "0"; + } + paddingBottomEdit.Text = paddingLeftEdit.Text = paddingRightEdit.Text = paddingTopEdit.Text; + }; + Win.Add (replacePadding); + + var cbUseUsePanelFrame = new CheckBox ("UsePanelFrame") { + X = Pos.X (replacePadding), + Y = Pos.Y (replacePadding) + 3, + Checked = smartPanel.UsePanelFrame + }; + cbUseUsePanelFrame.Toggled += (e) => smartPanel.UsePanelFrame = !e; + Win.Add (cbUseUsePanelFrame); + + Win.Add (new Label ("Border:") { + X = Pos.Center () + 11, + }); + + var borderTopEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 1, + Width = 5 + }; + borderTopEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.BorderThickness = new Thickness (smartPanel.Child.Border.BorderThickness.Left, + int.Parse (e.NewText.ToString ()), smartPanel.Child.Border.BorderThickness.Right, + smartPanel.Child.Border.BorderThickness.Bottom); + + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderTopEdit.Text = $"{smartView.Border.BorderThickness.Top}"; + + Win.Add (borderTopEdit); + + var borderLeftEdit = new TextField ("") { + X = Pos.Center () + 5, + Y = 2, + Width = 5 + }; + borderLeftEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.BorderThickness = new Thickness (int.Parse (e.NewText.ToString ()), + smartPanel.Child.Border.BorderThickness.Top, smartPanel.Child.Border.BorderThickness.Right, + smartPanel.Child.Border.BorderThickness.Bottom); + + smartView.Border.BorderThickness = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderLeftEdit.Text = $"{smartView.Border.BorderThickness.Left}"; + Win.Add (borderLeftEdit); + + var borderRightEdit = new TextField ("") { + X = Pos.Center () + 19, + Y = 2, + Width = 5 + }; + borderRightEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.BorderThickness = new Thickness (smartPanel.Child.Border.BorderThickness.Left, + smartPanel.Child.Border.BorderThickness.Top, int.Parse (e.NewText.ToString ()), + smartPanel.Child.Border.BorderThickness.Bottom); + + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderRightEdit.Text = $"{smartView.Border.BorderThickness.Right}"; + Win.Add (borderRightEdit); + + var borderBottomEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 3, + Width = 5 + }; + borderBottomEdit.TextChanging += (e) => { + try { + smartPanel.Child.Border.BorderThickness = new Thickness (smartPanel.Child.Border.BorderThickness.Left, + smartPanel.Child.Border.BorderThickness.Top, smartPanel.Child.Border.BorderThickness.Right, + int.Parse (e.NewText.ToString ())); + + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderBottomEdit.Text = $"{smartView.Border.BorderThickness.Bottom}"; + Win.Add (borderBottomEdit); + + var replaceBorder = new Button ("Replace all based on top") { + X = Pos.Center () + 1, + Y = 5 + }; + replaceBorder.Clicked += () => { + smartPanel.Child.Border.BorderThickness = new Thickness (smartPanel.Child.Border.BorderThickness.Top); + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Top); + if (borderTopEdit.Text.IsEmpty) { + borderTopEdit.Text = "0"; + } + borderBottomEdit.Text = borderLeftEdit.Text = borderRightEdit.Text = borderTopEdit.Text; + }; + Win.Add (replaceBorder); + + Win.Add (new Label ("BorderStyle:")); + + var borderStyleEnum = Enum.GetValues (typeof (BorderStyle)).Cast ().ToList (); + var rbBorderStyle = new RadioGroup (borderStyleEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 1, + SelectedItem = (int)smartView.Border.BorderStyle + }; + Win.Add (rbBorderStyle); + + var cbDrawMarginFrame = new CheckBox ("Draw Margin Frame", smartView.Border.DrawMarginFrame) { + X = Pos.AnchorEnd (20), + Y = 0, + Width = 5 + }; + cbDrawMarginFrame.Toggled += (e) => { + try { + smartPanel.Child.Border.DrawMarginFrame = cbDrawMarginFrame.Checked; + smartView.Border.DrawMarginFrame = cbDrawMarginFrame.Checked; + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + } catch { } + }; + Win.Add (cbDrawMarginFrame); + + rbBorderStyle.SelectedItemChanged += (e) => { + smartPanel.Child.Border.BorderStyle = (BorderStyle)e.SelectedItem; + smartView.Border.BorderStyle = (BorderStyle)e.SelectedItem; + smartView.SetNeedsDisplay (); + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + }; + + var cbEffect3D = new CheckBox ("Draw 3D effects", smartView.Border.Effect3D) { + X = Pos.AnchorEnd (20), + Y = 1, + Width = 5 + }; + Win.Add (cbEffect3D); + + Win.Add (new Label ("Effect3D Offset:") { + X = Pos.AnchorEnd (20), + Y = 2 + }); + Win.Add (new Label ("X:") { + X = Pos.AnchorEnd (19), + Y = 3 + }); + + var effect3DOffsetX = new TextField ("") { + X = Pos.AnchorEnd (16), + Y = 3, + Width = 5 + }; + effect3DOffsetX.TextChanging += (e) => { + try { + smartPanel.Child.Border.Effect3DOffset = new Point (int.Parse (e.NewText.ToString ()), + smartPanel.Child.Border.Effect3DOffset.Y); + + smartView.Border.Effect3DOffset = new Point (int.Parse (e.NewText.ToString ()), + smartView.Border.Effect3DOffset.Y); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetX.Text = $"{smartView.Border.Effect3DOffset.X}"; + Win.Add (effect3DOffsetX); + + Win.Add (new Label ("Y:") { + X = Pos.AnchorEnd (10), + Y = 3 + }); + + var effect3DOffsetY = new TextField ("") { + X = Pos.AnchorEnd (7), + Y = 3, + Width = 5 + }; + effect3DOffsetY.TextChanging += (e) => { + try { + smartPanel.Child.Border.Effect3DOffset = new Point (smartPanel.Child.Border.Effect3DOffset.X, + int.Parse (e.NewText.ToString ())); + + smartView.Border.Effect3DOffset = new Point (smartView.Border.Effect3DOffset.X, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetY.Text = $"{smartView.Border.Effect3DOffset.Y}"; + Win.Add (effect3DOffsetY); + + cbEffect3D.Toggled += (e) => { + try { + smartPanel.Child.Border.Effect3D = smartView.Border.Effect3D = effect3DOffsetX.Enabled = + effect3DOffsetY.Enabled = cbEffect3D.Checked; + } catch { } + }; + + Win.Add (new Label ("Background:") { + Y = 5 + }); + + var colorEnum = Enum.GetValues (typeof (Color)).Cast ().ToList (); + var rbBackground = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 6, + SelectedItem = (int)smartView.Border.Background + }; + rbBackground.SelectedItemChanged += (e) => { + smartPanel.Child.Border.Background = smartView.Border.Background = (Color)e.SelectedItem; + }; + Win.Add (rbBackground); + + Win.Add (new Label ("BorderBrush:") { + X = Pos.AnchorEnd (20), + Y = 5 + }); + + var rbBorderBrush = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = Pos.AnchorEnd (18), + Y = 6, + SelectedItem = (int)smartView.Border.BorderBrush + }; + rbBorderBrush.SelectedItemChanged += (e) => { + smartPanel.Child.Border.BorderBrush = smartView.Border.BorderBrush = (Color)e.SelectedItem; + }; + Win.Add (rbBorderBrush); + + Win.Add (smartPanel); + Win.Add (smartView); + + Win.BringSubviewToFront (smartPanel); + } + } +} \ No newline at end of file diff --git a/UICatalog/Scenarios/BordersComparisons.cs b/UICatalog/Scenarios/BordersComparisons.cs new file mode 100644 index 000000000..988f41b41 --- /dev/null +++ b/UICatalog/Scenarios/BordersComparisons.cs @@ -0,0 +1,144 @@ +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Borders Comparisons", Description: "Compares Window, Toplevel and FrameView borders.")] + [ScenarioCategory ("Border")] + class BordersComparisons : Scenario { + public override void Init (Toplevel top, ColorScheme colorScheme) + { + top.Dispose (); + Application.Init (); + + top = Application.Top; + + var borderStyle = BorderStyle.Double; + var drawMarginFrame = false; + var borderThickness = new Thickness (1, 2, 3, 4); + var borderBrush = Colors.Base.HotFocus.Foreground; + var padding = new Thickness (1, 2, 3, 4); + var background = Colors.Base.HotNormal.Foreground; + var effect3D = true; + + var win = new Window (new Rect (5, 5, 40, 20), "Test", 8, + new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }); + + var tf1 = new TextField ("1234567890") { Width = 10 }; + + var button = new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + }; + button.Clicked += () => MessageBox.Query (20, 7, "Hi", "I'm a Window?", "Yes", "No"); + var label = new Label ("I'm a Window") { + X = Pos.Center (), + Y = Pos.Center () - 3, + }; + var tf2 = new TextField ("1234567890") { + X = Pos.AnchorEnd (10), + Y = Pos.AnchorEnd (1), + Width = 10 + }; + var tv = new TextView () { + Y = Pos.AnchorEnd (2), + Width = 10, + Height = Dim.Fill (), + ColorScheme = Colors.Dialog, + Text = "1234567890" + }; + win.Add (tf1, button, label, tf2, tv); + top.Add (win); + + var top2 = new Border.ToplevelContainer (new Rect (50, 5, 40, 20), + new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }, + "Test2") { + ColorScheme = Colors.Base, + }; + + var tf3 = new TextField ("1234567890") { Width = 10 }; + + var button2 = new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + }; + button2.Clicked += () => MessageBox.Query (20, 7, "Hi", "I'm a Toplevel?", "Yes", "No"); + var label2 = new Label ("I'm a Toplevel") { + X = Pos.Center (), + Y = Pos.Center () - 3, + }; + var tf4 = new TextField ("1234567890") { + X = Pos.AnchorEnd (10), + Y = Pos.AnchorEnd (1), + Width = 10 + }; + var tv2 = new TextView () { + Y = Pos.AnchorEnd (2), + Width = 10, + Height = Dim.Fill (), + ColorScheme = Colors.Dialog, + Text = "1234567890" + }; + top2.Add (tf3, button2, label2, tf4, tv2); + top.Add (top2); + + var frm = new FrameView (new Rect (95, 5, 40, 20), "Test3", null, + new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }) { ColorScheme = Colors.Base }; + + var tf5 = new TextField ("1234567890") { Width = 10 }; + + var button3 = new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + }; + button3.Clicked += () => MessageBox.Query (20, 7, "Hi", "I'm a FrameView?", "Yes", "No"); + var label3 = new Label ("I'm a FrameView") { + X = Pos.Center (), + Y = Pos.Center () - 3, + }; + var tf6 = new TextField ("1234567890") { + X = Pos.AnchorEnd (10), + Y = Pos.AnchorEnd (1), + Width = 10 + }; + var tv3 = new TextView () { + Y = Pos.AnchorEnd (2), + Width = 10, + Height = Dim.Fill (), + ColorScheme = Colors.Dialog, + Text = "1234567890" + }; + frm.Add (tf5, button3, label3, tf6, tv3); + top.Add (frm); + + Application.Run (); + } + + public override void Run () + { + // Do nothing + } + } +} \ No newline at end of file diff --git a/UICatalog/Scenarios/BordersOnFrameView.cs b/UICatalog/Scenarios/BordersOnFrameView.cs new file mode 100644 index 000000000..bd4838b73 --- /dev/null +++ b/UICatalog/Scenarios/BordersOnFrameView.cs @@ -0,0 +1,386 @@ +using System; +using System.Globalization; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Borders on FrameView", Description: "Demonstrate FrameView borders manipulation.")] + [ScenarioCategory ("Border")] + class BordersOnFrameView : Scenario { + public override void Setup () + { + var borderStyle = BorderStyle.Double; + var drawMarginFrame = false; + var borderThickness = new Thickness (1, 2, 3, 4); + var borderBrush = Colors.Base.HotFocus.Foreground; + var padding = new Thickness (1, 2, 3, 4); + var background = Colors.Base.HotNormal.Foreground; + var effect3D = true; + + var smartView = new FrameView () { + X = Pos.Center (), + Y = Pos.Center () - 7, + Width = 40, + Height = 20, + Border = new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }, + ColorScheme = Colors.TopLevel + }; + + var tf1 = new TextField ("1234567890") { Width = 10 }; + + var button = new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + }; + button.Clicked += () => MessageBox.Query (20, 7, "Hi", "I'm a FrameView?", "Yes", "No"); + var label = new Label ("I'm a FrameView") { + X = Pos.Center (), + Y = Pos.Center () - 3, + }; + var tf2 = new TextField ("1234567890") { + X = Pos.AnchorEnd (10), + Y = Pos.AnchorEnd (1), + Width = 10 + }; + var tv = new TextView () { + Y = Pos.AnchorEnd (2), + Width = 10, + Height = Dim.Fill (), + ColorScheme = Colors.Dialog, + Text = "1234567890" + }; + smartView.Add (tf1, button, label, tf2, tv); + + Win.Add (new Label ("Padding:") { + X = Pos.Center () - 23, + }); + + var paddingTopEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 1, + Width = 5 + }; + paddingTopEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingTopEdit.Text = $"{smartView.Border.Padding.Top}"; + + Win.Add (paddingTopEdit); + + var paddingLeftEdit = new TextField ("") { + X = Pos.Center () - 30, + Y = 2, + Width = 5 + }; + paddingLeftEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingLeftEdit.Text = $"{smartView.Border.Padding.Left}"; + Win.Add (paddingLeftEdit); + + var paddingRightEdit = new TextField ("") { + X = Pos.Center () - 15, + Y = 2, + Width = 5 + }; + paddingRightEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingRightEdit.Text = $"{smartView.Border.Padding.Right}"; + Win.Add (paddingRightEdit); + + var paddingBottomEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 3, + Width = 5 + }; + paddingBottomEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingBottomEdit.Text = $"{smartView.Border.Padding.Bottom}"; + Win.Add (paddingBottomEdit); + + var replacePadding = new Button ("Replace all based on top") { + X = Pos.Center () - 35, + Y = 5 + }; + replacePadding.Clicked += () => { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Top); + if (paddingTopEdit.Text.IsEmpty) { + paddingTopEdit.Text = "0"; + } + paddingBottomEdit.Text = paddingLeftEdit.Text = paddingRightEdit.Text = paddingTopEdit.Text; + }; + Win.Add (replacePadding); + + Win.Add (new Label ("Border:") { + X = Pos.Center () + 11, + }); + + var borderTopEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 1, + Width = 5 + }; + borderTopEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderTopEdit.Text = $"{smartView.Border.BorderThickness.Top}"; + + Win.Add (borderTopEdit); + + var borderLeftEdit = new TextField ("") { + X = Pos.Center () + 5, + Y = 2, + Width = 5 + }; + borderLeftEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderLeftEdit.Text = $"{smartView.Border.BorderThickness.Left}"; + Win.Add (borderLeftEdit); + + var borderRightEdit = new TextField ("") { + X = Pos.Center () + 19, + Y = 2, + Width = 5 + }; + borderRightEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderRightEdit.Text = $"{smartView.Border.BorderThickness.Right}"; + Win.Add (borderRightEdit); + + var borderBottomEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 3, + Width = 5 + }; + borderBottomEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderBottomEdit.Text = $"{smartView.Border.BorderThickness.Bottom}"; + Win.Add (borderBottomEdit); + + var replaceBorder = new Button ("Replace all based on top") { + X = Pos.Center () + 1, + Y = 5 + }; + replaceBorder.Clicked += () => { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Top); + if (borderTopEdit.Text.IsEmpty) { + borderTopEdit.Text = "0"; + } + borderBottomEdit.Text = borderLeftEdit.Text = borderRightEdit.Text = borderTopEdit.Text; + }; + Win.Add (replaceBorder); + + Win.Add (new Label ("BorderStyle:")); + + var borderStyleEnum = Enum.GetValues (typeof (BorderStyle)).Cast ().ToList (); + var rbBorderStyle = new RadioGroup (borderStyleEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 1, + SelectedItem = (int)smartView.Border.BorderStyle + }; + Win.Add (rbBorderStyle); + + var cbDrawMarginFrame = new CheckBox ("Draw Margin Frame", smartView.Border.DrawMarginFrame) { + X = Pos.AnchorEnd (20), + Y = 0, + Width = 5 + }; + cbDrawMarginFrame.Toggled += (e) => { + try { + smartView.Border.DrawMarginFrame = cbDrawMarginFrame.Checked; + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + } catch { } + }; + Win.Add (cbDrawMarginFrame); + + rbBorderStyle.SelectedItemChanged += (e) => { + smartView.Border.BorderStyle = (BorderStyle)e.SelectedItem; + smartView.SetNeedsDisplay (); + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + }; + + var cbEffect3D = new CheckBox ("Draw 3D effects", smartView.Border.Effect3D) { + X = Pos.AnchorEnd (20), + Y = 1, + Width = 5 + }; + Win.Add (cbEffect3D); + + Win.Add (new Label ("Effect3D Offset:") { + X = Pos.AnchorEnd (20), + Y = 2 + }); + Win.Add (new Label ("X:") { + X = Pos.AnchorEnd (19), + Y = 3 + }); + + var effect3DOffsetX = new TextField ("") { + X = Pos.AnchorEnd (16), + Y = 3, + Width = 5 + }; + effect3DOffsetX.TextChanging += (e) => { + try { + smartView.Border.Effect3DOffset = new Point (int.Parse (e.NewText.ToString ()), + smartView.Border.Effect3DOffset.Y); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetX.Text = $"{smartView.Border.Effect3DOffset.X}"; + Win.Add (effect3DOffsetX); + + Win.Add (new Label ("Y:") { + X = Pos.AnchorEnd (10), + Y = 3 + }); + + var effect3DOffsetY = new TextField ("") { + X = Pos.AnchorEnd (7), + Y = 3, + Width = 5 + }; + effect3DOffsetY.TextChanging += (e) => { + try { + smartView.Border.Effect3DOffset = new Point (smartView.Border.Effect3DOffset.X, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetY.Text = $"{smartView.Border.Effect3DOffset.Y}"; + Win.Add (effect3DOffsetY); + + cbEffect3D.Toggled += (e) => { + try { + smartView.Border.Effect3D = effect3DOffsetX.Enabled = + effect3DOffsetY.Enabled = cbEffect3D.Checked; + } catch { } + }; + + Win.Add (new Label ("Background:") { + Y = 5 + }); + + var colorEnum = Enum.GetValues (typeof (Color)).Cast ().ToList (); + var rbBackground = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 6, + SelectedItem = (int)smartView.Border.Background + }; + rbBackground.SelectedItemChanged += (e) => { + smartView.Border.Background = (Color)e.SelectedItem; + }; + Win.Add (rbBackground); + + Win.Add (new Label ("BorderBrush:") { + X = Pos.AnchorEnd (20), + Y = 5 + }); + + var rbBorderBrush = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = Pos.AnchorEnd (18), + Y = 6, + SelectedItem = (int)smartView.Border.BorderBrush + }; + rbBorderBrush.SelectedItemChanged += (e) => { + smartView.Border.BorderBrush = (Color)e.SelectedItem; + }; + Win.Add (rbBorderBrush); + + Win.Add (smartView); + } + } +} \ No newline at end of file diff --git a/UICatalog/Scenarios/BordersOnToplevel.cs b/UICatalog/Scenarios/BordersOnToplevel.cs new file mode 100644 index 000000000..6ee1de308 --- /dev/null +++ b/UICatalog/Scenarios/BordersOnToplevel.cs @@ -0,0 +1,386 @@ +using System; +using System.Globalization; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Borders on Toplevel", Description: "Demonstrate Toplevel borders manipulation.")] + [ScenarioCategory ("Border")] + class BordersOnToplevel : Scenario { + public override void Setup () + { + var borderStyle = BorderStyle.Double; + var drawMarginFrame = false; + var borderThickness = new Thickness (1, 2, 3, 4); + var borderBrush = Colors.Base.HotFocus.Foreground; + var padding = new Thickness (1, 2, 3, 4); + var background = Colors.Base.HotNormal.Foreground; + var effect3D = true; + + var smartView = new Border.ToplevelContainer () { + X = Pos.Center (), + Y = Pos.Center () - 7, + Width = 40, + Height = 20, + Border = new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }, + ColorScheme = Colors.TopLevel + }; + + var tf1 = new TextField ("1234567890") { Width = 10 }; + + var button = new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + }; + button.Clicked += () => MessageBox.Query (20, 7, "Hi", "I'm a Toplevel?", "Yes", "No"); + var label = new Label ("I'm a Toplevel") { + X = Pos.Center (), + Y = Pos.Center () - 3, + }; + var tf2 = new TextField ("1234567890") { + X = Pos.AnchorEnd (10), + Y = Pos.AnchorEnd (1), + Width = 10 + }; + var tv = new TextView () { + Y = Pos.AnchorEnd (2), + Width = 10, + Height = Dim.Fill (), + ColorScheme = Colors.Dialog, + Text = "1234567890" + }; + smartView.Add (tf1, button, label, tf2, tv); + + Win.Add (new Label ("Padding:") { + X = Pos.Center () - 23, + }); + + var paddingTopEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 1, + Width = 5 + }; + paddingTopEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingTopEdit.Text = $"{smartView.Border.Padding.Top}"; + + Win.Add (paddingTopEdit); + + var paddingLeftEdit = new TextField ("") { + X = Pos.Center () - 30, + Y = 2, + Width = 5 + }; + paddingLeftEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingLeftEdit.Text = $"{smartView.Border.Padding.Left}"; + Win.Add (paddingLeftEdit); + + var paddingRightEdit = new TextField ("") { + X = Pos.Center () - 15, + Y = 2, + Width = 5 + }; + paddingRightEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingRightEdit.Text = $"{smartView.Border.Padding.Right}"; + Win.Add (paddingRightEdit); + + var paddingBottomEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 3, + Width = 5 + }; + paddingBottomEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingBottomEdit.Text = $"{smartView.Border.Padding.Bottom}"; + Win.Add (paddingBottomEdit); + + var replacePadding = new Button ("Replace all based on top") { + X = Pos.Center () - 35, + Y = 5 + }; + replacePadding.Clicked += () => { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Top); + if (paddingTopEdit.Text.IsEmpty) { + paddingTopEdit.Text = "0"; + } + paddingBottomEdit.Text = paddingLeftEdit.Text = paddingRightEdit.Text = paddingTopEdit.Text; + }; + Win.Add (replacePadding); + + Win.Add (new Label ("Border:") { + X = Pos.Center () + 11, + }); + + var borderTopEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 1, + Width = 5 + }; + borderTopEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderTopEdit.Text = $"{smartView.Border.BorderThickness.Top}"; + + Win.Add (borderTopEdit); + + var borderLeftEdit = new TextField ("") { + X = Pos.Center () + 5, + Y = 2, + Width = 5 + }; + borderLeftEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderLeftEdit.Text = $"{smartView.Border.BorderThickness.Left}"; + Win.Add (borderLeftEdit); + + var borderRightEdit = new TextField ("") { + X = Pos.Center () + 19, + Y = 2, + Width = 5 + }; + borderRightEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderRightEdit.Text = $"{smartView.Border.BorderThickness.Right}"; + Win.Add (borderRightEdit); + + var borderBottomEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 3, + Width = 5 + }; + borderBottomEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderBottomEdit.Text = $"{smartView.Border.BorderThickness.Bottom}"; + Win.Add (borderBottomEdit); + + var replaceBorder = new Button ("Replace all based on top") { + X = Pos.Center () + 1, + Y = 5 + }; + replaceBorder.Clicked += () => { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Top); + if (borderTopEdit.Text.IsEmpty) { + borderTopEdit.Text = "0"; + } + borderBottomEdit.Text = borderLeftEdit.Text = borderRightEdit.Text = borderTopEdit.Text; + }; + Win.Add (replaceBorder); + + Win.Add (new Label ("BorderStyle:")); + + var borderStyleEnum = Enum.GetValues (typeof (BorderStyle)).Cast ().ToList (); + var rbBorderStyle = new RadioGroup (borderStyleEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 1, + SelectedItem = (int)smartView.Border.BorderStyle + }; + Win.Add (rbBorderStyle); + + var cbDrawMarginFrame = new CheckBox ("Draw Margin Frame", smartView.Border.DrawMarginFrame) { + X = Pos.AnchorEnd (20), + Y = 0, + Width = 5 + }; + cbDrawMarginFrame.Toggled += (e) => { + try { + smartView.Border.DrawMarginFrame = cbDrawMarginFrame.Checked; + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + } catch { } + }; + Win.Add (cbDrawMarginFrame); + + rbBorderStyle.SelectedItemChanged += (e) => { + smartView.Border.BorderStyle = (BorderStyle)e.SelectedItem; + smartView.SetNeedsDisplay (); + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + }; + + var cbEffect3D = new CheckBox ("Draw 3D effects", smartView.Border.Effect3D) { + X = Pos.AnchorEnd (20), + Y = 1, + Width = 5 + }; + Win.Add (cbEffect3D); + + Win.Add (new Label ("Effect3D Offset:") { + X = Pos.AnchorEnd (20), + Y = 2 + }); + Win.Add (new Label ("X:") { + X = Pos.AnchorEnd (19), + Y = 3 + }); + + var effect3DOffsetX = new TextField ("") { + X = Pos.AnchorEnd (16), + Y = 3, + Width = 5 + }; + effect3DOffsetX.TextChanging += (e) => { + try { + smartView.Border.Effect3DOffset = new Point (int.Parse (e.NewText.ToString ()), + smartView.Border.Effect3DOffset.Y); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetX.Text = $"{smartView.Border.Effect3DOffset.X}"; + Win.Add (effect3DOffsetX); + + Win.Add (new Label ("Y:") { + X = Pos.AnchorEnd (10), + Y = 3 + }); + + var effect3DOffsetY = new TextField ("") { + X = Pos.AnchorEnd (7), + Y = 3, + Width = 5 + }; + effect3DOffsetY.TextChanging += (e) => { + try { + smartView.Border.Effect3DOffset = new Point (smartView.Border.Effect3DOffset.X, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetY.Text = $"{smartView.Border.Effect3DOffset.Y}"; + Win.Add (effect3DOffsetY); + + cbEffect3D.Toggled += (e) => { + try { + smartView.Border.Effect3D = effect3DOffsetX.Enabled = + effect3DOffsetY.Enabled = cbEffect3D.Checked; + } catch { } + }; + + Win.Add (new Label ("Background:") { + Y = 5 + }); + + var colorEnum = Enum.GetValues (typeof (Color)).Cast ().ToList (); + var rbBackground = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 6, + SelectedItem = (int)smartView.Border.Background + }; + rbBackground.SelectedItemChanged += (e) => { + smartView.Border.Background = (Color)e.SelectedItem; + }; + Win.Add (rbBackground); + + Win.Add (new Label ("BorderBrush:") { + X = Pos.AnchorEnd (20), + Y = 5 + }); + + var rbBorderBrush = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = Pos.AnchorEnd (18), + Y = 6, + SelectedItem = (int)smartView.Border.BorderBrush + }; + rbBorderBrush.SelectedItemChanged += (e) => { + smartView.Border.BorderBrush = (Color)e.SelectedItem; + }; + Win.Add (rbBorderBrush); + + Win.Add (smartView); + } + } +} \ No newline at end of file diff --git a/UICatalog/Scenarios/BordersOnWindow.cs b/UICatalog/Scenarios/BordersOnWindow.cs new file mode 100644 index 000000000..2b96ffb65 --- /dev/null +++ b/UICatalog/Scenarios/BordersOnWindow.cs @@ -0,0 +1,386 @@ +using System; +using System.Globalization; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Borders on Window", Description: "Demonstrate Window borders manipulation.")] + [ScenarioCategory ("Border")] + class BordersOnWindow : Scenario { + public override void Setup () + { + var borderStyle = BorderStyle.Double; + var drawMarginFrame = false; + var borderThickness = new Thickness (1, 2, 3, 4); + var borderBrush = Colors.Base.HotFocus.Foreground; + var padding = new Thickness (1, 2, 3, 4); + var background = Colors.Base.HotNormal.Foreground; + var effect3D = true; + + var smartView = new Window () { + X = Pos.Center (), + Y = Pos.Center () - 7, + Width = 40, + Height = 20, + Border = new Border () { + BorderStyle = borderStyle, + DrawMarginFrame = drawMarginFrame, + BorderThickness = borderThickness, + BorderBrush = borderBrush, + Padding = padding, + Background = background, + Effect3D = effect3D + }, + ColorScheme = Colors.TopLevel + }; + + var tf1 = new TextField ("1234567890") { Width = 10 }; + + var button = new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + }; + button.Clicked += () => MessageBox.Query (20, 7, "Hi", "I'm a Window?", "Yes", "No"); + var label = new Label ("I'm a Window") { + X = Pos.Center (), + Y = Pos.Center () - 3, + }; + var tf2 = new TextField ("1234567890") { + X = Pos.AnchorEnd (10), + Y = Pos.AnchorEnd (1), + Width = 10 + }; + var tv = new TextView () { + Y = Pos.AnchorEnd (2), + Width = 10, + Height = Dim.Fill (), + ColorScheme = Colors.Dialog, + Text = "1234567890" + }; + smartView.Add (tf1, button, label, tf2, tv); + + Win.Add (new Label ("Padding:") { + X = Pos.Center () - 23, + }); + + var paddingTopEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 1, + Width = 5 + }; + paddingTopEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingTopEdit.Text = $"{smartView.Border.Padding.Top}"; + + Win.Add (paddingTopEdit); + + var paddingLeftEdit = new TextField ("") { + X = Pos.Center () - 30, + Y = 2, + Width = 5 + }; + paddingLeftEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingLeftEdit.Text = $"{smartView.Border.Padding.Left}"; + Win.Add (paddingLeftEdit); + + var paddingRightEdit = new TextField ("") { + X = Pos.Center () - 15, + Y = 2, + Width = 5 + }; + paddingRightEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.Padding.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingRightEdit.Text = $"{smartView.Border.Padding.Right}"; + Win.Add (paddingRightEdit); + + var paddingBottomEdit = new TextField ("") { + X = Pos.Center () - 22, + Y = 3, + Width = 5 + }; + paddingBottomEdit.TextChanging += (e) => { + try { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Left, + smartView.Border.Padding.Top, smartView.Border.Padding.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + paddingBottomEdit.Text = $"{smartView.Border.Padding.Bottom}"; + Win.Add (paddingBottomEdit); + + var replacePadding = new Button ("Replace all based on top") { + X = Pos.Center () - 35, + Y = 5 + }; + replacePadding.Clicked += () => { + smartView.Border.Padding = new Thickness (smartView.Border.Padding.Top); + if (paddingTopEdit.Text.IsEmpty) { + paddingTopEdit.Text = "0"; + } + paddingBottomEdit.Text = paddingLeftEdit.Text = paddingRightEdit.Text = paddingTopEdit.Text; + }; + Win.Add (replacePadding); + + Win.Add (new Label ("Border:") { + X = Pos.Center () + 11, + }); + + var borderTopEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 1, + Width = 5 + }; + borderTopEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + int.Parse (e.NewText.ToString ()), smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderTopEdit.Text = $"{smartView.Border.BorderThickness.Top}"; + + Win.Add (borderTopEdit); + + var borderLeftEdit = new TextField ("") { + X = Pos.Center () + 5, + Y = 2, + Width = 5 + }; + borderLeftEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderLeftEdit.Text = $"{smartView.Border.BorderThickness.Left}"; + Win.Add (borderLeftEdit); + + var borderRightEdit = new TextField ("") { + X = Pos.Center () + 19, + Y = 2, + Width = 5 + }; + borderRightEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, int.Parse (e.NewText.ToString ()), + smartView.Border.BorderThickness.Bottom); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderRightEdit.Text = $"{smartView.Border.BorderThickness.Right}"; + Win.Add (borderRightEdit); + + var borderBottomEdit = new TextField ("") { + X = Pos.Center () + 12, + Y = 3, + Width = 5 + }; + borderBottomEdit.TextChanging += (e) => { + try { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Left, + smartView.Border.BorderThickness.Top, smartView.Border.BorderThickness.Right, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty) { + e.Cancel = true; + } + } + }; + borderBottomEdit.Text = $"{smartView.Border.BorderThickness.Bottom}"; + Win.Add (borderBottomEdit); + + var replaceBorder = new Button ("Replace all based on top") { + X = Pos.Center () + 1, + Y = 5 + }; + replaceBorder.Clicked += () => { + smartView.Border.BorderThickness = new Thickness (smartView.Border.BorderThickness.Top); + if (borderTopEdit.Text.IsEmpty) { + borderTopEdit.Text = "0"; + } + borderBottomEdit.Text = borderLeftEdit.Text = borderRightEdit.Text = borderTopEdit.Text; + }; + Win.Add (replaceBorder); + + Win.Add (new Label ("BorderStyle:")); + + var borderStyleEnum = Enum.GetValues (typeof (BorderStyle)).Cast ().ToList (); + var rbBorderStyle = new RadioGroup (borderStyleEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 1, + SelectedItem = (int)smartView.Border.BorderStyle + }; + Win.Add (rbBorderStyle); + + var cbDrawMarginFrame = new CheckBox ("Draw Margin Frame", smartView.Border.DrawMarginFrame) { + X = Pos.AnchorEnd (20), + Y = 0, + Width = 5 + }; + cbDrawMarginFrame.Toggled += (e) => { + try { + smartView.Border.DrawMarginFrame = cbDrawMarginFrame.Checked; + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + } catch { } + }; + Win.Add (cbDrawMarginFrame); + + rbBorderStyle.SelectedItemChanged += (e) => { + smartView.Border.BorderStyle = (BorderStyle)e.SelectedItem; + smartView.SetNeedsDisplay (); + if (cbDrawMarginFrame.Checked != smartView.Border.DrawMarginFrame) { + cbDrawMarginFrame.Checked = smartView.Border.DrawMarginFrame; + } + }; + + var cbEffect3D = new CheckBox ("Draw 3D effects", smartView.Border.Effect3D) { + X = Pos.AnchorEnd (20), + Y = 1, + Width = 5 + }; + Win.Add (cbEffect3D); + + Win.Add (new Label ("Effect3D Offset:") { + X = Pos.AnchorEnd (20), + Y = 2 + }); + Win.Add (new Label ("X:") { + X = Pos.AnchorEnd (19), + Y = 3 + }); + + var effect3DOffsetX = new TextField ("") { + X = Pos.AnchorEnd (16), + Y = 3, + Width = 5 + }; + effect3DOffsetX.TextChanging += (e) => { + try { + smartView.Border.Effect3DOffset = new Point (int.Parse (e.NewText.ToString ()), + smartView.Border.Effect3DOffset.Y); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetX.Text = $"{smartView.Border.Effect3DOffset.X}"; + Win.Add (effect3DOffsetX); + + Win.Add (new Label ("Y:") { + X = Pos.AnchorEnd (10), + Y = 3 + }); + + var effect3DOffsetY = new TextField ("") { + X = Pos.AnchorEnd (7), + Y = 3, + Width = 5 + }; + effect3DOffsetY.TextChanging += (e) => { + try { + smartView.Border.Effect3DOffset = new Point (smartView.Border.Effect3DOffset.X, + int.Parse (e.NewText.ToString ())); + } catch { + if (!e.NewText.IsEmpty && e.NewText != CultureInfo.CurrentCulture.NumberFormat.NegativeSign) { + e.Cancel = true; + } + } + }; + effect3DOffsetY.Text = $"{smartView.Border.Effect3DOffset.Y}"; + Win.Add (effect3DOffsetY); + + cbEffect3D.Toggled += (e) => { + try { + smartView.Border.Effect3D = effect3DOffsetX.Enabled = + effect3DOffsetY.Enabled = cbEffect3D.Checked; + } catch { } + }; + + Win.Add (new Label ("Background:") { + Y = 5 + }); + + var colorEnum = Enum.GetValues (typeof (Color)).Cast ().ToList (); + var rbBackground = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = 2, + Y = 6, + SelectedItem = (int)smartView.Border.Background + }; + rbBackground.SelectedItemChanged += (e) => { + smartView.Border.Background = (Color)e.SelectedItem; + }; + Win.Add (rbBackground); + + Win.Add (new Label ("BorderBrush:") { + X = Pos.AnchorEnd (20), + Y = 5 + }); + + var rbBorderBrush = new RadioGroup (colorEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + + X = Pos.AnchorEnd (18), + Y = 6, + SelectedItem = (int)smartView.Border.BorderBrush + }; + rbBorderBrush.SelectedItemChanged += (e) => { + smartView.Border.BorderBrush = (Color)e.SelectedItem; + }; + Win.Add (rbBorderBrush); + + Win.Add (smartView); + } + } +} \ No newline at end of file diff --git a/UnitTests/AttributeTests.cs b/UnitTests/AttributeTests.cs index 34877f628..7137e0878 100644 --- a/UnitTests/AttributeTests.cs +++ b/UnitTests/AttributeTests.cs @@ -43,6 +43,14 @@ namespace Terminal.Gui.ConsoleDrivers { Assert.Equal (fg, attr.Foreground); Assert.Equal (bg, attr.Background); + attr = new Attribute (fg); + Assert.Equal (fg, attr.Foreground); + Assert.Equal (fg, attr.Background); + + attr = new Attribute (bg); + Assert.Equal (bg, attr.Foreground); + Assert.Equal (bg, attr.Background); + driver.End (); Application.Shutdown (); } @@ -63,12 +71,12 @@ namespace Terminal.Gui.ConsoleDrivers { var bg = new Color (); bg = Color.Blue; - // Test converstion to int + // Test conversion to int attr = new Attribute (value, fg, bg); int value_implicit = (int)attr.Value; Assert.Equal (value, value_implicit); - // Test converstion from int + // Test conversion from int attr = value; Assert.Equal (value, attr.Value); @@ -143,5 +151,17 @@ namespace Terminal.Gui.ConsoleDrivers { driver.End (); Application.Shutdown (); } + + [Fact] + [AutoInitShutdown] + public void GetColors_Based_On_Value () + { + var driver = Application.Driver; + var attrValue = new Attribute (Color.Red, Color.Green).Value; + driver.GetColors (attrValue, out Color fg, out Color bg); + + Assert.Equal (Color.Red, fg); + Assert.Equal (Color.Green, bg); + } } } diff --git a/UnitTests/BorderTests.cs b/UnitTests/BorderTests.cs new file mode 100644 index 000000000..d65623136 --- /dev/null +++ b/UnitTests/BorderTests.cs @@ -0,0 +1,540 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Rune = System.Rune; + +namespace Terminal.Gui.Core { + public class BorderTests { + [Fact] + public void Constructor_Defaults () + { + var b = new Border (); + Assert.Equal (BorderStyle.None, b.BorderStyle); + Assert.False (b.DrawMarginFrame); + Assert.Equal (default, b.BorderThickness); + Assert.Equal (default, b.BorderBrush); + Assert.Equal (default, b.Background); + Assert.Equal (default, b.Padding); + Assert.Equal (0, b.ActualWidth); + Assert.Equal (0, b.ActualHeight); + Assert.Null (b.Child); + Assert.Null (b.ChildContainer); + Assert.False (b.Effect3D); + Assert.Equal (new Point (1, 1), b.Effect3DOffset); + Assert.Equal (Color.DarkGray, b.Effect3DBrush); + } + + [Fact] + public void BorderStyle_Different_None_Ensures_DrawMarginFrame_To_True () + { + var b = new Border () { + BorderStyle = BorderStyle.Single, + DrawMarginFrame = false + }; + + Assert.True (b.DrawMarginFrame); + + b.BorderStyle = BorderStyle.None; + Assert.True (b.DrawMarginFrame); + b.DrawMarginFrame = false; + Assert.False (b.DrawMarginFrame); + } + + [Fact] + public void ActualWidth_ActualHeight () + { + var v = new View (new Rect (5, 10, 60, 20), "", new Border ()); + + Assert.Equal (60, v.Border.ActualWidth); + Assert.Equal (20, v.Border.ActualHeight); + } + + [Fact] + public void ToplevelContainer_LayoutStyle_Computed_Constuctor_ () + { + var tc = new Border.ToplevelContainer (new Border ()); + + Assert.Equal (LayoutStyle.Computed, tc.LayoutStyle); + } + + [Fact] + public void ToplevelContainer_LayoutStyle_Absolute_Constuctor_ () + { + var tc = new Border.ToplevelContainer (new Rect (1, 2, 3, 4), new Border ()); + + Assert.Equal (LayoutStyle.Absolute, tc.LayoutStyle); + } + + [Fact] + public void GetSumThickness_Test () + { + var b = new Border () { + BorderThickness = new Thickness (1, 2, 3, 4), + Padding = new Thickness (4, 3, 2, 1) + }; + Assert.Equal (new Thickness (5, 5, 5, 5), b.GetSumThickness ()); + } + + [Fact] + [AutoInitShutdown] + public void DrawContent_With_Child_Border () + { + var top = Application.Top; + var driver = (FakeDriver)Application.Driver; + + var label = new Label () { + X = Pos.Center (), + Y = Pos.Center (), + Border = new Border () { + BorderStyle = BorderStyle.Single, + Padding = new Thickness (2), + BorderThickness = new Thickness (2), + BorderBrush = Color.Red, + Background = Color.BrightGreen, + Effect3D = true, + Effect3DOffset = new Point (2, -3) + }, + ColorScheme = Colors.TopLevel, + Text = "This is a test" + }; + label.Border.Child = label; + top.Add (label); + + top.LayoutSubviews (); + label.Redraw (label.Bounds); + + var frame = label.Frame; + var drawMarginFrame = label.Border.DrawMarginFrame ? 1 : 0; + var sumThickness = label.Border.GetSumThickness (); + var padding = label.Border.Padding; + var effect3DOffset = label.Border.Effect3DOffset; + var borderStyle = label.Border.BorderStyle; + + // Check the upper BorderThickness + for (int r = frame.Y - drawMarginFrame - sumThickness.Top; + r < frame.Y - drawMarginFrame - padding.Top; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left; + c < frame.Right + drawMarginFrame + sumThickness.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the left BorderThickness + for (int r = frame.Y - drawMarginFrame - padding.Top; + r < frame.Bottom + drawMarginFrame + padding.Bottom; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left; + c < frame.X - drawMarginFrame - padding.Left; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the right BorderThickness + for (int r = frame.Y - drawMarginFrame - padding.Top; + r < frame.Bottom + drawMarginFrame + padding.Bottom; r++) { + for (int c = frame.Right + drawMarginFrame + padding.Right; + c < frame.Right + drawMarginFrame - sumThickness.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the lower BorderThickness + for (int r = frame.Bottom + drawMarginFrame + padding.Bottom; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left; + c < frame.Right + drawMarginFrame + sumThickness.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the upper Padding + for (int r = frame.Y - drawMarginFrame - padding.Top; + r < frame.Y - drawMarginFrame; r++) { + for (int c = frame.X - drawMarginFrame - padding.Left; + c < frame.Right + drawMarginFrame + padding.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + // Check the left Padding + for (int r = frame.Y - drawMarginFrame; + r < frame.Bottom + drawMarginFrame; r++) { + for (int c = frame.X - drawMarginFrame - padding.Left; + c < frame.X - drawMarginFrame; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + // Check the right Padding + for (int r = frame.Y - drawMarginFrame; + r < frame.Bottom + drawMarginFrame; r++) { + for (int c = frame.Right + drawMarginFrame; + c < frame.Right + drawMarginFrame - padding.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + // Check the lower Padding + for (int r = frame.Bottom + drawMarginFrame; + r < frame.Bottom + drawMarginFrame + padding.Bottom; r++) { + for (int c = frame.X - drawMarginFrame - padding.Left; + c < frame.Right + drawMarginFrame + padding.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + Rune hLine = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.HLine : (borderStyle == BorderStyle.Double ? driver.HDLine : ' ')) : ' '; + Rune vLine = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.VLine : (borderStyle == BorderStyle.Double ? driver.VDLine : ' ')) : ' '; + Rune uRCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.URCorner : (borderStyle == BorderStyle.Double ? driver.URDCorner : ' ')) : ' '; + Rune uLCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.ULCorner : (borderStyle == BorderStyle.Double ? driver.ULDCorner : ' ')) : ' '; + Rune lLCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.LLCorner : (borderStyle == BorderStyle.Double ? driver.LLDCorner : ' ')) : ' '; + Rune lRCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.LRCorner : (borderStyle == BorderStyle.Double ? driver.LRDCorner : ' ')) : ' '; + + var text = ""; + // Check the MarginFrame + for (int r = frame.Y - drawMarginFrame; + r < frame.Bottom + drawMarginFrame; r++) { + for (int c = frame.X - drawMarginFrame; + c <= frame.Right + drawMarginFrame - 1; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + var rune = (Rune)driver.Contents [r, c, 0]; + Assert.Equal (Color.Black, color.Background); + if (c == frame.X - drawMarginFrame && r == frame.Y - drawMarginFrame) { + Assert.Equal (uLCorner, rune); + } else if (c == frame.Right && r == frame.Y - drawMarginFrame) { + Assert.Equal (uRCorner, rune); + } else if (c == frame.X - drawMarginFrame && r == frame.Bottom) { + Assert.Equal (lLCorner, rune); + } else if (c == frame.Right && r == frame.Bottom) { + Assert.Equal (lRCorner, rune); + } else if (c >= frame.X && (r == frame.Y - drawMarginFrame + || r == frame.Bottom)) { + Assert.Equal (hLine, rune); + } else if ((c == frame.X - drawMarginFrame || c == frame.Right) + && r >= frame.Y && r <= frame.Bottom - drawMarginFrame) { + Assert.Equal (vLine, rune); + } else { + text += rune.ToString (); + } + } + } + Assert.Equal ("This is a test", text.Trim ()); + + // Check the upper Effect3D + for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y; + r < frame.Y - drawMarginFrame - sumThickness.Top; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X; + c < frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the left Effect3D + for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X; + c < frame.X - drawMarginFrame - sumThickness.Left; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the right Effect3D + for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.Right + drawMarginFrame + sumThickness.Right; + c < frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the lower Effect3D + for (int r = frame.Bottom + drawMarginFrame + sumThickness.Bottom; + r < frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X; + c < frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the Child frame + for (int r = frame.Y; r < frame.Y + frame.Height; r++) { + for (int c = frame.X; c < frame.X + frame.Width; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Foreground); + Assert.Equal (Color.Black, color.Background); + } + } + } + + [Fact] + [AutoInitShutdown] + public void DrawContent_With_Parent_Border () + { + var top = Application.Top; + var driver = (FakeDriver)Application.Driver; + + var frameView = new FrameView () { + X = Pos.Center (), + Y = Pos.Center (), + Width = 24, + Height = 13, + Border = new Border () { + BorderStyle = BorderStyle.Single, + Padding = new Thickness (2), + BorderThickness = new Thickness (2), + BorderBrush = Color.Red, + Background = Color.BrightGreen, + Effect3D = true, + Effect3DOffset = new Point (2, -3) + } + }; + frameView.Add (new Label () { + ColorScheme = Colors.TopLevel, + Text = "This is a test" + }); + //frameView.Border.Child = frameView; + top.Add (frameView); + + top.LayoutSubviews (); + frameView.Redraw (frameView.Bounds); + + var frame = frameView.Frame; + var drawMarginFrame = frameView.Border.DrawMarginFrame ? 1 : 0; + var sumThickness = frameView.Border.GetSumThickness (); + var borderThickness = frameView.Border.BorderThickness; + var padding = frameView.Border.Padding; + + var effect3DOffset = frameView.Border.Effect3DOffset; + var borderStyle = frameView.Border.BorderStyle; + + // Check the upper BorderThickness + for (int r = frame.Y; + r < Math.Min (frame.Y + borderThickness.Top, frame.Bottom); r++) { + for (int c = frame.X; + c < frame.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the left BorderThickness + for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom); + r < frame.Bottom - borderThickness.Bottom; r++) { + for (int c = frame.X; + c < Math.Min (frame.X + borderThickness.Left, frame.Right); c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the right BorderThickness + for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom); + r < frame.Bottom - borderThickness.Bottom; r++) { + for (int c = Math.Max (frame.Right - borderThickness.Right, frame.X); + c < frame.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the lower BorderThickness + for (int r = Math.Max (frame.Bottom - borderThickness.Bottom, frame.Y); + r < frame.Bottom; r++) { + for (int c = frame.X; + c < frame.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.Red, color.Background); + } + } + + // Check the upper Padding + for (int r = frame.Y + borderThickness.Top; + r < Math.Min (frame.Y + sumThickness.Top, frame.Bottom - borderThickness.Bottom); r++) { + for (int c = frame.X + borderThickness.Left; + c < frame.Right - borderThickness.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + // Check the left Padding + for (int r = frame.Y + sumThickness.Top; + r < frame.Bottom - sumThickness.Bottom; r++) { + for (int c = frame.X + borderThickness.Left; + c < Math.Min (frame.X + sumThickness.Left, frame.Right - borderThickness.Right); c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + // Check the right Padding + // Draw the right Padding + for (int r = frame.Y + sumThickness.Top; + r < frame.Bottom - sumThickness.Bottom; r++) { + for (int c = Math.Max (frame.Right - sumThickness.Right, frame.X + sumThickness.Left); + c < Math.Max (frame.Right - borderThickness.Right, frame.X + sumThickness.Left); c++) { + + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + // Check the lower Padding + for (int r = Math.Max (frame.Bottom - sumThickness.Bottom, frame.Y + borderThickness.Top); + r < frame.Bottom - borderThickness.Bottom; r++) { + for (int c = frame.X + borderThickness.Left; + c < frame.Right - borderThickness.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Background); + } + } + + Rune hLine = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.HLine : (borderStyle == BorderStyle.Double ? driver.HDLine : ' ')) : ' '; + Rune vLine = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.VLine : (borderStyle == BorderStyle.Double ? driver.VDLine : ' ')) : ' '; + Rune uRCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.URCorner : (borderStyle == BorderStyle.Double ? driver.URDCorner : ' ')) : ' '; + Rune uLCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.ULCorner : (borderStyle == BorderStyle.Double ? driver.ULDCorner : ' ')) : ' '; + Rune lLCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.LLCorner : (borderStyle == BorderStyle.Double ? driver.LLDCorner : ' ')) : ' '; + Rune lRCorner = drawMarginFrame > 0 ? (borderStyle == BorderStyle.Single + ? driver.LRCorner : (borderStyle == BorderStyle.Double ? driver.LRDCorner : ' ')) : ' '; + + var text = ""; + // Check the MarginFrame + for (int r = frame.Y + sumThickness.Top; + r < frame.Bottom - sumThickness.Bottom; r++) { + for (int c = frame.X + sumThickness.Left; + c <= frame.Right - sumThickness.Right - 1; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + var rune = (Rune)driver.Contents [r, c, 0]; + Assert.Equal (Color.Black, color.Background); + if (c == frame.X + sumThickness.Left && r == frame.Y + sumThickness.Top) { + Assert.Equal (uLCorner, rune); + } else if (c == frame.Right - drawMarginFrame - sumThickness.Right + && r == frame.Y + sumThickness.Top) { + Assert.Equal (uRCorner, rune); + } else if (c == frame.X + sumThickness.Left + && r == frame.Bottom - drawMarginFrame - sumThickness.Bottom) { + Assert.Equal (lLCorner, rune); + } else if (c == frame.Right - drawMarginFrame - sumThickness.Right + && r == frame.Bottom - drawMarginFrame - sumThickness.Bottom) { + Assert.Equal (lRCorner, rune); + } else if (c > frame.X + sumThickness.Left + && (r == frame.Y + sumThickness.Top + || r == frame.Bottom - drawMarginFrame - sumThickness.Bottom)) { + Assert.Equal (hLine, rune); + } else if ((c == frame.X + sumThickness.Left + || c == frame.Right - drawMarginFrame - sumThickness.Right) + && r >= frame.Y + drawMarginFrame + sumThickness.Top) { + Assert.Equal (vLine, rune); + } else { + text += rune.ToString (); + } + } + } + Assert.Equal ("This is a test", text.Trim ()); + + // Check the upper Effect3D + for (int r = frame.Y + effect3DOffset.Y; + r < frame.Y; r++) { + for (int c = frame.X + effect3DOffset.X; + c < frame.Right + effect3DOffset.X; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the left Effect3D + for (int r = frame.Y + effect3DOffset.Y; + r < frame.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X + effect3DOffset.X; + c < frame.X; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the right Effect3D + for (int r = frame.Y + effect3DOffset.Y; + r < frame.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.Right; + c < frame.Right + effect3DOffset.X; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the lower Effect3D + for (int r = frame.Bottom; + r < frame.Bottom + effect3DOffset.Y; r++) { + for (int c = frame.X + effect3DOffset.X; + c < frame.Right + effect3DOffset.X; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.DarkGray, color.Background); + } + } + + // Check the Child frame + for (int r = frame.Y + drawMarginFrame + sumThickness.Top; + r < frame.Bottom - drawMarginFrame - sumThickness.Bottom; r++) { + for (int c = frame.X + drawMarginFrame + sumThickness.Left; + c < frame.Right - drawMarginFrame - sumThickness.Right; c++) { + + var color = (Attribute)driver.Contents [r, c, 1]; + Assert.Equal (Color.BrightGreen, color.Foreground); + Assert.Equal (Color.Black, color.Background); + } + } + } + } +} diff --git a/UnitTests/PanelViewTests.cs b/UnitTests/PanelViewTests.cs new file mode 100644 index 000000000..f049a249f --- /dev/null +++ b/UnitTests/PanelViewTests.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Terminal.Gui.Views { + public class PanelViewTests { + [Fact] + public void Constructor_Defaults () + { + var pv = new PanelView (); + + Assert.False (pv.CanFocus); + Assert.False (pv.Visible); + Assert.False (pv.UsePanelFrame); + Assert.Null (pv.Child); + + pv = new PanelView (new Label ("This is a test.")); + + Assert.False (pv.CanFocus); + Assert.True (pv.Visible); + Assert.False (pv.UsePanelFrame); + Assert.NotNull (pv.Child); + Assert.NotNull (pv.Border); + Assert.Null (pv.Child.Border); + } + + [Fact] + public void Child_Sets_To_Null_Remove_From_Subviews_PanelView () + { + var pv = new PanelView (new Label ("This is a test.")); + Assert.NotNull (pv.Child); + Assert.Equal (1, pv.Subviews[0].Subviews.Count); + + pv.Child = null; + Assert.Null (pv.Child); + Assert.Equal (0, pv.Subviews [0].Subviews.Count); + } + + [Fact] + public void Add_View_Also_Sets_Child () + { + var pv = new PanelView (); + Assert.Null (pv.Child); + Assert.Equal (0, pv.Subviews [0].Subviews.Count); + + pv.Add (new Label ("This is a test.")); + Assert.NotNull (pv.Child); + Assert.Equal (1, pv.Subviews [0].Subviews.Count); + } + + [Fact] + public void Add_More_Views_Remove_Last_Child_Before__Only_One_Is_Allowed () + { + var pv = new PanelView (new Label ("This is a test.")); + Assert.NotNull (pv.Child); + Assert.Equal (1, pv.Subviews [0].Subviews.Count); + Assert.IsType