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 ); } } }