namespace Editor.Wizards;
///
/// A window that has multiple steps, talks you through a process
///
public abstract class BaseWizard : Widget
{
public virtual string Title => "Base Page";
public virtual string Icon => "people";
public Layout BodyLayout;
public Layout HeaderLayout;
public Layout FooterRight;
public Layout FooterLeft;
public Layout Footer;
protected ScrollArea ScrollArea;
protected Button BackButton { get; init; }
protected Button NextButton { get; init; }
protected Label PageTitle { get; init; }
protected Label PageSubtitle { get; init; }
bool loading;
BaseWizardPage _current;
protected BaseWizardPage Current
{
get => _current;
set
{
if ( _current == value ) return;
foreach ( var p in Steps )
p.Visible = false;
_current?.TokenSource?.Cancel();
_current = value;
_ = SwitchCurrentPage();
Update();
}
}
protected List Steps = new();
public BaseWindow CreateWindow( int width = 1280, int height = 770 )
{
var window = new BaseWindow()
{
Size = new Vector2( width, height ),
MinimumSize = new Vector2( width, height ),
TranslucentBackground = true,
NoSystemBackground = true,
WindowTitle = "Wizard"
};
window.SetModal( true, true );
window.Layout = Layout.Column();
window.Layout.Margin = 0;
window.Layout.Spacing = 0;
window.Layout.Add( this );
window.WindowTitle = $"{Title}";
window.SetWindowIcon( Icon );
window.Show();
return window;
}
internal BaseWizard() : base( null )
{
Layout = Layout.Column();
HeaderLayout = Layout.AddColumn();
var body = Layout.AddColumn( 1 );
ScrollArea = new ScrollArea( this );
body.Add( ScrollArea );
ScrollArea.Canvas = new Widget( ScrollArea );
ScrollArea.Canvas.Layout = Layout.Column();
BodyLayout = ScrollArea.Canvas.Layout;
BodyLayout.Margin = 32;
Layout.AddStretchCell( 0 );
Footer = Layout.AddRow();
Footer.Margin = 16;
FooterLeft = Footer.AddRow();
Footer.AddStretchCell();
FooterRight = Footer.AddRow();
FooterRight.Spacing = 4;
HeaderLayout.Margin = new Sandbox.UI.Margin( 0, 0, 0, 0 );
PageTitle = HeaderLayout.Add( new Label.Title( "Title" ) );
PageTitle.ContentMargins = new Sandbox.UI.Margin( 32, 16, 0, 0 );
PageSubtitle = HeaderLayout.Add( new Label.Body( "Subtitle" ) { Color = Theme.Primary.Lighten( 0.3f ) } );
PageSubtitle.ContentMargins = new Sandbox.UI.Margin( 32, 0, 0, 16 );
FooterRight.Spacing = 8;
BackButton = FooterRight.Add( new Button( "Back", "navigate_before" ) );
BackButton.Clicked = LastPage;
NextButton = FooterRight.Add( new Button.Primary( "Next", "navigate_next" ) );
NextButton.Clicked = NextPage;
}
public Layout StartSection( string name, Layout layout = null )
{
layout ??= BodyLayout;
var block = layout.AddColumn();
block.Margin = 8;
block.Add( new Label.Subtitle( name ) );
var inner = block.AddColumn();
inner.Margin = 8;
return inner;
}
public void AddFooterDefaults()
{
var revert = new Button( "Revert Changes", "history", this );
revert.Clicked = Reset;
var save = new Button.Primary( "Save", "save", this );
save.Clicked = OnSave;
FooterRight.Add( revert );
FooterRight.Add( save );
}
public virtual void OnSave()
{
}
///
/// Rebuild the page on hotload for quick iteration
///
[EditorEvent.Hotload]
public void Reset()
{
HeaderLayout.Clear( true );
BodyLayout.Clear( true );
FooterLeft.Clear( true );
FooterRight.Clear( true );
}
async Task SwitchCurrentPage()
{
try
{
loading = true;
_current.TokenSource = new System.Threading.CancellationTokenSource();
await _current.OpenAsync();
loading = false;
if ( _current.IsAutoStep && _current.CanProceed() )
{
NextPage();
}
}
catch ( System.Exception e )
{
Log.Error( e );
}
}
protected override void OnPaint()
{
base.OnPaint();
var steps = (float)Steps.Count - 1;
var index = (float)Steps.IndexOf( Current );
var progress = index / steps;
var left = Color.Lerp( Theme.WindowBackground, Theme.Primary, 0.5f );
var r = HeaderLayout.InnerRect;
Paint.Antialiasing = true;
Paint.SetBrushAndPen( left );
Paint.DrawRect( HeaderLayout.InnerRect );
Paint.SetBrushAndPen( Theme.WindowBackground );
Paint.DrawRect( Footer.OuterRect );
}
[EditorEvent.Frame]
public void Tick()
{
if ( Current == null )
Current = Steps.FirstOrDefault();
bool finalpage = Current == Steps.LastOrDefault();
PageTitle.Text = Current.PageTitle;
PageSubtitle.Text = Current.PageSubtitle;
// special case for end of wizard
if ( finalpage && Current.CanProceed() && !loading )
{
BackButton.Visible = false;
NextButton.Enabled = true;
NextButton.Text = "Finished";
NextButton.Icon = "done";
return;
}
BackButton.Visible = true;
BackButton.Enabled = !loading && Steps.First() != Current;
NextButton.Enabled = !loading && Steps.Last() != Current && Current.CanProceed();
NextButton.Text = Current.NextButtonText;
NextButton.Icon = "navigate_next";
}
protected void AddStep( BaseWizardPage p )
{
Steps.Add( p );
BodyLayout.Add( p );
p.Visible = false;
}
protected void LastPage()
{
var i = Steps.IndexOf( Current );
if ( i <= 0 ) return;
OnSave();
Current = Steps[i - 1];
}
protected void NextPage()
{
if ( !Current.CanProceed() )
return;
OnSave();
// Finish closes the window
bool finalpage = Current == Steps.Last();
if ( finalpage )
{
var p = Parent;
while ( p.IsValid() )
{
if ( p is BaseWindow )
{
p.Close();
return;
}
p = p.Parent;
}
return;
}
_ = TrySwitchToNextPage();
}
async Task TrySwitchToNextPage()
{
loading = true;
var i = Steps.IndexOf( Current );
var current = Current;
var next = Steps[i + 1];
try
{
var result = await current.FinishAsync();
loading = false;
if ( !result )
return;
Current = next;
}
catch ( System.Exception e )
{
Log.Error( e );
}
}
}