Merge branch 'v2_develop' into v2_4274_v2win-window-size-vsdebugconsole-fix

This commit is contained in:
Tig
2025-10-11 10:37:16 -06:00
committed by GitHub
4 changed files with 150 additions and 3 deletions

View File

@@ -92,7 +92,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (SuperView is { })
{
// If we are TabStop, and we have at least one other focusable peer, move to the SuperView's chain
if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetFocusChain (direction, behavior).Length > 1)
if (TabStop == TabBehavior.TabStop && SuperView is { } && ShouldBubbleUpForWrapping (SuperView, direction, behavior))
{
return false;
}
@@ -171,6 +171,45 @@ public partial class View // Focus and cross-view navigation management (TabStop
}
}
/// <summary>
/// Determines if focus should bubble up to a SuperView when wrapping would occur.
/// Iteratively checks up the SuperView hierarchy to see if there are any focusable peers at any level.
/// </summary>
/// <param name="view">The SuperView to check.</param>
/// <param name="direction">The navigation direction.</param>
/// <param name="behavior">The tab behavior to filter by.</param>
/// <returns>
/// <see langword="true"/> if there are focusable peers at this level or any ancestor level,
/// <see langword="false"/> otherwise.
/// </returns>
private bool ShouldBubbleUpForWrapping (View? view, NavigationDirection direction, TabBehavior? behavior)
{
View? currentView = view;
while (currentView is { })
{
// If this parent has multiple focusable children, we should bubble up
View [] chain = currentView.GetFocusChain (direction, behavior);
if (chain.Length > 1)
{
return true;
}
// If parent has only 1 child but parent is also TabStop with a SuperView, continue checking up the hierarchy
if (currentView.TabStop == TabBehavior.TabStop && currentView.SuperView is { })
{
currentView = currentView.SuperView;
}
else
{
break;
}
}
return false;
}
private bool RaiseAdvancingFocus (NavigationDirection direction, TabBehavior? behavior)
{
// Call the virtual method

View File

@@ -43,7 +43,7 @@ namespace Terminal.Gui.Views;
public class ListView : View, IDesignable
{
private bool _allowsMarking;
private bool _allowsMultipleSelection = true;
private bool _allowsMultipleSelection = false;
private int _lastSelectedItem = -1;
private int _selected = -1;
private IListDataSource _source;

View File

@@ -16,6 +16,7 @@ public class ListViewTests (ITestOutputHelper output)
Assert.Null (lv.Source);
Assert.True (lv.CanFocus);
Assert.Equal (-1, lv.SelectedItem);
Assert.False (lv.AllowsMultipleSelection);
lv = new () { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
Assert.NotNull (lv.Source);
@@ -37,6 +38,7 @@ public class ListViewTests (ITestOutputHelper output)
Assert.NotNull (lv.Source);
Assert.Equal (-1, lv.SelectedItem);
Assert.Equal (new (0, 1, 10, 20), lv.Frame);
}
[Fact]
@@ -524,10 +526,72 @@ Item 6",
}
[Fact]
public void AllowsMarking_True_SpaceWithShift_SelectsThenDown ()
public void AllowsMarking_True_SpaceWithShift_SelectsThenDown_SingleSelection ()
{
var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
lv.AllowsMarking = true;
lv.AllowsMultipleSelection = false;
Assert.NotNull (lv.Source);
// first item should be deselected by default
Assert.Equal (-1, lv.SelectedItem);
// nothing is ticked
Assert.False (lv.Source.IsMarked (0));
Assert.False (lv.Source.IsMarked (1));
Assert.False (lv.Source.IsMarked (2));
// view should indicate that it has accepted and consumed the event
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
// first item should now be selected
Assert.Equal (0, lv.SelectedItem);
// none of the items should be ticked
Assert.False (lv.Source.IsMarked (0));
Assert.False (lv.Source.IsMarked (1));
Assert.False (lv.Source.IsMarked (2));
// Press key combo again
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
// second item should now be selected
Assert.Equal (1, lv.SelectedItem);
// first item only should be ticked
Assert.True (lv.Source.IsMarked (0));
Assert.False (lv.Source.IsMarked (1));
Assert.False (lv.Source.IsMarked (2));
// Press key combo again
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
Assert.Equal (2, lv.SelectedItem);
Assert.False (lv.Source.IsMarked (0));
Assert.True (lv.Source.IsMarked (1));
Assert.False (lv.Source.IsMarked (2));
// Press key combo again
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
Assert.Equal (2, lv.SelectedItem); // cannot move down any further
Assert.False (lv.Source.IsMarked (0));
Assert.False (lv.Source.IsMarked (1));
Assert.True (lv.Source.IsMarked (2)); // but can toggle marked
// Press key combo again
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
Assert.Equal (2, lv.SelectedItem); // cannot move down any further
Assert.False (lv.Source.IsMarked (0));
Assert.False (lv.Source.IsMarked (1));
Assert.False (lv.Source.IsMarked (2)); // untoggle toggle marked
}
[Fact]
public void AllowsMarking_True_SpaceWithShift_SelectsThenDown_MultipleSelection ()
{
var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
lv.AllowsMarking = true;
lv.AllowsMultipleSelection = true;
Assert.NotNull (lv.Source);

View File

@@ -644,4 +644,48 @@ public class AdvanceFocusTests ()
Assert.Equal (canFocus, view.CanFocus);
Assert.Equal (tabStop, view.TabStop);
}
[Fact]
public void AdvanceFocus_Cycles_Through_Peers_And_All_Nested_SubViews_When_Multiple ()
{
var top = new View { Id = "top", CanFocus = true };
View peer1 = new View
{
CanFocus = true,
Id = "peer1",
};
var peer2 = new View
{
CanFocus = true,
Id = "peer2",
};
var peer2SubView = new View
{
Id = "peer2SubView", CanFocus = true
};
var v1 = new View { Id = "v1", CanFocus = true };
var v2 = new View { Id = "v2", CanFocus = true };
peer2SubView.Add (v1, v2);
peer2.Add (peer2SubView);
top.Add (peer1, peer2);
top.SetFocus ();
Assert.Equal (peer1, top.MostFocused);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.Equal (v1, top.MostFocused);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.Equal (v2, top.MostFocused);
// This should cycle to peer1 - previously it incorrectly cycled to v1
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.Equal (peer1, top.MostFocused);
top.Dispose ();
}
}