diff --git a/.gitignore b/.gitignore index 2a7c213a..63f1c47a 100644 --- a/.gitignore +++ b/.gitignore @@ -351,4 +351,5 @@ ASALocalRun/ .localhistory/ # BeatPulse healthcheck temp database -healthchecksdb \ No newline at end of file +healthchecksdb +.vscode/settings.json diff --git a/userinterface/App.axaml.cs b/userinterface/App.axaml.cs index 1db2efbc..fc839a04 100644 --- a/userinterface/App.axaml.cs +++ b/userinterface/App.axaml.cs @@ -2,9 +2,12 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using System; using userinterface.ViewModels; using userinterface.Views; using userspace_backend; +using userspace_backend.IO; using DATA = userspace_backend.Data; namespace userinterface; @@ -18,7 +21,24 @@ public override void Initialize() public override void OnFrameworkInitializationCompleted() { - BackEnd backEnd = new BackEnd(BootstrapBackEnd()); + // Create the DI container + ServiceCollection services = new ServiceCollection(); + + // Register BackEndLoader first with settings directory + string settingsDirectory = System.AppDomain.CurrentDomain.BaseDirectory; + services.AddSingleton(sp => + { + var devicesRW = sp.GetRequiredService(); + var mappingsRW = sp.GetRequiredService(); + var profileRW = sp.GetRequiredService(); + return new BackEndLoader(settingsDirectory, devicesRW, mappingsRW, profileRW); + }); + + // Compose all other services + IServiceProvider serviceProvider = BackEndComposer.Compose(services); + + // Resolve and initialize BackEnd + IBackEnd backEnd = serviceProvider.GetRequiredService(); backEnd.Load(); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) @@ -44,7 +64,11 @@ protected Bootstrapper BootstrapBackEnd() { return new Bootstrapper() { - BackEndLoader = new BackEndLoader(System.AppDomain.CurrentDomain.BaseDirectory), + BackEndLoader = new BackEndLoader( + System.AppDomain.CurrentDomain.BaseDirectory, + new DevicesReaderWriter(), + new MappingsReaderWriter(), + new ProfileReaderWriter()), DevicesToLoad = [ new DATA.Device() { Name = "Superlight 2", DPI = 32000, HWID = @"HID\VID_046D&PID_C54D&MI_00", PollingRate = 1000, DeviceGroup = "Logitech Mice" }, diff --git a/userinterface/ViewModels/Device/DeviceGroupSelectorViewModel.cs b/userinterface/ViewModels/Device/DeviceGroupSelectorViewModel.cs index fd6cea58..9b6db302 100644 --- a/userinterface/ViewModels/Device/DeviceGroupSelectorViewModel.cs +++ b/userinterface/ViewModels/Device/DeviceGroupSelectorViewModel.cs @@ -5,29 +5,29 @@ namespace userinterface.ViewModels.Device { public partial class DeviceGroupSelectorViewModel : ViewModelBase { - protected DeviceGroupModel selectedEntry = null!; + protected string selectedEntry = null!; - public DeviceGroupSelectorViewModel(DeviceModel device, DeviceGroups deviceGroupsBE) + public DeviceGroupSelectorViewModel(IDeviceModel device, DeviceGroups deviceGroupsBE) { Device = device; DeviceGroupsBE = deviceGroupsBE; RefreshSelectedDeviceGroup(); } - protected DeviceModel Device { get; } + protected IDeviceModel Device { get; } protected DeviceGroups DeviceGroupsBE { get; } - public ObservableCollection DeviceGroupEntries => + public ObservableCollection DeviceGroupEntries => DeviceGroupsBE.DeviceGroupModels; - public DeviceGroupModel SelectedEntry + public string SelectedEntry { get => selectedEntry; set { if (DeviceGroupEntries.Contains(value)) { - Device.DeviceGroup = value; + Device.DeviceGroup.TryUpdateModelDirectly(value); selectedEntry = value; } } @@ -37,7 +37,7 @@ public DeviceGroupModel SelectedEntry public void RefreshSelectedDeviceGroup() { - if (!DeviceGroupEntries.Contains(Device.DeviceGroup)) + if (!DeviceGroupEntries.Contains(Device.DeviceGroup.ModelValue)) { IsValid = false; SelectedEntry = DeviceGroups.DefaultDeviceGroup; @@ -45,7 +45,7 @@ public void RefreshSelectedDeviceGroup() } IsValid = true; - selectedEntry = Device.DeviceGroup; + selectedEntry = Device.DeviceGroup.ModelValue; } } } diff --git a/userinterface/ViewModels/Device/DeviceGroupViewModel.cs b/userinterface/ViewModels/Device/DeviceGroupViewModel.cs index 0cc59cd8..69e26549 100644 --- a/userinterface/ViewModels/Device/DeviceGroupViewModel.cs +++ b/userinterface/ViewModels/Device/DeviceGroupViewModel.cs @@ -5,13 +5,13 @@ namespace userinterface.ViewModels.Device { public partial class DeviceGroupViewModel : ViewModelBase { - public DeviceGroupViewModel(BE.DeviceGroupModel deviceGroupBE, BE.DeviceGroups deviceGroupsBE) + public DeviceGroupViewModel(string deviceGroupBE, BE.DeviceGroups deviceGroupsBE) { DeviceGroupBE = deviceGroupBE; DeviceGroupsBE = deviceGroupsBE; } - public BE.DeviceGroupModel DeviceGroupBE { get; } + public string DeviceGroupBE { get; } protected BE.DeviceGroups DeviceGroupsBE { get; } diff --git a/userinterface/ViewModels/Device/DeviceGroupsViewModel.cs b/userinterface/ViewModels/Device/DeviceGroupsViewModel.cs index 97ad801e..7fc54716 100644 --- a/userinterface/ViewModels/Device/DeviceGroupsViewModel.cs +++ b/userinterface/ViewModels/Device/DeviceGroupsViewModel.cs @@ -16,17 +16,17 @@ public DeviceGroupsViewModel(BE.DeviceGroups deviceGroupsBE) protected BE.DeviceGroups DeviceGroupsBE { get; } - public ObservableCollection DeviceGroups => DeviceGroupsBE.DeviceGroupModels; + public ObservableCollection DeviceGroups => DeviceGroupsBE.DeviceGroupModels; public ObservableCollection DeviceGroupViews { get; } - private void DeviceGroupsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => + private void DeviceGroupsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => UpdateDeviceGroupViews(); public void UpdateDeviceGroupViews() { DeviceGroupViews.Clear(); - foreach (BE.DeviceGroupModel deviceGroup in DeviceGroupsBE.DeviceGroupModels) + foreach (string deviceGroup in DeviceGroupsBE.DeviceGroupModels) { DeviceGroupViews.Add(new DeviceGroupViewModel(deviceGroup, DeviceGroupsBE)); } diff --git a/userinterface/ViewModels/Device/DeviceViewModel.cs b/userinterface/ViewModels/Device/DeviceViewModel.cs index 670dbd40..f58b5ef1 100644 --- a/userinterface/ViewModels/Device/DeviceViewModel.cs +++ b/userinterface/ViewModels/Device/DeviceViewModel.cs @@ -6,7 +6,7 @@ namespace userinterface.ViewModels.Device { public partial class DeviceViewModel : ViewModelBase { - public DeviceViewModel(BE.DeviceModel deviceBE, BE.DevicesModel devicesBE) + public DeviceViewModel(BE.IDeviceModel deviceBE, BE.DevicesModel devicesBE) { DeviceBE = deviceBE; DevicesBE = devicesBE; @@ -18,7 +18,7 @@ public DeviceViewModel(BE.DeviceModel deviceBE, BE.DevicesModel devicesBE) DeviceGroup = new DeviceGroupSelectorViewModel(DeviceBE, DevicesBE.DeviceGroups); } - protected BE.DeviceModel DeviceBE { get; } + protected BE.IDeviceModel DeviceBE { get; } protected BE.DevicesModel DevicesBE { get; } @@ -36,7 +36,7 @@ public DeviceViewModel(BE.DeviceModel deviceBE, BE.DevicesModel devicesBE) public void DeleteSelf() { - bool success = DevicesBE.RemoveDevice(DeviceBE); + bool success = DevicesBE.TryRemoveElement(DeviceBE); Debug.Assert(success); } } diff --git a/userinterface/ViewModels/Device/DevicesListViewModel.cs b/userinterface/ViewModels/Device/DevicesListViewModel.cs index 3a0d4239..2cb913a2 100644 --- a/userinterface/ViewModels/Device/DevicesListViewModel.cs +++ b/userinterface/ViewModels/Device/DevicesListViewModel.cs @@ -11,12 +11,12 @@ public DevicesListViewModel(BE.DevicesModel devicesBE) DevicesBE = devicesBE; DeviceViews = []; UpdateDeviceViews(); - DevicesBE.Devices.CollectionChanged += DevicesCollectionChanged; + ((INotifyCollectionChanged)DevicesBE.Elements).CollectionChanged += DevicesCollectionChanged; } protected BE.DevicesModel DevicesBE { get; } - public ObservableCollection Devices => DevicesBE.Devices; + public ReadOnlyObservableCollection Devices => DevicesBE.Elements; public ObservableCollection DeviceViews { get; } @@ -26,12 +26,12 @@ private void DevicesCollectionChanged(object? sender, NotifyCollectionChangedEve public void UpdateDeviceViews() { DeviceViews.Clear(); - foreach (BE.DeviceModel device in DevicesBE.Devices) + foreach (BE.IDeviceModel device in DevicesBE.Elements) { DeviceViews.Add(new DeviceViewModel(device, DevicesBE)); } } - public bool TryAddDevice() => DevicesBE.TryAddDevice(); + public bool TryAddDevice() => DevicesBE.TryAddNewDefault(); } } diff --git a/userinterface/ViewModels/MainWindowViewModel.cs b/userinterface/ViewModels/MainWindowViewModel.cs index 9a1752b9..431d925b 100644 --- a/userinterface/ViewModels/MainWindowViewModel.cs +++ b/userinterface/ViewModels/MainWindowViewModel.cs @@ -16,7 +16,7 @@ public partial class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged private string _selectedPage = DefaultPage; - public MainWindowViewModel(BE.BackEnd backEnd) + public MainWindowViewModel(BE.IBackEnd backEnd) { BackEnd = backEnd; DevicesPage = new DevicesPageViewModel(backEnd.Devices); @@ -30,7 +30,7 @@ public MainWindowViewModel(BE.BackEnd backEnd) public MappingsPageViewModel MappingsPage { get; } - protected BE.BackEnd BackEnd { get; } + protected BE.IBackEnd BackEnd { get; } public string SelectedPage { diff --git a/userinterface/ViewModels/Mapping/MappingViewModel.cs b/userinterface/ViewModels/Mapping/MappingViewModel.cs index 54e5f3fb..48d6c170 100644 --- a/userinterface/ViewModels/Mapping/MappingViewModel.cs +++ b/userinterface/ViewModels/Mapping/MappingViewModel.cs @@ -21,9 +21,11 @@ public MappingViewModel(BE.MappingModel mappingBE, BE.MappingsModel mappingsBE) public void HandleAddMappingSelection(SelectionChangedEventArgs e) { - if (e.AddedItems.Count > 0 && e.AddedItems[0] is BE.DeviceGroupModel deviceGroup) + if (e.AddedItems.Count > 0 && e.AddedItems[0] is string deviceGroup) { - MappingBE.TryAddMapping(deviceGroup.CurrentValidatedValue, BE.ProfilesModel.DefaultProfile.CurrentNameForDisplay); + // TODO: re-add default profile + // MappingBE.TryAddMapping(deviceGroup, BE.ProfilesModel.DefaultProfile.CurrentNameForDisplay); + MappingBE.TryAddMapping(deviceGroup, "Default"); } } diff --git a/userinterface/ViewModels/Profile/AccelerationFormulaSettingsViewModel.cs b/userinterface/ViewModels/Profile/AccelerationFormulaSettingsViewModel.cs index 304ad2c9..34465bdb 100644 --- a/userinterface/ViewModels/Profile/AccelerationFormulaSettingsViewModel.cs +++ b/userinterface/ViewModels/Profile/AccelerationFormulaSettingsViewModel.cs @@ -14,30 +14,30 @@ public class AccelerationFormulaSettingsViewModel : ViewModelBase .Cast() .Select(formulaType => formulaType.ToString())); - public AccelerationFormulaSettingsViewModel(BE.FormulaAccelModel formulaAccel) + public AccelerationFormulaSettingsViewModel(BE.IFormulaAccelModel formulaAccel) { FormulaAccelBE = formulaAccel; - SynchronousSettings = new SynchronousSettings((formulaAccel.GetAccelerationModelOfType(BEData.AccelerationFormulaType.Synchronous) - as BE.Formula.SynchronousAccelerationDefinitionModel)!); + SynchronousSettings = new SynchronousSettings((formulaAccel.GetSelectable(BEData.AccelerationFormulaType.Synchronous) + as BE.Formula.ISynchronousAccelerationDefinitionModel)!); - LinearSettings = new LinearSettings((formulaAccel.GetAccelerationModelOfType(BEData.AccelerationFormulaType.Linear) + LinearSettings = new LinearSettings((formulaAccel.GetSelectable(BEData.AccelerationFormulaType.Linear) as BE.Formula.LinearAccelerationDefinitionModel)!); - ClassicSettings = new ClassicSettings((formulaAccel.GetAccelerationModelOfType(BEData.AccelerationFormulaType.Classic) + ClassicSettings = new ClassicSettings((formulaAccel.GetSelectable(BEData.AccelerationFormulaType.Classic) as BE.Formula.ClassicAccelerationDefinitionModel)!); - PowerSettings = new PowerSettings((formulaAccel.GetAccelerationModelOfType(BEData.AccelerationFormulaType.Power) + PowerSettings = new PowerSettings((formulaAccel.GetSelectable(BEData.AccelerationFormulaType.Power) as BE.Formula.PowerAccelerationDefinitionModel)!); - NaturalSettings = new NaturalSettings((formulaAccel.GetAccelerationModelOfType(BEData.AccelerationFormulaType.Natural) + NaturalSettings = new NaturalSettings((formulaAccel.GetSelectable(BEData.AccelerationFormulaType.Natural) as BE.Formula.NaturalAccelerationDefinitionModel)!); - JumpSettings = new JumpSettings((formulaAccel.GetAccelerationModelOfType(BEData.AccelerationFormulaType.Jump) + JumpSettings = new JumpSettings((formulaAccel.GetSelectable(BEData.AccelerationFormulaType.Jump) as BE.Formula.JumpAccelerationDefinitionModel)!); } - public BE.FormulaAccelModel FormulaAccelBE { get; } + public BE.IFormulaAccelModel FormulaAccelBE { get; } public ObservableCollection FormulaTypesLocal => FormulaTypes; @@ -56,7 +56,7 @@ public AccelerationFormulaSettingsViewModel(BE.FormulaAccelModel formulaAccel) public class SynchronousSettings { - public SynchronousSettings(BE.Formula.SynchronousAccelerationDefinitionModel synchronousAccelModelBE) + public SynchronousSettings(BE.Formula.ISynchronousAccelerationDefinitionModel synchronousAccelModelBE) { SyncSpeed = new EditableFieldViewModel(synchronousAccelModelBE.SyncSpeed); Motivity = new EditableFieldViewModel(synchronousAccelModelBE.Motivity); diff --git a/userinterface/ViewModels/Profile/AccelerationLUTSettingsViewModel.cs b/userinterface/ViewModels/Profile/AccelerationLUTSettingsViewModel.cs index 7164b457..0b854acc 100644 --- a/userinterface/ViewModels/Profile/AccelerationLUTSettingsViewModel.cs +++ b/userinterface/ViewModels/Profile/AccelerationLUTSettingsViewModel.cs @@ -5,13 +5,13 @@ namespace userinterface.ViewModels.Profile { public partial class AccelerationLUTSettingsViewModel : ViewModelBase { - public AccelerationLUTSettingsViewModel(BE.LookupTableDefinitionModel lutAccelBE) + public AccelerationLUTSettingsViewModel(BE.ILookupTableDefinitionModel lutAccelBE) { LUTAccelBE = lutAccelBE; LUTPoints = new EditableFieldViewModel(lutAccelBE.Data); } - public BE.LookupTableDefinitionModel LUTAccelBE { get; } + public BE.ILookupTableDefinitionModel LUTAccelBE { get; } public EditableFieldViewModel LUTPoints { get; set; } } diff --git a/userinterface/ViewModels/Profile/AccelerationProfileSettingsViewModel.cs b/userinterface/ViewModels/Profile/AccelerationProfileSettingsViewModel.cs index bec4409e..e3eb4469 100644 --- a/userinterface/ViewModels/Profile/AccelerationProfileSettingsViewModel.cs +++ b/userinterface/ViewModels/Profile/AccelerationProfileSettingsViewModel.cs @@ -18,18 +18,21 @@ public partial class AccelerationProfileSettingsViewModel : ViewModelBase [ObservableProperty] public bool areAccelSettingsVisible; - public AccelerationProfileSettingsViewModel(BE.AccelerationModel accelerationBE) + public AccelerationProfileSettingsViewModel(BE.IAccelerationModel accelerationBE) { AccelerationBE = accelerationBE; - AccelerationFormulaSettings = new AccelerationFormulaSettingsViewModel(accelerationBE.FormulaAccel); - AccelerationLUTSettings = new AccelerationLUTSettingsViewModel(accelerationBE.LookupTableAccel); + AccelerationFormulaSettings = new AccelerationFormulaSettingsViewModel( + accelerationBE.GetSelectable(BEData.AccelerationDefinitionType.Formula) as BE.IFormulaAccelModel); + AccelerationLUTSettings = new AccelerationLUTSettingsViewModel( + accelerationBE.GetSelectable(BEData.AccelerationDefinitionType.LookupTable) as BE.ILookupTableDefinitionModel); AnisotropySettings = new AnisotropyProfileSettingsViewModel(accelerationBE.Anisotropy); CoalescionSettings = new CoalescionProfileSettingsViewModel(accelerationBE.Coalescion); - AccelerationBE.DefinitionType.AutoUpdateFromInterface = true; - AccelerationBE.DefinitionType.PropertyChanged += OnDefinitionTypeChanged; + // TODO: editable settings composition + AccelerationBE.Selection.AutoUpdateFromInterface = true; + AccelerationBE.Selection.PropertyChanged += OnDefinitionTypeChanged; } - public BE.AccelerationModel AccelerationBE { get; } + public BE.IAccelerationModel AccelerationBE { get; } public ObservableCollection DefinitionTypesLocal => DefinitionTypes; @@ -43,9 +46,9 @@ public AccelerationProfileSettingsViewModel(BE.AccelerationModel accelerationBE) private void OnDefinitionTypeChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(AccelerationBE.DefinitionType.CurrentValidatedValue)) + if (e.PropertyName == nameof(AccelerationBE.Selection.ModelValue)) { - AreAccelSettingsVisible = AccelerationBE.DefinitionType.ModelValue != BEData.AccelerationDefinitionType.None; + AreAccelSettingsVisible = AccelerationBE.Selection.ModelValue != BEData.AccelerationDefinitionType.None; } } } diff --git a/userinterface/ViewModels/Profile/AnisotropyProfileSettingsViewModel.cs b/userinterface/ViewModels/Profile/AnisotropyProfileSettingsViewModel.cs index d39935c5..cadb929a 100644 --- a/userinterface/ViewModels/Profile/AnisotropyProfileSettingsViewModel.cs +++ b/userinterface/ViewModels/Profile/AnisotropyProfileSettingsViewModel.cs @@ -5,7 +5,7 @@ namespace userinterface.ViewModels.Profile { public partial class AnisotropyProfileSettingsViewModel : ViewModelBase { - public AnisotropyProfileSettingsViewModel(BE.AnisotropyModel anisotropyBE) + public AnisotropyProfileSettingsViewModel(BE.IAnisotropyModel anisotropyBE) { AnisotropyBE = anisotropyBE; DomainX = new EditableFieldViewModel(AnisotropyBE.DomainX); @@ -15,7 +15,7 @@ public AnisotropyProfileSettingsViewModel(BE.AnisotropyModel anisotropyBE) LPNorm = new NamedEditableFieldViewModel(AnisotropyBE.LPNorm); } - protected BE.AnisotropyModel AnisotropyBE { get; } + protected BE.IAnisotropyModel AnisotropyBE { get; } public EditableFieldViewModel DomainX { get; set; } diff --git a/userinterface/ViewModels/Profile/CoalescionProfileSettingsViewModel.cs b/userinterface/ViewModels/Profile/CoalescionProfileSettingsViewModel.cs index 6c71d099..2e27cf4a 100644 --- a/userinterface/ViewModels/Profile/CoalescionProfileSettingsViewModel.cs +++ b/userinterface/ViewModels/Profile/CoalescionProfileSettingsViewModel.cs @@ -5,14 +5,14 @@ namespace userinterface.ViewModels.Profile { public partial class CoalescionProfileSettingsViewModel : ViewModelBase { - public CoalescionProfileSettingsViewModel(BE.CoalescionModel coalescionBE) + public CoalescionProfileSettingsViewModel(BE.ICoalescionModel coalescionBE) { CoalescionBE = coalescionBE; InputSmoothingHalfLife = new EditableFieldViewModel(coalescionBE.InputSmoothingHalfLife); ScaleSmoothingHalfLife = new EditableFieldViewModel(coalescionBE.ScaleSmoothingHalfLife); } - protected BE.CoalescionModel CoalescionBE { get; } + protected BE.ICoalescionModel CoalescionBE { get; } public EditableFieldViewModel InputSmoothingHalfLife { get; set; } diff --git a/userinterface/ViewModels/Profile/HiddenProfileSettingsViewModel.cs b/userinterface/ViewModels/Profile/HiddenProfileSettingsViewModel.cs index 81eb5daf..e4179df0 100644 --- a/userinterface/ViewModels/Profile/HiddenProfileSettingsViewModel.cs +++ b/userinterface/ViewModels/Profile/HiddenProfileSettingsViewModel.cs @@ -5,7 +5,7 @@ namespace userinterface.ViewModels.Profile { public partial class HiddenProfileSettingsViewModel : ViewModelBase { - public HiddenProfileSettingsViewModel(BE.ProfileComponents.HiddenModel hiddenBE) + public HiddenProfileSettingsViewModel(BE.ProfileComponents.IHiddenModel hiddenBE) { HiddenBE = hiddenBE; RotationField = new EditableFieldViewModel(hiddenBE.RotationDegrees); @@ -16,7 +16,7 @@ public HiddenProfileSettingsViewModel(BE.ProfileComponents.HiddenModel hiddenBE) OutputSmoothingHalfLifeField = new EditableFieldViewModel(hiddenBE.OutputSmoothingHalfLife); } - protected BE.ProfileComponents.HiddenModel HiddenBE { get; } + protected BE.ProfileComponents.IHiddenModel HiddenBE { get; } public EditableFieldViewModel RotationField { get; set; } diff --git a/userinterface/ViewModels/Profile/ProfileListViewModel.cs b/userinterface/ViewModels/Profile/ProfileListViewModel.cs index 2fa99b0f..4e27b92a 100644 --- a/userinterface/ViewModels/Profile/ProfileListViewModel.cs +++ b/userinterface/ViewModels/Profile/ProfileListViewModel.cs @@ -1,6 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.ObjectModel; +using userspace_backend.Model; using BE = userspace_backend.Model; namespace userinterface.ViewModels.Profile @@ -12,15 +13,15 @@ public partial class ProfileListViewModel : ViewModelBase [ObservableProperty] public BE.ProfileModel? currentSelectedProfile; - private BE.ProfilesModel profilesModel { get; } + private BE.IProfilesModel profilesModel { get; } - public ProfileListViewModel(BE.ProfilesModel profiles, Action selectionChangeAction) + public ProfileListViewModel(BE.IProfilesModel profiles, Action selectionChangeAction) { profilesModel = profiles; SelectionChangeAction = selectionChangeAction; } - public ObservableCollection Profiles => profilesModel.Profiles; + public ReadOnlyObservableCollection Profiles => profilesModel.Elements; public Action SelectionChangeAction { get; } partial void OnCurrentSelectedProfileChanged(BE.ProfileModel? value) @@ -30,22 +31,14 @@ partial void OnCurrentSelectedProfileChanged(BE.ProfileModel? value) public bool TryAddProfile() { - for (int i = 0; i < MaxProfileAttempts; i++) - { - string newProfileName = $"Profile{i}"; - if (profilesModel.TryAddNewDefaultProfile(newProfileName)) - { - return true; - } - } - return false; + return profilesModel.TryAddNewDefault(); } public void RemoveSelectedProfile() { if (CurrentSelectedProfile != null) { - _ = profilesModel.RemoveProfile(CurrentSelectedProfile); + _ = profilesModel.TryRemoveElement(CurrentSelectedProfile); } } } diff --git a/userinterface/ViewModels/Profile/ProfileSettingsViewModel.cs b/userinterface/ViewModels/Profile/ProfileSettingsViewModel.cs index db86a9c3..63f0e0aa 100644 --- a/userinterface/ViewModels/Profile/ProfileSettingsViewModel.cs +++ b/userinterface/ViewModels/Profile/ProfileSettingsViewModel.cs @@ -5,7 +5,7 @@ namespace userinterface.ViewModels.Profile { public partial class ProfileSettingsViewModel : ViewModelBase { - public ProfileSettingsViewModel(BE.ProfileModel profileBE) + public ProfileSettingsViewModel(BE.IProfileModel profileBE) { ProfileModelBE = profileBE; OutputDPIField = new EditableFieldViewModel(profileBE.OutputDPI); @@ -14,7 +14,7 @@ public ProfileSettingsViewModel(BE.ProfileModel profileBE) HiddenSettings = new HiddenProfileSettingsViewModel(profileBE.Hidden); } - protected BE.ProfileModel ProfileModelBE { get; } + protected BE.IProfileModel ProfileModelBE { get; } public EditableFieldViewModel OutputDPIField { get; set; } diff --git a/userinterface/ViewModels/Profile/ProfileViewModel.cs b/userinterface/ViewModels/Profile/ProfileViewModel.cs index 727204be..6fdfa996 100644 --- a/userinterface/ViewModels/Profile/ProfileViewModel.cs +++ b/userinterface/ViewModels/Profile/ProfileViewModel.cs @@ -4,16 +4,16 @@ namespace userinterface.ViewModels.Profile { public partial class ProfileViewModel : ViewModelBase { - public ProfileViewModel(BE.ProfileModel profileBE) + public ProfileViewModel(BE.IProfileModel profileBE) { ProfileModelBE = profileBE; Settings = new ProfileSettingsViewModel(profileBE); Chart = new ProfileChartViewModel(profileBE.CurvePreview); } - protected BE.ProfileModel ProfileModelBE { get; } + protected BE.IProfileModel ProfileModelBE { get; } - public string CurrentName => ProfileModelBE.Name.CurrentValidatedValue; + public string CurrentName => ProfileModelBE.Name.ModelValue; public ProfileSettingsViewModel Settings { get; } diff --git a/userinterface/ViewModels/Profile/ProfilesPageViewModel.cs b/userinterface/ViewModels/Profile/ProfilesPageViewModel.cs index 47766f5d..8228e13e 100644 --- a/userinterface/ViewModels/Profile/ProfilesPageViewModel.cs +++ b/userinterface/ViewModels/Profile/ProfilesPageViewModel.cs @@ -12,9 +12,9 @@ public partial class ProfilesPageViewModel : ViewModelBase [ObservableProperty] public ProfileViewModel? selectedProfileView; - public ProfilesPageViewModel(BE.ProfilesModel profileModels) + public ProfilesPageViewModel(BE.IProfilesModel profileModels) { - ProfileModels = profileModels.Profiles; + ProfileModels = profileModels.Elements; ProfileViewModels = new ObservableCollection(); UpdateProfileViewModels(); SelectedProfileView = ProfileViewModels.FirstOrDefault(); @@ -22,7 +22,7 @@ public ProfilesPageViewModel(BE.ProfilesModel profileModels) ActiveProfilesListView = new ActiveProfilesListViewModel(); } - protected IEnumerable ProfileModels { get; } + protected IEnumerable ProfileModels { get; } protected ObservableCollection ProfileViewModels { get; } diff --git a/userinterface/Views/Device/DeviceGroupSelectorView.axaml b/userinterface/Views/Device/DeviceGroupSelectorView.axaml index d9afab00..afacaf20 100644 --- a/userinterface/Views/Device/DeviceGroupSelectorView.axaml +++ b/userinterface/Views/Device/DeviceGroupSelectorView.axaml @@ -113,7 +113,7 @@ Margin="12,0,0,0"> - + diff --git a/userinterface/Views/Device/DeviceGroupView.axaml b/userinterface/Views/Device/DeviceGroupView.axaml index 3ba04d58..bcb2ba21 100644 --- a/userinterface/Views/Device/DeviceGroupView.axaml +++ b/userinterface/Views/Device/DeviceGroupView.axaml @@ -61,7 +61,7 @@ + Text="{Binding DeviceGroupBE, Mode=TwoWay}" /> diff --git a/userinterface/Views/Device/DeviceGroupView.axaml.cs b/userinterface/Views/Device/DeviceGroupView.axaml.cs index 4fc30295..a2f26927 100644 --- a/userinterface/Views/Device/DeviceGroupView.axaml.cs +++ b/userinterface/Views/Device/DeviceGroupView.axaml.cs @@ -31,12 +31,7 @@ public void TextBox_KeyDown(object sender, KeyEventArgs e) public void LostFocusHandler(object sender, RoutedEventArgs args) { - if (sender is TextBox senderTextBox) - { - if (senderTextBox.DataContext is BE.DeviceGroupModel deviceGroup) - { - deviceGroup.TryUpdateFromInterface(); - } - } + // Device groups are now simple strings, no longer editable settings + // The binding handles updates automatically } } \ No newline at end of file diff --git a/userinterface/Views/Mapping/MappingView.axaml b/userinterface/Views/Mapping/MappingView.axaml index 072155b0..5eb4d757 100644 --- a/userinterface/Views/Mapping/MappingView.axaml +++ b/userinterface/Views/Mapping/MappingView.axaml @@ -132,7 +132,7 @@ @@ -155,7 +155,7 @@ BorderThickness="0"> - + @@ -173,7 +173,7 @@ + Text="{Binding DeviceGroup}"/> - + diff --git a/userinterface/Views/Mapping/MappingView.axaml.cs b/userinterface/Views/Mapping/MappingView.axaml.cs index 60f0b4ca..2bce604b 100644 --- a/userinterface/Views/Mapping/MappingView.axaml.cs +++ b/userinterface/Views/Mapping/MappingView.axaml.cs @@ -26,7 +26,7 @@ public void AddMappingSelectionChanged(object sender, SelectionChangedEventArgs if (e.AddedItems.Count > 0 && DataContext is MappingViewModel viewModel) { - DeviceGroupSelectorToAddMapping.ItemsSource = Enumerable.Empty(); + DeviceGroupSelectorToAddMapping.ItemsSource = Enumerable.Empty(); viewModel.HandleAddMappingSelection(e); DeviceGroupSelectorToAddMapping.ItemsSource = viewModel.MappingBE.DeviceGroupsStillUnmapped; } diff --git a/userinterface/Views/Profile/AccelerationFormulaSettingsView.axaml.cs b/userinterface/Views/Profile/AccelerationFormulaSettingsView.axaml.cs index 74118125..26cd229c 100644 --- a/userinterface/Views/Profile/AccelerationFormulaSettingsView.axaml.cs +++ b/userinterface/Views/Profile/AccelerationFormulaSettingsView.axaml.cs @@ -48,7 +48,7 @@ private void SetupControls() } CreateFormulaFieldViewModel(); - var currentFormulaType = GetCurrentFormulaType(viewModel.FormulaAccelBE.FormulaType.InterfaceValue); + var currentFormulaType = GetCurrentFormulaType(viewModel.FormulaAccelBE.Selection.InterfaceValue); AddFormulaSpecificFields(currentFormulaType, viewModel); AddControlToMainPanel(); } @@ -107,9 +107,9 @@ private void OnFormulaTypeSelectionChanged(object? sender, SelectionChangedEvent return; } - viewModel.FormulaAccelBE.FormulaType.TryUpdateFromInterface(); + viewModel.FormulaAccelBE.Selection.TryUpdateFromInterface(); RemoveFormulaSpecificFields(); - var currentFormulaType = GetCurrentFormulaType(viewModel.FormulaAccelBE.FormulaType.InterfaceValue); + var currentFormulaType = GetCurrentFormulaType(viewModel.FormulaAccelBE.Selection.InterfaceValue); AddFormulaSpecificFields(currentFormulaType, viewModel); } diff --git a/userinterface/Views/Profile/AccelerationProfileSettingsView.axaml.cs b/userinterface/Views/Profile/AccelerationProfileSettingsView.axaml.cs index a27ab1fa..bc37826a 100644 --- a/userinterface/Views/Profile/AccelerationProfileSettingsView.axaml.cs +++ b/userinterface/Views/Profile/AccelerationProfileSettingsView.axaml.cs @@ -57,7 +57,7 @@ private void CreateAccelerationComboBox(AccelerationProfileSettingsViewModel vie VerticalAlignment = VerticalAlignment.Center, DataContext = viewModel, ItemsSource = viewModel.DefinitionTypesLocal, - SelectedItem = viewModel.AccelerationBE.DefinitionType.InterfaceValue + SelectedItem = viewModel.AccelerationBE.Selection.InterfaceValue }; _accelerationComboBox.SelectionChanged += OnAccelerationTypeSelectionChanged; diff --git a/userinterface/userinterface.csproj b/userinterface/userinterface.csproj index ef78b937..4961125a 100644 --- a/userinterface/userinterface.csproj +++ b/userinterface/userinterface.csproj @@ -26,6 +26,7 @@ + diff --git a/userspace-backend-tests/ModelTests/EditableSettingsCollectionTests.cs b/userspace-backend-tests/ModelTests/EditableSettingsCollectionTests.cs new file mode 100644 index 00000000..e6391b9b --- /dev/null +++ b/userspace-backend-tests/ModelTests/EditableSettingsCollectionTests.cs @@ -0,0 +1,274 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using userspace_backend.Model.EditableSettings; + +namespace userspace_backend_tests.ModelTests +{ + [TestClass] + public class EditableSettingsCollectionTests + { + #region TestClasses + + protected class TestDataType + { + public int Property { get; set; } + + public TestSubDataType? SubData { get; set; } + + public override bool Equals(object? obj) + { + return obj is TestDataType type && + Property == type.Property && + EqualityComparer.Default.Equals(SubData, type.SubData); + } + + public override int GetHashCode() + { + return HashCode.Combine(Property, SubData); + } + } + + protected class TestSubDataType + { + public int SubProperty { get; set; } + + public override bool Equals(object? obj) + { + return obj is TestSubDataType type && + SubProperty == type.SubProperty; + } + + public override int GetHashCode() + { + return HashCode.Combine(SubProperty); + } + } + + protected interface IEditableSettingsTestCollection : IEditableSettingsCollectionSpecific + { + public IEditableSettingSpecific PropertySetting { get; } + + public IEditableSettingsTestSubCollection SubCollection { get; } + } + + protected interface IEditableSettingsTestSubCollection : IEditableSettingsCollectionSpecific + { + public IEditableSettingSpecific SubPropertySetting { get; } + } + + protected class EditableSettingsTestCollection : EditableSettingsCollectionV2, IEditableSettingsTestCollection + { + public const string ProperySettingName = $"{nameof(EditableSettingsTestCollection)}.{nameof(PropertySetting)}"; + public const string SubCollectionName = $"{nameof(EditableSettingsTestCollection)}.{nameof(SubCollection)}"; + + public EditableSettingsTestCollection( + [FromKeyedServices(ProperySettingName)]IEditableSettingSpecific propertySetting, + IEditableSettingsTestSubCollection subCollection) + : base([propertySetting], [subCollection]) + { + PropertySetting = propertySetting; + SubCollection = subCollection; + } + + public IEditableSettingSpecific PropertySetting { get; protected set; } + + public IEditableSettingsTestSubCollection SubCollection { get; } + + public override TestDataType MapToData() + { + return new TestDataType() + { + Property = PropertySetting.ModelValue, + SubData = SubCollection.MapToData(), + }; + } + + protected override bool TryMapEditableSettingsCollectionsFromData(TestDataType data) + { + return SubCollection.TryMapFromData(data.SubData); + } + + protected override bool TryMapEditableSettingsFromData(TestDataType data) + { + return PropertySetting.TryUpdateModelDirectly(data.Property); + } + } + + protected class EditableSettingsTestSubCollection : EditableSettingsCollectionV2, IEditableSettingsTestSubCollection + { + public const string SubPropertySettingName = $"{nameof(EditableSettingsTestSubCollection)}.{nameof(SubPropertySetting)}"; + + public EditableSettingsTestSubCollection( + [FromKeyedServices(SubPropertySettingName)]IEditableSettingSpecific subPropertySetting) + : base([subPropertySetting], []) + { + SubPropertySetting = subPropertySetting; + } + + public IEditableSettingSpecific SubPropertySetting { get; protected set; } + + public override TestSubDataType MapToData() + { + return new TestSubDataType() + { + SubProperty = SubPropertySetting.ModelValue, + }; + } + + protected override bool TryMapEditableSettingsCollectionsFromData(TestSubDataType data) + { + return true; + } + + protected override bool TryMapEditableSettingsFromData(TestSubDataType data) + { + return SubPropertySetting.TryUpdateModelDirectly(data.SubProperty); + } + } + + #endregion TestClasses + + #region InitDI + + protected static IEditableSettingsTestCollection InitTestObject( + string testSettingName, + string testSubSettingName, + int testSettingInitialValue, + int testSubSettingInitialValue) + { + ServiceCollection services = new ServiceCollection(); + services.AddTransient(); + services.AddKeyedTransient>( + EditableSettingsTestCollection.ProperySettingName, (_, _) => + new EditableSettingV2( + testSettingName, + testSettingInitialValue, + UserInputParsers.IntParser, + ModelValueValidators.DefaultIntValidator, + autoUpdateFromInterface: false)); + services.AddTransient(); + services.AddKeyedTransient>( + EditableSettingsTestSubCollection.SubPropertySettingName, (_, _) => + new EditableSettingV2( + testSubSettingName, + testSubSettingInitialValue, + UserInputParsers.IntParser, + ModelValueValidators.DefaultIntValidator, + autoUpdateFromInterface: false)); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IEditableSettingsTestCollection testObject = serviceProvider.GetRequiredService(); + return testObject; + } + + #endregion InitDI + + #region Tests + + [TestMethod] + public void EditableSettingsCollection_Construction() + { + string testSettingName = "Test Setting"; + string testSubSettingName = "Test Sub Setting"; + int testSettingInitialValue = 0; + int testSubSettingInitialValue = 1; + + IEditableSettingsTestCollection testObject = + InitTestObject(testSettingName, testSubSettingName, testSettingInitialValue, testSubSettingInitialValue); + + Assert.IsNotNull(testObject); + Assert.IsNotNull(testObject.PropertySetting); + Assert.AreEqual(testSettingInitialValue, testObject.PropertySetting.ModelValue); + Assert.AreEqual(testSettingName, testObject.PropertySetting.DisplayName); + + Assert.IsNotNull(testObject.SubCollection); + Assert.IsNotNull(testObject.SubCollection.SubPropertySetting); + Assert.AreEqual(testSubSettingInitialValue, testObject.SubCollection.SubPropertySetting.ModelValue); + Assert.AreEqual(testSubSettingName, testObject.SubCollection.SubPropertySetting.DisplayName); + } + + [TestMethod] + public void EditableSettingsCollection_PropertyChange() + { + string testSettingName = "Test Setting"; + string testSubSettingName = "Test Sub Setting"; + int testSettingInitialValue = 0; + int testSubSettingInitialValue = 1; + + IEditableSettingsTestCollection testObject = + InitTestObject(testSettingName, testSubSettingName, testSettingInitialValue, testSubSettingInitialValue); + + // Test Case: property is changed + int propertyChangedHandlerCalls = 0; + void TestSettingChangedHandler(object? sender, EventArgs e) + { + propertyChangedHandlerCalls++; + } + + testObject.AnySettingChanged += TestSettingChangedHandler; + testObject.PropertySetting.InterfaceValue = "2"; + testObject.PropertySetting.TryUpdateFromInterface(); + + Assert.AreEqual(2, testObject.PropertySetting.ModelValue); + Assert.AreEqual(1, propertyChangedHandlerCalls); + + // Test Case: property of sub collection is changed + propertyChangedHandlerCalls = 0; + testObject.SubCollection.SubPropertySetting.InterfaceValue = "3"; + testObject.SubCollection.SubPropertySetting.TryUpdateFromInterface(); + + Assert.AreEqual(3, testObject.SubCollection.SubPropertySetting.ModelValue); + Assert.AreEqual(1, propertyChangedHandlerCalls); + } + + [TestMethod] + public void EditableSettingsCollection_MapToData() + { + string testSettingName = "Test Setting"; + string testSubSettingName = "Test Sub Setting"; + int testSettingInitialValue = 0; + int testSubSettingInitialValue = 1; + + IEditableSettingsTestCollection testObject = + InitTestObject(testSettingName, testSubSettingName, testSettingInitialValue, testSubSettingInitialValue); + + // Test Case: initial values + TestDataType expectedData = new TestDataType() + { + Property = testSettingInitialValue, + SubData = new TestSubDataType() + { + SubProperty = testSubSettingInitialValue, + } + }; + + TestDataType actualData = testObject.MapToData(); + Assert.IsNotNull(actualData); + Assert.AreEqual(expectedData, actualData); + + // Test case: properties change + testObject.PropertySetting.InterfaceValue = "2"; + testObject.PropertySetting.TryUpdateFromInterface(); + testObject.SubCollection.SubPropertySetting.InterfaceValue = "3"; + testObject.SubCollection.SubPropertySetting.TryUpdateFromInterface(); + + expectedData = new TestDataType() + { + Property = 2, + SubData = new TestSubDataType() + { + SubProperty = 3, + } + }; + + actualData = testObject.MapToData(); + Assert.IsNotNull(actualData); + Assert.AreEqual(expectedData, actualData); + } + + #endregion Tests + } +} diff --git a/userspace-backend-tests/ModelTests/EditableSettingsListTests.cs b/userspace-backend-tests/ModelTests/EditableSettingsListTests.cs new file mode 100644 index 00000000..2eaf163b --- /dev/null +++ b/userspace-backend-tests/ModelTests/EditableSettingsListTests.cs @@ -0,0 +1,227 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using userspace_backend.Model.EditableSettings; + +namespace userspace_backend_tests.ModelTests +{ + [TestClass] + public class EditableSettingsListTests + { + #region TestClasses + + public class TestData + { + public string Name { get; set; } + + public int Property { get; set; } + } + + public interface IEditableSettingsTestCollection : IEditableSettingsCollectionSpecific + { + public IEditableSettingSpecific NameSetting { get; } + + public IEditableSettingSpecific PropertySetting { get; } + } + + public interface IEditableSettingsTestList : IEditableSettingsList + { + } + + public class EditableSettingsTestCollection : EditableSettingsCollectionV2, IEditableSettingsTestCollection + { + public const string NameSettingName = $"{nameof(EditableSettingsTestCollection)}.{nameof(NameSetting)}"; + public const string ProperySettingName = $"{nameof(EditableSettingsTestCollection)}.{nameof(PropertySetting)}"; + + public EditableSettingsTestCollection( + [FromKeyedServices(NameSettingName)]IEditableSettingSpecific nameSetting, + [FromKeyedServices(ProperySettingName)]IEditableSettingSpecific propertySetting) + : base([propertySetting], []) + + { + NameSetting = nameSetting; + PropertySetting = propertySetting; + } + + public IEditableSettingSpecific NameSetting { get; } + + public IEditableSettingSpecific PropertySetting { get; } + + public override TestData MapToData() + { + return new TestData() + { + Property = PropertySetting.ModelValue, + }; + } + + protected override bool TryMapEditableSettingsCollectionsFromData(TestData data) + { + return PropertySetting.TryUpdateModelDirectly(data.Property) + & NameSetting.TryUpdateModelDirectly(data.Name); + } + + protected override bool TryMapEditableSettingsFromData(TestData data) + { + return true; + } + } + + public class EditableSettingsTestList : EditableSettingsList, IEditableSettingsTestList + { + public const string TestNameTemplate = "TestData"; + + public EditableSettingsTestList(IServiceProvider serviceProvider) + : base(serviceProvider, [], []) + { + } + + protected override string DefaultNameTemplate => TestNameTemplate; + + public override IEnumerable MapToData() + { + return ElementsInternal.Select(e => e.MapToData()); + } + + protected override string GetNameFromData(TestData data) + { + return data.Name; + } + + protected override string GetNameFromElement(IEditableSettingsTestCollection element) => element.NameSetting.ModelValue; + + protected override void SetElementName(IEditableSettingsTestCollection element, string name) + { + element.NameSetting.InterfaceValue = name; + element.NameSetting.TryUpdateFromInterface(); + } + + protected override bool TryMapEditableSettingsFromData(IEnumerable data) + { + return true; + } + } + + #endregion TestClasses + + #region InitDI + + public (IEditableSettingsTestList, IServiceProvider) InitTestObject( + string propertyName, + int propertyValue, + string nameName, + string nameValue) + { + ServiceCollection services = new ServiceCollection(); + services.AddTransient(); + services.AddTransient(); + services.AddKeyedTransient>( + EditableSettingsTestCollection.ProperySettingName, (_, _) => + new EditableSettingV2( + propertyName, + propertyValue, + UserInputParsers.IntParser, + ModelValueValidators.DefaultIntValidator, + autoUpdateFromInterface: false)); + services.AddKeyedTransient>( + EditableSettingsTestCollection.NameSettingName, (_, _) => + new EditableSettingV2( + nameName, + nameValue, + UserInputParsers.StringParser, + ModelValueValidators.DefaultStringValidator, + autoUpdateFromInterface: false)); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + IEditableSettingsTestList testObject = serviceProvider.GetRequiredService(); + return (testObject, serviceProvider); + } + + #endregion InitDI + + #region Tests + + [TestMethod] + public void EditableSettingsList_Construction() + { + string propertyName = "Property"; + int propertyInitialValue = 2; + string nameName = "Name"; + string nameInitialValue = "My test data list"; + (IEditableSettingsTestList testObject, _) = InitTestObject( + propertyName, + propertyInitialValue, + nameName, + nameInitialValue); + + Assert.IsNotNull(testObject); + Assert.IsNotNull(testObject.Elements); + Assert.AreEqual(0, testObject.Elements.Count); + } + + [TestMethod] + public void EditableSettingsList_AddRemoveElements() + { + string propertyName = "Property"; + int propertyInitialValue = 2; + string nameName = "Name"; + string nameInitialValue = "My test data list"; + (IEditableSettingsTestList testObject, IServiceProvider serviceProvider) = InitTestObject( + propertyName, + propertyInitialValue, + nameName, + nameInitialValue); + + Assert.IsNotNull(testObject); + Assert.IsNotNull(testObject.Elements); + Assert.AreEqual(0, testObject.Elements.Count); + + // Test case: add one default element + testObject.TryAddNewDefault(); + Assert.AreEqual(1, testObject.Elements.Count); + IEditableSettingsTestCollection firstElement = testObject.Elements[0]; + Assert.IsNotNull(firstElement); + Assert.AreEqual($"{EditableSettingsTestList.TestNameTemplate}1", firstElement.NameSetting.ModelValue); + Assert.AreEqual(propertyInitialValue, firstElement.PropertySetting.ModelValue); + + // Test case: add second default element + testObject.TryAddNewDefault(); + Assert.AreEqual(2, testObject.Elements.Count); + IEditableSettingsTestCollection secondElement = testObject.Elements[1]; + Assert.IsNotNull(secondElement ); + Assert.AreEqual($"{EditableSettingsTestList.TestNameTemplate}2", secondElement.NameSetting.ModelValue); + Assert.AreEqual(propertyInitialValue, secondElement.PropertySetting.ModelValue); + + // Test case: add custom-created element + IEditableSettingsTestCollection elementToAdd = serviceProvider.GetRequiredService(); + bool result = testObject.TryAdd(elementToAdd); + Assert.IsTrue(result); + Assert.AreEqual(3, testObject.Elements.Count); + IEditableSettingsTestCollection thirdElement = testObject.Elements[2]; + Assert.AreEqual(elementToAdd, thirdElement); + Assert.AreEqual(nameInitialValue, elementToAdd.NameSetting.ModelValue); + + // Test case: add element with same name (should fail) + IEditableSettingsTestCollection duplicateElementToAdd = serviceProvider.GetRequiredService(); + result = testObject.TryAdd(duplicateElementToAdd); + Assert.IsFalse(result); + Assert.AreEqual(3, testObject.Elements.Count); + + // Test case: remove element + result = testObject.TryRemoveElement(elementToAdd); + Assert.IsTrue(result); + Assert.AreEqual(2, testObject.Elements.Count); + + // Test case: remove element that was never added (should fail) + result = testObject.TryRemoveElement(duplicateElementToAdd); + Assert.IsFalse(result); + Assert.AreEqual(2, testObject.Elements.Count); + } + + #endregion Tests + } +} diff --git a/userspace-backend-tests/ModelTests/EditableSettingsSelectorTests.cs b/userspace-backend-tests/ModelTests/EditableSettingsSelectorTests.cs new file mode 100644 index 00000000..eb3a091a --- /dev/null +++ b/userspace-backend-tests/ModelTests/EditableSettingsSelectorTests.cs @@ -0,0 +1,347 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using userspace_backend.Model.EditableSettings; + +namespace userspace_backend_tests.ModelTests +{ + [TestClass] + public class EditableSettingsSelectorTests + { + #region TestClasses + + public abstract class TestDataAbstract + { + public enum TestDataType + { + A, + B, + } + + public abstract TestDataType Type { get; } + + public static DefaultModelValueValidator DefaultTestDataTypeValidator = + new DefaultModelValueValidator(); + + public override bool Equals(object? obj) + { + return obj is TestDataAbstract @abstract && + Type == @abstract.Type; + } + + public override int GetHashCode() + { + return HashCode.Combine(Type); + } + } + + public class TestDataTypeParser : IUserInputParser + { + public static TestDataTypeParser Singleton = new TestDataTypeParser(); + + public bool TryParse(string input, out TestDataAbstract.TestDataType parsedValue) + { + if (Enum.TryParse(input, ignoreCase: true, out parsedValue)) + { + return true; + } + + parsedValue = default; + return false; + } + } + + public class TestDataA : TestDataAbstract + { + public int PropertyA; + + public override TestDataType Type => TestDataType.A; + + public override bool Equals(object? obj) + { + return obj is TestDataA a && + base.Equals(obj) && + Type == a.Type && + PropertyA == a.PropertyA; + } + + public override int GetHashCode() + { + return HashCode.Combine(base.GetHashCode(), Type, PropertyA); + } + } + + public class TestDataB : TestDataAbstract + { + public int PropertyB; + + public override TestDataType Type => TestDataType.B; + + public override bool Equals(object? obj) + { + return obj is TestDataB b && + base.Equals(obj) && + Type == b.Type && + PropertyB == b.PropertyB; + } + + public override int GetHashCode() + { + return HashCode.Combine(base.GetHashCode(), Type, PropertyB); + } + } + + public interface IEditableSettingsTestA : IEditableSettingsCollectionSpecific + { + public IEditableSettingSpecific PropertyA { get; } + } + + public interface IEditableSettingsTestB : IEditableSettingsCollectionSpecific, + IEditableSettingsCollectionSpecific + { + public IEditableSettingSpecific PropertyB { get; } + } + + public interface IEditableSettingsTestSelector : IEditableSettingsSelector< + TestDataAbstract.TestDataType, + TestDataAbstract> + { + } + + public class EditableSettingsTestA : EditableSettingsSelectable, IEditableSettingsTestA + { + public const string PropertyAName = $"{nameof(EditableSettingsTestA)}.{nameof(PropertyA)}"; + + public EditableSettingsTestA([FromKeyedServices(PropertyAName)]IEditableSettingSpecific propertyA) + : base([propertyA], []) + { + PropertyA = propertyA; + } + + public IEditableSettingSpecific PropertyA { get; } + + public override TestDataA MapToData() + { + return new TestDataA() + { + PropertyA = PropertyA.ModelValue, + }; + } + + protected override bool TryMapEditableSettingsCollectionsFromData(TestDataA data) + { + return true; + } + + protected override bool TryMapEditableSettingsFromData(TestDataA data) + { + return PropertyA.TryUpdateModelDirectly(data.PropertyA); + } + } + + public class EditableSettingsTestB : EditableSettingsSelectable, IEditableSettingsTestB + { + public const string PropertyBName = $"{nameof(EditableSettingsTestB)}.{nameof(PropertyB)}"; + + public EditableSettingsTestB([FromKeyedServices(PropertyBName)]IEditableSettingSpecific propertyB) + : base([propertyB], []) + { + PropertyB = propertyB; + } + + public IEditableSettingSpecific PropertyB { get; } + + public override TestDataB MapToData() + { + return new TestDataB() + { + PropertyB = PropertyB.ModelValue, + }; + } + + protected override bool TryMapEditableSettingsCollectionsFromData(TestDataB data) + { + return true; + } + + protected override bool TryMapEditableSettingsFromData(TestDataB data) + { + return PropertyB.TryUpdateModelDirectly(data.PropertyB); + } + } + + public class EditableSettingsTestSelector : EditableSettingsSelector< + TestDataAbstract.TestDataType, + TestDataAbstract> + , IEditableSettingsTestSelector + { + public const string SelectionName = $"{nameof(EditableSettingsTestSelector)}.{nameof(Selection)}"; + + public EditableSettingsTestSelector( + IServiceProvider serviceProvider, + [FromKeyedServices(SelectionName)]IEditableSettingSpecific selection) + : base(serviceProvider, selection, [], []) + { + } + + protected override bool TryMapEditableSettingsCollectionsFromData(TestDataAbstract data) + { + return Selection.TryUpdateModelDirectly(data.Type); + } + + protected override bool TryMapEditableSettingsFromData(TestDataAbstract data) + { + return Selected.TryMapFromData(data); + } + } + + #endregion TestClasses + + #region InitDI + + protected static IEditableSettingsTestSelector InitTestObject( + string selectionName, + string aName, + string bName, + TestDataAbstract.TestDataType selectionInitialValue, + int aInitialValue, + int bInitialValue) + { + ServiceCollection services = new ServiceCollection(); + services.AddTransient(); + services.AddKeyedTransient>( + EditableSettingsTestSelector.SelectionName, (_, _) => + new EditableSettingV2( + selectionName, + selectionInitialValue, + TestDataTypeParser.Singleton, + TestDataAbstract.DefaultTestDataTypeValidator, + autoUpdateFromInterface: false)); + services.AddTransient(); + services.AddKeyedTransient, EditableSettingsTestA>( + EditableSettingsSelectorHelper.GetSelectionKey(TestDataAbstract.TestDataType.A)); + services.AddKeyedTransient>( + EditableSettingsTestA.PropertyAName, (_, _) => + new EditableSettingV2( + aName, + aInitialValue, + UserInputParsers.IntParser, + ModelValueValidators.DefaultIntValidator, + autoUpdateFromInterface: false)); + services.AddTransient(); + services.AddKeyedTransient, EditableSettingsTestB>( + EditableSettingsSelectorHelper.GetSelectionKey(TestDataAbstract.TestDataType.B)); + services.AddKeyedTransient>( + EditableSettingsTestB.PropertyBName, (_, _) => + new EditableSettingV2( + bName, + bInitialValue, + UserInputParsers.IntParser, + ModelValueValidators.DefaultIntValidator, + autoUpdateFromInterface: false)); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + IEditableSettingsTestSelector testObject = serviceProvider.GetRequiredService(); + return testObject; + } + + #endregion InitDI + + #region Tests + + [TestMethod] + public void EditableSettingsSelector_Construction() + { + string selectionName = "Selection"; + string aName = "Property A"; + string bName = "Property B"; + TestDataAbstract.TestDataType selectionInitialValue = TestDataAbstract.TestDataType.A; + int aInitialValue = 1; + int bInitialValue = 2; + + IEditableSettingsTestSelector testObject = + InitTestObject(selectionName, aName, bName, selectionInitialValue, aInitialValue, bInitialValue); + Assert.IsNotNull(testObject); + Assert.IsNotNull(testObject.Selection); + Assert.AreEqual(selectionInitialValue, testObject.Selection.ModelValue); + + IEditableSettingsCollectionSpecific testObjectAbstractA = + testObject.GetSelectable(TestDataAbstract.TestDataType.A); + Assert.IsNotNull(testObjectAbstractA); + IEditableSettingsTestA testObjectA = testObjectAbstractA as IEditableSettingsTestA; + Assert.IsNotNull(testObjectA); + Assert.AreEqual(aName, testObjectA.PropertyA.DisplayName); + Assert.AreEqual(aInitialValue, testObjectA.PropertyA.ModelValue); + + IEditableSettingsCollectionSpecific testObjectAbstractB = + testObject.GetSelectable(TestDataAbstract.TestDataType.B); + Assert.IsNotNull(testObjectAbstractB); + IEditableSettingsTestB testObjectB = testObjectAbstractB as IEditableSettingsTestB; + Assert.IsNotNull(testObjectB); + Assert.AreEqual(bName, testObjectB.PropertyB.DisplayName); + Assert.AreEqual(bInitialValue, testObjectB.PropertyB.ModelValue); + } + + [TestMethod] + public void EditableSettingsSelector_SelectionChange() + { + string selectionName = "Selection"; + string aName = "Property A"; + string bName = "Property B"; + TestDataAbstract.TestDataType selectionInitialValue = TestDataAbstract.TestDataType.A; + int aInitialValue = 1; + int bInitialValue = 2; + + IEditableSettingsTestSelector testObject = + InitTestObject(selectionName, aName, bName, selectionInitialValue, aInitialValue, bInitialValue); + + int propertyChangedHandlerCalls = 0; + void TestSettingChangedHandler(object? sender, EventArgs e) + { + propertyChangedHandlerCalls++; + } + + testObject.AnySettingChanged += TestSettingChangedHandler; + testObject.Selection.InterfaceValue = "b"; + testObject.Selection.TryUpdateFromInterface(); + + Assert.AreEqual(TestDataAbstract.TestDataType.B, testObject.Selection.ModelValue); + Assert.AreEqual(1, propertyChangedHandlerCalls); + } + + + [TestMethod] + public void EditableSettingsSelector_MapToData() + { + string selectionName = "Selection"; + string aName = "Property A"; + string bName = "Property B"; + TestDataAbstract.TestDataType selectionInitialValue = TestDataAbstract.TestDataType.A; + int aInitialValue = 1; + int bInitialValue = 2; + + IEditableSettingsTestSelector testObject = + InitTestObject(selectionName, aName, bName, selectionInitialValue, aInitialValue, bInitialValue); + + // Test case: initial values + TestDataAbstract expectedData = new TestDataA() + { + PropertyA = aInitialValue, + }; + TestDataAbstract actualData = testObject.MapToData(); + Assert.AreEqual(expectedData, actualData); + + // Test case: selection changes + testObject.Selection.InterfaceValue = "b"; + testObject.Selection.TryUpdateFromInterface(); + expectedData = new TestDataB() + { + PropertyB = bInitialValue, + }; + actualData = testObject.MapToData(); + Assert.AreEqual(expectedData, actualData); + } + + #endregion Tests + } +} diff --git a/userspace-backend-tests/ModelTests/EditableSettingsTests.cs b/userspace-backend-tests/ModelTests/EditableSettingsTests.cs new file mode 100644 index 00000000..517e06ca --- /dev/null +++ b/userspace-backend-tests/ModelTests/EditableSettingsTests.cs @@ -0,0 +1,143 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using userspace_backend.Model.EditableSettings; + +namespace userspace_backend_tests.ModelTests +{ + [TestClass] + public class EditableSettingsTests + { + #region Init + + public static EditableSettingV2 InitTestObject( + string testSettingName, + int testSettingInitialValue, + bool autoUpddateFromInterface = false) + { + return new EditableSettingV2( + testSettingName, + testSettingInitialValue, + UserInputParsers.IntParser, + ModelValueValidators.DefaultIntValidator, + autoUpdateFromInterface: autoUpddateFromInterface); + } + + #endregion Init + + #region Tests + + [TestMethod] + public void EditableSetting_Construction() + { + string testSettingName = "Test Setting"; + int testSettingInitialValue = 0; + + EditableSettingV2 testObject = InitTestObject(testSettingName, testSettingInitialValue); + + Assert.IsNotNull(testObject); + Assert.AreEqual(testSettingInitialValue, testObject.ModelValue); + Assert.AreEqual(testSettingName, testObject.DisplayName); + } + + [TestMethod] + public void EditableSetting_SetFromInterface() + { + int propertyChangedHookCalls = 0; + + void TestObject_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (string.Equals(e.PropertyName, nameof(EditableSettingV2.ModelValue))) + { + propertyChangedHookCalls++; + } + } + + string testSettingName = "Test Setting"; + int testSettingInitialValue = 0; + int testSettingSecondValue = 500; + + EditableSettingV2 testObject = InitTestObject(testSettingName, testSettingInitialValue); + + testObject.PropertyChanged += TestObject_PropertyChanged; + testObject.InterfaceValue = testSettingSecondValue.ToString(); + bool updateResult = testObject.TryUpdateFromInterface(); + + Assert.IsTrue(updateResult); + Assert.AreEqual(testSettingSecondValue, testObject.ModelValue); + Assert.AreEqual(1, propertyChangedHookCalls); + } + + [TestMethod] + public void EditableSetting_SetFromInterface_Automatic() + { + int propertyChangedHookCalls = 0; + + void TestObject_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (string.Equals(e.PropertyName, nameof(EditableSettingV2.ModelValue))) + { + propertyChangedHookCalls++; + } + } + + string testSettingName = "Test Setting"; + int testSettingInitialValue = 0; + int testSettingSecondValue = 500; + + EditableSettingV2 testObject = InitTestObject(testSettingName, testSettingInitialValue, autoUpddateFromInterface: true); + + testObject.PropertyChanged += TestObject_PropertyChanged; + testObject.InterfaceValue = testSettingSecondValue.ToString(); + + Assert.AreEqual(testSettingSecondValue, testObject.ModelValue); + Assert.AreEqual(1, propertyChangedHookCalls); + } + + [TestMethod] + public void EditableSetting_SetFromInterface_BadValue() + { + int propertyChangedHookCalls = 0; + + void TestObject_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (string.Equals(e.PropertyName, nameof(EditableSettingV2.ModelValue))) + { + propertyChangedHookCalls++; + } + } + + string testSettingName = "Test Setting"; + int testSettingInitialValue = 0; + + // Test case: bad value, cannot parse + EditableSettingV2 testObject = InitTestObject(testSettingName, testSettingInitialValue); + + testObject.PropertyChanged += TestObject_PropertyChanged; + testObject.InterfaceValue = "ASDFJLKL"; + bool updateResult = testObject.TryUpdateFromInterface(); + + Assert.IsFalse(updateResult); + Assert.AreEqual(testSettingInitialValue, testObject.ModelValue); + Assert.AreEqual(0, propertyChangedHookCalls); + + // Test case: validator determines input is invalid + testObject = + new EditableSettingV2( + testSettingName, + testSettingInitialValue, + UserInputParsers.IntParser, + new AllChangeInvalidValueValidator(), + autoUpdateFromInterface: false); + + testObject.PropertyChanged += TestObject_PropertyChanged; + testObject.InterfaceValue = 500.ToString(); + updateResult = testObject.TryUpdateFromInterface(); + + Assert.IsFalse(updateResult); + Assert.AreEqual(testSettingInitialValue, testObject.ModelValue); + Assert.AreEqual(0, propertyChangedHookCalls); + } + + #endregion Tests + } +} diff --git a/userspace-backend-tests/ModelTests/SystemDevicesTests.cs b/userspace-backend-tests/ModelTests/SystemDevicesTests.cs new file mode 100644 index 00000000..6120402a --- /dev/null +++ b/userspace-backend-tests/ModelTests/SystemDevicesTests.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using userspace_backend.Model; + +namespace userspace_backend_tests.ModelTests +{ + [TestClass] + public class SystemDevicesTests + { + private class TestDevicesRetriever : ISystemDevicesRetriever + { + public List Devices { get; set; } + + public IList GetSystemDevices() => Devices; + } + + private class TestSystemDevice : ISystemDevice + { + public string Name { get; set; } + + public string HWID { get; set; } + } + + [TestMethod] + public void SystemDevicesProvider_ProvidesDevices() + { + List testSystemDevices = new List() + { + new TestSystemDevice() + { + Name = "test", + HWID = "garble", + } + }; + + TestDevicesRetriever testDevicesRetriever = new TestDevicesRetriever() + { + Devices = testSystemDevices, + }; + var services = new ServiceCollection(); + services.Add(ServiceDescriptor.Describe( + serviceType: typeof(ISystemDevicesRetriever), + implementationFactory: _ => testDevicesRetriever, + lifetime: ServiceLifetime.Singleton)); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + SystemDevicesProvider testObject = serviceProvider.GetRequiredService(); + + Assert.IsNotNull(testObject); + Assert.AreEqual(testSystemDevices.Count, testObject.SystemDevices.Count); + CollectionAssert.AreEquivalent(testSystemDevices, testObject.SystemDevices); + } + + // This test may need to be excluded if built from deviceless server. + [TestMethod] + public void SystemDevicesRetriever_RetrievesDevices() + { + var services = new ServiceCollection(); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + + SystemDevicesRetriever testObject = serviceProvider.GetRequiredService(); + Assert.IsNotNull(testObject); + + // These devices will be different per user, but should not be null as long as the user has something giving mouse input. + IList retrievedDevices = testObject.GetSystemDevices(); + Assert.IsNotNull(retrievedDevices); + Assert.IsTrue(retrievedDevices.Count > 0); + } + } +} diff --git a/userspace-backend-tests/userspace-backend-tests.csproj b/userspace-backend-tests/userspace-backend-tests.csproj index 6019e606..779b1296 100644 --- a/userspace-backend-tests/userspace-backend-tests.csproj +++ b/userspace-backend-tests/userspace-backend-tests.csproj @@ -11,6 +11,7 @@ + diff --git a/userspace-backend/BackEnd.cs b/userspace-backend/BackEnd.cs index 96e03886..c8fbc197 100644 --- a/userspace-backend/BackEnd.cs +++ b/userspace-backend/BackEnd.cs @@ -1,59 +1,151 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using DATA = userspace_backend.Data; -using userspace_backend.IO; using userspace_backend.Model; -using userspace_backend.Data.Profiles; +using Microsoft.Extensions.DependencyInjection; namespace userspace_backend { - public class BackEnd + public interface IBackEnd { + void Load(); - public BackEnd(IBackEndLoader backEndLoader) + void Apply(); + + DevicesModel Devices { get; } + + MappingsModel Mappings { get; } + + IProfilesModel Profiles { get; } + } + + public class BackEnd : IBackEnd + { + public BackEnd( + IBackEndLoader backEndLoader, + IProfilesModel profilesModel, + DevicesModel devicesModel, + MappingsModel mappingsModel, + IServiceProvider serviceProvider) { BackEndLoader = backEndLoader; - Devices = new DevicesModel(); - Profiles = new ProfilesModel([]); + Devices = devicesModel; + Mappings = mappingsModel; + Profiles = profilesModel; + ServiceProvider = serviceProvider; } public DevicesModel Devices { get; set; } public MappingsModel Mappings { get; set; } - public ProfilesModel Profiles { get; set; } + public IProfilesModel Profiles { get; set; } protected IBackEndLoader BackEndLoader { get; set; } + protected IServiceProvider ServiceProvider { get; set; } + public void Load() { - IEnumerable devicesData = BackEndLoader.LoadDevices(); ; + IEnumerable devicesData = BackEndLoader.LoadDevices(); LoadDevicesFromData(devicesData); - IEnumerable profilesData = BackEndLoader.LoadProfiles(); ; + IEnumerable profilesData = BackEndLoader.LoadProfiles(); LoadProfilesFromData(profilesData); DATA.MappingSet mappingData = BackEndLoader.LoadMappings(); - Mappings = new MappingsModel(mappingData, Devices.DeviceGroups, Profiles); + LoadMappingsFromData(mappingData); + + EnsureDefaultDeviceGroupExists(); + EnsureDefaultDeviceExists(); + EnsureDefaultProfileExists(); + EnsureDefaultMappingExists(); } protected void LoadDevicesFromData(IEnumerable devicesData) { - foreach(var deviceData in devicesData) + Devices.TryMapFromData(devicesData); + } + + protected void LoadProfilesFromData(IEnumerable profileData) + { + Profiles.TryMapFromData(profileData); + } + + protected void LoadMappingsFromData(DATA.MappingSet mappingData) + { + // Clear existing mappings and reload from data + Mappings.Mappings.Clear(); + foreach (var mapping in mappingData.Mappings) { - Devices.TryAddDevice(deviceData); + Mappings.TryAddMapping(mapping); } } - protected void LoadProfilesFromData(IEnumerable profileData) + protected void EnsureDefaultDeviceGroupExists() + { + // If no device groups exist, create a "Default" group + if (Devices.DeviceGroups.DeviceGroupModels.Count == 0) + { + Devices.DeviceGroups.AddOrGetDeviceGroup(DeviceGroups.DefaultDeviceGroup); + } + } + + protected void EnsureDefaultDeviceExists() + { + // If no devices exist, create a default device + if (Devices.Elements.Count == 0) + { + var defaultDevice = ServiceProvider.GetRequiredService(); + defaultDevice.Name.TryUpdateModelDirectly("Default"); + defaultDevice.HardwareID.TryUpdateModelDirectly("DEFAULT_DEVICE_ID"); + defaultDevice.DeviceGroup.TryUpdateModelDirectly(DeviceGroups.DefaultDeviceGroup); + // DPI, PollRate, and Ignore already have sensible defaults from DI (1000, 1000, false) + + Devices.TryInsert(0, defaultDevice); + } + } + + protected void EnsureDefaultProfileExists() { - foreach (var profile in profileData) + // If no profiles exist, create a default profile + if (Profiles.Elements.Count == 0) + { + var defaultProfile = ServiceProvider.GetRequiredService(); + defaultProfile.Name.TryUpdateModelDirectly("Default"); + Profiles.TryInsert(0, defaultProfile); + } + } + + protected void EnsureDefaultMappingExists() + { + // If no mappings exist, create a default mapping + if (Mappings.Mappings.Count == 0) + { + var defaultMapping = new DATA.Mapping + { + Name = "Default", + GroupsToProfiles = new DATA.Mapping.GroupsToProfilesMapping + { + { DeviceGroups.DefaultDeviceGroup, "Default" } + } + }; + + if (Mappings.TryAddMapping(defaultMapping)) + { + // Set this as the active mapping + if (Mappings.TryGetMapping("Default", out MappingModel? mapping) && mapping != null) + { + mapping.SetActive = true; + } + } + } + + // Ensure at least one mapping has SetActive = true + if (Mappings.GetMappingToSetActive() == null && Mappings.Mappings.Count > 0) { - Profiles.TryAddProfile(profile); + Mappings.Mappings[0].SetActive = true; } } @@ -74,9 +166,9 @@ public void Apply() protected void WriteSettingsToDisk() { BackEndLoader.WriteSettingsToDisk( - Devices.DevicesEnumerable, + Devices.Elements, Mappings, - Profiles.Profiles); + Profiles.Elements); } protected void WriteToDriver() @@ -113,17 +205,17 @@ protected IEnumerable MapToDriverDevices(MappingModel mapping) protected IEnumerable MapToDriverProfiles(MappingModel mapping) { - IEnumerable ProfilesToMap = mapping.IndividualMappings.Select(m => m.Profile).Distinct(); + IEnumerable ProfilesToMap = mapping.IndividualMappings.Select(m => m.Profile).Distinct(); return ProfilesToMap.Select(p => p.CurrentValidatedDriverProfile); } - protected IEnumerable MapToDriverDevices(DeviceGroupModel dg, string profileName) + protected IEnumerable MapToDriverDevices(string dg, string profileName) { - IEnumerable deviceModels = Devices.Devices.Where(d => d.DeviceGroup.Equals(dg)); + IEnumerable deviceModels = Devices.Elements.Where(d => d.DeviceGroup.ModelValue.Equals(dg)); return deviceModels.Select(dm => MapToDriverDevice(dm, profileName)); } - protected DeviceSettings MapToDriverDevice(DeviceModel deviceModel, string profileName) + protected DeviceSettings MapToDriverDevice(IDeviceModel deviceModel, string profileName) { return new DeviceSettings() { diff --git a/userspace-backend/BackEndComposer.cs b/userspace-backend/BackEndComposer.cs new file mode 100644 index 00000000..2af9974b --- /dev/null +++ b/userspace-backend/BackEndComposer.cs @@ -0,0 +1,596 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using DATA = userspace_backend.Data; +using userspace_backend.Display; +using userspace_backend.IO; +using userspace_backend.Model; +using userspace_backend.Model.AccelDefinitions; +using userspace_backend.Model.AccelDefinitions.Formula; +using userspace_backend.Model.EditableSettings; +using userspace_backend.Model.ProfileComponents; +using static userspace_backend.Data.Profiles.Accel.FormulaAccel; +using static userspace_backend.Data.Profiles.Accel.LookupTableAccel; +using static userspace_backend.Data.Profiles.Acceleration; +using static userspace_backend.Model.EditableSettings.EditableSettingsSelectorHelper; + +namespace userspace_backend +{ + public static class BackEndComposer + { + public static IServiceProvider Compose(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + + #region Parsers + + services.AddSingleton, StringParser>(); + services.AddSingleton, IntParser>(); + services.AddSingleton, DoubleParser>(); + services.AddSingleton, BoolParser>(); + services.AddSingleton, AccelerationDefinitionTypeParser>(); + services.AddSingleton, AccelerationFormulaTypeParser>(); + services.AddSingleton, LookupTableTypeParser>(); + services.AddSingleton, LookupTableDataParser>(); + + #endregion Parsers + + #region Validators + + services.AddSingleton, DefaultModelValueValidator>(); + services.AddSingleton, DefaultModelValueValidator>(); + services.AddSingleton, DefaultModelValueValidator>(); + services.AddSingleton, DefaultModelValueValidator>(); + services.AddSingleton, DefaultModelValueValidator>(); + services.AddSingleton, DefaultModelValueValidator>(); + services.AddSingleton, DefaultModelValueValidator>(); + services.AddSingleton, DefaultModelValueValidator>(); + + services.AddKeyedSingleton, DefaultModelValueValidator>( + DefaultModelValueValidator.AllChangeInvalidDIKey); + + services.AddKeyedSingleton, MaxNameLengthValidator>( + ProfileModel.NameDIKey); + + #endregion Validators + + #region Hidden + + services.AddTransient(); + services.AddKeyedTransient>( + HiddenModel.RotationDegreesDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Rotation", + initialValue: 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + HiddenModel.AngleSnappingDegreesDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Angle Snapping", + initialValue: 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + HiddenModel.LeftRightRatioDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "L/R Ratio", + initialValue: 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + HiddenModel.UpDownRatioDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "U/D Ratio", + initialValue: 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + HiddenModel.SpeedCapDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Speed Cap", + initialValue: 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + HiddenModel.OutputSmoothingHalfLifeDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Output Smoothing Half-Life", + initialValue: 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion Hidden + + #region Coalescion + + services.AddTransient(); + services.AddKeyedTransient>( + CoalescionModel.InputSmoothingHalfLifeDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Input Smoothing Half-Life", + initialValue: 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + CoalescionModel.ScaleSmoothingHalfLifeDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Scale Smoothing Half-Life", + initialValue: 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion Coalescion + + #region Anisotropy + + services.AddTransient(); + services.AddKeyedTransient>( + AnisotropyModel.DomainXDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Domain X", + initialValue: 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + AnisotropyModel.DomainYDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Domain Y", + initialValue: 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + AnisotropyModel.RangeXDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Range X", + initialValue: 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + AnisotropyModel.RangeYDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Range Y", + initialValue: 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + AnisotropyModel.LPNormDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "LP Norm", + initialValue: 2, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + AnisotropyModel.CombineXYComponentsDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Combine X and Y Components", + initialValue: false, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion Anisotropy + + #region Acceleration + + services.AddTransient(); + services.AddKeyedTransient>( + AccelerationModel.SelectionDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Definition Type", + initialValue: AccelerationDefinitionType.None, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + // Register selector options for AccelerationDefinitionType + services.AddKeyedTransient>( + GetSelectionKey(AccelerationDefinitionType.None), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + services.AddKeyedTransient>( + GetSelectionKey(AccelerationDefinitionType.Formula), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + services.AddKeyedTransient>( + GetSelectionKey(AccelerationDefinitionType.LookupTable), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + + #endregion Acceleration + + #region FormulaAccel + + services.AddTransient(); + services.AddKeyedTransient>( + FormulaAccelModel.SelectionDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Formula Type", + initialValue: AccelerationFormulaType.Synchronous, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>(), + autoUpdateFromInterface: true)); + services.AddKeyedTransient>( + FormulaAccelModel.GainDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Apply to Gain", + initialValue: false, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + // Register selector options for AccelerationFormulaType + services.AddKeyedTransient>( + GetSelectionKey(AccelerationFormulaType.Synchronous), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + services.AddKeyedTransient>( + GetSelectionKey(AccelerationFormulaType.Linear), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + services.AddKeyedTransient>( + GetSelectionKey(AccelerationFormulaType.Classic), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + services.AddKeyedTransient>( + GetSelectionKey(AccelerationFormulaType.Power), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + services.AddKeyedTransient>( + GetSelectionKey(AccelerationFormulaType.Natural), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + services.AddKeyedTransient>( + GetSelectionKey(AccelerationFormulaType.Jump), + (sp, key) => (IEditableSettingsCollectionSpecific)sp.GetRequiredService()); + + #endregion FormulaAccel + + #region LookupTable + + services.AddTransient(); + services.AddKeyedTransient>( + LookupTableDefinitionModel.ApplyAsDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Apply as", + initialValue: LookupTableType.Velocity, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + LookupTableDefinitionModel.DataDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Data", + initialValue: new LookupTableData(), + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion LookupTable + + #region NoAccel + + services.AddTransient(); + + #endregion NoAccel + + #region SynchronousAccel + + services.AddTransient(); + services.AddKeyedTransient>( + SynchronousAccelerationDefinitionModel.SyncSpeedDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Sync Speed", + 15, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + SynchronousAccelerationDefinitionModel.MotivityDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Motivity", + 1.4, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + SynchronousAccelerationDefinitionModel.GammaDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Gamma", + 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + SynchronousAccelerationDefinitionModel.SmoothnessDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Smoothness", + 0.5, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion SynchronousAccel + + #region LinearAccel + + services.AddTransient(); + services.AddKeyedTransient>( + LinearAccelerationDefinitionModel.AccelerationDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Acceleration", + 0.01, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + LinearAccelerationDefinitionModel.OffsetDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Offset", + 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + LinearAccelerationDefinitionModel.CapDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Cap", + 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion LinearAccel + + #region ClassicAccel + + services.AddTransient(); + services.AddKeyedTransient>( + ClassicAccelerationDefinitionModel.AccelerationDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Acceleration", + 0.01, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + ClassicAccelerationDefinitionModel.ExponentDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Exponent", + 2, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + ClassicAccelerationDefinitionModel.OffsetDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Offset", + 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + ClassicAccelerationDefinitionModel.CapDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Cap", + 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion ClassicAccel + + #region PowerAccel + + services.AddTransient(); + services.AddKeyedTransient>( + PowerAccelerationDefinitionModel.ScaleDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Scale", + 1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + PowerAccelerationDefinitionModel.ExponentDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Exponent", + 0.05, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + PowerAccelerationDefinitionModel.OutputOffsetDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Output Offset", + 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + PowerAccelerationDefinitionModel.CapDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Cap", + 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion PowerAccel + + #region JumpAccel + + services.AddTransient(); + services.AddKeyedTransient>( + JumpAccelerationDefinitionModel.SmoothDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Smooth", + 0.5, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + JumpAccelerationDefinitionModel.InputDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Input", + 15, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + JumpAccelerationDefinitionModel.OutputDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Output", + 1.5, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion JumpAccel + + #region NaturalAccel + + services.AddTransient(); + services.AddKeyedTransient>( + NaturalAccelerationDefinitionModel.DecayRateDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Decay Rate", + 0.1, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + NaturalAccelerationDefinitionModel.InputOffsetDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Input Offset", + 0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + NaturalAccelerationDefinitionModel.LimitDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Limit", + 1.5, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion NaturalAccel + + #region Profile + + services.AddTransient(); + services.AddKeyedTransient>( + ProfileModel.NameDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Name", + "Empty", + parser: services.GetRequiredService>(), + validator: services.GetRequiredKeyedService>(ProfileModel.NameDIKey))); + services.AddKeyedTransient>( + ProfileModel.OutputDPIDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Output DPI", + 1000, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + ProfileModel.YXRatioDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Y/X Ratio", + 1.0, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion Profile + + #region Display + + services.AddTransient(); + + #endregion Display + + #region DeviceGroup + + services.AddSingleton(sp => + { + // Initialize with empty collection - will be populated during BackEnd.Load() + return new DeviceGroups([]); + }); + + services.AddSingleton(sp => + { + var deviceGroups = sp.GetRequiredService(); + return new DeviceGroupValidator(deviceGroups); + }); + + services.AddSingleton(sp => + { + var systemDevicesProvider = sp.GetRequiredService(); + var deviceGroups = sp.GetRequiredService(); + var devicesModel = new DevicesModel(sp, systemDevicesProvider); + devicesModel.DeviceGroups = deviceGroups; + return devicesModel; + }); + + services.AddTransient(); + services.AddKeyedTransient>( + DeviceModel.NameDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Name", + initialValue: "name", + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + DeviceModel.HardwareIDDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Hardware ID", + initialValue: "hwid", + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + DeviceModel.DPIDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "DPI", + initialValue: 1000, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + DeviceModel.PollRateDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Polling Rate", + initialValue: 1000, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + DeviceModel.IgnoreDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Ignore", + initialValue: false, + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + services.AddKeyedTransient>( + DeviceModel.DeviceGroupDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Device Group", + initialValue: "default", + parser: services.GetRequiredService>(), + validator: services.GetRequiredService>())); + + #endregion DeviceGroup + + #region Mapping + + services.AddSingleton(sp => + { + // Initialize with empty MappingSet - will be populated during BackEnd.Load() + var deviceGroups = sp.GetRequiredService(); + var profiles = sp.GetRequiredService(); + var emptyMappingSet = new DATA.MappingSet { Mappings = [] }; + return new MappingsModel(emptyMappingSet, deviceGroups, profiles, sp); + }); + + services.AddSingleton(sp => + { + var mappings = sp.GetRequiredService(); + return new MappingNameValidator(mappings); + }); + + services.AddTransient(); + services.AddKeyedTransient>( + MappingModel.NameDIKey, (IServiceProvider services, object? key) => + new EditableSettingV2( + displayName: "Name", + initialValue: "name", + parser: services.GetRequiredService>(), + validator: services.GetRequiredService())); + + #endregion Mapping + + #region IO Layer + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + #endregion IO Layer + + #region BackEnd + + services.AddSingleton(); + + services.AddSingleton(); + + #endregion BackEnd + + return services.BuildServiceProvider(); + } + } +} diff --git a/userspace-backend/BackEndLoader.cs b/userspace-backend/BackEndLoader.cs index ae09dfef..a0cea532 100644 --- a/userspace-backend/BackEndLoader.cs +++ b/userspace-backend/BackEndLoader.cs @@ -19,55 +19,85 @@ public interface IBackEndLoader public IEnumerable LoadProfiles(); public void WriteSettingsToDisk( - IEnumerable devices, + IEnumerable devices, MappingsModel mappings, - IEnumerable profiles); + IEnumerable profiles); } public class BackEndLoader : IBackEndLoader { - public static DevicesReaderWriter DevicesReaderWriter = new DevicesReaderWriter(); - - public static MappingsReaderWriter MappingsReaderWriter = new MappingsReaderWriter(); - - public static ProfileReaderWriter ProfileReaderWriter = new ProfileReaderWriter(); - - public BackEndLoader(string settingsDirectory) + public BackEndLoader( + string settingsDirectory, + DevicesReaderWriter devicesReaderWriter, + MappingsReaderWriter mappingsReaderWriter, + ProfileReaderWriter profileReaderWriter) { SettingsDirectory = settingsDirectory; + DevicesReaderWriter = devicesReaderWriter; + MappingsReaderWriter = mappingsReaderWriter; + ProfileReaderWriter = profileReaderWriter; } public string SettingsDirectory { get; private set; } + protected DevicesReaderWriter DevicesReaderWriter { get; } + protected MappingsReaderWriter MappingsReaderWriter { get; } + protected ProfileReaderWriter ProfileReaderWriter { get; } public IEnumerable LoadDevices() { string devicesFile = GetDevicesFile(SettingsDirectory); + if (!File.Exists(devicesFile)) + { + return []; + } string devicesText = File.ReadAllText(devicesFile); - IEnumerable devicesData = DevicesReaderWriter.Read(devicesText); + IEnumerable devicesData = DevicesReaderWriter.Deserialize(devicesText); return devicesData; } public DATA.MappingSet LoadMappings() { - throw new NotImplementedException(); + string mappingsFile = GetMappingsFile(SettingsDirectory); + if (!File.Exists(mappingsFile)) + { + return new DATA.MappingSet { Mappings = [] }; + } + string mappingsText = File.ReadAllText(mappingsFile); + DATA.MappingSet mappingsData = MappingsReaderWriter.Deserialize(mappingsText); + return mappingsData; } public IEnumerable LoadProfiles() { - throw new NotImplementedException(); + string profilesDirectory = GetProfilesDirectory(SettingsDirectory); + if (!Directory.Exists(profilesDirectory)) + { + return []; + } + + string[] profileFiles = Directory.GetFiles(profilesDirectory, "*.json"); + List profiles = []; + foreach (string profileFile in profileFiles) + { + string profileText = File.ReadAllText(profileFile); + DATA.Profile profileData = ProfileReaderWriter.Deserialize(profileText); + profiles.Add(profileData); + } + + return profiles; } public void WriteSettingsToDisk( - IEnumerable devices, + IEnumerable devices, MappingsModel mappings, - IEnumerable profiles) + IEnumerable profiles) { WriteDevices(devices); WriteMappings(mappings); WriteProfiles(profiles); } - protected void WriteDevices(IEnumerable devices) + protected void WriteDevices(IEnumerable devices) { IEnumerable devicesData = devices.Select(d => d.MapToData()); string devicesFileText = DevicesReaderWriter.Serialize(devicesData); @@ -83,7 +113,7 @@ protected void WriteMappings(MappingsModel mappings) File.WriteAllText(mappingsFilePath, mappingsFileText); } - protected void WriteProfiles(IEnumerable profiles) + protected void WriteProfiles(IEnumerable profiles) { string profilesDirectory = GetProfilesDirectory(SettingsDirectory); Directory.CreateDirectory(profilesDirectory); diff --git a/userspace-backend/Bootstrapper.cs b/userspace-backend/Bootstrapper.cs index 71408ad8..0ed0ede9 100644 --- a/userspace-backend/Bootstrapper.cs +++ b/userspace-backend/Bootstrapper.cs @@ -35,10 +35,7 @@ public DATA.MappingSet LoadMappings() return ProfilesToLoad; } - public void WriteSettingsToDisk( - IEnumerable devices, - MappingsModel mappings, - IEnumerable profiles) + public void WriteSettingsToDisk(IEnumerable devices, MappingsModel mappings, IEnumerable profiles) { BackEndLoader.WriteSettingsToDisk(devices, mappings, profiles); } diff --git a/userspace-backend/Data/Profiles/Accel/Formula/SynchronousAccel.cs b/userspace-backend/Data/Profiles/Accel/Formula/SynchronousAccel.cs index 8f239836..822a79fe 100644 --- a/userspace-backend/Data/Profiles/Accel/Formula/SynchronousAccel.cs +++ b/userspace-backend/Data/Profiles/Accel/Formula/SynchronousAccel.cs @@ -10,10 +10,10 @@ public class SynchronousAccel : FormulaAccel { public override AccelerationFormulaType FormulaType => AccelerationFormulaType.Synchronous; - public double Motivity { get; set; } - public double SyncSpeed { get; set; } + public double Motivity { get; set; } + public double Gamma { get; set; } public double Smoothness { get; set; } diff --git a/userspace-backend/Data/Profiles/Acceleration.cs b/userspace-backend/Data/Profiles/Acceleration.cs index c8b0992e..a4379505 100644 --- a/userspace-backend/Data/Profiles/Acceleration.cs +++ b/userspace-backend/Data/Profiles/Acceleration.cs @@ -23,8 +23,8 @@ public enum AccelerationDefinitionType public virtual AccelerationDefinitionType Type { get; init; } - public Anisotropy Anisotropy { get; init; } + public Anisotropy Anisotropy { get; set; } - public Coalescion Coalescion { get; init; } + public Coalescion Coalescion { get; set; } } } diff --git a/userspace-backend/IO/DevicesReaderWriter.cs b/userspace-backend/IO/DevicesReaderWriter.cs index b1883988..964f7e8c 100644 --- a/userspace-backend/IO/DevicesReaderWriter.cs +++ b/userspace-backend/IO/DevicesReaderWriter.cs @@ -19,7 +19,7 @@ public override string Serialize(IEnumerable devices) public override IEnumerable Deserialize(string toRead) { - return JsonSerializer.Deserialize>(toRead); + return JsonSerializer.Deserialize>(toRead, JsonOptions); } } } diff --git a/userspace-backend/IO/MappingsReaderWriter.cs b/userspace-backend/IO/MappingsReaderWriter.cs index af6b605c..5c4d5018 100644 --- a/userspace-backend/IO/MappingsReaderWriter.cs +++ b/userspace-backend/IO/MappingsReaderWriter.cs @@ -25,7 +25,7 @@ public override string Serialize(MappingSet toWrite) public override MappingSet Deserialize(string toRead) { - return JsonSerializer.Deserialize(toRead); + return JsonSerializer.Deserialize(toRead, JsonOptions); } } } diff --git a/userspace-backend/IO/ProfileReaderWriter.cs b/userspace-backend/IO/ProfileReaderWriter.cs index a0c39c83..175c7067 100644 --- a/userspace-backend/IO/ProfileReaderWriter.cs +++ b/userspace-backend/IO/ProfileReaderWriter.cs @@ -20,7 +20,7 @@ public class ProfileReaderWriter : ReaderWriterBase public override DATA.Profile Deserialize(string toRead) { - return JsonSerializer.Deserialize(toRead); + return JsonSerializer.Deserialize(toRead, JsonOptions); } public override string Serialize(DATA.Profile toWrite) diff --git a/userspace-backend/Model/AccelDefinitions/AccelDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/AccelDefinitionModel.cs index 6c437f27..17b194f1 100644 --- a/userspace-backend/Model/AccelDefinitions/AccelDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/AccelDefinitionModel.cs @@ -1,36 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data.Profiles; +using userspace_backend.Data.Profiles; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions { - public interface IAccelDefinitionModel : IEditableSettingsCollection + public interface IAccelDefinitionModel : IEditableSettingsCollectionV2 { - Acceleration MapToData(); - AccelArgs MapToDriver(); } - public abstract class AccelDefinitionModel : EditableSettingsCollection, IAccelDefinitionModel where T : Acceleration + public interface IAccelDefinitionModelSpecific : IAccelDefinitionModel, IEditableSettingsCollectionSpecific where T : Acceleration { - protected AccelDefinitionModel(Acceleration dataObject) : base(dataObject) - { - } - - protected override void InitEditableSettingsAndCollections(Acceleration dataObject) - { - T dataAccel = dataObject as T ?? GenerateDefaultDataObject(); - InitSpecificSettingsAndCollections(dataAccel); - } - - protected abstract void InitSpecificSettingsAndCollections(T dataObject); - - protected abstract T GenerateDefaultDataObject(); - - public abstract AccelArgs MapToDriver(); - } + } } diff --git a/userspace-backend/Model/AccelDefinitions/AccelerationModel.cs b/userspace-backend/Model/AccelDefinitions/AccelerationModel.cs index de02380e..33bb3827 100644 --- a/userspace-backend/Model/AccelDefinitions/AccelerationModel.cs +++ b/userspace-backend/Model/AccelDefinitions/AccelerationModel.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using Microsoft.Extensions.DependencyInjection; +using System; using userspace_backend.Data.Profiles; using userspace_backend.Model.EditableSettings; using userspace_backend.Model.ProfileComponents; @@ -8,106 +7,68 @@ namespace userspace_backend.Model.AccelDefinitions { - public class AccelerationModel : EditableSettingsCollection + public interface IAccelerationModel : IEditableSettingsSelector { - public AccelerationModel(Acceleration dataObject) : base(dataObject) - { - DefinitionType.PropertyChanged += DefinitionTypeChangedEventHandler; - } - - public EditableSetting DefinitionType { get; set; } + IAnisotropyModel Anisotropy { get; } - protected Dictionary DefinitionModels { get; set; } + ICoalescionModel Coalescion { get; } - public AnisotropyModel Anisotropy { get; set; } - - public CoalescionModel Coalescion { get; set; } + AccelArgs MapToDriver(); + } - public FormulaAccelModel FormulaAccel + public class AccelerationModel : EditableSettingsSelector, IAccelerationModel + { + public const string SelectionDIKey = $"{nameof(AccelerationModel)}.{nameof(Selection)}"; + + public AccelerationModel( + IServiceProvider serviceProvider, + [FromKeyedServices(SelectionDIKey)]IEditableSettingSpecific definitionType, + IAnisotropyModel anisotropy, + ICoalescionModel coalescion) + : base(serviceProvider, definitionType, [], [anisotropy, coalescion]) { - get - { - if (DefinitionModels.TryGetValue(AccelerationDefinitionType.Formula, out IAccelDefinitionModel value)) - { - return value as FormulaAccelModel; - } - - return null; - } + Anisotropy = anisotropy; + Coalescion = coalescion; } - public LookupTableDefinitionModel LookupTableAccel - { - get - { - if (DefinitionModels.TryGetValue(AccelerationDefinitionType.LookupTable, out IAccelDefinitionModel value)) - { - return value as LookupTableDefinitionModel; - } - - return null; - } - } + public IAnisotropyModel Anisotropy { get; set; } - public override Acceleration MapToData() - { - return DefinitionModels[DefinitionType.ModelValue].MapToData(); - } + public ICoalescionModel Coalescion { get; set; } - public AccelArgs MapToDriver() + public FormulaAccelModel FormulaAccel { - return DefinitionModels[DefinitionType.ModelValue].MapToDriver(); + get => SelectionLookup.TryGetValue(AccelerationDefinitionType.Formula, out IEditableSettingsCollectionSpecific value) + ? value as FormulaAccelModel + : null; } - protected void DefinitionTypeChangedEventHandler(object? sender, PropertyChangedEventArgs e) + public LookupTableDefinitionModel LookupTableAccel { - // When the definition type changes, contained editable settings collections need to correspond to new type - if (string.Equals(e.PropertyName, nameof(DefinitionType.CurrentValidatedValue))) - { - GatherEditableSettingsCollections(); - } + get => SelectionLookup.TryGetValue(AccelerationDefinitionType.LookupTable, out IEditableSettingsCollectionSpecific value) + ? value as LookupTableDefinitionModel + : null; } - protected override IEnumerable EnumerateEditableSettings() + public override Acceleration MapToData() { - return [DefinitionType]; + Acceleration acceleration = base.MapToData(); + acceleration.Anisotropy = Anisotropy.MapToData(); + acceleration.Coalescion = Coalescion.MapToData(); + return acceleration; } - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return [DefinitionModels[DefinitionType.ModelValue]]; - } + public AccelArgs MapToDriver() => ((IAccelDefinitionModel)Selected)?.MapToDriver() ?? new AccelArgs(); - protected override void InitEditableSettingsAndCollections(Acceleration dataObject) + protected override bool TryMapEditableSettingsFromData(Acceleration data) { - DefinitionType = new EditableSetting( - displayName: "Definition Type", - initialValue: dataObject?.Type ?? AccelerationDefinitionType.None, - parser: UserInputParsers.AccelerationDefinitionTypeParser, - validator: ModelValueValidators.DefaultAccelerationTypeValidator); - - DefinitionModels = new Dictionary(); - foreach (AccelerationDefinitionType defnType in Enum.GetValues(typeof(AccelerationDefinitionType))) - { - DefinitionModels.Add(defnType, CreateAccelerationDefinitionModelOfType(defnType, dataObject)); - } - - Anisotropy = new AnisotropyModel(dataObject?.Anisotropy); - Coalescion = new CoalescionModel(dataObject?.Coalescion); + return Selection.TryUpdateModelDirectly(data.Type); } - protected IAccelDefinitionModel CreateAccelerationDefinitionModelOfType(AccelerationDefinitionType definitionType, Acceleration dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(Acceleration data) { - switch (definitionType) - { - case AccelerationDefinitionType.Formula: - return new FormulaAccelModel(dataObject); - case AccelerationDefinitionType.LookupTable: - return new LookupTableDefinitionModel(dataObject); - case AccelerationDefinitionType.None: - default: - return new NoAccelDefinitionModel(dataObject); - } + return Anisotropy.TryMapFromData(data.Anisotropy) + & Coalescion.TryMapFromData(data.Coalescion) + & Selected.TryMapFromData(data); } } } diff --git a/userspace-backend/Model/AccelDefinitions/Formula/ClassicAccelerationDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/Formula/ClassicAccelerationDefinitionModel.cs index 5f68f711..b1f0fb70 100644 --- a/userspace-backend/Model/AccelDefinitions/Formula/ClassicAccelerationDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/Formula/ClassicAccelerationDefinitionModel.cs @@ -1,29 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data.Profiles; +using Microsoft.Extensions.DependencyInjection; +using userspace_backend.Data.Profiles.Accel; using userspace_backend.Data.Profiles.Accel.Formula; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions.Formula { - public class ClassicAccelerationDefinitionModel : AccelDefinitionModel + + public interface IClassicAccelerationDefinitionModel : IAccelDefinitionModelSpecific + { + } + + public class ClassicAccelerationDefinitionModel + : EditableSettingsSelectable, + IClassicAccelerationDefinitionModel { - public ClassicAccelerationDefinitionModel(Acceleration dataObject) : base(dataObject) + public const string AccelerationDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Acceleration)}"; + public const string ExponentDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Exponent)}"; + public const string OffsetDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Offset)}"; + public const string CapDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(CapDIKey)}"; + + public ClassicAccelerationDefinitionModel( + [FromKeyedServices(AccelerationDIKey)]IEditableSettingSpecific acceleration, + [FromKeyedServices(ExponentDIKey)]IEditableSettingSpecific exponent, + [FromKeyedServices(OffsetDIKey)]IEditableSettingSpecific offset, + [FromKeyedServices(CapDIKey)]IEditableSettingSpecific cap) + : base([acceleration, exponent, offset, cap], []) { + Acceleration = acceleration; + Exponent = exponent; + Offset = offset; + Cap = cap; } - public EditableSetting Acceleration { get; set; } + public IEditableSettingSpecific Acceleration { get; set; } - public EditableSetting Exponent { get; set; } + public IEditableSettingSpecific Exponent { get; set; } - public EditableSetting Offset { get; set; } + public IEditableSettingSpecific Offset { get; set; } - public EditableSetting Cap { get; set; } + public IEditableSettingSpecific Cap { get; set; } - public override AccelArgs MapToDriver() + public AccelArgs MapToDriver() { return new AccelArgs { @@ -36,7 +53,7 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() + public override ClassicAccel MapToData() { return new ClassicAccel() { @@ -45,52 +62,19 @@ public override Acceleration MapToData() Offset = Offset.ModelValue, Cap = Cap.ModelValue, }; - } - protected override IEnumerable EnumerateEditableSettings() + protected override bool TryMapEditableSettingsFromData(ClassicAccel data) { - return [ Acceleration, Exponent, Offset, Cap ]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return Enumerable.Empty(); - } - - protected override ClassicAccel GenerateDefaultDataObject() - { - return new ClassicAccel() - { - Acceleration = 0.001, - Exponent = 2, - Offset = 0, - Cap = 0, - }; + return Acceleration.TryUpdateModelDirectly(data.Acceleration) + & Exponent.TryUpdateModelDirectly(data.Exponent) + & Offset.TryUpdateModelDirectly(data.Offset) + & Cap.TryUpdateModelDirectly(data.Cap); } - protected override void InitSpecificSettingsAndCollections(ClassicAccel dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(ClassicAccel data) { - Acceleration = new EditableSetting( - displayName: "Acceleration", - initialValue: dataObject.Acceleration, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Exponent = new EditableSetting( - displayName: "Exponent", - initialValue: dataObject.Exponent, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Offset = new EditableSetting( - displayName: "Offset", - initialValue: dataObject.Offset, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Cap = new EditableSetting( - displayName: "Cap", - initialValue: dataObject.Cap, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return true; } } } diff --git a/userspace-backend/Model/AccelDefinitions/Formula/JumpAccelerationDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/Formula/JumpAccelerationDefinitionModel.cs index a80e67b0..a38c6131 100644 --- a/userspace-backend/Model/AccelDefinitions/Formula/JumpAccelerationDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/Formula/JumpAccelerationDefinitionModel.cs @@ -1,27 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data.Profiles; +using Microsoft.Extensions.DependencyInjection; +using userspace_backend.Data.Profiles.Accel; using userspace_backend.Data.Profiles.Accel.Formula; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions.Formula { - public class JumpAccelerationDefinitionModel : AccelDefinitionModel + public interface IJumpAccelerationDefinitionModel : IAccelDefinitionModelSpecific { - public JumpAccelerationDefinitionModel(Acceleration dataObject) : base(dataObject) + } + + public class JumpAccelerationDefinitionModel + : EditableSettingsSelectable, + IJumpAccelerationDefinitionModel + { + public const string SmoothDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Smooth)}"; + public const string InputDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Input)}"; + public const string OutputDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Output)}"; + + public JumpAccelerationDefinitionModel( + [FromKeyedServices(SmoothDIKey)]IEditableSettingSpecific smooth, + [FromKeyedServices(InputDIKey)]IEditableSettingSpecific input, + [FromKeyedServices(OutputDIKey)]IEditableSettingSpecific output) + : base([smooth, input, output], []) { + Smooth = smooth; + Input = input; + Output = output; } - public EditableSetting Smooth { get; set; } + public IEditableSettingSpecific Smooth { get; set; } - public EditableSetting Input { get; set; } + public IEditableSettingSpecific Input { get; set; } - public EditableSetting Output { get; set; } + public IEditableSettingSpecific Output { get; set; } - public override AccelArgs MapToDriver() + public AccelArgs MapToDriver() { return new AccelArgs { @@ -31,7 +44,7 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() + public override JumpAccel MapToData() { return new JumpAccel() { @@ -41,43 +54,16 @@ public override Acceleration MapToData() }; } - protected override IEnumerable EnumerateEditableSettings() - { - return [ Smooth, Input, Output ]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override bool TryMapEditableSettingsFromData(JumpAccel data) { - return Enumerable.Empty(); - } - - protected override JumpAccel GenerateDefaultDataObject() - { - return new JumpAccel() - { - Smooth = 0.5, - Input = 15, - Output = 1.5, - }; + return Smooth.TryUpdateModelDirectly(data.Smooth) + & Input.TryUpdateModelDirectly(data.Input) + & Output.TryUpdateModelDirectly(data.Output); } - protected override void InitSpecificSettingsAndCollections(JumpAccel dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(JumpAccel data) { - Smooth = new EditableSetting( - displayName: "Smooth", - initialValue: dataObject.Smooth, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Input = new EditableSetting( - displayName: "Input", - initialValue: dataObject.Input, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Output = new EditableSetting( - displayName: "Output", - initialValue: dataObject.Output, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return true; } } } diff --git a/userspace-backend/Model/AccelDefinitions/Formula/LinearAccelerationDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/Formula/LinearAccelerationDefinitionModel.cs index bc778e61..086a8bba 100644 --- a/userspace-backend/Model/AccelDefinitions/Formula/LinearAccelerationDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/Formula/LinearAccelerationDefinitionModel.cs @@ -1,27 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data.Profiles; +using Microsoft.Extensions.DependencyInjection; +using userspace_backend.Data.Profiles.Accel; using userspace_backend.Data.Profiles.Accel.Formula; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions.Formula { - public class LinearAccelerationDefinitionModel : AccelDefinitionModel + public interface ILinearAccelerationDefinitionModel : IAccelDefinitionModelSpecific { - public LinearAccelerationDefinitionModel(Acceleration dataObject) : base(dataObject) + } + + public class LinearAccelerationDefinitionModel + : EditableSettingsSelectable, + ILinearAccelerationDefinitionModel + { + public const string AccelerationDIKey = $"{nameof(LinearAccelerationDefinitionModel)}.{nameof(Acceleration)}"; + public const string OffsetDIKey = $"{nameof(LinearAccelerationDefinitionModel)}.{nameof(Offset)}"; + public const string CapDIKey = $"{nameof(LinearAccelerationDefinitionModel)}.{nameof(CapDIKey)}"; + + public LinearAccelerationDefinitionModel( + [FromKeyedServices(AccelerationDIKey)]IEditableSettingSpecific acceleration, + [FromKeyedServices(OffsetDIKey)]IEditableSettingSpecific offset, + [FromKeyedServices(CapDIKey)]IEditableSettingSpecific cap) + : base([acceleration, offset, cap], []) { + Acceleration = acceleration; + Offset = offset; + Cap = cap; } - public EditableSetting Acceleration { get; set; } + public IEditableSettingSpecific Acceleration { get; set; } - public EditableSetting Offset { get; set; } + public IEditableSettingSpecific Offset { get; set; } - public EditableSetting Cap { get; set; } + public IEditableSettingSpecific Cap { get; set; } - public override AccelArgs MapToDriver() + public AccelArgs MapToDriver() { return new AccelArgs { @@ -34,7 +47,7 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() + public override LinearAccel MapToData() { return new LinearAccel() { @@ -44,43 +57,16 @@ public override Acceleration MapToData() }; } - protected override IEnumerable EnumerateEditableSettings() - { - return [ Acceleration, Offset, Cap ]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override bool TryMapEditableSettingsFromData(LinearAccel data) { - return Enumerable.Empty(); - } - - protected override LinearAccel GenerateDefaultDataObject() - { - return new LinearAccel() - { - Acceleration = 0.001, - Offset = 0, - Cap = 0, - }; + return Acceleration.TryUpdateModelDirectly(data.Acceleration) + & Offset.TryUpdateModelDirectly(data.Offset) + & Cap.TryUpdateModelDirectly(data.Cap); } - protected override void InitSpecificSettingsAndCollections(LinearAccel dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(LinearAccel data) { - Acceleration = new EditableSetting( - displayName: "Acceleration", - initialValue: dataObject.Acceleration, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Offset = new EditableSetting( - displayName: "Offset", - initialValue: dataObject.Offset, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Cap = new EditableSetting( - displayName: "Cap", - initialValue: dataObject.Cap, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return true; } } } diff --git a/userspace-backend/Model/AccelDefinitions/Formula/NaturalAccelerationDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/Formula/NaturalAccelerationDefinitionModel.cs index f12dfc0c..a4abde4c 100644 --- a/userspace-backend/Model/AccelDefinitions/Formula/NaturalAccelerationDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/Formula/NaturalAccelerationDefinitionModel.cs @@ -1,26 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data.Profiles; +using Microsoft.Extensions.DependencyInjection; +using userspace_backend.Data.Profiles.Accel; using userspace_backend.Data.Profiles.Accel.Formula; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions.Formula { - public class NaturalAccelerationDefinitionModel : AccelDefinitionModel + public interface INaturalAccelerationDefinitionModel : IAccelDefinitionModelSpecific { - public NaturalAccelerationDefinitionModel(Acceleration dataObject) : base(dataObject) + } + + public class NaturalAccelerationDefinitionModel + : EditableSettingsSelectable, + INaturalAccelerationDefinitionModel + { + public const string DecayRateDIKey = $"{nameof(NaturalAccelerationDefinitionModel)}.{nameof(DecayRate)}"; + public const string InputOffsetDIKey = $"{nameof(NaturalAccelerationDefinitionModel)}.{nameof(InputOffset)}"; + public const string LimitDIKey = $"{nameof(NaturalAccelerationDefinitionModel)}.{nameof(Limit)}"; + + public NaturalAccelerationDefinitionModel( + [FromKeyedServices(DecayRateDIKey)]IEditableSettingSpecific decayRate, + [FromKeyedServices(InputOffsetDIKey)]IEditableSettingSpecific inputOffset, + [FromKeyedServices(LimitDIKey)]IEditableSettingSpecific limit) + : base([decayRate, inputOffset, limit], []) { + DecayRate = decayRate; + InputOffset = inputOffset; + Limit = limit; } - public EditableSetting DecayRate { get; set; } - public EditableSetting InputOffset { get; set; } + public IEditableSettingSpecific DecayRate { get; set; } - public EditableSetting Limit { get; set; } + public IEditableSettingSpecific InputOffset { get; set; } - public override AccelArgs MapToDriver() + public IEditableSettingSpecific Limit { get; set; } + + public AccelArgs MapToDriver() { return new AccelArgs { @@ -31,7 +45,7 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() + public override NaturalAccel MapToData() { return new NaturalAccel() { @@ -41,43 +55,16 @@ public override Acceleration MapToData() }; } - protected override IEnumerable EnumerateEditableSettings() - { - return [ DecayRate, InputOffset, Limit ]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return Enumerable.Empty(); - } - - protected override NaturalAccel GenerateDefaultDataObject() + protected override bool TryMapEditableSettingsFromData(NaturalAccel data) { - return new NaturalAccel() - { - DecayRate = 0.1, - InputOffset = 0, - Limit = 1.5, - }; + return DecayRate.TryUpdateModelDirectly(data.DecayRate) + & InputOffset.TryUpdateModelDirectly(data.InputOffset) + & Limit.TryUpdateModelDirectly(data.Limit); } - protected override void InitSpecificSettingsAndCollections(NaturalAccel dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(NaturalAccel data) { - DecayRate = new EditableSetting( - displayName: "Decay Rate", - initialValue: dataObject.DecayRate, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - InputOffset = new EditableSetting( - displayName: "Input Offset", - initialValue: dataObject.InputOffset, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Limit = new EditableSetting( - displayName: "Limit", - initialValue: dataObject.Limit, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return true; } } } diff --git a/userspace-backend/Model/AccelDefinitions/Formula/PowerAccelerationDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/Formula/PowerAccelerationDefinitionModel.cs index 87634939..25c4df61 100644 --- a/userspace-backend/Model/AccelDefinitions/Formula/PowerAccelerationDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/Formula/PowerAccelerationDefinitionModel.cs @@ -1,29 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using userspace_backend.Data.Profiles; +using userspace_backend.Data.Profiles.Accel; using userspace_backend.Data.Profiles.Accel.Formula; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions.Formula { - public class PowerAccelerationDefinitionModel : AccelDefinitionModel + public interface IPowerAccelerationDefinitionModel : IAccelDefinitionModelSpecific { - public PowerAccelerationDefinitionModel(Acceleration dataObject) : base(dataObject) + } + + public class PowerAccelerationDefinitionModel + : EditableSettingsSelectable, + IPowerAccelerationDefinitionModel + { + public const string ScaleDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Scale)}"; + public const string ExponentDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(Exponent)}"; + public const string OutputOffsetDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(OutputOffset)}"; + public const string CapDIKey = $"{nameof(ClassicAccelerationDefinitionModel)}.{nameof(CapDIKey)}"; + + public PowerAccelerationDefinitionModel( + [FromKeyedServices(ScaleDIKey)]IEditableSettingSpecific scale, + [FromKeyedServices(ExponentDIKey)]IEditableSettingSpecific exponent, + [FromKeyedServices(OutputOffsetDIKey)]IEditableSettingSpecific outputOffset, + [FromKeyedServices(CapDIKey)]IEditableSettingSpecific cap) + : base([scale, exponent, outputOffset, cap], []) { + Scale = scale; + Exponent = exponent; + OutputOffset = outputOffset; + Cap = cap; } - public EditableSetting Scale { get; set; } + public IEditableSettingSpecific Scale { get; set; } - public EditableSetting Exponent { get; set; } + public IEditableSettingSpecific Exponent { get; set; } - public EditableSetting OutputOffset { get; set; } + public IEditableSettingSpecific OutputOffset { get; set; } - public EditableSetting Cap { get; set; } + public IEditableSettingSpecific Cap { get; set; } - public override AccelArgs MapToDriver() + public AccelArgs MapToDriver() { return new AccelArgs { @@ -36,54 +53,28 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() - { - throw new NotImplementedException(); - } - - protected override IEnumerable EnumerateEditableSettings() - { - return [ Scale, Exponent, OutputOffset, Cap ]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return Enumerable.Empty(); - } - - protected override PowerAccel GenerateDefaultDataObject() + public override PowerAccel MapToData() { return new PowerAccel() { - Scale = 1, - Exponent = 0.05, - Cap = 0, - OutputOffset = 0, + Scale = Scale.ModelValue, + Exponent = Exponent.ModelValue, + OutputOffset = OutputOffset.ModelValue, + Cap = Cap.ModelValue, }; } - protected override void InitSpecificSettingsAndCollections(PowerAccel dataObject) + protected override bool TryMapEditableSettingsFromData(PowerAccel data) + { + return Scale.TryUpdateModelDirectly(data.Scale) + & Exponent.TryUpdateModelDirectly(data.Exponent) + & OutputOffset.TryUpdateModelDirectly(data.OutputOffset) + & Cap.TryUpdateModelDirectly(data.Cap); + } + + protected override bool TryMapEditableSettingsCollectionsFromData(PowerAccel data) { - Scale = new EditableSetting( - displayName: "Scale", - initialValue: dataObject.Scale, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Exponent = new EditableSetting( - displayName: "Exponent", - initialValue: dataObject.Exponent, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - OutputOffset = new EditableSetting( - displayName: "Output Offset", - initialValue: dataObject.OutputOffset, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Cap = new EditableSetting( - displayName: "Cap", - initialValue: dataObject.Cap, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return true; } } } diff --git a/userspace-backend/Model/AccelDefinitions/Formula/SynchronousAccelerationDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/Formula/SynchronousAccelerationDefinitionModel.cs index 7078b3e8..77a90d05 100644 --- a/userspace-backend/Model/AccelDefinitions/Formula/SynchronousAccelerationDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/Formula/SynchronousAccelerationDefinitionModel.cs @@ -1,26 +1,52 @@ -using System.Collections.Generic; -using System.Linq; -using userspace_backend.Data.Profiles; +using Microsoft.Extensions.DependencyInjection; +using userspace_backend.Data.Profiles.Accel; using userspace_backend.Data.Profiles.Accel.Formula; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions.Formula { - public class SynchronousAccelerationDefinitionModel : AccelDefinitionModel + public interface ISynchronousAccelerationDefinitionModel : IEditableSettingsCollectionSpecific { - public SynchronousAccelerationDefinitionModel(Acceleration dataObject) : base(dataObject) + IEditableSettingSpecific SyncSpeed { get; } + + IEditableSettingSpecific Motivity { get; } + + IEditableSettingSpecific Gamma { get; } + + IEditableSettingSpecific Smoothness { get; } + } + + public class SynchronousAccelerationDefinitionModel + : EditableSettingsSelectable, + ISynchronousAccelerationDefinitionModel + { + public const string SyncSpeedDIKey = $"{nameof(SynchronousAccelerationDefinitionModel)}.{nameof(SyncSpeed)}"; + public const string MotivityDIKey = $"{nameof(SynchronousAccelerationDefinitionModel)}.{nameof(Motivity)}"; + public const string GammaDIKey = $"{nameof(SynchronousAccelerationDefinitionModel)}.{nameof(Gamma)}"; + public const string SmoothnessDIKey = $"{nameof(SynchronousAccelerationDefinitionModel)}.{nameof(Smoothness)}"; + + public SynchronousAccelerationDefinitionModel( + [FromKeyedServices(SyncSpeedDIKey)]IEditableSettingSpecific syncSpeed, + [FromKeyedServices(MotivityDIKey)]IEditableSettingSpecific motivity, + [FromKeyedServices(GammaDIKey)]IEditableSettingSpecific gamma, + [FromKeyedServices(SmoothnessDIKey)]IEditableSettingSpecific smoothness) + : base([syncSpeed, motivity, gamma, smoothness], []) { + SyncSpeed = syncSpeed; + Motivity = motivity; + Gamma = gamma; + Smoothness = smoothness; } - public EditableSetting Gamma { get; set; } + public IEditableSettingSpecific SyncSpeed { get; set; } - public EditableSetting Motivity { get; set; } + public IEditableSettingSpecific Motivity { get; set; } - public EditableSetting SyncSpeed { get; set; } + public IEditableSettingSpecific Gamma { get; set; } - public EditableSetting Smoothness { get; set; } + public IEditableSettingSpecific Smoothness { get; set; } - public override AccelArgs MapToDriver() + public AccelArgs MapToDriver() { return new AccelArgs { @@ -32,60 +58,28 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() + public override SynchronousAccel MapToData() { return new SynchronousAccel() { - Gamma = Gamma.ModelValue, - Motivity = Motivity.ModelValue, SyncSpeed = SyncSpeed.ModelValue, + Motivity = Motivity.ModelValue, + Gamma = Gamma.ModelValue, Smoothness = Smoothness.ModelValue, }; } - protected override IEnumerable EnumerateEditableSettings() - { - return [Gamma, Motivity, SyncSpeed, Smoothness]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return Enumerable.Empty(); - } - - protected override SynchronousAccel GenerateDefaultDataObject() + protected override bool TryMapEditableSettingsFromData(SynchronousAccel data) { - return new SynchronousAccel() - { - Gamma = 1, - Motivity = 1.4, - SyncSpeed = 12, - Smoothness = 0.5, - }; + return SyncSpeed.TryUpdateModelDirectly(data.SyncSpeed) + & Motivity.TryUpdateModelDirectly(data.Motivity) + & Gamma.TryUpdateModelDirectly(data.Gamma) + & Smoothness.TryUpdateModelDirectly(data.Smoothness); } - protected override void InitSpecificSettingsAndCollections(SynchronousAccel dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(SynchronousAccel data) { - Gamma = new EditableSetting( - displayName: "Gamma", - dataObject.Gamma, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Motivity = new EditableSetting( - displayName: "Motivity", - dataObject.Motivity, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - SyncSpeed = new EditableSetting( - displayName: "Sync Speed", - dataObject.SyncSpeed, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Smoothness = new EditableSetting( - displayName: "Smoothness", - dataObject.Smoothness, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return true; } } } diff --git a/userspace-backend/Model/AccelDefinitions/FormulaAccelModel.cs b/userspace-backend/Model/AccelDefinitions/FormulaAccelModel.cs index a404f782..2647e121 100644 --- a/userspace-backend/Model/AccelDefinitions/FormulaAccelModel.cs +++ b/userspace-backend/Model/AccelDefinitions/FormulaAccelModel.cs @@ -1,110 +1,48 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using userspace_backend.Data.Profiles; using userspace_backend.Data.Profiles.Accel; using userspace_backend.Data.Profiles.Accel.Formula; using userspace_backend.Model.AccelDefinitions.Formula; using userspace_backend.Model.EditableSettings; +using static userspace_backend.Data.Profiles.Accel.FormulaAccel; namespace userspace_backend.Model.AccelDefinitions { - public class FormulaAccelModel : AccelDefinitionModel + public interface IFormulaAccelModel: IEditableSettingsSelector { - public FormulaAccelModel(Acceleration dataObject) : base(dataObject) - { - FormulaType.PropertyChanged += FormulaTypeChangedEventHandler; - } - - - public EditableSetting FormulaType { get; set; } - - public int FormulaTypeIndex { get => (int)FormulaType.ModelValue; } - - protected Dictionary FormulaModels { get; set; } - - public override AccelArgs MapToDriver() - { - return FormulaModels[FormulaType.ModelValue].MapToDriver(); - } - - public override Acceleration MapToData() - { - return FormulaModels[FormulaType.ModelValue].MapToData(); - } - - public IAccelDefinitionModel GetAccelerationModelOfType(FormulaAccel.AccelerationFormulaType formulaType) - { - return FormulaModels[formulaType]; - } + } - protected void FormulaTypeChangedEventHandler(object? sender, PropertyChangedEventArgs e) - { - // When the formula type changes, contained editable settings collections need to correspond to new type - if (string.Equals(e.PropertyName, nameof(FormulaType.CurrentValidatedValue))) - { - GatherEditableSettingsCollections(); - } - } + public class FormulaAccelModel : + EditableSettingsSelectableSelector, + IFormulaAccelModel + { + public const string SelectionDIKey = $"{nameof(FormulaAccelModel)}.{nameof(Selection)}"; + public const string GainDIKey = $"{nameof(FormulaAccelModel)}.{nameof(Gain)}"; - protected override IEnumerable EnumerateEditableSettings() + public FormulaAccelModel( + IServiceProvider serviceProvider, + [FromKeyedServices(SelectionDIKey)]IEditableSettingSpecific formulaType, + [FromKeyedServices(GainDIKey)]IEditableSettingSpecific gain) + : base(serviceProvider, formulaType, [gain], []) { - return [ FormulaType ]; + Gain = gain; } - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return [ FormulaModels[FormulaType.ModelValue] ]; - } + public IEditableSettingSpecific Gain { get; set; } - protected override FormulaAccel GenerateDefaultDataObject() - { - return new LinearAccel() - { - Acceleration = 0.001, - Cap = 0, - Offset = 0, - }; - } + public AccelArgs MapToDriver() => ((IAccelDefinitionModel)Selected)?.MapToDriver() ?? new AccelArgs(); - protected override void InitSpecificSettingsAndCollections(FormulaAccel dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(FormulaAccel data) { - FormulaType = new EditableSetting( - displayName: "Formula Type", - initialValue: dataObject.FormulaType, - parser: UserInputParsers.AccelerationFormulaTypeParser, - validator: ModelValueValidators.DefaultAccelerationFormulaTypeValidator, - autoUpdateFromInterface: true); - - FormulaModels = new Dictionary(); - - foreach (FormulaAccel.AccelerationFormulaType formulaType in Enum.GetValues(typeof(FormulaAccel.AccelerationFormulaType))) - { - FormulaModels.Add(formulaType, CreateAccelerationDefinitionModelOfType(formulaType, dataObject)); - } + return Selected.TryMapFromData(data); } - protected IAccelDefinitionModel CreateAccelerationDefinitionModelOfType(FormulaAccel.AccelerationFormulaType formulaType, Acceleration dataObject) + protected override bool TryMapEditableSettingsFromData(FormulaAccel data) { - switch (formulaType) - { - case FormulaAccel.AccelerationFormulaType.Synchronous: - return new SynchronousAccelerationDefinitionModel(dataObject); - case FormulaAccel.AccelerationFormulaType.Jump: - return new JumpAccelerationDefinitionModel(dataObject); - case FormulaAccel.AccelerationFormulaType.Power: - return new PowerAccelerationDefinitionModel(dataObject); - case FormulaAccel.AccelerationFormulaType.Natural: - return new NaturalAccelerationDefinitionModel(dataObject); - case FormulaAccel.AccelerationFormulaType.Classic: - return new ClassicAccelerationDefinitionModel(dataObject); - case FormulaAccel.AccelerationFormulaType.Linear: - default: - return new LinearAccelerationDefinitionModel(dataObject); - } + return Gain.TryUpdateModelDirectly(data.Gain); } } } diff --git a/userspace-backend/Model/AccelDefinitions/LookupTableDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/LookupTableDefinitionModel.cs index 1a4057fa..a414a2fe 100644 --- a/userspace-backend/Model/AccelDefinitions/LookupTableDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/LookupTableDefinitionModel.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Linq; -using System.Text; -using System.Threading.Tasks; using userspace_backend.Data.Profiles; using userspace_backend.Data.Profiles.Accel; using userspace_backend.Model.EditableSettings; @@ -10,17 +8,33 @@ namespace userspace_backend.Model.AccelDefinitions { - public class LookupTableDefinitionModel : AccelDefinitionModel + public interface ILookupTableDefinitionModel : IAccelDefinitionModelSpecific { - public LookupTableDefinitionModel(Acceleration dataObject) : base(dataObject) + IEditableSettingSpecific ApplyAs { get; } + + IEditableSettingSpecific Data { get; } + + } + + public class LookupTableDefinitionModel : EditableSettingsSelectable, ILookupTableDefinitionModel + { + public const string ApplyAsDIKey = $"{nameof(LookupTableDefinitionModel)}.{nameof(ApplyAs)}"; + public const string DataDIKey = $"{nameof(LookupTableDefinitionModel)}.{nameof(Data)}"; + + public LookupTableDefinitionModel( + [FromKeyedServices(ApplyAsDIKey)]IEditableSettingSpecific applyAs, + [FromKeyedServices(DataDIKey)]IEditableSettingSpecific data) + : base([applyAs, data], []) { + ApplyAs = applyAs; + Data = data; } - public EditableSetting ApplyAs { get; set; } + public IEditableSettingSpecific ApplyAs { get; set; } - public EditableSetting Data { get; set; } + public IEditableSettingSpecific Data { get; set; } - public override AccelArgs MapToDriver() + public AccelArgs MapToDriver() { // data in driver profile must be predefined length for marshalling purposes var accelArgsData = new float[AccelArgs.MaxLutPoints*2]; @@ -34,7 +48,7 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() + public override LookupTableAccel MapToData() { return new LookupTableAccel() { @@ -43,37 +57,15 @@ public override Acceleration MapToData() }; } - protected override IEnumerable EnumerateEditableSettings() + protected override bool TryMapEditableSettingsFromData(LookupTableAccel data) { - return [ApplyAs, Data]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return Enumerable.Empty(); - } - - protected override LookupTableAccel GenerateDefaultDataObject() - { - return new LookupTableAccel() - { - ApplyAs = LookupTableType.Velocity, - Data = [], - }; + return ApplyAs.TryUpdateModelDirectly(data.ApplyAs) + & Data.TryUpdateModelDirectly(new LookupTableData(data.Data)); } - protected override void InitSpecificSettingsAndCollections(LookupTableAccel dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(LookupTableAccel data) { - ApplyAs = new EditableSetting( - displayName: "Apply as", - initialValue: dataObject.ApplyAs, - parser: UserInputParsers.LookupTableTypeParser, - validator: ModelValueValidators.DefaultLookupTableTypeValidator); - Data = new EditableSetting( - displayName: "Data", - initialValue: new LookupTableData(dataObject.Data), - parser: UserInputParsers.LookupTableDataParser, - validator: ModelValueValidators.DefaultLookupTableDataValidator); + return true; } } diff --git a/userspace-backend/Model/AccelDefinitions/NoAccelDefinitionModel.cs b/userspace-backend/Model/AccelDefinitions/NoAccelDefinitionModel.cs index f1f21c9f..5155103e 100644 --- a/userspace-backend/Model/AccelDefinitions/NoAccelDefinitionModel.cs +++ b/userspace-backend/Model/AccelDefinitions/NoAccelDefinitionModel.cs @@ -1,24 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data.Profiles; +using userspace_backend.Data.Profiles; using userspace_backend.Data.Profiles.Accel; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.AccelDefinitions { - public class NoAccelDefinitionModel : AccelDefinitionModel + public interface INoAccelDefinitionModel : IAccelDefinitionModelSpecific { - public NoAccelDefinitionModel(Acceleration dataObject) : base(dataObject) + } + + public class NoAccelDefinitionModel : EditableSettingsSelectable, INoAccelDefinitionModel + { + public NoAccelDefinitionModel() + : base([], []) { NoAcceleration = new NoAcceleration(); } public NoAcceleration NoAcceleration { get; protected set; } - public override AccelArgs MapToDriver() + public AccelArgs MapToDriver() { return new AccelArgs() { @@ -26,29 +26,19 @@ public override AccelArgs MapToDriver() }; } - public override Acceleration MapToData() + public override NoAcceleration MapToData() { return NoAcceleration; } - protected override IEnumerable EnumerateEditableSettings() - { - return Enumerable.Empty(); - } - - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return Enumerable.Empty(); - } - - protected override NoAcceleration GenerateDefaultDataObject() + protected override bool TryMapEditableSettingsFromData(NoAcceleration data) { - return new NoAcceleration(); + return true; } - protected override void InitSpecificSettingsAndCollections(NoAcceleration dataObject) + protected override bool TryMapEditableSettingsCollectionsFromData(NoAcceleration data) { - // Nothing to do here since no acceleration has no settings + return true; } } } diff --git a/userspace-backend/Model/DeviceGroupModel.cs b/userspace-backend/Model/DeviceGroupModel.cs deleted file mode 100644 index a1c4df15..00000000 --- a/userspace-backend/Model/DeviceGroupModel.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data; -using userspace_backend.Model.EditableSettings; - -namespace userspace_backend.Model -{ - public class DeviceGroupModel : EditableSetting, IComparable - { - public DeviceGroupModel(string dataObject, IModelValueValidator validator) - : base("Device Group", dataObject, UserInputParsers.StringParser, validator) - { - } - - public int CompareTo(object? obj) - { - DeviceGroupModel other = obj as DeviceGroupModel; - - if (other == null) - { - return int.MaxValue; - } - - return other.ModelValue.CompareTo(ModelValue); - } - - public override bool Equals(object? obj) - { - return obj is DeviceGroupModel model && - string.Equals(model.ModelValue, this.ModelValue, StringComparison.InvariantCultureIgnoreCase); - } - - public override int GetHashCode() - { - return HashCode.Combine(ModelValue); - } - } -} diff --git a/userspace-backend/Model/DeviceGroups.cs b/userspace-backend/Model/DeviceGroups.cs index cf5d02cc..aa908b5c 100644 --- a/userspace-backend/Model/DeviceGroups.cs +++ b/userspace-backend/Model/DeviceGroups.cs @@ -7,10 +7,14 @@ namespace userspace_backend.Model { - public class DeviceGroups : EditableSettingsCollection> + public interface IDeviceGroups { - public static readonly DeviceGroupModel DefaultDeviceGroup = - new DeviceGroupModel("Default", ModelValueValidators.AllChangesInvalidStringValidator); + bool TryGetDeviceGroup(string name, out string? deviceGroup); + } + + public class DeviceGroups : EditableSettingsCollection>, IDeviceGroups + { + public const string DefaultDeviceGroup = "Default"; public DeviceGroups(IEnumerable devices) : base(devices) @@ -18,23 +22,23 @@ public DeviceGroups(IEnumerable devices) GroupNameChangeValidator = new DeviceGroupValidator(this); } - public ObservableCollection DeviceGroupModels { get; set; } + public ObservableCollection DeviceGroupModels { get; set; } protected DeviceGroupValidator GroupNameChangeValidator { get; set; } - public bool TryGetDeviceGroup(string name, out DeviceGroupModel? deviceGroup) + public bool TryGetDeviceGroup(string name, out string? deviceGroup) { deviceGroup = DeviceGroupModels.FirstOrDefault( - g => string.Equals(g.ModelValue, name, StringComparison.InvariantCultureIgnoreCase)); + g => string.Equals(g, name, StringComparison.InvariantCultureIgnoreCase)); return deviceGroup is not null; } - public DeviceGroupModel AddOrGetDeviceGroup(string deviceGroupName) + public string AddOrGetDeviceGroup(string deviceGroupName) { - if (!TryGetDeviceGroup(deviceGroupName, out DeviceGroupModel? deviceGroup)) + if (!TryGetDeviceGroup(deviceGroupName, out string? deviceGroup)) { - deviceGroup = new DeviceGroupModel(deviceGroupName, GroupNameChangeValidator); + deviceGroup = deviceGroupName; DeviceGroupModels.Add(deviceGroup); } @@ -75,27 +79,26 @@ public bool TryAddDeviceGroup(string? deviceGroupName = null) return false; } - DeviceGroupModel deviceGroup = new DeviceGroupModel(deviceGroupName, GroupNameChangeValidator); - DeviceGroupModels.Add(deviceGroup); + DeviceGroupModels.Add(deviceGroupName); return true; } - public bool RemoveDeviceGroup(DeviceGroupModel deviceGroup) + public bool RemoveDeviceGroup(string deviceGroup) { return DeviceGroupModels.Remove(deviceGroup); } public override IEnumerable MapToData() { - return DeviceGroupModels.Select(g => g.ModelValue); + return DeviceGroupModels; } protected override IEnumerable EnumerateEditableSettings() { - return DeviceGroupModels; + return []; } - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override IEnumerable EnumerateEditableSettingsCollections() { return []; } @@ -104,7 +107,7 @@ protected override void InitEditableSettingsAndCollections(IEnumerable d { // This initialization does not set up all device group models. // That is done in backend construction in order to point the devices to their groups. - DeviceGroupModels = new ObservableCollection() { DefaultDeviceGroup }; + DeviceGroupModels = new ObservableCollection() { DefaultDeviceGroup }; } } diff --git a/userspace-backend/Model/DeviceModel.cs b/userspace-backend/Model/DeviceModel.cs index ac8943f7..8b5bfab9 100644 --- a/userspace-backend/Model/DeviceModel.cs +++ b/userspace-backend/Model/DeviceModel.cs @@ -1,91 +1,85 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using Microsoft.Extensions.DependencyInjection; using userspace_backend.Data; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model { - public class DeviceModel : EditableSettingsCollection + public interface IDeviceModel : IEditableSettingsCollectionSpecific { - public DeviceModel( - Device device, - DeviceGroupModel deviceGroup, - DeviceModelNameValidator deviceModelNameValidator, - DeviceModelHWIDValidator deviceModelHWIDValidator) - : base(device) - { - DeviceGroup = deviceGroup; - Name.Validator = deviceModelNameValidator; - HardwareID.Validator = deviceModelHWIDValidator; - } - - public EditableSetting Name { get; protected set; } - - public EditableSetting HardwareID { get; protected set; } + IEditableSettingSpecific Name { get; } - public EditableSetting DPI { get; protected set; } + IEditableSettingSpecific HardwareID { get; } - public EditableSetting PollRate { get; protected set; } + IEditableSettingSpecific DPI { get; } - public EditableSetting Ignore { get; protected set; } + IEditableSettingSpecific PollRate { get; } - public DeviceGroupModel DeviceGroup { get; set; } + IEditableSettingSpecific Ignore { get; } - protected DeviceModelNameValidator DeviceModelNameValidator { get; } + IEditableSettingSpecific DeviceGroup { get; } + } - protected DeviceModelHWIDValidator DeviceModelHWIDValidator { get; } + public class DeviceModel : NamedEditableSettingsCollection, IDeviceModel + { + public const string NameDIKey = $"{nameof(DeviceModel)}.{nameof(Name)}"; + public const string HardwareIDDIKey = $"{nameof(DeviceModel)}.{nameof(HardwareID)}"; + public const string DPIDIKey = $"{nameof(DeviceModel)}.{nameof(DPI)}"; + public const string PollRateDIKey = $"{nameof(DeviceModel)}.{nameof(PollRate)}"; + public const string IgnoreDIKey = $"{nameof(DeviceModel)}.{nameof(Ignore)}"; + public const string DeviceGroupDIKey = $"{nameof(DeviceModel)}.{nameof(DeviceGroup)}"; - protected override IEnumerable EnumerateEditableSettings() + public DeviceModel( + [FromKeyedServices(NameDIKey)] IEditableSettingSpecific name, + [FromKeyedServices(HardwareIDDIKey)] IEditableSettingSpecific hardwareID, + [FromKeyedServices(DPIDIKey)] IEditableSettingSpecific dpi, + [FromKeyedServices(PollRateDIKey)] IEditableSettingSpecific pollRate, + [FromKeyedServices(IgnoreDIKey)] IEditableSettingSpecific ignore, + [FromKeyedServices(DeviceGroupDIKey)] IEditableSettingSpecific deviceGroup) + : base(name, [hardwareID, dpi, pollRate, ignore, deviceGroup], []) { - return [Name, HardwareID, DPI, PollRate, Ignore, DeviceGroup]; + HardwareID = hardwareID; + DPI = dpi; + PollRate = pollRate; + Ignore = ignore; + DeviceGroup = deviceGroup; } - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return []; - } + public IEditableSettingSpecific HardwareID { get; protected set; } - protected override void InitEditableSettingsAndCollections(Device device) - { - Name = new EditableSetting( - displayName: "Name", - initialValue: device.Name, - parser: UserInputParsers.StringParser, - validator: ModelValueValidators.DefaultStringValidator); - HardwareID = new EditableSetting( - displayName: "Hardware ID", - initialValue: device.HWID, - parser: UserInputParsers.StringParser, - validator: ModelValueValidators.DefaultStringValidator); - DPI = new EditableSetting( - displayName: "DPI", - initialValue: device.DPI, - parser: UserInputParsers.IntParser, - validator: ModelValueValidators.DefaultIntValidator); - PollRate = new EditableSetting( - displayName: "Polling Rate", - initialValue: device.PollingRate, - parser: UserInputParsers.IntParser, - validator: ModelValueValidators.DefaultIntValidator); - Ignore = new EditableSetting( - displayName: "Ignore", - initialValue: device.Ignore, - parser: UserInputParsers.BoolParser, - validator: ModelValueValidators.DefaultBoolValidator); - } + public IEditableSettingSpecific DPI { get; protected set; } + + public IEditableSettingSpecific PollRate { get; protected set; } + + public IEditableSettingSpecific Ignore { get; protected set; } + + public IEditableSettingSpecific DeviceGroup { get; set; } public override Device MapToData() { return new Device() { - Name = this.Name.ModelValue, - HWID = this.HardwareID.ModelValue, - DPI = this.DPI.ModelValue, - PollingRate = this.PollRate.ModelValue, - Ignore = this.Ignore.ModelValue, + Name = Name.ModelValue, + HWID = HardwareID.ModelValue, + DPI = DPI.ModelValue, + PollingRate = PollRate.ModelValue, + Ignore = Ignore.ModelValue, DeviceGroup = DeviceGroup.ModelValue, }; } + + protected override bool TryMapEditableSettingsFromData(Device data) + { + return Name.TryUpdateModelDirectly(data.Name) + & HardwareID.TryUpdateModelDirectly(data.HWID) + & DPI.TryUpdateModelDirectly(data.DPI) + & PollRate.TryUpdateModelDirectly(data.PollingRate) + & Ignore.TryUpdateModelDirectly(data.Ignore) + & DeviceGroup.TryUpdateModelDirectly(data.DeviceGroup); + } + + protected override bool TryMapEditableSettingsCollectionsFromData(Device data) + { + return true; + } } } diff --git a/userspace-backend/Model/DevicesModel.cs b/userspace-backend/Model/DevicesModel.cs index 3558e4b0..f3db5cdd 100644 --- a/userspace-backend/Model/DevicesModel.cs +++ b/userspace-backend/Model/DevicesModel.cs @@ -8,142 +8,44 @@ namespace userspace_backend.Model { - public class DevicesModel + public interface IDevicesModel : IEditableSettingsList { - public DevicesModel() + } + + public class DevicesModel : EditableSettingsList, IDevicesModel + { + public DevicesModel( + IServiceProvider serviceProvider, + ISystemDevicesProvider systemDevicesProvider) + : base(serviceProvider, [], []) { - Devices = new ObservableCollection(); - DeviceGroups = new DeviceGroups([]); - DeviceModelNameValidator = new DeviceModelNameValidator(this); - DeviceModelHWIDValidator = new DeviceModelHWIDValidator(this); - SystemDevices = new ObservableCollection(); - RefreshSystemDevices(); + SystemDevices = systemDevicesProvider; } public DeviceGroups DeviceGroups { get; set; } - public IEnumerable DevicesEnumerable { get => Devices; } - - public ObservableCollection Devices { get; set; } - - public ObservableCollection SystemDevices { get; protected set; } + public ISystemDevicesProvider SystemDevices { get; protected set; } - protected DeviceModelNameValidator DeviceModelNameValidator { get; } + protected override string DefaultNameTemplate => "Device"; - protected DeviceModelHWIDValidator DeviceModelHWIDValidator { get; } - - public bool DoesDeviceAlreadyExist(string name, string hwid) + protected override string GetNameFromElement(IDeviceModel element) { - return Devices.Any(d => - string.Equals(d.Name.ModelValue, name, StringComparison.InvariantCultureIgnoreCase) - || string.Equals(d.HardwareID.ModelValue, hwid, StringComparison.InvariantCultureIgnoreCase)); + return element.Name.ModelValue; } - public bool DoesDeviceNameAlreadyExist(string name) + protected override void SetElementName(IDeviceModel element, string name) { - return Devices.Any(d => - string.Equals(d.Name.ModelValue, name, StringComparison.InvariantCultureIgnoreCase)); + element.Name.TryUpdateModelDirectly(name); } - public bool DoesDeviceHardwareIDAlreadyExist(string hwid) + protected override string GetNameFromData(Device data) { - return Devices.Any(d => - string.Equals(d.HardwareID.ModelValue, hwid, StringComparison.InvariantCultureIgnoreCase)); - } - - protected bool TryGetDefaultDevice([MaybeNullWhen(false)] out Device device) - { - for (int i = 0; i < 10; i++) - { - string deviceNameToAdd = $"Device{i}"; - if (DoesDeviceNameAlreadyExist(deviceNameToAdd)) - { - continue; - } - - device = new() - { - Name = deviceNameToAdd, - HWID = "", - DPI = 1600, - PollingRate = 1000, - DeviceGroup = "Default", - }; - - return true; - } - - device = null; - return false; + return data.Name; } - public bool TryAddDevice(Device? deviceData = null) + protected override bool TryMapEditableSettingsFromData(IEnumerable data) { - if (deviceData is null) - { - if (!TryGetDefaultDevice(out var defaultDevice)) - { - return false; - } - - deviceData = defaultDevice; - } - else if (DoesDeviceAlreadyExist(deviceData.Name, deviceData.HWID)) - { - return false; - } - - DeviceGroupModel deviceGroup = DeviceGroups.AddOrGetDeviceGroup(deviceData.DeviceGroup); - DeviceModel deviceModel = new DeviceModel(deviceData, deviceGroup, DeviceModelNameValidator, DeviceModelHWIDValidator); - Devices.Add(deviceModel); - return true; } - - public bool RemoveDevice(DeviceModel device) - { - return Devices.Remove(device); - } - - protected void RefreshSystemDevices() - { - SystemDevices.Clear(); - var systemDevicesList = MultiHandleDevice.GetList(); - foreach (var systemDevice in systemDevicesList) - { - SystemDevices.Add(systemDevice); - } - } - } - - public class DeviceModelNameValidator : IModelValueValidator - { - public DeviceModelNameValidator(DevicesModel devices) - { - Devices = devices; - } - - public DevicesModel Devices { get; } - - public bool Validate(string modelValue) - { - return !Devices.DoesDeviceNameAlreadyExist(modelValue); - } } - - public class DeviceModelHWIDValidator : IModelValueValidator - { - public DeviceModelHWIDValidator(DevicesModel devices) - { - Devices = devices; - } - - public DevicesModel Devices { get; } - - public bool Validate(string modelValue) - { - return !Devices.DoesDeviceHardwareIDAlreadyExist(modelValue); - } - } - } diff --git a/userspace-backend/Model/EditableSettings/EditableSetting.cs b/userspace-backend/Model/EditableSettings/EditableSetting.cs index f4d5aa68..35c296e4 100644 --- a/userspace-backend/Model/EditableSettings/EditableSetting.cs +++ b/userspace-backend/Model/EditableSettings/EditableSetting.cs @@ -3,7 +3,7 @@ namespace userspace_backend.Model.EditableSettings { - public partial class EditableSetting : ObservableObject, IEditableSetting where T : IComparable + public partial class EditableSetting : ObservableObject, IEditableSettingSpecific where T : IComparable { /// /// This value can be bound in UI for direct editing @@ -11,17 +11,11 @@ public partial class EditableSetting : ObservableObject, IEditableSetting whe [ObservableProperty] public string interfaceValue; - /// - /// This value can be bound in UI for readonly display of validated input - /// - [ObservableProperty] - public string currentValidatedValueString; - /// /// This value can be bound in UI for logic based on validated input /// [ObservableProperty] - public T currentValidatedValue; + public T modelValue; public EditableSetting( string displayName, @@ -44,12 +38,9 @@ public EditableSetting( /// public string DisplayName { get; } - public virtual T ModelValue { get; protected set; } - + // TODO: test or remove public T LastWrittenValue { get; protected set; } - public string EditedValueForDiplay { get => ModelValue?.ToString() ?? string.Empty; } - /// /// Interface can set this for cases when new value arrives all at once (such as menu selection) /// instead of cases where new value arrives in parts (typing) @@ -105,8 +96,6 @@ protected void UpdateModelValueFromLastKnown() protected void UpdatedModeValue(T value) { ModelValue = value; - CurrentValidatedValue = ModelValue; - CurrentValidatedValueString = ModelValue?.ToString(); } partial void OnInterfaceValueChanged(string value) @@ -116,5 +105,145 @@ partial void OnInterfaceValueChanged(string value) TryUpdateFromInterface(); } } + + public bool TryUpdateModelDirectly(T data) + { + throw new NotImplementedException(); + } + } + + public partial class EditableSettingV2 : ObservableObject, IEditableSettingSpecific where T : IComparable + { + /// + /// This value can be bound in UI for direct editing + /// + [ObservableProperty] + public string interfaceValue; + + /// + /// This value can be bound in UI for logic based on validated input + /// + [ObservableProperty] + public T modelValue; + + public EditableSettingV2( + string displayName, + T initialValue, + IUserInputParser parser, + IModelValueValidator validator, + bool autoUpdateFromInterface = false) + { + DisplayName = displayName; + LastWrittenValue = initialValue; + Parser = parser; + Validator = validator; + UpdateModelValueFromLastKnown(); + SetInterfaceToModel(); + AutoUpdateFromInterface = autoUpdateFromInterface; + } + + /// + /// Display name for this setting in UI + /// + public string DisplayName { get; } + + public T LastWrittenValue { get; protected set; } + + /// + /// Interface can set this for cases when new value arrives all at once (such as menu selection) + /// instead of cases where new value arrives in parts (typing) + /// + public bool AutoUpdateFromInterface { get; set; } + + private IUserInputParser Parser { get; } + + //TODO: change settings collections init so that this can be made private for non-static validators + public IModelValueValidator Validator { get; set; } + + private bool AllowAutoUpdateFromInterface { get; set; } = true; + + public bool HasChanged() => ModelValue.CompareTo(LastWrittenValue) == 0; + + public bool TryUpdateFromInterface() + { + bool result = TryUpdateFromInterfaceImpl(out bool editedInterfaceNeedsReset); + + if (editedInterfaceNeedsReset) + { + SetInterfaceToModel(); + } + + return result; + } + + protected bool TryUpdateFromInterfaceImpl(out bool editedInterfaceNeedsReset) + { + editedInterfaceNeedsReset = true; + + if (string.IsNullOrEmpty(InterfaceValue)) + { + return false; + } + + if (!Parser.TryParse(InterfaceValue.Trim(), out T parsedValue)) + { + return false; + } + + return TryUpdateModelDirectlyImpl(parsedValue, out editedInterfaceNeedsReset); + } + + protected void SetInterfaceToModel() + { + bool previous = AllowAutoUpdateFromInterface; + AllowAutoUpdateFromInterface = false; + InterfaceValue = ModelValue?.ToString(); + AllowAutoUpdateFromInterface = true; + } + + protected void UpdateModelValueFromLastKnown() + { + UpdateModeValue(LastWrittenValue); + } + + protected void UpdateModeValue(T value) + { + ModelValue = value; + } + + partial void OnInterfaceValueChanged(string value) + { + // TODO: double-check race conditions + if (AutoUpdateFromInterface && AllowAutoUpdateFromInterface) + { + TryUpdateFromInterface(); + } + } + + // TODO: unit test + public bool TryUpdateModelDirectly(T data) + { + return TryUpdateModelDirectlyImpl(data, out _); + } + + private bool TryUpdateModelDirectlyImpl(T data, out bool editedInterfaceNeedsReset) + { + editedInterfaceNeedsReset = false; + + if (data.CompareTo(ModelValue) == 0) + { + return true; + } + + if (!Validator.Validate(data)) + { + editedInterfaceNeedsReset = true; + return false; + } + + UpdateModeValue(data); + return true; + + } } } diff --git a/userspace-backend/Model/EditableSettings/EditableSettingsCollection.cs b/userspace-backend/Model/EditableSettings/EditableSettingsCollection.cs index 226ee7b2..64c15d1f 100644 --- a/userspace-backend/Model/EditableSettings/EditableSettingsCollection.cs +++ b/userspace-backend/Model/EditableSettings/EditableSettingsCollection.cs @@ -6,7 +6,7 @@ namespace userspace_backend.Model.EditableSettings { - public abstract class EditableSettingsCollection : ObservableObject, IEditableSettingsCollection + public abstract class EditableSettingsCollection : ObservableObject, IEditableSettingsCollectionV2 { public EditableSettingsCollection(T dataObject) { @@ -19,7 +19,7 @@ public EditableSettingsCollection(T dataObject) public IEnumerable AllContainedEditableSettings { get; set; } - public IEnumerable AllContainedEditableSettingsCollections { get; set; } + public IEnumerable AllContainedEditableSettingsCollections { get; set; } public bool HasChanged { get; protected set; } @@ -63,7 +63,7 @@ public void GatherEditableSettingsCollections() protected void EditableSettingChangedEventHandler(object? sender, PropertyChangedEventArgs e) { - if (string.Equals(e.PropertyName, nameof(EditableSetting.CurrentValidatedValue))) + if (string.Equals(e.PropertyName, nameof(IEditableSettingSpecific.ModelValue))) { OnAnySettingChanged(); } @@ -83,8 +83,144 @@ protected void OnAnySettingChanged() protected abstract IEnumerable EnumerateEditableSettings(); - protected abstract IEnumerable EnumerateEditableSettingsCollections(); + protected abstract IEnumerable EnumerateEditableSettingsCollections(); public abstract T MapToData(); } + + /// + /// Collection of editable settings and other collections. + /// Internal node of settings tree. + /// + /// + /// The settings model for this backend is a composed object containing other composed objects and settings. + /// In this way, the objects form a tree. The root node is the base model class, the internal nodes are objects containing other objects + /// and settings, and the settings themselves are the leaf nodes. + /// This interface is then an internal node of the tree. It can contain other settings collections (internal nodes) and also contain + /// settings themselves. + /// + public interface IEditableSettingsCollectionV2 + { + public bool HasChanged { get; } + + public EventHandler AnySettingChanged { get; set; } + } + + public interface IEditableSettingsCollectionSpecific : IEditableSettingsCollectionV2 + { + T MapToData(); + + bool TryMapFromData(T data); + } + + public interface INamedEditableSettingsCollectionSpecific : IEditableSettingsCollectionSpecific + { + public IEditableSettingSpecific Name { get; } + } + + /// + /// Base class for settings collections. + /// + /// + /// Each unique set of collection logic should be generalized into a class that is either this class or a child of this class, + /// but not the actual class in the model. + /// This class, and any child class that is a parent to actual settings collections in the model, requires unit tests. + /// The actual settings collections in the model do not need to each be tested beyond composition. + /// + /// + public abstract class EditableSettingsCollectionV2 : ObservableObject, IEditableSettingsCollectionSpecific + { + public EditableSettingsCollectionV2( + IEnumerable editableSettings, + IEnumerable editableSettingsCollections) + { + AllContainedEditableSettings = editableSettings; + AllContainedEditableSettingsCollections = editableSettingsCollections; + + foreach (var setting in AllContainedEditableSettings) + { + // TODO: revisit settings composition so that this null check is unnecessary + if (setting != null) + { + setting.PropertyChanged += EditableSettingChangedEventHandler; + } + } + + // TODO: separate "All" and "currently selected" settings collections + // so that incorrect assignment is not done here for collections that alter this through use + foreach (var settingsCollection in AllContainedEditableSettingsCollections) + { + settingsCollection.AnySettingChanged += EditableSettingsCollectionChangedEventHandler; + } + } + + public EventHandler AnySettingChanged { get; set; } + + public IEnumerable AllContainedEditableSettings { get; set; } + + public IEnumerable AllContainedEditableSettingsCollections { get; set; } + + public bool HasChanged { get; protected set; } + + public bool TryMapFromData(T data) + { + bool result = true; + + result &= TryMapEditableSettingsFromData(data); + result &= TryMapEditableSettingsCollectionsFromData(data); + + return result; + } + + public void EvaluateWhetherHasChanged() + { + if (AllContainedEditableSettings.Any(s => s.HasChanged()) || + AllContainedEditableSettingsCollections.Any(c => c.HasChanged)) + { + HasChanged = true; + } + else + { + HasChanged = false; + } + } + + protected void EditableSettingChangedEventHandler(object? sender, PropertyChangedEventArgs e) + { + if (string.Equals(e.PropertyName, nameof(IEditableSettingSpecific.ModelValue))) + { + OnAnySettingChanged(); + } + } + + protected void EditableSettingsCollectionChangedEventHandler(object? sender, EventArgs e) + { + OnAnySettingChanged(); + } + + protected void OnAnySettingChanged() + { + AnySettingChanged?.Invoke(this, new EventArgs()); + } + + public abstract T MapToData(); + + protected abstract bool TryMapEditableSettingsFromData(T data); + + protected abstract bool TryMapEditableSettingsCollectionsFromData(T data); + } + + public abstract class NamedEditableSettingsCollection : EditableSettingsCollectionV2, INamedEditableSettingsCollectionSpecific + { + public NamedEditableSettingsCollection( + IEditableSettingSpecific name, + IEnumerable editableSettings, + IEnumerable editableSettingsCollections) + : base(editableSettings.Union([name]), editableSettingsCollections) + { + Name = name; + } + + public IEditableSettingSpecific Name { get; } + } } diff --git a/userspace-backend/Model/EditableSettings/EditableSettingsList.cs b/userspace-backend/Model/EditableSettings/EditableSettingsList.cs new file mode 100644 index 00000000..7e85777e --- /dev/null +++ b/userspace-backend/Model/EditableSettings/EditableSettingsList.cs @@ -0,0 +1,159 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace userspace_backend.Model.EditableSettings +{ + public interface IEditableSettingsList + : IEditableSettingsCollectionSpecific> where T : IEditableSettingsCollectionSpecific + { + public ReadOnlyObservableCollection Elements { get; } + + public bool TryAddNewDefault(); + + public bool TryAdd(T element); + + public bool TryInsert(int index, T element); + + public bool TryGetElement(string name, out T? element); + + public bool TryRemoveElement(T element); + } + + public abstract class EditableSettingsList + : EditableSettingsCollectionV2>, IEditableSettingsList where T : IEditableSettingsCollectionSpecific + { + public EditableSettingsList( + IServiceProvider serviceProvider, + IEnumerable editableSettings, + IEnumerable editableSettingsCollections) + : base(editableSettings, editableSettingsCollections) + { + ServiceProvider = serviceProvider; + ElementsInternal = new ObservableCollection(); + Elements = new ReadOnlyObservableCollection(ElementsInternal); + } + + public ReadOnlyObservableCollection Elements { get; } + + protected ObservableCollection ElementsInternal { get; } + + protected IServiceProvider ServiceProvider { get; } + + protected abstract string DefaultNameTemplate { get; } + + public override IEnumerable MapToData() + { + return ElementsInternal.Select(e => e.MapToData()); + } + + public bool TryAdd(T element) + { + string name = GetNameFromElement(element); + + if (!ContainsElementWithName(name)) + { + AddElement(element); + return true; + } + + return false; + } + + public bool TryInsert(int index, T element) + { + string name = GetNameFromElement(element); + + if (!ContainsElementWithName(name)) + { + ElementsInternal.Insert(index, element); + return true; + } + + return false; + } + + public bool TryAddNewDefault() + { + for (int i = 1; i < 10; i++) + { + string defaultProfileName = GenerateDefaultName(i); + + if (!ContainsElementWithName(defaultProfileName)) + { + T newDefaultElement = GenerateDefaultElement(defaultProfileName); + AddElement(newDefaultElement); + + return true; + } + } + + return false; + } + + public bool TryGetElement(string name, out T? element) + { + element = default; + + foreach (T elementInList in ElementsInternal) + { + if (string.Equals(name, GetNameFromElement(elementInList), StringComparison.InvariantCultureIgnoreCase)) + { + element = elementInList; + return true; + } + } + + return false; + } + + public bool TryRemoveElement(T element) + { + return ElementsInternal.Remove(element); + } + + protected bool ContainsElementWithName(string name) => TryGetElement(name, out T? _); + + protected string GenerateDefaultName(int index) => $"{DefaultNameTemplate}{index}"; + + protected void AddElement(T element) + { + ElementsInternal.Add(element); + } + + protected T GenerateDefaultElement(string name) + { + T newDefaultElement = ServiceProvider.GetRequiredService(); + SetElementName(newDefaultElement, name); + + return newDefaultElement; + } + + protected abstract string GetNameFromElement(T element); + + protected abstract void SetElementName(T element, string name); + + protected abstract string GetNameFromData(U data); + + protected override bool TryMapEditableSettingsCollectionsFromData(IEnumerable data) + { + bool result = true; + + foreach (U dataElement in data) + { + string elementName = GetNameFromData(dataElement); + + if (!TryGetElement(elementName, out T? element)) + { + element = GenerateDefaultElement(elementName); + } + + result &= element!.TryMapFromData(dataElement); + } + + return result; + } + } +} diff --git a/userspace-backend/Model/EditableSettings/EditableSettingsSelector.cs b/userspace-backend/Model/EditableSettings/EditableSettingsSelector.cs new file mode 100644 index 00000000..2a33477d --- /dev/null +++ b/userspace-backend/Model/EditableSettings/EditableSettingsSelector.cs @@ -0,0 +1,121 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace userspace_backend.Model.EditableSettings +{ + public interface IEditableSettingsSelector : IEditableSettingsCollectionSpecific where T : Enum + { + public IEditableSettingSpecific Selection { get; } + + public IEditableSettingsCollectionSpecific GetSelectable(T choice); + } + + public static class EditableSettingsSelectorHelper + { + public static string GetSelectionKey(T value) where T : Enum + { + return $"{typeof(T)}.{value.ToString()}"; + } + + } + + public abstract class EditableSettingsSelectableIntermediate : + EditableSettingsCollectionV2 + { + protected EditableSettingsSelectableIntermediate( + IEnumerable editableSettings, + IEnumerable editableSettingsCollections) + : base(editableSettings, editableSettingsCollections) + { + } + } + + public abstract class EditableSettingsSelectable : + EditableSettingsSelectableIntermediate, + IEditableSettingsCollectionSpecific where T : class, U + { + protected EditableSettingsSelectable( + IEnumerable editableSettings, + IEnumerable editableSettingsCollections) + : base(editableSettings, editableSettingsCollections) + { + } + + public bool TryMapFromData(U data) + { + T dataCasted = data as T; + return dataCasted == null ? false : TryMapFromData(dataCasted); + } + + U IEditableSettingsCollectionSpecific.MapToData() + { + return MapToData(); + } + } + + public abstract class EditableSettingsSelector + : EditableSettingsCollectionV2, + IEditableSettingsSelector where T : Enum + { + protected EditableSettingsSelector( + IServiceProvider serviceProvider, + IEditableSettingSpecific selection, + IEnumerable editableSettings, + IEnumerable editableSettingsCollections) + : base(editableSettings.Union([selection]), editableSettingsCollections) + { + SelectionLookup = new Dictionary>(); + InitSelectionLookup(serviceProvider); + Selection = selection; + } + + public IEditableSettingSpecific Selection { get; } + + protected IDictionary> SelectionLookup { get; } + + public IEditableSettingsCollectionSpecific GetSelectable(T choice) => SelectionLookup[choice]; + + public IEditableSettingsCollectionSpecific Selected => GetSelectable(Selection.ModelValue); + + protected void InitSelectionLookup(IServiceProvider serviceProvider) + { + foreach (T value in Enum.GetValues(typeof(T))) + { + string key = EditableSettingsSelectorHelper.GetSelectionKey(value); + SelectionLookup.Add(value, serviceProvider.GetRequiredKeyedService>(key)); + } + } + + public override U MapToData() + { + return Selected.MapToData(); + } + } + + public abstract class EditableSettingsSelectableSelector + : EditableSettingsSelector , + IEditableSettingsCollectionSpecific where U : class, V where T : Enum + { + protected EditableSettingsSelectableSelector( + IServiceProvider serviceProvider, + IEditableSettingSpecific selection, + IEnumerable editableSettings, + IEnumerable editableSettingsCollections) + : base(serviceProvider, selection, editableSettings, editableSettingsCollections) + { + } + + public bool TryMapFromData(V data) + { + U dataCasted = data as U; + return dataCasted == null ? false : TryMapFromData(dataCasted); + } + + V IEditableSettingsCollectionSpecific.MapToData() + { + return MapToData(); + } + } +} diff --git a/userspace-backend/Model/EditableSettings/IEditableSetting.cs b/userspace-backend/Model/EditableSettings/IEditableSetting.cs index 0131fd3c..a57199fc 100644 --- a/userspace-backend/Model/EditableSettings/IEditableSetting.cs +++ b/userspace-backend/Model/EditableSettings/IEditableSetting.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; namespace userspace_backend.Model.EditableSettings { @@ -6,12 +7,26 @@ public interface IEditableSetting : INotifyPropertyChanged { string DisplayName { get; } - string EditedValueForDiplay { get; } - string InterfaceValue { get; set; } bool HasChanged(); + bool AutoUpdateFromInterface { get; set; } + bool TryUpdateFromInterface(); } + + public interface IEditableSettingSpecific : IEditableSetting where T : IComparable + { + public T ModelValue { get; } + + /// + /// Attempts to update the model directly. Validates the input as if it had been parsed from interface. + /// This method should probably not be called from the interface. Instead, set InterfaceValue and + /// call TryUpdateFromInterface(). + /// + /// Value to which model should be tried to be set. + /// bool indicating success + public bool TryUpdateModelDirectly(T data); + } } diff --git a/userspace-backend/Model/EditableSettings/IEditableSettingsCollection.cs b/userspace-backend/Model/EditableSettings/IEditableSettingsCollection.cs deleted file mode 100644 index d2f91676..00000000 --- a/userspace-backend/Model/EditableSettings/IEditableSettingsCollection.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace userspace_backend.Model.EditableSettings -{ - public interface IEditableSettingsCollection - { - bool HasChanged { get; } - - EventHandler AnySettingChanged { get; set; } - } -} diff --git a/userspace-backend/Model/EditableSettings/MaxNameLengthValidator.cs b/userspace-backend/Model/EditableSettings/MaxNameLengthValidator.cs new file mode 100644 index 00000000..31011c6f --- /dev/null +++ b/userspace-backend/Model/EditableSettings/MaxNameLengthValidator.cs @@ -0,0 +1,24 @@ +namespace userspace_backend.Model.EditableSettings +{ + public class MaxNameLengthValidator : IModelValueValidator + { + public const int MaxNameLength = 256; + + public MaxNameLengthValidator() + { + MaxLength = MaxNameLength; + } + + public MaxNameLengthValidator(int maxLength) + { + MaxLength = maxLength; + } + + public int MaxLength { get; } + + public bool Validate(string value) + { + return !string.IsNullOrEmpty(value) && value.Length <= MaxLength; + } + } +} diff --git a/userspace-backend/Model/EditableSettings/ModelValueValidator.cs b/userspace-backend/Model/EditableSettings/ModelValueValidator.cs index 476df45e..22ffcb0e 100644 --- a/userspace-backend/Model/EditableSettings/ModelValueValidator.cs +++ b/userspace-backend/Model/EditableSettings/ModelValueValidator.cs @@ -1,34 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using userspace_backend.Data.Profiles.Accel; -using userspace_backend.Data.Profiles; -using userspace_backend.Model.AccelDefinitions; - -namespace userspace_backend.Model.EditableSettings +namespace userspace_backend.Model.EditableSettings { public interface IModelValueValidator { bool Validate(T value); } - public static class ModelValueValidators - { - public static DefaultModelValueValidator DefaultIntValidator = new DefaultModelValueValidator(); - public static DefaultModelValueValidator DefaultDoubleValidator = new DefaultModelValueValidator(); - public static DefaultModelValueValidator DefaultStringValidator = new DefaultModelValueValidator(); - public static AllChangeInvalidValueValidator AllChangesInvalidStringValidator = new AllChangeInvalidValueValidator(); - public static DefaultModelValueValidator DefaultBoolValidator = new DefaultModelValueValidator(); - public static DefaultModelValueValidator DefaultAccelerationTypeValidator = new DefaultModelValueValidator(); - public static DefaultModelValueValidator DefaultLookupTableTypeValidator = new DefaultModelValueValidator(); - public static DefaultModelValueValidator DefaultLookupTableDataValidator = new DefaultModelValueValidator(); - public static DefaultModelValueValidator DefaultAccelerationFormulaTypeValidator = new DefaultModelValueValidator(); - } - public class DefaultModelValueValidator : IModelValueValidator { + public const string AllChangeInvalidDIKey = nameof(AllChangeInvalidDIKey); + public bool Validate(T value) { return true; diff --git a/userspace-backend/Model/EditableSettings/UserInputParsers.cs b/userspace-backend/Model/EditableSettings/UserInputParser.cs similarity index 84% rename from userspace-backend/Model/EditableSettings/UserInputParsers.cs rename to userspace-backend/Model/EditableSettings/UserInputParser.cs index ca78e370..a3f6aa11 100644 --- a/userspace-backend/Model/EditableSettings/UserInputParsers.cs +++ b/userspace-backend/Model/EditableSettings/UserInputParser.cs @@ -9,18 +9,6 @@ namespace userspace_backend.Model.EditableSettings { - public static class UserInputParsers - { - public static StringParser StringParser = new StringParser(); - public static IntParser IntParser = new IntParser(); - public static DoubleParser DoubleParser = new DoubleParser(); - public static BoolParser BoolParser = new BoolParser(); - public static AccelerationDefinitionTypeParser AccelerationDefinitionTypeParser = new AccelerationDefinitionTypeParser(); - public static LookupTableTypeParser LookupTableTypeParser = new LookupTableTypeParser(); - public static LookupTableDataParser LookupTableDataParser = new LookupTableDataParser(); - public static AccelerationFormulaTypeParser AccelerationFormulaTypeParser = new AccelerationFormulaTypeParser(); - } - public interface IUserInputParser { bool TryParse(string input, out T parsedValue); diff --git a/userspace-backend/Model/MappingConstructor.cs b/userspace-backend/Model/MappingConstructor.cs deleted file mode 100644 index c91a102a..00000000 --- a/userspace-backend/Model/MappingConstructor.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DATA = userspace_backend.Data; -using userspace_backend.Model.AccelDefinitions; -using userspace_backend.Model.EditableSettings; -using userspace_backend.Model.ProfileComponents; -using userspace_backend.Data; -using CommunityToolkit.Mvvm.Collections; -using System.Collections.ObjectModel; - -namespace userspace_backend.Model -{ - public class MappingConstructor - { - public MappingConstructor(ProfilesModel profiles, DeviceGroups deviceGroups) - { - Profiles = profiles; - DeviceGroups = deviceGroups; - } - - public ProfilesModel Profiles { get; } - - public DeviceGroups DeviceGroups { get; } - } -} diff --git a/userspace-backend/Model/MappingModel.cs b/userspace-backend/Model/MappingModel.cs index 4f9e16fd..3ec2d29b 100644 --- a/userspace-backend/Model/MappingModel.cs +++ b/userspace-backend/Model/MappingModel.cs @@ -1,51 +1,60 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DATA = userspace_backend.Data; -using userspace_backend.Model.AccelDefinitions; using userspace_backend.Model.EditableSettings; -using userspace_backend.Model.ProfileComponents; using userspace_backend.Data; -using CommunityToolkit.Mvvm.Collections; -using System.Collections.ObjectModel; using System.Collections.Specialized; namespace userspace_backend.Model { - public class MappingModel : EditableSettingsCollection + public interface IMappingModel : IEditableSettingsCollectionSpecific + { + } + + public class MappingModel: NamedEditableSettingsCollection, IMappingModel { + public const string NameDIKey = $"{nameof(MappingModel)}.{nameof(Name)}"; + public MappingModel( - Mapping dataObject, + IEditableSettingSpecific name, IModelValueValidator nameValidator, - DeviceGroups deviceGroups, - ProfilesModel profiles) : base(dataObject) + IDeviceGroups deviceGroups, + IProfilesModel profiles, + Mapping dataObject) + : base(name, [], []) { NameValidator = nameValidator; SetActive = true; DeviceGroups = deviceGroups; Profiles = profiles; + + // Initialize collections + IndividualMappings = new ObservableCollection(); + DeviceGroupsStillUnmapped = new ObservableCollection(); + + // Initialize mappings from data InitIndividualMappings(dataObject); - DeviceGroupsStillUnmapped = new ObservableCollection(); FindDeviceGroupsStillUnmapped(); + + // Wire up events IndividualMappings.CollectionChanged += OnIndividualMappingsChanged; - DeviceGroups.DeviceGroupModels.CollectionChanged += OnIndividualMappingsChanged; + if (DeviceGroups is DeviceGroups dg) + { + dg.DeviceGroupModels.CollectionChanged += OnIndividualMappingsChanged; + } } public bool SetActive { get; set; } - public EditableSetting Name { get; set; } - public ObservableCollection IndividualMappings { get; protected set; } - public ObservableCollection DeviceGroupsStillUnmapped { get; protected set; } - + public ObservableCollection DeviceGroupsStillUnmapped { get; protected set; } + protected IModelValueValidator NameValidator { get; } - protected DeviceGroups DeviceGroups { get; } + protected IDeviceGroups DeviceGroups { get; } - protected ProfilesModel Profiles { get; } + protected IProfilesModel Profiles { get; } public override Mapping MapToData() { @@ -57,33 +66,12 @@ public override Mapping MapToData() foreach (var group in IndividualMappings) { - mapping.GroupsToProfiles.Add(group.DeviceGroup.ModelValue, group.Profile.Name.ModelValue); + mapping.GroupsToProfiles.Add(group.DeviceGroup, group.Profile.Name.ModelValue); } return mapping; } - protected override IEnumerable EnumerateEditableSettings() - { - return []; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return []; - } - - protected override void InitEditableSettingsAndCollections(Mapping dataObject) - { - Name = new EditableSetting( - displayName: "Name", - initialValue: dataObject.Name, - parser: UserInputParsers.StringParser, - validator: NameValidator); - - IndividualMappings = new ObservableCollection(); - } - protected void InitIndividualMappings(Mapping dataObject) { foreach (var kvp in dataObject.GroupsToProfiles) @@ -94,14 +82,14 @@ protected void InitIndividualMappings(Mapping dataObject) public bool TryAddMapping(string deviceGroupName, string profileName) { - if (!DeviceGroups.TryGetDeviceGroup(deviceGroupName, out DeviceGroupModel? deviceGroup) + if (!DeviceGroups.TryGetDeviceGroup(deviceGroupName, out string? deviceGroup) || deviceGroup == null - || IndividualMappings.Any(m => m.DeviceGroup.Equals(deviceGroup))) + || IndividualMappings.Any(m => string.Equals(m.DeviceGroup, deviceGroup, StringComparison.InvariantCultureIgnoreCase))) { return false; } - if (!Profiles.TryGetProfile(profileName, out ProfileModel? profile) + if (!Profiles.TryGetElement(profileName, out IProfileModel? profile) || profile == null) { return false; @@ -122,11 +110,14 @@ protected void FindDeviceGroupsStillUnmapped() { DeviceGroupsStillUnmapped.Clear(); - foreach (DeviceGroupModel group in DeviceGroups.DeviceGroupModels) + if (DeviceGroups is DeviceGroups dg) { - if (!IndividualMappings.Any(m => m.DeviceGroup.Equals(group))) + foreach (string group in dg.DeviceGroupModels) { - DeviceGroupsStillUnmapped.Add(group); + if (!IndividualMappings.Any(m => string.Equals(m.DeviceGroup, group, StringComparison.InvariantCultureIgnoreCase))) + { + DeviceGroupsStillUnmapped.Add(group); + } } } } @@ -135,15 +126,25 @@ protected void OnIndividualMappingsChanged(object? sender, NotifyCollectionChang { FindDeviceGroupsStillUnmapped(); } + + protected override bool TryMapEditableSettingsFromData(Mapping data) + { + return Name.TryUpdateModelDirectly(data.Name); + } + + protected override bool TryMapEditableSettingsCollectionsFromData(Mapping data) + { + return true; + } } public class MappingGroup { - public DeviceGroupModel DeviceGroup { get; set; } + public string DeviceGroup { get; set; } - public ProfileModel Profile { get; set; } + public IProfileModel Profile { get; set; } // This is here for easy binding - public ProfilesModel Profiles { get; set; } + public IProfilesModel Profiles { get; set; } } } diff --git a/userspace-backend/Model/MappingsModel.cs b/userspace-backend/Model/MappingsModel.cs index 9c56c74b..e884ffb5 100644 --- a/userspace-backend/Model/MappingsModel.cs +++ b/userspace-backend/Model/MappingsModel.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -10,9 +11,10 @@ namespace userspace_backend.Model { public class MappingsModel : EditableSettingsCollection { - public MappingsModel(DATA.MappingSet dataObject, DeviceGroups deviceGroups, ProfilesModel profiles) + public MappingsModel(DATA.MappingSet dataObject, DeviceGroups deviceGroups, IProfilesModel profiles, IServiceProvider serviceProvider) : base(dataObject) { + ServiceProvider = serviceProvider; DeviceGroups = deviceGroups; Profiles = profiles; NameValidator = new MappingNameValidator(this); @@ -21,9 +23,11 @@ public MappingsModel(DATA.MappingSet dataObject, DeviceGroups deviceGroups, Prof public ObservableCollection Mappings { get; protected set; } + protected IServiceProvider ServiceProvider { get; } + protected DeviceGroups DeviceGroups { get; } - protected ProfilesModel Profiles { get; } + protected IProfilesModel Profiles { get; } protected MappingNameValidator NameValidator { get; } @@ -79,7 +83,15 @@ public bool TryAddMapping(DATA.Mapping? mappingToAdd = null) return false; } - MappingModel mapping = new MappingModel(mappingToAdd, NameValidator, DeviceGroups, Profiles); + // Create Name setting for the mapping + var nameSetting = new EditableSettingV2( + displayName: "Name", + initialValue: mappingToAdd.Name, + parser: ServiceProvider.GetRequiredService>(), + validator: NameValidator); + + // Construct MappingModel with DI pattern + MappingModel mapping = new MappingModel(nameSetting, NameValidator, DeviceGroups, Profiles, mappingToAdd); Mappings.Add(mapping); return true; } @@ -102,7 +114,7 @@ protected override IEnumerable EnumerateEditableSettings() return []; } - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override IEnumerable EnumerateEditableSettingsCollections() { return Mappings; } diff --git a/userspace-backend/Model/ProfileComponents/AnisotropyModel.cs b/userspace-backend/Model/ProfileComponents/AnisotropyModel.cs index f7f219c6..4576f164 100644 --- a/userspace-backend/Model/ProfileComponents/AnisotropyModel.cs +++ b/userspace-backend/Model/ProfileComponents/AnisotropyModel.cs @@ -1,30 +1,61 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using userspace_backend.Data.Profiles; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.ProfileComponents { - public class AnisotropyModel : EditableSettingsCollection + public interface IAnisotropyModel : IEditableSettingsCollectionSpecific { - public AnisotropyModel(Anisotropy dataObject) : base(dataObject) + IEditableSettingSpecific DomainX { get; } + + IEditableSettingSpecific DomainY { get; } + + IEditableSettingSpecific RangeX { get; } + + IEditableSettingSpecific RangeY { get; } + + IEditableSettingSpecific LPNorm { get; } + + IEditableSettingSpecific CombineXYComponents { get; } + } + + public class AnisotropyModel : EditableSettingsCollectionV2, IAnisotropyModel + { + public const string DomainXDIKey = $"{nameof(AnisotropyModel)}.{nameof(DomainX)}"; + public const string DomainYDIKey = $"{nameof(AnisotropyModel)}.{nameof(DomainY)}"; + public const string RangeXDIKey = $"{nameof(AnisotropyModel)}.{nameof(RangeX)}"; + public const string RangeYDIKey = $"{nameof(AnisotropyModel)}.{nameof(RangeYDIKey)}"; + public const string LPNormDIKey = $"{nameof(AnisotropyModel)}.{nameof(LPNorm)}"; + public const string CombineXYComponentsDIKey = $"{nameof(AnisotropyModel)}.{nameof(CombineXYComponents)}"; + + public AnisotropyModel( + [FromKeyedServices(DomainXDIKey)]IEditableSettingSpecific domainX, + [FromKeyedServices(DomainYDIKey)]IEditableSettingSpecific domainY, + [FromKeyedServices(RangeXDIKey)]IEditableSettingSpecific rangeX, + [FromKeyedServices(RangeYDIKey)]IEditableSettingSpecific rangeY, + [FromKeyedServices(LPNormDIKey)]IEditableSettingSpecific lpNorm, + [FromKeyedServices(CombineXYComponentsDIKey)]IEditableSettingSpecific combineXYComponents + ) : base([domainX, domainY, rangeX, rangeY, lpNorm, combineXYComponents], []) { + DomainX = domainX; + DomainY = domainY; + RangeX = rangeX; + RangeY = rangeY; + LPNorm = lpNorm; + CombineXYComponents = combineXYComponents; } - public EditableSetting DomainX { get; set; } + public IEditableSettingSpecific DomainX { get; set; } - public EditableSetting DomainY { get; set; } + public IEditableSettingSpecific DomainY { get; set; } - public EditableSetting RangeX { get; set; } + public IEditableSettingSpecific RangeX { get; set; } - public EditableSetting RangeY { get; set; } + public IEditableSettingSpecific RangeY { get; set; } - public EditableSetting LPNorm { get; set; } + public IEditableSettingSpecific LPNorm { get; set; } - public EditableSetting CombineXYComponents { get; set; } + public IEditableSettingSpecific CombineXYComponents { get; set; } public override Anisotropy MapToData() { @@ -36,48 +67,20 @@ public override Anisotropy MapToData() }; } - protected override IEnumerable EnumerateEditableSettings() - { - return [DomainX, DomainY, RangeX, RangeY, LPNorm]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override bool TryMapEditableSettingsCollectionsFromData(Anisotropy data) { - return Enumerable.Empty(); + // Nothing to do here + return true; } - protected override void InitEditableSettingsAndCollections(Anisotropy dataObject) + protected override bool TryMapEditableSettingsFromData(Anisotropy data) { - DomainX = new EditableSetting( - displayName: "Domain X", - initialValue: dataObject?.Domain?.X ?? 1, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - DomainY = new EditableSetting( - displayName: "Domain Y", - initialValue: dataObject?.Domain?.Y ?? 1, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - RangeX = new EditableSetting( - displayName: "Range X", - initialValue: dataObject?.Range?.X ?? 1, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - RangeY = new EditableSetting( - displayName: "Range Y", - initialValue: dataObject?.Range?.Y ?? 1, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - LPNorm = new EditableSetting( - displayName: "LP Norm", - initialValue: dataObject?.LPNorm ?? 2, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - CombineXYComponents = new EditableSetting( - displayName: "Combine X and Y Components", - initialValue: dataObject?.CombineXYComponents ?? false, - parser: UserInputParsers.BoolParser, - validator: ModelValueValidators.DefaultBoolValidator); + return DomainX.TryUpdateModelDirectly(data.Domain.X) + & DomainY.TryUpdateModelDirectly(data.Domain.Y) + & RangeX.TryUpdateModelDirectly(data.Range.X) + & RangeY.TryUpdateModelDirectly(data.Range.Y) + & LPNorm.TryUpdateModelDirectly(data.LPNorm) + & CombineXYComponents.TryUpdateModelDirectly(data.CombineXYComponents); } } } diff --git a/userspace-backend/Model/ProfileComponents/CoalescionModel.cs b/userspace-backend/Model/ProfileComponents/CoalescionModel.cs index 45fd136f..5e58a3ea 100644 --- a/userspace-backend/Model/ProfileComponents/CoalescionModel.cs +++ b/userspace-backend/Model/ProfileComponents/CoalescionModel.cs @@ -1,22 +1,33 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using userspace_backend.Data.Profiles; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.ProfileComponents { - public class CoalescionModel : EditableSettingsCollection + public interface ICoalescionModel : IEditableSettingsCollectionSpecific { - public CoalescionModel(Coalescion dataObject) : base(dataObject) + IEditableSettingSpecific InputSmoothingHalfLife { get; } + + IEditableSettingSpecific ScaleSmoothingHalfLife { get; } + } + + public class CoalescionModel : EditableSettingsCollectionV2, ICoalescionModel + { + public const string InputSmoothingHalfLifeDIKey = $"{nameof(CoalescionModel)}.{nameof(InputSmoothingHalfLife)}"; + public const string ScaleSmoothingHalfLifeDIKey = $"{nameof(CoalescionModel)}.{nameof(ScaleSmoothingHalfLife)}"; + + public CoalescionModel( + [FromKeyedServices(InputSmoothingHalfLifeDIKey)]IEditableSettingSpecific inputSmoothingHalfLife, + [FromKeyedServices(ScaleSmoothingHalfLifeDIKey)]IEditableSettingSpecific scaleSmoothHalfLife) + : base([inputSmoothingHalfLife, scaleSmoothHalfLife], []) { + InputSmoothingHalfLife = inputSmoothingHalfLife; + ScaleSmoothingHalfLife = scaleSmoothHalfLife; } - public EditableSetting InputSmoothingHalfLife { get; set; } + public IEditableSettingSpecific InputSmoothingHalfLife { get; set; } - public EditableSetting ScaleSmoothingHalfLife { get; set; } + public IEditableSettingSpecific ScaleSmoothingHalfLife { get; set; } public override Coalescion MapToData() { @@ -27,28 +38,15 @@ public override Coalescion MapToData() }; } - protected override IEnumerable EnumerateEditableSettings() - { - return [ InputSmoothingHalfLife, ScaleSmoothingHalfLife ]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override bool TryMapEditableSettingsCollectionsFromData(Coalescion data) { - return Enumerable.Empty(); + return true; } - protected override void InitEditableSettingsAndCollections(Coalescion dataObject) + protected override bool TryMapEditableSettingsFromData(Coalescion data) { - InputSmoothingHalfLife = new EditableSetting( - displayName: "Input Smoothing Half-Life", - initialValue: dataObject?.InputSmoothingHalfLife ?? 0, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - ScaleSmoothingHalfLife = new EditableSetting( - displayName: "Scale Smoothing Half-Life", - initialValue: dataObject?.ScaleSmoothingHalfLife ?? 0, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return InputSmoothingHalfLife.TryUpdateModelDirectly(data.InputSmoothingHalfLife) + & ScaleSmoothingHalfLife.TryUpdateModelDirectly(data.ScaleSmoothingHalfLife); } } } diff --git a/userspace-backend/Model/ProfileComponents/HiddenModel.cs b/userspace-backend/Model/ProfileComponents/HiddenModel.cs index 264156ed..411c5164 100644 --- a/userspace-backend/Model/ProfileComponents/HiddenModel.cs +++ b/userspace-backend/Model/ProfileComponents/HiddenModel.cs @@ -1,30 +1,61 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using userspace_backend.Data.Profiles; using userspace_backend.Model.EditableSettings; namespace userspace_backend.Model.ProfileComponents { - public class HiddenModel : EditableSettingsCollection + public interface IHiddenModel : IEditableSettingsCollectionSpecific { - public HiddenModel(Hidden dataObject) : base(dataObject) + IEditableSettingSpecific RotationDegrees { get; } + + IEditableSettingSpecific AngleSnappingDegrees { get; } + + IEditableSettingSpecific LeftRightRatio { get; } + + IEditableSettingSpecific UpDownRatio { get; } + + IEditableSettingSpecific SpeedCap { get; } + + IEditableSettingSpecific OutputSmoothingHalfLife { get; } + } + + public class HiddenModel : EditableSettingsCollectionV2, IHiddenModel + { + public const string RotationDegreesDIKey = $"{nameof(HiddenModel)}.{nameof(RotationDegrees)}"; + public const string AngleSnappingDegreesDIKey = $"{nameof(HiddenModel)}.{nameof(AngleSnappingDegrees)}"; + public const string LeftRightRatioDIKey = $"{nameof(HiddenModel)}.{nameof(LeftRightRatio)}"; + public const string UpDownRatioDIKey = $"{nameof(HiddenModel)}.{nameof(UpDownRatio)}"; + public const string SpeedCapDIKey = $"{nameof(HiddenModel)}.{nameof(SpeedCap)}"; + public const string OutputSmoothingHalfLifeDIKey = $"{nameof(HiddenModel)}.{nameof(OutputSmoothingHalfLife)}"; + + public HiddenModel( + [FromKeyedServices(RotationDegreesDIKey)]IEditableSettingSpecific rotationDegrees, + [FromKeyedServices(AngleSnappingDegreesDIKey)]IEditableSettingSpecific angleSnappingDegrees, + [FromKeyedServices(LeftRightRatioDIKey)]IEditableSettingSpecific leftRightRatio, + [FromKeyedServices(UpDownRatioDIKey)]IEditableSettingSpecific upDownRatio, + [FromKeyedServices(SpeedCapDIKey)]IEditableSettingSpecific speedCap, + [FromKeyedServices(OutputSmoothingHalfLifeDIKey)]IEditableSettingSpecific outputSmoothingHalfLife + ) : base([rotationDegrees, angleSnappingDegrees, leftRightRatio, upDownRatio, speedCap, outputSmoothingHalfLife], []) { + RotationDegrees = rotationDegrees; + AngleSnappingDegrees = angleSnappingDegrees; + LeftRightRatio = leftRightRatio; + UpDownRatio = upDownRatio; + SpeedCap = speedCap; + OutputSmoothingHalfLife = outputSmoothingHalfLife; } - public EditableSetting RotationDegrees { get; set; } + public IEditableSettingSpecific RotationDegrees { get; set; } - public EditableSetting AngleSnappingDegrees { get; set; } + public IEditableSettingSpecific AngleSnappingDegrees { get; set; } - public EditableSetting LeftRightRatio { get; set; } + public IEditableSettingSpecific LeftRightRatio { get; set; } - public EditableSetting UpDownRatio { get; set; } + public IEditableSettingSpecific UpDownRatio { get; set; } - public EditableSetting SpeedCap { get; set; } + public IEditableSettingSpecific SpeedCap { get; set; } - public EditableSetting OutputSmoothingHalfLife { get; set; } + public IEditableSettingSpecific OutputSmoothingHalfLife { get; set; } public override Hidden MapToData() { @@ -39,48 +70,19 @@ public override Hidden MapToData() }; } - protected override IEnumerable EnumerateEditableSettings() - { - return [ RotationDegrees, AngleSnappingDegrees, LeftRightRatio, UpDownRatio, SpeedCap ]; - } - - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override bool TryMapEditableSettingsCollectionsFromData(Hidden data) { - return Enumerable.Empty(); + return true; } - protected override void InitEditableSettingsAndCollections(Hidden dataObject) + protected override bool TryMapEditableSettingsFromData(Hidden data) { - RotationDegrees = new EditableSetting( - displayName: "Rotation", - initialValue: dataObject?.RotationDegrees ?? 0, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - AngleSnappingDegrees = new EditableSetting( - displayName: "Angle Snapping", - initialValue: dataObject?.AngleSnappingDegrees ?? 0, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - LeftRightRatio = new EditableSetting( - displayName: "L/R Ratio", - initialValue: dataObject?.LeftRightRatio ?? 1, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - UpDownRatio = new EditableSetting( - displayName: "U/D Ratio", - initialValue: dataObject?.UpDownRatio ?? 1, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - SpeedCap = new EditableSetting( - displayName: "Speed Cap", - initialValue: dataObject?.SpeedCap ?? 0, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - OutputSmoothingHalfLife = new EditableSetting( - displayName: "Output Smoothing Half-Life", - initialValue: dataObject?.OutputSmoothingHalfLife ?? 0, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); + return RotationDegrees.TryUpdateModelDirectly(data.RotationDegrees) + & AngleSnappingDegrees.TryUpdateModelDirectly(data.AngleSnappingDegrees) + & LeftRightRatio.TryUpdateModelDirectly(data.LeftRightRatio) + & UpDownRatio.TryUpdateModelDirectly(data.UpDownRatio) + & SpeedCap.TryUpdateModelDirectly(data.SpeedCap) + & OutputSmoothingHalfLife.TryUpdateModelDirectly(data.OutputSmoothingHalfLife); } } } diff --git a/userspace-backend/Model/ProfileModel.cs b/userspace-backend/Model/ProfileModel.cs index 64ed4547..312b8ea4 100644 --- a/userspace-backend/Model/ProfileModel.cs +++ b/userspace-backend/Model/ProfileModel.cs @@ -1,37 +1,76 @@ -using System.Collections.Generic; -using DATA = userspace_backend.Data; +using DATA = userspace_backend.Data; using userspace_backend.Model.AccelDefinitions; using userspace_backend.Model.EditableSettings; using userspace_backend.Model.ProfileComponents; using System; using System.ComponentModel; using userspace_backend.Common; -using System.Collections.ObjectModel; -using CommunityToolkit.Mvvm.ComponentModel; using userspace_backend.Display; +using Microsoft.Extensions.DependencyInjection; namespace userspace_backend.Model { - public class ProfileModel : EditableSettingsCollection + public interface IProfileModel : IEditableSettingsCollectionSpecific { - public ProfileModel(DATA.Profile dataObject, IModelValueValidator nameValidator) : base(dataObject) + IEditableSettingSpecific Name { get; } + + IEditableSettingSpecific OutputDPI { get; } + + IEditableSettingSpecific YXRatio { get; } + + IAccelerationModel Acceleration { get; } + + IHiddenModel Hidden { get; } + + ICurvePreview CurvePreview { get; } + + string CurrentNameForDisplay { get; } + + Profile CurrentValidatedDriverProfile { get; } + } + + public class ProfileModel : NamedEditableSettingsCollection, IProfileModel + { + public const string NameDIKey = $"{nameof(ProfileModel)}.{nameof(Name)}"; + public const string OutputDPIDIKey = $"{nameof(ProfileModel)}.{nameof(OutputDPI)}"; + public const string YXRatioDIKey = $"{nameof(ProfileModel)}.{nameof(YXRatio)}"; + + public ProfileModel( + [FromKeyedServices(NameDIKey)]IEditableSettingSpecific name, + [FromKeyedServices(OutputDPIDIKey)]IEditableSettingSpecific outputDPI, + [FromKeyedServices(YXRatioDIKey)]IEditableSettingSpecific yxRatio, + IAccelerationModel acceleration, + IHiddenModel hidden, + ICurvePreview curvePreview + ) : base(name, [outputDPI, yxRatio], [acceleration, hidden]) { - NameValidator = nameValidator; - CurvePreview = new CurvePreview(); + OutputDPI = outputDPI; + YXRatio = yxRatio; + Acceleration = acceleration; + Hidden = hidden; + + // Name and Output DPI do not need to generate a new curve preview + Name!.PropertyChanged += AnyNonPreviewPropertyChangedEventHandler; + OutputDPI.PropertyChanged += AnyNonPreviewPropertyChangedEventHandler; + + // The rest of settings should generate a new curve preview + YXRatio.PropertyChanged += AnyCurvePreviewPropertyChangedEventHandler; + Acceleration.AnySettingChanged += AnyCurveSettingCollectionChangedEventHandler; + Hidden.AnySettingChanged += AnyCurveSettingCollectionChangedEventHandler; + + CurvePreview = curvePreview; RecalculateDriverDataAndCurvePreview(); } - public string CurrentNameForDisplay => Name.CurrentValidatedValue; - - public EditableSetting Name { get; set; } + public string CurrentNameForDisplay => Name.ModelValue; - public EditableSetting OutputDPI { get; set; } + public IEditableSettingSpecific OutputDPI { get; set; } - public EditableSetting YXRatio { get; set; } + public IEditableSettingSpecific YXRatio { get; set; } - public AccelerationModel Acceleration { get; set; } + public IAccelerationModel Acceleration { get; set; } - public HiddenModel Hidden { get; set; } + public IHiddenModel Hidden { get; set; } public Profile CurrentValidatedDriverProfile { get; protected set; } @@ -53,7 +92,7 @@ public override DATA.Profile MapToData() protected void AnyNonPreviewPropertyChangedEventHandler(object? send, PropertyChangedEventArgs e) { - if (string.Equals(e.PropertyName, nameof(EditableSetting.CurrentValidatedValue))) + if (string.Equals(e.PropertyName, nameof(IEditableSettingSpecific.ModelValue))) { RecalculateDriverData(); } @@ -61,7 +100,7 @@ protected void AnyNonPreviewPropertyChangedEventHandler(object? send, PropertyCh protected void AnyCurvePreviewPropertyChangedEventHandler(object? send, PropertyChangedEventArgs e) { - if (string.Equals(e.PropertyName, nameof(EditableSetting.CurrentValidatedValue))) + if (string.Equals(e.PropertyName, nameof(IEditableSettingSpecific.ModelValue))) { RecalculateDriverDataAndCurvePreview(); } @@ -84,44 +123,17 @@ protected void RecalculateDriverDataAndCurvePreview() CurvePreview.GeneratePoints(CurrentValidatedDriverProfile); } - protected override IEnumerable EnumerateEditableSettings() + protected override bool TryMapEditableSettingsFromData(DATA.Profile data) { - return [Name, OutputDPI, YXRatio]; + return Name.TryUpdateModelDirectly(data.Name) + & OutputDPI.TryUpdateModelDirectly(data.OutputDPI) + & YXRatio.TryUpdateModelDirectly(data.YXRatio); } - protected override IEnumerable EnumerateEditableSettingsCollections() + protected override bool TryMapEditableSettingsCollectionsFromData(DATA.Profile data) { - return [Acceleration, Hidden]; - } - - protected override void InitEditableSettingsAndCollections(DATA.Profile dataObject) - { - Name = new EditableSetting( - displayName: "Name", - initialValue: dataObject.Name, - parser: UserInputParsers.StringParser, - validator: NameValidator); - OutputDPI = new EditableSetting( - displayName: "Output DPI", - initialValue: dataObject.OutputDPI, - parser: UserInputParsers.IntParser, - validator: ModelValueValidators.DefaultIntValidator); - YXRatio = new EditableSetting( - displayName: "Y/X Ratio", - initialValue: dataObject.YXRatio, - parser: UserInputParsers.DoubleParser, - validator: ModelValueValidators.DefaultDoubleValidator); - Acceleration = new AccelerationModel(dataObject.Acceleration); - Hidden = new HiddenModel(dataObject.Hidden); - - // Name and Output DPI do not need to generate a new curve preview - Name.PropertyChanged += AnyNonPreviewPropertyChangedEventHandler; - OutputDPI.PropertyChanged += AnyNonPreviewPropertyChangedEventHandler; - - // The rest of settings should generate a new curve preview - YXRatio.PropertyChanged += AnyCurvePreviewPropertyChangedEventHandler; - Acceleration.AnySettingChanged += AnyCurveSettingCollectionChangedEventHandler; - Hidden.AnySettingChanged += AnyCurveSettingCollectionChangedEventHandler; + return Acceleration.TryMapFromData(data.Acceleration) + & Hidden.TryMapFromData(data.Hidden); } } } diff --git a/userspace-backend/Model/ProfilesModel.cs b/userspace-backend/Model/ProfilesModel.cs index 74dde87e..48692d22 100644 --- a/userspace-backend/Model/ProfilesModel.cs +++ b/userspace-backend/Model/ProfilesModel.cs @@ -1,102 +1,47 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using userspace_backend.Model.EditableSettings; using DATA = userspace_backend.Data; namespace userspace_backend.Model { - public class ProfilesModel : EditableSettingsCollection> + public interface IProfilesModel : IEditableSettingsList { - public static readonly ProfileModel DefaultProfile = new ProfileModel( - GenerateNewDefaultProfile("Default"), ModelValueValidators.AllChangesInvalidStringValidator); - - public ProfilesModel(IEnumerable dataObject) : base(dataObject) - { - NameValidator = new ProfileNameValidator(this); - } - - public ObservableCollection Profiles { get; protected set; } - - protected ProfileNameValidator NameValidator { get; } - - public override IEnumerable MapToData() - { - return Profiles.Select(p => p.MapToData()); - } - - protected override IEnumerable EnumerateEditableSettings() - { - return []; - } + } - protected override IEnumerable EnumerateEditableSettingsCollections() - { - return Profiles; - } + public class ProfilesModel : EditableSettingsList, IProfilesModel + { + // Default profile is created during BackEnd.Load() if it doesn't exist - protected override void InitEditableSettingsAndCollections(IEnumerable dataObject) + public ProfilesModel(IServiceProvider serviceProvider) + : base(serviceProvider, [], []) { - Profiles = new ObservableCollection() { DefaultProfile, }; } - public bool TryGetProfile(string name, out ProfileModel? profileModel) - { - profileModel = Profiles.FirstOrDefault( - p => string.Equals(p.Name.ModelValue, name, StringComparison.InvariantCultureIgnoreCase)); - return profileModel != null; - } + protected override string DefaultNameTemplate => "Profile"; - public bool TryAddNewDefaultProfile(string name) + protected override string GetNameFromElement(IProfileModel element) { - if (TryGetProfile(name, out _)) - { - return false; - } - - DATA.Profile profile = GenerateNewDefaultProfile(name); - ProfileModel profileModel = new ProfileModel(profile, NameValidator); - Profiles.Add(profileModel); - return true; + return element.Name.ModelValue; } - public bool TryAddProfile(DATA.Profile profileToAdd) + protected override bool TryMapEditableSettingsFromData(IEnumerable data) { - if (TryGetProfile(profileToAdd.Name, out _)) - { - return false; - } - - ProfileModel profileModel = new ProfileModel(profileToAdd, NameValidator); - Profiles.Add(profileModel); return true; } - public bool RemoveProfile(ProfileModel profile) + protected override void SetElementName(IProfileModel element, string name) { - return Profiles.Remove(profile); + element.Name.TryUpdateModelDirectly(name); } - protected static DATA.Profile GenerateNewDefaultProfile(string name) - { - return new DATA.Profile() - { - Name = name, - OutputDPI = 1000, - YXRatio = 1, - }; - } - } - - public class ProfileNameValidator(ProfilesModel profilesModel) : IModelValueValidator - { - ProfilesModel ProfilesModel { get; } = profilesModel; - - public bool Validate(string value) + protected override string GetNameFromData(DATA.Profile data) { - return !ProfilesModel.TryGetProfile(value, out _); + return data.Name; } } } diff --git a/userspace-backend/Model/SystemDevices.cs b/userspace-backend/Model/SystemDevices.cs new file mode 100644 index 00000000..28fc8fd3 --- /dev/null +++ b/userspace-backend/Model/SystemDevices.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace userspace_backend.Model +{ + /// + /// Holds system devices in observable collection and refreshes list when desired. + /// + public interface ISystemDevicesProvider + { + ReadOnlyObservableCollection SystemDevices { get; } + + void RefreshSystemDevices(); + } + + /// + /// Application implementation of + /// + public class SystemDevicesProvider : ISystemDevicesProvider + { + public SystemDevicesProvider(ISystemDevicesRetriever devicesRetriever) + { + DevicesRetriever = devicesRetriever; + SystemDevicesInternal = new ObservableCollection(); + SystemDevices = new ReadOnlyObservableCollection(SystemDevicesInternal); + RefreshSystemDevices(); + } + + public ReadOnlyObservableCollection SystemDevices { get; } + + protected ObservableCollection SystemDevicesInternal { get; } + + protected ISystemDevicesRetriever DevicesRetriever { get; } + + public void RefreshSystemDevices() + { + // TODO: Replace with "addrange" equivalent from ObservableCollection child class + SystemDevicesInternal.Clear(); + IList retrievedDevices = DevicesRetriever.GetSystemDevices(); + + foreach (ISystemDevice retrievedDevice in retrievedDevices) + { + SystemDevicesInternal.Add(retrievedDevice); + } + } + } + + /// + /// Retrieves list of devices from operating system + /// + public interface ISystemDevicesRetriever + { + IList GetSystemDevices(); + } + + /// + /// Application implementation of + /// + public sealed class SystemDevicesRetriever : ISystemDevicesRetriever + { + public IList GetSystemDevices() + { + IList rawDevices = MultiHandleDevice.GetList(); + return rawDevices.Select(d => new SystemDevice(d) as ISystemDevice).ToList(); + } + } + + /// + /// Interface to represent devices as they come from windows. + /// The actual class from windows is non-trivial to construct and test. + /// + public interface ISystemDevice + { + public string Name { get; } + + public string HWID { get; } + } + + /// + /// Data class to wrap + /// + public class SystemDevice : ISystemDevice + { + public SystemDevice(MultiHandleDevice multiHandleDevice) + { + RawDevice = multiHandleDevice; + } + + public string Name { get => RawDevice.name; } + + public string HWID { get => RawDevice.id; } + + private MultiHandleDevice RawDevice { get; } + } +} diff --git a/userspace-backend/userspace-backend.csproj b/userspace-backend/userspace-backend.csproj index bd556195..84011a76 100644 --- a/userspace-backend/userspace-backend.csproj +++ b/userspace-backend/userspace-backend.csproj @@ -9,6 +9,7 @@ +