using System.Windows.Controls; using Wpf.Ui.Controls.Interfaces; using Wpf.Ui.Mvvm.Contracts; using System.ComponentModel; using System.Windows; using Bloxstrap.UI.ViewModels.Installer; using Bloxstrap.UI.Elements.Installer.Pages; using Bloxstrap.UI.Elements.Base; using System.Windows.Media.Animation; using System.Reflection.Metadata.Ecma335; using Bloxstrap.Resources; namespace Bloxstrap.UI.Elements.Installer { /// /// Interaction logic for MainWindow.xaml /// /// /// The logic behind this wizard-like interface is full of gross hacks, but there's no easy way to do this and I've tried to /// make it as nice and MVVM-"""conformant""" as can possibly be ¯\_(ツ)_/¯ /// /// Page ViewModels can request changing of navigation button states through the following call flow: /// - Page ViewModel holds event for requesting button state change /// - Page CodeBehind subscribes to event on page creation /// - Page ViewModel invokes event when ready /// - Page CodeBehind receives it, gets MainWindow, and directly calls MainWindow.SetButtonEnabled() /// - MainWindow.SetButtonEnabled() directly calls MainWindowViewModel.SetButtonEnabled() which does the thing a voila /// /// Page ViewModels can also be notified of when the next page button has been pressed and stop progression if needed through a callback /// - MainWindow has a single-set Func property named NextPageCallback which is reset on every page load /// - This callback is called when the next page button is pressed /// - Page CodeBehind gets MainWindow and sets the callback to its own local function on page load /// - CodeBehind's local function then directly calls its ViewModel to do whatever it needs to do public partial class MainWindow : WpfUiWindow, INavigationWindow { internal readonly MainWindowViewModel _viewModel = new(); private Type _currentPage = typeof(WelcomePage); private List _pages = new() { typeof(WelcomePage), typeof(InstallPage), typeof(CompletionPage) }; private DateTimeOffset _lastNavigation = DateTimeOffset.Now; public Func? NextPageCallback; public NextAction CloseAction = NextAction.Terminate; public bool Finished => _currentPage == _pages.Last(); public MainWindow() { _viewModel.CloseWindowRequest += (_, _) => CloseWindow(); _viewModel.PageRequest += (_, type) => { // debounce if (DateTimeOffset.Now.Subtract(_lastNavigation).TotalMilliseconds < 500) return; if (type == "next") NextPage(); else if (type == "back") BackPage(); _lastNavigation = DateTimeOffset.Now; }; DataContext = _viewModel; InitializeComponent(); App.Logger.WriteLine("MainWindow", "Initializing installer window"); Closing += new CancelEventHandler(MainWindow_Closing); } void NextPage() { if (NextPageCallback is not null && !NextPageCallback()) return; if (_currentPage == _pages.Last()) return; var page = _pages[_pages.IndexOf(_currentPage) + 1]; Navigate(page); SetButtonEnabled("next", page != _pages.Last()); SetButtonEnabled("back", true); } void BackPage() { if (_currentPage == _pages.First()) return; var page = _pages[_pages.IndexOf(_currentPage) - 1]; Navigate(page); SetButtonEnabled("next", true); SetButtonEnabled("back", page != _pages.First()); } void MainWindow_Closing(object? sender, CancelEventArgs e) { if (Finished) return; var result = Frontend.ShowMessageBox(Strings.Installer_ShouldCancel, MessageBoxImage.Warning, MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) e.Cancel = true; } public void SetNextButtonText(string text) => _viewModel.SetNextButtonText(text); public void SetButtonEnabled(string type, bool state) => _viewModel.SetButtonEnabled(type, state); #region INavigationWindow methods public Frame GetFrame() => RootFrame; public INavigation GetNavigation() => RootNavigation; public bool Navigate(Type pageType) { _currentPage = pageType; NextPageCallback = null; return RootNavigation.Navigate(pageType); } public void SetPageService(IPageService pageService) => RootNavigation.PageService = pageService; public void ShowWindow() => Show(); public void CloseWindow() => Close(); #endregion INavigationWindow methods } }