Skip to content

Commit 0954330

Browse files
authored
Merge branch 'v2_develop' into v2_4274_v2win-window-size-vsdebugconsole-fix
2 parents 30b798b + c814741 commit 0954330

File tree

4 files changed

+150
-3
lines changed

4 files changed

+150
-3
lines changed

Terminal.Gui/ViewBase/View.Navigation.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
9292
if (SuperView is { })
9393
{
9494
// If we are TabStop, and we have at least one other focusable peer, move to the SuperView's chain
95-
if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetFocusChain (direction, behavior).Length > 1)
95+
if (TabStop == TabBehavior.TabStop && SuperView is { } && ShouldBubbleUpForWrapping (SuperView, direction, behavior))
9696
{
9797
return false;
9898
}
@@ -171,6 +171,45 @@ bool AdvanceFocusChain ()
171171
}
172172
}
173173

174+
/// <summary>
175+
/// Determines if focus should bubble up to a SuperView when wrapping would occur.
176+
/// Iteratively checks up the SuperView hierarchy to see if there are any focusable peers at any level.
177+
/// </summary>
178+
/// <param name="view">The SuperView to check.</param>
179+
/// <param name="direction">The navigation direction.</param>
180+
/// <param name="behavior">The tab behavior to filter by.</param>
181+
/// <returns>
182+
/// <see langword="true"/> if there are focusable peers at this level or any ancestor level,
183+
/// <see langword="false"/> otherwise.
184+
/// </returns>
185+
private bool ShouldBubbleUpForWrapping (View? view, NavigationDirection direction, TabBehavior? behavior)
186+
{
187+
View? currentView = view;
188+
189+
while (currentView is { })
190+
{
191+
// If this parent has multiple focusable children, we should bubble up
192+
View [] chain = currentView.GetFocusChain (direction, behavior);
193+
194+
if (chain.Length > 1)
195+
{
196+
return true;
197+
}
198+
199+
// If parent has only 1 child but parent is also TabStop with a SuperView, continue checking up the hierarchy
200+
if (currentView.TabStop == TabBehavior.TabStop && currentView.SuperView is { })
201+
{
202+
currentView = currentView.SuperView;
203+
}
204+
else
205+
{
206+
break;
207+
}
208+
}
209+
210+
return false;
211+
}
212+
174213
private bool RaiseAdvancingFocus (NavigationDirection direction, TabBehavior? behavior)
175214
{
176215
// Call the virtual method

Terminal.Gui/Views/ListView.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ namespace Terminal.Gui.Views;
4343
public class ListView : View, IDesignable
4444
{
4545
private bool _allowsMarking;
46-
private bool _allowsMultipleSelection = true;
46+
private bool _allowsMultipleSelection = false;
4747
private int _lastSelectedItem = -1;
4848
private int _selected = -1;
4949
private IListDataSource _source;

Tests/UnitTests/Views/ListViewTests.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public void Constructors_Defaults ()
1616
Assert.Null (lv.Source);
1717
Assert.True (lv.CanFocus);
1818
Assert.Equal (-1, lv.SelectedItem);
19+
Assert.False (lv.AllowsMultipleSelection);
1920

2021
lv = new () { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
2122
Assert.NotNull (lv.Source);
@@ -37,6 +38,7 @@ public void Constructors_Defaults ()
3738
Assert.NotNull (lv.Source);
3839
Assert.Equal (-1, lv.SelectedItem);
3940
Assert.Equal (new (0, 1, 10, 20), lv.Frame);
41+
4042
}
4143

4244
[Fact]
@@ -524,10 +526,72 @@ public void ListViewProcessKeyReturnValue_WithMultipleCommands ()
524526
}
525527

526528
[Fact]
527-
public void AllowsMarking_True_SpaceWithShift_SelectsThenDown ()
529+
public void AllowsMarking_True_SpaceWithShift_SelectsThenDown_SingleSelection ()
530+
{
531+
var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
532+
lv.AllowsMarking = true;
533+
lv.AllowsMultipleSelection = false;
534+
535+
Assert.NotNull (lv.Source);
536+
537+
// first item should be deselected by default
538+
Assert.Equal (-1, lv.SelectedItem);
539+
540+
// nothing is ticked
541+
Assert.False (lv.Source.IsMarked (0));
542+
Assert.False (lv.Source.IsMarked (1));
543+
Assert.False (lv.Source.IsMarked (2));
544+
545+
// view should indicate that it has accepted and consumed the event
546+
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
547+
548+
// first item should now be selected
549+
Assert.Equal (0, lv.SelectedItem);
550+
551+
// none of the items should be ticked
552+
Assert.False (lv.Source.IsMarked (0));
553+
Assert.False (lv.Source.IsMarked (1));
554+
Assert.False (lv.Source.IsMarked (2));
555+
556+
// Press key combo again
557+
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
558+
559+
// second item should now be selected
560+
Assert.Equal (1, lv.SelectedItem);
561+
562+
// first item only should be ticked
563+
Assert.True (lv.Source.IsMarked (0));
564+
Assert.False (lv.Source.IsMarked (1));
565+
Assert.False (lv.Source.IsMarked (2));
566+
567+
// Press key combo again
568+
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
569+
Assert.Equal (2, lv.SelectedItem);
570+
Assert.False (lv.Source.IsMarked (0));
571+
Assert.True (lv.Source.IsMarked (1));
572+
Assert.False (lv.Source.IsMarked (2));
573+
574+
// Press key combo again
575+
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
576+
Assert.Equal (2, lv.SelectedItem); // cannot move down any further
577+
Assert.False (lv.Source.IsMarked (0));
578+
Assert.False (lv.Source.IsMarked (1));
579+
Assert.True (lv.Source.IsMarked (2)); // but can toggle marked
580+
581+
// Press key combo again
582+
Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift));
583+
Assert.Equal (2, lv.SelectedItem); // cannot move down any further
584+
Assert.False (lv.Source.IsMarked (0));
585+
Assert.False (lv.Source.IsMarked (1));
586+
Assert.False (lv.Source.IsMarked (2)); // untoggle toggle marked
587+
}
588+
589+
[Fact]
590+
public void AllowsMarking_True_SpaceWithShift_SelectsThenDown_MultipleSelection ()
528591
{
529592
var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
530593
lv.AllowsMarking = true;
594+
lv.AllowsMultipleSelection = true;
531595

532596
Assert.NotNull (lv.Source);
533597

Tests/UnitTestsParallelizable/View/Navigation/AdvanceFocusTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,4 +644,48 @@ public void TabStop_And_CanFocus_Are_Decoupled (bool canFocus, TabBehavior tabSt
644644
Assert.Equal (canFocus, view.CanFocus);
645645
Assert.Equal (tabStop, view.TabStop);
646646
}
647+
648+
[Fact]
649+
public void AdvanceFocus_Cycles_Through_Peers_And_All_Nested_SubViews_When_Multiple ()
650+
{
651+
var top = new View { Id = "top", CanFocus = true };
652+
653+
View peer1 = new View
654+
{
655+
CanFocus = true,
656+
Id = "peer1",
657+
};
658+
659+
var peer2 = new View
660+
{
661+
CanFocus = true,
662+
Id = "peer2",
663+
};
664+
var peer2SubView = new View
665+
{
666+
Id = "peer2SubView", CanFocus = true
667+
};
668+
var v1 = new View { Id = "v1", CanFocus = true };
669+
var v2 = new View { Id = "v2", CanFocus = true };
670+
peer2SubView.Add (v1, v2);
671+
672+
peer2.Add (peer2SubView);
673+
674+
top.Add (peer1, peer2);
675+
top.SetFocus ();
676+
677+
Assert.Equal (peer1, top.MostFocused);
678+
679+
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
680+
Assert.Equal (v1, top.MostFocused);
681+
682+
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
683+
Assert.Equal (v2, top.MostFocused);
684+
685+
// This should cycle to peer1 - previously it incorrectly cycled to v1
686+
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
687+
Assert.Equal (peer1, top.MostFocused);
688+
689+
top.Dispose ();
690+
}
647691
}

0 commit comments

Comments
 (0)