From fd942dab25edc9ee432a10e601dee551bedb69a1 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 18 Nov 2019 20:11:17 +0100 Subject: [PATCH] Started work on profiles --- src/Artemis.Core/Artemis.Core.csproj | 5 +- src/Artemis.Core/Models/Profile/Profile.cs | 26 +- .../Plugins/Abstract/ModuleViewModel.cs | 7 +- .../Plugins/Abstract/ProfileModule.cs | 13 + src/Artemis.Core/Services/CoreService.cs | 1 + .../Storage/Interfaces/IProfileService.cs | 14 ++ .../{ => Interfaces}/ISurfaceService.cs | 2 +- .../ProfileService.cs} | 29 ++- .../Services/Storage/SurfaceService.cs | 1 + .../GeneralModule.cs | 2 +- src/Artemis.Storage/Entities/ProfileEntity.cs | 1 + .../Repositories/ProfileRepository.cs | 5 + src/Artemis.UI/Artemis.UI.csproj | 6 + .../IProfileEditorViewModelFactory.cs | 2 +- src/Artemis.UI/Ninject/UiModule.cs | 10 + .../Screens/Module/ModuleRootView.xaml | 2 +- .../Screens/Module/ModuleRootViewModel.cs | 19 +- .../DisplayConditionsViewModel.cs | 2 +- .../ElementPropertiesViewModel.cs | 2 +- .../LayerElements/LayerElementsViewModel.cs | 2 +- .../ProfileEditor/Layers/LayersViewModel.cs | 2 +- .../ProfileEditorPanelViewModel.cs | 8 + .../ProfileEditor/ProfileEditorView.xaml | 188 ++++----------- .../ProfileEditor/ProfileEditorViewModel.cs | 227 ++++-------------- .../Visualization/ProfileView.xaml | 152 ++++++++++++ .../Visualization/ProfileViewModel.cs | 220 +++++++++++++++++ .../Screens/Settings/SettingsViewModel.cs | 1 + .../SurfaceEditor/SurfaceEditorViewModel.cs | 1 + 28 files changed, 575 insertions(+), 375 deletions(-) create mode 100644 src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs rename src/Artemis.Core/Services/Storage/{ => Interfaces}/ISurfaceService.cs (97%) rename src/Artemis.Core/Services/{StorageService.cs => Storage/ProfileService.cs} (53%) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorPanelViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index b77c9e7fd..2080d665d 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -192,8 +192,9 @@ - - + + + diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index f8a76cdf1..b7d871f5c 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -11,9 +11,21 @@ namespace Artemis.Core.Models.Profile { public class Profile : IProfileElement { - private Profile(PluginInfo pluginInfo) + internal Profile(PluginInfo pluginInfo, ProfileEntity profileEntity, IPluginService pluginService) { PluginInfo = pluginInfo; + Name = profileEntity.Name; + + // Populate the profile starting at the root, the rest is populated recursively + Children = new List {Folder.FromFolderEntity(this, profileEntity.RootFolder, pluginService)}; + } + + internal Profile(PluginInfo pluginInfo, string name) + { + PluginInfo = pluginInfo; + Name = name; + + Children = new List(); } public PluginInfo PluginInfo { get; } @@ -46,18 +58,6 @@ namespace Artemis.Core.Models.Profile } } - public static Profile FromProfileEntity(PluginInfo pluginInfo, ProfileEntity profileEntity, IPluginService pluginService) - { - var profile = new Profile(pluginInfo) {Name = profileEntity.Name}; - lock (profile) - { - // Populate the profile starting at the root, the rest is populated recursively - profile.Children.Add(Folder.FromFolderEntity(profile, profileEntity.RootFolder, pluginService)); - - return profile; - } - } - public void Activate() { lock (this) diff --git a/src/Artemis.Core/Plugins/Abstract/ModuleViewModel.cs b/src/Artemis.Core/Plugins/Abstract/ModuleViewModel.cs index ead43c4e7..baacbe5bf 100644 --- a/src/Artemis.Core/Plugins/Abstract/ModuleViewModel.cs +++ b/src/Artemis.Core/Plugins/Abstract/ModuleViewModel.cs @@ -4,13 +4,12 @@ namespace Artemis.Core.Plugins.Abstract { public abstract class ModuleViewModel : Screen { - protected ModuleViewModel(Module module, string name) + protected ModuleViewModel(Module module, string displayName) { Module = module; - Name = name; + DisplayName = displayName; } - - public string Name { get; } + public Module Module { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index 8e0a8992e..a72e7bb5d 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -46,6 +46,19 @@ namespace Artemis.Core.Plugins.Abstract ActiveProfile = profile; ActiveProfile.Activate(); } + + OnActiveProfileChanged(); } + + #region Events + + public event EventHandler ActiveProfileChanged; + + protected virtual void OnActiveProfileChanged() + { + ActiveProfileChanged?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index ec41cb386..2e1bdd5ac 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -5,6 +5,7 @@ using Artemis.Core.Exceptions; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage; +using Artemis.Core.Services.Storage.Interfaces; using RGB.NET.Core; using Serilog; using Color = System.Drawing.Color; diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs new file mode 100644 index 000000000..65db1b113 --- /dev/null +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Artemis.Core.Models.Profile; +using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Services.Interfaces; + +namespace Artemis.Core.Services.Storage.Interfaces +{ + public interface IProfileService : IArtemisService + { + Task> GetProfiles(ProfileModule module); + Task GetActiveProfile(ProfileModule module); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ISurfaceService.cs b/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs similarity index 97% rename from src/Artemis.Core/Services/Storage/ISurfaceService.cs rename to src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs index 50cea7b92..440a228a4 100644 --- a/src/Artemis.Core/Services/Storage/ISurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs @@ -4,7 +4,7 @@ using Artemis.Core.Events; using Artemis.Core.Models.Surface; using Artemis.Core.Services.Interfaces; -namespace Artemis.Core.Services.Storage +namespace Artemis.Core.Services.Storage.Interfaces { public interface ISurfaceService : IArtemisService { diff --git a/src/Artemis.Core/Services/StorageService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs similarity index 53% rename from src/Artemis.Core/Services/StorageService.cs rename to src/Artemis.Core/Services/Storage/ProfileService.cs index fcb4891a2..89de07f78 100644 --- a/src/Artemis.Core/Services/StorageService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -1,36 +1,46 @@ using System.Collections.Generic; using System.Threading.Tasks; using Artemis.Core.Models.Profile; -using Artemis.Core.Plugins.Models; +using Artemis.Core.Plugins.Abstract; using Artemis.Core.Services.Interfaces; +using Artemis.Core.Services.Storage.Interfaces; using Artemis.Storage.Repositories; -namespace Artemis.Core.Services +namespace Artemis.Core.Services.Storage { /// /// Provides access to profile storage /// - public class StorageService : IStorageService + public class ProfileService : IProfileService { private readonly IPluginService _pluginService; private readonly ProfileRepository _profileRepository; - internal StorageService(IPluginService pluginService) + internal ProfileService(IPluginService pluginService) { _pluginService = pluginService; _profileRepository = new ProfileRepository(); } - public async Task> GetModuleProfiles(PluginInfo pluginInfo) + public async Task> GetProfiles(ProfileModule module) { - var profileEntities = await _profileRepository.GetByPluginGuidAsync(pluginInfo.Guid); + var profileEntities = await _profileRepository.GetByPluginGuidAsync(module.PluginInfo.Guid); var profiles = new List(); foreach (var profileEntity in profileEntities) - profiles.Add(Profile.FromProfileEntity(pluginInfo, profileEntity, _pluginService)); + profiles.Add(Profile.FromProfileEntity(module.PluginInfo, profileEntity, _pluginService)); return profiles; } + public async Task GetActiveProfile(ProfileModule module) + { + var profileEntity = await _profileRepository.GetActiveProfileByPluginGuidAsync(module.PluginInfo.Guid); + if (profileEntity == null) + return null; + + return Profile.FromProfileEntity(module.PluginInfo, profileEntity, _pluginService); + } + public async Task SaveProfile(Profile profile) { // Find a matching profile entity to update @@ -40,9 +50,4 @@ namespace Artemis.Core.Services await _profileRepository.SaveAsync(); } } - - public interface IStorageService - { - Task> GetModuleProfiles(PluginInfo pluginInfo); - } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index f57a7764e..9ab0d4c90 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -8,6 +8,7 @@ using Artemis.Core.Extensions; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; +using Artemis.Core.Services.Storage.Interfaces; using Artemis.Storage.Repositories.Interfaces; using RGB.NET.Core; using Serilog; diff --git a/src/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Artemis.Plugins.Modules.General/GeneralModule.cs index 4547ccfef..54fd49f59 100644 --- a/src/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -11,7 +11,7 @@ using Device = Artemis.Core.Models.Surface.Device; namespace Artemis.Plugins.Modules.General { - public class GeneralModule : Module + public class GeneralModule : ProfileModule { private readonly ColorBlend _rainbowColorBlend; private readonly PluginSettings _settings; diff --git a/src/Artemis.Storage/Entities/ProfileEntity.cs b/src/Artemis.Storage/Entities/ProfileEntity.cs index 1e39e3b30..cfe8a98ec 100644 --- a/src/Artemis.Storage/Entities/ProfileEntity.cs +++ b/src/Artemis.Storage/Entities/ProfileEntity.cs @@ -11,6 +11,7 @@ namespace Artemis.Storage.Entities public Guid PluginGuid { get; set; } public string Name { get; set; } + public bool IsActive { get; set; } public int RootFolderId { get; set; } public virtual FolderEntity RootFolder { get; set; } diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs index c4b0d544a..084cb236c 100644 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs @@ -28,6 +28,11 @@ namespace Artemis.Storage.Repositories return await _dbContext.Profiles.Where(p => p.PluginGuid == pluginGuid).ToListAsync(); } + public async Task GetActiveProfileByPluginGuidAsync(Guid pluginGuid) + { + return await _dbContext.Profiles.FirstOrDefaultAsync(p => p.PluginGuid == pluginGuid && p.IsActive); + } + public async Task GetByGuidAsync(string guid) { return await _dbContext.Profiles.FirstOrDefaultAsync(p => p.Guid == guid); diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index b1c65bcf8..ab4a6146f 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -181,6 +181,8 @@ + + @@ -245,6 +247,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/Artemis.UI/Ninject/Factories/IProfileEditorViewModelFactory.cs b/src/Artemis.UI/Ninject/Factories/IProfileEditorViewModelFactory.cs index 6ed149c55..9875bea54 100644 --- a/src/Artemis.UI/Ninject/Factories/IProfileEditorViewModelFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IProfileEditorViewModelFactory.cs @@ -5,6 +5,6 @@ namespace Artemis.UI.Ninject.Factories { public interface IProfileEditorViewModelFactory { - ProfileEditorViewModel CreateModuleViewModel(Module module); + ProfileEditorViewModel CreateModuleViewModel(ProfileModule module); } } \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index fe53db1a8..5d9869b46 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -1,5 +1,6 @@ using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens; +using Artemis.UI.Screens.Module.ProfileEditor; using Artemis.UI.Services.Interfaces; using Artemis.UI.Stylet; using Artemis.UI.ViewModels.Dialogs; @@ -38,6 +39,15 @@ namespace Artemis.UI.Ninject Bind().ToFactory(); Bind().ToFactory(); + // Bind profile editor VMs + Kernel.Bind(x => + { + x.FromThisAssembly() + .SelectAllClasses() + .InheritedFrom() + .BindAllBaseClasses(); + }); + // Bind all UI services as singletons Kernel.Bind(x => { diff --git a/src/Artemis.UI/Screens/Module/ModuleRootView.xaml b/src/Artemis.UI/Screens/Module/ModuleRootView.xaml index 0a0582796..842c06649 100644 --- a/src/Artemis.UI/Screens/Module/ModuleRootView.xaml +++ b/src/Artemis.UI/Screens/Module/ModuleRootView.xaml @@ -14,7 +14,7 @@ - + diff --git a/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs b/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs index a299832c5..9efa15d3d 100644 --- a/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs @@ -1,11 +1,12 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Artemis.Core.Plugins.Abstract; using Artemis.UI.Ninject.Factories; using Stylet; namespace Artemis.UI.Screens.Module { - public class ModuleRootViewModel : Conductor.Collection.OneActive + public class ModuleRootViewModel : Conductor.Collection.OneActive { private readonly IProfileEditorViewModelFactory _profileEditorViewModelFactory; @@ -27,14 +28,16 @@ namespace Artemis.UI.Screens.Module await Task.Delay(400); // Create the profile editor and module VMs - var profileEditor = _profileEditorViewModelFactory.CreateModuleViewModel(Module); + if (Module is ProfileModule profileModule) + { + var profileEditor = _profileEditorViewModelFactory.CreateModuleViewModel(profileModule); + Items.Add(profileEditor); + } + var moduleViewModels = Module.GetViewModels(); - - Items.Add(profileEditor); Items.AddRange(moduleViewModels); - - // Activate the profile editor - ActiveItem = profileEditor; + + ActiveItem = Items.FirstOrDefault(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 7bf9b1a8e..9f8ca9b7f 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { - class DisplayConditionsViewModel + public class DisplayConditionsViewModel : ProfileEditorPanelViewModel { } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ElementProperties/ElementPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ElementProperties/ElementPropertiesViewModel.cs index 5f2db30a7..463f037b8 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ElementProperties/ElementPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ElementProperties/ElementPropertiesViewModel.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Artemis.UI.Screens.Module.ProfileEditor.ElementProperties { - class ElementPropertiesViewModel + public class ElementPropertiesViewModel : ProfileEditorPanelViewModel { } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerElements/LayerElementsViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerElements/LayerElementsViewModel.cs index 594150b1b..9200ff227 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerElements/LayerElementsViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerElements/LayerElementsViewModel.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerElements { - public class LayerElementsViewModel + public class LayerElementsViewModel : ProfileEditorPanelViewModel { } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Layers/LayersViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Layers/LayersViewModel.cs index 6efa9ec85..58c4de160 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Layers/LayersViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Layers/LayersViewModel.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Artemis.UI.Screens.Module.ProfileEditor.Layers { - public class LayersViewModel + public class LayersViewModel : ProfileEditorPanelViewModel { } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorPanelViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorPanelViewModel.cs new file mode 100644 index 000000000..4d58d279f --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorPanelViewModel.cs @@ -0,0 +1,8 @@ +using Stylet; + +namespace Artemis.UI.Screens.Module.ProfileEditor +{ + public class ProfileEditorPanelViewModel : Screen + { + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml index 21c6f6617..199b7e00e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorView.xaml @@ -17,31 +17,6 @@ - - @@ -58,129 +33,52 @@ - + The profile defines what colors the LEDs will have. Multiple groups of LEDs are defined into layers. On these layers you can apply effects. - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Initializing LED visualization... - - - - - + @@ -194,13 +92,13 @@ - Right top + - Right bottom + @@ -214,13 +112,13 @@ - Bottom left + - Bottom right + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs index 31000990c..5f5a49369 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs @@ -1,220 +1,81 @@ using System; -using System.Collections.ObjectModel; +using System.Collections.Generic; using System.Linq; -using System.Windows; -using System.Windows.Input; -using System.Windows.Media; -using Artemis.Core.Events; -using Artemis.Core.Models.Surface; +using System.Threading.Tasks; +using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Abstract; -using Artemis.Core.Services; -using Artemis.Core.Services.Storage; +using Artemis.Core.Services.Storage.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions; +using Artemis.UI.Screens.Module.ProfileEditor.ElementProperties; +using Artemis.UI.Screens.Module.ProfileEditor.LayerElements; +using Artemis.UI.Screens.Module.ProfileEditor.Layers; using Artemis.UI.Screens.Module.ProfileEditor.Visualization; -using Artemis.UI.Screens.Shared; -using Artemis.UI.Screens.SurfaceEditor; -using RGB.NET.Core; using Stylet; -using Point = System.Windows.Point; namespace Artemis.UI.Screens.Module.ProfileEditor { - public class ProfileEditorViewModel : ModuleViewModel + public class ProfileEditorViewModel : Conductor.Collection.AllActive { - private readonly TimerUpdateTrigger _updateTrigger; + private readonly IProfileService _profileService; - public ProfileEditorViewModel(Core.Plugins.Abstract.Module module, ISurfaceService surfaceService, ISettingsService settingsService) : base(module, "Profile Editor") + public ProfileEditorViewModel(ProfileModule module, ICollection viewModels, IProfileService profileService) { - surfaceService.ActiveSurfaceConfigurationChanged += OnActiveSurfaceConfigurationChanged; - Devices = new ObservableCollection(); - Execute.OnUIThread(() => - { - SelectionRectangle = new RectangleGeometry(); - PanZoomViewModel = new PanZoomViewModel(); - }); + _profileService = profileService; - ApplySurfaceConfiguration(surfaceService.ActiveSurface); + DisplayName = "Profile editor"; + Module = module; - // Borrow RGB.NET's update trigger but limit the FPS - var targetFpsSetting = settingsService.GetSetting("TargetFrameRate", 25); - var editorTargetFpsSetting = settingsService.GetSetting("EditorTargetFrameRate", 15); - var targetFps = Math.Min(targetFpsSetting.Value, editorTargetFpsSetting.Value); - _updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFps}; - _updateTrigger.Update += UpdateLeds; + DisplayConditionsViewModel = (DisplayConditionsViewModel) viewModels.First(vm => vm is DisplayConditionsViewModel); + ElementPropertiesViewModel = (ElementPropertiesViewModel) viewModels.First(vm => vm is ElementPropertiesViewModel); + LayerElementsViewModel = (LayerElementsViewModel) viewModels.First(vm => vm is LayerElementsViewModel); + LayersViewModel = (LayersViewModel) viewModels.First(vm => vm is LayersViewModel); + ProfileViewModel = (ProfileViewModel) viewModels.First(vm => vm is ProfileViewModel); + + Items.AddRange(viewModels); + + module.ActiveProfileChanged += ModuleOnActiveProfileChanged; } - public ObservableCollection Devices { get; set; } - public bool IsInitializing { get; private set; } - public RectangleGeometry SelectionRectangle { get; set; } - public PanZoomViewModel PanZoomViewModel { get; set; } + public Core.Plugins.Abstract.Module Module { get; } + public DisplayConditionsViewModel DisplayConditionsViewModel { get; } + public ElementPropertiesViewModel ElementPropertiesViewModel { get; } + public LayerElementsViewModel LayerElementsViewModel { get; } + public LayersViewModel LayersViewModel { get; } + public ProfileViewModel ProfileViewModel { get; } - private void OnActiveSurfaceConfigurationChanged(object sender, SurfaceConfigurationEventArgs e) + public BindableCollection Profiles { get; set; } + public Profile SelectedProfile { get; set; } + public bool CanDeleteActiveProfile => SelectedProfile != null; + + public async Task AddProfile() { - ApplySurfaceConfiguration(e.Surface); } - private void UpdateLeds(object sender, CustomUpdateData customUpdateData) + public async Task DeleteActiveProfile() { - if (IsInitializing) - IsInitializing = Devices.Any(d => !d.AddedLeds); - - foreach (var profileDeviceViewModel in Devices) - profileDeviceViewModel.Update(); } - private void ApplySurfaceConfiguration(Surface surface) + private void ModuleOnActiveProfileChanged(object sender, EventArgs e) { - // Make sure all devices have an up-to-date VM - foreach (var surfaceDeviceConfiguration in surface.Devices) - { - // Create VMs for missing devices - var viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice); - if (viewModel == null) - { - // Create outside the UI thread to avoid slowdowns as much as possible - var profileDeviceViewModel = new ProfileDeviceViewModel(surfaceDeviceConfiguration); - Execute.OnUIThread(() => - { - // Gotta call IsInitializing on the UI thread or its never gets picked up - IsInitializing = true; - lock (Devices) - { - Devices.Add(profileDeviceViewModel); - } - }); - } - // Update existing devices - else - viewModel.Device = surfaceDeviceConfiguration; - } - - // Sort the devices by ZIndex - Execute.OnUIThread(() => - { - lock (Devices) - { - foreach (var device in Devices.OrderBy(d => d.ZIndex).ToList()) - Devices.Move(Devices.IndexOf(device), device.ZIndex - 1); - } - }); + SelectedProfile = ((ProfileModule) Module).ActiveProfile; } protected override void OnActivate() { - _updateTrigger.Start(); + Task.Run(LoadProfilesAsync); base.OnActivate(); } - protected override void OnDeactivate() + private async Task LoadProfilesAsync() { - _updateTrigger.Stop(); - base.OnDeactivate(); - } + var profiles = await _profileService.GetProfiles((ProfileModule) Module); + Profiles.Clear(); + Profiles.AddRange(profiles); - #region Selection - - private MouseDragStatus _mouseDragStatus; - private Point _mouseDragStartPoint; - - // ReSharper disable once UnusedMember.Global - Called from view - public void EditorGridMouseClick(object sender, MouseEventArgs e) - { - if (IsPanKeyDown()) - return; - - var position = e.GetPosition((IInputElement) sender); - var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e); - if (e.LeftButton == MouseButtonState.Pressed) - StartMouseDrag(position, relative); - else - StopMouseDrag(position); - } - - // ReSharper disable once UnusedMember.Global - Called from view - public void EditorGridMouseMove(object sender, MouseEventArgs e) - { - // If holding down Ctrl, pan instead of move/select - if (IsPanKeyDown()) + if (!profiles.Any()) { - Pan(sender, e); - return; - } - - var position = e.GetPosition((IInputElement) sender); - if (_mouseDragStatus == MouseDragStatus.Selecting) - UpdateSelection(position); - } - - private void StartMouseDrag(Point position, Point relative) - { - _mouseDragStatus = MouseDragStatus.Selecting; - _mouseDragStartPoint = position; - - // Any time dragging starts, start with a new rect - SelectionRectangle.Rect = new Rect(); - } - - private void StopMouseDrag(Point position) - { - var selectedRect = new Rect(_mouseDragStartPoint, position); - // TODO: Select LEDs - - Mouse.OverrideCursor = null; - _mouseDragStatus = MouseDragStatus.None; - } - - private void UpdateSelection(Point position) - { - if (IsPanKeyDown()) - return; - - lock (Devices) - { - var selectedRect = new Rect(_mouseDragStartPoint, position); - SelectionRectangle.Rect = selectedRect; - - // TODO: Highlight LEDs + var profile = new Profile(); } } - - #endregion - - #region Panning and zooming - - public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e) - { - PanZoomViewModel.ProcessMouseScroll(sender, e); - } - - public void EditorGridKeyDown(object sender, KeyEventArgs e) - { - if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown) - Mouse.OverrideCursor = Cursors.ScrollAll; - } - - public void EditorGridKeyUp(object sender, KeyEventArgs e) - { - if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp) - Mouse.OverrideCursor = null; - } - - public void Pan(object sender, MouseEventArgs e) - { - PanZoomViewModel.ProcessMouseMove(sender, e); - - // Empty the selection rect since it's shown while mouse is down - SelectionRectangle.Rect = Rect.Empty; - } - - public void ResetZoomAndPan() - { - PanZoomViewModel.Reset(); - } - - private bool IsPanKeyDown() - { - return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml new file mode 100644 index 000000000..9a62e9eda --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Initializing LED visualization... + + + + + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs new file mode 100644 index 000000000..24ddf9d50 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using Artemis.Core.Events; +using Artemis.Core.Models.Surface; +using Artemis.Core.Services; +using Artemis.Core.Services.Storage; +using Artemis.Core.Services.Storage.Interfaces; +using Artemis.UI.Screens.Shared; +using Artemis.UI.Screens.SurfaceEditor; +using RGB.NET.Core; +using Stylet; +using Point = System.Windows.Point; + +namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization +{ + public class ProfileViewModel : ProfileEditorPanelViewModel + { + private readonly TimerUpdateTrigger _updateTrigger; + + public ProfileViewModel(ISurfaceService surfaceService, ISettingsService settingsService) + { + Devices = new ObservableCollection(); + Execute.OnUIThread(() => + { + SelectionRectangle = new RectangleGeometry(); + PanZoomViewModel = new PanZoomViewModel(); + }); + + ApplySurfaceConfiguration(surfaceService.ActiveSurface); + + // Borrow RGB.NET's update trigger but limit the FPS + var targetFpsSetting = settingsService.GetSetting("TargetFrameRate", 25); + var editorTargetFpsSetting = settingsService.GetSetting("EditorTargetFrameRate", 15); + var targetFps = Math.Min(targetFpsSetting.Value, editorTargetFpsSetting.Value); + _updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFps}; + _updateTrigger.Update += UpdateLeds; + + surfaceService.ActiveSurfaceConfigurationChanged += OnActiveSurfaceConfigurationChanged; + } + + public bool IsInitializing { get; private set; } + public ObservableCollection Devices { get; set; } + public RectangleGeometry SelectionRectangle { get; set; } + public PanZoomViewModel PanZoomViewModel { get; set; } + + private void OnActiveSurfaceConfigurationChanged(object sender, SurfaceConfigurationEventArgs e) + { + ApplySurfaceConfiguration(e.Surface); + } + + private void ApplySurfaceConfiguration(Surface surface) + { + // Make sure all devices have an up-to-date VM + foreach (var surfaceDeviceConfiguration in surface.Devices) + { + // Create VMs for missing devices + var viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice); + if (viewModel == null) + { + // Create outside the UI thread to avoid slowdowns as much as possible + var profileDeviceViewModel = new ProfileDeviceViewModel(surfaceDeviceConfiguration); + Execute.OnUIThread(() => + { + // Gotta call IsInitializing on the UI thread or its never gets picked up + IsInitializing = true; + lock (Devices) + { + Devices.Add(profileDeviceViewModel); + } + }); + } + // Update existing devices + else + viewModel.Device = surfaceDeviceConfiguration; + } + + // Sort the devices by ZIndex + Execute.OnUIThread(() => + { + lock (Devices) + { + foreach (var device in Devices.OrderBy(d => d.ZIndex).ToList()) + Devices.Move(Devices.IndexOf(device), device.ZIndex - 1); + } + }); + } + + private void UpdateLeds(object sender, CustomUpdateData customUpdateData) + { + if (IsInitializing) + IsInitializing = Devices.Any(d => !d.AddedLeds); + + foreach (var profileDeviceViewModel in Devices) + profileDeviceViewModel.Update(); + } + + protected override void OnActivate() + { + _updateTrigger.Start(); + base.OnActivate(); + } + + protected override void OnDeactivate() + { + _updateTrigger.Stop(); + base.OnDeactivate(); + } + + #region Selection + + private MouseDragStatus _mouseDragStatus; + private System.Windows.Point _mouseDragStartPoint; + + // ReSharper disable once UnusedMember.Global - Called from view + public void EditorGridMouseClick(object sender, MouseEventArgs e) + { + if (IsPanKeyDown()) + return; + + var position = e.GetPosition((IInputElement)sender); + var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e); + if (e.LeftButton == MouseButtonState.Pressed) + StartMouseDrag(position, relative); + else + StopMouseDrag(position); + } + + // ReSharper disable once UnusedMember.Global - Called from view + public void EditorGridMouseMove(object sender, MouseEventArgs e) + { + // If holding down Ctrl, pan instead of move/select + if (IsPanKeyDown()) + { + Pan(sender, e); + return; + } + + var position = e.GetPosition((IInputElement)sender); + if (_mouseDragStatus == MouseDragStatus.Selecting) + UpdateSelection(position); + } + + private void StartMouseDrag(System.Windows.Point position, System.Windows.Point relative) + { + _mouseDragStatus = MouseDragStatus.Selecting; + _mouseDragStartPoint = position; + + // Any time dragging starts, start with a new rect + SelectionRectangle.Rect = new Rect(); + } + + private void StopMouseDrag(System.Windows.Point position) + { + var selectedRect = new Rect(_mouseDragStartPoint, position); + // TODO: Select LEDs + + Mouse.OverrideCursor = null; + _mouseDragStatus = MouseDragStatus.None; + } + + private void UpdateSelection(Point position) + { + if (IsPanKeyDown()) + return; + + lock (Devices) + { + var selectedRect = new Rect(_mouseDragStartPoint, position); + SelectionRectangle.Rect = selectedRect; + + // TODO: Highlight LEDs + } + } + + #endregion + + #region Panning and zooming + + public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e) + { + PanZoomViewModel.ProcessMouseScroll(sender, e); + } + + public void EditorGridKeyDown(object sender, KeyEventArgs e) + { + if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown) + Mouse.OverrideCursor = Cursors.ScrollAll; + } + + public void EditorGridKeyUp(object sender, KeyEventArgs e) + { + if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp) + Mouse.OverrideCursor = null; + } + + public void Pan(object sender, MouseEventArgs e) + { + PanZoomViewModel.ProcessMouseMove(sender, e); + + // Empty the selection rect since it's shown while mouse is down + SelectionRectangle.Rect = Rect.Empty; + } + + public void ResetZoomAndPan() + { + PanZoomViewModel.Reset(); + } + + private bool IsPanKeyDown() + { + return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs index cf19d95c0..3a73b9924 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs @@ -1,6 +1,7 @@ using Artemis.Core.Services; using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage; +using Artemis.Core.Services.Storage.Interfaces; using Artemis.UI.Screens.Settings.Debug; using Artemis.UI.Screens.Settings.Tabs.Devices; using Ninject; diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index f78736aa1..1931fc00e 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -9,6 +9,7 @@ using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Models; using Artemis.Core.Services; using Artemis.Core.Services.Storage; +using Artemis.Core.Services.Storage.Interfaces; using Artemis.UI.Screens.Shared; using Artemis.UI.Screens.SurfaceEditor.Dialogs; using Artemis.UI.Screens.SurfaceEditor.Visualization;