1
0
mirror of synced 2024-12-22 00:53:08 +01:00

Fixed: send initial values when device connects

Implemented various TODO's
This commit is contained in:
Mark van Renswoude 2021-02-28 14:40:51 +01:00
parent de27a6ccee
commit cd1ab91f23
19 changed files with 204 additions and 119 deletions

View File

@ -6,7 +6,7 @@ namespace MassiveKnob.Plugin.CoreAudio.Base
{
public Guid? DeviceId { get; set; }
// TODO more options, like positioning and style
// TODO (nice to have) more options, like positioning and style
public bool OSD { get; set; } = true;
}
}

View File

@ -1,24 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MassiveKnob.Plugin.CoreAudio.Base
namespace MassiveKnob.Plugin.CoreAudio.Base
{
/// <summary>
/// Interaction logic for BaseDeviceSettingsView.xaml
/// </summary>
public partial class BaseDeviceSettingsView : UserControl
public partial class BaseDeviceSettingsView
{
public BaseDeviceSettingsView()
{

View File

@ -6,8 +6,6 @@ using Microsoft.Extensions.Logging;
namespace MassiveKnob.Plugin.CoreAudio.GetMuted
{
// TODO send out initial muted state after proper initialization
public class DeviceGetMutedAction : IMassiveKnobAction
{
public Guid ActionId { get; } = new Guid("86646ca7-f472-4c5a-8d0f-7e5d2d162ab9");
@ -54,6 +52,9 @@ namespace MassiveKnob.Plugin.CoreAudio.GetMuted
deviceChanged?.Dispose();
deviceChanged = playbackDevice?.MuteChanged.Subscribe(MuteChanged);
if (playbackDevice != null)
actionContext.SetDigitalOutput(settings.Inverted ? !playbackDevice.IsMuted : playbackDevice.IsMuted);
}

View File

@ -6,8 +6,6 @@ using Microsoft.Extensions.Logging;
namespace MassiveKnob.Plugin.CoreAudio.GetVolume
{
// TODO send out initial volume after proper initialization
public class DeviceGetVolumeAction : IMassiveKnobAction
{
public Guid ActionId { get; } = new Guid("6ebf91af-8240-4a75-9729-c6a1eb60dcba");
@ -54,6 +52,9 @@ namespace MassiveKnob.Plugin.CoreAudio.GetVolume
deviceChanged?.Dispose();
deviceChanged = playbackDevice?.VolumeChanged.Subscribe(VolumeChanged);
if (playbackDevice != null)
actionContext.SetAnalogOutput((byte)playbackDevice.Volume);
}

View File

@ -1,6 +1,4 @@
using MassiveKnob.Plugin.CoreAudio.SetMuted;
namespace MassiveKnob.Plugin.CoreAudio.GetVolume
namespace MassiveKnob.Plugin.CoreAudio.GetVolume
{
/// <summary>
/// Interaction logic for DeviceGetVolumeActionSettingsView.xaml

View File

@ -1,6 +1,5 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Security.RightsManagement;
using System.Windows;
using AudioSwitcher.AudioApi;

View File

@ -14,34 +14,35 @@
x:Key="SpeakerIcon"
d:DataContext="{d:DesignInstance osd:OSDWindowViewModel}">
<Canvas Width="256" Height="256">
<Polygon Visibility="{Binding IsNotMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Points=" 133.5,215.101 61.75,168 8.75,168 8.75,88 61.75,88 133.5,40.899 " Name="Speaker_1_" FillRule="NonZero" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round"/>
<Path Visibility="{Binding VolumeLowVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="Low" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Polygon Visibility="{Binding IsNotMutedVisibility}"
Points=" 133.5,215.101 61.75,168 8.75,168 8.75,88 61.75,88 133.5,40.899 " Name="Speaker_1_" FillRule="NonZero" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round"/>
<Path Visibility="{Binding VolumeLowVisibility}" Name="Low" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data>
<PathGeometry Figures=" M166.806 86c0 0 12.528 15.833 12.528 40.167s-12.528 43.823-12.528 43.823" FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Visibility="{Binding VolumeMediumVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="Medium" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path Visibility="{Binding VolumeMediumVisibility}" Name="Medium" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data>
<PathGeometry Figures=" M188.479 57c0 0 21.183 26.769 21.183 67.91c0 41.141-21.183 74.089-21.183 74.089" FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Visibility="{Binding VolumeHighVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="High" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path Visibility="{Binding VolumeHighVisibility}" Name="High" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data>
<PathGeometry Figures=" M216.737 35.517c0 0 27.944 35.316 27.944 89.593s-27.944 97.75-27.944 97.75" FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Visibility="{Binding IsMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path4561" Fill="#FFFFFFFF">
<Path Visibility="{Binding IsMutedVisibility}" Name="path4561" Fill="#FFFFFFFF">
<Path.Data>
<PathGeometry Figures="M160.503 221.101c-1.717 0-3.421-0.732-4.608-2.153L10.395 44.746c-2.125-2.543-1.785-6.327 0.759-8.451 c2.544-2.125 6.328-1.784 8.451 0.759l145.5 174.201c2.124 2.544 1.784 6.327-0.759 8.452 C163.224 220.644 161.859 221.101 160.503 221.101z" FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Visibility="{Binding IsMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path4563" Fill="#FFFFFFFF">
<Path Visibility="{Binding IsMutedVisibility}" Name="path4563" Fill="#FFFFFFFF">
<Path.Data>
<PathGeometry Figures="M127.5 203.984l-62.458-41C64.064 162.342 62.92 162 61.75 162h-47V94h28.967L33.694 82H8.75c-3.313 0-6 2.687-6 6v80 c0 3.313 2.687 6 6 6h51.207l70.25 46.116c0.997 0.654 2.144 0.984 3.293 0.984c0.979 0 1.958-0.238 2.85-0.72 c1.94-1.048 3.15-3.075 3.15-5.28v-6.423l-12-14.367V203.984z" FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Visibility="{Binding IsMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path4565" Fill="#FFFFFFFF">
<Path Visibility="{Binding IsMutedVisibility}" Name="path4565" Fill="#FFFFFFFF">
<Path.Data>
<PathGeometry Figures="M127.5 52.016v104.856l12 14.367V40.899c0-2.205-1.21-4.232-3.15-5.28c-1.939-1.047-4.299-0.947-6.143 0.264L63.19 79.877 l7.744 9.271L127.5 52.016z" FillRule="NonZero"/>
</Path.Data>

View File

@ -4,6 +4,5 @@ namespace MassiveKnob.Plugin.CoreAudio.SetVolume
{
public class DeviceSetVolumeActionSettings : BaseDeviceSettings
{
// TODO OSD
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;

View File

@ -73,7 +73,7 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
serialPorts = SerialPort.GetPortNames();
// TODO subscribe to device notification to refresh list
// TODO (must have - port from old source) subscribe to device notification to refresh list
}

View File

@ -12,6 +12,10 @@ namespace MassiveKnob.Plugin
/// <summary>
/// Called right after this instance is created.
/// </summary>
/// <remarks>
/// Do not perform anything but basic initialization until this method is called, as the action
/// instance will be created temporarily at startup to verify it is correctly implemented!
/// </remarks>
/// <param name="context">Provides an interface to the Massive Knob settings and device. Can be stored until the action instance is disposed.</param>
void Initialize(IMassiveKnobActionContext context);

View File

@ -1,5 +1,7 @@
using System;
// ReSharper disable UnusedMember.Global - public API
namespace MassiveKnob.Plugin
{
/// <summary>

View File

@ -1,5 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EOF/@EntryIndexedValue">EOF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MIN/@EntryIndexedValue">MIN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSD/@EntryIndexedValue">OSD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SOF/@EntryIndexedValue">SOF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>

View File

@ -5,7 +5,6 @@ using System.Reactive.Subjects;
using MassiveKnob.Helpers;
using MassiveKnob.Plugin;
using MassiveKnob.Settings;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Serilog.Extensions.Logging;
using ILogger = Serilog.ILogger;
@ -30,6 +29,9 @@ namespace MassiveKnob.Model
private readonly List<ActionMapping> analogOutputs = new List<ActionMapping>();
private readonly List<ActionMapping> digitalOutputs = new List<ActionMapping>();
private readonly Dictionary<int, byte> analogOutputValues = new Dictionary<int, byte>();
private readonly Dictionary<int, bool> digitalOutputValues = new Dictionary<int, bool>();
public MassiveKnobDeviceInfo ActiveDevice
{
@ -58,17 +60,22 @@ namespace MassiveKnob.Model
{
activeDevice?.Instance?.Dispose();
void DisposeMappings(IEnumerable<ActionMapping> mappings)
void DisposeMappings(ICollection<ActionMapping> mappings)
{
foreach (var mapping in mappings)
mapping?.ActionInfo.Instance?.Dispose();
mappings.Clear();
}
lock (settingsLock)
{
DisposeMappings(analogInputs);
DisposeMappings(digitalInputs);
DisposeMappings(analogOutputs);
DisposeMappings(digitalOutputs);
}
DisposeMappings(analogInputs);
DisposeMappings(digitalInputs);
DisposeMappings(analogOutputs);
DisposeMappings(digitalOutputs);
activeDeviceInfoSubject?.Dispose();
}
@ -101,24 +108,29 @@ namespace MassiveKnob.Model
public MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index)
{
var list = GetActionMappingList(actionType);
return index >= list.Count ? null : list[index]?.ActionInfo;
lock (settingsLock)
{
var list = GetActionMappingList(actionType);
return index >= list.Count ? null : list[index]?.ActionInfo;
}
}
public MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action)
{
var list = GetActionMappingList(actionType);
if (index >= list.Count)
return null;
if (list[index]?.ActionInfo.Info == action)
return list[index].ActionInfo;
List<ActionMapping> list;
list[index]?.ActionInfo.Instance?.Dispose();
lock (settingsLock)
{
list = GetActionMappingList(actionType);
if (index >= list.Count)
return null;
if (list[index]?.ActionInfo.Info == action)
return list[index].ActionInfo;
list[index]?.ActionInfo.Instance?.Dispose();
var settingsList = GetActionSettingsList(actionType);
while (index >= settingsList.Count)
settingsList.Add(null);
@ -139,7 +151,11 @@ namespace MassiveKnob.Model
initializeAfterRegistration = () => actionInstance.Initialize(actionContext);
});
list[index] = mapping;
lock (settingsLock)
{
list[index] = mapping;
}
initializeAfterRegistration?.Invoke();
return mapping?.ActionInfo;
@ -225,15 +241,15 @@ namespace MassiveKnob.Model
protected T GetActionSettings<T>(IMassiveKnobActionContext context, IMassiveKnobAction action, int index) where T : class, new()
{
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return new T();
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
lock (settingsLock)
{
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return new T();
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
var settingsList = GetActionSettingsList(action.ActionType);
if (index >= settingsList.Count)
return new T();
@ -245,15 +261,15 @@ namespace MassiveKnob.Model
protected void SetActionSettings<T>(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, T actionSettings) where T : class, new()
{
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return;
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
lock (settingsLock)
{
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return;
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
var settingsList = GetActionSettingsList(action.ActionType);
while (index >= settingsList.Count)
@ -272,19 +288,29 @@ namespace MassiveKnob.Model
}
// TODO store output values for when the device connects and should receive initial values
protected void AnalogChanged(IMassiveKnobDeviceContext context, int analogInputIndex, byte value)
{
if (context != activeDeviceContext)
return;
var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog);
if (mapping == null || analogInputIndex >= mapping.Count || mapping[analogInputIndex] == null)
return;
IMassiveKnobAnalogAction analogAction;
lock (settingsLock)
{
if (analogOutputValues.TryGetValue(analogInputIndex, out var currentValue) && currentValue == value)
return;
if (mapping[analogInputIndex].ActionInfo.Instance is IMassiveKnobAnalogAction analogAction)
analogAction.AnalogChanged(value);
analogOutputValues[analogInputIndex] = value;
var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog);
if (mapping == null || analogInputIndex >= mapping.Count || mapping[analogInputIndex] == null)
return;
analogAction = mapping[analogInputIndex].ActionInfo.Instance as IMassiveKnobAnalogAction;
}
analogAction?.AnalogChanged(value);
}
@ -293,44 +319,70 @@ namespace MassiveKnob.Model
if (context != activeDeviceContext)
return;
var mapping = GetActionMappingList(MassiveKnobActionType.InputDigital);
if (mapping == null || digitalInputIndex >= mapping.Count || mapping[digitalInputIndex] == null)
return;
IMassiveKnobDigitalAction digitalAction;
if (mapping[digitalInputIndex].ActionInfo.Instance is IMassiveKnobDigitalAction digitalAction)
digitalAction.DigitalChanged(on);
lock (settingsLock)
{
if (digitalOutputValues.TryGetValue(digitalInputIndex, out var currentValue) && currentValue == on)
return;
digitalOutputValues[digitalInputIndex] = on;
var mapping = GetActionMappingList(MassiveKnobActionType.InputDigital);
if (mapping == null || digitalInputIndex >= mapping.Count || mapping[digitalInputIndex] == null)
return;
digitalAction = mapping[digitalInputIndex].ActionInfo.Instance as IMassiveKnobDigitalAction;
}
digitalAction?.DigitalChanged(on);
}
public void SetAnalogOutput(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, byte value)
{
if (activeDevice == null)
return;
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return;
if (list[index]?.Context != context)
return;
activeDevice.Instance.SetAnalogOutput(index, value);
}
public void SetDigitalOutput(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, bool on)
public void SetAnalogOutput(IMassiveKnobActionContext context, int index, byte value)
{
if (activeDevice == null)
return;
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
IMassiveKnobDeviceInstance deviceInstance;
lock (settingsLock)
{
var list = GetActionMappingList(MassiveKnobActionType.OutputAnalog);
if (index >= list.Count)
return;
if (context != null && list[index]?.Context != context)
return;
deviceInstance = activeDevice.Instance;
}
deviceInstance.SetAnalogOutput(index, value);
}
public void SetDigitalOutput(IMassiveKnobActionContext context, int index, bool on)
{
if (activeDevice == null)
return;
if (list[index]?.Context != context)
return;
IMassiveKnobDeviceInstance deviceInstance;
activeDevice.Instance.SetDigitalOutput(index, on);
lock (settingsLock)
{
var list = GetActionMappingList(MassiveKnobActionType.OutputDigital);
if (index >= list.Count)
return;
if (context != null && list[index]?.Context != context)
return;
deviceInstance = activeDevice.Instance;
}
deviceInstance.SetDigitalOutput(index, on);
}
@ -424,6 +476,14 @@ namespace MassiveKnob.Model
ActiveDevice.Info,
ActiveDevice.Instance,
specs);
// Send out all cached values to initialize the device's outputs
foreach (var pair in analogOutputValues.Where(pair => pair.Key < specs.AnalogOutputCount))
SetAnalogOutput(null, pair.Key, pair.Value);
foreach (var pair in digitalOutputValues.Where(pair => pair.Key < specs.DigitalOutputCount))
SetDigitalOutput(null, pair.Key, pair.Value);
}
@ -520,14 +580,13 @@ namespace MassiveKnob.Model
public void Connecting()
{
// TODO update status ?
// TODO (should have) update status ?
}
public void Connected(DeviceSpecs specs)
{
// TODO update status ?
// TODO send out initial values for outputs
// TODO (should have) update status ?
owner.UpdateActiveDeviceSpecs(this, specs);
}
@ -535,7 +594,7 @@ namespace MassiveKnob.Model
public void Disconnected()
{
// TODO update status ?
// TODO (should have) update status ?
}
@ -581,13 +640,13 @@ namespace MassiveKnob.Model
public void SetAnalogOutput(byte value)
{
owner.SetAnalogOutput(this, action, index, value);
owner.SetAnalogOutput(this, index, value);
}
public void SetDigitalOutput(bool on)
{
owner.SetDigitalOutput(this, action, index, on);
owner.SetDigitalOutput(this, index, on);
}
}
}

View File

@ -4,6 +4,8 @@ using System.IO;
using System.Linq;
using System.Reflection;
using MassiveKnob.Plugin;
using Serilog;
using Serilog.Extensions.Logging;
namespace MassiveKnob.Model
{
@ -29,9 +31,16 @@ namespace MassiveKnob.Model
public class PluginManager : IPluginManager
{
private readonly ILogger logger;
private readonly List<IMassiveKnobPlugin> plugins = new List<IMassiveKnobPlugin>();
public PluginManager(ILogger logger)
{
this.logger = logger;
}
public IEnumerable<IMassiveKnobDevicePlugin> GetDevicePlugins()
{
return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast<IMassiveKnobDevicePlugin>();
@ -101,7 +110,7 @@ namespace MassiveKnob.Model
}
private static void ValidateRegistration(string filename, IMassiveKnobPlugin plugin, RegisteredIds registeredIds)
private void ValidateRegistration(string filename, IMassiveKnobPlugin plugin, RegisteredIds registeredIds)
{
// Make sure all GUIDs are actually unique and someone has not copy/pasted a plugin without
// modifying the values. This way we can safely make that assumption in other code.
@ -133,11 +142,40 @@ namespace MassiveKnob.Model
throw new MassiveKnobPluginIdConflictException(action.ActionId, conflictingActionFilename, filename);
registeredIds.ActionById.Add(action.ActionId, filename);
// TODO check ActionType vs. implemented interfaces
ValidateActionType(action);
}
}
}
private void ValidateActionType(IMassiveKnobAction action)
{
var instance = action.Create(new SerilogLoggerProvider(logger).CreateLogger(null));
if (instance == null)
throw new NullReferenceException("Create method must not return null");
switch (action.ActionType)
{
case MassiveKnobActionType.InputAnalog:
if (!(instance is IMassiveKnobAnalogAction))
throw new InvalidCastException("InputAnalog action must implement IMassiveKnobAnalogAction");
break;
case MassiveKnobActionType.InputDigital:
if (!(instance is IMassiveKnobDigitalAction))
throw new InvalidCastException("InputDigital action must implement IMassiveKnobDigitalAction");
break;
case MassiveKnobActionType.OutputAnalog:
case MassiveKnobActionType.OutputDigital:
break;
default:
throw new ArgumentOutOfRangeException(nameof(action.ActionType), action.ActionType, @"Unsupported action type: " + (int)action.ActionType);
}
}
private class RegisteredIds

View File

@ -21,7 +21,7 @@ namespace MassiveKnob
[STAThread]
public static int Main()
{
// TODO make configurable
// TODO (should have) make configurable
var loggingLevelSwitch = new LoggingLevelSwitch();
//var loggingLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
@ -35,7 +35,7 @@ namespace MassiveKnob
.CreateLogger();
var pluginManager = new PluginManager();
var pluginManager = new PluginManager(logger);
var messages = new StringBuilder();
pluginManager.Load((exception, filename) =>

View File

@ -10,7 +10,6 @@ namespace MassiveKnob.ViewModel
{
public class InputOutputViewModel : INotifyPropertyChanged
{
private readonly SettingsViewModel settingsViewModel;
private readonly IMassiveKnobOrchestrator orchestrator;
private readonly MassiveKnobActionType actionType;
private readonly int index;
@ -61,7 +60,6 @@ namespace MassiveKnob.ViewModel
public InputOutputViewModel(SettingsViewModel settingsViewModel, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType actionType, int index)
{
this.settingsViewModel = settingsViewModel;
this.orchestrator = orchestrator;
this.actionType = actionType;
this.index = index;

View File

@ -10,7 +10,7 @@ using MassiveKnob.Plugin;
namespace MassiveKnob.ViewModel
{
// TODO better design-time version
// TODO (nice to have) better design-time version
public class SettingsViewModel : INotifyPropertyChanged
{
private readonly IMassiveKnobOrchestrator orchestrator;

@ -1 +1 @@
Subproject commit 223cfafaf40e0e26e7660860ddfd755cb671f81b
Subproject commit 6db7da6234713a50a2c278c00bcd710249738e5e