diff --git a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml
index 535504c04..7dec575f1 100644
--- a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml
+++ b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.xml
@@ -68,6 +68,9 @@
Gets or sets a list of LEDs to highlight
+
+
+
@@ -295,8 +298,8 @@
Represents a layer brush registered through
- or
-
+ or
+
@@ -349,7 +352,7 @@
Gets the type of event arguments this event triggers and that must be displayed as children
-
+
@@ -389,7 +392,7 @@
-
+
@@ -417,7 +420,7 @@
-
+
@@ -453,7 +456,7 @@
Gets a list of child view models that visualize the elements in the list
-
+
@@ -474,7 +477,7 @@
Gets the value of the property that is being visualized
-
+
@@ -504,7 +507,7 @@
Gets the view model used to display the display value
-
+
@@ -587,7 +590,7 @@
Gets a user-friendly representation of the
-
+
Updates the datamodel and if in an parent, any children
@@ -837,6 +840,104 @@
Represents a service provided by the Artemis Shared UI library
+
+
+ A service for UI related data model tasks
+
+
+
+
+ Gets a read-only list of all registered data model editors
+
+
+
+
+ Gets a read-only list of all registered data model displays
+
+
+
+
+ Creates a data model visualization view model for the main data model
+
+
+ A data model visualization view model containing all data model expansions and modules that expand the main
+ data model
+
+
+
+
+ Creates a data model visualization view model for the data model of the provided plugin feature
+
+ The modules to create the data model visualization view model for
+
+ Whether or not also to include the main data model (and therefore any modules marked
+ as )
+
+ A data model visualization view model containing the data model of the provided feature
+
+
+
+ Updates the children of the provided main data model visualization, removing disabled children and adding newly
+ enabled children
+
+
+
+
+ Registers a new data model editor
+
+ The type of the editor
+ The plugin this editor belongs to
+ A collection of extra types this editor supports
+ A registration that can be used to remove the editor
+
+
+
+ Registers a new data model display
+
+ The type of the display
+ The plugin this display belongs to
+ A registration that can be used to remove the display
+
+
+
+ Removes a data model editor
+
+
+ The registration of the editor as returned by
+
+
+
+
+ Removes a data model display
+
+
+ The registration of the display as returned by
+
+
+
+
+ Creates the most appropriate display view model for the provided that can display
+ a value
+
+ The type of data model property to find a display view model for
+ The description of the data model property
+
+ If , a simple .ToString() display view model will be
+ returned if nothing else is found
+
+ The most appropriate display view model for the provided
+
+
+
+ Creates the most appropriate input view model for the provided that allows
+ inputting a value
+
+ The type of data model property to find a display view model for
+ The description of the data model property
+ The initial value to show in the input
+ A function to call whenever the input was updated (submitted or not)
+ The most appropriate input view model for the provided
+
Creates a view model instance of type and shows its corresponding View as a window
@@ -895,103 +996,79 @@
The builder that can be used to configure the dialog
-
+
- A service for UI related data model tasks
+ Creates a content dialog, use the fluent API to configure it
+
+ The builder that can be used to configure the dialog
+
+
+
+ Gets the current window of the application
+
+ The current window of the application
+
+
+
+ Represents a class that provides the main window, so that can control the state of
+ the main window.
-
+
- Gets a read-only list of all registered data model editors
+ Gets a boolean indicating whether the main window is currently open
-
+
- Gets a read-only list of all registered data model displays
+ Opens the main window
-
+
- Creates a data model visualization view model for the main data model
-
-
- A data model visualization view model containing all data model expansions and modules that expand the main
- data model
-
-
-
-
- Creates a data model visualization view model for the data model of the provided plugin feature
-
- The modules to create the data model visualization view model for
-
- Whether or not also to include the main data model (and therefore any modules marked
- as )
-
- A data model visualization view model containing the data model of the provided feature
-
-
-
- Updates the children of the provided main data model visualization, removing disabled children and adding newly
- enabled children
+ Closes the main window
-
+
- Registers a new data model editor
+ Occurs when the main window has been opened
- The type of the editor
- The plugin this editor belongs to
- A collection of extra types this editor supports
- A registration that can be used to remove the editor
-
+
- Registers a new data model display
+ Occurs when the main window has been closed
- The type of the display
- The plugin this display belongs to
- A registration that can be used to remove the display
-
+
- Removes a data model editor
+ Gets a boolean indicating whether the main window is currently open
-
- The registration of the editor as returned by
-
-
+
- Removes a data model display
+ Sets up the main window provider that controls the state of the main window
-
- The registration of the display as returned by
-
+ The main window provider to use to control the state of the main window
-
+
- Creates the most appropriate display view model for the provided that can display
- a value
+ Opens the main window if it is not already open
- The type of data model property to find a display view model for
- The description of the data model property
-
- If , a simple .ToString() display view model will be
- returned if nothing else is found
-
- The most appropriate display view model for the provided
-
+
- Creates the most appropriate input view model for the provided that allows
- inputting a value
+ Closes the main window if it is not already closed
+
+
+
+
+ Occurs when the main window has been opened
+
+
+
+
+ Occurs when the main window has been closed
- The type of data model property to find a display view model for
- The description of the data model property
- The initial value to show in the input
- A function to call whenever the input was updated (submitted or not)
- The most appropriate input view model for the provided
diff --git a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs
index f80543a79..cbcd5ffdb 100644
--- a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs
+++ b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs
@@ -19,7 +19,7 @@ namespace Artemis.UI.Screens.Splash
{
_coreService = coreService;
_pluginManagementService = pluginManagementService;
- Status = "Initializing Core";
+ Status = "Initializing Core";
}
public string Status
diff --git a/src/Avalonia/Artemis.UI.Linux/App.axaml.cs b/src/Avalonia/Artemis.UI.Linux/App.axaml.cs
index a054a252d..ea12e7cc6 100644
--- a/src/Avalonia/Artemis.UI.Linux/App.axaml.cs
+++ b/src/Avalonia/Artemis.UI.Linux/App.axaml.cs
@@ -1,5 +1,4 @@
using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ReactiveUI;
@@ -10,17 +9,14 @@ namespace Artemis.UI.Linux
{
public override void Initialize()
{
- ArtemisBootstrapper.Bootstrap();
+ ArtemisBootstrapper.Bootstrap(this);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
- if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
- ArtemisBootstrapper.ConfigureApplicationLifetime(desktop);
-
- base.OnFrameworkInitializationCompleted();
+ ArtemisBootstrapper.Initialized();
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.MacOS/App.axaml.cs b/src/Avalonia/Artemis.UI.MacOS/App.axaml.cs
index 597873852..fefca7aee 100644
--- a/src/Avalonia/Artemis.UI.MacOS/App.axaml.cs
+++ b/src/Avalonia/Artemis.UI.MacOS/App.axaml.cs
@@ -1,5 +1,4 @@
using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ReactiveUI;
@@ -10,17 +9,14 @@ namespace Artemis.UI.MacOS
{
public override void Initialize()
{
- ArtemisBootstrapper.Bootstrap();
+ ArtemisBootstrapper.Bootstrap(this);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
- if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
- ArtemisBootstrapper.ConfigureApplicationLifetime(desktop);
-
- base.OnFrameworkInitializationCompleted();
+ ArtemisBootstrapper.Initialized();
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Avalonia/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
index 2e6e832ea..9358e1e61 100644
--- a/src/Avalonia/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
@@ -88,20 +88,6 @@ namespace Artemis.UI.Shared.Controls
///
public event EventHandler? LedClicked;
- protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
- {
- _deviceImage?.Dispose();
- _deviceImage = null;
-
- if (Device != null)
- {
- Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
- Device.DeviceUpdated -= DeviceUpdated;
- }
-
- base.OnDetachedFromVisualTree(e);
- }
-
///
/// Invokes the event
///
@@ -216,6 +202,21 @@ namespace Artemis.UI.Shared.Controls
#region Lifetime management
+ ///
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ _deviceImage?.Dispose();
+ _deviceImage = null;
+
+ if (Device != null)
+ {
+ Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
+ Device.DeviceUpdated -= DeviceUpdated;
+ }
+
+ base.OnDetachedFromVisualTree(e);
+ }
+
///
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs
index b67b4fb26..e867a2995 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/DataModelVisualizationRegistration.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Artemis.Core;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared.DataModelVisualization
{
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs
index 93a3382c7..c6b3b1478 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs
@@ -3,6 +3,7 @@ using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs
index 216c333ef..8bd21ea38 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs
index 5ba602769..161b76cbe 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs
@@ -1,5 +1,6 @@
using System;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs
index 71853705a..975a06295 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs
index 7cc7b4502..e162f0599 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs
@@ -2,6 +2,7 @@
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs
index 004ac7083..f6215d7af 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs
@@ -2,6 +2,7 @@
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
diff --git a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs
index 80ba67563..aaa651957 100644
--- a/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs
@@ -7,6 +7,7 @@ using System.Text;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs
index 3afaf0d0d..8a3060d13 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs
@@ -8,6 +8,7 @@ using Artemis.Core.Services;
using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
+using Artemis.UI.Shared.Services.Interfaces;
using Ninject;
using Ninject.Parameters;
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs
index d2c798207..57e9b6214 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs
@@ -4,9 +4,8 @@ using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared;
-using Artemis.UI.Shared.Services.Interfaces;
-namespace Artemis.UI.Shared.Services
+namespace Artemis.UI.Shared.Services.Interfaces
{
///
/// A service for UI related data model tasks
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs
index 5c63797bb..a844a7c9c 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs
@@ -65,8 +65,16 @@ namespace Artemis.UI.Shared.Services.Interfaces
/// The builder that can be used to configure the dialog
SaveFileDialogBuilder CreateSaveFileDialog();
+ ///
+ /// Creates a content dialog, use the fluent API to configure it
+ ///
+ /// The builder that can be used to configure the dialog
ContentDialogBuilder CreateContentDialog();
+ ///
+ /// Gets the current window of the application
+ ///
+ /// The current window of the application
Window GetCurrentWindow();
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowProvider.cs b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowProvider.cs
new file mode 100644
index 000000000..7ad9ef027
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowProvider.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Artemis.UI.Shared.Services.MainWindowService
+{
+ ///
+ /// Represents a class that provides the main window, so that can control the state of
+ /// the main window.
+ ///
+ public interface IMainWindowProvider
+ {
+ ///
+ /// Gets a boolean indicating whether the main window is currently open
+ ///
+ bool IsMainWindowOpen { get; }
+
+ ///
+ /// Opens the main window
+ ///
+ void OpenMainWindow();
+
+ ///
+ /// Closes the main window
+ ///
+ void CloseMainWindow();
+
+ ///
+ /// Occurs when the main window has been opened
+ ///
+ public event EventHandler? MainWindowOpened;
+
+ ///
+ /// Occurs when the main window has been closed
+ ///
+ public event EventHandler? MainWindowClosed;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowService.cs b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowService.cs
new file mode 100644
index 000000000..8a42d73a9
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/IMainWindowService.cs
@@ -0,0 +1,39 @@
+using System;
+using Artemis.UI.Shared.Services.Interfaces;
+
+namespace Artemis.UI.Shared.Services.MainWindowService
+{
+ public interface IMainWindowService : IArtemisSharedUIService
+ {
+ ///
+ /// Gets a boolean indicating whether the main window is currently open
+ ///
+ bool IsMainWindowOpen { get; }
+
+ ///
+ /// Sets up the main window provider that controls the state of the main window
+ ///
+ /// The main window provider to use to control the state of the main window
+ void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider);
+
+ ///
+ /// Opens the main window if it is not already open
+ ///
+ void OpenMainWindow();
+
+ ///
+ /// Closes the main window if it is not already closed
+ ///
+ void CloseMainWindow();
+
+ ///
+ /// Occurs when the main window has been opened
+ ///
+ public event EventHandler? MainWindowOpened;
+
+ ///
+ /// Occurs when the main window has been closed
+ ///
+ public event EventHandler? MainWindowClosed;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/MainWindowService.cs b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/MainWindowService.cs
new file mode 100644
index 000000000..d2ee398e7
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/MainWindowService/MainWindowService.cs
@@ -0,0 +1,80 @@
+using System;
+
+namespace Artemis.UI.Shared.Services.MainWindowService
+{
+ internal class MainWindowService : IMainWindowService
+ {
+ private IMainWindowProvider? _mainWindowManager;
+
+ protected virtual void OnMainWindowOpened()
+ {
+ MainWindowOpened?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnMainWindowClosed()
+ {
+ MainWindowClosed?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void SyncWithManager()
+ {
+ if (_mainWindowManager == null)
+ return;
+
+ if (IsMainWindowOpen && !_mainWindowManager.IsMainWindowOpen)
+ {
+ IsMainWindowOpen = false;
+ OnMainWindowClosed();
+ }
+
+ if (!IsMainWindowOpen && _mainWindowManager.IsMainWindowOpen)
+ {
+ IsMainWindowOpen = true;
+ OnMainWindowOpened();
+ }
+ }
+
+ private void HandleMainWindowOpened(object? sender, EventArgs e)
+ {
+ SyncWithManager();
+ }
+
+ private void HandleMainWindowClosed(object? sender, EventArgs e)
+ {
+ SyncWithManager();
+ }
+
+ public bool IsMainWindowOpen { get; private set; }
+
+ public void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider)
+ {
+ if (mainWindowProvider == null) throw new ArgumentNullException(nameof(mainWindowProvider));
+
+ if (_mainWindowManager != null)
+ {
+ _mainWindowManager.MainWindowOpened -= HandleMainWindowOpened;
+ _mainWindowManager.MainWindowClosed -= HandleMainWindowClosed;
+ }
+
+ _mainWindowManager = mainWindowProvider;
+ _mainWindowManager.MainWindowOpened += HandleMainWindowOpened;
+ _mainWindowManager.MainWindowClosed += HandleMainWindowClosed;
+
+ // Sync up with the new manager's state
+ SyncWithManager();
+ }
+
+ public void OpenMainWindow()
+ {
+ _mainWindowManager?.OpenMainWindow();
+ }
+
+ public void CloseMainWindow()
+ {
+ _mainWindowManager?.CloseMainWindow();
+ }
+
+ public event EventHandler? MainWindowOpened;
+ public event EventHandler? MainWindowClosed;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs b/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs
index 4d18cfbd0..8ef62b62c 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/WindowService/WindowService.cs
@@ -33,7 +33,7 @@ namespace Artemis.UI.Shared.Services
public void ShowWindow(object viewModel)
{
- Window parent = GetCurrentWindow();
+ Window? parent = GetCurrentWindow();
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = viewModel.GetType().Assembly.GetType(name);
@@ -50,7 +50,10 @@ namespace Artemis.UI.Shared.Services
Window window = (Window) Activator.CreateInstance(type)!;
window.DataContext = viewModel;
- window.Show(parent);
+ if (parent != null)
+ window.Show(parent);
+ else
+ window.Show();
}
public async Task ShowDialogAsync(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase
@@ -132,14 +135,14 @@ namespace Artemis.UI.Shared.Services
return new SaveFileDialogBuilder(GetCurrentWindow());
}
- public Window GetCurrentWindow()
+ public Window? GetCurrentWindow()
{
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)
{
throw new ArtemisSharedUIException("Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime.");
}
- Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow;
+ Window? parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow;
return parent;
}
}
diff --git a/src/Avalonia/Artemis.UI.Windows/App.axaml.cs b/src/Avalonia/Artemis.UI.Windows/App.axaml.cs
index cc0592702..471e65589 100644
--- a/src/Avalonia/Artemis.UI.Windows/App.axaml.cs
+++ b/src/Avalonia/Artemis.UI.Windows/App.axaml.cs
@@ -2,7 +2,6 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
-using FluentAvalonia.Styling;
using Ninject;
using ReactiveUI;
@@ -10,27 +9,23 @@ namespace Artemis.UI.Windows
{
public class App : Application
{
- private StandardKernel _kernel;
- private ApplicationStateManager _stateManager;
+ // ReSharper disable NotAccessedField.Local
+ private StandardKernel? _kernel;
+ private ApplicationStateManager? _applicationStateManager;
+ // ReSharper restore NotAccessedField.Local
public override void Initialize()
{
- _kernel = ArtemisBootstrapper.Bootstrap();
+ _kernel = ArtemisBootstrapper.Bootstrap(this);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
+ ArtemisBootstrapper.Initialized();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
- {
- ArtemisBootstrapper.ConfigureApplicationLifetime(desktop);
- AvaloniaLocator.Current.GetService().ForceNativeTitleBarToTheme(desktop.MainWindow, "Dark");
-
- _stateManager = new ApplicationStateManager(_kernel, desktop.Args);
- }
-
- base.OnFrameworkInitializationCompleted();
+ _applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs b/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs
index 18f78e866..15275c559 100644
--- a/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs
+++ b/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs
@@ -3,6 +3,8 @@ using Artemis.UI.Exceptions;
using Artemis.UI.Ninject;
using Artemis.UI.Screens.Root;
using Artemis.UI.Shared.Ninject;
+using Avalonia;
+using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Ninject;
using Splat.Ninject;
@@ -12,12 +14,14 @@ namespace Artemis.UI
public static class ArtemisBootstrapper
{
private static StandardKernel? _kernel;
+ private static Application? _application;
- public static StandardKernel Bootstrap()
+ public static StandardKernel Bootstrap(Application application)
{
- if (_kernel != null)
+ if (_application != null || _kernel != null)
throw new ArtemisUIException("UI already bootstrapped");
-
+
+ _application = application;
_kernel = new StandardKernel();
_kernel.Settings.InjectNonPublic = true;
@@ -30,12 +34,19 @@ namespace Artemis.UI
return _kernel;
}
- public static void ConfigureApplicationLifetime(IClassicDesktopStyleApplicationLifetime applicationLifetime)
+ public static void Initialized()
{
- if (_kernel == null)
+ if (_application == null || _kernel == null)
throw new ArtemisUIException("UI not yet bootstrapped");
+ if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+ return;
- applicationLifetime.MainWindow = new MainWindow {DataContext = _kernel.Get()};
+ // Don't shut down when the last window closes, we might still be active in the tray
+ desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
+ // Create the root view model that drives the UI
+ RootViewModel rootViewModel = _kernel.Get();
+ // Apply the root view model to the data context of the application so that tray icon commands work
+ _application.DataContext = rootViewModel;
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/ArtemisTrayIcon.axaml b/src/Avalonia/Artemis.UI/ArtemisTrayIcon.axaml
new file mode 100644
index 000000000..2e2530293
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/ArtemisTrayIcon.axaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
index 736298c31..878b09a36 100644
--- a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
+++ b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
@@ -34,7 +34,7 @@ namespace Artemis.UI.Ninject.Factories
public interface ISidebarVmFactory : IVmFactory
{
- SidebarViewModel SidebarViewModel(IScreen hostScreen);
+ SidebarViewModel? SidebarViewModel(IScreen hostScreen);
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
}
diff --git a/src/Avalonia/Artemis.UI/Ninject/UIModule.cs b/src/Avalonia/Artemis.UI/Ninject/UIModule.cs
index 30f910279..4461c6a66 100644
--- a/src/Avalonia/Artemis.UI/Ninject/UIModule.cs
+++ b/src/Avalonia/Artemis.UI/Ninject/UIModule.cs
@@ -3,6 +3,8 @@ using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
+using Avalonia.Platform;
+using Avalonia.Shared.PlatformSupport;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
using Ninject.Planning.Bindings.Resolvers;
@@ -17,6 +19,7 @@ namespace Artemis.UI.Ninject
throw new ArgumentNullException("Kernel shouldn't be null here.");
Kernel.Components.Add();
+ Kernel.Bind().ToConstant(new AssetLoader());
Kernel.Bind(x =>
{
diff --git a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs
index 734634975..71dac014d 100644
--- a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs
@@ -11,6 +11,7 @@ using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services;
+using Artemis.UI.Shared.Services.Interfaces;
using DynamicData;
using ReactiveUI;
diff --git a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs
index 8e6bdda11..a702c091e 100644
--- a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs
@@ -1,30 +1,172 @@
-using Artemis.Core.Services;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Artemis.Core;
+using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Root.Sidebar;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services.Interfaces;
+using Artemis.UI.Shared.Services.MainWindowService;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform;
+using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Root
{
- public class RootViewModel : ActivatableViewModelBase, IScreen
+ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvider
{
+ private readonly IClassicDesktopStyleApplicationLifetime _lifeTime;
private readonly ICoreService _coreService;
+ private readonly ISettingsService _settingsService;
+ private readonly IWindowService _windowService;
+ private readonly IAssetLoader _assetLoader;
+ private readonly ISidebarVmFactory _sidebarVmFactory;
+ private SidebarViewModel? _sidebarViewModel;
+ private TrayIcon? _trayIcon;
+ private TrayIcons? _trayIcons;
- public RootViewModel(ICoreService coreService, IRegistrationService registrationService, ISidebarVmFactory sidebarVmFactory)
+ public RootViewModel(ICoreService coreService,
+ ISettingsService settingsService,
+ IRegistrationService registrationService,
+ IWindowService windowService,
+ IMainWindowService mainWindowService,
+ IAssetLoader assetLoader,
+ ISidebarVmFactory sidebarVmFactory)
{
Router = new RoutingState();
- SidebarViewModel = sidebarVmFactory.SidebarViewModel(this);
_coreService = coreService;
- _coreService.Initialize();
+ _settingsService = settingsService;
+ _windowService = windowService;
+ _assetLoader = assetLoader;
+ _sidebarVmFactory = sidebarVmFactory;
+ _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current.ApplicationLifetime;
+ coreService.StartupArguments = _lifeTime.Args.ToList();
+ mainWindowService.ConfigureMainWindowProvider(this);
registrationService.RegisterProviders();
+
+ DisplayAccordingToSettings();
+ Task.Run(coreService.Initialize);
}
- public SidebarViewModel SidebarViewModel { get; }
+ public SidebarViewModel? SidebarViewModel
+ {
+ get => _sidebarViewModel;
+ set => this.RaiseAndSetIfChanged(ref _sidebarViewModel, value);
+ }
///
public RoutingState Router { get; }
+
+ public async Task Exit()
+ {
+ // Don't freeze the UI right after clicking
+ await Task.Delay(200);
+ Utilities.Shutdown();
+ }
+
+ private void CurrentMainWindowOnClosed(object? sender, EventArgs e)
+ {
+ _lifeTime.MainWindow = null;
+ SidebarViewModel = null;
+
+ OnMainWindowClosed();
+ }
+
+ private void DisplayAccordingToSettings()
+ {
+ bool autoRunning = _coreService.StartupArguments.Contains("--autorun");
+ bool minimized = _coreService.StartupArguments.Contains("--minimized");
+ bool showOnAutoRun = _settingsService.GetSetting("UI.ShowOnStartup", true).Value;
+
+ // Always show the tray icon if ShowOnStartup is false or the user has no way to open the main window
+ bool showTrayIcon = !showOnAutoRun || _settingsService.GetSetting("UI.ShowTrayIcon", true).Value;
+
+ if (showTrayIcon)
+ ShowTrayIcon();
+
+ if (autoRunning && !showOnAutoRun || minimized)
+ {
+ // TODO: Auto-update
+ }
+ else
+ {
+ ShowSplashScreen();
+ _coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
+ }
+ }
+
+ private void ShowSplashScreen()
+ {
+ _windowService.ShowWindow();
+ }
+
+ private void ShowTrayIcon()
+ {
+ _trayIcon = new TrayIcon {Icon = new WindowIcon(_assetLoader.Open(new Uri("avares://Artemis.UI/Assets/Images/Logo/bow.ico")))};
+ _trayIcon.Menu = (NativeMenu?) Application.Current.FindResource("TrayIconMenu");
+ _trayIcons = new TrayIcons {_trayIcon};
+ TrayIcon.SetIcons(Application.Current, _trayIcons);
+ }
+
+ private void HideTrayIcon()
+ {
+ _trayIcon?.Dispose();
+ TrayIcon.SetIcons(Application.Current, null!);
+
+ _trayIcon = null;
+ _trayIcons = null;
+ }
+
+ #region Implementation of IMainWindowProvider
+
+ ///
+ public bool IsMainWindowOpen => _lifeTime.MainWindow != null;
+
+ ///
+ public void OpenMainWindow()
+ {
+ if (_lifeTime.MainWindow == null)
+ {
+ SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this);
+ _lifeTime.MainWindow = new MainWindow {DataContext = this};
+ _lifeTime.MainWindow.Show();
+ _lifeTime.MainWindow.Closed += CurrentMainWindowOnClosed;
+ }
+
+ _lifeTime.MainWindow.Activate();
+
+ OnMainWindowOpened();
+ }
+
+ ///
+ public void CloseMainWindow()
+ {
+ _lifeTime.MainWindow?.Close();
+ }
+
+ ///
+ public event EventHandler? MainWindowOpened;
+
+ ///
+ public event EventHandler? MainWindowClosed;
+
+ protected virtual void OnMainWindowOpened()
+ {
+ MainWindowOpened?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnMainWindowClosed()
+ {
+ MainWindowClosed?.Invoke(this, EventArgs.Empty);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml b/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml
new file mode 100644
index 000000000..37211a912
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+ Artemis is initializing...
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml.cs
new file mode 100644
index 000000000..b3fed1a81
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using Avalonia;
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+using Avalonia.Threading;
+using ReactiveUI;
+
+namespace Artemis.UI.Screens.Root
+{
+ public class SplashView : ReactiveWindow
+ {
+ public SplashView()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ this.WhenActivated(disposables =>
+ {
+ Observable.FromEventPattern(x => ViewModel!.CoreService.Initialized += x, x => ViewModel!.CoreService.Initialized -= x)
+ .Subscribe(_ => Dispatcher.UIThread.Post(Close))
+ .DisposeWith(disposables);
+ });
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/Root/SplashViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/SplashViewModel.cs
new file mode 100644
index 000000000..e12ab63d6
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/Root/SplashViewModel.cs
@@ -0,0 +1,71 @@
+using System;
+using Artemis.Core;
+using Artemis.Core.Services;
+using Artemis.UI.Shared;
+using Humanizer;
+using ReactiveUI;
+
+namespace Artemis.UI.Screens.Root
+{
+ public class SplashViewModel : ViewModelBase
+ {
+ private string _status;
+
+ public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
+ {
+ CoreService = coreService;
+ _status = "Initializing Core";
+
+ pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement;
+ pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
+ pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
+ pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
+ pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginManagementEnabled;
+ pluginManagementService.PluginFeatureEnabling += PluginManagementServiceOnPluginFeatureEnabling;
+ pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled;
+ }
+
+ public ICoreService CoreService { get; }
+
+ public string Status
+ {
+ get => _status;
+ set => this.RaiseAndSetIfChanged(ref _status, value);
+ }
+
+ private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
+ {
+ Status = "Initializing UI";
+ }
+
+ private void OnPluginManagementServiceOnPluginManagementLoading(object? sender, PluginEventArgs args)
+ {
+ Status = "Loading plugin: " + args.Plugin.Info.Name;
+ }
+
+ private void PluginManagementServiceOnPluginManagementEnabled(object? sender, PluginEventArgs args)
+ {
+ Status = "Initializing UI";
+ }
+
+ private void PluginManagementServiceOnPluginManagementEnabling(object? sender, PluginEventArgs args)
+ {
+ Status = "Enabling plugin: " + args.Plugin.Info.Name;
+ }
+
+ private void PluginManagementServiceOnPluginFeatureEnabling(object? sender, PluginFeatureEventArgs e)
+ {
+ Status = "Enabling: " + e.PluginFeature.GetType().Name.Humanize();
+ }
+
+ private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
+ {
+ Status = "Initializing UI";
+ }
+
+ private void OnPluginManagementServiceOnCopyingBuildInPluginsManagement(object? sender, EventArgs args)
+ {
+ Status = "Updating built-in plugins";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Styles/Artemis.axaml b/src/Avalonia/Artemis.UI/Styles/Artemis.axaml
index daa01349c..c40b34e96 100644
--- a/src/Avalonia/Artemis.UI/Styles/Artemis.axaml
+++ b/src/Avalonia/Artemis.UI/Styles/Artemis.axaml
@@ -1,7 +1,14 @@
-
+
+
+
+
+
+ Yellow
+
+