mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54ed8f70815a
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
@@ -24,6 +21,33 @@ public class CharacterMap : Scenario
|
||||
private Label? _errorLabel;
|
||||
private TableView? _categoryList;
|
||||
private CharMap? _charMap;
|
||||
private OptionSelector? _unicodeCategorySelector;
|
||||
|
||||
public override List<Key> GetDemoKeyStrokes ()
|
||||
{
|
||||
List<Key> keys = new ();
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
keys.Add (Key.CursorDown);
|
||||
}
|
||||
|
||||
// Category table
|
||||
keys.Add (Key.Tab.WithShift);
|
||||
|
||||
// Block elements
|
||||
keys.Add (Key.B);
|
||||
keys.Add (Key.L);
|
||||
|
||||
keys.Add (Key.Tab);
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
keys.Add (Key.CursorLeft);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Don't create a Window, just return the top-level view
|
||||
public override void Main ()
|
||||
@@ -39,9 +63,9 @@ public class CharacterMap : Scenario
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Height = Dim.Fill (),
|
||||
// SchemeName = "Base"
|
||||
Height = Dim.Fill ()
|
||||
|
||||
// SchemeName = "Base"
|
||||
};
|
||||
top.Add (_charMap);
|
||||
|
||||
@@ -50,7 +74,8 @@ public class CharacterMap : Scenario
|
||||
X = Pos.Right (_charMap) + 1,
|
||||
Y = Pos.Y (_charMap),
|
||||
HotKeySpecifier = (Rune)'_',
|
||||
Text = "_Jump To:",
|
||||
Text = "_Jump To:"
|
||||
|
||||
//SchemeName = "Dialog"
|
||||
};
|
||||
top.Add (jumpLabel);
|
||||
@@ -60,7 +85,8 @@ public class CharacterMap : Scenario
|
||||
X = Pos.Right (jumpLabel) + 1,
|
||||
Y = Pos.Y (_charMap),
|
||||
Width = 17,
|
||||
Caption = "e.g. 01BE3 or ✈",
|
||||
Caption = "e.g. 01BE3 or ✈"
|
||||
|
||||
//SchemeName = "Dialog"
|
||||
};
|
||||
top.Add (jumpEdit);
|
||||
@@ -89,10 +115,12 @@ public class CharacterMap : Scenario
|
||||
|
||||
jumpEdit.Accepting += JumpEditOnAccept;
|
||||
|
||||
_categoryList = new () {
|
||||
X = Pos.Right (_charMap),
|
||||
Y = Pos.Bottom (jumpLabel),
|
||||
Height = Dim.Fill (),
|
||||
_categoryList = new ()
|
||||
{
|
||||
X = Pos.Right (_charMap),
|
||||
Y = Pos.Bottom (jumpLabel),
|
||||
Height = Dim.Fill ()
|
||||
|
||||
//SchemeName = "Dialog"
|
||||
};
|
||||
_categoryList.FullRowSelect = true;
|
||||
@@ -165,7 +193,7 @@ public class CharacterMap : Scenario
|
||||
),
|
||||
new (
|
||||
"_Options",
|
||||
new MenuItemv2 [] { CreateMenuShowWidth () }
|
||||
[CreateMenuShowWidth (), CreateMenuUnicodeCategorySelector ()]
|
||||
)
|
||||
]
|
||||
};
|
||||
@@ -317,6 +345,7 @@ public class CharacterMap : Scenario
|
||||
CheckedState = _charMap!.ShowGlyphWidths ? CheckState.Checked : CheckState.None
|
||||
};
|
||||
var item = new MenuItemv2 { CommandView = cb };
|
||||
|
||||
item.Action += () =>
|
||||
{
|
||||
if (_charMap is { })
|
||||
@@ -328,29 +357,48 @@ public class CharacterMap : Scenario
|
||||
return item;
|
||||
}
|
||||
|
||||
public override List<Key> GetDemoKeyStrokes ()
|
||||
private MenuItemv2 CreateMenuUnicodeCategorySelector ()
|
||||
{
|
||||
List<Key> keys = new ();
|
||||
// First option is "All" (no filter), followed by all UnicodeCategory names
|
||||
string [] allCategoryNames = Enum.GetNames<UnicodeCategory> ();
|
||||
var options = new string [allCategoryNames.Length + 1];
|
||||
options [0] = "All";
|
||||
Array.Copy (allCategoryNames, 0, options, 1, allCategoryNames.Length);
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
// TODO: When #4126 is merged update this to use OptionSelector<UnicodeCategory?>
|
||||
var selector = new OptionSelector
|
||||
{
|
||||
keys.Add (Key.CursorDown);
|
||||
}
|
||||
AssignHotKeysToCheckBoxes = true,
|
||||
Options = options
|
||||
};
|
||||
|
||||
// Category table
|
||||
keys.Add (Key.Tab.WithShift);
|
||||
_unicodeCategorySelector = selector;
|
||||
|
||||
// Block elements
|
||||
keys.Add (Key.B);
|
||||
keys.Add (Key.L);
|
||||
// Default to "All"
|
||||
selector.SelectedItem = 0;
|
||||
_charMap!.ShowUnicodeCategory = null;
|
||||
|
||||
keys.Add (Key.Tab);
|
||||
selector.SelectedItemChanged += (s, e) =>
|
||||
{
|
||||
int? idx = selector.SelectedItem;
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
keys.Add (Key.CursorLeft);
|
||||
}
|
||||
if (idx is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
return keys;
|
||||
if (idx.Value == 0)
|
||||
{
|
||||
_charMap.ShowUnicodeCategory = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Map index to UnicodeCategory (offset by 1 because 0 is "All")
|
||||
UnicodeCategory cat = Enum.GetValues<UnicodeCategory> () [idx.Value - 1];
|
||||
_charMap.ShowUnicodeCategory = cat;
|
||||
}
|
||||
};
|
||||
|
||||
return new() { CommandView = selector };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,22 +11,78 @@ public class CombiningMarks : Scenario
|
||||
var top = new Toplevel ();
|
||||
|
||||
top.DrawComplete += (s, e) =>
|
||||
{
|
||||
top.Move (0, 0);
|
||||
top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
|
||||
top.Move (0, 2);
|
||||
top.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr.");
|
||||
top.Move (0, 3);
|
||||
top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr.");
|
||||
top.Move (0, 4);
|
||||
top.AddRune ('[');
|
||||
top.AddRune ('a');
|
||||
top.AddRune ('\u0301');
|
||||
top.AddRune ('\u0301');
|
||||
top.AddRune ('\u0328');
|
||||
top.AddRune (']');
|
||||
top.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each.");
|
||||
};
|
||||
{
|
||||
// Forces reset _lineColsOffset because we're dealing with direct draw
|
||||
Application.ClearScreenNextIteration = true;
|
||||
|
||||
var i = -1;
|
||||
top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("\u0301<- \"\\u0301\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\u0301]<- \"[\\u0301]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[ \u0301]<- \"[ \\u0301]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\u0301 ]<- \"[\\u0301 ]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("\u0301\u0301\u0328<- \"\\u0301\\u0301\\u0328\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\u0301\u0301\u0328]<- \"[\\u0301\\u0301\\u0328]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u0301\\u0301\\u0328]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddRune ('[');
|
||||
top.AddRune ('a');
|
||||
top.AddRune ('\u0301');
|
||||
top.AddRune ('\u0301');
|
||||
top.AddRune ('\u0328');
|
||||
top.AddRune (']');
|
||||
top.AddStr ("<- \"[a\\u0301\\u0301\\u0328]\" using AddRune for each.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u0301\\u0301\\u0328]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[e\u0301\u0301\u0328]<- \"[e\\u0301\\u0301\\u0328]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[e\u0328\u0301]<- \"[e\\u0328\\u0301]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("\u00ad<- \"\\u00ad\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\u00ad]<- \"[\\u00ad]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddRune ('[');
|
||||
top.AddRune ('\u00ad');
|
||||
top.AddRune (']');
|
||||
top.AddStr ("<- \"[\\u00ad]\" using AddRune for each.");
|
||||
i++;
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("From now on we are using TextFormatter");
|
||||
TextFormatter tf = new () { Text = "[e\u0301\u0301\u0328]<- \"[e\\u0301\\u0301\\u0328]\" using TextFormatter." };
|
||||
tf.Draw (new (0, ++i, tf.Text.Length, 1), top.GetAttributeForRole (VisualRole.Normal), top.GetAttributeForRole (VisualRole.Normal));
|
||||
tf.Text = "[e\u0328\u0301]<- \"[e\\u0328\\u0301]\" using TextFormatter.";
|
||||
tf.Draw (new (0, ++i, tf.Text.Length, 1), top.GetAttributeForRole (VisualRole.Normal), top.GetAttributeForRole (VisualRole.Normal));
|
||||
i++;
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("From now on we are using Surrogate pairs with combining diacritics");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\ud835\udc4b\u0302]<- \"[\\ud835\\udc4b\\u0302]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\ud83d\udc68\ud83e\uddd2]<- \"[\\ud83d\\udc68\\ud83e\\uddd2]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("\u200d<- \"\\u200d\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\u200d]<- \"[\\u200d]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\ud83d\udc68\u200d\ud83e\uddd2]<- \"[\\ud83d\\udc68\\u200d\\ud83e\\uddd2]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\U0001F469\U0001F9D2]<- \"[\\U0001F469\\U0001F9D2]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\U0001F469\u200D\U0001F9D2]<- \"[\\U0001F469\\u200D\\U0001F9D2]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\U0001F468\U0001F469\U0001F9D2]<- \"[\\U0001F468\\U0001F469\\U0001F9D2]\" using AddStr.");
|
||||
top.Move (0, ++i);
|
||||
top.AddStr ("[\U0001F468\u200D\U0001F469\u200D\U0001F9D2]<- \"[\\U0001F468\\u200D\\U0001F469\\u200D\\U0001F9D2]\" using AddStr.");
|
||||
};
|
||||
|
||||
Application.Run (top);
|
||||
top.Dispose ();
|
||||
|
||||
@@ -82,16 +82,6 @@ public static partial class Application // Initialization (Init/Shutdown)
|
||||
if (driver is { })
|
||||
{
|
||||
Driver = driver;
|
||||
|
||||
if (driver is FakeDriver)
|
||||
{
|
||||
//// We're running unit tests. Disable loading config files other than default
|
||||
//if (Locations == ConfigLocations.All)
|
||||
//{
|
||||
// Locations = ConfigLocations.Default;
|
||||
// ResetAllSettings ();
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore Configuration for ForceDriver if driverName is specified
|
||||
|
||||
@@ -111,7 +111,22 @@ public static class RuneExtensions
|
||||
/// The number of columns required to fit the rune, 0 if the argument is the null character, or -1 if the value is
|
||||
/// not printable, otherwise the number of columns that the rune occupies.
|
||||
/// </returns>
|
||||
public static int GetColumns (this Rune rune) { return UnicodeCalculator.GetWidth (rune); }
|
||||
public static int GetColumns (this Rune rune)
|
||||
{
|
||||
int value = rune.Value;
|
||||
|
||||
// TODO: Remove this code when #4259 is fixed
|
||||
// TODO: See https://github.com/gui-cs/Terminal.Gui/issues/4259
|
||||
if (value is >= 0x2630 and <= 0x2637 || // Trigrams
|
||||
value is >= 0x268A and <= 0x268F || // Monograms/Digrams
|
||||
value is >= 0x4DC0 and <= 0x4DFF) // Hexagrams
|
||||
{
|
||||
return 2; // Assume double-width due to Windows Terminal font rendering
|
||||
}
|
||||
|
||||
// Fallback to original GetWidth for other code points
|
||||
return UnicodeCalculator.GetWidth (rune);
|
||||
}
|
||||
|
||||
/// <summary>Get number of bytes required to encode the rune, based on the provided encoding.</summary>
|
||||
/// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
@@ -15,7 +16,9 @@ public class CharMap : View, IDesignable
|
||||
{
|
||||
private const int COLUMN_WIDTH = 3; // Width of each column of glyphs
|
||||
private const int HEADER_HEIGHT = 1; // Height of the header
|
||||
private int _rowHeight = 1; // Height of each row of 16 glyphs - changing this is not tested
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly int MAX_CODE_POINT = UnicodeRange.Ranges.Max (r => r.End);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance.
|
||||
@@ -64,7 +67,8 @@ public class CharMap : View, IDesignable
|
||||
MouseBindings.Add (MouseFlags.WheeledLeft, Command.ScrollLeft);
|
||||
MouseBindings.Add (MouseFlags.WheeledRight, Command.ScrollRight);
|
||||
|
||||
SetContentSize (new (COLUMN_WIDTH * 16 + RowLabelWidth, MAX_CODE_POINT / 16 * _rowHeight + HEADER_HEIGHT));
|
||||
// Initial content size; height will be corrected by RebuildVisibleRows()
|
||||
SetContentSize (new (COLUMN_WIDTH * 16 + RowLabelWidth, HEADER_HEIGHT + _rowHeight));
|
||||
|
||||
// Set up the horizontal scrollbar. Turn off AutoShow since we do it manually.
|
||||
HorizontalScrollBar.AutoShow = false;
|
||||
@@ -100,89 +104,82 @@ public class CharMap : View, IDesignable
|
||||
// The scrollbars are in the Padding. VisualRole.Focus/Active are used to draw the
|
||||
// CharMap headers. Override Padding to force it to draw to match.
|
||||
Padding!.GettingAttributeForRole += PaddingOnGettingAttributeForRole;
|
||||
|
||||
// Build initial visible rows (all rows with at least one valid codepoint)
|
||||
RebuildVisibleRows ();
|
||||
}
|
||||
|
||||
private void PaddingOnGettingAttributeForRole (object? sender, VisualRoleEventArgs e)
|
||||
// Visible rows management: each entry is the starting code point of a 16-wide row
|
||||
private readonly List<int> _visibleRowStarts = new ();
|
||||
private readonly Dictionary<int, int> _rowStartToVisibleIndex = new ();
|
||||
|
||||
private void RebuildVisibleRows ()
|
||||
{
|
||||
if (e.Role != VisualRole.Focus && e.Role != VisualRole.Active)
|
||||
_visibleRowStarts.Clear ();
|
||||
_rowStartToVisibleIndex.Clear ();
|
||||
|
||||
int maxRow = MAX_CODE_POINT / 16;
|
||||
|
||||
for (var row = 0; row <= maxRow; row++)
|
||||
{
|
||||
e.Result = GetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Active);
|
||||
int start = row * 16;
|
||||
bool anyValid = false;
|
||||
bool anyVisible = false;
|
||||
|
||||
for (var col = 0; col < 16; col++)
|
||||
{
|
||||
int cp = start + col;
|
||||
if (cp > RuneExtensions.MaxUnicodeCodePoint)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Rune.IsValid (cp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
anyValid = true;
|
||||
|
||||
if (!ShowUnicodeCategory.HasValue)
|
||||
{
|
||||
// With no filter, a row is displayed if it has any valid codepoint
|
||||
anyVisible = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var rune = new Rune (cp);
|
||||
Span<char> utf16 = new char [2];
|
||||
rune.EncodeToUtf16 (utf16);
|
||||
UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (utf16 [0]);
|
||||
if (cat == ShowUnicodeCategory.Value)
|
||||
{
|
||||
anyVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyValid && (!ShowUnicodeCategory.HasValue ? anyValid : anyVisible))
|
||||
{
|
||||
_rowStartToVisibleIndex [start] = _visibleRowStarts.Count;
|
||||
_visibleRowStarts.Add (start);
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
// Update content size to match visible rows
|
||||
SetContentSize (new (COLUMN_WIDTH * 16 + RowLabelWidth, _visibleRowStarts.Count * _rowHeight + HEADER_HEIGHT));
|
||||
|
||||
// Keep vertical scrollbar aligned with new content size
|
||||
VerticalScrollBar.ScrollableContentSize = GetContentSize ().Height;
|
||||
}
|
||||
|
||||
private bool? Move (ICommandContext? commandContext, int cpOffset)
|
||||
private int VisibleRowIndexForCodePoint (int codePoint)
|
||||
{
|
||||
if (RaiseSelecting (commandContext) is true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SelectedCodePoint += cpOffset;
|
||||
|
||||
return true;
|
||||
int start = (codePoint / 16) * 16;
|
||||
return _rowStartToVisibleIndex.GetValueOrDefault (start, -1);
|
||||
}
|
||||
|
||||
private void ScrollToMakeCursorVisible (Point offsetToNewCursor)
|
||||
{
|
||||
// Adjust vertical scrolling
|
||||
if (offsetToNewCursor.Y < 1) // Header is at Y = 0
|
||||
{
|
||||
ScrollVertical (offsetToNewCursor.Y - HEADER_HEIGHT);
|
||||
}
|
||||
else if (offsetToNewCursor.Y >= Viewport.Height)
|
||||
{
|
||||
ScrollVertical (offsetToNewCursor.Y - Viewport.Height + HEADER_HEIGHT);
|
||||
}
|
||||
|
||||
// Adjust horizontal scrolling
|
||||
if (offsetToNewCursor.X < RowLabelWidth + 1)
|
||||
{
|
||||
ScrollHorizontal (offsetToNewCursor.X - (RowLabelWidth + 1));
|
||||
}
|
||||
else if (offsetToNewCursor.X >= Viewport.Width)
|
||||
{
|
||||
ScrollHorizontal (offsetToNewCursor.X - Viewport.Width + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#region Cursor
|
||||
|
||||
private Point GetCursor (int codePoint)
|
||||
{
|
||||
// + 1 for padding between label and first column
|
||||
int x = codePoint % 16 * COLUMN_WIDTH + RowLabelWidth + 1 - Viewport.X;
|
||||
int y = codePoint / 16 * _rowHeight + HEADER_HEIGHT - Viewport.Y;
|
||||
|
||||
return new (x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Point? PositionCursor ()
|
||||
{
|
||||
Point cursor = GetCursor (SelectedCodePoint);
|
||||
|
||||
if (HasFocus
|
||||
&& cursor.X >= RowLabelWidth
|
||||
&& cursor.X < Viewport.Width
|
||||
&& cursor.Y > 0
|
||||
&& cursor.Y < Viewport.Height)
|
||||
{
|
||||
Move (cursor.X, cursor.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#endregion Cursor
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly int MAX_CODE_POINT = UnicodeRange.Ranges.Max (r => r.End);
|
||||
private int _rowHeight = 1; // Height of each row of 16 glyphs - changing this is not tested
|
||||
private int _selectedCodepoint; // Currently selected codepoint
|
||||
private int _startCodepoint; // The codepoint that will be displayed at the top of the Viewport
|
||||
|
||||
@@ -219,6 +216,21 @@ public class CharMap : View, IDesignable
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs<int>>? SelectedCodePointChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the number of columns each glyph is displayed.
|
||||
/// </summary>
|
||||
public bool ShowGlyphWidths
|
||||
{
|
||||
get => _rowHeight == 2;
|
||||
set
|
||||
{
|
||||
_rowHeight = value ? 2 : 1;
|
||||
// height changed => content height depends on row height
|
||||
RebuildVisibleRows ();
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing
|
||||
/// characters.
|
||||
@@ -233,15 +245,42 @@ public class CharMap : View, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private UnicodeCategory? _showUnicodeCategory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the number of columns each glyph is displayed.
|
||||
/// When set, only glyphs whose UnicodeCategory matches the value are rendered. If <see langword="null"/> (default),
|
||||
/// all glyphs are rendered.
|
||||
/// </summary>
|
||||
public bool ShowGlyphWidths
|
||||
public UnicodeCategory? ShowUnicodeCategory
|
||||
{
|
||||
get => _rowHeight == 2;
|
||||
get => _showUnicodeCategory;
|
||||
set
|
||||
{
|
||||
_rowHeight = value ? 2 : 1;
|
||||
if (_showUnicodeCategory == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_showUnicodeCategory = value;
|
||||
RebuildVisibleRows ();
|
||||
|
||||
// Ensure selection is on a visible row
|
||||
int desiredRowStart = (SelectedCodePoint / 16) * 16;
|
||||
if (!_rowStartToVisibleIndex.ContainsKey (desiredRowStart))
|
||||
{
|
||||
// Find nearest visible row (prefer next; fallback to last)
|
||||
int idx = _visibleRowStarts.FindIndex (s => s >= desiredRowStart);
|
||||
if (idx < 0 && _visibleRowStarts.Count > 0)
|
||||
{
|
||||
idx = _visibleRowStarts.Count - 1;
|
||||
}
|
||||
if (idx >= 0)
|
||||
{
|
||||
SelectedCodePoint = _visibleRowStarts [idx];
|
||||
}
|
||||
}
|
||||
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
@@ -249,6 +288,292 @@ public class CharMap : View, IDesignable
|
||||
private void CopyCodePoint () { Clipboard.Contents = $"U+{SelectedCodePoint:x5}"; }
|
||||
private void CopyGlyph () { Clipboard.Contents = $"{new Rune (SelectedCodePoint)}"; }
|
||||
|
||||
private bool? Move (ICommandContext? commandContext, int cpOffset)
|
||||
{
|
||||
if (RaiseSelecting (commandContext) is true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SelectedCodePoint += cpOffset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PaddingOnGettingAttributeForRole (object? sender, VisualRoleEventArgs e)
|
||||
{
|
||||
if (e.Role != VisualRole.Focus && e.Role != VisualRole.Active)
|
||||
{
|
||||
e.Result = GetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Active);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollToMakeCursorVisible (Point offsetToNewCursor)
|
||||
{
|
||||
// Adjust vertical scrolling
|
||||
if (offsetToNewCursor.Y < 1) // Header is at Y = 0
|
||||
{
|
||||
ScrollVertical (offsetToNewCursor.Y - HEADER_HEIGHT);
|
||||
}
|
||||
else if (offsetToNewCursor.Y >= Viewport.Height)
|
||||
{
|
||||
ScrollVertical (offsetToNewCursor.Y - Viewport.Height + HEADER_HEIGHT);
|
||||
}
|
||||
|
||||
// Adjust horizontal scrolling
|
||||
if (offsetToNewCursor.X < RowLabelWidth + 1)
|
||||
{
|
||||
ScrollHorizontal (offsetToNewCursor.X - (RowLabelWidth + 1));
|
||||
}
|
||||
else if (offsetToNewCursor.X >= Viewport.Width)
|
||||
{
|
||||
ScrollHorizontal (offsetToNewCursor.X - Viewport.Width + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#region Details Dialog
|
||||
|
||||
[RequiresUnreferencedCode ("AOT")]
|
||||
[RequiresDynamicCode ("AOT")]
|
||||
private void ShowDetails ()
|
||||
{
|
||||
if (!Application.Initialized)
|
||||
{
|
||||
// Some unit tests invoke Accept without Init
|
||||
return;
|
||||
}
|
||||
|
||||
UcdApiClient? client = new ();
|
||||
var decResponse = string.Empty;
|
||||
var getCodePointError = string.Empty;
|
||||
|
||||
Dialog? waitIndicator = new ()
|
||||
{
|
||||
Title = Strings.charMapCPInfoDlgTitle,
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Center (),
|
||||
Width = 40,
|
||||
Height = 10,
|
||||
Buttons = [new () { Text = Strings.btnCancel }]
|
||||
};
|
||||
|
||||
var errorLabel = new Label
|
||||
{
|
||||
Text = UcdApiClient.BaseUrl,
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (3),
|
||||
TextAlignment = Alignment.Center
|
||||
};
|
||||
|
||||
var spinner = new SpinnerView
|
||||
{
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (errorLabel),
|
||||
Style = new SpinnerStyle.Aesthetic ()
|
||||
};
|
||||
spinner.AutoSpin = true;
|
||||
waitIndicator.Add (errorLabel);
|
||||
waitIndicator.Add (spinner);
|
||||
|
||||
waitIndicator.Ready += async (s, a) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
decResponse = await client.GetCodepointDec (SelectedCodePoint).ConfigureAwait (false);
|
||||
Application.Invoke (() => waitIndicator.RequestStop ());
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
getCodePointError = errorLabel.Text = e.Message;
|
||||
Application.Invoke (() => waitIndicator.RequestStop ());
|
||||
}
|
||||
};
|
||||
Application.Run (waitIndicator);
|
||||
waitIndicator.Dispose ();
|
||||
|
||||
var name = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty (decResponse))
|
||||
{
|
||||
using JsonDocument document = JsonDocument.Parse (decResponse);
|
||||
|
||||
JsonElement root = document.RootElement;
|
||||
|
||||
// Get a property by name and output its value
|
||||
if (root.TryGetProperty ("name", out JsonElement nameElement))
|
||||
{
|
||||
name = nameElement.GetString ();
|
||||
}
|
||||
|
||||
decResponse = JsonSerializer.Serialize (
|
||||
document.RootElement,
|
||||
new
|
||||
JsonSerializerOptions
|
||||
{ WriteIndented = true }
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
decResponse = getCodePointError;
|
||||
}
|
||||
|
||||
var title = $"{ToCamelCase (name!)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}";
|
||||
|
||||
Button? copyGlyph = new () { Text = Strings.charMapCopyGlyph };
|
||||
Button? copyCodepoint = new () { Text = Strings.charMapCopyCP };
|
||||
Button? cancel = new () { Text = Strings.btnCancel };
|
||||
|
||||
var dlg = new Dialog { Title = title, Buttons = [copyGlyph, copyCodepoint, cancel] };
|
||||
|
||||
copyGlyph.Accepting += (s, a) =>
|
||||
{
|
||||
CopyGlyph ();
|
||||
dlg!.RequestStop ();
|
||||
a.Handled = true;
|
||||
};
|
||||
|
||||
copyCodepoint.Accepting += (s, a) =>
|
||||
{
|
||||
CopyCodePoint ();
|
||||
dlg!.RequestStop ();
|
||||
a.Handled = true;
|
||||
};
|
||||
|
||||
cancel.Accepting += (s, a) =>
|
||||
{
|
||||
dlg!.RequestStop ();
|
||||
a.Handled = true;
|
||||
};
|
||||
|
||||
var rune = (Rune)SelectedCodePoint;
|
||||
var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsAscii}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", Bmp: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsBmp}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", CombiningMark: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsCombiningMark ()}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", SurrogatePair: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsSurrogatePair ()}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", Plane: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.Plane}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = "Columns: ", X = 0, Y = Pos.Bottom (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.GetColumns ()}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", Utf16SequenceLength: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.Utf16SequenceLength}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = "Category: ", X = 0, Y = Pos.Bottom (label) };
|
||||
dlg.Add (label);
|
||||
Span<char> utf16 = stackalloc char [2];
|
||||
int charCount = rune.EncodeToUtf16 (utf16);
|
||||
|
||||
// Get the bidi class for the first code unit
|
||||
// For most bidi characters, the first code unit is sufficient
|
||||
UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory (utf16 [0]);
|
||||
|
||||
label = new () { Text = $"{category}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new ()
|
||||
{
|
||||
Text =
|
||||
$"{Strings.charMapInfoDlgInfoLabel} {UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint}:",
|
||||
X = 0,
|
||||
Y = Pos.Bottom (label)
|
||||
};
|
||||
dlg.Add (label);
|
||||
|
||||
var json = new TextView
|
||||
{
|
||||
X = 0,
|
||||
Y = Pos.Bottom (label),
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (2),
|
||||
ReadOnly = true,
|
||||
Text = decResponse
|
||||
};
|
||||
|
||||
dlg.Add (json);
|
||||
|
||||
Application.Run (dlg);
|
||||
dlg.Dispose ();
|
||||
}
|
||||
|
||||
#endregion Details Dialog
|
||||
|
||||
#region Cursor
|
||||
|
||||
private Point GetCursor (int codePoint)
|
||||
{
|
||||
// + 1 for padding between label and first column
|
||||
int x = codePoint % 16 * COLUMN_WIDTH + RowLabelWidth + 1 - Viewport.X;
|
||||
|
||||
int visibleRowIndex = VisibleRowIndexForCodePoint (codePoint);
|
||||
if (visibleRowIndex < 0)
|
||||
{
|
||||
// If filtered out, stick to current Y to avoid jumping; caller will clamp
|
||||
int fallbackY = HEADER_HEIGHT - Viewport.Y;
|
||||
return new (x, fallbackY);
|
||||
}
|
||||
|
||||
int y = visibleRowIndex * _rowHeight + HEADER_HEIGHT - Viewport.Y;
|
||||
|
||||
return new (x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Point? PositionCursor ()
|
||||
{
|
||||
Point cursor = GetCursor (SelectedCodePoint);
|
||||
|
||||
if (HasFocus
|
||||
&& cursor.X >= RowLabelWidth
|
||||
&& cursor.X < Viewport.Width
|
||||
&& cursor.Y > 0
|
||||
&& cursor.Y < Viewport.Height)
|
||||
{
|
||||
Move (cursor.X, cursor.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#endregion Cursor
|
||||
|
||||
#region Drawing
|
||||
|
||||
private static int RowLabelWidth => $"U+{MAX_CODE_POINT:x5}".Length + 1;
|
||||
@@ -262,7 +587,7 @@ public class CharMap : View, IDesignable
|
||||
}
|
||||
|
||||
int selectedCol = SelectedCodePoint % 16;
|
||||
int selectedRow = SelectedCodePoint / 16;
|
||||
int selectedRowIndex = VisibleRowIndexForCodePoint (SelectedCodePoint);
|
||||
|
||||
// Headers
|
||||
|
||||
@@ -302,32 +627,33 @@ public class CharMap : View, IDesignable
|
||||
// Start at 1 because Header.
|
||||
for (var y = 1; y < Viewport.Height; y++)
|
||||
{
|
||||
// What row is this?
|
||||
int row = (y + Viewport.Y - 1) / _rowHeight;
|
||||
int val = row * 16;
|
||||
// Which visible row is this?
|
||||
int visibleRow = (y + Viewport.Y - 1) / _rowHeight;
|
||||
|
||||
if (visibleRow < 0 || visibleRow >= _visibleRowStarts.Count)
|
||||
{
|
||||
// No row at this y; clear label area and continue
|
||||
Move (0, y);
|
||||
AddStr (new (' ', Viewport.Width));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int rowStart = _visibleRowStarts [visibleRow];
|
||||
|
||||
// Draw the row label (U+XXXX_)
|
||||
SetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Active);
|
||||
Move (0, y);
|
||||
|
||||
// Swap Active/Focus so the selected row is highlighted
|
||||
if (y + Viewport.Y - 1 == selectedRow)
|
||||
if (visibleRow == selectedRowIndex)
|
||||
{
|
||||
SetAttributeForRole (HasFocus ? VisualRole.Active : VisualRole.Focus);
|
||||
}
|
||||
|
||||
if (val > MAX_CODE_POINT)
|
||||
{
|
||||
// No row
|
||||
Move (0, y);
|
||||
AddStr (new (' ', RowLabelWidth));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0)
|
||||
{
|
||||
AddStr ($"U+{val / 16:x5}_");
|
||||
AddStr ($"U+{rowStart / 16:x5}_");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -349,12 +675,24 @@ public class CharMap : View, IDesignable
|
||||
Move (x, y);
|
||||
|
||||
// If we're at the cursor position highlight the cell
|
||||
if (row == selectedRow && col == selectedCol)
|
||||
if (visibleRow == selectedRowIndex && col == selectedCol)
|
||||
{
|
||||
SetAttributeForRole (VisualRole.Active);
|
||||
}
|
||||
|
||||
int scalar = val + col;
|
||||
int scalar = rowStart + col;
|
||||
|
||||
// Don't render out-of-range scalars
|
||||
if (scalar > MAX_CODE_POINT)
|
||||
{
|
||||
AddRune (' ');
|
||||
if (visibleRow == selectedRowIndex && col == selectedCol)
|
||||
{
|
||||
SetAttributeForRole (VisualRole.Normal);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var rune = (Rune)'?';
|
||||
|
||||
if (Rune.IsValid (scalar))
|
||||
@@ -364,9 +702,88 @@ public class CharMap : View, IDesignable
|
||||
|
||||
int width = rune.GetColumns ();
|
||||
|
||||
// Compute visibility based on ShowUnicodeCategory
|
||||
bool isVisible = Rune.IsValid (scalar);
|
||||
if (isVisible && ShowUnicodeCategory.HasValue)
|
||||
{
|
||||
Span<char> filterUtf16 = new char [2];
|
||||
rune.EncodeToUtf16 (filterUtf16);
|
||||
UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (filterUtf16 [0]);
|
||||
isVisible = cat == ShowUnicodeCategory.Value;
|
||||
}
|
||||
|
||||
if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0)
|
||||
{
|
||||
// Draw the rune
|
||||
// Glyph row
|
||||
if (isVisible)
|
||||
{
|
||||
RenderRune (rune, width);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRune (' ');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Width row (ShowGlyphWidths)
|
||||
if (isVisible)
|
||||
{
|
||||
// Draw the width of the rune faint
|
||||
Attribute attr = GetAttributeForRole (VisualRole.Normal);
|
||||
SetAttribute (attr with { Style = attr.Style | TextStyle.Faint });
|
||||
AddStr ($"{width}");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRune (' ');
|
||||
}
|
||||
}
|
||||
|
||||
// If we're at the cursor position, and we don't have focus
|
||||
if (visibleRow == selectedRowIndex && col == selectedCol)
|
||||
{
|
||||
SetAttributeForRole (VisualRole.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
void RenderRune (Rune rune, int width)
|
||||
{
|
||||
// Get the UnicodeCategory
|
||||
Span<char> utf16 = new char [2];
|
||||
int charCount = rune.EncodeToUtf16 (utf16);
|
||||
|
||||
// Get the bidi class for the first code unit
|
||||
// For most bidi characters, the first code unit is sufficient
|
||||
UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory (utf16 [0]);
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case UnicodeCategory.OtherNotAssigned:
|
||||
SetAttributeForRole (VisualRole.Highlight);
|
||||
AddRune (Rune.ReplacementChar);
|
||||
SetAttributeForRole (VisualRole.Normal);
|
||||
|
||||
break;
|
||||
|
||||
// Format character that affects the layout of text or the operation of text processes, but is not normally rendered.
|
||||
// These report width of 0 and don't render on their own.
|
||||
case UnicodeCategory.Format:
|
||||
SetAttributeForRole (VisualRole.Highlight);
|
||||
AddRune ('F');
|
||||
SetAttributeForRole (VisualRole.Normal);
|
||||
|
||||
break;
|
||||
|
||||
// Nonspacing character that indicates modifications of a base character.
|
||||
case UnicodeCategory.NonSpacingMark:
|
||||
// Spacing character that indicates modifications of a base character and affects the width of the glyph for that base character.
|
||||
case UnicodeCategory.SpacingCombiningMark:
|
||||
// Enclosing mark character, which is a nonspacing combining character that surrounds all previous characters up to and including a base character.
|
||||
case UnicodeCategory.EnclosingMark:
|
||||
if (width > 0)
|
||||
{
|
||||
AddRune (rune);
|
||||
@@ -394,28 +811,39 @@ public class CharMap : View, IDesignable
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRune (Rune.ReplacementChar);
|
||||
SetAttributeForRole (VisualRole.Highlight);
|
||||
AddRune ('M');
|
||||
SetAttributeForRole (VisualRole.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw the width of the rune faint
|
||||
Attribute attr = GetAttributeForRole (VisualRole.Normal);
|
||||
SetAttribute (attr with { Style = attr.Style | TextStyle.Faint });
|
||||
AddStr ($"{width}");
|
||||
}
|
||||
|
||||
// If we're at the cursor position, and we don't have focus
|
||||
if (row == selectedRow && col == selectedCol)
|
||||
{
|
||||
SetAttributeForRole (VisualRole.Normal);
|
||||
}
|
||||
break;
|
||||
|
||||
// These report width of 0, but render as 1
|
||||
case UnicodeCategory.Control:
|
||||
case UnicodeCategory.LineSeparator:
|
||||
case UnicodeCategory.ParagraphSeparator:
|
||||
case UnicodeCategory.Surrogate:
|
||||
AddRune (rune);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
// Draw the rune
|
||||
if (width > 0)
|
||||
{
|
||||
AddRune (rune);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException ($"The Rune \"{rune}\" (U+{rune.Value:x6}) has zero width and no special-case UnicodeCategory logic applies.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -560,7 +988,14 @@ public class CharMap : View, IDesignable
|
||||
return false;
|
||||
}
|
||||
|
||||
int row = (position.Y - 1 - -Viewport.Y) / _rowHeight; // -1 for header
|
||||
int visibleRow = (position.Y - 1 - -Viewport.Y) / _rowHeight;
|
||||
|
||||
if (visibleRow < 0 || visibleRow >= _visibleRowStarts.Count)
|
||||
{
|
||||
codePoint = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
int col = (position.X - RowLabelWidth - -Viewport.X) / COLUMN_WIDTH;
|
||||
|
||||
if (col > 15)
|
||||
@@ -568,7 +1003,7 @@ public class CharMap : View, IDesignable
|
||||
col = 15;
|
||||
}
|
||||
|
||||
codePoint = row * 16 + col;
|
||||
codePoint = _visibleRowStarts [visibleRow] + col;
|
||||
|
||||
if (codePoint > MAX_CODE_POINT)
|
||||
{
|
||||
@@ -579,199 +1014,4 @@ public class CharMap : View, IDesignable
|
||||
}
|
||||
|
||||
#endregion Mouse Handling
|
||||
|
||||
#region Details Dialog
|
||||
|
||||
[RequiresUnreferencedCode ("AOT")]
|
||||
[RequiresDynamicCode ("AOT")]
|
||||
private void ShowDetails ()
|
||||
{
|
||||
if (!Application.Initialized)
|
||||
{
|
||||
// Some unit tests invoke Accept without Init
|
||||
return;
|
||||
}
|
||||
|
||||
UcdApiClient? client = new ();
|
||||
var decResponse = string.Empty;
|
||||
var getCodePointError = string.Empty;
|
||||
|
||||
Dialog? waitIndicator = new ()
|
||||
{
|
||||
Title = Strings.charMapCPInfoDlgTitle,
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Center (),
|
||||
Width = 40,
|
||||
Height = 10,
|
||||
Buttons = [new () { Text = Strings.btnCancel }]
|
||||
};
|
||||
|
||||
var errorLabel = new Label
|
||||
{
|
||||
Text = UcdApiClient.BaseUrl,
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (3),
|
||||
TextAlignment = Alignment.Center
|
||||
};
|
||||
|
||||
var spinner = new SpinnerView
|
||||
{
|
||||
X = Pos.Center (),
|
||||
Y = Pos.Bottom (errorLabel),
|
||||
Style = new SpinnerStyle.Aesthetic ()
|
||||
};
|
||||
spinner.AutoSpin = true;
|
||||
waitIndicator.Add (errorLabel);
|
||||
waitIndicator.Add (spinner);
|
||||
|
||||
waitIndicator.Ready += async (s, a) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
decResponse = await client.GetCodepointDec (SelectedCodePoint).ConfigureAwait (false);
|
||||
Application.Invoke (() => waitIndicator.RequestStop ());
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
getCodePointError = errorLabel.Text = e.Message;
|
||||
Application.Invoke (() => waitIndicator.RequestStop ());
|
||||
}
|
||||
};
|
||||
Application.Run (waitIndicator);
|
||||
waitIndicator.Dispose ();
|
||||
|
||||
if (!string.IsNullOrEmpty (decResponse))
|
||||
{
|
||||
var name = string.Empty;
|
||||
|
||||
using (JsonDocument document = JsonDocument.Parse (decResponse))
|
||||
{
|
||||
JsonElement root = document.RootElement;
|
||||
|
||||
// Get a property by name and output its value
|
||||
if (root.TryGetProperty ("name", out JsonElement nameElement))
|
||||
{
|
||||
name = nameElement.GetString ();
|
||||
}
|
||||
|
||||
//// Navigate to a nested property and output its value
|
||||
//if (root.TryGetProperty ("property3", out JsonElement property3Element)
|
||||
//&& property3Element.TryGetProperty ("nestedProperty", out JsonElement nestedPropertyElement)) {
|
||||
// Console.WriteLine (nestedPropertyElement.GetString ());
|
||||
//}
|
||||
decResponse = JsonSerializer.Serialize (
|
||||
document.RootElement,
|
||||
new
|
||||
JsonSerializerOptions
|
||||
{ WriteIndented = true }
|
||||
);
|
||||
}
|
||||
|
||||
var title = $"{ToCamelCase (name!)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}";
|
||||
|
||||
Button? copyGlyph = new () { Text = Strings.charMapCopyGlyph };
|
||||
Button? copyCodepoint = new () { Text = Strings.charMapCopyCP };
|
||||
Button? cancel = new () { Text = Strings.btnCancel };
|
||||
|
||||
var dlg = new Dialog { Title = title, Buttons = [copyGlyph, copyCodepoint, cancel] };
|
||||
|
||||
copyGlyph.Accepting += (s, a) =>
|
||||
{
|
||||
CopyGlyph ();
|
||||
dlg!.RequestStop ();
|
||||
a.Handled = true;
|
||||
};
|
||||
|
||||
copyCodepoint.Accepting += (s, a) =>
|
||||
{
|
||||
CopyCodePoint ();
|
||||
dlg!.RequestStop ();
|
||||
a.Handled = true;
|
||||
};
|
||||
cancel.Accepting += (s, a) =>
|
||||
{
|
||||
dlg!.RequestStop ();
|
||||
a.Handled = true;
|
||||
};
|
||||
|
||||
var rune = (Rune)SelectedCodePoint;
|
||||
var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsAscii}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", Bmp: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsBmp}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", CombiningMark: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsCombiningMark ()}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", SurrogatePair: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.IsSurrogatePair ()}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", Plane: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.Plane}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = "Columns: ", X = 0, Y = Pos.Bottom (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.GetColumns ()}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = ", Utf16SequenceLength: ", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new () { Text = $"{rune.Utf16SequenceLength}", X = Pos.Right (label), Y = Pos.Top (label) };
|
||||
dlg.Add (label);
|
||||
|
||||
label = new ()
|
||||
{
|
||||
Text =
|
||||
$"{Strings.charMapInfoDlgInfoLabel} {UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint}:",
|
||||
X = 0,
|
||||
Y = Pos.Bottom (label)
|
||||
};
|
||||
dlg.Add (label);
|
||||
|
||||
var json = new TextView
|
||||
{
|
||||
X = 0,
|
||||
Y = Pos.Bottom (label),
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (2),
|
||||
ReadOnly = true,
|
||||
Text = decResponse
|
||||
};
|
||||
|
||||
dlg.Add (json);
|
||||
|
||||
Application.Run (dlg);
|
||||
dlg.Dispose ();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.ErrorQuery (
|
||||
Strings.error,
|
||||
$"{UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint} {Strings.failedGetting}{Environment.NewLine}{new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}.",
|
||||
Strings.btnOk
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Details Dialog
|
||||
}
|
||||
|
||||
@@ -515,7 +515,7 @@ public class TextView : View, IDesignable
|
||||
Command.Context,
|
||||
() =>
|
||||
{
|
||||
ShowContextMenu (true);
|
||||
ShowContextMenu (null);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1745,13 +1745,7 @@ public class TextView : View, IDesignable
|
||||
}
|
||||
else if (ev.Flags == ContextMenu!.MouseFlags)
|
||||
{
|
||||
ContextMenu!.X = ev.ScreenPosition.X;
|
||||
ContextMenu!.Y = ev.ScreenPosition.Y;
|
||||
|
||||
ShowContextMenu (false);
|
||||
|
||||
//ContextMenu.Position = ViewportToScreen ((Viewport with { X = ev.Position.X, Y = ev.Position.Y }).Location);
|
||||
//ShowContextMenu ();
|
||||
ShowContextMenu (ev.ScreenPosition);
|
||||
}
|
||||
|
||||
OnUnwrappedCursorPosition ();
|
||||
@@ -4574,14 +4568,18 @@ public class TextView : View, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowContextMenu (bool keyboard)
|
||||
private void ShowContextMenu (Point? mousePosition)
|
||||
{
|
||||
if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
|
||||
{
|
||||
_currentCulture = Thread.CurrentThread.CurrentUICulture;
|
||||
}
|
||||
|
||||
ContextMenu?.MakeVisible (ViewportToScreen (new Point (CursorPosition.X, CursorPosition.Y)));
|
||||
if (mousePosition is null)
|
||||
{
|
||||
mousePosition = ViewportToScreen (new Point (CursorPosition.X, CursorPosition.Y));
|
||||
}
|
||||
ContextMenu?.MakeVisible (mousePosition);
|
||||
}
|
||||
|
||||
private void StartSelecting ()
|
||||
|
||||
@@ -222,10 +222,12 @@ public class RuneTests
|
||||
[InlineData (
|
||||
'\u4dc0',
|
||||
"䷀",
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
)] // ䷀Hexagram For The Creative Heaven - U+4dc0 - https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
|
||||
)] // ䷀Hexagram For The Creative Heaven - U+4dc0 - https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
|
||||
// See https://github.com/microsoft/terminal/issues/19389
|
||||
|
||||
[InlineData ('\ud7b0', "ힰ", 1, 1, 3)] // ힰ ┤Hangul Jungseong O-Yeo - ힰ U+d7b0')]
|
||||
[InlineData ('\uf61e', "", 1, 1, 3)] // Private Use Area
|
||||
[InlineData ('\u23f0', "⏰", 2, 1, 3)] // Alarm Clock - ⏰ U+23f0
|
||||
|
||||
Reference in New Issue
Block a user