diff --git a/src/Artemis.UI.Linux/App.axaml b/src/Artemis.UI.Linux/App.axaml index 56b423b9a..a63d617b9 100644 --- a/src/Artemis.UI.Linux/App.axaml +++ b/src/Artemis.UI.Linux/App.axaml @@ -14,7 +14,7 @@ - + diff --git a/src/Artemis.UI.MacOS/App.axaml b/src/Artemis.UI.MacOS/App.axaml index f17dcf30d..6ef3efd97 100644 --- a/src/Artemis.UI.MacOS/App.axaml +++ b/src/Artemis.UI.MacOS/App.axaml @@ -14,7 +14,7 @@ - + diff --git a/src/Artemis.UI.Shared/Routing/Router/IRouter.cs b/src/Artemis.UI.Shared/Routing/Router/IRouter.cs index 1778d9f20..44d77bb5c 100644 --- a/src/Artemis.UI.Shared/Routing/Router/IRouter.cs +++ b/src/Artemis.UI.Shared/Routing/Router/IRouter.cs @@ -59,4 +59,9 @@ public interface IRouter /// The type of the root screen. It must be a class. /// The type of the parameters for the root screen. It must have a parameterless constructor. void SetRoot(RoutableScreen root) where TScreen : class where TParam : new(); + + /// + /// Clears the route used by the previous window, so that it is not restored when the main window opens. + /// + void ClearPreviousWindowRoute(); } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Routing/Router/Navigation.cs b/src/Artemis.UI.Shared/Routing/Router/Navigation.cs index 567a7b719..d04fded6f 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Navigation.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Navigation.cs @@ -35,11 +35,12 @@ internal class Navigation public async Task Navigate(NavigationArguments args) { - _logger.Information("Navigating to {Path}", _resolution.Path); + if (_options.EnableLogging) + _logger.Information("Navigating to {Path}", _resolution.Path); _cts = new CancellationTokenSource(); await NavigateResolution(_resolution, args, _root); - if (!Cancelled) + if (!Cancelled && _options.EnableLogging) _logger.Information("Navigated to {Path}", _resolution.Path); } @@ -48,7 +49,8 @@ internal class Navigation if (Cancelled || Completed) return; - _logger.Information("Cancelled navigation to {Path}", _resolution.Path); + if (_options.EnableLogging) + _logger.Information("Cancelled navigation to {Path}", _resolution.Path); _cts.Cancel(); } diff --git a/src/Artemis.UI.Shared/Routing/Router/Router.cs b/src/Artemis.UI.Shared/Routing/Router/Router.cs index b3b8a88c1..028d7be02 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Router.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Router.cs @@ -3,27 +3,34 @@ using System.Collections.Generic; using System.Reactive.Subjects; using System.Threading.Tasks; using Artemis.Core; +using Artemis.UI.Shared.Services.MainWindow; using Avalonia.Threading; using Serilog; namespace Artemis.UI.Shared.Routing; -internal class Router : CorePropertyChanged, IRouter +internal class Router : CorePropertyChanged, IRouter, IDisposable { private readonly Stack _backStack = new(); private readonly BehaviorSubject _currentRouteSubject; private readonly Stack _forwardStack = new(); private readonly Func _getNavigation; private readonly ILogger _logger; + private readonly IMainWindowService _mainWindowService; private Navigation? _currentNavigation; private IRoutableScreen? _root; + private string? _previousWindowRoute; - public Router(ILogger logger, Func getNavigation) + public Router(ILogger logger, IMainWindowService mainWindowService, Func getNavigation) { _logger = logger; + _mainWindowService = mainWindowService; _getNavigation = getNavigation; _currentRouteSubject = new BehaviorSubject(null); + + mainWindowService.MainWindowOpened += MainWindowServiceOnMainWindowOpened; + mainWindowService.MainWindowClosed += MainWindowServiceOnMainWindowClosed; } private RouteResolution Resolve(string path) @@ -128,7 +135,7 @@ internal class Router : CorePropertyChanged, IRouter await Navigate(path, new RouterNavigationOptions {AddToHistory = false}); if (previousPath != null) _forwardStack.Push(previousPath); - + return true; } @@ -142,7 +149,7 @@ internal class Router : CorePropertyChanged, IRouter await Navigate(path, new RouterNavigationOptions {AddToHistory = false}); if (previousPath != null) _backStack.Push(previousPath); - + return true; } @@ -164,4 +171,29 @@ internal class Router : CorePropertyChanged, IRouter { _root = root; } + + /// + public void ClearPreviousWindowRoute() + { + _previousWindowRoute = null; + } + + public void Dispose() + { + _currentRouteSubject.Dispose(); + _mainWindowService.MainWindowOpened -= MainWindowServiceOnMainWindowOpened; + _mainWindowService.MainWindowClosed -= MainWindowServiceOnMainWindowClosed; + } + + private void MainWindowServiceOnMainWindowOpened(object? sender, EventArgs e) + { + if (_previousWindowRoute != null && _currentRouteSubject.Value == "blank") + Dispatcher.UIThread.InvokeAsync(async () => await Navigate(_previousWindowRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = false})); + } + + private void MainWindowServiceOnMainWindowClosed(object? sender, EventArgs e) + { + _previousWindowRoute = _currentRouteSubject.Value; + Dispatcher.UIThread.InvokeAsync(async () => await Navigate("blank", new RouterNavigationOptions {AddToHistory = false, EnableLogging = false})); + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs b/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs index de4f25b10..a5c63ab88 100644 --- a/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs +++ b/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs @@ -20,4 +20,10 @@ public class RouterNavigationOptions /// /// If set to true, a route change from page/subpage1/subpage2 to page/subpage1 will be ignored. public bool IgnoreOnPartialMatch { get; set; } = false; + + /// + /// Gets or sets a boolean value indicating whether logging should be enabled. + /// Errors and warnings are always logged. + /// + public bool EnableLogging { get; set; } = true; } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/App.axaml b/src/Artemis.UI.Windows/App.axaml index da6064c9c..836d6a11f 100644 --- a/src/Artemis.UI.Windows/App.axaml +++ b/src/Artemis.UI.Windows/App.axaml @@ -14,13 +14,13 @@ - + - + diff --git a/src/Artemis.UI/Routing/Routes.cs b/src/Artemis.UI/Routing/Routes.cs index dbdf17053..ba0949669 100644 --- a/src/Artemis.UI/Routing/Routes.cs +++ b/src/Artemis.UI/Routing/Routes.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Artemis.UI.Screens.Home; using Artemis.UI.Screens.ProfileEditor; +using Artemis.UI.Screens.Root; using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.Settings.Updating; using Artemis.UI.Screens.SurfaceEditor; @@ -16,6 +17,7 @@ public static class Routes { public static List ArtemisRoutes = new() { + new RouteRegistration("blank"), new RouteRegistration("home"), #if DEBUG new RouteRegistration("workshop") diff --git a/src/Artemis.UI/Screens/Root/BlankView.axaml b/src/Artemis.UI/Screens/Root/BlankView.axaml new file mode 100644 index 000000000..37f0a544c --- /dev/null +++ b/src/Artemis.UI/Screens/Root/BlankView.axaml @@ -0,0 +1,7 @@ + + diff --git a/src/Artemis.UI/Screens/Root/BlankView.axaml.cs b/src/Artemis.UI/Screens/Root/BlankView.axaml.cs new file mode 100644 index 000000000..c4c8b84be --- /dev/null +++ b/src/Artemis.UI/Screens/Root/BlankView.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Screens.Root; + +public partial class BlankView : UserControl +{ + public BlankView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Root/BlankViewModel.cs b/src/Artemis.UI/Screens/Root/BlankViewModel.cs new file mode 100644 index 000000000..fe445a6ee --- /dev/null +++ b/src/Artemis.UI/Screens/Root/BlankViewModel.cs @@ -0,0 +1,9 @@ +using Artemis.UI.Shared; + +namespace Artemis.UI.Screens.Root; + +public class BlankViewModel : ViewModelBase, IMainScreenViewModel +{ + /// + public ViewModelBase? TitleBarViewModel => null; +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 828b080ae..9b2a46ddf 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -58,7 +58,7 @@ public class RootViewModel : RoutableScreen, IMainWindowPr mainWindowService.ConfigureMainWindowProvider(this); DisplayAccordingToSettings(); - OpenScreen = ReactiveCommand.Create(ExecuteOpenScreen); + OpenScreen = ReactiveCommand.Create(ExecuteOpenScreen); OpenDebugger = ReactiveCommand.CreateFromTask(ExecuteOpenDebugger); Exit = ReactiveCommand.CreateFromTask(ExecuteExit); this.WhenAnyValue(vm => vm.Screen).Subscribe(UpdateTitleBarViewModel); @@ -72,11 +72,13 @@ public class RootViewModel : RoutableScreen, IMainWindowPr registrationService.RegisterBuiltInDataModelDisplays(); registrationService.RegisterBuiltInDataModelInputs(); registrationService.RegisterBuiltInPropertyEditors(); + + _router.Navigate("home"); }); } public SidebarViewModel SidebarViewModel { get; } - public ReactiveCommand OpenScreen { get; } + public ReactiveCommand OpenScreen { get; } public ReactiveCommand OpenDebugger { get; } public ReactiveCommand Exit { get; } @@ -133,8 +135,11 @@ public class RootViewModel : RoutableScreen, IMainWindowPr #region Tray commands - private void ExecuteOpenScreen(string path) + private void ExecuteOpenScreen(string? path) { + if (path != null) + _router.ClearPreviousWindowRoute(); + // The window will open on the UI thread at some point, respond to that to select the chosen screen MainWindowOpened += OnEventHandler; OpenMainWindow(); @@ -143,7 +148,8 @@ public class RootViewModel : RoutableScreen, IMainWindowPr { MainWindowOpened -= OnEventHandler; // Avoid threading issues by running this on the UI thread - Dispatcher.UIThread.InvokeAsync(async () => await _router.Navigate(path)); + if (path != null) + Dispatcher.UIThread.InvokeAsync(async () => await _router.Navigate(path)); } } diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index 25cb7575b..0e8f7141d 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -88,7 +86,6 @@ public class SidebarViewModel : ActivatableViewModelBase SidebarCategories = categoryViewModels; }); - SelectedScreen = SidebarScreen.Screens.First(); } public SidebarScreenViewModel SidebarScreen { get; } diff --git a/src/Artemis.UI/ViewLocator.cs b/src/Artemis.UI/ViewLocator.cs index d95012653..9518edd8d 100644 --- a/src/Artemis.UI/ViewLocator.cs +++ b/src/Artemis.UI/ViewLocator.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using Artemis.Core; using Artemis.UI.Exceptions; +using Artemis.UI.Screens.Root; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.ReactiveUI; @@ -11,9 +12,13 @@ namespace Artemis.UI; public class ViewLocator : IDataTemplate { - public Control Build(object data) + public Control Build(object? data) { + if (data == null) + return new TextBlock {Text = "No data provided"}; + Type dataType = data.GetType(); + string name = dataType.FullName!.Split('`')[0].Replace("ViewModel", "View"); Type? type = dataType.Assembly.GetType(name); @@ -27,7 +32,7 @@ public class ViewLocator : IDataTemplate return new TextBlock {Text = "Not Found: " + name}; } - public bool Match(object data) + public bool Match(object? data) { return data is ReactiveObject; }