Fixed bug in caching output values
Fixed VoiceMeeter version selection Added device status indicator
This commit is contained in:
parent
2525fae237
commit
69a93f68d5
Binary file not shown.
@ -80,7 +80,7 @@ namespace MassiveKnob.Plugin.CoreAudio.GetDefault
|
||||
|
||||
CheckActive();
|
||||
|
||||
// TODO default OSD
|
||||
// TODO (should have) OSD for changing default
|
||||
//if (settings.OSD)
|
||||
//OSDManager.Show(args.Device);
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ namespace MassiveKnob.Plugin.CoreAudio.SetDefault
|
||||
await playbackDevice.SetAsDefaultCommunicationsAsync();
|
||||
|
||||
|
||||
// TODO OSD for default device
|
||||
// TODO (should have) OSD for changing default
|
||||
//if (settings.OSD)
|
||||
//OSDManager.Show(playbackDevice);
|
||||
}
|
||||
|
@ -98,12 +98,6 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnOtherPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void OnNext(DeviceNotificationEvent value)
|
||||
{
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace MassiveKnob.Plugin.VoiceMeeter.Base
|
||||
using System;
|
||||
|
||||
namespace MassiveKnob.Plugin.VoiceMeeter.Base
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for BaseVoiceMeeterSettingsView.xaml
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Voicemeeter;
|
||||
|
||||
@ -16,10 +18,11 @@ namespace MassiveKnob.Plugin.VoiceMeeter.Base
|
||||
|
||||
|
||||
|
||||
public class BaseVoiceMeeterSettingsViewModel : INotifyPropertyChanged
|
||||
public class BaseVoiceMeeterSettingsViewModel : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
protected readonly BaseVoiceMeeterSettings Settings;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public event EventHandler Disposed;
|
||||
|
||||
// ReSharper disable UnusedMember.Global - used by WPF Binding
|
||||
public IList<VoiceMeeterVersionViewModel> Versions { get; }
|
||||
@ -52,6 +55,14 @@ namespace MassiveKnob.Plugin.VoiceMeeter.Base
|
||||
new VoiceMeeterVersionViewModel(RunVoicemeeterParam.VoicemeeterBanana, "VoiceMeeter Banana"),
|
||||
new VoiceMeeterVersionViewModel(RunVoicemeeterParam.VoicemeeterPotato, "VoiceMeeter Potato")
|
||||
};
|
||||
|
||||
UpdateSelectedVersion();
|
||||
}
|
||||
|
||||
|
||||
private void UpdateSelectedVersion()
|
||||
{
|
||||
selectedVersion = Versions.SingleOrDefault(v => v.Version == Settings.Version) ?? Versions.First();
|
||||
}
|
||||
|
||||
|
||||
@ -63,10 +74,28 @@ namespace MassiveKnob.Plugin.VoiceMeeter.Base
|
||||
}
|
||||
|
||||
|
||||
public virtual void VoiceMeeterVersionChanged()
|
||||
{
|
||||
UpdateSelectedVersion();
|
||||
OnDependantPropertyChanged(nameof(SelectedVersion));
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
protected virtual void OnDependantPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Disposed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public class VoiceMeeterVersionViewModel
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Channels;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Voicemeeter;
|
||||
@ -12,18 +13,19 @@ namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter
|
||||
public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.OutputDigital;
|
||||
public string Name { get; } = Strings.GetParameterName;
|
||||
public string Description { get; } = Strings.GetParameterDescription;
|
||||
|
||||
|
||||
|
||||
|
||||
public IMassiveKnobActionInstance Create(ILogger logger)
|
||||
{
|
||||
return new Instance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private class Instance : IMassiveKnobActionInstance, IVoiceMeeterAction
|
||||
{
|
||||
private IMassiveKnobActionContext actionContext;
|
||||
private VoiceMeeterGetParameterActionSettings settings;
|
||||
private VoiceMeeterGetParameterActionSettingsViewModel viewModel;
|
||||
private Parameters parameters;
|
||||
private IDisposable parameterChanged;
|
||||
|
||||
@ -48,51 +50,54 @@ namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter
|
||||
|
||||
private void ApplySettings()
|
||||
{
|
||||
if (InstanceRegister.Version == RunVoicemeeterParam.None)
|
||||
if (InstanceRegister.Version == RunVoicemeeterParam.None || string.IsNullOrEmpty(settings.Parameter))
|
||||
{
|
||||
parameterChanged?.Dispose();
|
||||
parameterChanged = null;
|
||||
|
||||
parameters?.Dispose();
|
||||
parameters = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters == null)
|
||||
parameters = new Parameters();
|
||||
|
||||
if (string.IsNullOrEmpty(settings.Parameter))
|
||||
{
|
||||
parameterChanged?.Dispose();
|
||||
parameterChanged = null;
|
||||
}
|
||||
|
||||
if (parameterChanged == null)
|
||||
parameterChanged = parameters.Subscribe(x => ParametersChanged());
|
||||
|
||||
// TODO directly update output depending on value
|
||||
/*
|
||||
if (playbackDevice != null)
|
||||
actionContext.SetDigitalOutput(settings.Inverted ? !playbackDevice.IsMuted : playbackDevice.IsMuted);
|
||||
*/
|
||||
ParametersChanged();
|
||||
}
|
||||
|
||||
|
||||
public UserControl CreateSettingsControl()
|
||||
{
|
||||
var viewModel = new VoiceMeeterGetParameterActionSettingsViewModel(settings);
|
||||
viewModel = new VoiceMeeterGetParameterActionSettingsViewModel(settings);
|
||||
viewModel.PropertyChanged += (sender, args) =>
|
||||
{
|
||||
if (!viewModel.IsSettingsProperty(args.PropertyName))
|
||||
return;
|
||||
|
||||
|
||||
actionContext.SetSettings(settings);
|
||||
ApplySettings();
|
||||
};
|
||||
|
||||
viewModel.Disposed += (sender, args) =>
|
||||
{
|
||||
if (sender == viewModel)
|
||||
viewModel = null;
|
||||
};
|
||||
|
||||
return new VoiceMeeterGetParameterActionSettingsView(viewModel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void VoiceMeeterVersionChanged()
|
||||
{
|
||||
// TODO update viewModel
|
||||
// TODO reset parameterChanged subscription
|
||||
viewModel?.VoiceMeeterVersionChanged();
|
||||
|
||||
actionContext.SetSettings(settings);
|
||||
ApplySettings();
|
||||
}
|
||||
|
||||
|
||||
@ -101,11 +106,8 @@ namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter
|
||||
if (InstanceRegister.Version == RunVoicemeeterParam.None || string.IsNullOrEmpty(settings.Parameter))
|
||||
return;
|
||||
|
||||
// TODO if another task is already running, wait / chain
|
||||
// TODO only start task if not yet initialized
|
||||
Task.Run(async () =>
|
||||
InstanceRegister.InitializeVoicemeeter().ContinueWith(t =>
|
||||
{
|
||||
await InstanceRegister.InitializeVoicemeeter();
|
||||
bool on;
|
||||
|
||||
if (float.TryParse(settings.Value, out var settingsFloatValue))
|
||||
@ -130,10 +132,9 @@ namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter
|
||||
on = string.Equals(value, settings.Value, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
// TODO check specific parameter for changes, not just any parameter
|
||||
actionContext.SetDigitalOutput(settings.Inverted ? !on : on);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,22 @@
|
||||
namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter
|
||||
using System;
|
||||
|
||||
namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for VoiceMeeterGetParameterActionSettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class VoiceMeeterGetParameterActionSettingsView
|
||||
public partial class VoiceMeeterGetParameterActionSettingsView : IDisposable
|
||||
{
|
||||
public VoiceMeeterGetParameterActionSettingsView(VoiceMeeterGetParameterActionSettingsViewModel viewModel)
|
||||
{
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(DataContext as VoiceMeeterGetParameterActionSettingsViewModel)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -106,5 +106,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Reactive">
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -24,6 +25,7 @@ namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
{
|
||||
private IMassiveKnobActionContext actionContext;
|
||||
private VoiceMeeterRunMacroActionSettings settings;
|
||||
private VoiceMeeterRunMacroActionSettingsViewModel viewModel;
|
||||
|
||||
|
||||
public void Initialize(IMassiveKnobActionContext context)
|
||||
@ -43,7 +45,7 @@ namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
|
||||
public UserControl CreateSettingsControl()
|
||||
{
|
||||
var viewModel = new VoiceMeeterRunMacroActionSettingsViewModel(settings);
|
||||
viewModel = new VoiceMeeterRunMacroActionSettingsViewModel(settings);
|
||||
viewModel.PropertyChanged += (sender, args) =>
|
||||
{
|
||||
if (!viewModel.IsSettingsProperty(args.PropertyName))
|
||||
@ -52,6 +54,12 @@ namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
actionContext.SetSettings(settings);
|
||||
};
|
||||
|
||||
viewModel.Disposed += (sender, args) =>
|
||||
{
|
||||
if (sender == viewModel)
|
||||
viewModel = null;
|
||||
};
|
||||
|
||||
return new VoiceMeeterRunMacroActionSettingsView(viewModel);
|
||||
}
|
||||
|
||||
@ -71,8 +79,7 @@ namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
|
||||
public void VoiceMeeterVersionChanged()
|
||||
{
|
||||
// TODO update viewModel
|
||||
|
||||
viewModel?.VoiceMeeterVersionChanged();
|
||||
actionContext.SetSettings(settings);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,22 @@
|
||||
namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
using System;
|
||||
|
||||
namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for VoiceMeeterRunMacroActionSettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class VoiceMeeterRunMacroActionSettingsView
|
||||
public partial class VoiceMeeterRunMacroActionSettingsView : IDisposable
|
||||
{
|
||||
// ReSharper disable once SuggestBaseTypeForParameter
|
||||
public VoiceMeeterRunMacroActionSettingsView(VoiceMeeterRunMacroActionSettingsViewModel viewModel)
|
||||
{
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(DataContext as VoiceMeeterRunMacroActionSettingsViewModel)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,26 @@
|
||||
using MassiveKnob.Plugin.VoiceMeeter.Base;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using MassiveKnob.Plugin.VoiceMeeter.Base;
|
||||
|
||||
namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
{
|
||||
public class VoiceMeeterRunMacroActionSettingsViewModel : BaseVoiceMeeterSettingsViewModel<VoiceMeeterRunMacroActionSettings>
|
||||
public class VoiceMeeterRunMacroActionSettingsViewModel : BaseVoiceMeeterSettingsViewModel<VoiceMeeterRunMacroActionSettings>, IDisposable
|
||||
{
|
||||
// ReSharper disable UnusedMember.Global - used by WPF Bindingpriv
|
||||
private readonly Subject<bool> throttledScriptChanged = new Subject<bool>();
|
||||
private readonly IDisposable scriptChangedSubscription;
|
||||
|
||||
// ReSharper disable UnusedMember.Global - used by WPF Binding
|
||||
public string Script
|
||||
{
|
||||
get => Settings.Script;
|
||||
set
|
||||
{
|
||||
// TODO timer for change notification
|
||||
if (value == Settings.Script)
|
||||
return;
|
||||
|
||||
Settings.Script = value;
|
||||
OnPropertyChanged();
|
||||
throttledScriptChanged.OnNext(true);
|
||||
}
|
||||
}
|
||||
// ReSharper restore UnusedMember.Global
|
||||
@ -24,6 +29,19 @@ namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro
|
||||
// ReSharper disable once SuggestBaseTypeForParameter - by design
|
||||
public VoiceMeeterRunMacroActionSettingsViewModel(VoiceMeeterRunMacroActionSettings settings) : base(settings)
|
||||
{
|
||||
scriptChangedSubscription = throttledScriptChanged
|
||||
.Throttle(TimeSpan.FromSeconds(1))
|
||||
.Subscribe(b =>
|
||||
{
|
||||
OnDependantPropertyChanged(nameof(Script));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
scriptChangedSubscription?.Dispose();
|
||||
throttledScriptChanged?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,24 @@ namespace MassiveKnob.Core
|
||||
|
||||
MassiveKnobDeviceInfo SetActiveDevice(IMassiveKnobDevice device);
|
||||
|
||||
MassiveKnobDeviceStatus DeviceStatus { get; }
|
||||
IObservable<MassiveKnobDeviceStatus> DeviceStatusSubject { get; }
|
||||
|
||||
|
||||
MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index);
|
||||
MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action);
|
||||
|
||||
MassiveKnobSettings GetSettings();
|
||||
void UpdateSettings(Action<MassiveKnobSettings> applyChanges);
|
||||
}
|
||||
|
||||
|
||||
public enum MassiveKnobDeviceStatus
|
||||
{
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected
|
||||
}
|
||||
|
||||
|
||||
public class MassiveKnobDeviceInfo
|
||||
|
@ -17,11 +17,12 @@ namespace MassiveKnob.Core
|
||||
private readonly ILogger logger;
|
||||
|
||||
private readonly object settingsLock = new object();
|
||||
private MassiveKnobSettings massiveKnobSettings;
|
||||
private readonly MassiveKnobSettings settings;
|
||||
private readonly SerialQueue flushSettingsQueue = new SerialQueue();
|
||||
|
||||
private MassiveKnobDeviceInfo activeDevice;
|
||||
private readonly Subject<MassiveKnobDeviceInfo> activeDeviceInfoSubject = new Subject<MassiveKnobDeviceInfo>();
|
||||
private readonly Subject<MassiveKnobDeviceStatus> deviceStatusSubject = new Subject<MassiveKnobDeviceStatus>();
|
||||
private IMassiveKnobDeviceContext activeDeviceContext;
|
||||
|
||||
private readonly List<ActionMapping> analogInputs = new List<ActionMapping>();
|
||||
@ -48,12 +49,15 @@ namespace MassiveKnob.Core
|
||||
|
||||
public IObservable<MassiveKnobDeviceInfo> ActiveDeviceSubject => activeDeviceInfoSubject;
|
||||
|
||||
public MassiveKnobDeviceStatus DeviceStatus { get; private set; } = MassiveKnobDeviceStatus.Disconnected;
|
||||
public IObservable<MassiveKnobDeviceStatus> DeviceStatusSubject => deviceStatusSubject;
|
||||
|
||||
public MassiveKnobOrchestrator(IPluginManager pluginManager, ILogger logger, MassiveKnobSettings massiveKnobSettings)
|
||||
|
||||
public MassiveKnobOrchestrator(IPluginManager pluginManager, ILogger logger, MassiveKnobSettings settings)
|
||||
{
|
||||
this.pluginManager = pluginManager;
|
||||
this.logger = logger;
|
||||
this.massiveKnobSettings = massiveKnobSettings;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
|
||||
@ -87,11 +91,11 @@ namespace MassiveKnob.Core
|
||||
{
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (massiveKnobSettings.Device == null)
|
||||
if (settings.Device == null)
|
||||
return;
|
||||
|
||||
var allDevices = pluginManager.GetDevicePlugins().SelectMany(dp => dp.Devices);
|
||||
var device = allDevices.FirstOrDefault(d => d.DeviceId == massiveKnobSettings.Device.DeviceId);
|
||||
var device = allDevices.FirstOrDefault(d => d.DeviceId == settings.Device.DeviceId);
|
||||
|
||||
InternalSetActiveDevice(device, false);
|
||||
}
|
||||
@ -105,7 +109,7 @@ namespace MassiveKnob.Core
|
||||
return InternalSetActiveDevice(device, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index)
|
||||
{
|
||||
lock (settingsLock)
|
||||
@ -166,7 +170,7 @@ namespace MassiveKnob.Core
|
||||
{
|
||||
lock (settingsLock)
|
||||
{
|
||||
return massiveKnobSettings.Clone();
|
||||
return settings.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +179,7 @@ namespace MassiveKnob.Core
|
||||
{
|
||||
lock (settingsLock)
|
||||
{
|
||||
applyChanges(massiveKnobSettings);
|
||||
applyChanges(settings);
|
||||
}
|
||||
|
||||
FlushSettings();
|
||||
@ -193,10 +197,10 @@ namespace MassiveKnob.Core
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (device == null)
|
||||
massiveKnobSettings.Device = null;
|
||||
settings.Device = null;
|
||||
else
|
||||
{
|
||||
massiveKnobSettings.Device = new MassiveKnobSettings.DeviceSettings
|
||||
settings.Device = new MassiveKnobSettings.DeviceSettings
|
||||
{
|
||||
DeviceId = device.DeviceId,
|
||||
Settings = null
|
||||
@ -208,7 +212,10 @@ namespace MassiveKnob.Core
|
||||
}
|
||||
|
||||
ActiveDevice?.Instance.Dispose();
|
||||
SetDeviceStatus(null, MassiveKnobDeviceStatus.Disconnected);
|
||||
|
||||
// TODO (must have) move initialization to separate Task, to prevent issues at startup
|
||||
// TODO (must have) exception handling!
|
||||
if (device != null)
|
||||
{
|
||||
var instance = device.Create(new SerilogLoggerProvider(logger.ForContext("Context", new { Device = device.DeviceId })).CreateLogger(null));
|
||||
@ -230,11 +237,11 @@ namespace MassiveKnob.Core
|
||||
protected T GetDeviceSettings<T>(IMassiveKnobDeviceContext context) where T : class, new()
|
||||
{
|
||||
if (context != activeDeviceContext)
|
||||
throw new InvalidOperationException("Caller must be the active device to retrieve the massiveKnobSettings");
|
||||
throw new InvalidOperationException("Caller must be the active device to retrieve the settings");
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
return massiveKnobSettings.Device.Settings?.ToObject<T>() ?? new T();
|
||||
return settings.Device.Settings?.ToObject<T>() ?? new T();
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,23 +249,40 @@ namespace MassiveKnob.Core
|
||||
protected void SetDeviceSettings<T>(IMassiveKnobDeviceContext context, IMassiveKnobDevice device, T deviceSettings) where T : class, new()
|
||||
{
|
||||
if (context != activeDeviceContext)
|
||||
throw new InvalidOperationException("Caller must be the active device to update the massiveKnobSettings");
|
||||
throw new InvalidOperationException("Caller must be the active device to update the settings");
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (massiveKnobSettings.Device == null)
|
||||
massiveKnobSettings.Device = new MassiveKnobSettings.DeviceSettings
|
||||
if (settings.Device == null)
|
||||
settings.Device = new MassiveKnobSettings.DeviceSettings
|
||||
{
|
||||
DeviceId = device.DeviceId
|
||||
};
|
||||
|
||||
massiveKnobSettings.Device.Settings = JObject.FromObject(deviceSettings);
|
||||
settings.Device.Settings = JObject.FromObject(deviceSettings);
|
||||
}
|
||||
|
||||
FlushSettings();
|
||||
}
|
||||
|
||||
|
||||
protected void SetDeviceStatus(IMassiveKnobDeviceContext context, MassiveKnobDeviceStatus status)
|
||||
{
|
||||
if (context != null && context != activeDeviceContext)
|
||||
return;
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (status == DeviceStatus)
|
||||
return;
|
||||
|
||||
DeviceStatus = status;
|
||||
}
|
||||
|
||||
deviceStatusSubject.OnNext(status);
|
||||
}
|
||||
|
||||
|
||||
protected T GetActionSettings<T>(IMassiveKnobActionContext context, IMassiveKnobAction action, int index) where T : class, new()
|
||||
{
|
||||
lock (settingsLock)
|
||||
@ -268,7 +292,7 @@ namespace MassiveKnob.Core
|
||||
return new T();
|
||||
|
||||
if (list[index]?.Context != context)
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the massiveKnobSettings");
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
|
||||
|
||||
var settingsList = GetActionSettingsList(action.ActionType);
|
||||
if (index >= settingsList.Count)
|
||||
@ -288,7 +312,7 @@ namespace MassiveKnob.Core
|
||||
return;
|
||||
|
||||
if (list[index]?.Context != context)
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the massiveKnobSettings");
|
||||
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
|
||||
|
||||
var settingsList = GetActionSettingsList(action.ActionType);
|
||||
|
||||
@ -317,11 +341,6 @@ namespace MassiveKnob.Core
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (analogOutputValues.TryGetValue(analogInputIndex, out var currentValue) && currentValue == value)
|
||||
return;
|
||||
|
||||
analogOutputValues[analogInputIndex] = value;
|
||||
|
||||
var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog);
|
||||
if (mapping == null || analogInputIndex >= mapping.Count || mapping[analogInputIndex] == null)
|
||||
return;
|
||||
@ -343,12 +362,6 @@ namespace MassiveKnob.Core
|
||||
|
||||
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;
|
||||
@ -362,13 +375,19 @@ namespace MassiveKnob.Core
|
||||
|
||||
public void SetAnalogOutput(IMassiveKnobActionContext context, int index, byte value)
|
||||
{
|
||||
if (activeDevice == null)
|
||||
return;
|
||||
|
||||
IMassiveKnobDeviceInstance deviceInstance;
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (analogOutputValues.TryGetValue(index, out var currentValue) && currentValue == value)
|
||||
return;
|
||||
|
||||
analogOutputValues[index] = value;
|
||||
|
||||
|
||||
if (activeDevice == null)
|
||||
return;
|
||||
|
||||
var list = GetActionMappingList(MassiveKnobActionType.OutputAnalog);
|
||||
if (index >= list.Count)
|
||||
return;
|
||||
@ -385,13 +404,19 @@ namespace MassiveKnob.Core
|
||||
|
||||
public void SetDigitalOutput(IMassiveKnobActionContext context, int index, bool on)
|
||||
{
|
||||
if (activeDevice == null)
|
||||
return;
|
||||
|
||||
IMassiveKnobDeviceInstance deviceInstance;
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
if (digitalOutputValues.TryGetValue(index, out var currentValue) && currentValue == on)
|
||||
return;
|
||||
|
||||
digitalOutputValues[index] = on;
|
||||
|
||||
|
||||
if (activeDevice == null)
|
||||
return;
|
||||
|
||||
var list = GetActionMappingList(MassiveKnobActionType.OutputDigital);
|
||||
if (index >= list.Count)
|
||||
return;
|
||||
@ -433,16 +458,16 @@ namespace MassiveKnob.Core
|
||||
switch (actionType)
|
||||
{
|
||||
case MassiveKnobActionType.InputAnalog:
|
||||
return massiveKnobSettings.AnalogInput;
|
||||
return settings.AnalogInput;
|
||||
|
||||
case MassiveKnobActionType.InputDigital:
|
||||
return massiveKnobSettings.DigitalInput;
|
||||
return settings.DigitalInput;
|
||||
|
||||
case MassiveKnobActionType.OutputAnalog:
|
||||
return massiveKnobSettings.AnalogOutput;
|
||||
return settings.AnalogOutput;
|
||||
|
||||
case MassiveKnobActionType.OutputDigital:
|
||||
return massiveKnobSettings.DigitalOutput;
|
||||
return settings.DigitalOutput;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(actionType), actionType, null);
|
||||
@ -455,7 +480,7 @@ namespace MassiveKnob.Core
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
massiveKnobSettingsSnapshot = massiveKnobSettings.Clone();
|
||||
massiveKnobSettingsSnapshot = settings.Clone();
|
||||
}
|
||||
|
||||
flushSettingsQueue.Enqueue(async () =>
|
||||
@ -482,10 +507,10 @@ namespace MassiveKnob.Core
|
||||
|
||||
lock (settingsLock)
|
||||
{
|
||||
UpdateMapping(analogInputs, specs.AnalogInputCount, massiveKnobSettings.AnalogInput, DelayedInitialize);
|
||||
UpdateMapping(digitalInputs, specs.DigitalInputCount, massiveKnobSettings.DigitalInput, DelayedInitialize);
|
||||
UpdateMapping(analogOutputs, specs.AnalogOutputCount, massiveKnobSettings.AnalogOutput, DelayedInitialize);
|
||||
UpdateMapping(digitalOutputs, specs.DigitalOutputCount, massiveKnobSettings.DigitalOutput, DelayedInitialize);
|
||||
UpdateMapping(analogInputs, specs.AnalogInputCount, settings.AnalogInput, DelayedInitialize);
|
||||
UpdateMapping(digitalInputs, specs.DigitalInputCount, settings.DigitalInput, DelayedInitialize);
|
||||
UpdateMapping(analogOutputs, specs.AnalogOutputCount, settings.AnalogOutput, DelayedInitialize);
|
||||
UpdateMapping(digitalOutputs, specs.DigitalOutputCount, settings.DigitalOutput, DelayedInitialize);
|
||||
}
|
||||
|
||||
foreach (var delayedInitializeAction in delayedInitializeActions)
|
||||
@ -603,21 +628,20 @@ namespace MassiveKnob.Core
|
||||
|
||||
public void Connecting()
|
||||
{
|
||||
// TODO (should have) update status ?
|
||||
owner.SetDeviceStatus(this, MassiveKnobDeviceStatus.Connecting);
|
||||
}
|
||||
|
||||
|
||||
public void Connected(DeviceSpecs specs)
|
||||
{
|
||||
// TODO (should have) update status ?
|
||||
|
||||
owner.SetDeviceStatus(this, MassiveKnobDeviceStatus.Connected);
|
||||
owner.UpdateActiveDeviceSpecs(this, specs);
|
||||
}
|
||||
|
||||
|
||||
public void Disconnected()
|
||||
{
|
||||
// TODO (should have) update status ?
|
||||
owner.SetDeviceStatus(this, MassiveKnobDeviceStatus.Disconnected);
|
||||
}
|
||||
|
||||
|
||||
|
27
Windows/MassiveKnob/Strings.Designer.cs
generated
27
Windows/MassiveKnob/Strings.Designer.cs
generated
@ -69,6 +69,33 @@ namespace MassiveKnob {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Connected.
|
||||
/// </summary>
|
||||
public static string DeviceStatusConnected {
|
||||
get {
|
||||
return ResourceManager.GetString("DeviceStatusConnected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Connecting....
|
||||
/// </summary>
|
||||
public static string DeviceStatusConnecting {
|
||||
get {
|
||||
return ResourceManager.GetString("DeviceStatusConnecting", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Disconnected.
|
||||
/// </summary>
|
||||
public static string DeviceStatusDisconnected {
|
||||
get {
|
||||
return ResourceManager.GetString("DeviceStatusDisconnected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Input #{0}.
|
||||
/// </summary>
|
||||
|
@ -120,6 +120,15 @@
|
||||
<data name="ActionNotConfigured" xml:space="preserve">
|
||||
<value>Not configured</value>
|
||||
</data>
|
||||
<data name="DeviceStatusConnected" xml:space="preserve">
|
||||
<value>Connected</value>
|
||||
</data>
|
||||
<data name="DeviceStatusConnecting" xml:space="preserve">
|
||||
<value>Connecting...</value>
|
||||
</data>
|
||||
<data name="DeviceStatusDisconnected" xml:space="preserve">
|
||||
<value>Disconnected</value>
|
||||
</data>
|
||||
<data name="InputHeader" xml:space="preserve">
|
||||
<value>Input #{0}</value>
|
||||
</data>
|
||||
|
@ -39,6 +39,11 @@
|
||||
|
||||
|
||||
<ContentControl Focusable="False" Content="{Binding SettingsControl}" Style="{StaticResource SettingsControl}" />
|
||||
|
||||
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
|
||||
<Ellipse Margin="0,0,4,0" Fill="{Binding ConnectionStatusColor}" Height="8" Width="8" />
|
||||
<TextBlock Text="{Binding ConnectionStatusText}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
@ -8,6 +8,7 @@ using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using MassiveKnob.Core;
|
||||
using MassiveKnob.Plugin;
|
||||
using MassiveKnob.Settings;
|
||||
@ -18,7 +19,7 @@ using Serilog.Events;
|
||||
namespace MassiveKnob.ViewModel
|
||||
{
|
||||
// TODO (code quality) split ViewModel for individual views, create viewmodel using container
|
||||
// TODO (must have) show device status
|
||||
// TODO (nice to have) installed plugins list
|
||||
public class SettingsViewModel : IDisposable, INotifyPropertyChanged
|
||||
{
|
||||
private readonly Dictionary<SettingsMenuItem, Type> menuItemControls = new Dictionary<SettingsMenuItem, Type>
|
||||
@ -47,6 +48,8 @@ namespace MassiveKnob.ViewModel
|
||||
private IEnumerable<InputOutputViewModel> analogOutputs;
|
||||
private IEnumerable<InputOutputViewModel> digitalOutputs;
|
||||
|
||||
private IDisposable activeDeviceSubscription;
|
||||
private IDisposable deviceStatusSubscription;
|
||||
|
||||
// ReSharper disable UnusedMember.Global - used by WPF Binding
|
||||
public SettingsMenuItem SelectedMenuItem
|
||||
@ -77,18 +80,18 @@ namespace MassiveKnob.ViewModel
|
||||
{
|
||||
if (value == selectedView)
|
||||
return;
|
||||
|
||||
|
||||
selectedView = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public IList<DeviceViewModel> Devices { get; }
|
||||
public IList<ActionViewModel> Actions { get; }
|
||||
|
||||
|
||||
|
||||
public DeviceViewModel SelectedDevice
|
||||
{
|
||||
get => selectedDevice;
|
||||
@ -141,23 +144,27 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
AnalogInputs = Enumerable
|
||||
.Range(0, specs?.AnalogInputCount ?? 0)
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputAnalog, i));
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputAnalog, i))
|
||||
.ToList();
|
||||
|
||||
DigitalInputs = Enumerable
|
||||
.Range(0, specs?.DigitalInputCount ?? 0)
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputDigital, i));
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputDigital, i))
|
||||
.ToList();
|
||||
|
||||
AnalogOutputs = Enumerable
|
||||
.Range(0, specs?.AnalogOutputCount ?? 0)
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputAnalog, i));
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputAnalog, i))
|
||||
.ToList();
|
||||
|
||||
DigitalOutputs = Enumerable
|
||||
.Range(0, specs?.DigitalOutputCount ?? 0)
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputDigital, i));
|
||||
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputDigital, i))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Visibility AnalogInputVisibility => specs.HasValue && specs.Value.AnalogInputCount > 0
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
@ -213,11 +220,12 @@ namespace MassiveKnob.ViewModel
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public IList<LoggingLevelViewModel> LoggingLevels { get; }
|
||||
|
||||
|
||||
private LoggingLevelViewModel selectedLoggingLevel;
|
||||
|
||||
public LoggingLevelViewModel SelectedLoggingLevel
|
||||
{
|
||||
get => selectedLoggingLevel;
|
||||
@ -225,7 +233,7 @@ namespace MassiveKnob.ViewModel
|
||||
{
|
||||
if (value == selectedLoggingLevel)
|
||||
return;
|
||||
|
||||
|
||||
selectedLoggingLevel = value;
|
||||
OnPropertyChanged();
|
||||
|
||||
@ -235,6 +243,7 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
|
||||
private bool loggingEnabled;
|
||||
|
||||
public bool LoggingEnabled
|
||||
{
|
||||
get => loggingEnabled;
|
||||
@ -245,14 +254,16 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
loggingEnabled = value;
|
||||
OnPropertyChanged();
|
||||
|
||||
|
||||
ApplyLoggingSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO (code quality) do not hardcode path here
|
||||
public string LoggingOutputPath { get; } = string.Format(Strings.LoggingOutputPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs"));
|
||||
public string LoggingOutputPath { get; } = string.Format(Strings.LoggingOutputPath,
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob",
|
||||
@"Logs"));
|
||||
|
||||
|
||||
private bool runAtStartup;
|
||||
@ -270,6 +281,55 @@ namespace MassiveKnob.ViewModel
|
||||
ApplyRunAtStartup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string ConnectionStatusText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (orchestrator == null)
|
||||
return "Design-time";
|
||||
|
||||
switch (orchestrator.DeviceStatus)
|
||||
{
|
||||
case MassiveKnobDeviceStatus.Disconnected:
|
||||
return Strings.DeviceStatusDisconnected;
|
||||
|
||||
case MassiveKnobDeviceStatus.Connecting:
|
||||
return Strings.DeviceStatusConnecting;
|
||||
|
||||
case MassiveKnobDeviceStatus.Connected:
|
||||
return Strings.DeviceStatusConnected;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Brush ConnectionStatusColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (orchestrator == null)
|
||||
return Brushes.Fuchsia;
|
||||
|
||||
switch (orchestrator.DeviceStatus)
|
||||
{
|
||||
case MassiveKnobDeviceStatus.Disconnected:
|
||||
return Brushes.DarkRed;
|
||||
|
||||
case MassiveKnobDeviceStatus.Connecting:
|
||||
return Brushes.Orange;
|
||||
|
||||
case MassiveKnobDeviceStatus.Connected:
|
||||
return Brushes.ForestGreen;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ReSharper restore UnusedMember.Global
|
||||
|
||||
|
||||
@ -288,11 +348,17 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
SelectedMenuItem = activeMenuItem;
|
||||
|
||||
orchestrator.ActiveDeviceSubject.Subscribe(info => { Specs = info.Specs; });
|
||||
activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info => { Specs = info.Specs; });
|
||||
deviceStatusSubscription = orchestrator.DeviceStatusSubject.Subscribe(status =>
|
||||
{
|
||||
OnDependantPropertyChanged(nameof(ConnectionStatusColor));
|
||||
OnDependantPropertyChanged(nameof(ConnectionStatusText));
|
||||
});
|
||||
|
||||
|
||||
Devices = pluginManager.GetDevicePlugins()
|
||||
.SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d)))
|
||||
.OrderBy(d => d.Name.ToLower())
|
||||
.ToList();
|
||||
|
||||
var allActions = new List<ActionViewModel>
|
||||
@ -302,7 +368,8 @@ namespace MassiveKnob.ViewModel
|
||||
|
||||
allActions.AddRange(
|
||||
pluginManager.GetActionPlugins()
|
||||
.SelectMany(ap => ap.Actions.Select(a => new ActionViewModel(ap, a))));
|
||||
.SelectMany(ap => ap.Actions.Select(a => new ActionViewModel(ap, a)))
|
||||
.OrderBy(a => a.Name.ToLower()));
|
||||
|
||||
Actions = allActions;
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 5d259cdaee942029487e37a02e9a32ed9833d80c
|
||||
Subproject commit e0e17e56feca7987a567a324132f785f1548a33f
|
Loading…
Reference in New Issue
Block a user