diff --git a/src/Artemis.VisualScripting/Nodes/Input/PressedKeyPositionNode.cs b/src/Artemis.VisualScripting/Nodes/Input/PressedKeyPositionNode.cs new file mode 100644 index 000000000..e08b05fab --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/PressedKeyPositionNode.cs @@ -0,0 +1,73 @@ +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.VisualScripting.Nodes.Input.Screens; +using SkiaSharp; +using static Artemis.VisualScripting.Nodes.Input.PressedKeyPositionNodeEntity; + +namespace Artemis.VisualScripting.Nodes.Input; + +[Node("Pressed Key Position", "Outputs the position of a pressed key relative to a layer", "Input", OutputType = typeof(Numeric))] +public class PressedKeyPositionNode : Node, IDisposable +{ + private readonly IInputService _inputService; + private Layer? _layer; + private SKPoint _ledPosition; + private Profile? _profile; + + public PressedKeyPositionNode(IInputService inputService) + { + _inputService = inputService; + XPosition = CreateOutputPin("X"); + YPosition = CreateOutputPin("Y"); + + StorageModified += OnStorageModified; + _inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown; + _inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp; + } + + public OutputPin XPosition { get; } + public OutputPin YPosition { get; } + + public override void Initialize(INodeScript script) + { + Storage ??= new PressedKeyPositionNodeEntity(); + + _profile = script.Context as Profile; + _layer = _profile?.GetAllLayers().FirstOrDefault(l => l.EntityId == Storage.LayerId); + } + + public override void Evaluate() + { + XPosition.Value = _ledPosition.X; + YPosition.Value = _ledPosition.Y; + } + + private void InputServiceOnKeyboardKeyDown(object? sender, ArtemisKeyboardKeyEventArgs e) + { + if (Storage?.RespondTo is KeyPressType.Down or KeyPressType.UpDown) + SetLedPosition(e.Led); + } + + private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) + { + if (Storage?.RespondTo is KeyPressType.Up or KeyPressType.UpDown) + SetLedPosition(e.Led); + } + + private void SetLedPosition(ArtemisLed? led) + { + if (_layer != null && led != null) + _ledPosition = new SKPoint((led.AbsoluteRectangle.MidX - _layer.Bounds.Left) / _layer.Bounds.Width, (led.AbsoluteRectangle.MidY - _layer.Bounds.Top) / _layer.Bounds.Height); + } + + private void OnStorageModified(object? sender, EventArgs e) + { + _layer = _profile?.GetAllLayers().FirstOrDefault(l => l.EntityId == Storage?.LayerId); + } + + public void Dispose() + { + _inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown; + _inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp; + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/PressedKeyPositionNodeEntity.cs b/src/Artemis.VisualScripting/Nodes/Input/PressedKeyPositionNodeEntity.cs new file mode 100644 index 000000000..1c74a9899 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/PressedKeyPositionNodeEntity.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; + +namespace Artemis.VisualScripting.Nodes.Input; + +public class PressedKeyPositionNodeEntity +{ + public PressedKeyPositionNodeEntity() + { + } + + public PressedKeyPositionNodeEntity(Guid layerId, KeyPressType respondTo) + { + LayerId = layerId; + RespondTo = respondTo; + } + + public Guid LayerId { get; set; } + public KeyPressType RespondTo { get; set; } + + public enum KeyPressType + { + [Description("Up")] + Up, + [Description("Down")] + Down, + [Description("Up/down")] + UpDown + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomView.axaml new file mode 100644 index 000000000..f9a87a72c --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomView.axaml @@ -0,0 +1,29 @@ + + + Layer + + + + + + + + + + + + Respond to + + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomView.axaml.cs b/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomView.axaml.cs new file mode 100644 index 000000000..0896825e0 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomView.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.VisualScripting.Nodes.Input.Screens; + +public partial class PressedKeyPositionNodeCustomView : ReactiveUserControl +{ + public PressedKeyPositionNodeCustomView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomViewModel.cs new file mode 100644 index 000000000..fce2c13a3 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/PressedKeyPositionNodeCustomViewModel.cs @@ -0,0 +1,94 @@ +using System.Collections.ObjectModel; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.UI.Shared.Services.NodeEditor; +using Artemis.UI.Shared.Services.NodeEditor.Commands; +using Artemis.UI.Shared.VisualScripting; +using Avalonia.Controls.Mixins; +using ReactiveUI; +using static Artemis.VisualScripting.Nodes.Input.PressedKeyPositionNodeEntity; + +namespace Artemis.VisualScripting.Nodes.Input.Screens; + +public class PressedKeyPositionNodeCustomViewModel : CustomNodeViewModel +{ + private readonly INodeEditorService _nodeEditorService; + private readonly Profile? _profile; + private readonly PressedKeyPositionNode _node; + private Layer? _selectedLayer; + private KeyPressType _respondTo; + + public PressedKeyPositionNodeCustomViewModel(PressedKeyPositionNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script) + { + _nodeEditorService = nodeEditorService; + _profile = script.Context as Profile; + _node = node; + + Layers = new ObservableCollection(); + + this.WhenActivated(d => + { + if (_profile == null) + return; + + Observable.FromEventPattern(x => _profile.DescendentAdded += x, x => _profile.DescendentAdded -= x).Subscribe(_ => GetLayers()).DisposeWith(d); + Observable.FromEventPattern(x => _profile.DescendentRemoved += x, x => _profile.DescendentRemoved -= x).Subscribe(_ => GetLayers()).DisposeWith(d); + Observable.FromEventPattern(x => _node.StorageModified += x, x => _node.StorageModified -= x).Subscribe(_ => Update()).DisposeWith(d); + + GetLayers(); + }); + + this.WhenAnyValue(vm => vm.SelectedLayer).Subscribe(UpdateSelectedLayer); + this.WhenAnyValue(vm => vm.RespondTo).Subscribe(UpdateSelectedRespondTo); + } + + public ObservableCollection Layers { get; } + + public Layer? SelectedLayer + { + get => _selectedLayer; + set => this.RaiseAndSetIfChanged(ref _selectedLayer, value); + } + + public KeyPressType RespondTo + { + get => _respondTo; + set => this.RaiseAndSetIfChanged(ref _respondTo, value); + } + + private void GetLayers() + { + Layers.Clear(); + if (_profile == null) + return; + foreach (Layer layer in _profile.GetAllLayers()) + Layers.Add(layer); + + Update(); + } + + private void Update() + { + SelectedLayer = Layers.FirstOrDefault(l => l.EntityId == _node.Storage?.LayerId); + RespondTo = _node.Storage?.RespondTo ?? KeyPressType.Up; + } + + private void UpdateSelectedLayer(Layer? layer) + { + if (layer == null || _node.Storage?.LayerId == layer.EntityId) + return; + + _nodeEditorService.ExecuteCommand( + Script, + new UpdateStorage(_node, new PressedKeyPositionNodeEntity(layer.EntityId, _node.Storage?.RespondTo ?? KeyPressType.Up), "layer") + ); + } + + private void UpdateSelectedRespondTo(KeyPressType respondTo) + { + if (_node.Storage?.RespondTo == respondTo) + return; + + _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, new PressedKeyPositionNodeEntity(_node.Storage?.LayerId ?? Guid.Empty, respondTo), "layer")); + } +} \ No newline at end of file