mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
343 lines
12 KiB
C#
343 lines
12 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using System.Timers;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using Artemis.Core;
|
|
using Artemis.Core.Services;
|
|
using Artemis.UI.Events;
|
|
using Artemis.UI.Screens.Modules;
|
|
using Artemis.UI.Screens.Settings.Tabs.General;
|
|
using Artemis.UI.Screens.Sidebar;
|
|
using Artemis.UI.Screens.StartupWizard;
|
|
using Artemis.UI.Services;
|
|
using Artemis.UI.Services.Interfaces;
|
|
using Artemis.UI.Utilities;
|
|
using MaterialDesignExtensions.Controls;
|
|
using MaterialDesignThemes.Wpf;
|
|
using Ninject;
|
|
using Stylet;
|
|
|
|
namespace Artemis.UI.Screens
|
|
{
|
|
public sealed class RootViewModel : Conductor<IScreen>, IDisposable
|
|
{
|
|
private readonly IRegistrationService _builtInRegistrationService;
|
|
private readonly PluginSetting<ApplicationColorScheme> _colorScheme;
|
|
private readonly ICoreService _coreService;
|
|
private readonly IWindowManager _windowManager;
|
|
private readonly IDebugService _debugService;
|
|
private readonly IKernel _kernel;
|
|
private readonly IEventAggregator _eventAggregator;
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly Timer _frameTimeUpdateTimer;
|
|
private readonly SidebarViewModel _sidebarViewModel;
|
|
private readonly ISnackbarMessageQueue _snackbarMessageQueue;
|
|
private readonly ThemeWatcher _themeWatcher;
|
|
private readonly PluginSetting<WindowSize> _windowSize;
|
|
private bool _activeItemReady;
|
|
private string _frameTime;
|
|
private bool _lostFocus;
|
|
private ISnackbarMessageQueue _mainMessageQueue;
|
|
private MaterialWindow _window;
|
|
private string _windowTitle;
|
|
|
|
public RootViewModel(
|
|
IKernel kernel,
|
|
IEventAggregator eventAggregator,
|
|
ISettingsService settingsService,
|
|
ICoreService coreService,
|
|
IWindowManager windowManager,
|
|
IDebugService debugService,
|
|
IRegistrationService builtInRegistrationService,
|
|
ISnackbarMessageQueue snackbarMessageQueue,
|
|
SidebarViewModel sidebarViewModel)
|
|
{
|
|
_kernel = kernel;
|
|
_eventAggregator = eventAggregator;
|
|
_settingsService = settingsService;
|
|
_coreService = coreService;
|
|
_windowManager = windowManager;
|
|
_debugService = debugService;
|
|
_builtInRegistrationService = builtInRegistrationService;
|
|
_snackbarMessageQueue = snackbarMessageQueue;
|
|
_sidebarViewModel = sidebarViewModel;
|
|
|
|
_frameTimeUpdateTimer = new Timer(500);
|
|
|
|
_colorScheme = _settingsService.GetSetting("UI.ColorScheme", ApplicationColorScheme.Automatic);
|
|
_windowSize = _settingsService.GetSetting<WindowSize>("UI.RootWindowSize");
|
|
|
|
_themeWatcher = new ThemeWatcher();
|
|
ApplyColorSchemeSetting();
|
|
|
|
ActiveItem = sidebarViewModel.SelectedItem;
|
|
ActiveItemReady = true;
|
|
PinSidebar = _settingsService.GetSetting("UI.PinSidebar", false);
|
|
|
|
AssemblyInformationalVersionAttribute versionAttribute = typeof(RootViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
|
WindowTitle = $"Artemis {versionAttribute?.InformationalVersion}";
|
|
}
|
|
|
|
public PluginSetting<bool> PinSidebar { get; }
|
|
|
|
// Just a litte trick to get the non-active variant completely removed from XAML (that should probably be done in the view)
|
|
public SidebarViewModel PinnedSidebarViewModel => PinSidebar.Value ? _sidebarViewModel : null;
|
|
public SidebarViewModel DockedSidebarViewModel => PinSidebar.Value ? null : _sidebarViewModel;
|
|
|
|
public ISnackbarMessageQueue MainMessageQueue
|
|
{
|
|
get => _mainMessageQueue;
|
|
set => SetAndNotify(ref _mainMessageQueue, value);
|
|
}
|
|
|
|
public bool ActiveItemReady
|
|
{
|
|
get => _activeItemReady;
|
|
set => SetAndNotify(ref _activeItemReady, value);
|
|
}
|
|
|
|
public string WindowTitle
|
|
{
|
|
get => _windowTitle;
|
|
set => SetAndNotify(ref _windowTitle, value);
|
|
}
|
|
|
|
public string FrameTime
|
|
{
|
|
get => _frameTime;
|
|
set => SetAndNotify(ref _frameTime, value);
|
|
}
|
|
|
|
public void WindowDeactivated()
|
|
{
|
|
WindowState windowState = ((Window) View).WindowState;
|
|
if (windowState == WindowState.Minimized)
|
|
return;
|
|
|
|
_lostFocus = true;
|
|
_eventAggregator.Publish(new MainWindowFocusChangedEvent(false));
|
|
}
|
|
|
|
public void WindowActivated()
|
|
{
|
|
if (!_lostFocus)
|
|
return;
|
|
|
|
_lostFocus = false;
|
|
_eventAggregator.Publish(new MainWindowFocusChangedEvent(true));
|
|
}
|
|
|
|
public void ShowDebugger()
|
|
{
|
|
_debugService.ShowDebugger();
|
|
}
|
|
|
|
public void WindowKeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
_eventAggregator.Publish(new MainWindowKeyEvent(sender, true, e));
|
|
}
|
|
|
|
public void WindowKeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
_eventAggregator.Publish(new MainWindowKeyEvent(sender, false, e));
|
|
}
|
|
|
|
public void WindowMouseDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
_eventAggregator.Publish(new MainWindowMouseEvent(sender, true, e));
|
|
}
|
|
|
|
public void WindowMouseUp(object sender, MouseButtonEventArgs e)
|
|
{
|
|
_eventAggregator.Publish(new MainWindowMouseEvent(sender, false, e));
|
|
}
|
|
|
|
private void UpdateSidebarPinState()
|
|
{
|
|
_sidebarViewModel.IsSidebarOpen = true;
|
|
|
|
NotifyOfPropertyChange(nameof(PinnedSidebarViewModel));
|
|
NotifyOfPropertyChange(nameof(DockedSidebarViewModel));
|
|
}
|
|
|
|
private void UpdateFrameTime()
|
|
{
|
|
FrameTime = $"Frame time: {_coreService.FrameTime.TotalMilliseconds:F2} ms";
|
|
}
|
|
|
|
private void SidebarViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(_sidebarViewModel.SelectedItem) && ActiveItem != _sidebarViewModel.SelectedItem)
|
|
{
|
|
// Unless the sidebar is pinned, close it upon selecting an item
|
|
if (!PinSidebar.Value)
|
|
_sidebarViewModel.IsSidebarOpen = false;
|
|
|
|
// Don't do a fade when selecting a module because the editor is so bulky the animation slows things down
|
|
if (!(_sidebarViewModel.SelectedItem is ModuleRootViewModel))
|
|
ActiveItemReady = false;
|
|
|
|
// Allow the menu to close, it's slower but feels more responsive, funny how that works right
|
|
Execute.PostToUIThreadAsync(async () =>
|
|
{
|
|
if (PinSidebar.Value)
|
|
await Task.Delay(200);
|
|
else
|
|
await Task.Delay(400);
|
|
|
|
ActiveItem = _sidebarViewModel.SelectedItem;
|
|
ActiveItemReady = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
private void ApplyColorSchemeSetting()
|
|
{
|
|
if (_colorScheme.Value == ApplicationColorScheme.Automatic)
|
|
ApplyWindowsTheme(_themeWatcher.GetWindowsTheme());
|
|
else
|
|
ChangeMaterialColors(_colorScheme.Value);
|
|
}
|
|
|
|
private void ApplyWindowsTheme(ThemeWatcher.WindowsTheme windowsTheme)
|
|
{
|
|
if (_colorScheme.Value != ApplicationColorScheme.Automatic)
|
|
return;
|
|
|
|
if (windowsTheme == ThemeWatcher.WindowsTheme.Dark)
|
|
ChangeMaterialColors(ApplicationColorScheme.Dark);
|
|
else
|
|
ChangeMaterialColors(ApplicationColorScheme.Light);
|
|
}
|
|
|
|
private void ChangeMaterialColors(ApplicationColorScheme colorScheme)
|
|
{
|
|
PaletteHelper paletteHelper = new PaletteHelper();
|
|
ITheme theme = paletteHelper.GetTheme();
|
|
theme.SetBaseTheme(colorScheme == ApplicationColorScheme.Dark ? Theme.Dark : Theme.Light);
|
|
paletteHelper.SetTheme(theme);
|
|
|
|
MaterialDesignExtensions.Themes.PaletteHelper extensionsPaletteHelper = new MaterialDesignExtensions.Themes.PaletteHelper();
|
|
extensionsPaletteHelper.SetLightDark(colorScheme == ApplicationColorScheme.Dark);
|
|
}
|
|
|
|
private void OnFrameTimeUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
|
|
{
|
|
UpdateFrameTime();
|
|
}
|
|
|
|
private void ThemeWatcherOnThemeChanged(object sender, WindowsThemeEventArgs e)
|
|
{
|
|
ApplyWindowsTheme(e.Theme);
|
|
}
|
|
|
|
private void ColorSchemeOnSettingChanged(object sender, EventArgs e)
|
|
{
|
|
ApplyColorSchemeSetting();
|
|
}
|
|
|
|
private void PinSidebarOnSettingChanged(object sender, EventArgs e)
|
|
{
|
|
UpdateSidebarPinState();
|
|
}
|
|
|
|
#region IDisposable
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
_frameTimeUpdateTimer?.Dispose();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Overrides of Screen
|
|
|
|
protected override void OnViewLoaded()
|
|
{
|
|
MaterialWindow window = (MaterialWindow) View;
|
|
if (_windowSize.Value != null)
|
|
{
|
|
_windowSize.Value.ApplyToWindow(window);
|
|
}
|
|
else
|
|
{
|
|
_windowSize.Value = new WindowSize();
|
|
_windowSize.Value.ApplyFromWindow(window);
|
|
}
|
|
|
|
base.OnViewLoaded();
|
|
}
|
|
|
|
protected override void OnInitialActivate()
|
|
{
|
|
MainMessageQueue = _snackbarMessageQueue;
|
|
UpdateFrameTime();
|
|
|
|
_builtInRegistrationService.RegisterBuiltInDataModelDisplays();
|
|
_builtInRegistrationService.RegisterBuiltInDataModelInputs();
|
|
_builtInRegistrationService.RegisterBuiltInPropertyEditors();
|
|
|
|
_frameTimeUpdateTimer.Elapsed += OnFrameTimeUpdateTimerOnElapsed;
|
|
_colorScheme.SettingChanged += ColorSchemeOnSettingChanged;
|
|
_themeWatcher.ThemeChanged += ThemeWatcherOnThemeChanged;
|
|
_sidebarViewModel.PropertyChanged += SidebarViewModelOnPropertyChanged;
|
|
PinSidebar.SettingChanged += PinSidebarOnSettingChanged;
|
|
|
|
_frameTimeUpdateTimer.Start();
|
|
|
|
_window = (MaterialWindow) View;
|
|
|
|
PluginSetting<bool> setupWizardCompleted = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
|
|
if (!setupWizardCompleted.Value)
|
|
ShowSetupWizard();
|
|
|
|
base.OnInitialActivate();
|
|
}
|
|
|
|
private void ShowSetupWizard()
|
|
{
|
|
_windowManager.ShowDialog(_kernel.Get<StartupWizardViewModel>());
|
|
}
|
|
|
|
protected override void OnClose()
|
|
{
|
|
// Ensure no element with focus can leak, if we don't do this the root VM is retained by Window.EffectiveValues
|
|
// https://stackoverflow.com/a/30864434
|
|
Keyboard.ClearFocus();
|
|
|
|
MainMessageQueue = null;
|
|
_frameTimeUpdateTimer.Stop();
|
|
|
|
_windowSize.Value ??= new WindowSize();
|
|
_windowSize.Value.ApplyFromWindow(_window);
|
|
_windowSize.Save();
|
|
|
|
_frameTimeUpdateTimer.Elapsed -= OnFrameTimeUpdateTimerOnElapsed;
|
|
_colorScheme.SettingChanged -= ColorSchemeOnSettingChanged;
|
|
_themeWatcher.ThemeChanged -= ThemeWatcherOnThemeChanged;
|
|
_sidebarViewModel.PropertyChanged -= SidebarViewModelOnPropertyChanged;
|
|
PinSidebar.SettingChanged -= PinSidebarOnSettingChanged;
|
|
|
|
_sidebarViewModel.Dispose();
|
|
|
|
// Lets force the GC to run after closing the window so it is obvious to users watching task manager
|
|
// that closing the UI will decrease the memory footprint of the application.
|
|
Task.Run(async () =>
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(15));
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
});
|
|
|
|
|
|
base.OnClose();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |