diff --git a/XOutput/Devices/AbstractInputHelper.cs b/XOutput/Devices/AbstractInputHelper.cs index 107e7f15..f4f50d35 100644 --- a/XOutput/Devices/AbstractInputHelper.cs +++ b/XOutput/Devices/AbstractInputHelper.cs @@ -31,7 +31,7 @@ public AbstractInputHelper() { throw new ArgumentException("Type must be enum", nameof(T)); } - values = (T[])Enum.GetValues(typeof(T)); + values = ((T[])Enum.GetValues(typeof(T))).Distinct(); buttons = values.Where(v => IsButton(v)).ToArray(); axes = values.Where(v => IsAxis(v)).ToArray(); dPad = values.Where(v => IsDPad(v)).ToArray(); diff --git a/XOutput/Devices/DeviceInputChangedEventArgs.cs b/XOutput/Devices/DeviceInputChangedEventArgs.cs index 66689da9..e9d39693 100644 --- a/XOutput/Devices/DeviceInputChangedEventArgs.cs +++ b/XOutput/Devices/DeviceInputChangedEventArgs.cs @@ -10,6 +10,26 @@ namespace XOutput.Devices public class DeviceInputChangedEventArgs : EventArgs { + public IEnumerable ChangedValues => changedValues.ToArray(); + public IEnumerable ChangedDPads => changedDPads.ToArray(); + protected IEnumerable changedValues; + protected IEnumerable changedDPads; + + public DeviceInputChangedEventArgs(IEnumerable changedValues, IEnumerable changedDPads) + { + this.changedDPads = changedDPads; + this.changedValues = changedValues; + } + + public bool HasValueChanged(Enum type) + { + return changedValues.Contains(type); + } + + public bool HasDPadChanged(int dPadIndex) + { + return changedDPads.Contains(dPadIndex); + } } } diff --git a/XOutput/Devices/DeviceState.cs b/XOutput/Devices/DeviceState.cs new file mode 100644 index 00000000..485d79f8 --- /dev/null +++ b/XOutput/Devices/DeviceState.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace XOutput.Devices +{ + public class DeviceState + { + public Dictionary Values => values; + public IEnumerable DPads => dPads; + protected Dictionary values = new Dictionary(); + protected DPadDirection[] dPads; + + public DeviceState(IEnumerable types, int dPadCount) + { + foreach (Enum type in types) + { + values.Add(type, 0); + } + dPads = new DPadDirection[dPadCount]; + } + + public IEnumerable SetDPads(IEnumerable newDPads) + { + if (newDPads.Count() != dPads.Length) + throw new ArgumentException(); + ICollection changed = new HashSet(); + foreach (var x in newDPads.Select((d, i) => new { New = d, Old = dPads[i], Index = i }).ToArray()) + { + if (x.New != x.Old) + { + dPads[x.Index] = x.New; + changed.Add(x.Index); + } + } + return changed; + } + + public IEnumerable SetValues(Dictionary newValues) + { + ICollection changed = new HashSet(); + foreach (var x in newValues.Select((d, i) => new { New = d.Value, Old = values[d.Key], Type = d.Key }).ToArray()) + { + if (x.New != x.Old) + { + values[x.Type] = x.New; + changed.Add(x.Type); + } + } + return changed; + } + } +} diff --git a/XOutput/Devices/Input/DirectInput/DirectDevice.cs b/XOutput/Devices/Input/DirectInput/DirectDevice.cs index 3147457b..7468c234 100644 --- a/XOutput/Devices/Input/DirectInput/DirectDevice.cs +++ b/XOutput/Devices/Input/DirectInput/DirectDevice.cs @@ -53,6 +53,8 @@ public bool Connected private readonly Enum[] axes; private readonly Enum[] sliders; private readonly DPadDirection[] dpads; + private readonly Enum[] allTypes; + private readonly DeviceState state; private readonly EffectInfo force; private readonly Dictionary actuators; private bool connected = false; @@ -96,6 +98,8 @@ public DirectDevice(DeviceInstance deviceInstance, Joystick joystick) { actuators = new Dictionary(); } + allTypes = buttons.Concat(axes).Concat(sliders).ToArray(); + state = new DeviceState(allTypes, dpads.Length); inputRefresher = new Thread(InputRefresher); inputRefresher.Name = ToString() + " input reader"; inputRefresher.SetApartmentState(ApartmentState.STA); @@ -223,11 +227,12 @@ public bool RefreshInput() try { joystick.Poll(); - foreach (var dpad in Enumerable.Range(0, dpads.Length)) - { - dpads[dpad] = GetDPadValue(dpad); - } - InputChanged?.Invoke(this, new DeviceInputChangedEventArgs()); + var newDPads = Enumerable.Range(0, dpads.Length).Select(i => GetDPadValue(i)); + var newValues = allTypes.ToDictionary(t => t, t => Get(t)); + var changedDPads = state.SetDPads(newDPads); + var changedValues = state.SetValues(newValues); + if (changedDPads.Any() || changedValues.Any()) + InputChanged?.Invoke(this, new DeviceInputChangedEventArgs(changedValues, changedDPads)); return true; } catch diff --git a/XOutput/Devices/Input/Keyboard/Keyboard.cs b/XOutput/Devices/Input/Keyboard/Keyboard.cs index d2edd429..0cbb1f3f 100644 --- a/XOutput/Devices/Input/Keyboard/Keyboard.cs +++ b/XOutput/Devices/Input/Keyboard/Keyboard.cs @@ -29,10 +29,12 @@ public event DeviceDisconnectedHandler Disconnected { add { } remove { } } private Thread inputRefresher; private readonly Enum[] buttons; + private readonly DeviceState state; public Keyboard() { buttons = KeyboardInputHelper.Instance.Buttons.Where(x => x != Key.None).OrderBy(x => x.ToString()).OfType().ToArray(); + state = new DeviceState(buttons, 0); inputRefresher = new Thread(InputRefresher); inputRefresher.Name = "Keyboard input notification"; inputRefresher.SetApartmentState(ApartmentState.STA); @@ -79,7 +81,10 @@ private void InputRefresher() { while (true) { - InputChanged?.Invoke(this, new DeviceInputChangedEventArgs()); + var newValues = buttons.ToDictionary(t => t, t => Get(t)); + var changedValues = state.SetValues(newValues); + if (changedValues.Any()) + InputChanged?.Invoke(this, new DeviceInputChangedEventArgs(changedValues, new int[0])); Thread.Sleep(1); } } diff --git a/XOutput/Devices/XInput/XInputHelper.cs b/XOutput/Devices/XInput/XInputHelper.cs index b2f4a103..8d934af4 100644 --- a/XOutput/Devices/XInput/XInputHelper.cs +++ b/XOutput/Devices/XInput/XInputHelper.cs @@ -97,6 +97,11 @@ public static bool IsDPad(this XInputTypes input) return XInputHelper.Instance.IsDPad(input); } + public static bool IsButton(this XInputTypes input) + { + return XInputHelper.Instance.IsButton(input); + } + public static double GetDisableValue(this XInputTypes input) { return XInputHelper.Instance.GetDisableValue(input); diff --git a/XOutput/Devices/XInput/XOutputDevice.cs b/XOutput/Devices/XInput/XOutputDevice.cs index e526d1de..e482c499 100644 --- a/XOutput/Devices/XInput/XOutputDevice.cs +++ b/XOutput/Devices/XInput/XOutputDevice.cs @@ -13,6 +13,8 @@ namespace XOutput.Devices.XInput /// public sealed class XOutputDevice : IDevice { + public const int DPadCount = 1; + /// /// This event is invoked if the data from the device was updated /// @@ -26,7 +28,8 @@ public sealed class XOutputDevice : IDevice private readonly Dictionary values = new Dictionary(); private readonly IInputDevice source; private readonly InputMapperBase mapper; - private DPadDirection[] dPads = new DPadDirection[1]; + private readonly DPadDirection[] dPads = new DPadDirection[DPadCount]; + private readonly DeviceState state; /// /// Creates a new XDevice. @@ -37,6 +40,7 @@ public XOutputDevice(IInputDevice source, Mapper.InputMapperBase mapper) { this.source = source; this.mapper = mapper; + state = new DeviceState(XInputHelper.Instance.Values.OfType().ToArray(), DPadCount); source.InputChanged += SourceInputChanged; } @@ -94,7 +98,10 @@ public bool RefreshInput() { dPads[0] = DPadHelper.GetDirection(GetBool(XInputTypes.UP), GetBool(XInputTypes.DOWN), GetBool(XInputTypes.LEFT), GetBool(XInputTypes.RIGHT)); } - InputChanged?.Invoke(this, new DeviceInputChangedEventArgs()); + var changedDPads = state.SetDPads(dPads); + var changedValues = state.SetValues(values.Where(t => !t.Key.IsDPad()).ToDictionary(x => (Enum)x.Key, x => x.Value)); + if (changedDPads.Any() || changedValues.Any()) + InputChanged?.Invoke(this, new DeviceInputChangedEventArgs(changedValues, changedDPads)); return true; } diff --git a/XOutput/UI/Component/ControllerModel.cs b/XOutput/UI/Component/ControllerModel.cs index 3b8a9ab6..57c1847d 100644 --- a/XOutput/UI/Component/ControllerModel.cs +++ b/XOutput/UI/Component/ControllerModel.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Media; using XOutput.Devices; namespace XOutput.UI.Component @@ -63,6 +64,20 @@ public bool CanStart } } } + + private Brush background; + public Brush Background + { + get => background; + set + { + if (background != value) + { + background = value; + OnPropertyChanged(nameof(Background)); + } + } + } public string DisplayName { get { return Controller.ToString(); } } } } diff --git a/XOutput/UI/Component/ControllerView.xaml b/XOutput/UI/Component/ControllerView.xaml index e4152da5..7214e513 100644 --- a/XOutput/UI/Component/ControllerView.xaml +++ b/XOutput/UI/Component/ControllerView.xaml @@ -7,7 +7,7 @@ d:DataContext="{d:DesignInstance Type=local:ControllerViewModel, IsDesignTimeCreatable=False}" mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="300"> - + diff --git a/XOutput/UI/Component/ControllerViewModel.cs b/XOutput/UI/Component/ControllerViewModel.cs index dc1c5050..af48163e 100644 --- a/XOutput/UI/Component/ControllerViewModel.cs +++ b/XOutput/UI/Component/ControllerViewModel.cs @@ -4,20 +4,28 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Threading; using XOutput.Devices; using XOutput.UI.Windows; namespace XOutput.UI.Component { - public class ControllerViewModel : ViewModelBase + public class ControllerViewModel : ViewModelBase, IDisposable { + private const int BackgroundDelayMS = 500; private readonly Action log; + private readonly DispatcherTimer timer = new DispatcherTimer(); public ControllerViewModel(ControllerModel model, GameController controller, Action log) : base(model) { this.log = log; Model.Controller = controller; Model.ButtonText = "Start"; + Model.Background = Brushes.White; + Model.Controller.InputDevice.InputChanged += InputDevice_InputChanged; + timer.Interval = TimeSpan.FromMilliseconds(BackgroundDelayMS); + timer.Tick += Timer_Tick; } public void Edit() @@ -57,5 +65,23 @@ public void Start() Model.Started = controllerCount != 0; } } + + public void Dispose() + { + timer.Tick -= Timer_Tick; + Model.Controller.InputDevice.InputChanged -= InputDevice_InputChanged; + } + + private void Timer_Tick(object sender, EventArgs e) + { + Model.Background = Brushes.White; + } + + private void InputDevice_InputChanged(object sender, DeviceInputChangedEventArgs e) + { + Model.Background = Brushes.LightGreen; + timer.Stop(); + timer.Start(); + } } } diff --git a/XOutput/UI/Windows/AutoConfigureViewModel.cs b/XOutput/UI/Windows/AutoConfigureViewModel.cs index a41fd7fe..ed9bad3a 100644 --- a/XOutput/UI/Windows/AutoConfigureViewModel.cs +++ b/XOutput/UI/Windows/AutoConfigureViewModel.cs @@ -75,7 +75,7 @@ private void ReadValues(object sender, DeviceInputChangedEventArgs e) { Enum maxType = null; double maxDiff = 0; - foreach (var type in inputTypes) + foreach (var type in e.ChangedValues) { double oldValue = referenceValues[type]; double newValue = controller.InputDevice.Get(type); diff --git a/XOutput/UI/Windows/MainWindowViewModel.cs b/XOutput/UI/Windows/MainWindowViewModel.cs index e7ea9b69..51e1524a 100644 --- a/XOutput/UI/Windows/MainWindowViewModel.cs +++ b/XOutput/UI/Windows/MainWindowViewModel.cs @@ -196,6 +196,7 @@ public void RefreshGameControllers() var controller = controllerView.ViewModel.Model.Controller; if (controller.InputDevice is DirectDevice && (!instances.Any(x => x.InstanceGuid == ((DirectDevice)controller.InputDevice).Id) || !controller.InputDevice.Connected)) { + controllerView.ViewModel.Dispose(); controller.Dispose(); Model.Controllers.Remove(controllerView); logger.Info($"{controller.ToString()} is disconnected."); diff --git a/XOutput/XOutput.csproj b/XOutput/XOutput.csproj index 4794f6bc..a774062b 100644 --- a/XOutput/XOutput.csproj +++ b/XOutput/XOutput.csproj @@ -80,6 +80,7 @@ MSBuild:Compile Designer +