Renamed classes; fixed rendering bug in ListView

This commit is contained in:
Charlie Kindel
2022-11-01 09:12:37 -06:00
parent e94cd4bc85
commit 66398eb9ef
9 changed files with 59 additions and 63 deletions

View File

@@ -15,17 +15,17 @@ namespace Terminal.Gui {
/// If the user pauses keystrokes for a short time (250ms), the search string is cleared.
/// </para>
/// </summary>
public class SearchCollectionNavigator {
public class CollectionNavigator {
/// <summary>
/// Constructs a new SearchCollectionNavigator.
/// Constructs a new CollectionNavigator.
/// </summary>
public SearchCollectionNavigator () { }
public CollectionNavigator () { }
/// <summary>
/// Constructs a new SearchCollectionNavigator for the given collection.
/// Constructs a new CollectionNavigator for the given collection.
/// </summary>
/// <param name="collection"></param>
public SearchCollectionNavigator (IEnumerable<object> collection) => Collection = collection;
public CollectionNavigator (IEnumerable<object> collection) => Collection = collection;
DateTime lastKeystroke = DateTime.Now;
internal int TypingDelay { get; set; } = 250;
@@ -41,7 +41,7 @@ namespace Terminal.Gui {
public IEnumerable<object> Collection { get; set; }
/// <summary>
/// Event arguments for the <see cref="SearchCollectionNavigator.SearchStringChanged"/> event.
/// Event arguments for the <see cref="CollectionNavigator.SearchStringChanged"/> event.
/// </summary>
public class KeystrokeNavigatorEventArgs {
/// <summary>
@@ -162,7 +162,7 @@ namespace Terminal.Gui {
/// <param name="minimizeMovement">Set to <see langword="true"/> to stop the search on the first match
/// if there are multiple matches for <paramref name="search"/>.
/// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If <see langword="false"/> (the default),
/// the next matching item will be returned, even if it is above in the collection.</param>
/// the next matching item will be returned, even if it is above in the collection.
/// </param>
/// <returns>The index of the next matching item or <see langword="-1"/> if no match was found.</returns>
internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)

View File

@@ -23,7 +23,7 @@
<!-- Uncomment the RestoreSources element to have dotnet restore pull NStack from a local dir for testing -->
<PropertyGroup>
<!-- See https://stackoverflow.com/a/44463578/297526 -->
<!--<RestoreSources>$(RestoreSources);../../local-packages;https://api.nuget.org/v3/index.json</RestoreSources>-->
<RestoreSources>$(RestoreSources);..\..\NStack\NStack\bin\Debug;https://api.nuget.org/v3/index.json</RestoreSources>
</PropertyGroup>
<!-- API Documentation -->
<ItemGroup>

View File

@@ -110,7 +110,7 @@ namespace Terminal.Gui {
get => source;
set {
source = value;
Navigator.Collection = source?.ToList ()?.Cast<object> ();
KeystrokeNavigator.Collection = source?.ToList ()?.Cast<object> ();
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<ListViewRowEventArgs> RowRender;
/// <summary>
/// Gets the <see cref="SearchCollectionNavigator"/> that is used to navigate the <see cref="ListView"/> when searching.
/// Gets the <see cref="CollectionNavigator"/> that searches the <see cref="ListView.Source"/> collection as
/// the user types.
/// </summary>
public SearchCollectionNavigator Navigator { get; private set; } = new SearchCollectionNavigator ();
public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
///<inheritdoc/>
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;
}
}

View File

@@ -547,7 +547,7 @@ namespace Terminal.Gui {
cachedLineMap = new ReadOnlyCollection<Branch<T>> (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 {
}
/// <summary>
/// Gets the <see cref="SearchCollectionNavigator"/> that is used to navigate the <see cref="TreeView"/>
/// when searching with the keyboard.
/// Gets the <see cref="CollectionNavigator"/> that searches the <see cref="Objects"/> collection as
/// the user types.
/// </summary>
public SearchCollectionNavigator Navigator { get; private set; } = new SearchCollectionNavigator ();
public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
/// <inheritdoc/>
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<Branch<T>> 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;

View File

@@ -45,7 +45,7 @@
"commandName": "WSL2",
"distributionName": ""
},
"SearchCollectionNavigatorTester": {
"CollectionNavigatorTester": {
"commandName": "Project",
"commandLineArgs": "\"Search Collection Nav\""
}

View File

@@ -233,7 +233,7 @@ namespace UICatalog {
}
/// <summary>
/// Returns an instance of each <see cref="Scenario"/> defined in the project.
/// Returns a list of all <see cref="Scenario"/> instanaces defined in the project, sorted by <see cref="ScenarioMetadata.Name"/>.
/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
/// </summary>
public static List<Scenario> 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)

View File

@@ -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<ITreeNode> ().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<ITreeNode> ().ToList ();
_treeView.AddObject (root);
_treeView.ExpandAll ();
_treeView.GoToFirst ();
_treeView.Navigator.SearchStringChanged += (state) => {
_treeView.KeystrokeNavigator.SearchStringChanged += (state) => {
label.Text = $"TreeView: {state.SearchString}";
};
}

View File

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

View File

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