diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs
index 0665acf54..8932bd198 100644
--- a/Terminal.Gui/Views/ScrollBarView.cs
+++ b/Terminal.Gui/Views/ScrollBarView.cs
@@ -705,7 +705,6 @@ namespace Terminal.Gui {
if (mouseEvent.Flags != MouseFlags.Button1Released
&& (Application.MouseGrabView == null || Application.MouseGrabView != this)) {
-
Application.GrabMouse (this);
} else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
_lastLocation = -1;
@@ -718,11 +717,11 @@ namespace Terminal.Gui {
return Host.MouseEvent (mouseEvent);
}
- if (location == 0) {
+ if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) {
if (pos > 0) {
Position = pos - 1;
}
- } else if (location == barsize + 1) {
+ } else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) {
if (CanScroll (1, out _, _vertical)) {
Position = pos + 1;
}
diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs
index 59eba276f..f8cd3a473 100644
--- a/Terminal.Gui/Views/ScrollView.cs
+++ b/Terminal.Gui/Views/ScrollView.cs
@@ -366,7 +366,9 @@ namespace Terminal.Gui {
Driver.SetAttribute (GetNormalColor ());
Clear ();
- _contentView.Draw ();
+ if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) {
+ _contentView.Draw ();
+ }
DrawScrollBars ();
diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs
index 8c5f9593a..f5b31c219 100644
--- a/UICatalog/Scenarios/CharacterMap.cs
+++ b/UICatalog/Scenarios/CharacterMap.cs
@@ -1,517 +1,637 @@
#define DRAW_CONTENT
//#define BASE_DRAW_CONTENT
-using Microsoft.VisualBasic;
-using System.Text;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Text.Unicode;
+using System.Threading.Tasks;
using Terminal.Gui;
-using Terminal.Gui.Resources;
+using static Terminal.Gui.TableView;
+
+namespace UICatalog.Scenarios;
+
+///
+/// This Scenario demonstrates building a custom control (a class deriving from View) that:
+/// - Provides a "Character Map" application (like Windows' charmap.exe).
+/// - Helps test unicode character rendering in Terminal.Gui
+/// - Illustrates how to use ScrollView to do infinite scrolling
+///
+[ScenarioMetadata (Name: "Character Map", Description: "Unicode viewer demonstrating the ScrollView control.")]
+[ScenarioCategory ("Text and Formatting")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("ScrollView")]
+public class CharacterMap : Scenario {
+ CharMap _charMap;
+ Label _errorLabel;
+ TableView _categoryList;
+
+ public override void Setup ()
+ {
+ _charMap = new CharMap () {
+ X = 0,
+ Y = 0,
+ Height = Dim.Fill ()
+ };
+ Win.Add (_charMap);
+
+ var jumpLabel = new Label ("Jump To Code Point:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
+ Win.Add (jumpLabel);
+ var jumpEdit = new TextField () { X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3" };
+ Win.Add (jumpEdit);
+ _errorLabel = new Label ("") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] };
+ Win.Add (_errorLabel);
+
+ jumpEdit.TextChanged += JumpEdit_TextChanged;
+
+ _categoryList = new TableView () {
+ X = Pos.Right (_charMap),
+ Y = Pos.Bottom (jumpLabel),
+ Height = Dim.Fill ()
+ };
+
+ _categoryList.FullRowSelect = true;
+ //jumpList.Style.ShowHeaders = false;
+ //jumpList.Style.ShowHorizontalHeaderOverline = false;
+ //jumpList.Style.ShowHorizontalHeaderUnderline = false;
+ _categoryList.Style.ShowHorizontalBottomline = true;
+ //jumpList.Style.ShowVerticalCellLines = false;
+ //jumpList.Style.ShowVerticalHeaderLines = false;
+ _categoryList.Style.AlwaysShowHeaders = true;
+
+ var isDescending = false;
+
+ _categoryList.Table = CreateCategoryTable (0, isDescending);
+
+ // if user clicks the mouse in TableView
+ _categoryList.MouseClick += (s, e) => {
+ _categoryList.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
+ if (clickedCol != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+ var table = (EnumerableTableSource)_categoryList.Table;
+ var prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
+ isDescending = !isDescending;
+
+ _categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
+
+ table = (EnumerableTableSource)_categoryList.Table;
+ _categoryList.SelectedRow = table.Data
+ .Select ((item, index) => new { item, index })
+ .FirstOrDefault (x => x.item.Category == prevSelection)?.index ?? -1;
+ }
+ };
+
+ var longestName = UnicodeRange.Ranges.Max (r => r.Category.GetColumns ());
+ _categoryList.Style.ColumnStyles.Add (0, new ColumnStyle () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName });
+ _categoryList.Style.ColumnStyles.Add (1, new ColumnStyle () { MaxWidth = 1, MinWidth = 6 });
+ _categoryList.Style.ColumnStyles.Add (2, new ColumnStyle () { MaxWidth = 1, MinWidth = 6 });
+
+ _categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
+
+ _categoryList.SelectedCellChanged += (s, args) => {
+ EnumerableTableSource table = (EnumerableTableSource)_categoryList.Table;
+ _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
+ };
+
+ Win.Add (_categoryList);
+
+ _charMap.SelectedCodePoint = 0;
+ //jumpList.Refresh ();
+ _charMap.SetFocus ();
+
+ _charMap.Width = Dim.Fill () - _categoryList.Width;
+ }
+
+
+ EnumerableTableSource CreateCategoryTable (int sortByColumn, bool descending)
+ {
+ Func orderBy;
+ var categorySort = string.Empty;
+ var startSort = string.Empty;
+ var endSort = string.Empty;
+
+ var sortIndicator = descending ? CM.Glyphs.DownArrow.ToString () : CM.Glyphs.UpArrow.ToString ();
+ switch (sortByColumn) {
+ case 0:
+ orderBy = r => r.Category;
+ categorySort = sortIndicator;
+ break;
+ case 1:
+ orderBy = r => r.Start;
+ startSort = sortIndicator;
+ break;
+ case 2:
+ orderBy = r => r.End;
+ endSort = sortIndicator;
+ break;
+ default:
+ throw new ArgumentException ("Invalid column number.");
+ }
+
+ IOrderedEnumerable sortedRanges = descending ?
+ UnicodeRange.Ranges.OrderByDescending (orderBy) :
+ UnicodeRange.Ranges.OrderBy (orderBy);
+
+ return new EnumerableTableSource (sortedRanges, new Dictionary> ()
+ {
+ { $"Category{categorySort}", s => s.Category },
+ { $"Start{startSort}", s => $"{s.Start:x5}" },
+ { $"End{endSort}", s => $"{s.End:x5}" },
+ });
+ }
+
+ private void JumpEdit_TextChanged (object sender, TextChangedEventArgs e)
+ {
+ var jumpEdit = sender as TextField;
+ if (jumpEdit.Text.Length == 0) {
+ return;
+ }
+ uint result = 0;
+
+ if (jumpEdit.Text.StartsWith ("U+", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u")) {
+ try {
+ result = uint.Parse (jumpEdit.Text [2..^0], NumberStyles.HexNumber);
+ } catch (FormatException) {
+ _errorLabel.Text = $"Invalid hex value";
+ return;
+ }
+ } else if (jumpEdit.Text.StartsWith ("0", StringComparison.OrdinalIgnoreCase) || jumpEdit.Text.StartsWith ("\\u")) {
+ try {
+ result = uint.Parse (jumpEdit.Text, NumberStyles.HexNumber);
+ } catch (FormatException) {
+ _errorLabel.Text = $"Invalid hex value";
+ return;
+ }
+ } else {
+ try {
+ result = uint.Parse (jumpEdit.Text, NumberStyles.Integer);
+ } catch (FormatException) {
+ _errorLabel.Text = $"Invalid value";
+ return;
+ }
+ }
+ if (result > RuneExtensions.MaxUnicodeCodePoint) {
+ _errorLabel.Text = $"Beyond maximum codepoint";
+ return;
+ }
+ _errorLabel.Text = $"U+{result:x4}";
+
+ var table = (EnumerableTableSource)_categoryList.Table;
+ _categoryList.SelectedRow = table.Data
+ .Select ((item, index) => new { item, index })
+ .FirstOrDefault (x => x.item.Start <= result && x.item.End >= result)?.index ?? -1;
+ _categoryList.EnsureSelectedCellIsVisible ();
+
+ // Ensure the typed glyph is selected
+ _charMap.SelectedCodePoint = (int)result;
+ }
+}
+
+class CharMap : ScrollView {
-namespace UICatalog.Scenarios {
///
- /// This Scenario demonstrates building a custom control (a class deriving from View) that:
- /// - Provides a "Character Map" application (like Windows' charmap.exe).
- /// - Helps test unicode character rendering in Terminal.Gui
- /// - Illustrates how to use ScrollView to do infinite scrolling
+ /// Specifies the starting offset for the character map. The default is 0x2500
+ /// which is the Box Drawing characters.
///
- [ScenarioMetadata (Name: "Character Map",
- Description: "A Unicode character set viewier built as a custom control using the ScrollView control.")]
- [ScenarioCategory ("Text and Formatting")]
- [ScenarioCategory ("Controls")]
- [ScenarioCategory ("ScrollView")]
- public class CharacterMap : Scenario {
- CharMap _charMap;
- public override void Setup ()
- {
- _charMap = new CharMap () {
- X = 0,
- Y = 0,
- Height = Dim.Fill ()
- };
- Win.Add (_charMap);
-
- var jumpLabel = new Label ("Jump To Glyph:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
- Win.Add (jumpLabel);
- var jumpEdit = new TextField () { X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3" };
- Win.Add (jumpEdit);
- var errorLabel = new Label ("") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] };
- Win.Add (errorLabel);
-
- var radioItems = new (string radioLabel, uint start, uint end) [UnicodeRange.Ranges.Count];
-
- var ranges = UnicodeRange.Ranges.OrderBy (o => o.Start).ToList ();
-
- for (var i = 0; i < ranges.Count; i++) {
- var range = ranges [i];
- radioItems [i] = CreateRadio (range.Category, range.Start, range.End);
- }
- (string radioLabel, uint start, uint end) CreateRadio (string title, uint start, uint end)
- {
- return ($"{title} (U+{start:x5}-{end:x5})", start, end);
- }
-
- var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (_charMap) + 1, Y = Pos.Bottom (jumpLabel) + 1 };
- Win.Add (label);
-
- var jumpList = new ListView (radioItems.Select (t => t.radioLabel).ToArray ()) {
- X = Pos.X (label) + 1,
- Y = Pos.Bottom (label),
- Width = radioItems.Max (r => r.radioLabel.Length) + 2,
- Height = Dim.Fill (1),
- SelectedItem = 0
- };
- jumpList.SelectedItemChanged += (s, args) => {
- _charMap.StartGlyph = radioItems [jumpList.SelectedItem].start;
- };
-
- Win.Add (jumpList);
-
- jumpEdit.TextChanged += (s, e) => {
- uint result = 0;
- if (jumpEdit.Text.Length == 0) return;
- try {
- result = Convert.ToUInt32 (jumpEdit.Text, 10);
- } catch (OverflowException) {
- errorLabel.Text = $"Invalid (overflow)";
- return;
- } catch (FormatException) {
- try {
- result = Convert.ToUInt32 (jumpEdit.Text, 16);
- } catch (OverflowException) {
- errorLabel.Text = $"Invalid (overflow)";
- return;
- } catch (FormatException) {
- errorLabel.Text = $"Invalid (can't parse)";
- return;
- }
- }
- errorLabel.Text = $"U+{result:x4}";
- var foundIndex = ranges.FindIndex (x => x.Start <= result && x.End >= result);
- if (foundIndex > -1 && jumpList.SelectedItem != foundIndex) {
- jumpList.SelectedItem = foundIndex;
- }
- // Ensure the typed glyph is elected after jumpList
- _charMap.SelectedGlyph = result;
- };
-
- //jumpList.Refresh ();
- _charMap.SetFocus ();
-
- _charMap.Width = Dim.Fill () - jumpList.Width;
+ public int StartCodePoint {
+ get => _start;
+ set {
+ _start = value;
+ _selected = value;
+ ContentOffset = new Point (0, (int)(_start / 16));
+ SetNeedsDisplay ();
}
}
- class CharMap : ScrollView {
+ public event EventHandler SelectedCodePointChanged;
- ///
- /// Specifies the starting offset for the character map. The default is 0x2500
- /// which is the Box Drawing characters.
- ///
- public uint StartGlyph {
- get => _start;
- set {
- _start = value;
- _selected = value;
- ContentOffset = new Point (0, (int)(_start / 16));
- SetNeedsDisplay ();
+ ///
+ /// Specifies the starting offset for the character map. The default is 0x2500
+ /// which is the Box Drawing characters.
+ ///
+ public int SelectedCodePoint {
+ get => _selected;
+ set {
+ _selected = value;
+ var col = Cursor.X;
+ var row = Cursor.Y;
+ var height = (Bounds.Height / ROW_HEIGHT) - (ShowHorizontalScrollIndicator ? 2 : 1);
+ if (row + ContentOffset.Y < 0) {
+ // Moving up.
+ ContentOffset = new Point (ContentOffset.X, row);
+ } else if (row + ContentOffset.Y >= height) {
+ // Moving down.
+ ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + ROW_HEIGHT));
}
- }
-
- ///
- /// Specifies the starting offset for the character map. The default is 0x2500
- /// which is the Box Drawing characters.
- ///
- public uint SelectedGlyph {
- get => _selected;
- set {
- _selected = value;
- int row = (int)_selected / 16;
- int height = (Bounds.Height / ROW_HEIGHT) - (ShowHorizontalScrollIndicator ? 2 : 1);
- if (row + ContentOffset.Y < 0) {
- // Moving up.
- ContentOffset = new Point (ContentOffset.X, row);
- } else if (row + ContentOffset.Y >= height) {
- // Moving down.
- ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + ROW_HEIGHT));
- }
- int col = (((int)_selected - (row * 16)) * COLUMN_WIDTH);
- int width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
- if (col + ContentOffset.X < 0) {
- // Moving left.
- ContentOffset = new Point (col, ContentOffset.Y);
- } else if (col + ContentOffset.X >= width) {
- // Moving right.
- ContentOffset = new Point (Math.Min (col, col - width + COLUMN_WIDTH), ContentOffset.Y);
- }
- SetNeedsDisplay ();
+ var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
+ if (col + ContentOffset.X < 0) {
+ // Moving left.
+ ContentOffset = new Point (col, ContentOffset.Y);
+ } else if (col + ContentOffset.X >= width) {
+ // Moving right.
+ ContentOffset = new Point (Math.Min (col, col - width + COLUMN_WIDTH), ContentOffset.Y);
}
+ SetNeedsDisplay ();
+ SelectedCodePointChanged?.Invoke (this, new ListViewItemEventArgs (SelectedCodePoint, null));
}
+ }
- uint _start = 0;
- uint _selected = 0;
-
- public const int COLUMN_WIDTH = 3;
- public const int ROW_HEIGHT = 1;
-
- public static uint MaxCodePointVal => 0x10FFFF;
-
- public static int RowLabelWidth => $"U+{MaxCodePointVal:x5}".Length + 1;
- public static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
-
- public CharMap ()
- {
- ColorScheme = Colors.Dialog;
- CanFocus = true;
-
- ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePointVal / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)));
-
- AddCommand (Command.ScrollUp, () => {
- if (SelectedGlyph >= 16) {
- SelectedGlyph = SelectedGlyph - 16;
- }
- return true;
- });
- AddCommand (Command.ScrollDown, () => {
- if (SelectedGlyph < MaxCodePointVal - 16) {
- SelectedGlyph = SelectedGlyph + 16;
- }
- return true;
- });
- AddCommand (Command.ScrollLeft, () => {
- if (SelectedGlyph > 0) {
- SelectedGlyph--;
- }
- return true;
- });
- AddCommand (Command.ScrollRight, () => {
- if (SelectedGlyph < MaxCodePointVal) {
- SelectedGlyph++;
- }
- return true;
- });
- AddCommand (Command.PageUp, () => {
- var page = (uint)(Bounds.Height / ROW_HEIGHT - 1) * 16;
- SelectedGlyph -= Math.Min (page, SelectedGlyph);
- return true;
- });
- AddCommand (Command.PageDown, () => {
- var page = (uint)(Bounds.Height / ROW_HEIGHT - 1) * 16;
- SelectedGlyph += Math.Min (page, MaxCodePointVal - SelectedGlyph);
- return true;
- });
- AddCommand (Command.TopHome, () => {
- SelectedGlyph = 0;
- return true;
- });
- AddCommand (Command.BottomEnd, () => {
- SelectedGlyph = MaxCodePointVal;
- return true;
- });
- AddKeyBinding (Key.Enter, Command.Accept);
- AddCommand (Command.Accept, () => {
- MessageBox.Query ("Glyph", $"{new Rune ((uint)SelectedGlyph)} U+{SelectedGlyph:x4}", "Ok");
- return true;
- });
-
- MouseClick += Handle_MouseClick;
- Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+ public Point Cursor {
+ get {
+ var row = SelectedCodePoint / 16;
+ var col = (SelectedCodePoint - row * 16) * COLUMN_WIDTH;
+ return new Point (col, row);
}
+ set => throw new NotImplementedException ();
+ }
- private void CopyValue ()
- {
- Clipboard.Contents = $"U+{SelectedGlyph:x5}";
+ public override void PositionCursor ()
+ {
+ if (HasFocus && Cursor.X + ContentOffset.X + RowLabelWidth + 1 >= RowLabelWidth &&
+ Cursor.X + ContentOffset.X + RowLabelWidth + 1 < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) &&
+ Cursor.Y + ContentOffset.Y + 1 > 0 &&
+ Cursor.Y + ContentOffset.Y + 1 < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) {
+
+ Driver.SetCursorVisibility (CursorVisibility.Default);
+ Move (Cursor.X + ContentOffset.X + RowLabelWidth + 1, Cursor.Y + ContentOffset.Y + 1);
+ } else {
+ Driver.SetCursorVisibility (CursorVisibility.Invisible);
}
+ }
- private void CopyGlyph ()
- {
- Clipboard.Contents = $"{new Rune (SelectedGlyph)}";
- }
- public override void OnDrawContent (Rect contentArea)
- {
- base.OnDrawContent (contentArea);
+ int _start = 0;
+ int _selected = 0;
- if (ShowHorizontalScrollIndicator && ContentSize.Height < (int)(MaxCodePointVal / 16 + 2)) {
- ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePointVal / 16 + 2));
- int row = (int)_selected / 16;
- int col = (((int)_selected - (row * 16)) * COLUMN_WIDTH);
- int width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
- if (col + ContentOffset.X >= width) {
- // Snap to the selected glyph.
- ContentOffset = new Point (Math.Min (col, col - width + COLUMN_WIDTH), ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
- } else {
- ContentOffset = new Point (ContentOffset.X - col, ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
- }
- SetNeedsDisplay ();
- } else if (!ShowHorizontalScrollIndicator && ContentSize.Height > (int)(MaxCodePointVal / 16 + 1)) {
- ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePointVal / 16 + 1));
- // Snap 1st column into view if it's been scrolled horizontally
- ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
- SetNeedsDisplay ();
+ public const int COLUMN_WIDTH = 3;
+ public const int ROW_HEIGHT = 1;
+
+ public static int MaxCodePoint => 0x10FFFF;
+
+ public static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1;
+ public static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
+
+ public CharMap ()
+ {
+ ColorScheme = Colors.Dialog;
+ CanFocus = true;
+ ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)));
+
+ AddCommand (Command.ScrollUp, () => {
+ if (SelectedCodePoint >= 16) {
+ SelectedCodePoint -= 16;
}
- }
-
- private Point _cursorPos;
-
- public override void OnDrawContentComplete (Rect contentArea)
- {
- Rect viewport = new Rect (ContentOffset,
- new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
- Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0)));
-
- var oldClip = Driver.Clip;
- Driver.Clip = Bounds;
- // Redraw doesn't know about the scroll indicators, so if off, add one to height
- if (!ShowHorizontalScrollIndicator) {
- Driver.Clip = new Rect (Bounds.X, Bounds.Y, Bounds.Width, Bounds.Height + 1);
+ return true;
+ });
+ AddCommand (Command.ScrollDown, () => {
+ if (SelectedCodePoint < MaxCodePoint - 16) {
+ SelectedCodePoint += 16;
}
- Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.Focus);
- Move (0, 0);
- Driver.AddStr (new string (' ', RowLabelWidth + 1));
- for (int hexDigit = 0; hexDigit < 16; hexDigit++) {
- var x = ContentOffset.X + RowLabelWidth + (hexDigit * COLUMN_WIDTH);
- if (x > RowLabelWidth - 2) {
- Move (x, 0);
- Driver.AddStr ($" {hexDigit:x} ");
- }
+ return true;
+ });
+ AddCommand (Command.ScrollLeft, () => {
+ if (SelectedCodePoint > 0) {
+ SelectedCodePoint--;
}
-
- var firstColumnX = viewport.X + RowLabelWidth;
- for (int row = -ContentOffset.Y, y = 0; row <= (-ContentOffset.Y) + (Bounds.Height / ROW_HEIGHT); row++, y += ROW_HEIGHT) {
- int val = (row) * 16;
- Driver.SetAttribute (GetNormalColor ());
- Move (firstColumnX, y + 1);
- Driver.AddStr (new string (' ', 16 * COLUMN_WIDTH));
- if (val <= MaxCodePointVal) {
- Driver.SetAttribute (GetNormalColor ());
- for (int col = 0; col < 16; col++) {
- uint glyph = (uint)((uint)val + col);
- Rune rune;
- if (char.IsSurrogate ((char)glyph)) {
- rune = Rune.ReplacementChar;
- } else {
- rune = new Rune (glyph);
- }
- Move (firstColumnX + (col * COLUMN_WIDTH) + 1, y + 1);
- if (glyph == SelectedGlyph) {
- Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
- _cursorPos = new Point (firstColumnX + (col * COLUMN_WIDTH) + 1, y + 1);
- } else {
- Driver.SetAttribute (GetNormalColor ());
- }
- Driver.AddRune (rune);
- }
- Move (0, y + 1);
- Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.Focus);
- var rowLabel = $"U+{val / 16:x5}_ ";
- Driver.AddStr (rowLabel);
- }
+ return true;
+ });
+ AddCommand (Command.ScrollRight, () => {
+ if (SelectedCodePoint < MaxCodePoint) {
+ SelectedCodePoint++;
}
- Driver.Clip = oldClip;
- }
+ return true;
+ });
+ AddCommand (Command.PageUp, () => {
+ var page = (Bounds.Height / ROW_HEIGHT - 1) * 16;
+ SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
+ return true;
+ });
+ AddCommand (Command.PageDown, () => {
+ var page = (Bounds.Height / ROW_HEIGHT - 1) * 16;
+ SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
+ return true;
+ });
+ AddCommand (Command.TopHome, () => {
+ SelectedCodePoint = 0;
+ return true;
+ });
+ AddCommand (Command.BottomEnd, () => {
+ SelectedCodePoint = MaxCodePoint;
+ return true;
+ });
+ AddKeyBinding (Key.Enter, Command.Accept);
+ AddCommand (Command.Accept, () => {
+ ShowDetails ();
+ return true;
+ });
- public override void PositionCursor ()
- {
- if (_cursorPos.Y < Bounds.Height && SelectedGlyph >= -ContentOffset.Y + _cursorPos.Y - 1
- && SelectedGlyph <= (-ContentOffset.Y + _cursorPos.Y - (ShowHorizontalScrollIndicator ? 1 : 0)) * 16 - 1) {
+ MouseClick += Handle_MouseClick;
+ }
- Application.Driver.SetCursorVisibility (CursorVisibility.Default);
- Move (_cursorPos.X, _cursorPos.Y);
+ private void CopyCodePoint () => Clipboard.Contents = $"U+{SelectedCodePoint:x5}";
+ private void CopyGlyph () => Clipboard.Contents = $"{new Rune ((char)SelectedCodePoint)}";
+
+ public override void OnDrawContent (Rect contentArea)
+ {
+ if (ShowHorizontalScrollIndicator && ContentSize.Height < (int)(MaxCodePoint / 16 + 2)) {
+ ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 2));
+ var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
+ if (Cursor.X + ContentOffset.X >= width) {
+ // Snap to the selected glyph.
+ ContentOffset = new Point (Math.Min (Cursor.X, Cursor.X - width + COLUMN_WIDTH), ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
} else {
- Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+ ContentOffset = new Point (ContentOffset.X - Cursor.X, ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
+ }
+ } else if (!ShowHorizontalScrollIndicator && ContentSize.Height > (int)(MaxCodePoint / 16 + 1)) {
+ ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 1));
+ // Snap 1st column into view if it's been scrolled horizontally
+ ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
+ }
+ base.OnDrawContent (contentArea);
+ }
+
+ //public void CharMap_DrawContent (object s, DrawEventArgs a)
+ public override void OnDrawContentComplete (Rect contentArea)
+ {
+ Rect viewport = new Rect (ContentOffset,
+ new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
+ Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0)));
+
+ var oldClip = ClipToBounds ();
+ if (ShowHorizontalScrollIndicator) {
+ // ClipToBounds doesn't know about the scroll indicators, so if off, subtract one from height
+ Driver.Clip = new Rect (Driver.Clip.Location, new Size (Driver.Clip.Width, Driver.Clip.Height - 1));
+ }
+ if (ShowVerticalScrollIndicator) {
+ // ClipToBounds doesn't know about the scroll indicators, so if off, subtract one from width
+ Driver.Clip = new Rect (Driver.Clip.Location, new Size (Driver.Clip.Width - 1, Driver.Clip.Height));
+ }
+
+ var cursorCol = Cursor.X;
+ var cursorRow = Cursor.Y;
+
+ Driver.SetAttribute (GetHotNormalColor ());
+ Move (0, 0);
+ Driver.AddStr (new string (' ', RowLabelWidth + 1));
+ for (int hexDigit = 0; hexDigit < 16; hexDigit++) {
+ var x = ContentOffset.X + RowLabelWidth + (hexDigit * COLUMN_WIDTH);
+ if (x > RowLabelWidth - 2) {
+ Move (x, 0);
+ Driver.SetAttribute (GetHotNormalColor ());
+ Driver.AddStr (" ");
+ Driver.SetAttribute (HasFocus && (cursorCol + RowLabelWidth == x) ? ColorScheme.HotFocus : GetHotNormalColor ());
+ Driver.AddStr ($"{hexDigit:x}");
+ Driver.SetAttribute (GetHotNormalColor ());
+ Driver.AddStr (" ");
}
}
- ContextMenu _contextMenu = new ContextMenu ();
- void Handle_MouseClick (object sender, MouseEventEventArgs args)
- {
- var me = args.MouseEvent;
- if (me.Flags == MouseFlags.ReportMousePosition || (me.Flags != MouseFlags.Button1Clicked &&
- me.Flags != MouseFlags.Button1DoubleClicked &&
- me.Flags != _contextMenu.MouseFlags)) {
- return;
+ var firstColumnX = viewport.X + RowLabelWidth;
+ for (int row = -ContentOffset.Y, y = 1; row <= (-ContentOffset.Y) + (Bounds.Height / ROW_HEIGHT); row++, y += ROW_HEIGHT) {
+ var val = (row) * 16;
+ if (val > MaxCodePoint) {
+ continue;
}
-
- if (me.X < RowLabelWidth) {
- return;
+ Move (firstColumnX + COLUMN_WIDTH, y);
+ Driver.SetAttribute (GetNormalColor ());
+ for (int col = 0; col < 16; col++) {
+ var x = firstColumnX + COLUMN_WIDTH * col + 1;
+ Move (x, y);
+ if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
+ Driver.SetAttribute (GetFocusColor ());
+ }
+ Driver.AddRune (new Rune ((char)(val + col)));
+ if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
+ Driver.SetAttribute (GetNormalColor ());
+ }
}
+ Move (0, y);
+ Driver.SetAttribute (HasFocus && (cursorRow + ContentOffset.Y + 1 == y) ? ColorScheme.HotFocus : ColorScheme.HotNormal);
+ var rowLabel = $"U+{val / 16:x5}_ ";
+ Driver.AddStr (rowLabel);
+ }
+ Driver.Clip = oldClip;
+ }
- if (me.Y < 1) {
- return;
- }
+ ContextMenu _contextMenu = new ContextMenu ();
+ void Handle_MouseClick (object sender, MouseEventEventArgs args)
+ {
+ var me = args.MouseEvent;
+ if (me.Flags == MouseFlags.ReportMousePosition || (me.Flags != MouseFlags.Button1Clicked &&
+ me.Flags != MouseFlags.Button1DoubleClicked)) { // && me.Flags != _contextMenu.MouseFlags)) {
+ return;
+ }
- var row = (me.Y - 1);
- var col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH;
- uint val = (uint)((((uint)row - (uint)ContentOffset.Y) * 16) + col);
- if (val > MaxCodePointVal) {
- return;
- }
+ if (me.X < RowLabelWidth) {
+ return;
+ }
- if (me.Flags == MouseFlags.Button1Clicked) {
- SelectedGlyph = (uint)val;
- return;
- }
+ if (me.Y < 1) {
+ return;
+ }
- if (me.Flags == MouseFlags.Button1DoubleClicked) {
- SelectedGlyph = (uint)val;
- MessageBox.Query ("Glyph", $"{new Rune (val)} U+{SelectedGlyph:x4}", "Ok");
- return;
- }
+ var row = me.Y - 1;
+ var col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH;
+ if (row < 0 || row > Bounds.Height || col < 0 || col > 15) {
+ return;
+ }
- if (me.Flags == _contextMenu.MouseFlags) {
- SelectedGlyph = (uint)val;
- _contextMenu = new ContextMenu (me.X + 1, me.Y + 1,
- new MenuBarItem (new MenuItem [] {
+ var val = (row - ContentOffset.Y) * 16 + col;
+ if (val > MaxCodePoint) {
+ return;
+ }
+
+ if (me.Flags == MouseFlags.Button1Clicked) {
+ SelectedCodePoint = val;
+ return;
+ }
+
+ if (me.Flags == MouseFlags.Button1DoubleClicked) {
+ SelectedCodePoint = val;
+ ShowDetails ();
+ return;
+ }
+
+ if (me.Flags == _contextMenu.MouseFlags) {
+ SelectedCodePoint = val;
+ _contextMenu = new ContextMenu (me.X + 1, me.Y + 1,
+ new MenuBarItem (new MenuItem [] {
new MenuItem ("_Copy Glyph", "", () => CopyGlyph (), null, null, Key.C | Key.CtrlMask),
- new MenuItem ("Copy _Value", "", () => CopyValue (), null, null, Key.C | Key.ShiftMask | Key.CtrlMask),
- }) {
+ new MenuItem ("Copy Code _Point", "", () => CopyCodePoint (), null, null, Key.C | Key.ShiftMask | Key.CtrlMask),
+ }) {
- }
- );
- _contextMenu.Show ();
- }
+ }
+ );
+ _contextMenu.Show ();
}
}
- class UnicodeRange {
- public uint Start;
- public uint End;
- public string Category;
- public UnicodeRange (uint start, uint end, string category)
- {
- this.Start = start;
- this.End = end;
- this.Category = category;
+ public static string ToCamelCase (string str)
+ {
+ if (string.IsNullOrEmpty (str)) {
+ return str;
}
- public static List Ranges = new List {
- new UnicodeRange (0x0000, 0x001F, "ASCII Control Characters"),
- new UnicodeRange (0x0080, 0x009F, "C0 Control Characters"),
- new UnicodeRange(0x1100, 0x11ff,"Hangul Jamo"), // This is where wide chars tend to start
- new UnicodeRange(0x20A0, 0x20CF,"Currency Symbols"),
- new UnicodeRange(0x2100, 0x214F,"Letterlike Symbols"),
- new UnicodeRange(0x2160, 0x218F, "Roman Numerals"),
- new UnicodeRange(0x2190, 0x21ff,"Arrows" ),
- new UnicodeRange(0x2200, 0x22ff,"Mathematical symbols"),
- new UnicodeRange(0x2300, 0x23ff,"Miscellaneous Technical"),
- new UnicodeRange(0x24B6, 0x24e9,"Circled Latin Capital Letters"),
- new UnicodeRange(0x1F130, 0x1F149,"Squared Latin Capital Letters"),
- new UnicodeRange(0x2500, 0x25ff,"Box Drawing & Geometric Shapes"),
- new UnicodeRange(0x2600, 0x26ff,"Miscellaneous Symbols"),
- new UnicodeRange(0x2700, 0x27ff,"Dingbats"),
- new UnicodeRange(0x2800, 0x28ff,"Braille"),
- new UnicodeRange(0x2b00, 0x2bff,"Miscellaneous Symbols and Arrows"),
- new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"),
- new UnicodeRange(0x12400, 0x1240f,"Cuneiform Numbers and Punctuation"),
- new UnicodeRange(0x1FA00, 0x1FA0f,"Chess Symbols"),
+ TextInfo textInfo = new CultureInfo ("en-US", false).TextInfo;
- new UnicodeRange (0x0020 ,0x007F ,"Basic Latin"),
- new UnicodeRange (0x00A0 ,0x00FF ,"Latin-1 Supplement"),
- new UnicodeRange (0x0100 ,0x017F ,"Latin Extended-A"),
- new UnicodeRange (0x0180 ,0x024F ,"Latin Extended-B"),
- new UnicodeRange (0x0250 ,0x02AF ,"IPA Extensions"),
- new UnicodeRange (0x02B0 ,0x02FF ,"Spacing Modifier Letters"),
- new UnicodeRange (0x0300 ,0x036F ,"Combining Diacritical Marks"),
- new UnicodeRange (0x0370 ,0x03FF ,"Greek and Coptic"),
- new UnicodeRange (0x0400 ,0x04FF ,"Cyrillic"),
- new UnicodeRange (0x0500 ,0x052F ,"Cyrillic Supplementary"),
- new UnicodeRange (0x0530 ,0x058F ,"Armenian"),
- new UnicodeRange (0x0590 ,0x05FF ,"Hebrew"),
- new UnicodeRange (0x0600 ,0x06FF ,"Arabic"),
- new UnicodeRange (0x0700 ,0x074F ,"Syriac"),
- new UnicodeRange (0x0780 ,0x07BF ,"Thaana"),
- new UnicodeRange (0x0900 ,0x097F ,"Devanagari"),
- new UnicodeRange (0x0980 ,0x09FF ,"Bengali"),
- new UnicodeRange (0x0A00 ,0x0A7F ,"Gurmukhi"),
- new UnicodeRange (0x0A80 ,0x0AFF ,"Gujarati"),
- new UnicodeRange (0x0B00 ,0x0B7F ,"Oriya"),
- new UnicodeRange (0x0B80 ,0x0BFF ,"Tamil"),
- new UnicodeRange (0x0C00 ,0x0C7F ,"Telugu"),
- new UnicodeRange (0x0C80 ,0x0CFF ,"Kannada"),
- new UnicodeRange (0x0D00 ,0x0D7F ,"Malayalam"),
- new UnicodeRange (0x0D80 ,0x0DFF ,"Sinhala"),
- new UnicodeRange (0x0E00 ,0x0E7F ,"Thai"),
- new UnicodeRange (0x0E80 ,0x0EFF ,"Lao"),
- new UnicodeRange (0x0F00 ,0x0FFF ,"Tibetan"),
- new UnicodeRange (0x1000 ,0x109F ,"Myanmar"),
- new UnicodeRange (0x10A0 ,0x10FF ,"Georgian"),
- new UnicodeRange (0x1100 ,0x11FF ,"Hangul Jamo"),
- new UnicodeRange (0x1200 ,0x137F ,"Ethiopic"),
- new UnicodeRange (0x13A0 ,0x13FF ,"Cherokee"),
- new UnicodeRange (0x1400 ,0x167F ,"Unified Canadian Aboriginal Syllabics"),
- new UnicodeRange (0x1680 ,0x169F ,"Ogham"),
- new UnicodeRange (0x16A0 ,0x16FF ,"Runic"),
- new UnicodeRange (0x1700 ,0x171F ,"Tagalog"),
- new UnicodeRange (0x1720 ,0x173F ,"Hanunoo"),
- new UnicodeRange (0x1740 ,0x175F ,"Buhid"),
- new UnicodeRange (0x1760 ,0x177F ,"Tagbanwa"),
- new UnicodeRange (0x1780 ,0x17FF ,"Khmer"),
- new UnicodeRange (0x1800 ,0x18AF ,"Mongolian"),
- new UnicodeRange (0x1900 ,0x194F ,"Limbu"),
- new UnicodeRange (0x1950 ,0x197F ,"Tai Le"),
- new UnicodeRange (0x19E0 ,0x19FF ,"Khmer Symbols"),
- new UnicodeRange (0x1D00 ,0x1D7F ,"Phonetic Extensions"),
- new UnicodeRange (0x1E00 ,0x1EFF ,"Latin Extended Additional"),
- new UnicodeRange (0x1F00 ,0x1FFF ,"Greek Extended"),
- new UnicodeRange (0x2000 ,0x206F ,"General Punctuation"),
- new UnicodeRange (0x2070 ,0x209F ,"Superscripts and Subscripts"),
- new UnicodeRange (0x20A0 ,0x20CF ,"Currency Symbols"),
- new UnicodeRange (0x20D0 ,0x20FF ,"Combining Diacritical Marks for Symbols"),
- new UnicodeRange (0x2100 ,0x214F ,"Letterlike Symbols"),
- new UnicodeRange (0x2150 ,0x218F ,"Number Forms"),
- new UnicodeRange (0x2190 ,0x21FF ,"Arrows"),
- new UnicodeRange (0x2200 ,0x22FF ,"Mathematical Operators"),
- new UnicodeRange (0x2300 ,0x23FF ,"Miscellaneous Technical"),
- new UnicodeRange (0x2400 ,0x243F ,"Control Pictures"),
- new UnicodeRange (0x2440 ,0x245F ,"Optical Character Recognition"),
- new UnicodeRange (0x2460 ,0x24FF ,"Enclosed Alphanumerics"),
- new UnicodeRange (0x2500 ,0x257F ,"Box Drawing"),
- new UnicodeRange (0x2580 ,0x259F ,"Block Elements"),
- new UnicodeRange (0x25A0 ,0x25FF ,"Geometric Shapes"),
- new UnicodeRange (0x2600 ,0x26FF ,"Miscellaneous Symbols"),
- new UnicodeRange (0x2700 ,0x27BF ,"Dingbats"),
- new UnicodeRange (0x27C0 ,0x27EF ,"Miscellaneous Mathematical Symbols-A"),
- new UnicodeRange (0x27F0 ,0x27FF ,"Supplemental Arrows-A"),
- new UnicodeRange (0x2800 ,0x28FF ,"Braille Patterns"),
- new UnicodeRange (0x2900 ,0x297F ,"Supplemental Arrows-B"),
- new UnicodeRange (0x2980 ,0x29FF ,"Miscellaneous Mathematical Symbols-B"),
- new UnicodeRange (0x2A00 ,0x2AFF ,"Supplemental Mathematical Operators"),
- new UnicodeRange (0x2B00 ,0x2BFF ,"Miscellaneous Symbols and Arrows"),
- new UnicodeRange (0x2E80 ,0x2EFF ,"CJK Radicals Supplement"),
- new UnicodeRange (0x2F00 ,0x2FDF ,"Kangxi Radicals"),
- new UnicodeRange (0x2FF0 ,0x2FFF ,"Ideographic Description Characters"),
- new UnicodeRange (0x3000 ,0x303F ,"CJK Symbols and Punctuation"),
- new UnicodeRange (0x3040 ,0x309F ,"Hiragana"),
- new UnicodeRange (0x30A0 ,0x30FF ,"Katakana"),
- new UnicodeRange (0x3100 ,0x312F ,"Bopomofo"),
- new UnicodeRange (0x3130 ,0x318F ,"Hangul Compatibility Jamo"),
- new UnicodeRange (0x3190 ,0x319F ,"Kanbun"),
- new UnicodeRange (0x31A0 ,0x31BF ,"Bopomofo Extended"),
- new UnicodeRange (0x31F0 ,0x31FF ,"Katakana Phonetic Extensions"),
- new UnicodeRange (0x3200 ,0x32FF ,"Enclosed CJK Letters and Months"),
- new UnicodeRange (0x3300 ,0x33FF ,"CJK Compatibility"),
- new UnicodeRange (0x3400 ,0x4DBF ,"CJK Unified Ideographs Extension A"),
- new UnicodeRange (0x4DC0 ,0x4DFF ,"Yijing Hexagram Symbols"),
- new UnicodeRange (0x4E00 ,0x9FFF ,"CJK Unified Ideographs"),
- new UnicodeRange (0xA000 ,0xA48F ,"Yi Syllables"),
- new UnicodeRange (0xA490 ,0xA4CF ,"Yi Radicals"),
- new UnicodeRange (0xAC00 ,0xD7AF ,"Hangul Syllables"),
- new UnicodeRange (0xD800 ,0xDB7F ,"High Surrogates"),
- new UnicodeRange (0xDB80 ,0xDBFF ,"High Private Use Surrogates"),
- new UnicodeRange (0xDC00 ,0xDFFF ,"Low Surrogates"),
- new UnicodeRange (0xE000 ,0xF8FF ,"Private Use Area"),
- new UnicodeRange (0xF900 ,0xFAFF ,"CJK Compatibility Ideographs"),
- new UnicodeRange (0xFB00 ,0xFB4F ,"Alphabetic Presentation Forms"),
- new UnicodeRange (0xFB50 ,0xFDFF ,"Arabic Presentation Forms-A"),
- new UnicodeRange (0xFE00 ,0xFE0F ,"Variation Selectors"),
- new UnicodeRange (0xFE20 ,0xFE2F ,"Combining Half Marks"),
- new UnicodeRange (0xFE30 ,0xFE4F ,"CJK Compatibility Forms"),
- new UnicodeRange (0xFE50 ,0xFE6F ,"Small Form Variants"),
- new UnicodeRange (0xFE70 ,0xFEFF ,"Arabic Presentation Forms-B"),
- new UnicodeRange (0xFF00 ,0xFFEF ,"Halfwidth and Fullwidth Forms"),
- new UnicodeRange (0xFFF0 ,0xFFFF ,"Specials"),
+ str = textInfo.ToLower (str);
+ str = textInfo.ToTitleCase (str);
+
+ return str;
+ }
+
+ void ShowDetails ()
+ {
+ var client = new UcdApiClient ();
+ string decResponse = string.Empty;
+
+ var waitIndicator = new Dialog (new Button ("Cancel")) {
+ Title = "Getting Code Point Information",
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Height = 7,
+ Width = 50
+ };
+ var errorLabel = new Label () {
+ Text = UcdApiClient.BaseUrl,
+ AutoSize = false,
+ X = 0,
+ Y = 1,
+ Width = Dim.Fill (),
+ Height = Dim.Fill (1),
+ TextAlignment = TextAlignment.Centered
+ };
+ var spinner = new SpinnerView () {
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Style = new SpinnerStyle.Aesthetic (),
+
+ };
+ spinner.AutoSpin ();
+ waitIndicator.Add (errorLabel);
+ waitIndicator.Add (spinner);
+ waitIndicator.Ready += async (s, a) => {
+ try {
+ decResponse = await client.GetCodepointDec ((int)SelectedCodePoint);
+ } catch (HttpRequestException e) {
+ (s as Dialog).Text = e.Message;
+ Application.MainLoop.Invoke (() => {
+ spinner.Visible = false;
+ errorLabel.Text = e.Message;
+ errorLabel.ColorScheme = Colors.ColorSchemes ["Error"];
+ errorLabel.Visible = true;
+ });
+ }
+ (s as Dialog)?.RequestStop ();
+ };
+ Application.Run (waitIndicator);
+
+ if (!string.IsNullOrEmpty (decResponse)) {
+ string 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 ());
+ //}
+ }
+
+ var title = $"{ToCamelCase (name)} - {new Rune ((char)SelectedCodePoint)} U+{SelectedCodePoint:x4}";
+ switch (MessageBox.Query (title, decResponse, "Copy _Glyph", "Copy Code _Point", "Cancel")) {
+ case 0:
+ CopyGlyph ();
+ break;
+ case 1:
+ CopyCodePoint ();
+ break;
+ }
+ } else {
+ MessageBox.ErrorQuery ("Code Point API", $"{UcdApiClient.BaseUrl} did not return a result.", "Ok");
+ }
+ // BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug
+ Application.GrabMouse (this);
+ }
+
+
+ public override bool OnEnter (View view)
+ {
+ if (IsInitialized) {
+ Application.Driver.SetCursorVisibility (CursorVisibility.Default);
+ }
+ return base.OnEnter (view);
+ }
+}
+
+public class UcdApiClient {
+ private static readonly HttpClient httpClient = new HttpClient ();
+ public const string BaseUrl = "https://ucdapi.org/unicode/latest/";
+
+ public async Task GetCodepointHex (string hex)
+ {
+ var response = await httpClient.GetAsync ($"{BaseUrl}codepoint/hex/{hex}");
+ response.EnsureSuccessStatusCode ();
+ return await response.Content.ReadAsStringAsync ();
+ }
+
+ public async Task GetCodepointDec (int dec)
+ {
+ var response = await httpClient.GetAsync ($"{BaseUrl}codepoint/dec/{dec}");
+ response.EnsureSuccessStatusCode ();
+ return await response.Content.ReadAsStringAsync ();
+ }
+
+ public async Task GetChars (string chars)
+ {
+ var response = await httpClient.GetAsync ($"{BaseUrl}chars/{Uri.EscapeDataString (chars)}");
+ response.EnsureSuccessStatusCode ();
+ return await response.Content.ReadAsStringAsync ();
+ }
+
+ public async Task GetCharsName (string chars)
+ {
+ var response = await httpClient.GetAsync ($"{BaseUrl}chars/{Uri.EscapeDataString (chars)}/name");
+ response.EnsureSuccessStatusCode ();
+ return await response.Content.ReadAsStringAsync ();
+ }
+}
+
+
+class UnicodeRange {
+ public int Start;
+ public int End;
+ public string Category;
+ public UnicodeRange (int start, int end, string category)
+ {
+ this.Start = start;
+ this.End = end;
+ this.Category = category;
+ }
+
+ public static List GetRanges ()
+ {
+ var ranges = (from r in typeof (UnicodeRanges).GetProperties (System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
+ let urange = r.GetValue (null) as System.Text.Unicode.UnicodeRange
+ let name = string.IsNullOrEmpty (r.Name) ? $"U+{urange.FirstCodePoint:x5}-U+{urange.FirstCodePoint + urange.Length:x5}" : r.Name
+ where name != "None" && name != "All"
+ select new UnicodeRange (urange.FirstCodePoint, urange.FirstCodePoint + urange.Length, name));
+
+ // .NET 8.0 only supports BMP in UnicodeRanges: https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.unicoderanges?view=net-8.0
+ var nonBmpRanges = new List {
+
+ new UnicodeRange (0x1F130, 0x1F149 ,"Squared Latin Capital Letters"),
+ new UnicodeRange (0x12400, 0x1240f ,"Cuneiform Numbers and Punctuation"),
+ new UnicodeRange (0x1FA00, 0x1FA0f ,"Chess Symbols"),
new UnicodeRange (0x10000, 0x1007F ,"Linear B Syllabary"),
new UnicodeRange (0x10080, 0x100FF ,"Linear B Ideograms"),
new UnicodeRange (0x10100, 0x1013F ,"Aegean Numbers"),
@@ -530,8 +650,10 @@ namespace UICatalog.Scenarios {
new UnicodeRange (0x20000, 0x2A6DF ,"CJK Unified Ideographs Extension B"),
new UnicodeRange (0x2F800, 0x2FA1F ,"CJK Compatibility Ideographs Supplement"),
new UnicodeRange (0xE0000, 0xE007F ,"Tags"),
- new UnicodeRange((uint)(CharMap.MaxCodePointVal - 16), (uint)CharMap.MaxCodePointVal,"End"),
};
+
+ return ranges.Concat (nonBmpRanges).OrderBy (r => r.Category).ToList ();
}
-}
+ public static List Ranges = GetRanges ();
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs
index 22037d970..9cf0c2f60 100644
--- a/UICatalog/Scenarios/TableEditor.cs
+++ b/UICatalog/Scenarios/TableEditor.cs
@@ -663,7 +663,7 @@ namespace UICatalog.Scenarios {
new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"),
new UnicodeRange(0x12400, 0x1240f,"Cuneiform Numbers and Punctuation"),
new UnicodeRange(0x1FA00, 0x1FA0f,"Chess Symbols"),
- new UnicodeRange((uint)(CharMap.MaxCodePointVal - 16), (uint)CharMap.MaxCodePointVal,"End"),
+ new UnicodeRange((uint)(CharMap.MaxCodePoint - 16), (uint)CharMap.MaxCodePoint,"End"),
new UnicodeRange (0x0020 ,0x007F ,"Basic Latin"),
new UnicodeRange (0x00A0 ,0x00FF ,"Latin-1 Supplement"),