Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
5234835
Adding DI and skeleton of EditableList
JacobPalecki Jan 19, 2025
3dd3cfc
Further DI work + cleaning up IEditableSettings interface
JacobPalecki Jan 19, 2025
27ef9f7
Condense currentvalidatedvalue and modelvalue
JacobPalecki Jan 19, 2025
c719e8f
Change EditableSettings references to IEditableSettingsSpecific
JacobPalecki Jan 19, 2025
183d79f
new format solidified with test
JacobPalecki Jan 26, 2025
dda421d
EditableSettingsTests
JacobPalecki Jan 26, 2025
bd3dede
Editable Settings Collections tests + organization
JacobPalecki Feb 8, 2025
993c909
Start work on editable selector
JacobPalecki Feb 10, 2025
4768f69
editable selector and tests
JacobPalecki Feb 16, 2025
6427ad2
editable list test
JacobPalecki Feb 19, 2025
ab8606a
merge latest
JacobPalecki Mar 11, 2025
1b02553
add AutoUpdateFromInterface to editable setting interface
JacobPalecki Mar 11, 2025
9a0df61
Updating DI defns
JacobPalecki Apr 2, 2025
6313b80
Merge latest
JacobPalecki Jul 4, 2025
453af71
tests pass after merge
JacobPalecki Jul 4, 2025
34bb911
Profile and hidden models converted to DI
JacobPalecki Jul 4, 2025
5c8ea31
Coalescion and anisotropy to DI plus some accel
JacobPalecki Jul 4, 2025
a72e0ef
Most of profile and accel changed to use DI
JacobPalecki Jul 5, 2025
63f6482
Partial work on setting editable settings from data
JacobPalecki Jul 6, 2025
af9f723
Tests build
JacobPalecki Jul 13, 2025
560f4ef
Add editableSelectable and tests pass
JacobPalecki Jul 13, 2025
162f0bd
Acceleration models now selectable
JacobPalecki Jul 13, 2025
a802aed
Working DI through BackEnd and into UI
JacobPalecki Jul 13, 2025
3c2f7f0
Starting DI through devices
JacobPalecki Jul 13, 2025
d5c15d1
Starting DI through models
JacobPalecki Jul 14, 2025
777d79d
Add validators and parsers to DI
JacobPalecki Jul 21, 2025
df41150
Add named editablesettings
JacobPalecki Jul 21, 2025
82a7cf1
Register IO Layer Components in DI
lexm2 Oct 22, 2025
2609606
Register CurvePreview in DI
lexm2 Oct 22, 2025
6f7e972
Create Specialized Validators
lexm2 Oct 22, 2025
e8a6080
Register DeviceGroups and Validators
lexm2 Oct 22, 2025
a2aba28
Register MappingsModel and Dependencies
lexm2 Oct 22, 2025
7c8a4ad
Complete BackEnd DI Integration
lexm2 Oct 22, 2025
d998c5b
Handle Default Profile
lexm2 Oct 22, 2025
7546607
Update Application Bootstrap
lexm2 Oct 22, 2025
0890580
Revert "Handle Default Profile"
lexm2 Oct 26, 2025
d12e4a4
Device groups are strings now + load default profile
lexm2 Oct 26, 2025
37c7412
Properly load devices from data
lexm2 Oct 26, 2025
9119560
Fix device loading and profile insertion
lexm2 Oct 26, 2025
6f46801
Changed method signatures to use Interfaces
lexm2 Oct 26, 2025
8a9c89b
Fix bootstrapper
lexm2 Oct 26, 2025
e6742d1
parsers from static to DI
lexm2 Oct 26, 2025
707037a
add missing imports
lexm2 Oct 26, 2025
b424448
Fix UI compile time errors
lexm2 Oct 26, 2025
462457c
Defaults for everything and runtime errors fixed
lexm2 Oct 26, 2025
9e66b07
Add device defult
lexm2 Oct 26, 2025
cbe7bcd
Load default device like the others
lexm2 Oct 26, 2025
2abefac
defaults with defaults
lexm2 Oct 26, 2025
8075fea
Fix MaxNameLength to match driver constant
lexm2 Oct 26, 2025
9aef04f
Merge pull request #4 from lexm2/userinterface-di-migration
JacobPalecki Nov 8, 2025
30a3760
Merge PR #316: Implement Dependency Injection for backend
lexm2 Dec 23, 2025
e1d8e73
Add SettingsReaderWriter to DI container
lexm2 Dec 24, 2025
c650627
Add convenience properties and missing interface members to backend m…
lexm2 Dec 24, 2025
fb01e7c
Update App.axaml.cs DI registrations to use interfaces
lexm2 Dec 24, 2025
73166af
Update UI ViewModels to use backend interfaces instead of concrete types
lexm2 Dec 24, 2025
cbc8922
Update UI Views to use backend interfaces
lexm2 Dec 24, 2025
525a0af
Fix IBackEnd namespace issues and IMappingsModel interface inheritance
lexm2 Dec 24, 2025
3ddd5aa
Fix remaining interface properties and type conversion errors
lexm2 Dec 24, 2025
5434380
Remove commented ReadOnlyCollection manipulation code
lexm2 Dec 24, 2025
5704ddf
Fix MainWindowViewModel namespace alias for IProfileModel
lexm2 Dec 24, 2025
2a0f0e6
Fix IBackEnd usage to use alias instead of BE.IBackEnd
lexm2 Dec 24, 2025
c9d998f
Fix DevicesPageViewModel to use IBackEnd interface
lexm2 Dec 24, 2025
ece975f
Fix RangeYDIKey typo causing NullReferenceException in AnisotropyModel
lexm2 Dec 24, 2025
705c3bc
Fix null data handling in deserialization for Anisotropy and Accelera…
lexm2 Dec 24, 2025
ac920fe
Fix InvalidCastException for FormulaAccelModel and SynchronousAccelModel
lexm2 Dec 24, 2025
5fca97f
Add IEditableSettingsSelector to IFormulaAccelModel interface
lexm2 Dec 24, 2025
aac215f
Change SettingsService to use IBackEnd interface
lexm2 Dec 24, 2025
901de00
Change MappingsPageViewModel to use IBackEnd interface
lexm2 Dec 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,5 @@ ASALocalRun/
.localhistory/

# BeatPulse healthcheck temp database
healthchecksdb
healthchecksdb
.vscode/settings.json
46 changes: 24 additions & 22 deletions userinterface/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
using userinterface.ViewModels.Settings;
using userinterface.Views;
using userspace_backend;
using Windows.System;
using userspace_backend.IO;
using DATA = userspace_backend.Data;

namespace userinterface;
Expand All @@ -35,10 +35,8 @@ public override void OnFrameworkInitializationCompleted()
{
var services = new ServiceCollection();

// Register logging
services.AddLogging(builder =>
{
// Change this to be "LogLevel.Debug" if you want to see logs.
#if DEBUG
builder.AddDebug();
builder.SetMinimumLevel(LogLevel.Warning);
Expand All @@ -47,7 +45,16 @@ public override void OnFrameworkInitializationCompleted()
#endif
});

// Register services
string settingsDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
services.AddSingleton<IBackEndLoader>(sp =>
{
var devicesRW = sp.GetRequiredService<DevicesReaderWriter>();
var mappingsRW = sp.GetRequiredService<MappingsReaderWriter>();
var profileRW = sp.GetRequiredService<ProfileReaderWriter>();
var settingsRW = sp.GetRequiredService<SettingsReaderWriter>();
return new BackEndLoader(settingsDirectory, devicesRW, mappingsRW, profileRW, settingsRW);
});

services.AddSingleton<INotificationService>(provider =>
new NotificationService(provider.GetRequiredService<LocalizationService>(), provider.GetRequiredService<ISettingsService>()));
services.AddSingleton<IModalService>(provider =>
Expand All @@ -59,25 +66,15 @@ public override void OnFrameworkInitializationCompleted()
services.AddSingleton<FrameTimerService>();
services.AddSingleton<PreviewChartRenderer>();
services.AddSingleton<IAnimationStateService, AnimationStateService>();

// Register backend services
services.AddSingleton<Bootstrapper>(provider => BootstrapBackEnd());
services.AddSingleton<BackEnd>(provider =>
{
var bootstrapper = provider.GetRequiredService<Bootstrapper>();
var backEnd = new BackEnd(bootstrapper);
backEnd.Load();
return backEnd;
});

// Register settings service that depends on backend
services.AddSingleton<ISettingsService, SettingsService>();

RegisterViewModels(services);

Services = services.BuildServiceProvider();
Services = BackEndComposer.Compose(services);

IBackEnd backEnd = Services.GetRequiredService<IBackEnd>();
backEnd.Load();

// Apply settings from backend after services are built
ApplyStartupSettings();

if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
Expand Down Expand Up @@ -119,7 +116,7 @@ private void RegisterViewModels(IServiceCollection services)
// Main ViewModels
services.AddSingleton<MainWindowViewModel>(provider =>
new MainWindowViewModel(
provider.GetRequiredService<BackEnd>(),
provider.GetRequiredService<IBackEnd>(),
provider.GetRequiredService<IThemeService>(),
provider.GetRequiredService<ISettingsService>(),
provider.GetRequiredService<FrameTimerService>()));
Expand All @@ -128,12 +125,12 @@ private void RegisterViewModels(IServiceCollection services)
// Device ViewModels
services.AddTransient<ViewModels.Device.DevicesPageViewModel>(provider =>
new ViewModels.Device.DevicesPageViewModel(
provider.GetRequiredService<BackEnd>(),
provider.GetRequiredService<IBackEnd>(),
provider.GetRequiredService<IModalService>(),
provider.GetRequiredService<LocalizationService>()));
services.AddTransient<ViewModels.Device.DevicesListViewModel>(provider =>
new ViewModels.Device.DevicesListViewModel(
provider.GetRequiredService<BackEnd>().Devices,
provider.GetRequiredService<IBackEnd>().Devices,
provider.GetRequiredService<IModalService>(),
provider.GetRequiredService<LocalizationService>()));
services.AddTransient<ViewModels.Device.DeviceGroupsViewModel>();
Expand Down Expand Up @@ -178,7 +175,12 @@ protected static 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(),
new SettingsReaderWriter()),
DevicesToLoad =
[
new DATA.Device() { Name = "Superlight 2", DPI = 32000, HWID = @"HID\VID_046D&PID_C54D&MI_00", PollingRate = 1000, DeviceGroup = "Logitech Mice" },
Expand Down
6 changes: 3 additions & 3 deletions userinterface/Services/IViewModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ namespace userinterface.Services
{
public interface IViewModelFactory
{
ProfileViewModel CreateProfileViewModel(BE.ProfileModel profileModel);
ProfileSettingsViewModel CreateProfileSettingsViewModel(BE.ProfileModel profileModel);
ProfileChartViewModel CreateProfileChartViewModel(BE.ProfileModel profileModel);
ProfileViewModel CreateProfileViewModel(BE.IProfileModel profileModel);
ProfileSettingsViewModel CreateProfileSettingsViewModel(BE.IProfileModel profileModel);
ProfileChartViewModel CreateProfileChartViewModel(BE.IProfileModel profileModel);
MappingViewModel CreateMappingViewModel(BE.MappingModel mappingModel, BE.MappingsModel mappingsModel, bool isActive, Action<MappingViewModel> onActivationRequested);
}
}
4 changes: 2 additions & 2 deletions userinterface/Services/SettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ namespace userinterface.Services;

public class SettingsService : ISettingsService
{
private readonly BackEnd backEnd;
private readonly IBackEnd backEnd;

public SettingsService(BackEnd backEnd)
public SettingsService(IBackEnd backEnd)
{
this.backEnd = backEnd;

Expand Down
24 changes: 12 additions & 12 deletions userinterface/Services/ViewModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,45 @@ public ViewModelFactory(IServiceProvider serviceProvider)

private IServiceProvider ServiceProvider { get; }

public ProfileViewModel CreateProfileViewModel(BE.ProfileModel profileModel)
public ProfileViewModel CreateProfileViewModel(BE.IProfileModel profileModel)
{
var stopwatch = Stopwatch.StartNew();

var viewModel = ServiceProvider.GetRequiredService<ProfileViewModel>();
Debug.WriteLine($"ProfileViewModel service resolution: {stopwatch.ElapsedMilliseconds}ms");

stopwatch.Restart();
viewModel.Initialize(profileModel);
Debug.WriteLine($"ProfileViewModel initialize: {stopwatch.ElapsedMilliseconds}ms");

return viewModel;
}

public ProfileSettingsViewModel CreateProfileSettingsViewModel(BE.ProfileModel profileModel)
public ProfileSettingsViewModel CreateProfileSettingsViewModel(BE.IProfileModel profileModel)
{
var stopwatch = Stopwatch.StartNew();

var viewModel = ServiceProvider.GetRequiredService<ProfileSettingsViewModel>();
Debug.WriteLine($"ProfileSettingsViewModel service resolution: {stopwatch.ElapsedMilliseconds}ms");

stopwatch.Restart();
viewModel.Initialize(profileModel);
Debug.WriteLine($"ProfileSettingsViewModel initialize: {stopwatch.ElapsedMilliseconds}ms");

return viewModel;
}

public ProfileChartViewModel CreateProfileChartViewModel(BE.ProfileModel profileModel)
public ProfileChartViewModel CreateProfileChartViewModel(BE.IProfileModel profileModel)
{
var stopwatch = Stopwatch.StartNew();

var viewModel = ServiceProvider.GetRequiredService<ProfileChartViewModel>();
Debug.WriteLine($"ProfileChartViewModel service resolution: {stopwatch.ElapsedMilliseconds}ms");

stopwatch.Restart();
viewModel.Initialize(profileModel);
Debug.WriteLine($"ProfileChartViewModel initialize: {stopwatch.ElapsedMilliseconds}ms");

return viewModel;
}

Expand Down
16 changes: 8 additions & 8 deletions userinterface/ViewModels/Device/DeviceGroupSelectorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeviceGroupModel> DeviceGroupEntries =>
public ObservableCollection<string> 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;
}
}
Expand All @@ -37,15 +37,15 @@ public DeviceGroupModel SelectedEntry

public void RefreshSelectedDeviceGroup()
{
if (!DeviceGroupEntries.Contains(Device.DeviceGroup))
if (!DeviceGroupEntries.Contains(Device.DeviceGroup.ModelValue))
{
IsValid = false;
SelectedEntry = DeviceGroups.DefaultDeviceGroup;
return;
}

IsValid = true;
selectedEntry = Device.DeviceGroup;
selectedEntry = Device.DeviceGroup.ModelValue;
}
}
}
4 changes: 2 additions & 2 deletions userinterface/ViewModels/Device/DeviceGroupViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace userinterface.ViewModels.Device
{
public partial class DeviceGroupViewModel : ViewModelBase
{
public DeviceGroupViewModel(BE.DeviceGroupModel deviceGroupBE, BE.DeviceGroups deviceGroupsBE, bool isDefault = false)
public DeviceGroupViewModel(string deviceGroupBE, BE.DeviceGroups deviceGroupsBE, bool isDefault = false)
{
DeviceGroupBE = deviceGroupBE;
DeviceGroupsBE = deviceGroupsBE;
Expand All @@ -17,7 +17,7 @@ public DeviceGroupViewModel(BE.DeviceGroupModel deviceGroupBE, BE.DeviceGroups d
() => DeleteSelf());
}

public BE.DeviceGroupModel DeviceGroupBE { get; }
public string DeviceGroupBE { get; }

protected BE.DeviceGroups DeviceGroupsBE { get; }

Expand Down
2 changes: 1 addition & 1 deletion userinterface/ViewModels/Device/DeviceGroupsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public DeviceGroupsViewModel(BE.DeviceGroups deviceGroupsBE)

protected BE.DeviceGroups DeviceGroupsBE { get; }

public ObservableCollection<BE.DeviceGroupModel> DeviceGroups => DeviceGroupsBE.DeviceGroupModels;
public ObservableCollection<string> DeviceGroups => DeviceGroupsBE.DeviceGroupModels;

public ObservableCollection<DeviceGroupViewModel> DeviceGroupViews { get; }

Expand Down
7 changes: 4 additions & 3 deletions userinterface/ViewModels/Device/DeviceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public partial class DeviceViewModel : ViewModelBase
{
private readonly IModalService modalService;

public DeviceViewModel(BE.DeviceModel deviceBE, BE.DevicesModel devicesBE, IModalService modalService, LocalizationService localizationService, bool isDefault = false, Func<DeviceViewModel, Task>? animatedDeleteCallback = null)
public DeviceViewModel(BE.IDeviceModel deviceBE, BE.DevicesModel devicesBE, IModalService modalService, LocalizationService localizationService, bool isDefault = false, Func<DeviceViewModel, Task>? animatedDeleteCallback = null)
{
DeviceBE = deviceBE;
DevicesBE = devicesBE;
Expand All @@ -37,7 +37,7 @@ public DeviceViewModel(BE.DeviceModel deviceBE, BE.DevicesModel devicesBE, IModa
DeleteCommand = new RelayCommand(async () => await DeleteWithAnimation());
}

internal BE.DeviceModel DeviceBE { get; }
internal BE.IDeviceModel DeviceBE { get; }

internal BE.DevicesModel DevicesBE { get; }

Expand Down Expand Up @@ -99,7 +99,8 @@ private async Task DeleteWithAnimation()

public void DeleteSelf()
{
DevicesBE.RemoveDevice(DeviceBE);
bool success = DevicesBE.TryRemoveElement(DeviceBE);
System.Diagnostics.Debug.Assert(success);
}
}
}
14 changes: 7 additions & 7 deletions userinterface/ViewModels/Device/DevicesListViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ public DevicesListViewModel(BE.DevicesModel devicesBE, IModalService modalServic
this.localizationService = localizationService;
DeviceViews = [];
UpdateDeviceViews();
DevicesBE.Devices.CollectionChanged += DevicesCollectionChanged;
((INotifyCollectionChanged)DevicesBE.Elements).CollectionChanged += DevicesCollectionChanged;

AddDeviceCommand = new RelayCommand(
() => TryAddDevice());
}

protected BE.DevicesModel DevicesBE { get; }

public ObservableCollection<BE.DeviceModel> Devices => DevicesBE.Devices;
public ReadOnlyObservableCollection<BE.IDeviceModel> Devices => DevicesBE.Elements;

public ObservableCollection<DeviceViewModel> DeviceViews { get; }

Expand All @@ -52,9 +52,9 @@ private void DevicesCollectionChanged(object? sender, NotifyCollectionChangedEve
case NotifyCollectionChangedAction.Add:
if (e.NewItems != null)
{
foreach (BE.DeviceModel device in e.NewItems)
foreach (BE.IDeviceModel device in e.NewItems)
{
int index = DevicesBE.Devices.IndexOf(device);
int index = e.NewStartingIndex;
bool isDefault = index == 0;
var animateCallback = devicesListView != null ? (Func<DeviceViewModel, Task>)devicesListView.AnimateDeviceDelete : null;
var deviceViewModel = new DeviceViewModel(device, DevicesBE, modalService, localizationService, isDefault, animateCallback);
Expand Down Expand Up @@ -85,15 +85,15 @@ private void DevicesCollectionChanged(object? sender, NotifyCollectionChangedEve
public void UpdateDeviceViews()
{
DeviceViews.Clear();
for (int i = 0; i < DevicesBE.Devices.Count; i++)
for (int i = 0; i < DevicesBE.Elements.Count; i++)
{
var device = DevicesBE.Devices[i];
var device = DevicesBE.Elements[i];
bool isDefault = i == 0;
var animateCallback = devicesListView != null ? (Func<DeviceViewModel, Task>)devicesListView.AnimateDeviceDelete : null;
DeviceViews.Add(new DeviceViewModel(device, DevicesBE, modalService, localizationService, isDefault, animateCallback));
}
}

public bool TryAddDevice() => DevicesBE.TryAddDevice();
public bool TryAddDevice() => DevicesBE.TryAddNewDefault();
}
}
3 changes: 2 additions & 1 deletion userinterface/ViewModels/Device/DevicesPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using userinterface.Services;
using IBackEnd = userspace_backend.IBackEnd;
using BE = userspace_backend.Model;

namespace userinterface.ViewModels.Device
Expand All @@ -13,7 +14,7 @@ public partial class DevicesPageViewModel : ViewModelBase
private readonly IModalService modalService;
private readonly LocalizationService localizationService;

public DevicesPageViewModel(userspace_backend.BackEnd backEnd, IModalService modalService, LocalizationService localizationService)
public DevicesPageViewModel(IBackEnd backEnd, IModalService modalService, LocalizationService localizationService)
{
devicesModel = backEnd?.Devices ?? throw new ArgumentNullException(nameof(backEnd));
this.modalService = modalService;
Expand Down
Loading