瀏覽代碼

Fixes remaining wide runes render issues.

BDisp 3 年之前
父節點
當前提交
4382a2c2c2

+ 12 - 4
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -70,7 +70,9 @@ namespace Terminal.Gui {
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 					Curses.move (crow, ccol);
 					Curses.attrset (curAtttib);
-				} else if (runeWidth < 2 && ccol < Cols - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					var curAtttib = currentAttribute;
 					Curses.attrset (contents [crow, ccol + 1, 1]);
@@ -78,9 +80,15 @@ namespace Terminal.Gui {
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
 					Curses.move (crow, ccol);
 					Curses.attrset (curAtttib);
+
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					Curses.addch ((int)(uint)' ');
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					Curses.addch ((int)(uint)rune);
+					contents [crow, ccol, 0] = (int)(uint)rune;
 				}
-				Curses.addch ((int)(uint)rune);
-				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 			} else
@@ -88,7 +96,7 @@ namespace Terminal.Gui {
 
 			ccol++;
 			if (runeWidth > 1) {
-				if (validClip) {
+				if (validClip && ccol < Clip.Right) {
 					contents [crow, ccol, 1] = currentAttribute;
 					contents [crow, ccol, 2] = 0;
 				}

+ 23 - 19
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -108,31 +108,34 @@ namespace Terminal.Gui {
 
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
-				} else if (runeWidth < 2 && ccol < Cols - 1
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
 					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
-				}
+					contents [crow, ccol + 1, 2] = 1;
 
-				contents [crow, ccol, 0] = (int)(uint)rune;
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					contents [crow, ccol, 0] = (int)(uint)rune;
+				}
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
+
 				dirtyLine [crow] = true;
 			} else
 				needMove = true;
 
 			ccol++;
 			if (runeWidth > 1) {
-				for (int i = 1; i < runeWidth; i++) {
-					if (validClip) {
-						contents [crow, ccol, 1] = currentAttribute;
-						contents [crow, ccol, 2] = 0;
-					} else {
-						break;
-					}
-					ccol++;
+				if (validClip && ccol < Clip.Right) {
+					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 2] = 0;
 				}
+				ccol++;
 			}
+
 			//if (ccol == Cols) {
 			//	ccol = 0;
 			//	if (crow + 1 < Rows)
@@ -248,17 +251,17 @@ namespace Terminal.Gui {
 
 			var savedRow = FakeConsole.CursorTop;
 			var savedCol = FakeConsole.CursorLeft;
-			for (int row = 0; row < rows; row++) {
+			var savedCursorVisible = FakeConsole.CursorVisible;
+			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row])
 					continue;
 				dirtyLine [row] = false;
-				for (int col = 0; col < cols; col++) {
+				for (int col = left; col < cols; col++) {
 					FakeConsole.CursorTop = row;
 					FakeConsole.CursorLeft = col;
 					for (; col < cols; col++) {
-						if (col > 0 && contents [row, col, 2] == 0
-							&& Rune.ColumnWidth ((char)contents [row, col - 1, 0]) > 1) {
-							FakeConsole.CursorLeft = col + 1;
+						if (contents [row, col, 2] == 0) {
+							FakeConsole.CursorLeft++;
 							continue;
 						}
 
@@ -273,6 +276,7 @@ namespace Terminal.Gui {
 			}
 			FakeConsole.CursorTop = savedRow;
 			FakeConsole.CursorLeft = savedCol;
+			FakeConsole.CursorVisible = savedCursorVisible;
 		}
 
 		public override void Refresh ()
@@ -549,13 +553,13 @@ namespace Terminal.Gui {
 			}
 
 			Clip = new Rect (0, 0, Cols, Rows);
-
-			contents = new int [Rows, Cols, 3];
-			dirtyLine = new bool [Rows];
 		}
 
 		public override void UpdateOffScreen ()
 		{
+			contents = new int [Rows, Cols, 3];
+			dirtyLine = new bool [Rows];
+
 			// Can raise an exception while is still resizing.
 			try {
 				for (int row = 0; row < rows; row++) {

+ 56 - 30
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -114,7 +114,7 @@ namespace Terminal.Gui {
 		int lastWindowHeight;
 		int largestWindowHeight;
 #if PROCESS_REQUEST
-		bool neededProcessRequest;
+				bool neededProcessRequest;
 #endif
 		public int NumberOfCSI { get; }
 
@@ -140,7 +140,7 @@ namespace Terminal.Gui {
 					inputReady.Reset ();
 				}
 #if PROCESS_REQUEST
-				neededProcessRequest = false;
+								neededProcessRequest = false;
 #endif
 				if (inputResultQueue.Count > 0) {
 					return inputResultQueue.Dequeue ();
@@ -205,10 +205,10 @@ namespace Terminal.Gui {
 						return;
 					}
 #if PROCESS_REQUEST
-					if (!neededProcessRequest) {
-						Console.Out.Write ("\x1b[6n");
-						neededProcessRequest = true;
-					}
+										if (!neededProcessRequest) {
+											Console.Out.Write ("\x1b[6n");
+											neededProcessRequest = true;
+										}
 #endif
 				}
 			}
@@ -469,7 +469,7 @@ namespace Terminal.Gui {
 			string value = "";
 			for (int i = 0; i < kChar.Length; i++) {
 				var c = kChar [i];
-				if (c == '[') {
+				if (c == '\u001b' || c == '[') {
 					foundPoint++;
 				} else if (foundPoint == 1 && c != ';' && c != '?') {
 					value += c.ToString ();
@@ -1242,12 +1242,18 @@ namespace Terminal.Gui {
 
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
-				} else if (runeWidth < 2 && ccol < Cols - 1
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
 					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+					contents [crow, ccol + 1, 2] = 1;
+
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					contents [crow, ccol, 0] = (int)(uint)rune;
 				}
-				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 
@@ -1256,7 +1262,7 @@ namespace Terminal.Gui {
 
 			ccol++;
 			if (runeWidth > 1) {
-				if (validClip) {
+				if (validClip && ccol < Clip.Right) {
 					contents [crow, ccol, 1] = currentAttribute;
 					contents [crow, ccol, 2] = 0;
 				}
@@ -1288,6 +1294,9 @@ namespace Terminal.Gui {
 			StopReportingMouseMoves ();
 			Console.ResetColor ();
 			Clear ();
+			//Set cursor key to cursor.
+			Console.Out.Write ("\x1b[?25h");
+			Console.Out.Flush ();
 		}
 
 		void Clear ()
@@ -1313,6 +1322,10 @@ namespace Terminal.Gui {
 		{
 			TerminalResized = terminalResized;
 
+			//Set cursor key to application.
+			Console.Out.Write ("\x1b[?25l");
+			Console.Out.Flush ();
+
 			Console.TreatControlCAsInput = true;
 
 			cols = Console.WindowWidth;
@@ -1455,28 +1468,41 @@ namespace Terminal.Gui {
 			}
 
 			int top = Top;
+			int left = Left;
 			int rows = Math.Min (Console.WindowHeight + top, Rows);
 			int cols = Cols;
+			System.Text.StringBuilder output = new System.Text.StringBuilder ();
+			var lastCol = left;
 
-			var savedCursorVisible = Console.CursorVisible = false;
+			Console.CursorVisible = false;
 			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row]) {
 					continue;
 				}
 				dirtyLine [row] = false;
-				System.Text.StringBuilder output = new System.Text.StringBuilder ();
-				for (int col = 0; col < cols; col++) {
+				output.Clear ();
+				for (int col = left; col < cols; col++) {
 					if (Console.WindowHeight > 0 && !SetCursorPosition (col, row)) {
 						return;
 					}
-					var lastCol = -1;
+					lastCol = left;
+					var outputWidth = 0;
 					for (; col < cols; col++) {
-						if (col > 0 && contents [row, col, 2] == 0
-							&& Rune.ColumnWidth ((char)contents [row, col - 1, 0]) > 1) {
-
-							if (col == cols - 1 && output.Length > 0) {
-								Console.CursorLeft = lastCol;
-								Console.Write (output);
+						if (contents [row, col, 2] == 0) {
+							if (output.Length > 0) {
+								if (col > 0 && Rune.ColumnWidth ((char)contents [row, col - 1, 0]) < 2) {
+									Console.CursorLeft = lastCol;
+									Console.CursorTop = row;
+									Console.Write (output);
+									output.Clear ();
+									lastCol += outputWidth;
+									outputWidth = 0;
+									if (lastCol + 1 < cols)
+										lastCol++;
+								}
+							} else {
+								if (lastCol + 1 < cols)
+									lastCol++;
 							}
 							continue;
 						}
@@ -1484,27 +1510,27 @@ namespace Terminal.Gui {
 						var attr = contents [row, col, 1];
 						if (attr != redrawAttr) {
 							output.Append (WriteAttributes (attr));
-							if (lastCol == -1)
-								lastCol = col;
 						}
 						if (AlwaysSetPosition && !SetCursorPosition (col, row)) {
 							return;
 						}
+						var rune = (char)contents [row, col, 0];
+						outputWidth += Math.Max (Rune.ColumnWidth (rune), 1);
 						if (AlwaysSetPosition) {
-							Console.Write ($"{output}{(char)contents [row, col, 0]}");
+							Console.Write ($"{output}{rune}");
+							output.Clear ();
 						} else {
-							output.Append ((char)contents [row, col, 0]);
-							if (lastCol == -1)
-								lastCol = col;
+							output.Append (rune);
 						}
 						contents [row, col, 2] = 0;
-						if (!AlwaysSetPosition && col == cols - 1) {
-							Console.Write (output);
-						}
 					}
 				}
+				if (output.Length > 0) {
+					Console.CursorLeft = lastCol;
+					Console.CursorTop = row;
+					Console.Write (output);
+				}
 			}
-			Console.CursorVisible = savedCursorVisible;
 		}
 
 		System.Text.StringBuilder WriteAttributes (int attr)

+ 11 - 4
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1480,15 +1480,22 @@ namespace Terminal.Gui {
 					OutputBuffer [prevPosition].Char.UnicodeChar = ' ';
 					contents [crow, ccol - 1, 0] = (int)(uint)' ';
 
-				} else if (runeWidth < 2 && ccol < Cols - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+				} else if (runeWidth < 2 && ccol <= Clip.Right - 1
+					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
 
 					var prevPosition = crow * Cols + ccol + 1;
 					OutputBuffer [prevPosition].Char.UnicodeChar = (char)' ';
 					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+
+				}
+				if (runeWidth > 1 && ccol == Clip.Right - 1) {
+					OutputBuffer [position].Char.UnicodeChar = (char)' ';
+					contents [crow, ccol, 0] = (int)(uint)' ';
+				} else {
+					OutputBuffer [position].Char.UnicodeChar = (char)rune;
+					contents [crow, ccol, 0] = (int)(uint)rune;
 				}
 				OutputBuffer [position].Attributes = (ushort)currentAttribute;
-				OutputBuffer [position].Char.UnicodeChar = (char)rune;
-				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 				WindowsConsole.SmallRect.Update (ref damageRegion, (short)ccol, (short)crow);
@@ -1496,7 +1503,7 @@ namespace Terminal.Gui {
 
 			ccol++;
 			if (runeWidth > 1) {
-				if (validClip) {
+				if (validClip && ccol < Clip.Right) {
 					position = crow * Cols + ccol;
 					OutputBuffer [position].Attributes = (ushort)currentAttribute;
 					OutputBuffer [position].Char.UnicodeChar = (char)0x00;

+ 151 - 0
UICatalog/Scenarios/RuneWidthGreaterThanOne.cs

@@ -0,0 +1,151 @@
+using System;
+using System.Globalization;
+using System.Text;
+using System.Threading;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "RuneWidthGreaterThanOne", Description: "Test rune width greater than one")]
+	[ScenarioCategory ("Controls")]
+	public class RuneWidthGreaterThanOne : Scenario {
+		private Label _label;
+		private TextField _text;
+		private Button _button;
+		private Label _labelR;
+		private Label _labelV;
+		private Window _win;
+
+		public override void Init (Toplevel top, ColorScheme colorScheme)
+		{
+			Application.Init ();
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("Margin", new MenuItem [] {
+					new MenuItem ("With margin", "", WithMargin),
+					new MenuItem ("Without margin", "", WithoutMargin)
+				}),
+				new MenuBarItem ("Draw Margin Frame", new MenuItem [] {
+					new MenuItem ("With draw", "", WithDrawMargin),
+					new MenuItem ("Without draw", "", WithoutDrawMargin)
+				}),
+				new MenuBarItem ("Runes length", new MenuItem [] {
+					new MenuItem ("Wide", "", WideRunes),
+					new MenuItem ("Narrow", "", NarrowRunes),
+					new MenuItem ("Mixed", "", MixedRunes)
+				})
+			});
+
+			_label = new Label (text: "あなたの名前を入力してください:") {
+				X = Pos.Center (),
+				Y = 0,
+				ColorScheme = new ColorScheme () {
+					Normal = Colors.Base.Focus
+				}
+			};
+			_text = new TextField ("ティラミス") {
+				X = Pos.Center (),
+				Y = 2,
+				Width = 20
+			};
+			_button = new Button (text: "こんにちはと言う") {
+				X = Pos.Center (),
+				Y = 4
+			};
+			_button.Clicked += () => MessageBox.Query ("こんにちはと言う", $"こんにちは {_text.Text}", "Ok");
+			_labelR = new Label (text: "あなたの名前を入力してください") {
+				X = Pos.AnchorEnd (30),
+				Y = 18
+			};
+			_labelV = new Label (text: "あなたの名前を入力してください", TextDirection.TopBottom_RightLeft) {
+				X = Pos.AnchorEnd (30),
+				Y = Pos.Bottom (_labelR)
+			};
+			_win = new Window ("デモエムポンズ") {
+				X = 5,
+				Y = 5,
+				Width = Dim.Fill (22),
+				Height = Dim.Fill (5)
+			};
+			_win.Add (_label, _text, _button, _labelR, _labelV);
+			Application.Top.Add (menu, _win);
+			Application.Run ();
+		}
+
+		private void MixedRunes ()
+		{
+			_label.Text = "Enter your name 你:";
+			_text.Text = "gui.cs 你:";
+			_button.Text = "Say Hello 你";
+			_button.Clicked += () => MessageBox.Query ("Say Hello 你", $"Hello {_text.Text}", "Ok");
+			_labelR.X = Pos.AnchorEnd (21);
+			_labelR.Y = 18;
+			_labelR.Text = "This is a test text 你";
+			_labelV.X = Pos.AnchorEnd (21);
+			_labelV.Y = Pos.Bottom (_labelR);
+			_labelV.Text = "This is a test text 你";
+			_win.Title = "HACC Demo 你";
+		}
+
+		private void NarrowRunes ()
+		{
+			_label.Text = "Enter your name:";
+			_text.Text = "gui.cs";
+			_button.Text = "Say Hello";
+			_button.Clicked += () => MessageBox.Query ("Say Hello", $"Hello {_text.Text}", "Ok");
+			_labelR.X = Pos.AnchorEnd (19);
+			_labelR.Y = 18;
+			_labelR.Text = "This is a test text";
+			_labelV.X = Pos.AnchorEnd (19);
+			_labelV.Y = Pos.Bottom (_labelR);
+			_labelV.Text = "This is a test text";
+			_win.Title = "HACC Demo";
+		}
+
+		private void WideRunes ()
+		{
+			_label.Text = "あなたの名前を入力してください:";
+			_text.Text = "ティラミス";
+			_button.Text = "こんにちはと言う";
+			_button.Clicked += () => MessageBox.Query ("こんにちはと言う", $"こんにちは {_text.Text}", "Ok");
+			_labelR.X = Pos.AnchorEnd (29);
+			_labelR.Y = 18;
+			_labelR.Text = "あなたの名前を入力してください";
+			_labelV.X = Pos.AnchorEnd (29);
+			_labelV.Y = Pos.Bottom (_labelR);
+			_labelV.Text = "あなたの名前を入力してください";
+			_win.Title = "デモエムポンズ";
+		}
+
+		private void WithoutDrawMargin ()
+		{
+			_win.Border.BorderStyle = BorderStyle.None;
+			_win.Border.DrawMarginFrame = false;
+		}
+
+		private void WithDrawMargin ()
+		{
+			_win.Border.DrawMarginFrame = true;
+			_win.Border.BorderStyle = BorderStyle.Single;
+		}
+
+		private void WithoutMargin ()
+		{
+			_win.X = 0;
+			_win.Y = 0;
+			_win.Width = Dim.Fill ();
+			_win.Height = Dim.Fill ();
+		}
+
+		private void WithMargin ()
+		{
+			_win.X = 5;
+			_win.Y = 5;
+			_win.Width = Dim.Fill (22);
+			_win.Height = Dim.Fill (5);
+		}
+
+		public override void Run ()
+		{
+		}
+	}
+}

+ 106 - 1
UnitTests/ConsoleDriverTests.cs

@@ -1,14 +1,23 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using Terminal.Gui;
+using Terminal.Gui.Views;
 using Xunit;
+using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.ConsoleDrivers {
 	public class ConsoleDriverTests {
+		readonly ITestOutputHelper output;
+
+		public ConsoleDriverTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void Init_Inits ()
 		{
@@ -503,5 +512,101 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.True (closed);
 			Assert.Empty (FakeConsole.MockKeyPresses);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
+		{
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = @"これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。"
+			};
+			var win = new Window ("ワイドルーン") { Width = Dim.Fill (), Height = Dim.Fill () };
+			win.Add (tv);
+			Application.Top.Add (win);
+			var lbl = new Label ("ワイドルーン。");
+			var dg = new Dialog ("テスト", 14, 4, new Button ("選ぶ"));
+			dg.Add (lbl);
+			Application.Begin (Application.Top);
+			Application.Begin (dg);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 10);
+
+			var expected = @"
+┌ ワイドルーン ──────────────┐
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+│これは ┌ テスト ────┐ です。│
+│これは │ワイドルーン│ です。│
+│これは │  [ 選ぶ ]  │ です。│
+│これは └────────────┘ です。│
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+└────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 10), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Write_Do_Not_Change_On_ProcessKey ()
+		{
+			var win = new Window ();
+			Application.Begin (win);
+			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
+
+			System.Threading.Tasks.Task.Run (() => {
+				System.Threading.Tasks.Task.Delay (500).Wait ();
+				Application.MainLoop.Invoke (() => {
+					var lbl = new Label ("Hello World") { X = Pos.Center () };
+					var dlg = new Dialog ("Test", new Button ("Ok"));
+					dlg.Add (lbl);
+					Application.Begin (dlg);
+
+					var expected = @"
+┌──────────────────┐
+│┌ Test ─────────┐ │
+││  Hello World  │ │
+││               │ │
+││               │ │
+││     [ Ok ]    │ │
+│└───────────────┘ │
+└──────────────────┘
+";
+
+					var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
+					dlg.Redraw (dlg.Bounds);
+
+					expected = @"
+┌──────────────────┐
+│┌ Test ─────────┐ │
+││  Hello World  │ │
+││               │ │
+││               │ │
+││     [ Ok ]    │ │
+│└───────────────┘ │
+└──────────────────┘
+";
+
+					pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+					win.RequestStop ();
+				});
+			});
+
+			Application.Run (win);
+			Application.Shutdown ();
+		}
 	}
 }

+ 4 - 2
UnitTests/GraphViewTests.cs

@@ -159,8 +159,10 @@ namespace Terminal.Gui.Views {
 			}
 
 			// Remove unnecessary empty lines
-			for (int r = lines.Count - 1; r > h - 1; r--) {
-				lines.RemoveAt (r);
+			if (lines.Count > 0) {
+				for (int r = lines.Count - 1; r > h - 1; r--) {
+					lines.RemoveAt (r);
+				}
 			}
 
 			// Remove trailing whitespace on each line