Browse Source

view2 WIP

Tig Kindel 2 years ago
parent
commit
9171afba18

+ 103 - 31
Terminal.Gui/Core/Border.cs

@@ -30,41 +30,51 @@ namespace Terminal.Gui {
 	///  the <see cref="Left"/>, <see cref="Top"/>, <see cref="Right"/>, and <see cref="Bottom"/> sides
 	///  of the rectangle, respectively.
 	/// </summary>
-	public struct Thickness {
+	public class Thickness {
+		private int _left;
+		private int _right;
+		private int _top;
+		private int _bottom;
+
+		private int validate (int width)
+		{
+			if (width < 0) {
+				throw new ArgumentException ("Thickness widths cannot be negative.");
+			}
+			return width;
+		}
 		/// <summary>
-		/// Gets or sets the width, in integers, of the left side of the bounding rectangle.
+		/// Gets or sets the width of the left side of the rectangle.
 		/// </summary>
-		public int Left;
+		public int Left { get => _left; set => _left = validate (value); }
 		/// <summary>
-		/// Gets or sets the width, in integers, of the upper side of the bounding rectangle.
+		/// Gets or sets the width of the upper side of the rectangle.
 		/// </summary>
-		public int Top;
+		public int Top { get => _top; set => _top = validate (value); }
 		/// <summary>
-		/// Gets or sets the width, in integers, of the right side of the bounding rectangle.
+		/// Gets or sets the width of the right side of the rectangle.
 		/// </summary>
-		public int Right;
+		public int Right { get => _right; set => _right = validate (value); }
 		/// <summary>
-		/// Gets or sets the width, in integers, of the lower side of the bounding rectangle.
+		/// Gets or sets the width of the lower side of the rectangle.
 		/// </summary>
-		public int Bottom;
+		public int Bottom { get => _bottom; set => _bottom = validate (value); }
 
 		/// <summary>
-		/// Initializes a new instance of the <see cref="Thickness"/> structure that has the
-		///  specified uniform length on each side.
+		/// Initializes a new instance of the <see cref="Thickness"/> class with all widths
+		/// set to 0.
 		/// </summary>
-		/// <param name="length"></param>
-		public Thickness (int length)
-		{
-			if (length < 0) {
-				throw new ArgumentException ("Invalid value for this property.");
-			}
+		public Thickness () { }
 
-			Left = Top = Right = Bottom = length;
-		}
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.
+		/// </summary>
+		/// <param name="width"></param>
+		public Thickness (int width) : this (width, width, width, width) { }
 
 		/// <summary>
-		/// Initializes a new instance of the <see cref="Thickness"/> structure that has specific
-		///  lengths (supplied as a <see cref="int"/>) applied to each side of the rectangle.
+		/// Initializes a new instance of the <see cref="Thickness"/> class that has specific
+		///  widths applied to each side of the rectangle.
 		/// </summary>
 		/// <param name="left"></param>
 		/// <param name="top"></param>
@@ -72,18 +82,80 @@ namespace Terminal.Gui {
 		/// <param name="bottom"></param>
 		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;
 		}
 
-		/// <summary>Returns the fully qualified type name of this instance.</summary>
-		/// <returns>The fully qualified type name.</returns>
+		/// <summary>
+		/// Returns a rectangle describing the location and size of the inner area of <paramref name="rect"/>
+		/// with the thickness widths subracted. The height and width of the retunred rect may be zero.
+		/// </summary>
+		/// <param name="rect">The source rectangle</param>
+		/// <returns></returns>
+		public Rect GetInnerRect (Rect rect)
+		{
+			var width = rect.Size.Width - (Left + Right);
+			var height = rect.Size.Height - (Top + Bottom);
+			var size = new Size (Math.Max (0, width), Math.Max (0, height));
+			return new Rect (new Point (rect.X + Left, rect.Y + Top), size);
+		}
+
+		/// <summary>
+		/// Draws the thickness rectangle with an optional diagnostics label.
+		/// </summary>
+		/// <param name="rect">The location and size of the rectangle that bounds the thickness rectangle, in 
+		/// screen coordinates.</param>
+		/// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
+		/// <returns>The inner rectangle remaining to be drawn.</returns>
+		public Rect Draw (Rect rect, string label = null)
+		{
+			// Draw the Top side
+			for (var r = rect.Y; r < Math.Min (rect.Y + rect.Height, rect.Y + Top); r++) {
+				for (var c = rect.X; c < rect.X + rect.Width; c++) {
+					Application.Driver.Move (c, r);
+					Application.Driver.AddRune (' ');
+				}
+			}
+
+			// Draw the Left side
+			for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
+				for (var c = rect.X; c < Math.Min (rect.X + rect.Width, rect.X + Left); c++) {
+					Application.Driver.Move (c, r);
+					Application.Driver.AddRune (' ');
+				}
+			}
+
+			// Draw the Right side			
+			for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
+				for (var c = rect.X + Math.Max (0, rect.Width - Right); c < rect.X + rect.Width; c++) {
+					Application.Driver.Move (c, r);
+					Application.Driver.AddRune (' ');
+				}
+			}
+
+			// Draw the Bottom side
+			for (var r = rect.Y + Math.Max (0, rect.Height - Bottom); r < rect.Y + rect.Height; r++) {
+				for (var c = rect.X; c < rect.X + rect.Width; c++) {
+					Application.Driver.Move (c, r);
+					Application.Driver.AddRune (' ');
+				}
+			}
+
+			// 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);
+
+			return GetInnerRect (rect);
+
+		}
+		/// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
+		/// <returns>The thickness widths as a string.</returns>
 		public override string ToString ()
 		{
 			return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})";
@@ -318,10 +390,10 @@ namespace Terminal.Gui {
 
 		private BorderStyle borderStyle;
 		private bool drawMarginFrame;
-		private Thickness borderThickness;
+		private Thickness borderThickness = new Thickness (0);
 		private Color borderBrush;
 		private Color background;
-		private Thickness padding;
+		private Thickness padding = new Thickness (0);
 		private bool effect3D;
 		private Point effect3DOffset = new Point (1, 1);
 		private Attribute? effect3DBrush;
@@ -719,8 +791,8 @@ namespace Terminal.Gui {
 					lc.AddLine (new Point (rect.X, rect.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, BorderStyle);
 					lc.AddLine (new Point (rect.X + rect.Width, rect.Y), rect.Height, Orientation.Vertical, BorderStyle);
 
-					driver.SetAttribute (new Attribute(Color.Red, Color.BrightYellow));
-					
+					driver.SetAttribute (new Attribute (Color.Red, Color.BrightYellow));
+
 					lc.Draw (null, rect);
 					DrawTitle (Child);
 				}

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

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Diagnostics;
 using System.Linq;
 using System.Reflection;
 using NStack;
@@ -402,7 +403,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		internal Rect NeedDisplay { get; private set; } = Rect.Empty;
+		internal Rect NeedDisplay { get; set; } = Rect.Empty;
 
 		// The frame for the object. Superview relative.
 		Rect frame;
@@ -499,8 +500,13 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </remarks>
 		public Rect Bounds {
+			// BUGBUG: This is crazy. Super restrictive that Bounds.Location is always Empty.
 			get => new Rect (Point.Empty, Frame.Size);
-			set => Frame = new Rect (frame.Location, value.Size);
+			// BUGBUG: This is even more crazy. This does not actually set Bounds, but Frame.
+			set {
+				Debug.Assert (value.Location.IsEmpty);
+				Frame = new Rect (frame.Location, value.Size);
+			}
 		}
 
 		Pos x, y;
@@ -654,6 +660,9 @@ namespace Terminal.Gui {
 		public bool SetMinWidthHeight ()
 		{
 			if (GetMinWidthHeight (out Size size)) {
+				// BUGBUG: Bounds ignores `value` on `set` so what this line of code actually does is
+				// `Frame = new Rect (frame.Location, value.Size);`
+				// ...But... `Bounds.get` simply returns `Frame` with a 0,0 for Location!
 				Bounds = new Rect (Bounds.Location, size);
 				TextFormatter.Size = GetBoundsTextFormatterSize ();
 				return true;
@@ -906,7 +915,7 @@ namespace Terminal.Gui {
 				}
 		}
 
-		internal bool ChildNeedsDisplay { get; private set; }
+		internal bool ChildNeedsDisplay { get; set; }
 
 		/// <summary>
 		/// Indicates that any child views (in the <see cref="Subviews"/> list) need to be repainted.
@@ -1571,7 +1580,7 @@ namespace Terminal.Gui {
 			ClearNeedsDisplay ();
 		}
 
-		Rect GetContainerBounds ()
+		internal Rect GetContainerBounds ()
 		{
 			var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);
 			var driverClip = Driver == null ? Rect.Empty : Driver.Clip;
@@ -2212,8 +2221,9 @@ namespace Terminal.Gui {
 			var r = new Rect (actX, actY, actW, actH);
 			if (Frame != r) {
 				Frame = r;
-				if (!SetMinWidthHeight ())
+				if (!SetMinWidthHeight ()) {
 					TextFormatter.Size = GetBoundsTextFormatterSize ();
+				}
 			}
 		}
 
@@ -2713,6 +2723,7 @@ namespace Terminal.Gui {
 				if (ForceValidatePosDim) {
 					aSize = SetWidthHeight (nBoundsSize);
 				} else {
+					// BUGBUG: `Bounds.set` ignores Location. This line also changes `Frame`
 					Bounds = new Rect (Bounds.X, Bounds.Y, nBoundsSize.Width, nBoundsSize.Height);
 				}
 			}
@@ -2734,6 +2745,7 @@ namespace Terminal.Gui {
 				height = rH;
 			}
 			if (aSize) {
+				// BUGBUG: `Bounds.set` ignores Location. This line also changes `Frame`
 				Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
 				TextFormatter.Size = GetBoundsTextFormatterSize ();
 			}
@@ -2980,7 +2992,7 @@ namespace Terminal.Gui {
 			Initialized?.Invoke (this, EventArgs.Empty);
 		}
 
-		bool CanBeVisible (View view)
+		internal bool CanBeVisible (View view)
 		{
 			if (!view.Visible) {
 				return false;

+ 221 - 8
Terminal.Gui/Core/View2.cs

@@ -1,27 +1,240 @@
-using System;
+using NStack;
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
-using System.Linq;
-using System.Reflection;
-using NStack;
+using Terminal.Gui.Graphs;
 
 namespace Terminal.Gui {
 
-	public class View2 : View, ISupportInitializeNotification {
-		public Thickness Margin { get; set; }
+	public class Container : View
+	{
+		public Container ()
+		{
+			IgnoreBorderPropertyOnRedraw = true;
+		}
 
-		void DrawThickness (Thickness thickness)
+		public virtual void OnDrawSubViews (Rect clipRect)
 		{
+			if (Subviews == null) {
+				return;
+			}
+
+			foreach (var view in Subviews) {
+				if (!view.NeedDisplay.IsEmpty || view.ChildNeedsDisplay || view.LayoutNeeded) {
+					if (view.Frame.IntersectsWith (clipRect)) {// && (view.Frame.IntersectsWith (boundsAdjustedForBorder) || boundsAdjustedForBorder.X < 0 || bounds.Y < 0)) {
+						if (view.LayoutNeeded) {
+							view.LayoutSubviews ();
+						}
+
+						// Draw the subview
+						// Use the view's bounds (view-relative; Location will always be (0,0)
+						if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
+							var rect = view.Bounds;
+							//view.OnDrawContent (rect);
+							view.Redraw (rect);
+							//view.OnDrawContentComplete (rect);
+						}
+					}
+					view.NeedDisplay = Rect.Empty;
+					view.ChildNeedsDisplay = false;
+				}
+			}
 
 		}
 
+		public override void OnDrawContent (Rect viewport)
+		{
+			if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
+				Clear (viewport);
+				SetChildNeedsDisplay ();
+				// Draw any Text
+				if (TextFormatter != null) {
+					TextFormatter.NeedsFormat = true;
+				}
+				Rect containerBounds = GetContainerBounds ();
+				TextFormatter?.Draw (ViewToScreen (viewport), HasFocus ? ColorScheme.Focus : GetNormalColor (),
+				    HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
+				    containerBounds);
+			}
+			//base.OnDrawContent (viewport);
+		}
+
+		public override void OnDrawContentComplete (Rect viewport)
+		{
+			//base.OnDrawContentComplete (viewport);
+		}
+
 		public override void Redraw (Rect bounds)
 		{
+			if (!CanBeVisible (this)) {
+				return;
+			}
+
+			if (ColorScheme != null) {
+				Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+			}
+
+			OnDrawContent (bounds);
+			OnDrawSubViews (bounds);
+			OnDrawContentComplete (bounds);
+		}
+
+	}
+
+	public class Frame : Container {
+		public Label DiagnosticsLabel { get; set; }
+		public BorderStyle BorderStyle { get; set; } = BorderStyle.None;
+
+		public Frame ()
+		{
+			IgnoreBorderPropertyOnRedraw = true;
+
+			DiagnosticsLabel = new Label () {
+				AutoSize = false,
+				X = 0,
+				Y = Pos.AnchorEnd (1),
+				Width = Dim.Fill (),
+				TextAlignment = TextAlignment.Centered
+
+			};
+			Add (DiagnosticsLabel);
+			SetNeedsLayout ();
+		}
+
+		public Thickness Thickness { get; set; }
+
+		public override void OnDrawContent (Rect viewport)
+		{
+			// do nothing
+		}
+
+		public override void Redraw (Rect bounds)
+		{
+			if (ColorScheme != null) {
+				Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+			}
+
+			Thickness.Draw (Frame, $"{Text} {DiagnosticsLabel.Text}");
+			if (BorderStyle != BorderStyle.None) {
+				var lc = new LineCanvas ();
+				lc.AddLine (Frame.Location, Frame.Width - 1, Orientation.Horizontal, BorderStyle);
+				lc.AddLine (Frame.Location, Frame.Height - 1, Orientation.Vertical, BorderStyle);
+
+				lc.AddLine (new Point (Frame.X, Frame.Y + Frame.Height - 1), Frame.Width - 1, Orientation.Horizontal, BorderStyle);
+				lc.AddLine (new Point (Frame.X + Frame.Width - 1, Frame.Y), Frame.Height - 1, Orientation.Vertical, BorderStyle);
+				lc.Draw (this, Frame);
+				Driver.DrawWindowTitle (Frame, $"{Text} {Thickness}", 0, 0, 0, 0);
+			}
+
 			base.Redraw (bounds);
+		}
+	}
+
+	public class View2 : Container {
+		public Frame Margin { get; set; }
+		public new Frame Border { get; set; }
+		public Frame Padding{ get; set; }
+
+		public View2 ()
+		{
+			IgnoreBorderPropertyOnRedraw = true;
+			Margin = new Frame () {
+				Text = "Margin",
+				Thickness = new Thickness (15, 2, 15, 4),
+				ColorScheme = Colors.ColorSchemes ["Error"]
+			};
+			//Margin.DiagnosticsLabel.Text = "Margin";
+
+			Border = new Frame () {
+				Text = "Border",
+				BorderStyle = BorderStyle.Single,
+				Thickness = new Thickness (2),
+				ColorScheme = Colors.ColorSchemes ["Dialog"]
+			};
+
+			Padding = new Frame () {
+				Text = "Padding",
+				Thickness = new Thickness (3),
+				ColorScheme = Colors.ColorSchemes ["Toplevel"]
+			};
+			SetNeedsLayout ();
+		}
+
+		public override void LayoutSubviews ()
+		{
+			Margin.X = Frame.Location.X;
+			Margin.Y = Frame.Location.Y;
+			Margin.Width = Frame.Size.Width;
+			Margin.Height = Frame.Size.Height;
+			Margin.SetNeedsLayout ();
+			Margin.LayoutSubviews ();
+			Margin.SetNeedsDisplay ();
+
+			var border = Margin.Thickness.GetInnerRect (Frame);
+			Border.X = border.Location.X;
+			Border.Y = border.Location.Y;
+			Border.Width = border.Size.Width;
+			Border.Height = border.Size.Height;
+			Border.SetNeedsLayout ();
+			Border.LayoutSubviews ();
+			Border.SetNeedsDisplay ();
+
+			var padding = Border.Thickness.GetInnerRect (border);
+			Padding.X = padding.Location.X;
+			Padding.Y = padding.Location.Y;
+			Padding.Width = padding.Size.Width;
+			Padding.Height = padding.Size.Height;
+			Padding.SetNeedsLayout ();
+			Padding.LayoutSubviews ();
+			Padding.SetNeedsDisplay ();
 
-			DrawThickness (Margin);
+			Bounds = Padding.Thickness.GetInnerRect (padding);
+
+			base.LayoutSubviews ();
 		}
 
+		public virtual void OnDrawFrames (Rect frame)
+		{
+			Margin.Redraw (Margin.Bounds);
+			Border.Redraw (Border.Bounds);
+			Padding.Redraw (Border.Bounds);
+
+			var border = Margin.Thickness.GetInnerRect (frame);
+			var padding = Border.Thickness.GetInnerRect (border);
+			var content = Padding.Thickness.GetInnerRect (padding);
+
+			// Draw the diagnostics label on the bottom of the content
+			var tf = new TextFormatter () {
+				Text = "Content",
+				Alignment = TextAlignment.Centered,
+				VerticalAlignment = VerticalTextAlignment.Bottom
+			};
+			tf.Draw (content, ColorScheme.Normal, ColorScheme.Normal);
+		}
+
+		public override void Redraw (Rect bounds)
+		{
+			if (!CanBeVisible (this)) {
+				return;
+			}
+
+			if (ColorScheme != null) {
+				Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+			}
 
+			OnDrawFrames (Frame);
+			base.Redraw (bounds);
+		}
+
+		protected override void Dispose (bool disposing)
+		{
+			base.Dispose (disposing);
+			Margin?.Dispose ();
+			Margin = null;
+			Border?.Dispose ();
+			Border = null;
+			Padding?.Dispose ();
+			Padding = null;
+		}
 	}
 }

+ 9 - 9
UICatalog/Scenarios/TileViewNesting.cs

@@ -90,9 +90,9 @@ namespace UICatalog.Scenarios {
 		{
 			int numberOfViews = GetNumberOfViews ();
 
-			bool titles = cbTitles.Checked;
-			bool border = cbBorder.Checked;
-			bool startHorizontal = cbHorizontal.Checked;
+			bool titles = cbTitles.Checked.Value;
+			bool border = cbBorder.Checked.Value;
+			bool startHorizontal = cbHorizontal.Checked.Value;
 
 			workArea.RemoveAll ();
 			
@@ -105,9 +105,9 @@ namespace UICatalog.Scenarios {
 					Terminal.Gui.Graphs.Orientation.Vertical);
 
 			root.Tiles.ElementAt(0).ContentView.Add (CreateContentControl (1));
-			root.Tiles.ElementAt(0).Title = cbTitles.Checked ? $"View 1" : string.Empty;
+			root.Tiles.ElementAt(0).Title = cbTitles.Checked.Value ? $"View 1" : string.Empty;
 			root.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (2));
-			root.Tiles.ElementAt(1).Title = cbTitles.Checked ? $"View 2" : string.Empty;
+			root.Tiles.ElementAt(1).Title = cbTitles.Checked.Value ? $"View 2" : string.Empty;
 			
 
 			root.Border.BorderStyle = border ? BorderStyle.Rounded : BorderStyle.None;
@@ -133,7 +133,7 @@ namespace UICatalog.Scenarios {
 
 		private View CreateContentControl (int number)
 		{
-			return cbUseLabels.Checked ?
+			return cbUseLabels.Checked.Value ?
 				CreateLabelView (number) :
 				CreateTextView (number);
 		}
@@ -200,7 +200,7 @@ namespace UICatalog.Scenarios {
 
 			// During splitting the old Title will have been migrated to View1 so we only need
 			// to set the Title on View2 (the one that gets our new TextView)
-			newView.Tiles.ElementAt(1).Title = cbTitles.Checked ? $"View {viewsCreated}" : string.Empty;
+			newView.Tiles.ElementAt(1).Title = cbTitles.Checked.Value ? $"View {viewsCreated}" : string.Empty;
 
 			// Flip orientation
 			newView.Orientation = to.Orientation == Orientation.Vertical ?
@@ -219,8 +219,8 @@ namespace UICatalog.Scenarios {
 				Orientation = orientation
 			};
 
-			toReturn.Tiles.ElementAt(0).Title = cbTitles.Checked ? $"View {titleNumber}" : string.Empty;
-			toReturn.Tiles.ElementAt (1).Title = cbTitles.Checked ? $"View {titleNumber + 1}" : string.Empty;
+			toReturn.Tiles.ElementAt(0).Title = cbTitles.Checked.Value ? $"View {titleNumber}" : string.Empty;
+			toReturn.Tiles.ElementAt (1).Title = cbTitles.Checked.Value ? $"View {titleNumber + 1}" : string.Empty;
 
 			return toReturn;
 		}

+ 45 - 0
UICatalog/Scenarios/View2Experiment.cs

@@ -0,0 +1,45 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "View2Experiment", Description: "View2 Experiment")]
+	[ScenarioCategory ("Controls")]
+	public class View2Experiment : Scenario {
+		public override void Init (ColorScheme colorScheme)
+		{
+			Application.Init ();
+
+			Application.Top.ColorScheme = Colors.Base;
+		}
+
+		public override void Setup ()
+		{
+			// Put your scenario code here, e.g.
+			var newFrameView = new View2 () {
+				X = 4,
+				Y = 4,
+				Height = Dim.Fill (4),
+				Width = Dim.Fill (4)
+			};
+
+			var label = new Label () {
+				Text = "Label: ",
+				AutoSize = true,
+				X = 2,
+				Y = 2
+			};
+			newFrameView.Add (label);
+
+			var edit = new TextField () {
+				Text = "Edit me",
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = Dim.Fill (4),
+				Height = 1
+			};
+			newFrameView.Add (edit);
+
+			Application.Top.Add (newFrameView);
+
+		}
+	}
+}