浏览代码

attempting to merge

Tig Kindel 2 年之前
父节点
当前提交
b7fe14ecff

+ 0 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -1013,8 +1013,6 @@ namespace Terminal.Gui {
 				return Curses.COLOR_YELLOW | Curses.A_BOLD | Curses.COLOR_GRAY;
 			case Color.White:
 				return Curses.COLOR_WHITE | Curses.A_BOLD | Curses.COLOR_GRAY;
-			case Color.Invalid:
-				return Curses.COLOR_BLACK; 
 			}
 			throw new ArgumentException ("Invalid color code");
 		}

+ 5 - 2
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -287,6 +287,9 @@ namespace Terminal.Gui {
 			position = new Point (csbi.srWindow.Left, csbi.srWindow.Top);
 			SetConsoleOutputWindow (csbi);
 			var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0));
+			if (!SetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) {
+				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+			}
 			if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) {
 				throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
 			}
@@ -1463,7 +1466,7 @@ namespace Terminal.Gui {
 
 				CurrentAttribute = MakeColor (Color.White, Color.Black);
 				InitalizeColorSchemes ();
-				
+
 				ResizeScreen ();
 				UpdateOffScreen ();
 			} catch (Win32Exception e) {
@@ -1571,7 +1574,7 @@ namespace Terminal.Gui {
 			if (runeWidth < 0 || runeWidth > 0) {
 				ccol++;
 			}
-			
+
 			if (runeWidth > 1) {
 				if (validClip && ccol < Clip.Right) {
 					position = GetOutputBufferPosition ();

+ 4 - 3
Terminal.Gui/Core/Border.cs

@@ -715,13 +715,14 @@ namespace Terminal.Gui {
 
 					lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, BorderStyle);
 					lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, BorderStyle);
-
+					
 					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));
-					
-					lc.Draw (null, rect);
+					foreach (var p in lc.GenerateImage (rect)) {
+						Child.AddRune (p.Key.X, p.Key.Y, p.Value);
+					}
 					DrawTitle (Child);
 				}
 			}

+ 21 - 29
Terminal.Gui/Core/ConsoleDriver.cs

@@ -15,7 +15,7 @@ namespace Terminal.Gui {
 	/// Colors that can be used to set the foreground and background colors in console applications.
 	/// </summary>
 	/// <remarks>
-	/// The <see cref="Color.Invalid"/> value indicates either no-color has been set or the color is invalid.
+	/// The <see cref="Attribute.HasValidColors"/> value indicates either no-color has been set or the color is invalid.
 	/// </remarks>
 	public enum Color {
 		/// <summary>
@@ -81,11 +81,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// The White color.
 		/// </summary>
-		White,
-		/// <summary>
-		/// Indicates an invalid or un-set color value. 
-		/// </summary>
-		Invalid = -1
+		White
 	}
 
 	/// <summary>
@@ -177,7 +173,7 @@ namespace Terminal.Gui {
 	public struct Attribute {
 		/// <summary>
 		/// The <see cref="ConsoleDriver"/>-specific color attribute value. If <see cref="Initialized"/> is <see langword="false"/> 
-		/// the value of this property is invalid (typcially because the Attribute was created before a driver was loaded)
+		/// the value of this property is invalid (typically because the Attribute was created before a driver was loaded)
 		/// and the attribute should be re-made (see <see cref="Make(Color, Color)"/>) before it is used.
 		/// </summary>
 		public int Value { get; }
@@ -199,8 +195,8 @@ namespace Terminal.Gui {
 		/// <param name="value">Value.</param>
 		public Attribute (int value)
 		{
-			Color foreground = Color.Invalid;
-			Color background = Color.Invalid;
+			Color foreground = default;
+			Color background = default;
 
 			Initialized = false;
 			if (Application.Driver != null) {
@@ -280,9 +276,9 @@ namespace Terminal.Gui {
 		{
 			if (Application.Driver == null) {
 				// Create the attribute, but show it's not been initialized
-				var a = new Attribute (-1, foreground, background);
-				a.Initialized = false;
-				return a;
+				return new Attribute (-1, foreground, background) {
+					Initialized = false
+				};
 			}
 			return Application.Driver.MakeAttribute (foreground, background);
 		}
@@ -299,10 +295,10 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// If <see langword="true"/> the attribute has been initialzed by a <see cref="ConsoleDriver"/> and 
+		/// If <see langword="true"/> the attribute has been initialized by a <see cref="ConsoleDriver"/> and 
 		/// thus has <see cref="Value"/> that is valid for that driver. If <see langword="false"/> the <see cref="Foreground"/>
-		/// and <see cref="Background"/> colors may have been set (see <see cref="Color.Invalid"/>) but
-		/// the attribute has not been mapped to a <see cref="ConsoleDriver"/> specific color value. 
+		/// and <see cref="Background"/> colors may have been set '-1' but
+		/// the attribute has not been mapped to a <see cref="ConsoleDriver"/> specific color value.
 		/// </summary>
 		/// <remarks>
 		/// Attributes that have not been initialized must eventually be initialized before being passed to a driver.
@@ -310,14 +306,10 @@ namespace Terminal.Gui {
 		public bool Initialized { get; internal set; }
 
 		/// <summary>
-		/// Returns <see langword="true"/> if the Atrribute is valid (both foreground and background have valid color values).
+		/// Returns <see langword="true"/> if the Attribute is valid (both foreground and background have valid color values).
 		/// </summary>
 		/// <returns></returns>
-		public bool HasValidColors {
-			get {
-				return Foreground != Color.Invalid && Background != Color.Invalid;
-			}
-		}
+		public bool HasValidColors { get => (int)Foreground > -1 && (int)Background > -1; }
 	}
 
 	/// <summary>
@@ -329,7 +321,7 @@ namespace Terminal.Gui {
 	/// See also: <see cref="Colors.ColorSchemes"/>.
 	/// </remarks>
 	public class ColorScheme : IEquatable<ColorScheme> {
-		Attribute _normal = new Attribute(Color.White, Color.Black);
+		Attribute _normal = new Attribute (Color.White, Color.Black);
 		Attribute _focus = new Attribute (Color.White, Color.Black);
 		Attribute _hotNormal = new Attribute (Color.White, Color.Black);
 		Attribute _hotFocus = new Attribute (Color.White, Color.Black);
@@ -520,13 +512,13 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Creates a new dictionary of new <see cref="ColorScheme"/> objects.
 		/// </summary>
-		public static Dictionary<string, ColorScheme> Create () 
+		public static Dictionary<string, ColorScheme> Create ()
 		{
 			// Use reflection to dynamically create the default set of ColorSchemes from the list defined 
 			// by the class. 
 			return typeof (Colors).GetProperties ()
 				.Where (p => p.PropertyType == typeof (ColorScheme))
-				.Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme()))
+				.Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme ()))
 				.ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ());
 		}
 
@@ -881,7 +873,7 @@ namespace Terminal.Gui {
 		/// The current attribute the driver is using. 
 		/// </summary>
 		public virtual Attribute CurrentAttribute {
-			get => currentAttribute; 
+			get => currentAttribute;
 			set {
 				if (!value.Initialized && value.HasValidColors && Application.Driver != null) {
 					CurrentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background);
@@ -1468,13 +1460,13 @@ namespace Terminal.Gui {
 		public abstract Attribute MakeColor (Color foreground, Color background);
 
 		/// <summary>
-		/// Ensures all <see cref="Attribute"/>s in <see cref="Colors.ColorSchemes"/> are correclty 
-		/// initalized by the driver.
+		/// Ensures all <see cref="Attribute"/>s in <see cref="Colors.ColorSchemes"/> are correctly 
+		/// initialized by the driver.
 		/// </summary>
 		/// <param name="supportsColors">Flag indicating if colors are supported (not used).</param>
 		public void InitalizeColorSchemes (bool supportsColors = true)
 		{
-			// Ensure all Attributes are initlaized by the driver
+			// Ensure all Attributes are initialized by the driver
 			foreach (var s in Colors.ColorSchemes) {
 				s.Value.Initialize ();
 			}
@@ -1485,7 +1477,7 @@ namespace Terminal.Gui {
 
 
 			// Define the default color theme only if the user has not defined one.
-			
+
 			Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black);
 			Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
 			Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black);

+ 15 - 35
Terminal.Gui/Core/Graphs/LineCanvas.cs

@@ -12,6 +12,7 @@ namespace Terminal.Gui.Graphs {
 	public class LineCanvas {
 
 
+
 		private List<StraightLine> lines = new List<StraightLine> ();
 
 		Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new Dictionary<IntersectionRuneType, IntersectionRuneResolver> {
@@ -48,21 +49,22 @@ namespace Terminal.Gui.Graphs {
 		}
 		/// <summary>
 		/// Evaluate all currently defined lines that lie within 
-		/// <paramref name="bounds"/> and generate a 'bitmap' that
+		/// <paramref name="inArea"/> and map that
 		/// shows what characters (if any) should be rendered at each
 		/// point so that all lines connect up correctly with appropriate
 		/// intersection symbols.
 		/// <returns></returns>
 		/// </summary>
-		/// <param name="bounds"></param>
-		/// <returns>Map as 2D array where first index is rows and second is column</returns>
-		public Rune? [,] GenerateImage (Rect inArea)
+		/// <param name="inArea"></param>
+		/// <returns>Mapping of all the points within <paramref name="inArea"/> to
+		/// line or intersection runes which should be drawn there.</returns>
+		public Dictionary<Point,Rune> GenerateImage (Rect inArea)
 		{
-			Rune? [,] canvas = new Rune? [inArea.Height, inArea.Width];
+			var map = new Dictionary<Point,Rune>();
 
 			// walk through each pixel of the bitmap
-			for (int y = 0; y < inArea.Height; y++) {
-				for (int x = 0; x < inArea.Width; x++) {
+			for (int y = inArea.Y; y < inArea.Height; y++) {
+				for (int x = inArea.X; x < inArea.Width; x++) {
 
 					var intersects = lines
 						.Select (l => l.Intersects (inArea.X + x, inArea.Y + y))
@@ -70,38 +72,16 @@ namespace Terminal.Gui.Graphs {
 						.ToArray ();
 
 					// TODO: use Driver and LineStyle to map
-					canvas [y, x] = GetRuneForIntersects (Application.Driver, intersects);
-
-				}
-			}
-
-			return canvas;
-		}
+					var rune = GetRuneForIntersects (Application.Driver, intersects);
 
-		/// <summary>
-		/// Draws all the lines that lie within the <paramref name="bounds"/> onto
-		/// the <paramref name="view"/> client area.  This method should be called from
-		/// <see cref="View.Redraw(Rect)"/>.
-		/// </summary>
-		/// <param name="view"></param>
-		/// <param name="bounds"></param>
-		public void Draw (View view, Rect bounds)
-		{
-			var runes = GenerateImage (bounds);
-
-			for (int y = bounds.Y; y < bounds.Y + bounds.Height; y++) {
-				for (int x = bounds.X; x < bounds.X + bounds.Width; x++) {
-					var rune = runes [y - bounds.Y, x - bounds.X];
-					if (rune.HasValue) {
-						if (view != null) {
-							view.AddRune (x - bounds.X, y - bounds.Y, rune.Value);
-						} else {
-							Application.Driver.Move (x, y);
-							Application.Driver.AddRune (rune.Value);
-						}
+					if(rune != null)
+					{
+						map.Add(new Point(x,y),rune.Value);
 					}
 				}
 			}
+
+			return map;
 		}
 
 		private abstract class IntersectionRuneResolver {

+ 4 - 3
Terminal.Gui/Views/FrameView.cs

@@ -262,9 +262,10 @@ namespace Terminal.Gui {
 				//}
 
 				Driver.SetAttribute (ColorScheme.Normal);
-				lc.Draw (this, bounds);
-
-
+				foreach (var p in lc.GenerateImage (bounds)) {
+					this.AddRune (p.Key.X, p.Key.Y, p.Value);
+				}
+				
 				// Redraw the lines so that focus/drag symbol renders
 				foreach (var subview in contentView.Subviews) {
 					//	line.DrawSplitterSymbol ();

+ 4 - 2
Terminal.Gui/Views/TileView.cs

@@ -459,8 +459,10 @@ namespace Terminal.Gui {
 			}
 
 			Driver.SetAttribute (ColorScheme.Normal);
-			lc.Draw (this, bounds);
-
+			foreach (var p in lc.GenerateImage (bounds)) {
+				this.AddRune (p.Key.X, p.Key.Y, p.Value);
+			}
+			
 			// Redraw the lines so that focus/drag symbol renders
 			foreach (var line in allLines) {
 				line.DrawSplitterSymbol ();

+ 234 - 0
UICatalog/Scenarios/Animation.cs

@@ -0,0 +1,234 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.ColorSpaces;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using Attribute = Terminal.Gui.Attribute;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Animation", Description: "Demonstration of how to render animated images with threading.")]
+	[ScenarioCategory ("Colors")]
+	public class Animation : Scenario
+	{
+		private bool isDisposed;
+
+		public override void Setup ()
+		{
+			base.Setup ();
+
+
+			var imageView = new ImageView () {
+				Width = Dim.Fill(),
+				Height = Dim.Fill()-2,
+			};
+
+			Win.Add (imageView);
+
+			var lbl = new Label("Image by Wikiscient"){
+				Y = Pos.AnchorEnd(2)
+			};
+			Win.Add(lbl);
+
+			var lbl2 = new Label("https://commons.wikimedia.org/wiki/File:Spinning_globe.gif"){
+				Y = Pos.AnchorEnd(1)
+			};
+			Win.Add(lbl2);
+
+			var dir = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
+			
+			var f = new FileInfo(
+				Path.Combine(dir.FullName,"Scenarios","Spinning_globe_dark_small.gif"));
+			if(!f.Exists)
+			{
+				MessageBox.ErrorQuery("Could not find gif","Could not find "+ f.FullName,"Ok");
+				return;
+			}
+
+			imageView.SetImage(Image.Load<Rgba32> (File.ReadAllBytes (f.FullName)));
+
+			Task.Run(()=>{
+				while(!isDisposed)
+				{
+					// When updating from a Thread/Task always use Invoke
+					Application.MainLoop.Invoke(()=>
+					{
+						imageView.NextFrame();
+						imageView.SetNeedsDisplay();
+					});
+
+					Task.Delay(100).Wait();
+				}
+			});
+		}
+
+		protected override void Dispose(bool disposing)
+		{
+			isDisposed = true;
+			base.Dispose();
+		}
+
+		// This is a C# port of https://github.com/andraaspar/bitmap-to-braille by Andraaspar
+
+		/// <summary>
+		/// Renders an image as unicode Braille.
+		/// </summary>
+		public class BitmapToBraille
+		{
+
+			public const int CHAR_WIDTH = 2;
+			public const int CHAR_HEIGHT = 4;
+
+			const string CHARS = " ⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿";
+
+			public int WidthPixels {get; }
+			public int HeightPixels { get; }
+
+			public Func<int,int,bool> PixelIsLit {get;}
+
+			public BitmapToBraille (int widthPixels, int heightPixels, Func<int, int, bool> pixelIsLit)
+			{
+				WidthPixels = widthPixels;
+				HeightPixels = heightPixels;
+				PixelIsLit = pixelIsLit;
+			}
+
+			public string GenerateImage() {
+				int imageHeightChars = (int) Math.Ceiling((double)HeightPixels / CHAR_HEIGHT);
+				int imageWidthChars = (int) Math.Ceiling((double)WidthPixels / CHAR_WIDTH);
+
+				var result = new StringBuilder();
+
+				for (int y = 0; y < imageHeightChars; y++) {
+					
+					for (int x = 0; x < imageWidthChars; x++) {
+						int baseX = x * CHAR_WIDTH;
+						int baseY = y * CHAR_HEIGHT;
+
+						int charIndex = 0;
+						int value = 1;
+
+						for (int charX = 0; charX < CHAR_WIDTH; charX++) {
+							for (int charY = 0; charY < CHAR_HEIGHT; charY++) {
+								int bitmapX = baseX + charX;
+								int bitmapY = baseY + charY;
+								bool pixelExists = bitmapX < WidthPixels && bitmapY < HeightPixels;
+
+								if (pixelExists && PixelIsLit(bitmapX, bitmapY)) {
+									charIndex += value;
+								}
+								value *= 2;
+							}
+						}
+
+						result.Append(CHARS[charIndex]);
+					}
+					result.Append('\n');
+				}
+				return result.ToString().TrimEnd();
+			}  
+		}
+
+		class ImageView : View {
+			private int frameCount;
+			private int currentFrame = 0;
+
+			private Image<Rgba32>[] fullResImages;
+			private Image<Rgba32>[] matchSizes;
+			private string[] brailleCache;
+
+			Rect oldSize = Rect.Empty;
+
+
+			internal void SetImage (Image<Rgba32> image)
+			{
+				frameCount = image.Frames.Count;
+
+				fullResImages = new Image<Rgba32>[frameCount];
+				matchSizes = new Image<Rgba32>[frameCount];
+				brailleCache = new string[frameCount];
+
+				for(int i=0;i<frameCount-1;i++)
+				{
+					fullResImages[i] = image.Frames.ExportFrame(0);
+				}
+				fullResImages[frameCount-1] = image;
+
+				this.SetNeedsDisplay ();
+			}
+			public void NextFrame()
+			{
+				currentFrame = (currentFrame+1)%frameCount;
+			}
+
+			public override void Redraw (Rect bounds)
+			{
+				base.Redraw (bounds);
+
+				if(oldSize != bounds)
+				{
+					// Invalidate cached images now size has changed
+					matchSizes = new Image<Rgba32>[frameCount];
+					brailleCache = new string[frameCount];
+					oldSize = bounds;
+				}
+
+				var imgScaled = matchSizes[currentFrame];
+				var braille = brailleCache[currentFrame];
+
+				if(imgScaled == null)
+				{
+					var imgFull = fullResImages[currentFrame];
+				
+					// keep aspect ratio
+					var newSize = Math.Min(bounds.Width,bounds.Height);
+
+					// generate one
+					matchSizes[currentFrame] = imgScaled = imgFull.Clone (
+						x => x.Resize (
+							 newSize * BitmapToBraille.CHAR_HEIGHT,
+							 newSize * BitmapToBraille.CHAR_HEIGHT));
+				}
+
+				if(braille == null)
+				{
+					brailleCache[currentFrame] = braille = GetBraille(matchSizes[currentFrame]);
+				}
+
+
+				var lines = braille.Split('\n');
+
+				for(int y = 0; y < lines.Length;y++)
+				{
+					var line = lines[y];
+					for(int x = 0;x<line.Length ;x++)
+					{
+						AddRune(x,y,line[x]);
+					}
+				}
+			}
+
+			private string GetBraille (Image<Rgba32> img)
+			{
+				var braille = new BitmapToBraille(
+					img.Width,
+					img.Height,
+					(x,y)=>IsLit(img,x,y));
+
+				return braille.GenerateImage();
+			}
+
+			private bool IsLit (Image<Rgba32> img, int x, int y)
+			{
+				var rgb = img[x,y];
+				return rgb.R + rgb.G + rgb.B > 50;
+			}
+		}
+	}
+}

+ 3 - 2
UICatalog/Scenarios/BasicColors.cs

@@ -13,13 +13,14 @@ namespace UICatalog.Scenarios {
 			var colors = System.Enum.GetValues (typeof (Color));
 
 			foreach (Color bg in colors) {
+				Attribute attr = new Attribute (bg, colors.Length - 1 - bg);
 				var vl = new Label (bg.ToString (), TextDirection.TopBottom_LeftRight) {
 					X = vx,
 					Y = 0,
 					Width = 1,
 					Height = 13,
 					VerticalTextAlignment = VerticalTextAlignment.Bottom,
-					ColorScheme = new ColorScheme () { Normal = new Attribute (bg, colors.Length - 1 - bg) }
+					ColorScheme = new ColorScheme () { Normal = attr }
 				};
 				Win.Add (vl);
 				var hl = new Label (bg.ToString ()) {
@@ -28,7 +29,7 @@ namespace UICatalog.Scenarios {
 					Width = 13,
 					Height = 1,
 					TextAlignment = TextAlignment.Right,
-					ColorScheme = new ColorScheme () { Normal = new Attribute (bg, colors.Length - 1 - bg) }
+					ColorScheme = new ColorScheme () { Normal = attr }
 				};
 				Win.Add (hl);
 				vx++;

+ 13 - 2
UICatalog/Scenarios/LineDrawing.cs

@@ -71,7 +71,12 @@ namespace UICatalog.Scenarios {
 				base.Redraw (bounds);
 
 				Driver.SetAttribute (new Terminal.Gui.Attribute (Color.DarkGray, ColorScheme.Normal.Background));
-				grid.Draw (this, bounds);
+				
+				
+				foreach(var p in grid.GenerateImage(bounds))
+				{
+					this.AddRune(p.Key.X,p.Key.Y,p.Value);
+				}
 
 				foreach (var swatch in swatches) {
 					Driver.SetAttribute (new Terminal.Gui.Attribute (swatch.Value, ColorScheme.Normal.Background));
@@ -151,7 +156,13 @@ namespace UICatalog.Scenarios {
 				foreach (var kvp in colorLayers) {
 
 					Driver.SetAttribute (new Terminal.Gui.Attribute (kvp.Key, ColorScheme.Normal.Background));
-					canvases [kvp.Value].Draw (this, bounds);
+
+					var canvas = canvases [kvp.Value];
+
+					foreach(var p in canvas.GenerateImage(bounds))
+					{
+						this.AddRune(p.Key.X,p.Key.Y,p.Value);
+					}
 				}
 			}
 			public override bool OnMouseEvent (MouseEvent mouseEvent)

二进制
UICatalog/Scenarios/Spinning_globe_dark_small.gif


+ 3 - 0
UICatalog/Scenarios/spinning-globe-attribution.txt

@@ -0,0 +1,3 @@
+Author: Wikiscient
+Date: 16 September 2011
+Original Url: https://commons.wikimedia.org/wiki/File:Spinning_globe.gif

+ 4 - 0
UICatalog/UICatalog.csproj

@@ -19,6 +19,10 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
+  <None Update="./Scenarios/Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
     <PackageReference Include="CsvHelper" Version="30.0.1" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
   </ItemGroup>

+ 52 - 32
UnitTests/Core/LineCanvasTests.cs

@@ -1,4 +1,7 @@
-using Terminal.Gui.Graphs;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Terminal.Gui.Graphs;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -157,34 +160,6 @@ namespace Terminal.Gui.CoreTests {
 			TestHelpers.AssertDriverContentsAre (looksLike, output);
 		}
 
-		[Fact, AutoInitShutdown]
-		public void TestLineCanvas_ScreenCoords ()
-		{
-			var x = 3;
-			var y = 5;
-			var screenCanvas = new LineCanvas ();
-
-			// outer box
-			screenCanvas.AddLine (new Point (x, y), 9, Orientation.Horizontal, BorderStyle.Single);
-			screenCanvas.AddLine (new Point (x + 9, y + 0), 4, Orientation.Vertical, BorderStyle.Single);
-			screenCanvas.AddLine (new Point (x + 9, y + 4), -9, Orientation.Horizontal, BorderStyle.Single);
-			screenCanvas.AddLine (new Point (x + 0, y + 4), -4, Orientation.Vertical, BorderStyle.Single);
-
-			screenCanvas.AddLine (new Point (x + 5, y + 0), 4, Orientation.Vertical, BorderStyle.Single);
-			screenCanvas.AddLine (new Point (x + 0, y + 2), 9, Orientation.Horizontal, BorderStyle.Single);
-
-			screenCanvas.Draw (null, new Rect (x, y, 10, 5));
-
-			string looksLike =
-@$"    
-{new string ('\n', y)}{new string (' ', x)}┌────┬───┐
-{new string (' ', x)}│    │   │
-{new string (' ', x)}├────┼───┤
-{new string (' ', x)}│    │   │
-{new string (' ', x)}└────┴───┘";
-			TestHelpers.AssertDriverContentsAre (looksLike, output);
-		}
-
 		/// <summary>
 		/// Demonstrates when <see cref="BorderStyle.Rounded"/> corners are used. Notice how
 		/// not all lines declare rounded.  If there are 1+ lines intersecting and a corner is
@@ -309,16 +284,61 @@ namespace Terminal.Gui.CoreTests {
 			TestHelpers.AssertDriverContentsAre (looksLike, output);
 		}
 
-		private View GetCanvas (out LineCanvas canvas, int x = 0, int y = 0)
+		[Fact, AutoInitShutdown]
+		public void TestLineCanvas_LeaveMargin_Top1_Left1 ()
+		{
+			// Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1)
+			var v = GetCanvas (out var canvas, 1, 1);
+
+			// outer box
+			canvas.AddLine (new Point (0, 0), 8, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (8, 0), 3, Orientation.Vertical, BorderStyle.Single);
+			canvas.AddLine (new Point (8, 3), -8, Orientation.Horizontal, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 3), -3, Orientation.Vertical, BorderStyle.Single);
+
+
+			canvas.AddLine (new Point (5, 0), 3, Orientation.Vertical, BorderStyle.Single);
+			canvas.AddLine (new Point (0, 2), 8, Orientation.Horizontal, BorderStyle.Single);
+
+			v.Redraw (v.Bounds);
+
+			string looksLike =
+@"
+ ┌────┬──┐
+ │    │  │
+ ├────┼──┤
+ └────┴──┘
+";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
+
+
+		/// <summary>
+		/// Creates a new <see cref="View"/> into which a <see cref="LineCanvas"/> is rendered
+		/// at <see cref="View.DrawContentComplete"/> time.
+		/// </summary>
+		/// <param name="canvas">The <see cref="LineCanvas"/> you can draw into.</param>
+		/// <param name="offsetX">How far to offset drawing in X</param>
+		/// <param name="offsetY">How far to offset drawing in Y</param>
+		/// <returns></returns>
+		private View GetCanvas (out LineCanvas canvas, int offsetX = 0, int offsetY = 0)
 		{
 			var v = new View {
 				Width = 10,
 				Height = 5,
-				Bounds = new Rect (x, y, 10, 5)
+				Bounds = new Rect (0, 0, 10, 5)
 			};
 
 			var canvasCopy = canvas = new LineCanvas ();
-			v.DrawContentComplete += (r) => canvasCopy.Draw (v, v.Bounds);
+			v.DrawContentComplete += (r) => {
+					foreach(var p in canvasCopy.GenerateImage(v.Bounds))
+					{
+						v.AddRune(
+							offsetX + p.Key.X,
+							offsetY + p.Key.Y,
+							p.Value);
+					}
+				};
 
 			return v;
 		}

+ 3 - 3
UnitTests/Drivers/AttributeTests.cs

@@ -218,13 +218,13 @@ namespace Terminal.Gui.DriverTests {
 			attr = new Attribute (Color.Red, Color.Green);
 			Assert.True (attr.HasValidColors);
 
-			attr = new Attribute (Color.Red, Color.Invalid);
+			attr = new Attribute (Color.Red, (Color)(-1));
 			Assert.False (attr.HasValidColors);
 
-			attr = new Attribute (Color.Invalid, Color.Green);
+			attr = new Attribute ((Color)(-1), Color.Green);
 			Assert.False (attr.HasValidColors);
 
-			attr = new Attribute (Color.Invalid, Color.Invalid);
+			attr = new Attribute ((Color)(-1), (Color)(-1));
 			Assert.False (attr.HasValidColors);
 		}
 	}

+ 29 - 0
UnitTests/Drivers/ColorTests.cs

@@ -44,5 +44,34 @@ namespace Terminal.Gui.DriverTests {
 			lbl.Redraw (lbl.Bounds);
 		}
 
+		[Fact]
+		public void TestAllColors ()
+		{
+			var colors = System.Enum.GetValues (typeof (Color));
+			Attribute [] attrs = new Attribute [colors.Length];
+
+			int idx = 0;
+			foreach (Color bg in colors) {
+				attrs [idx] = new Attribute (bg, colors.Length - 1 - bg);
+				idx++;
+			}
+			Assert.Equal (16, attrs.Length);
+			Assert.Equal (new Attribute (Color.Black, Color.White), attrs [0]);
+			Assert.Equal (new Attribute (Color.Blue, Color.BrightYellow), attrs [1]);
+			Assert.Equal (new Attribute (Color.Green, Color.BrightMagenta), attrs [2]);
+			Assert.Equal (new Attribute (Color.Cyan, Color.BrightRed), attrs [3]);
+			Assert.Equal (new Attribute (Color.Red, Color.BrightCyan), attrs [4]);
+			Assert.Equal (new Attribute (Color.Magenta, Color.BrightGreen), attrs [5]);
+			Assert.Equal (new Attribute (Color.Brown, Color.BrightBlue), attrs [6]);
+			Assert.Equal (new Attribute (Color.Gray, Color.DarkGray), attrs [7]);
+			Assert.Equal (new Attribute (Color.DarkGray, Color.Gray), attrs [8]);
+			Assert.Equal (new Attribute (Color.BrightBlue, Color.Brown), attrs [9]);
+			Assert.Equal (new Attribute (Color.BrightGreen, Color.Magenta), attrs [10]);
+			Assert.Equal (new Attribute (Color.BrightCyan, Color.Red), attrs [11]);
+			Assert.Equal (new Attribute (Color.BrightRed, Color.Cyan), attrs [12]);
+			Assert.Equal (new Attribute (Color.BrightMagenta, Color.Green), attrs [13]);
+			Assert.Equal (new Attribute (Color.BrightYellow, Color.Blue), attrs [14]);
+			Assert.Equal (new Attribute (Color.White, Color.Black), attrs [^1]);
+		}
 	}
 }