mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Renamed classes; fixed rendering bug in ListView
This commit is contained in:
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"commandName": "WSL2",
|
||||
"distributionName": ""
|
||||
},
|
||||
"SearchCollectionNavigatorTester": {
|
||||
"CollectionNavigatorTester": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"Search Collection Nav\""
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}";
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user