From 66398eb9ef3ec98e792d511829de4280a2be1d5b Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Tue, 1 Nov 2022 09:12:37 -0600 Subject: [PATCH] Renamed classes; fixed rendering bug in ListView --- ...ionNavigator.cs => CollectionNavigator.cs} | 14 +++---- Terminal.Gui/Terminal.Gui.csproj | 2 +- Terminal.Gui/Views/ListView.cs | 39 ++++++++----------- Terminal.Gui/Views/TreeView.cs | 12 +++--- UICatalog/Properties/launchSettings.json | 2 +- UICatalog/Scenario.cs | 4 +- ...Tester.cs => CollectionNavigatorTester.cs} | 21 +++++----- UICatalog/Scenarios/ListViewWithSelection.cs | 2 +- ...orTests.cs => CollectionNavigatorTests.cs} | 26 ++++++------- 9 files changed, 59 insertions(+), 63 deletions(-) rename Terminal.Gui/Core/{SearchCollectionNavigator.cs => CollectionNavigator.cs} (94%) rename UICatalog/Scenarios/{SearchCollectionNavigatorTester.cs => CollectionNavigatorTester.cs} (87%) rename UnitTests/{SearchCollectionNavigatorTests.cs => CollectionNavigatorTests.cs} (94%) diff --git a/Terminal.Gui/Core/SearchCollectionNavigator.cs b/Terminal.Gui/Core/CollectionNavigator.cs similarity index 94% rename from Terminal.Gui/Core/SearchCollectionNavigator.cs rename to Terminal.Gui/Core/CollectionNavigator.cs index c87865b6e..cc3b1124f 100644 --- a/Terminal.Gui/Core/SearchCollectionNavigator.cs +++ b/Terminal.Gui/Core/CollectionNavigator.cs @@ -15,17 +15,17 @@ namespace Terminal.Gui { /// If the user pauses keystrokes for a short time (250ms), the search string is cleared. /// /// - public class SearchCollectionNavigator { + public class CollectionNavigator { /// - /// Constructs a new SearchCollectionNavigator. + /// Constructs a new CollectionNavigator. /// - public SearchCollectionNavigator () { } + public CollectionNavigator () { } /// - /// Constructs a new SearchCollectionNavigator for the given collection. + /// Constructs a new CollectionNavigator for the given collection. /// /// - public SearchCollectionNavigator (IEnumerable collection) => Collection = collection; + public CollectionNavigator (IEnumerable collection) => Collection = collection; DateTime lastKeystroke = DateTime.Now; internal int TypingDelay { get; set; } = 250; @@ -41,7 +41,7 @@ namespace Terminal.Gui { public IEnumerable Collection { get; set; } /// - /// Event arguments for the event. + /// Event arguments for the event. /// public class KeystrokeNavigatorEventArgs { /// @@ -162,7 +162,7 @@ namespace Terminal.Gui { /// Set to to stop the search on the first match /// if there are multiple matches for . /// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If (the default), - /// the next matching item will be returned, even if it is above in the collection. + /// the next matching item will be returned, even if it is above in the collection. /// /// The index of the next matching item or if no match was found. internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false) diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 39d2e1ff2..79d2e4121 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -23,7 +23,7 @@ - + $(RestoreSources);..\..\NStack\NStack\bin\Debug;https://api.nuget.org/v3/index.json diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 388def50a..957216837 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -110,7 +110,7 @@ namespace Terminal.Gui { get => source; set { source = value; - Navigator.Collection = source?.ToList ()?.Cast (); + KeystrokeNavigator.Collection = source?.ToList ()?.Cast (); top = 0; selected = 0; lastSelectedItem = -1; @@ -383,7 +383,7 @@ namespace Terminal.Gui { Driver.SetAttribute (current); } if (allowsMarking) { - Driver.AddRune (source.IsMarked (item) ? (AllowsMultipleSelection ? Driver.Checked : Driver.Selected) : + Driver.AddRune (source.IsMarked (item) ? (AllowsMultipleSelection ? Driver.Checked : Driver.Selected) : (AllowsMultipleSelection ? Driver.UnChecked : Driver.UnSelected)); Driver.AddRune (' '); } @@ -408,9 +408,10 @@ namespace Terminal.Gui { public event Action RowRender; /// - /// Gets the that is used to navigate the when searching. + /// Gets the that searches the collection as + /// the user types. /// - public SearchCollectionNavigator Navigator { get; private set; } = new SearchCollectionNavigator (); + public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator (); /// public override bool ProcessKey (KeyEvent kb) @@ -423,10 +424,10 @@ namespace Terminal.Gui { if (result != null) { return (bool)result; } - + // Enable user to find & select an item by typing text - if (SearchCollectionNavigator.IsCompatibleKey(kb)) { - var newItem = Navigator?.GetNextMatchingItem (SelectedItem, (char)kb.KeyValue); + if (CollectionNavigator.IsCompatibleKey (kb)) { + var newItem = KeystrokeNavigator?.GetNextMatchingItem (SelectedItem, (char)kb.KeyValue); if (newItem is int && newItem != -1) { SelectedItem = (int)newItem; EnsuresVisibilitySelectedItem (); @@ -840,13 +841,13 @@ namespace Terminal.Gui { if (src == null || src?.Count == 0) { return 0; } - + int maxLength = 0; for (int i = 0; i < src.Count; i++) { var t = src [i]; int l; if (t is ustring u) { - l = u.RuneCount; + l = TextFormatter.GetTextWidth (u); } else if (t is string s) { l = s.Length; } else { @@ -863,18 +864,10 @@ namespace Terminal.Gui { void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0) { - int byteLen = ustr.Length; - int used = 0; - for (int i = start; i < byteLen;) { - (var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen); - var count = Rune.ColumnWidth (rune); - if (used + count > width) - break; - driver.AddRune (rune); - used += count; - i += size; - } - for (; used < width; used++) { + var u = TextFormatter.ClipAndJustify (ustr, width, TextAlignment.Left); + driver.AddStr (u); + width -= TextFormatter.GetTextWidth (u); + while (width-- > 0) { driver.AddRune (' '); } } @@ -924,7 +917,7 @@ namespace Terminal.Gui { if (src == null || src?.Count == 0) { return -1; } - + for (int i = 0; i < src.Count; i++) { var t = src [i]; if (t is ustring u) { @@ -932,7 +925,7 @@ namespace Terminal.Gui { return i; } } else if (t is string s) { - if (s.ToUpperInvariant().StartsWith (search.ToUpperInvariant())) { + if (s.ToUpperInvariant ().StartsWith (search.ToUpperInvariant ())) { return i; } } diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs index 5ccf8b8c1..baab64642 100644 --- a/Terminal.Gui/Views/TreeView.cs +++ b/Terminal.Gui/Views/TreeView.cs @@ -547,7 +547,7 @@ namespace Terminal.Gui { cachedLineMap = new ReadOnlyCollection> (toReturn); // Update the collection used for search-typing - Navigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray (); + KeystrokeNavigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray (); return cachedLineMap; } @@ -565,10 +565,10 @@ namespace Terminal.Gui { } /// - /// Gets the that is used to navigate the - /// when searching with the keyboard. + /// Gets the that searches the collection as + /// the user types. /// - public SearchCollectionNavigator Navigator { get; private set; } = new SearchCollectionNavigator (); + public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator (); /// public override bool ProcessKey (KeyEvent keyEvent) @@ -585,7 +585,7 @@ namespace Terminal.Gui { } // If not a keybinding, is the key a searchable key press? - if (SearchCollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) { + if (CollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) { IReadOnlyCollection> map; // If there has been a call to InvalidateMap since the last time @@ -594,7 +594,7 @@ namespace Terminal.Gui { // Find the current selected object within the tree var current = map.IndexOf (b => b.Model == SelectedObject); - var newIndex = Navigator?.GetNextMatchingItem (current, (char)keyEvent.KeyValue); + var newIndex = KeystrokeNavigator?.GetNextMatchingItem (current, (char)keyEvent.KeyValue); if (newIndex is int && newIndex != -1) { SelectedObject = map.ElementAt ((int)newIndex).Model; diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index e1f2b1db2..ec419ec20 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -45,7 +45,7 @@ "commandName": "WSL2", "distributionName": "" }, - "SearchCollectionNavigatorTester": { + "CollectionNavigatorTester": { "commandName": "Project", "commandLineArgs": "\"Search Collection Nav\"" } diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs index c747829e3..30767e190 100644 --- a/UICatalog/Scenario.cs +++ b/UICatalog/Scenario.cs @@ -233,7 +233,7 @@ namespace UICatalog { } /// - /// Returns an instance of each defined in the project. + /// Returns a list of all instanaces defined in the project, sorted by . /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class /// public static List GetScenarios () @@ -245,7 +245,7 @@ namespace UICatalog { objects.Add (scenario); _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1); } - return objects; + return objects.OrderBy (s => s.GetName ()).ToList (); } protected virtual void Dispose (bool disposing) diff --git a/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs b/UICatalog/Scenarios/CollectionNavigatorTester.cs similarity index 87% rename from UICatalog/Scenarios/SearchCollectionNavigatorTester.cs rename to UICatalog/Scenarios/CollectionNavigatorTester.cs index 74dde8aef..d97f6890c 100644 --- a/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs +++ b/UICatalog/Scenarios/CollectionNavigatorTester.cs @@ -6,13 +6,13 @@ using Terminal.Gui.Trees; namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "Search Collection Nav", Description: "Demonstrates & tests SearchCollectionNavigator.")] - [ScenarioCategory ("Controls"), - ScenarioCategory ("ListView"), - ScenarioCategory ("TreeView"), + [ScenarioMetadata (Name: "Collection Navigator", Description: "Demonstrates keyboard navigation in ListView & TreeView (CollectionNavigator).")] + [ScenarioCategory ("Controls"), + ScenarioCategory ("ListView"), + ScenarioCategory ("TreeView"), ScenarioCategory ("Text and Formatting"), ScenarioCategory ("Mouse and Keyboard")] - public class SearchCollectionNavigatorTester : Scenario { + public class CollectionNavigatorTester : Scenario { // Don't create a Window, just return the top-level view public override void Init (Toplevel top, ColorScheme colorScheme) @@ -70,6 +70,9 @@ namespace UICatalog.Scenarios { "egg", "candle", " <- space", + "\t<- tab", + "\n<- newline", + "\r<- formfeed", "q", "quit", "quitter" @@ -141,7 +144,7 @@ namespace UICatalog.Scenarios { _listView.SetSource (_items); - _listView.Navigator.SearchStringChanged += (state) => { + _listView.KeystrokeNavigator.SearchStringChanged += (state) => { label.Text = $"ListView: {state.SearchString}"; }; } @@ -169,16 +172,16 @@ namespace UICatalog.Scenarios { }; Top.Add (_treeView); - var root = new TreeNode ("Alpha examples"); + var root = new TreeNode ("IsLetterOrDigit examples"); root.Children = _items.Where (i => char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast ().ToList (); _treeView.AddObject (root); - root = new TreeNode ("Non-Alpha examples"); + root = new TreeNode ("Non-IsLetterOrDigit examples"); root.Children = _items.Where (i => !char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast ().ToList (); _treeView.AddObject (root); _treeView.ExpandAll (); _treeView.GoToFirst (); - _treeView.Navigator.SearchStringChanged += (state) => { + _treeView.KeystrokeNavigator.SearchStringChanged += (state) => { label.Text = $"TreeView: {state.SearchString}"; }; } diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index bd1afc40b..c132cf8f5 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -21,7 +21,7 @@ namespace UICatalog.Scenarios { public override void Setup () { - _scenarios = Scenario.GetScenarios ().OrderBy (s => s.GetName ()).ToList (); + _scenarios = Scenario.GetScenarios (); _customRenderCB = new CheckBox ("Use custom rendering") { X = 0, diff --git a/UnitTests/SearchCollectionNavigatorTests.cs b/UnitTests/CollectionNavigatorTests.cs similarity index 94% rename from UnitTests/SearchCollectionNavigatorTests.cs rename to UnitTests/CollectionNavigatorTests.cs index b7e0a4df8..06ebc0000 100644 --- a/UnitTests/SearchCollectionNavigatorTests.cs +++ b/UnitTests/CollectionNavigatorTests.cs @@ -2,7 +2,7 @@ using Xunit; namespace Terminal.Gui.Core { - public class SearchCollectionNavigatorTests { + public class CollectionNavigatorTests { static string [] simpleStrings = new string []{ "appricot", // 0 "arm", // 1 @@ -14,7 +14,7 @@ namespace Terminal.Gui.Core { [Fact] public void ShouldAcceptNegativeOne () { - var n = new SearchCollectionNavigator (simpleStrings); + var n = new CollectionNavigator (simpleStrings); // Expect that index of -1 (i.e. no selection) should work correctly // and select the first entry of the letter 'b' @@ -23,7 +23,7 @@ namespace Terminal.Gui.Core { [Fact] public void OutOfBoundsShouldBeIgnored () { - var n = new SearchCollectionNavigator (simpleStrings); + var n = new CollectionNavigator (simpleStrings); // Expect saying that index 500 is the current selection should not cause // error and just be ignored (treated as no selection) @@ -33,7 +33,7 @@ namespace Terminal.Gui.Core { [Fact] public void Cycling () { - var n = new SearchCollectionNavigator (simpleStrings); + var n = new CollectionNavigator (simpleStrings); Assert.Equal (2, n.GetNextMatchingItem (0, 'b')); Assert.Equal (3, n.GetNextMatchingItem (2, 'b')); @@ -55,7 +55,7 @@ namespace Terminal.Gui.Core { }; int current = 0; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (2, current = n.GetNextMatchingItem (current, 'b')); // match bat Assert.Equal (4, current = n.GetNextMatchingItem (current, 'b')); // match bbfish @@ -77,7 +77,7 @@ namespace Terminal.Gui.Core { "candle" }; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (2, n.GetNextMatchingItem (0, 't')); // should match "te" in "text" @@ -104,7 +104,7 @@ namespace Terminal.Gui.Core { "candle" }; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (3, n.GetNextMatchingItem (0, '丗')); // 丗丙业丞 is as good a match as 丗丙丛 @@ -135,7 +135,7 @@ namespace Terminal.Gui.Core { "candle" }; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (3, n.GetNextMatchingItem (0, '@')); Assert.Equal (3, n.GetNextMatchingItem (3, 'b')); Assert.Equal (4, n.GetNextMatchingItem (3, 'b')); @@ -153,7 +153,7 @@ namespace Terminal.Gui.Core { "candle" }; int current = 0; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'b')); // match bat Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'a')); // match bat Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 't')); // match bat @@ -178,7 +178,7 @@ namespace Terminal.Gui.Core { "appricot" }; int current = 0; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a')); Assert.Equal ("a", n.SearchString); @@ -221,7 +221,7 @@ namespace Terminal.Gui.Core { "appricot" }; int current = 0; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); // No delay Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a')); @@ -271,7 +271,7 @@ namespace Terminal.Gui.Core { "cart", }; int current = 0; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", false)); Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", false)); Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", false)); // back to top @@ -317,7 +317,7 @@ namespace Terminal.Gui.Core { "cart", }; int current = 0; - var n = new SearchCollectionNavigator (strings); + var n = new CollectionNavigator (strings); Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", true)); Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$", true)); Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", true)); // back to top