using System;
using System.Collections.Generic;
using NStack;
namespace Terminal.Gui {
///
/// Provides a step-based "wizard" UI. The Wizard supports multiple steps. Each step () can host
/// arbitrary s, much like a . Each step also has a pane for help text. Along the
/// bottom of the Wizard view are customizable buttons enabling the user to navigate forward and backward through the Wizard.
///
///
///
public class Wizard : Dialog {
///
/// One step for the Wizard. The view hosts two sub-views: 1) add s to ,
/// 2) use to set the contents of the that shows on the
/// right side. Use and to
/// control wether the control or help pane are shown.
///
///
/// If s are added, do not set to true as this will conflict
/// with the Next button of the Wizard.
///
public class WizardStep : View {
///
/// The title of the .
///
public ustring Title { get => title; set => title = value; }
// TODO: Update Wizard title when step title is changed if step is current - this will require step to slueth it's parent
private ustring title;
private View controlPane = new FrameView ();
///
/// THe pane that holds the controls for the . Use `Add(View`) to add
/// controls. Note that the Controls view is sized to take 70% of the Wizard's width and the
/// takes the other 30%. This can be adjusted by setting `Width` from `Dim.Percent(70)` to
/// another value. If is set to `false` the control pane will fill the entire
/// Wizard.
///
public View Controls { get => controlPane; }
///
/// Sets or gets help text for the .If is set to
/// `false` the control pane will fill the entire wizard.
///
/// The help text is displayed using a read-only .
public ustring HelpText { get => helpTextView.Text; set => helpTextView.Text = value; }
private TextView helpTextView = new TextView ();
///
/// Sets or gets the text for the back button. The back button will only be visible on
/// steps after the first step.
///
/// The default text is "Back"
public ustring BackButtonText { get; set; } = ustring.Empty;
// TODO: Update button text of Wizard button when step's button text is changed if step is current - this will require step to slueth it's parent
///
/// Sets or gets the text for the next/finish button.
///
/// The default text is "Next..." if the Pane is not the last pane. Otherwise it is "Finish"
public ustring NextButtonText { get; set; } = ustring.Empty;
// TODO: Update button text of Wizard button when step's button text is changed if step is current - this will require step to slueth it's parent
///
/// Initializes a new instance of the class using positioning.
///
/// Title for the Step. Will be appended to the containing Wizard's title as
/// "Wizard Title - Wizard Step Title" when this step is active.
///
///
public WizardStep (ustring title)
{
this.Title = title; // this.Title holds just the "Wizard Title"; base.Title holds "Wizard Title - Step Title"
this.ColorScheme = Colors.Menu;
Y = 0;
Height = Dim.Fill (1); // for button frame
Width = Dim.Fill ();
Controls.ColorScheme = Colors.Dialog;
Controls.Border.BorderStyle = BorderStyle.None;
Controls.Border.Padding = new Thickness (0);
Controls.Border.BorderThickness = new Thickness (0);
this.Add (Controls);
helpTextView.ColorScheme = Colors.Menu;
helpTextView.Y = 0;
helpTextView.ReadOnly = true;
helpTextView.WordWrap = true;
this.Add (helpTextView);
ShowHide ();
var scrollBar = new ScrollBarView (helpTextView, true);
scrollBar.ChangedPosition += () => {
helpTextView.TopRow = scrollBar.Position;
if (helpTextView.TopRow != scrollBar.Position) {
scrollBar.Position = helpTextView.TopRow;
}
helpTextView.SetNeedsDisplay ();
};
scrollBar.VisibleChanged += () => {
if (scrollBar.Visible && helpTextView.RightOffset == 0) {
helpTextView.RightOffset = 1;
} else if (!scrollBar.Visible && helpTextView.RightOffset == 1) {
helpTextView.RightOffset = 0;
}
};
helpTextView.DrawContent += (e) => {
scrollBar.Size = helpTextView.Lines;
scrollBar.Position = helpTextView.TopRow;
if (scrollBar.OtherScrollBarView != null) {
scrollBar.OtherScrollBarView.Size = helpTextView.Maxlength;
scrollBar.OtherScrollBarView.Position = helpTextView.LeftColumn;
}
scrollBar.LayoutSubviews ();
scrollBar.Refresh ();
};
this.Add (scrollBar);
}
///
/// If true (the default) the help will be visible. If false, the help will not be shown and the control pane will
/// fill the wizard step.
///
public bool ShowHelp {
get => showHelp;
set {
showHelp = value;
ShowHide ();
}
}
private bool showHelp = true;
///
/// If true (the default) the View will be visible. If false, the controls will not be shown and the help will
/// fill the wizard step.
///
public bool ShowControls {
get => showControls;
set {
showControls = value;
ShowHide ();
}
}
private bool showControls = true;
///
/// Does the work to show and hide the controls, help, and buttons as appropriate
///
private void ShowHide ()
{
Controls.Height = Dim.Fill (1);
helpTextView.Height = Dim.Fill (1);
helpTextView.Width = Dim.Fill ();
if (showControls) {
if (showHelp) {
Controls.Width = Dim.Percent (70);
helpTextView.X = Pos.Right (Controls) ;
helpTextView.Width = Dim.Fill ();
} else {
Controls.Width = Dim.Percent (100);
}
} else {
if (showHelp) {
helpTextView.X = 0;
} else {
// Error - no pane shown
}
}
Controls.Visible = showControls;
helpTextView.Visible = showHelp;
}
}
///
/// If the is not the first step in the wizard, this button causes
/// the event to be fired and the wizard moves to the previous step.
///
///
/// Use the event to be notified when the user attempts to go back.
///
public Button BackButton { get => backBtn; }
private Button backBtn;
///
/// If the is the last step in the wizard, this button causes
/// the event to be fired and the wizard to close. If the step is not the last step,
/// the event will be fired and the wizard will move next step.
///
///
/// Use the and events to be notified
/// when the user attempts go to the next step or finish the wizard.
///
public Button NextFinishButton { get => nextfinishBtn; }
private Button nextfinishBtn;
///
/// Initializes a new instance of the class using positioning.
///
///
/// The Wizard will be vertically and horizontally centered in the container.
/// After initialization use X, Y, Width, and Height change size and position.
///
public Wizard () : this (ustring.Empty)
{
}
///
/// Initializes a new instance of the class using positioning.
///
/// Title for the Wizard.
///
/// The Wizard will be vertically and horizontally centered in the container.
/// After initialization use X, Y, Width, and Height change size and position.
///
public Wizard (ustring title) : base (title)
{
wizardTitle = title;
// Using Justify causes the Back and Next buttons to be hard justified against
// the left and right edge
ButtonAlignment = ButtonAlignments.Justify;
this.Border.BorderStyle = BorderStyle.Double;
// Add a horiz separator
var separator = new LineView (Graphs.Orientation.Horizontal) {
Y = Pos.AnchorEnd (2)
};
Add (separator);
backBtn = new Button ("_Back") { AutoSize = true };
AddButton (backBtn);
nextfinishBtn = new Button ("_Next...") { AutoSize = true };
nextfinishBtn.IsDefault = true;
AddButton (nextfinishBtn);
backBtn.Clicked += () => {
var args = new WizardStepEventArgs ();
MovingBack?.Invoke (args);
if (!args.Cancel) {
if (currentStep > 0) {
CurrentStep--;
}
}
};
nextfinishBtn.Clicked += () => {
if (currentStep == steps.Count - 1) {
var args = new WizardStepEventArgs ();
Finished?.Invoke (args);
if (!args.Cancel) {
Application.RequestStop (this);
}
} else {
var args = new WizardStepEventArgs ();
MovingNext?.Invoke (args);
if (!args.Cancel) {
CurrentStep++;
}
}
};
Loaded += () => {
foreach (var step in steps) {
step.Y = 0;
}
if (steps.Count > 0) {
CurrentStep = 0;
}
};
}
private List steps = new List ();
private int currentStep = 0;
///
/// Adds a step to the wizard. The Next and Back buttons navigate through the added steps in the
/// order they were added.
///
///
/// The "Next..." button of the last step added will read "Finish" (unless changed from default).
public void AddStep (WizardStep newStep)
{
steps.Add (newStep);
this.Add (newStep);
}
///
/// The title of the Wizard, shown at the top of the Wizard with " - currentStep.Title" appended.
///
public new ustring Title {
get {
// The base (Dialog) Title holds the full title ("Wizard Title - Step Title")
return base.Title;
}
set {
wizardTitle = value;
base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + steps [currentStep].Title : string.Empty)}";
}
}
private ustring wizardTitle = ustring.Empty;
///
/// for transition events.
///
public class WizardStepEventArgs : EventArgs {
///
/// Set to true to cancel the transition to the next step.
///
public bool Cancel { get; set; }
///
/// Initializes a new instance of
///
public WizardStepEventArgs ()
{
Cancel = false;
}
}
///
/// This event is raised when the Back button in the is clicked. The Back button is always
/// the first button in the array of Buttons passed to the constructor, if any.
///
public event Action MovingBack;
///
/// This event is raised when the Next/Finish button in the is clicked. The Next/Finish button is always
/// the last button in the array of Buttons passed to the constructor, if any. This event is only
/// raised if the is the last Step in the Wizard flow
/// (otherwise the event is raised).
///
public event Action MovingNext;
///
/// This event is raised when the Next/Finish button in the is clicked. The Next/Finish button is always
/// the last button in the array of Buttons passed to the constructor, if any. This event is only
/// raised if the is the last Step in the Wizard flow
/// (otherwise the event is raised).
///
public event Action Finished;
///
/// This event is raised when the current step )) in the changes.
///
public event Action CurrentStepChanged;
///
/// for events.
///
public class CurrentStepChangedEventArgs : EventArgs {
///
/// The new current .
///
public int CurrentStepIndex { get; }
///
/// Initializes a new instance of
///
/// The new current .
public CurrentStepChangedEventArgs (int currentStepIndex)
{
CurrentStepIndex = currentStepIndex;
}
}
///
/// Gets or sets the currently active .
///
public int CurrentStep {
get => currentStep;
set {
currentStep = value;
OnCurrentStepChanged ();
}
}
///
/// Called when the current has changed ().
///
public virtual void OnCurrentStepChanged ()
{
CurrentStepChanged?.Invoke (new CurrentStepChangedEventArgs (currentStep));
// Hide all but the first step
foreach (WizardStep step in steps) {
step.Visible = (steps [currentStep] == step);
}
// TODO: Add support for "Wizard Title - Step Title"
base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + steps [currentStep].Title : string.Empty)}";
backBtn.Text = steps [currentStep].BackButtonText != ustring.Empty ? steps [currentStep].BackButtonText : "_Back";
if (currentStep == 0) {
backBtn.Visible = false;
} else {
backBtn.Visible = true;
}
if (currentStep == steps.Count - 1) {
nextfinishBtn.Text = steps [currentStep].NextButtonText != ustring.Empty ? steps [currentStep].NextButtonText : "Fi_nish";
} else {
nextfinishBtn.Text = steps [currentStep].NextButtonText != ustring.Empty ? steps [currentStep].NextButtonText : "_Next...";
}
}
}
}