浏览代码

Got FramesEditor scenario working

Tigger Kindel 2 年之前
父节点
当前提交
45c3d08ac6

+ 36 - 33
Terminal.Gui/Core/Frame.cs

@@ -7,32 +7,21 @@ using Terminal.Gui.Graphs;
 
 namespace Terminal.Gui {
 
+	// TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Frames
+	// TODO: v2 - If a Frame has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Frame
+	// QUESTION: How does a user navigate out of a Frame to another Frame, or back into the Parent's SubViews?
+
 	/// <summary>
 	/// Frames are a special form of <see cref="View"/> that act as adornments; they appear outside of the <see cref="View.Bounds"/>
 	/// enabling borders, menus, etc... 
 	/// </summary>
 	public class Frame : View {
+		private Thickness _thickness;
 
-		// TODO: v2 - If a Frame has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Frame
-		// QUESTION: How does a user navigate out of a Frame to another Frame, or back into the Parent's SubViews?
+		internal override void CreateFrames (){ /* Do nothing - Frames do not have Frames */ }
 
 		/// <summary>
-		/// Frames are a special form of <see cref="View"/> that act as adornments; they appear outside of the <see cref="View.Bounds"/>
-		/// enabling borders, menus, etc... 
-		/// </summary>
-		//public Frame ()
-		//{
-		//}
-
-		
-		internal override void CreateFrames ()
-		{
-			// Do nothing - Frame
-		}
-
-
-		/// <summary>
-		/// The Parent of this Frame. 
+		/// The Parent of this Frame (the View that this Frame surrounds).
 		/// </summary>
 		public View Parent { get; set; }
 
@@ -70,6 +59,7 @@ namespace Terminal.Gui {
 		/// <param name="clipRect"></param>
 		public virtual void OnDrawSubViews (Rect clipRect)
 		{
+			// TODO: Enable subviews of Frames (adornments).
 			//	if (Subviews == null) {
 			//		return;
 			//	}
@@ -135,10 +125,7 @@ namespace Terminal.Gui {
 			Driver.Clip = prevClip;
 		}
 
-		//public Label DiagnosticsLabel { get; set; }
-		// TODO: v2 = This is teporary; need to also enable (or not) simple way of setting 
-		// other border properties
-		// TOOD: v2 - Missing 3D effect
+		// TODO: v2 - Frame.BorderStyle is temporary - Eventually the border will be drawn by a "BorderView" that is a subview of the Frame.
 		/// <summary>
 		/// 
 		/// </summary>
@@ -147,24 +134,40 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Defines the rectangle that the <see cref="Frame"/> will use to draw its content. 
 		/// </summary>
-		public Thickness Thickness { get; set; }
+		public Thickness Thickness {
+			get { return _thickness; }
+			set {
+				var prev = _thickness;
+				_thickness = value;
+				if (prev != _thickness) {
+					OnThicknessChanged ();
+				}
+				
+			}
+		}
+
+		/// <summary>
+		/// Called whenever the <see cref="Thickness"/> property changes.
+		/// </summary>
+		public virtual void OnThicknessChanged ()
+		{
+			ThicknessChanged?.Invoke (this, new ThicknessEventArgs () { Thickness = Thickness });
+		}
 
-		// TODO: v2 - This is confusing. It is a read-only property and actually only returns a size, so 
-		// should not be a Rect. However, it may make sense to keep it a Rect and support negative Location
-		// for scrolling. Still noodling this.
 		/// <summary>
-		/// Gets the rectangle that describes the inner area of the frame. The Location is always 0, 0.
+		/// Fired whenever the <see cref="Thickness"/> property changes.
+		/// </summary>
+		public event EventHandler<ThicknessEventArgs> ThicknessChanged;
+
+		/// <summary>
+		/// Gets the rectangle that describes the inner area of the frame. The Location is always (0,0).
 		/// </summary>
 		public override Rect Bounds {
 			get {
-				if (Thickness == null) {
-					return new Rect (Point.Empty, Frame.Size);
-				}
-				// Return the frame-relative bounds 
-				return Thickness.GetInnerRect (new Rect (Point.Empty, Frame.Size));
+				return Thickness?.GetInnerRect (new Rect (Point.Empty, Frame.Size)) ?? new Rect (Point.Empty, Frame.Size);
 			}
 			set {
-				throw new InvalidOperationException ("It makes no sense to explicitly set Bounds.");
+				throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
 			}
 		}
 	}

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

@@ -3,6 +3,7 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Text.Json.Serialization;
+using Terminal.Gui.Configuration;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -19,24 +20,25 @@ namespace Terminal.Gui {
 			}
 			return width;
 		}
+
 		/// <summary>
 		/// Gets or sets the width of the left side of the rectangle.
 		/// </summary>
 		[JsonInclude]
 		public int Left;
-		
+
 		/// <summary>
 		/// Gets or sets the width of the upper side of the rectangle.
 		/// </summary>
 		[JsonInclude]
 		public int Top;
-		
+
 		/// <summary>
 		/// Gets or sets the width of the right side of the rectangle.
 		/// </summary>
 		[JsonInclude]
 		public int Right;
-		
+
 		/// <summary>
 		/// Gets or sets the width of the lower side of the rectangle.
 		/// </summary>
@@ -70,11 +72,30 @@ namespace Terminal.Gui {
 			Right = right;
 			Bottom = bottom;
 		}
-		
-		///// <summary>
-		///// <see langword="true"/> if all dimensions are 0.
-		///// </summary>
-		//public bool Empty => Left == 0 && Top == 0 && Right == 0 && Bottom == 0;
+
+		public int Vertical {
+			get {
+				return Top + Bottom;
+			}
+			set {
+				Top = Bottom = value / 2;
+			}
+		}
+
+		public int Horizontal {
+			get {
+				return Left + Right;
+			}
+			set {
+				Left = Right = value / 2;
+			}
+		}
+
+		//public virtual void OnChanged()
+		//{
+		//	Changed?.Invoke (this, new ThicknessEventArgs () { Thickness = this });
+		//}
+		//public event EventHandler<ThicknessEventArgs> Changed;
 
 		/// <summary>
 		/// Returns a rectangle describing the location and size of the inner area of <paramref name="rect"/>
@@ -173,13 +194,15 @@ namespace Terminal.Gui {
 				}
 			}
 
-			// Draw the diagnostics label on the bottom
-			var tf = new TextFormatter () {
-				Text = label == null ? string.Empty : $"{label} {this}",
-				Alignment = TextAlignment.Centered,
-				VerticalAlignment = VerticalTextAlignment.Bottom
-			};
-			tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect, false);
+			if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding) {
+				// Draw the diagnostics label on the bottom
+				var tf = new TextFormatter () {
+					Text = label == null ? string.Empty : $"{label} {this}",
+					Alignment = TextAlignment.Centered,
+					VerticalAlignment = VerticalTextAlignment.Bottom
+				};
+				tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect, false);
+			}
 
 			return GetInnerRect (rect);
 
@@ -218,7 +241,7 @@ namespace Terminal.Gui {
 			       Top == other.Top &&
 			       Bottom == other.Bottom;
 		}
-		
+
 		/// <inheritdoc/>
 		public override int GetHashCode ()
 		{
@@ -242,7 +265,7 @@ namespace Terminal.Gui {
 			return !(left == right);
 		}
 	}
-	
+
 	internal static class StringExtensions {
 		public static string Repeat (this string instr, int n)
 		{

+ 23 - 0
Terminal.Gui/Core/ThicknessEventArgs.cs

@@ -0,0 +1,23 @@
+using System;
+
+#nullable enable
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Event arguments for the <see cref="ConfigurationManager"/> events.
+	/// </summary>
+	public class ThicknessEventArgs : EventArgs {
+
+		/// <summary>
+		/// Initializes a new instance of <see cref="ThicknessEventArgs"/>
+		/// </summary>
+		public ThicknessEventArgs ()
+		{
+		}
+
+		/// <summary>
+		/// The new Thickness.
+		/// </summary>
+		public Thickness Thickness { get; set; } = Thickness.Empty;
+	}
+}

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

@@ -477,7 +477,7 @@ namespace Terminal.Gui {
 		/// The Thickness that separates a View from other SubViews of the same SuperView. 
 		/// The Margin is not part of the View's content and is not clipped by the View's Clip Area. 
 		/// </summary>
-		public Frame Margin { get; internal set; }
+		public Frame Margin { get; private set; }
 
 		// TODO: Rename BorderFrame to Border
 		/// <summary>
@@ -486,7 +486,7 @@ namespace Terminal.Gui {
 		///  title will take up the first row and the second row will be filled with spaces. 
 		///  The Border is not part of the View's content and is not clipped by the View's `ClipArea`.
 		/// </summary>
-		public Frame BorderFrame { get; internal set; }
+		public Frame BorderFrame { get; private set; }
 
 		/// <summary>
 		/// Means the Thickness inside of an element that offsets the `Content` from the Border. 
@@ -495,7 +495,7 @@ namespace Terminal.Gui {
 		/// <remarks>
 		/// (NOTE: in v1 `Padding` is OUTSIDE of the `Border`). 
 		/// </remarks>
-		public Frame Padding { get; internal set; }
+		public Frame Padding { get; private set; }
 
 		/// <summary>
 		/// Gets the rectangle that describes the location and size of the area within the View where
@@ -518,19 +518,36 @@ namespace Terminal.Gui {
 		/// </summary>
 		internal virtual void CreateFrames ()
 		{
-			Margin?.Dispose ();
+			void ThicknessChangedHandler (object sender, EventArgs e)
+			{
+				SetNeedsLayout ();
+			}
+
+			if (Margin != null) {
+				Margin.ThicknessChanged -= ThicknessChangedHandler;
+				Margin.Dispose ();
+			}
 			Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) };
+			Margin.ThicknessChanged += ThicknessChangedHandler;
 			Margin.Parent = this;
 			//Margin.DiagnosticsLabel.Text = "Margin";
 
-			BorderFrame?.Dispose ();
+			if (BorderFrame != null) {
+				BorderFrame.ThicknessChanged -= ThicknessChangedHandler;
+				BorderFrame.Dispose ();
+			}
 			// TODO: create default for borderstyle
 			BorderFrame = new Frame () { Id = "BorderFrame", Thickness = new Thickness (0), BorderStyle = BorderStyle.Single };
+			BorderFrame.ThicknessChanged += ThicknessChangedHandler;
 			BorderFrame.Parent = this;
 			// TODO: Create View.AddAdornment
 
-			Padding?.Dispose ();
+			if (Padding != null) {
+				Padding.ThicknessChanged -= ThicknessChangedHandler;
+				Padding.Dispose ();
+			}
 			Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) };
+			Padding.ThicknessChanged += ThicknessChangedHandler;
 			Padding.Parent = this;
 		}
 

+ 6 - 5
Terminal.Gui/Core/Window.cs

@@ -114,11 +114,6 @@ namespace Terminal.Gui {
 			} else {
 				Border = border;
 			}
-		}
-
-		public override void BeginInit ()
-		{
-			base.BeginInit ();
 			BorderFrame.Thickness = new Thickness (1);
 			BorderFrame.BorderStyle = Border.BorderStyle;
 			BorderFrame.ColorScheme = ColorScheme;
@@ -128,6 +123,12 @@ namespace Terminal.Gui {
 			Padding.Thickness = Border.PaddingThickness ?? Padding.Thickness;
 		}
 
+		public override void BeginInit ()
+		{
+			base.BeginInit ();
+
+		}
+
 		/// <inheritdoc/>
 		public override void Add (View view)
 		{

+ 7 - 5
Terminal.Gui/Views/FrameView.cs

@@ -72,7 +72,12 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Initializes a new instance of the <see cref="Gui.FrameView"/> class using <see cref="LayoutStyle.Computed"/> layout.
 		/// </summary>
-		public FrameView () : this (title: string.Empty) { }
+		public FrameView () : this (title: string.Empty) {
+			BorderFrame.Thickness = new Thickness (1);
+			BorderFrame.BorderStyle = Border.BorderStyle;
+			BorderFrame.ColorScheme = ColorScheme;
+			BorderFrame.Data = "BorderFrame";
+		}
 
 		/// <summary>
 		/// The default <see cref="BorderStyle"/> for <see cref="FrameView"/>. The default is <see cref="BorderStyle.Single"/>.
@@ -102,10 +107,7 @@ namespace Terminal.Gui {
 		public override void BeginInit ()
 		{
 			base.BeginInit ();
-			BorderFrame.Thickness = new Thickness (1);
-			BorderFrame.BorderStyle = Border.BorderStyle;
-			BorderFrame.ColorScheme = ColorScheme;
-			BorderFrame.Data = "BorderFrame";
+
 
 		}
 

+ 290 - 0
UICatalog/Scenarios/Frames.cs

@@ -0,0 +1,290 @@
+using System.Globalization;
+using System;
+using Terminal.Gui;
+using System.Linq;
+using NStack;
+using Terminal.Gui.Configuration;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Frames Demo", Description: "Demonstrates Margin, Border, and Padding on Views.")]
+	[ScenarioCategory ("Layout")]
+	[ScenarioCategory ("Borders")]
+	public class Frames : Scenario {
+
+		public class ThicknessEditor : View {
+			private Thickness thickness;
+
+			public Thickness Thickness {
+				get => thickness;
+				set {
+					thickness = value;
+					ThicknessChanged?.Invoke (this, new ThicknessEventArgs () {  Thickness = Thickness });
+				}
+			}
+
+			public event EventHandler<ThicknessEventArgs> ThicknessChanged;
+
+			public ThicknessEditor ()
+			{
+				Margin.Thickness = new Thickness (1);
+				BorderFrame.Thickness = new Thickness (1);
+			}
+
+			public override void BeginInit ()
+			{
+				base.BeginInit ();
+
+				var topEdit = new TextField ("") {
+					X = Pos.Center (),
+					Y = 0,
+					Width = 5
+				};
+				topEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (Thickness.Left,
+							int.Parse (e.NewText.ToString ()), Thickness.Right,
+							Thickness.Bottom);
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				topEdit.Text = $"{Thickness.Top}";
+
+				Add (topEdit);
+
+				var leftEdit = new TextField ("") {
+					X = 0,
+					Y = Pos.Bottom (topEdit),
+					Width = 5
+				};
+				leftEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (int.Parse (e.NewText.ToString ()),
+							Thickness.Top, Thickness.Right,
+							Thickness.Bottom);
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				leftEdit.Text = $"{Thickness.Left}";
+				Add (leftEdit);
+
+				var rightEdit = new TextField ("") {
+					X = Pos.Right (topEdit) + 1,
+					Y = Pos.Bottom (topEdit),
+					Width = 5
+				};
+				rightEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (Thickness.Left,
+							Thickness.Top, int.Parse (e.NewText.ToString ()),
+							Thickness.Bottom);
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				rightEdit.Text = $"{Thickness.Right}";
+				Add (rightEdit);
+
+				var bottomEdit = new TextField ("") {
+					X = Pos.Center (),
+					Y = Pos.Bottom (leftEdit),
+					Width = 5
+				};
+				bottomEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (Thickness.Left,
+							Thickness.Top, Thickness.Right,
+							int.Parse (e.NewText.ToString ()));
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				bottomEdit.Text = $"{Thickness.Bottom}";
+				Add (bottomEdit);
+
+				var copyTop = new Button ("Copy Top") {
+					X = Pos.Center (),
+					Y = Pos.AnchorEnd (1)
+				};
+				copyTop.Clicked += () => {
+					Thickness = new Thickness (Thickness.Top);
+					if (topEdit.Text.IsEmpty) {
+						topEdit.Text = "0";
+					}
+					bottomEdit.Text = leftEdit.Text = rightEdit.Text = topEdit.Text;
+				};
+				Add (copyTop);
+
+				LayoutSubviews ();
+				Height = Margin.Thickness.Vertical + BorderFrame.Thickness.Vertical + Padding.Thickness.Vertical + 4;
+				Width = 20;
+			}
+		}
+
+		public class FramesEditor : Window {
+			public FramesEditor (NStack.ustring title, View viewToEdit)
+			{
+				viewToEdit.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
+				var marginEditor = new ThicknessEditor () {
+					X = 20,
+					Y = 0,
+					Title = "Margin",
+					Thickness = viewToEdit.Margin.Thickness,
+				};
+				marginEditor.ThicknessChanged += (s, a) => {
+					viewToEdit.Margin.Thickness = a.Thickness;
+				};
+				Add (marginEditor);
+
+				viewToEdit.BorderFrame.ColorScheme = Colors.ColorSchemes ["Base"];
+				var borderEditor = new ThicknessEditor () {
+					X = Pos.Right(marginEditor),
+					Y = 0,
+					Title = "Border",
+					Thickness = viewToEdit.BorderFrame.Thickness,
+				};
+				borderEditor.ThicknessChanged += (s, a) => {
+					viewToEdit.BorderFrame.Thickness = a.Thickness;
+				};
+				Add (borderEditor);
+
+				viewToEdit.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
+				var paddingEditor = new ThicknessEditor () {
+					X = Pos.Right (borderEditor),
+					Y = 0,
+					Title = "Padding",
+					Thickness = viewToEdit.Padding.Thickness,
+				};
+				paddingEditor.ThicknessChanged += (s, a) => {
+					viewToEdit.Padding.Thickness = a.Thickness;
+				};
+				Add (paddingEditor);
+
+				viewToEdit.Y = Pos.Center () + 4;
+
+				Add (new Label ("BorderStyle:"));
+
+				var borderStyleEnum = Enum.GetValues (typeof (BorderStyle)).Cast<BorderStyle> ().ToList ();
+				var rbBorderStyle = new RadioGroup (borderStyleEnum.Select (
+					e => NStack.ustring.Make (e.ToString ())).ToArray ()) {
+
+					X = 2,
+					Y = 1,
+					SelectedItem = (int)viewToEdit.BorderFrame.BorderStyle
+				};
+				Add (rbBorderStyle);
+
+				//rbBorderStyle.SelectedItemChanged += (e) => {
+				//	viewToEdit.BorderFrame.BorderStyle = (BorderStyle)e.SelectedItem;
+				//	viewToEdit.SetNeedsDisplay ();
+				//};
+
+				//Add (new Label ("Background:") {
+				//	Y = 5
+				//});
+
+				//var colorEnum = Enum.GetValues (typeof (Color)).Cast<Color> ().ToList ();
+				//var rbBackground = new RadioGroup (colorEnum.Select (
+				//	e => NStack.ustring.Make (e.ToString ())).ToArray ()) {
+
+				//	X = 2,
+				//	Y = 6,
+				//	SelectedItem = (int)viewToEdit.Border.BackgroundColor
+				//};
+				//rbBackground.SelectedItemChanged += (e) => {
+				//	if (viewToEdit.Border != null) {
+				//		viewToEdit.Border.BackgroundColor = (Color)e.SelectedItem;
+				//	}
+				//};
+				//Add (rbBackground);
+
+				//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)viewToEdit.Border.ForgroundColor
+				//};
+				//rbBorderBrush.SelectedItemChanged += (e) => {
+				//	if (viewToEdit.Border != null) {
+				//		viewToEdit.Border.ForgroundColor = (Color)e.SelectedItem;
+				//	}
+				//};
+				//Add (rbBorderBrush);
+
+				viewToEdit.X = Pos.Center ();
+				viewToEdit.Y = Pos.Bottom (marginEditor);
+				viewToEdit.Width = 50;
+				viewToEdit.Height = 20;
+				Add (viewToEdit);
+
+				LayoutSubviews ();
+
+				Title = title;
+			}
+		}
+
+		public override void Init ()
+		{
+			Application.Init ();
+			ConfigurationManager.Themes.Theme = Theme;
+			ConfigurationManager.Apply ();
+			Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+
+			var view = new Window ();
+			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 {view.GetType().Name}?", "Yes", "No");
+			var label = new Label ($"I'm a {view.GetType ().Name}") {
+				X = Pos.Center (),
+				Y = Pos.Center () - 1,
+			};
+			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 (),
+				Text = "1234567890"
+			};
+
+			view.Margin.Thickness = new Thickness (3);
+			view.Margin.ColorScheme = Colors.ColorSchemes ["Dialog"];
+
+			view.Add (tf1, button, label, tf2, tv);
+			view.LayoutComplete += (a) => view.Title = view.ToString ();
+
+			var editor = new FramesEditor (
+				$"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+				view);
+
+			Application.Run (editor);
+			Application.Shutdown ();
+		}
+
+		public override void Run ()
+		{
+		}
+	}
+}

+ 240 - 3
UICatalog/Scenarios/ViewExperiments.cs

@@ -1,10 +1,233 @@
-using Terminal.Gui;
+using System;
+using System.Linq;
+using Terminal.Gui;
 using Terminal.Gui.Configuration;
 
 namespace UICatalog.Scenarios {
 	[ScenarioMetadata (Name: "_ View Experiments", Description: "v2 View Experiments")]
 	[ScenarioCategory ("Controls")]
 	public class ViewExperiments : Scenario {
+
+		public class ThicknessEditor : View {
+			private Thickness thickness;
+
+			public Thickness Thickness {
+				get => thickness;
+				set {
+					thickness = value;
+					ThicknessChanged?.Invoke (this, new ThicknessEventArgs () { Thickness = Thickness });
+				}
+			}
+
+			public event EventHandler<ThicknessEventArgs> ThicknessChanged;
+
+			public ThicknessEditor ()
+			{
+				Margin.Thickness = new Thickness (1);
+				BorderFrame.Thickness = new Thickness (1);
+			}
+
+			public override void BeginInit ()
+			{
+				base.BeginInit ();
+
+				var topEdit = new TextField ("") {
+					X = Pos.Center (),
+					Y = 0,
+					Width = 5
+				};
+				topEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (Thickness.Left,
+							int.Parse (e.NewText.ToString ()), Thickness.Right,
+							Thickness.Bottom);
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				topEdit.Text = $"{Thickness.Top}";
+
+				Add (topEdit);
+
+				var leftEdit = new TextField ("") {
+					X = 0,
+					Y = Pos.Bottom (topEdit),
+					Width = 5
+				};
+				leftEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (int.Parse (e.NewText.ToString ()),
+							Thickness.Top, Thickness.Right,
+							Thickness.Bottom);
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				leftEdit.Text = $"{Thickness.Left}";
+				Add (leftEdit);
+
+				var rightEdit = new TextField ("") {
+					X = Pos.Right (topEdit) + 1,
+					Y = Pos.Bottom (topEdit),
+					Width = 5
+				};
+				rightEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (Thickness.Left,
+							Thickness.Top, int.Parse (e.NewText.ToString ()),
+							Thickness.Bottom);
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				rightEdit.Text = $"{Thickness.Right}";
+				Add (rightEdit);
+
+				var bottomEdit = new TextField ("") {
+					X = Pos.Center (),
+					Y = Pos.Bottom (leftEdit),
+					Width = 5
+				};
+				bottomEdit.TextChanging += (e) => {
+					try {
+						Thickness = new Thickness (Thickness.Left,
+							Thickness.Top, Thickness.Right,
+							int.Parse (e.NewText.ToString ()));
+					} catch {
+						if (!e.NewText.IsEmpty) {
+							e.Cancel = true;
+						}
+					}
+				};
+				bottomEdit.Text = $"{Thickness.Bottom}";
+				Add (bottomEdit);
+
+				var copyTop = new Button ("Copy Top") {
+					X = Pos.Center (),
+					Y = Pos.AnchorEnd (1)
+				};
+				copyTop.Clicked += () => {
+					Thickness = new Thickness (Thickness.Top);
+					if (topEdit.Text.IsEmpty) {
+						topEdit.Text = "0";
+					}
+					bottomEdit.Text = leftEdit.Text = rightEdit.Text = topEdit.Text;
+				};
+				Add (copyTop);
+
+				LayoutSubviews ();
+				Height = Margin.Thickness.Vertical + BorderFrame.Thickness.Vertical + Padding.Thickness.Vertical + 4;
+				Width = 20;
+			}
+		}
+
+		public class FramesEditor : Window {
+			public FramesEditor (NStack.ustring title, View viewToEdit)
+			{
+				viewToEdit.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
+				var marginEditor = new ThicknessEditor () {
+					X = 20,
+					Y = 0,
+					Title = "Margin",
+					Thickness = viewToEdit.Margin.Thickness,
+				};
+				marginEditor.ThicknessChanged += (s, a) => {
+					viewToEdit.Margin.Thickness = a.Thickness;
+				};
+				Add (marginEditor);
+
+				viewToEdit.BorderFrame.ColorScheme = Colors.ColorSchemes ["Base"];
+				var borderEditor = new ThicknessEditor () {
+					X = Pos.Right (marginEditor),
+					Y = 0,
+					Title = "Border",
+					Thickness = viewToEdit.BorderFrame.Thickness,
+				};
+				borderEditor.ThicknessChanged += (s, a) => {
+					viewToEdit.BorderFrame.Thickness = a.Thickness;
+				};
+				Add (borderEditor);
+
+				viewToEdit.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
+				var paddingEditor = new ThicknessEditor () {
+					X = Pos.Right (borderEditor),
+					Y = 0,
+					Title = "Padding",
+					Thickness = viewToEdit.Padding.Thickness,
+				};
+				paddingEditor.ThicknessChanged += (s, a) => {
+					viewToEdit.Padding.Thickness = a.Thickness;
+				};
+				Add (paddingEditor);
+
+				viewToEdit.Y = Pos.Center () + 4;
+
+				Add (new Label ("BorderStyle:"));
+
+				var borderStyleEnum = Enum.GetValues (typeof (BorderStyle)).Cast<BorderStyle> ().ToList ();
+				var rbBorderStyle = new RadioGroup (borderStyleEnum.Select (
+					e => NStack.ustring.Make (e.ToString ())).ToArray ()) {
+
+					X = 2,
+					Y = 1,
+					SelectedItem = (int)viewToEdit.BorderFrame.BorderStyle
+				};
+				Add (rbBorderStyle);
+
+				//rbBorderStyle.SelectedItemChanged += (e) => {
+				//	viewToEdit.BorderFrame.BorderStyle = (BorderStyle)e.SelectedItem;
+				//	viewToEdit.SetNeedsDisplay ();
+				//};
+
+				//Add (new Label ("Background:") {
+				//	Y = 5
+				//});
+
+				//var colorEnum = Enum.GetValues (typeof (Color)).Cast<Color> ().ToList ();
+				//var rbBackground = new RadioGroup (colorEnum.Select (
+				//	e => NStack.ustring.Make (e.ToString ())).ToArray ()) {
+
+				//	X = 2,
+				//	Y = 6,
+				//	SelectedItem = (int)viewToEdit.Border.BackgroundColor
+				//};
+				//rbBackground.SelectedItemChanged += (e) => {
+				//	if (viewToEdit.Border != null) {
+				//		viewToEdit.Border.BackgroundColor = (Color)e.SelectedItem;
+				//	}
+				//};
+				//Add (rbBackground);
+
+				//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)viewToEdit.Border.ForgroundColor
+				//};
+				//rbBorderBrush.SelectedItemChanged += (e) => {
+				//	if (viewToEdit.Border != null) {
+				//		viewToEdit.Border.ForgroundColor = (Color)e.SelectedItem;
+				//	}
+				//};
+				//Add (rbBorderBrush);
+
+				Height = 9; 
+				Title = title;
+			}
+		}
+
 		public override void Init ()
 		{
 			Application.Init ();
@@ -28,14 +251,14 @@ namespace UICatalog.Scenarios {
 
 			var view = new View () {
 				X = 2,
-				Y = Pos.Bottom(containerLabel),
+				Y = Pos.Bottom (containerLabel),
 				Height = Dim.Fill (2),
 				Width = Dim.Fill (2),
 				Title = "View with 2xMargin, 2xBorder, & 2xPadding",
 				ColorScheme = Colors.ColorSchemes ["Base"],
 			};
 
-			Application.Top.Add (view);
+			//Application.Top.Add (view);
 
 			//view.InitializeFrames ();
 			view.Margin.Thickness = new Thickness (2, 2, 2, 2);
@@ -220,6 +443,20 @@ namespace UICatalog.Scenarios {
 				containerLabel.Text = $"Container.Frame: {Application.Top.Frame} .Bounds: {Application.Top.Bounds}\nView.Frame: {view.Frame} .Bounds: {view.Bounds}\nView.ContentArea: {view.ContentArea}";
 			};
 
+			view.X = Pos.Center ();
+
+			var editor = new FramesEditor ($"Frame Editor", view) {
+				X = 0,
+				Y = Pos.Bottom (containerLabel),
+				Width = Dim.Fill (),
+			};
+
+			Application.Top.Add (editor);
+
+			view.Y = Pos.Bottom (editor);
+			view.Width = Dim.Fill ();
+			view.Height = Dim.Fill ();
+			Application.Top.Add (view);
 		}
 	}
 }