Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Examples/UICatalog/Resources/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"Themes": [
{
"Hot Dog Stand": {
"Glyphs.WideGlyphReplacement": "",
"Schemes": [
{
"Runnable": {
Expand Down Expand Up @@ -134,7 +135,7 @@
}
},
{
"UI Catalog Theme": {
"UI Catalog": {
"Window.DefaultShadow": "Transparent",
"Button.DefaultShadow": "None",
"CheckBox.DefaultHighlightStates": "In, Pressed, PressedOutside",
Expand Down
4 changes: 0 additions & 4 deletions Examples/UICatalog/Scenarios/WideGlyphs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ public override void Main ()
AutoSelectAdornments = false,
ShowViewIdentifier = true
};
adornmentsEditor.ExpanderButton.Accepting += (sender, args) =>
{
//adornmentsEditor.ExpanderButton.Collapsed = args.NewValue;
};
appWindow.Add (adornmentsEditor);

ViewportSettingsEditor viewportSettingsEditor = new ()
Expand Down
5 changes: 5 additions & 0 deletions Terminal.Gui/Drawing/Glyphs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public class Glyphs
// IMPORTANT: Configuration Manager test SaveDefaults uses this class to generate the default config file
// IMPORTANT: in ./UnitTests/bin/Debug/netX.0/config.json

/// <summary>Unicode replacement character; used by Drivers when rendering in cases where a wide glyph can't
/// be output because it would be clipped. Defaults to ' ' (Space).</summary>
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static Rune WideGlyphReplacement { get; set; } = (Rune)' ';

/// <summary>File icon. Defaults to ☰ (Trigram For Heaven)</summary>
[ConfigurationProperty (Scope = typeof (ThemeScope))]
public static Rune File { get; set; } = (Rune)'☰';
Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Drivers/DriverImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ public void Dispose ()

private readonly IOutput _output;

/// <inheritdoc />
public IOutputBuffer GetOutputBuffer () => OutputBuffer;

public IOutput GetOutput () => _output;

private readonly IInputProcessor _inputProcessor;
Expand Down
8 changes: 7 additions & 1 deletion Terminal.Gui/Drivers/IDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ public interface IDriver : IDisposable
IInputProcessor GetInputProcessor ();

/// <summary>
/// Gets the output handler responsible for writing to the terminal.
/// Gets the <see cref="IOutputBuffer"/> containing the buffered screen contents.
/// </summary>
/// <returns></returns>
IOutputBuffer GetOutputBuffer ();

/// <summary>
/// Gets the <see cref="IOutput"/> responsible for writing to the terminal.
/// </summary>
IOutput GetOutput ();

Expand Down
14 changes: 12 additions & 2 deletions Terminal.Gui/Drivers/IOutputBuffer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

namespace Terminal.Gui.Drivers;
namespace Terminal.Gui.Drivers;

/// <summary>
/// Represents the desired screen state for console rendering. This interface provides methods for building up
Expand Down Expand Up @@ -128,4 +127,15 @@ public interface IOutputBuffer
/// Changing this may have unexpected consequences.
/// </summary>
int Top { get; set; }

/// <summary>
/// Sets the replacement character that will be used when a wide glyph (double-width character) cannot fit in the
/// available space.
/// If not set, the default will be <see cref="Glyphs.WideGlyphReplacement"/>.
/// </summary>
/// <param name="column1ReplacementChar">
/// The character used when the first column of a wide character is invalid (for example, when it is overlapped by the
/// trailing half of a previous wide character).
/// </param>
void SetWideGlyphReplacement (Rune column1ReplacementChar);
}
22 changes: 10 additions & 12 deletions Terminal.Gui/Drivers/OutputBufferImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ public int Cols
/// <summary>The topmost row in the terminal.</summary>
public virtual int Top { get; set; } = 0;

private Rune _column1ReplacementChar = Glyphs.WideGlyphReplacement;

/// <inheritdoc />
public void SetWideGlyphReplacement (Rune column1ReplacementChar)
{
_column1ReplacementChar = column1ReplacementChar;
}

/// <summary>
/// Indicates which lines have been modified and need to be redrawn.
/// </summary>
Expand Down Expand Up @@ -205,7 +213,7 @@ private void InvalidateOverlappedWideGlyph (int col, int row)
{
if (col > 0 && Contents! [row, col - 1].Grapheme.GetColumns () > 1)
{
Contents [row, col - 1].Grapheme = Rune.ReplacementChar.ToString ();
Contents [row, col - 1].Grapheme = _column1ReplacementChar.ToString ();
Contents [row, col - 1].IsDirty = true;
}
}
Expand Down Expand Up @@ -273,17 +281,7 @@ private void WriteWideGrapheme (int col, int row, string grapheme)
if (!Clip!.Contains (col + 1, row))
{
// Second column is outside clip - can't fit wide char here
Contents! [row, col].Grapheme = Rune.ReplacementChar.ToString ();
}
else if (!Clip.Contains (col, row))
{
// First column is outside clip but second isn't
// Mark second column as replacement to indicate partial overlap
if (col + 1 < Cols)
{
Contents! [row, col + 1].Grapheme = Rune.ReplacementChar.ToString ();
Contents! [row, col + 1].IsDirty = true;
}
Contents! [row, col].Grapheme = _column1ReplacementChar.ToString ();
}
else
{
Expand Down
10 changes: 5 additions & 5 deletions Terminal.Gui/ViewBase/View.Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ protected virtual void OnMouseLeave () { }
/// <item>
/// <description>
/// Invokes commands bound to mouse clicks via <see cref="MouseBindings"/>
/// (default: <see cref="Command.Select"/> → <see cref="Selecting"/> event)
/// (default: <see cref="Command.Activate"/> → <see cref="Activating"/> event)
/// </description>
/// </item>
/// <item>
Expand Down Expand Up @@ -295,7 +295,7 @@ protected virtual void OnMouseLeave () { }
/// <seealso cref="MouseEvent"/>
/// <seealso cref="OnMouseEvent"/>
/// <seealso cref="MouseBindings"/>
/// <seealso cref="Selecting"/>
/// <seealso cref="Activating"/>
/// <seealso cref="WantContinuousButtonPressed"/>
/// <seealso cref="HighlightStates"/>
public bool? NewMouseEvent (MouseEventArgs mouseEvent)
Expand Down Expand Up @@ -414,8 +414,8 @@ public bool RaiseMouseEvent (MouseEventArgs mouseEvent)
/// <summary>
/// INTERNAL: For cases where the view is grabbed and the mouse is pressed, this method handles the pressed events from
/// the driver.
/// When <see cref="WantContinuousButtonPressed"/> is set, this method will raise the Clicked/Selecting event
/// via <see cref="Command.Select"/> each time it is called (after the first time the mouse is pressed).
/// When <see cref="WantContinuousButtonPressed"/> is set, this method will raise the Clicked/Activating event
/// via <see cref="Command.Activate"/> each time it is called (after the first time the mouse is pressed).
/// </summary>
/// <param name="mouseEvent"></param>
/// <returns><see langword="true"/>, if processing should stop, <see langword="false"/> otherwise.</returns>
Expand Down Expand Up @@ -531,7 +531,7 @@ internal bool WhenGrabbedHandleClicked (MouseEventArgs mouseEvent)
/// <summary>
/// INTERNAL API: Converts mouse click events into <see cref="Command"/>s by invoking the commands bound
/// to the mouse button via <see cref="MouseBindings"/>. By default, all mouse clicks are bound to
/// <see cref="Command.Select"/> which raises the <see cref="Selecting"/> event.
/// <see cref="Command.Activate"/> which raises the <see cref="Activating"/> event.
/// </summary>
protected bool RaiseCommandsBoundToMouse (MouseEventArgs args)
{
Expand Down
5 changes: 3 additions & 2 deletions Tests/UnitTests/View/Draw/ClipTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height)
public void Clipping_Wide_Runes ()
{
Application.Driver!.SetScreenSize (30, 1);
Application.Driver!.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');

var top = new View
{
Expand Down Expand Up @@ -231,9 +232,9 @@ public void Clipping_Wide_Runes ()
// 012 34 56 78 90 12 34 56 78 90 12 34 56 78
// │こ れ は 広 い ル ー ン ラ イ ン で す 。
// 01 2345678901234 56 78 90 12 34 56
// │ |0123456989│ ン ラ イ ン で す 。
// │ |0123456989│ ン ラ イ ン で す 。
expectedOutput = """
│0123456789│ ンラインです。
│0123456789│ ンラインです。
""";

DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output);
Expand Down
6 changes: 3 additions & 3 deletions Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ public void AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly ()
{
IDriver? driver = CreateFakeDriver ();
driver.SetScreenSize (6, 3);
driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');

driver!.Clip = new (driver.Screen);

driver.Move (1, 0);
driver.AddStr ("┌");
driver.Move (2, 0);
Expand All @@ -197,14 +197,14 @@ public void AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_Outputs_Correctly ()

DriverAssert.AssertDriverContentsAre (
"""
┌─┐🍎
┌─┐🍎
""",
output,
driver);

driver.Refresh ();

DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
output, driver);
}
}
6 changes: 4 additions & 2 deletions Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
using System.Text;
using UnitTests;
using Xunit.Abstractions;

Expand Down Expand Up @@ -104,6 +105,7 @@ public void All_Drivers_When_Clipped_AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_O
IApplication? app = Application.Create ();
app.Init (driverName);
IDriver driver = app.Driver!;
driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');

// Need to force "windows" driver to override legacy console mode for this test
driver.IsLegacyConsole = false;
Expand All @@ -127,14 +129,14 @@ public void All_Drivers_When_Clipped_AddStr_Glyph_On_Second_Cell_Of_Wide_Glyph_O

DriverAssert.AssertDriverContentsAre (
"""
┌─┐🍎
┌─┐🍎
""",
output,
driver);

driver.Refresh ();

DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
output, driver);
}
}
Expand Down
9 changes: 7 additions & 2 deletions Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace DriverTests;
using System.Text;
using Terminal.Gui.Drivers;

namespace DriverTests;

public class OutputBaseTests
{
Expand Down Expand Up @@ -161,6 +164,8 @@ public void Write_Virtual_Or_NonVirtual_Uses_WriteToConsole_And_Clears_Dirty_Fla
// FakeOutput exposes this because it's in test scope
var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
IOutputBuffer buffer = output.GetLastBuffer ()!;
buffer.SetWideGlyphReplacement ((Rune)'①');

buffer.SetSize (3, 1);

// Write '🦮' at col 0 and 'A' at col 2
Expand Down Expand Up @@ -209,7 +214,7 @@ public void Write_Virtual_Or_NonVirtual_Uses_WriteToConsole_And_Clears_Dirty_Fla

output.Write (buffer);

Assert.Contains ("", output.GetLastOutput ());
Assert.Contains ("", output.GetLastOutput ());
Assert.Contains ("X", output.GetLastOutput ());

// Dirty flags cleared for the written cells
Expand Down
Loading
Loading