Fixes remaining wide runes render issues.

This commit is contained in:
BDisp
2022-05-11 00:24:57 +01:00
parent 736d10598b
commit 4382a2c2c2
7 changed files with 363 additions and 60 deletions

View File

@@ -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;
}

View File

@@ -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++) {

View File

@@ -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)

View File

@@ -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;

View File

@@ -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 ()
{
}
}
}

View File

@@ -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 ();
}
}
}

View File

@@ -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