diff --git a/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs b/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs index 33f41b97..74bbc484 100644 --- a/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs +++ b/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs @@ -15,12 +15,12 @@ namespace BrickController2.DeviceManagement.Lego; /// /// Represents a LEGO® Powered Up 88010 Remote Control /// -internal class RemoteControl : WirelessProtocolBasedDevice, IDeviceType +internal class RemoteControl : WirelessProtocolBasedDevice, IDeviceType, IDynamicInputDevice { private const string ENABLED_SETTING_NAME = "RemoteControlEnabled"; private const bool DEFAULT_ENABLED = false; - private InputDeviceBase? _inputController; + private IInputDeviceConnector? _inputDeviceConnector; public RemoteControl(string name, string address, IEnumerable settings, IDeviceRepository deviceRepository, IBluetoothLEService bleService) : base(name, address, deviceRepository, bleService) @@ -39,14 +39,14 @@ public RemoteControl(string name, string address, IEnumerable sett public override void SetOutput(int channel, float value) => throw new InvalidOperationException(); - internal void ConnectInputController(TController inputController) where TController : InputDeviceBase + public void ConnectInputController(IInputDeviceConnector inputController) { - _inputController = inputController; + _inputDeviceConnector = inputController; } - internal void DisconnectInputController() + public void DisconnectInputController() { - _inputController = default; + _inputDeviceConnector = default; } internal void ResetEvents() => RaiseButtonEvents( @@ -123,16 +123,16 @@ private void OnButtonEvents(string plus, string stop, string minus, ReadOnlySpan private void RaiseButtonEvents((string eventName, float value)[] buttonEvents) { - if (_inputController is null) + if (_inputDeviceConnector is null) { return; } var events = buttonEvents - .Where(e => _inputController.HasValueChanged(e.eventName, e.value)) + .Where(e => _inputDeviceConnector.HasValueChanged(e.eventName, e.value)) .ToDictionary(e => (InputDeviceEventType.Button, e.eventName), e => e.value); - _inputController.RaiseEvent(events); + _inputDeviceConnector.RaiseEvent(events); } private static float GetButtonValue(byte flag) => flag != 0 ? BUTTON_PRESSED : BUTTON_RELEASED; diff --git a/BrickController2/BrickController2/PlatformServices/InputDevice/IDynamicInputDevice.cs b/BrickController2/BrickController2/PlatformServices/InputDevice/IDynamicInputDevice.cs new file mode 100644 index 00000000..3b84c938 --- /dev/null +++ b/BrickController2/BrickController2/PlatformServices/InputDevice/IDynamicInputDevice.cs @@ -0,0 +1,7 @@ +namespace BrickController2.PlatformServices.InputDevice; + +internal interface IDynamicInputDevice +{ + void ConnectInputController(IInputDeviceConnector controller); + void DisconnectInputController(); +} diff --git a/BrickController2/BrickController2/PlatformServices/InputDevice/IInputDeviceConnector.cs b/BrickController2/BrickController2/PlatformServices/InputDevice/IInputDeviceConnector.cs new file mode 100644 index 00000000..67bc615e --- /dev/null +++ b/BrickController2/BrickController2/PlatformServices/InputDevice/IInputDeviceConnector.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace BrickController2.PlatformServices.InputDevice; + +internal interface IInputDeviceConnector +{ + internal bool HasValueChanged(string axisName, float value); + + internal void RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events); +} diff --git a/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs b/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs index eb213228..822485d9 100644 --- a/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs +++ b/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs @@ -9,7 +9,7 @@ namespace BrickController2.PlatformServices.InputDevice; /// abstract base class for input devices /// /// Type of native instance of inputdevice device -public abstract class InputDeviceBase : IInputDevice +public abstract class InputDeviceBase : IInputDevice, IInputDeviceConnector where TInputDeviceDevice : class { /// stored last value per axis to detect changes @@ -66,7 +66,7 @@ public virtual void Stop() protected bool ContainsAxisValue(string axisName) => _lastAxisValues.ContainsKey(axisName); - protected internal bool HasValueChanged(string axisName, float value) + public bool HasValueChanged(string axisName, float value) { // get last reported value or the default one _lastAxisValues.TryGetValue(axisName, out float lastValue); @@ -80,7 +80,7 @@ protected internal bool HasValueChanged(string axisName, float value) return true; } - protected internal void RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events) + public void RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events) { if (!events.Any()) { diff --git a/BrickController2/BrickController2/UI/Pages/DevicePage.xaml b/BrickController2/BrickController2/UI/Pages/DevicePage.xaml index a65f72db..c5475454 100644 --- a/BrickController2/BrickController2/UI/Pages/DevicePage.xaml +++ b/BrickController2/BrickController2/UI/Pages/DevicePage.xaml @@ -133,6 +133,27 @@ + + + + + + + + + + + + + + + + diff --git a/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs index 710220d3..65f1c184 100644 --- a/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs +++ b/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,19 +10,22 @@ using BrickController2.CreationManagement; using BrickController2.DeviceManagement; using BrickController2.Helpers; +using BrickController2.PlatformServices.InputDevice; using BrickController2.UI.Commands; using BrickController2.UI.Services.Navigation; using BrickController2.UI.Services.Dialog; using BrickController2.UI.Services.Translation; using Device = BrickController2.DeviceManagement.Device; using static BrickController2.CreationManagement.ControllerDefaults; +using static BrickController2.PlatformServices.InputDevice.InputDevices; namespace BrickController2.UI.ViewModels { - public class DevicePageViewModel : PageViewModelBase + public class DevicePageViewModel : PageViewModelBase, IInputDeviceConnector { private readonly IDeviceManager _deviceManager; private readonly IDialogService _dialogService; + private readonly ConcurrentDictionary _lastAxisValues = []; private CancellationTokenSource? _connectionTokenSource; private Task? _connectionTask; @@ -83,6 +88,12 @@ public DevicePageViewModel( public IEnumerable DeviceOutputs { get; } + public ObservableCollection InputEventList { get; } = []; + + public bool IsInputDevice => InputDevice is not null; + + internal IDynamicInputDevice? InputDevice => Device as IDynamicInputDevice; + public override async void OnAppearing() { _isDisappearing = false; @@ -103,6 +114,10 @@ await _dialogService.ShowMessageBoxAsync( } } + // connect input device if available + ResetInputEvents(); + InputDevice?.ConnectInputController(this); + _connectionTokenSource = new CancellationTokenSource(); _connectionTask = ConnectAsync(); } @@ -112,9 +127,58 @@ public override async void OnDisappearing() _isDisappearing = true; base.OnDisappearing(); + // disconnect input device if available + ResetInputEvents(); + InputDevice?.DisconnectInputController(); + await DisconnectAsync(); } + bool IInputDeviceConnector.HasValueChanged(string axisName, float value) + { + // get last reported value or the default one + _lastAxisValues.TryGetValue(axisName, out float lastValue); + // skip value if there is no change + if (AreAlmostEqual(value, lastValue)) + { + return false; + } + // persist + _lastAxisValues[axisName] = value; + return true; + } + + void IInputDeviceConnector.RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events) + { + foreach (var inputEvent in events) + { + var (eventType, eventCode) = inputEvent.Key; + var item = InputEventList.FirstOrDefault(x => x.EventCode == eventCode && x.EventType == eventType); + + if (AXIS_DELTA_VALUE >= Math.Abs(inputEvent.Value)) + { + if (item != null) + { + InputEventList.Remove(item); + } + } + else if(item is null) + { + InputEventList.Add(new InputDeviceEventViewModel(eventType, eventCode, inputEvent.Value)); + } + else + { + item.Value = inputEvent.Value; + } + } + } + + private void ResetInputEvents() + { + _lastAxisValues.Clear(); + InputEventList.Clear(); + } + private async Task DisconnectAsync() { if (_connectionTokenSource is not null && _connectionTask is not null) @@ -346,6 +410,8 @@ await _dialogService.ShowMessageBoxAsync( private void OnDeviceDisconnected(Device device) { + // clear input events + ResetInputEvents(); // update command enablement UpdateCommandsAvailability(); }