Merge branch 'view-tab-index-feature' into listview-reseting

This commit is contained in:
BDisp
2020-07-06 17:31:09 +01:00
2 changed files with 437 additions and 24 deletions

View File

@@ -190,6 +190,63 @@ namespace Terminal.Gui {
// to make the same mistakes our users make when they poke at the Subviews.
internal IList<View> InternalSubviews => subviews ?? empty;
// This is null, and allocated on demand.
List<View> tabIndexes;
/// <summary>
/// This returns a tab index list of the subviews contained by this view.
/// </summary>
/// <value>The tabIndexes.</value>
public IList<View> TabIndexes => tabIndexes == null ? empty : tabIndexes.AsReadOnly ();
int tabIndex = -1;
/// <summary>
/// Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list.
/// </summary>
public int TabIndex {
get { return tabIndex; }
set {
if (!CanFocus || SuperView?.tabIndexes == null || SuperView?.tabIndexes.Count == 1 || tabIndex == value) {
return;
}
tabIndex = value > SuperView.tabIndexes.Count - 1 ? SuperView.tabIndexes.Count - 1 : value < 0 ? 0 : value;
SuperView.tabIndexes.Remove (this);
SuperView.tabIndexes.Insert (tabIndex, this);
}
}
bool tabStop = true;
/// <summary>
/// This only be <c>true</c> if the <see cref="CanFocus"/> is also <c>true</c> and the focus can be avoided by setting this to <c>false</c>
/// </summary>
public bool TabStop {
get { return tabStop; }
set {
if (tabStop == value) {
return;
}
tabStop = CanFocus && value;
}
}
/// <inheritdoc/>
public override bool CanFocus {
get => base.CanFocus;
set {
if (base.CanFocus != value) {
base.CanFocus = value;
if (!value && tabIndex > -1) {
tabIndex = -1;
}
if (!value && tabStop) {
tabStop = false;
}
}
}
}
internal Rect NeedDisplay { get; private set; } = Rect.Empty;
// The frame for the object. Superview relative.
@@ -548,12 +605,20 @@ namespace Terminal.Gui {
{
if (view == null)
return;
if (subviews == null)
if (subviews == null) {
subviews = new List<View> ();
}
if (tabIndexes == null) {
tabIndexes = new List<View> ();
}
subviews.Add (view);
tabIndexes.Add (view);
view.container = this;
if (view.CanFocus)
if (view.CanFocus) {
CanFocus = true;
view.tabIndex = tabIndexes.IndexOf (view);
}
SetNeedsLayout ();
SetNeedsDisplay ();
}
@@ -583,6 +648,7 @@ namespace Terminal.Gui {
while (subviews.Count > 0) {
Remove (subviews [0]);
Remove (tabIndexes [0]);
}
}
@@ -600,8 +666,9 @@ namespace Terminal.Gui {
SetNeedsDisplay ();
var touched = view.Frame;
subviews.Remove (view);
tabIndexes.Remove (view);
view.container = null;
view.tabIndex = -1;
if (subviews.Count < 1)
this.CanFocus = false;
@@ -917,8 +984,8 @@ namespace Terminal.Gui {
// Remove focus down the chain of subviews if focus is removed
if (!value && focused != null) {
focused.OnLeave (focused);
focused.hasFocus = false;
focused.OnLeave (view);
focused.SetHasFocus (false, view);
focused = null;
}
}
@@ -1135,8 +1202,9 @@ namespace Terminal.Gui {
if (focused != null)
focused.SetHasFocus (false, view);
var f = focused;
focused = view;
focused.SetHasFocus (true, view);
focused.SetHasFocus (true, f);
focused.EnsureFocus ();
// Send focus upwards
@@ -1270,13 +1338,13 @@ namespace Terminal.Gui {
/// </summary>
public void FocusFirst ()
{
if (subviews == null) {
if (tabIndexes == null) {
SuperView?.SetFocus (this);
return;
}
foreach (var view in subviews) {
if (view.CanFocus) {
foreach (var view in tabIndexes) {
if (view.CanFocus && view.tabStop) {
SetFocus (view);
return;
}
@@ -1288,16 +1356,16 @@ namespace Terminal.Gui {
/// </summary>
public void FocusLast ()
{
if (subviews == null) {
if (tabIndexes == null) {
SuperView?.SetFocus (this);
return;
}
for (int i = subviews.Count; i > 0;) {
for (int i = tabIndexes.Count; i > 0;) {
i--;
View v = subviews [i];
if (v.CanFocus) {
View v = tabIndexes [i];
if (v.CanFocus && v.tabStop) {
SetFocus (v);
return;
}
@@ -1311,7 +1379,7 @@ namespace Terminal.Gui {
public bool FocusPrev ()
{
FocusDirection = Direction.Backward;
if (subviews == null || subviews.Count == 0)
if (tabIndexes == null || tabIndexes.Count == 0)
return false;
if (focused == null) {
@@ -1319,9 +1387,9 @@ namespace Terminal.Gui {
return focused != null;
}
int focused_idx = -1;
for (int i = subviews.Count; i > 0;) {
for (int i = tabIndexes.Count; i > 0;) {
i--;
View w = subviews [i];
View w = tabIndexes [i];
if (w.HasFocus) {
if (w.FocusPrev ())
@@ -1329,10 +1397,10 @@ namespace Terminal.Gui {
focused_idx = i;
continue;
}
if (w.CanFocus && focused_idx != -1) {
if (w.CanFocus && focused_idx != -1 && w.tabStop) {
focused.SetHasFocus (false, w);
if (w != null && w.CanFocus)
if (w != null && w.CanFocus && w.tabStop)
w.FocusLast ();
SetFocus (w);
@@ -1340,7 +1408,7 @@ namespace Terminal.Gui {
}
}
if (focused != null) {
focused.SetHasFocus (false, focused);
focused.SetHasFocus (false, this);
focused = null;
}
return false;
@@ -1353,17 +1421,17 @@ namespace Terminal.Gui {
public bool FocusNext ()
{
FocusDirection = Direction.Forward;
if (subviews == null || subviews.Count == 0)
if (tabIndexes == null || tabIndexes.Count == 0)
return false;
if (focused == null) {
FocusFirst ();
return focused != null;
}
int n = subviews.Count;
int n = tabIndexes.Count;
int focused_idx = -1;
for (int i = 0; i < n; i++) {
View w = subviews [i];
View w = tabIndexes [i];
if (w.HasFocus) {
if (w.FocusNext ())
@@ -1371,10 +1439,10 @@ namespace Terminal.Gui {
focused_idx = i;
continue;
}
if (w.CanFocus && focused_idx != -1) {
if (w.CanFocus && focused_idx != -1 && w.tabStop) {
focused.SetHasFocus (false, w);
if (w != null && w.CanFocus)
if (w != null && w.CanFocus && w.tabStop)
w.FocusFirst ();
SetFocus (w);

View File

@@ -135,5 +135,350 @@ namespace Terminal.Gui {
sub2.Width = Dim.Width (sub2);
Assert.Throws<InvalidOperationException> (() => root.LayoutSubviews ());
}
[Fact]
public void Subviews_TabIndexes_AreEqual ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.Subviews.IndexOf (v2) == 1);
Assert.True (r.Subviews.IndexOf (v3) == 2);
Assert.True (r.TabIndexes.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v2) == 1);
Assert.True (r.TabIndexes.IndexOf (v3) == 2);
Assert.Equal (r.Subviews.IndexOf (v1), r.TabIndexes.IndexOf (v1));
Assert.Equal (r.Subviews.IndexOf (v2), r.TabIndexes.IndexOf (v2));
Assert.Equal (r.Subviews.IndexOf (v3), r.TabIndexes.IndexOf (v3));
}
[Fact]
public void BringSubviewToFront_Subviews_vs_TabIndexes ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
r.BringSubviewToFront (v1);
Assert.True (r.Subviews.IndexOf (v1) == 2);
Assert.True (r.Subviews.IndexOf (v2) == 0);
Assert.True (r.Subviews.IndexOf (v3) == 1);
Assert.True (r.TabIndexes.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v2) == 1);
Assert.True (r.TabIndexes.IndexOf (v3) == 2);
}
[Fact]
public void BringSubviewForward_Subviews_vs_TabIndexes ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
r.BringSubviewForward (v1);
Assert.True (r.Subviews.IndexOf (v1) == 1);
Assert.True (r.Subviews.IndexOf (v2) == 0);
Assert.True (r.Subviews.IndexOf (v3) == 2);
Assert.True (r.TabIndexes.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v2) == 1);
Assert.True (r.TabIndexes.IndexOf (v3) == 2);
}
[Fact]
public void SendSubviewToBack_Subviews_vs_TabIndexes ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
r.SendSubviewToBack (v3);
Assert.True (r.Subviews.IndexOf (v1) == 1);
Assert.True (r.Subviews.IndexOf (v2) == 2);
Assert.True (r.Subviews.IndexOf (v3) == 0);
Assert.True (r.TabIndexes.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v2) == 1);
Assert.True (r.TabIndexes.IndexOf (v3) == 2);
}
[Fact]
public void SendSubviewBackwards_Subviews_vs_TabIndexes ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
r.SendSubviewBackwards (v3);
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.Subviews.IndexOf (v2) == 2);
Assert.True (r.Subviews.IndexOf (v3) == 1);
Assert.True (r.TabIndexes.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v2) == 1);
Assert.True (r.TabIndexes.IndexOf (v3) == 2);
}
[Fact]
public void TabIndex_Set_CanFocus_ValidValues ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
v1.TabIndex = 1;
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v1) == 1);
v1.TabIndex = 2;
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v1) == 2);
}
[Fact]
public void TabIndex_Set_CanFocus_HigherValues ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
v1.TabIndex = 3;
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v1) == 2);
}
[Fact]
public void TabIndex_Set_CanFocus_LowerValues ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
v1.TabIndex = -1;
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v1) == 0);
}
[Fact]
public void TabIndex_Set_CanFocus_False ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
v1.CanFocus = false;
v1.TabIndex = 0;
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v1) == 0);
Assert.Equal (-1, v1.TabIndex);
}
[Fact]
public void TabIndex_Set_CanFocus_False_To_True ()
{
var r = new View ();
var v1 = new View ();
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
v1.CanFocus = true;
v1.TabIndex = 1;
Assert.True (r.Subviews.IndexOf (v1) == 0);
Assert.True (r.TabIndexes.IndexOf (v1) == 1);
}
[Fact]
public void TabStop_And_CanFocus_Are_All_True ()
{
var r = new View ();
var v1 = new View () { CanFocus = true };
var v2 = new View () { CanFocus = true };
var v3 = new View () { CanFocus = true };
r.Add (v1, v2, v3);
r.FocusNext ();
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.True (v3.HasFocus);
}
[Fact]
public void TabStop_Are_All_True_And_CanFocus_Are_All_False ()
{
var r = new View ();
var v1 = new View ();
var v2 = new View ();
var v3 = new View ();
r.Add (v1, v2, v3);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
}
[Fact]
public void TabStop_Are_All_False_And_CanFocus_Are_All_True ()
{
var r = new View ();
var v1 = new View () { CanFocus = true, TabStop = false };
var v2 = new View () { CanFocus = true, TabStop = false };
var v3 = new View () { CanFocus = true, TabStop = false };
r.Add (v1, v2, v3);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
}
[Fact]
public void TabStop_And_CanFocus_Mixed_And_BothFalse ()
{
var r = new View ();
var v1 = new View () { CanFocus = true, TabStop = false };
var v2 = new View () { CanFocus = false, TabStop = true };
var v3 = new View () { CanFocus = false, TabStop = false };
r.Add (v1, v2, v3);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
}
[Fact]
public void TabStop_All_True_And_Changing_CanFocus_Later ()
{
var r = new View ();
var v1 = new View ();
var v2 = new View ();
var v3 = new View ();
r.Add (v1, v2, v3);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
v1.CanFocus = true;
r.FocusNext ();
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
v2.CanFocus = true;
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
v3.CanFocus = true;
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.True (v3.HasFocus);
}
[Fact]
public void TabStop_All_False_And_All_True_And_Changing_TabStop_Later ()
{
var r = new View ();
var v1 = new View () { CanFocus = true, TabStop = false };
var v2 = new View () { CanFocus = true, TabStop = false };
var v3 = new View () { CanFocus = true, TabStop = false };
r.Add (v1, v2, v3);
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
v1.TabStop = true;
r.FocusNext ();
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
v2.TabStop = true;
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
v3.TabStop = true;
r.FocusNext ();
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.True (v3.HasFocus);
}
}
}