Skip to content

Commit

Permalink
Fixes #3707 - Wizard focus (#3708)
Browse files Browse the repository at this point in the history
* Initial commit.

* More fixes

* Fixed AdvanceFocus issue with nested subviews. E.g. Wizard->WizardStep->_contentView->subview

* Fixed DatePicker tests

* Fixed DatePicker tests
  • Loading branch information
tig authored Aug 31, 2024
1 parent 56d9c59 commit 27234b7
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 66 deletions.
62 changes: 32 additions & 30 deletions Terminal.Gui/View/View.Navigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
/// </summary>
/// <remarks>
/// <para>
/// If there is no next/previous view, the focus is set to the view itself.
/// If there is no next/previous view to advance to, the focus is set to the view itself.
/// </para>
/// </remarks>
/// <param name="direction"></param>
Expand All @@ -37,17 +37,19 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
return true;
}

// AdvanceFocus did not advance
View [] index = GetSubviewFocusChain (direction, behavior);
// AdvanceFocus did not advance - do we wrap, or move up to the superview?

if (index.Length == 0)
View [] focusChain = GetSubviewFocusChain (direction, behavior);

if (focusChain.Length == 0)
{
return false;
}

// Special case TabGroup
if (behavior == TabBehavior.TabGroup)
{
if (direction == NavigationDirection.Forward && focused == index [^1] && SuperView is null)
if (direction == NavigationDirection.Forward && focused == focusChain [^1] && SuperView is null)
{
// We're at the top of the focus chain. Go back down the focus chain and focus the first TabGroup
View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
Expand All @@ -66,7 +68,7 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
}
}

if (direction == NavigationDirection.Backward && focused == index [0])
if (direction == NavigationDirection.Backward && focused == focusChain [0])
{
// We're at the bottom of the focus chain
View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
Expand All @@ -86,38 +88,38 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
}
}

int focusedIndex = index.IndexOf (Focused); // Will return -1 if Focused can't be found or is null
int next = 0;
int focusedIndex = focusChain.IndexOf (Focused); // Will return -1 if Focused can't be found or is null
var next = 0; // Assume we wrap to start of the focus chain

if (focusedIndex < index.Length - 1)
if (focusedIndex < focusChain.Length - 1)
{
// We're moving w/in the subviews
next = focusedIndex + 1;
}
else
{
// We're moving beyond the last subview

// Determine if focus should remain in this focus chain, or move to the superview's focus chain

// If we are TabStop and our SuperView has at least one other TabStop subview, move to the SuperView's chain
if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetSubviewFocusChain (direction, behavior).Length > 1)
{
return false;
}

// TabGroup is special-cased.
if (focused?.TabStop == TabBehavior.TabGroup)
if (SuperView is { })
{
if (SuperView?.GetSubviewFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
// 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.GetSubviewFocusChain (direction, behavior).Length > 1)
{
// Our superview has a TabGroup subview; signal we couldn't move so we nav out to it
return false;
}

// TabGroup is special-cased.
if (focused?.TabStop == TabBehavior.TabGroup)
{
if (SuperView?.GetSubviewFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
{
// Our superview has a TabGroup subview; signal we couldn't move so we nav out to it
return false;
}
}
}
}

View view = index [next];
View view = focusChain [next];

if (view.HasFocus)
{
Expand Down Expand Up @@ -259,7 +261,7 @@ internal bool RestoreFocus ()
{
if (Focused is null && _subviews?.Count > 0)
{
if (_previouslyMostFocused is { } /* && (behavior is null || _previouslyMostFocused.TabStop == behavior)*/)
if (_previouslyMostFocused is { })
{
return _previouslyMostFocused.SetFocus ();
}
Expand Down Expand Up @@ -355,6 +357,11 @@ public bool SetFocus ()
return focusSet;
}

/// <summary>
/// Caches the most focused subview when this view is losing focus. This is used by <see cref="RestoreFocus"/>.
/// </summary>
private View? _previouslyMostFocused;

/// <summary>
/// INTERNAL: Called when focus is going to change to this view. This method is called by <see cref="SetFocus"/> and
/// other methods that
Expand Down Expand Up @@ -625,7 +632,7 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false

if (SuperView is { })
{
SuperView._previouslyMostFocused = focusedPeer;
//SuperView._previouslyMostFocused = focusedPeer;
}

// Post-conditions - prove correctness
Expand All @@ -637,11 +644,6 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
SetNeedsDisplay ();
}

/// <summary>
/// Caches the most focused subview when this view is losing focus. This is used by <see cref="RestoreFocus"/>.
/// </summary>
private View? _previouslyMostFocused;

private void NotifyFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
{
// Call the virtual method
Expand Down
4 changes: 4 additions & 0 deletions Terminal.Gui/Views/DatePicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ private void SetInitialProperties (DateTime date)

_calendar = new TableView
{
Id = "_calendar",
X = 0,
Y = Pos.Bottom (_dateLabel),
Height = 11,
Expand All @@ -206,6 +207,7 @@ private void SetInitialProperties (DateTime date)

_dateField = new DateField (DateTime.Now)
{
Id = "_dateField",
X = Pos.Right (_dateLabel),
Y = 0,
Width = Dim.Width (_calendar) - Dim.Width (_dateLabel),
Expand All @@ -215,6 +217,7 @@ private void SetInitialProperties (DateTime date)

_previousMonthButton = new Button
{
Id = "_previousMonthButton",
X = Pos.Center () - 2,
Y = Pos.Bottom (_calendar) - 1,
Width = 2,
Expand All @@ -234,6 +237,7 @@ private void SetInitialProperties (DateTime date)

_nextMonthButton = new Button
{
Id = "_nextMonthButton",
X = Pos.Right (_previousMonthButton) + 2,
Y = Pos.Bottom (_calendar) - 1,
Width = 2,
Expand Down
13 changes: 5 additions & 8 deletions Terminal.Gui/Views/Wizard/Wizard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,14 +351,11 @@ public bool GoToStep (WizardStep newStep)

UpdateButtonsAndTitle ();

// Set focus to the nav buttons
if (BackButton.HasFocus)
{
BackButton.SetFocus ();
}
else

// Set focus on the contentview
if (newStep is { })
{
NextFinishButton.SetFocus ();
newStep.Subviews.ToArray () [0].SetFocus ();
}

if (OnStepChanged (oldStep, _currentStep))
Expand Down Expand Up @@ -543,7 +540,7 @@ private void UpdateButtonsAndTitle ()

SetNeedsLayout ();
LayoutSubviews ();
Draw ();
//Draw ();
}

private void Wizard_Closing (object sender, ToplevelClosingEventArgs obj)
Expand Down
28 changes: 18 additions & 10 deletions Terminal.Gui/Views/Wizard/WizardStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/// the step is active; see also: <see cref="Wizard.StepChanged"/>. To enable or disable a step from being shown to the
/// user, set <see cref="View.Enabled"/>.
/// </remarks>
public class WizardStep : FrameView
public class WizardStep : View
{
///// <summary>
///// The title of the <see cref="WizardStep"/>.
Expand All @@ -36,19 +36,32 @@ public class WizardStep : FrameView
//private string title = string.Empty;

// The contentView works like the ContentView in FrameView.
private readonly View _contentView = new () { Id = "WizardContentView" };
private readonly TextView _helpTextView = new ();
private readonly View _contentView = new ()
{
CanFocus = true,
TabStop = TabBehavior.TabStop,
Id = "WizardStep._contentView"
};
private readonly TextView _helpTextView = new ()
{
CanFocus = true,
TabStop = TabBehavior.TabStop,
ReadOnly = true,
WordWrap = true,
AllowsTab = false,
Id = "WizardStep._helpTextView"
};

/// <summary>
/// Initializes a new instance of the <see cref="Wizard"/> class.
/// </summary>
public WizardStep ()
{
TabStop = TabBehavior.TabStop;
CanFocus = true;
BorderStyle = LineStyle.None;
base.Add (_contentView);

_helpTextView.ReadOnly = true;
_helpTextView.WordWrap = true;
base.Add (_helpTextView);

// BUGBUG: v2 - Disabling scrolling for now
Expand Down Expand Up @@ -144,11 +157,6 @@ public override View Add (View view)
/// <remarks></remarks>
public override View Remove (View view)
{
if (view is null)
{
return view;
}

SetNeedsDisplay ();
View container = view?.SuperView;

Expand Down
40 changes: 30 additions & 10 deletions UICatalog/Scenarios/Wizards.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public override void Main ()
};
win.Add (frame);

var label = new Label { X = 0, Y = 0, TextAlignment = Alignment.End, Text = "Width:" };
var label = new Label { X = 0, Y = 0, TextAlignment = Alignment.End, Text = "_Width:", Width = 10 };
frame.Add (label);

var widthEdit = new TextField
Expand All @@ -39,15 +39,15 @@ public override void Main ()
};
frame.Add (widthEdit);

label = new()
label = new ()
{
X = 0,
Y = Pos.Bottom (label),

Width = Dim.Width (label),
Height = 1,
TextAlignment = Alignment.End,
Text = "Height:"
Text = "_Height:"
};
frame.Add (label);

Expand All @@ -61,15 +61,15 @@ public override void Main ()
};
frame.Add (heightEdit);

label = new()
label = new ()
{
X = 0,
Y = Pos.Bottom (label),

Width = Dim.Width (label),
Height = 1,
TextAlignment = Alignment.End,
Text = "Title:"
Text = "_Title:"
};
frame.Add (label);

Expand All @@ -91,7 +91,7 @@ void Win_Loaded (object sender, EventArgs args)

win.Loaded += Win_Loaded;

label = new()
label = new ()
{
X = Pos.Center (), Y = Pos.AnchorEnd (1), TextAlignment = Alignment.End, Text = "Action:"
};
Expand All @@ -105,7 +105,7 @@ void Win_Loaded (object sender, EventArgs args)

var showWizardButton = new Button
{
X = Pos.Center (), Y = Pos.Bottom (frame) + 2, IsDefault = true, Text = "Show Wizard"
X = Pos.Center (), Y = Pos.Bottom (frame) + 2, IsDefault = true, Text = "_Show Wizard"
};

showWizardButton.Accept += (s, e) =>
Expand Down Expand Up @@ -162,6 +162,13 @@ void Win_Loaded (object sender, EventArgs args)
firstStep.HelpText =
"This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA.";
RadioGroup radioGroup = new ()
{
RadioLabels = ["_One", "_Two", "_3"]
};
firstStep.Add (radioGroup);
wizard.AddStep (firstStep);
// Add 2nd step
Expand All @@ -178,6 +185,13 @@ void Win_Loaded (object sender, EventArgs args)
Text = "Press Me to Rename Step", X = Pos.Right (buttonLbl), Y = Pos.Top (buttonLbl)
};
RadioGroup radioGroup2 = new ()
{
RadioLabels = ["_A", "_B", "_C"],
Orientation = Orientation.Horizontal
};
secondStep.Add (radioGroup2);
button.Accept += (s, e) =>
{
secondStep.Title = "2nd Step";
Expand All @@ -193,7 +207,7 @@ void Win_Loaded (object sender, EventArgs args)
var firstNameField =
new TextField { Text = "Number", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
secondStep.Add (lbl, firstNameField);
lbl = new() { Text = "Last Name: ", X = 1, Y = Pos.Bottom (lbl) };
lbl = new () { Text = "Last Name: ", X = 1, Y = Pos.Bottom (lbl) };
var lastNameField = new TextField { Text = "Six", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
secondStep.Add (lbl, lastNameField);
Expand All @@ -213,7 +227,8 @@ void Win_Loaded (object sender, EventArgs args)
Y = Pos.Bottom (thirdStepEnabledCeckBox) + 2,
Width = Dim.Fill (),
Height = 4,
Title = "A Broken Frame (by Depeche Mode)"
Title = "A Broken Frame (by Depeche Mode)",
TabStop = TabBehavior.NoStop
};
frame.Add (new TextField { Text = "This is a TextField inside of the frame." });
secondStep.Add (frame);
Expand Down Expand Up @@ -286,7 +301,7 @@ void Win_Loaded (object sender, EventArgs args)
}
};
fourthStep.Add (hideHelpBtn);
fourthStep.NextButtonText = "Go To Last Step";
fourthStep.NextButtonText = "_Go To Last Step";
var scrollBar = new ScrollBarView (someText, true);
scrollBar.ChangedPosition += (s, e) =>
Expand Down Expand Up @@ -355,4 +370,9 @@ void Win_Loaded (object sender, EventArgs args)
win.Dispose ();
Application.Shutdown ();
}

private void Wizard_StepChanged (object sender, StepChangeEventArgs e)
{
throw new NotImplementedException ();
}
}
Loading

0 comments on commit 27234b7

Please sign in to comment.