diff --git a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs index 51d0ce1ef..ac2c9ed24 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs @@ -16,7 +16,10 @@ public class Hotkey : CorePropertyChanged, IStorageModel Entity = new ProfileConfigurationHotkeyEntity(); } - internal Hotkey(ProfileConfigurationHotkeyEntity entity) + /// + /// Creates a new instance of based on the provided entity + /// + public Hotkey(ProfileConfigurationHotkeyEntity entity) { Entity = entity; Load(); @@ -32,7 +35,10 @@ public class Hotkey : CorePropertyChanged, IStorageModel /// public KeyboardModifierKey? Modifiers { get; set; } - internal ProfileConfigurationHotkeyEntity Entity { get; } + /// + /// Gets the entity used to store this hotkey + /// + public ProfileConfigurationHotkeyEntity Entity { get; } /// /// Determines whether the provided match the hotkey diff --git a/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs b/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs index 2129430fd..0c50e69e4 100644 --- a/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml.cs @@ -8,6 +8,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using FluentAvalonia.Core; using Humanizer; using Material.Icons; @@ -47,7 +48,8 @@ public class HotkeyBox : UserControl Hotkey.Key = (KeyboardKey?) e.Key; Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers; UpdateDisplayTextBox(); - + HotkeyChanged?.Invoke(this, EventArgs.Empty); + e.Handled = true; } @@ -134,4 +136,13 @@ public class HotkeyBox : UserControl } #endregion + + #region Events + + /// + /// Occurs when the hotkey changes. + /// + public event TypedEventHandler? HotkeyChanged; + + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Styles/Condensed.axaml b/src/Artemis.UI.Shared/Styles/Condensed.axaml index 54dd1b525..7b9f44d08 100644 --- a/src/Artemis.UI.Shared/Styles/Condensed.axaml +++ b/src/Artemis.UI.Shared/Styles/Condensed.axaml @@ -39,6 +39,9 @@ + + + @@ -110,4 +113,10 @@ + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/HotkeyEnableDisableNode.cs b/src/Artemis.VisualScripting/Nodes/Input/HotkeyEnableDisableNode.cs new file mode 100644 index 000000000..12946deb8 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/HotkeyEnableDisableNode.cs @@ -0,0 +1,74 @@ +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.VisualScripting.Nodes.Input.Screens; + +namespace Artemis.VisualScripting.Nodes.Input; + +[Node("Hotkey enable/disable", "Outputs a boolean value enabled and disabled by a set of hotkeys", "Input", OutputType = typeof(bool))] +public class HotkeyEnableDisableNode : Node, IDisposable +{ + private readonly IInputService _inputService; + private Hotkey? _disableHotkey; + private Hotkey? _enableHotkey; + private bool _value; + private bool _retrievedInitialValue; + + public HotkeyEnableDisableNode(IInputService inputService) + { + _inputService = inputService; + _inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp; + + InitialValue = CreateInputPin(); + Output = CreateOutputPin(); + StorageModified += OnStorageModified; + } + + public InputPin InitialValue { get; } + public OutputPin Output { get; } + + public override void Initialize(INodeScript script) + { + LoadHotkeys(); + } + + public override void Evaluate() + { + if (!_retrievedInitialValue) + { + _value = InitialValue.Value; + _retrievedInitialValue = true; + } + + Output.Value = _value; + } + + private void OnStorageModified(object? sender, EventArgs e) + { + LoadHotkeys(); + } + + private void LoadHotkeys() + { + if (Storage == null) + return; + + _enableHotkey = Storage.EnableHotkey != null ? new Hotkey(Storage.EnableHotkey) : null; + _disableHotkey = Storage.DisableHotkey != null ? new Hotkey(Storage.DisableHotkey) : null; + } + + private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) + { + if (Storage == null) + return; + + if (_disableHotkey != null && _disableHotkey.MatchesEventArgs(e)) + _value = false; + else if (_enableHotkey != null && _enableHotkey.MatchesEventArgs(e)) + _value = true; + } + + public void Dispose() + { + _inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp; + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/HotkeyEnableDisableNodeEntity.cs b/src/Artemis.VisualScripting/Nodes/Input/HotkeyEnableDisableNodeEntity.cs new file mode 100644 index 000000000..98f84b2b0 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/HotkeyEnableDisableNodeEntity.cs @@ -0,0 +1,18 @@ +using Artemis.Core; +using Artemis.Storage.Entities.Profile; + +namespace Artemis.VisualScripting.Nodes.Input; + +public class HotkeyEnableDisableNodeEntity +{ + public HotkeyEnableDisableNodeEntity(Hotkey? enableHotkey, Hotkey? disableHotkey) + { + enableHotkey?.Save(); + EnableHotkey = enableHotkey?.Entity; + disableHotkey?.Save(); + DisableHotkey = disableHotkey?.Entity; + } + + public ProfileConfigurationHotkeyEntity? EnableHotkey { get; set; } + public ProfileConfigurationHotkeyEntity? DisableHotkey { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/HotkeyToggleNode.cs b/src/Artemis.VisualScripting/Nodes/Input/HotkeyToggleNode.cs new file mode 100644 index 000000000..a31d5d253 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/HotkeyToggleNode.cs @@ -0,0 +1,69 @@ +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.VisualScripting.Nodes.Input.Screens; + +namespace Artemis.VisualScripting.Nodes.Input; + +[Node("Hotkey toggle", "Outputs a boolean value toggled by a hotkey", "Input", OutputType = typeof(bool))] +public class HotkeyToggleNode : Node, IDisposable +{ + private readonly IInputService _inputService; + private Hotkey? _toggleHotkey; + private bool _value; + private bool _retrievedInitialValue; + + public HotkeyToggleNode(IInputService inputService) + { + _inputService = inputService; + _inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp; + + InitialValue = CreateInputPin(); + Output = CreateOutputPin(); + StorageModified += OnStorageModified; + } + + public InputPin InitialValue { get; } + public OutputPin Output { get; } + + public override void Initialize(INodeScript script) + { + LoadHotkeys(); + } + + public override void Evaluate() + { + if (!_retrievedInitialValue) + { + _value = InitialValue.Value; + _retrievedInitialValue = true; + } + + Output.Value = _value; + } + + private void OnStorageModified(object? sender, EventArgs e) + { + LoadHotkeys(); + } + + private void LoadHotkeys() + { + if (Storage == null) + return; + + _toggleHotkey = Storage.EnableHotkey != null ? new Hotkey(Storage.EnableHotkey) : null; + } + + private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) + { + if (Storage == null) + return; + if (_toggleHotkey != null && _toggleHotkey.MatchesEventArgs(e)) + _value = !_value; + } + + public void Dispose() + { + _inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp; + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomView.axaml new file mode 100644 index 000000000..7285adced --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomView.axaml @@ -0,0 +1,16 @@ + + + Enable + + Disable + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomView.axaml.cs b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomView.axaml.cs new file mode 100644 index 000000000..90bf774e0 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomView.axaml.cs @@ -0,0 +1,25 @@ +using Artemis.UI.Shared; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.VisualScripting.Nodes.Input.Screens; + +public partial class HotkeyEnableDisableNodeCustomView : ReactiveUserControl +{ + public HotkeyEnableDisableNodeCustomView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void HotkeyBox_OnHotkeyChanged(HotkeyBox sender, EventArgs args) + { + ViewModel?.Save(); + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomViewModel.cs new file mode 100644 index 000000000..ccb1d7342 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyEnableDisableNodeCustomViewModel.cs @@ -0,0 +1,65 @@ +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; + +namespace Artemis.VisualScripting.Nodes.Input.Screens; + +public class HotkeyEnableDisableNodeCustomViewModel : CustomNodeViewModel +{ + private readonly HotkeyEnableDisableNode _enableDisableNode; + private readonly INodeEditorService _nodeEditorService; + private Hotkey? _enableHotkey; + private Hotkey? _disableHotkey; + private bool _updating; + + /// + public HotkeyEnableDisableNodeCustomViewModel(HotkeyEnableDisableNode enableDisableNode, INodeScript script, INodeEditorService nodeEditorService) : base(enableDisableNode, script) + { + _enableDisableNode = enableDisableNode; + _nodeEditorService = nodeEditorService; + + this.WhenActivated(d => + { + Observable.FromEventPattern(x => _enableDisableNode.StorageModified += x, x => _enableDisableNode.StorageModified -= x).Subscribe(_ => Update()).DisposeWith(d); + Update(); + }); + + } + + private void Update() + { + _updating = true; + + EnableHotkey = _enableDisableNode.Storage?.EnableHotkey != null ? new Hotkey(_enableDisableNode.Storage.EnableHotkey) : null; + DisableHotkey = _enableDisableNode.Storage?.DisableHotkey != null ? new Hotkey(_enableDisableNode.Storage.DisableHotkey) : null; + + _updating = false; + } + + public Hotkey? EnableHotkey + { + get => _enableHotkey; + set => this.RaiseAndSetIfChanged(ref _enableHotkey, value); + } + + public Hotkey? DisableHotkey + { + get => _disableHotkey; + set => this.RaiseAndSetIfChanged(ref _disableHotkey, value); + } + + public void Save() + { + if (_updating) + return; + + _nodeEditorService.ExecuteCommand( + Script, + new UpdateStorage(_enableDisableNode, new HotkeyEnableDisableNodeEntity(EnableHotkey, DisableHotkey), "hotkey") + ); + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomView.axaml new file mode 100644 index 000000000..b147737f3 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomView.axaml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomView.axaml.cs b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomView.axaml.cs new file mode 100644 index 000000000..46aed80ce --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomView.axaml.cs @@ -0,0 +1,25 @@ +using Artemis.UI.Shared; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.VisualScripting.Nodes.Input.Screens; + +public partial class HotkeyToggleNodeCustomView : ReactiveUserControl +{ + public HotkeyToggleNodeCustomView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void HotkeyBox_OnHotkeyChanged(HotkeyBox sender, EventArgs args) + { + ViewModel?.Save(); + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomViewModel.cs new file mode 100644 index 000000000..57ea098d6 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Input/Screens/HotkeyToggleNodeCustomViewModel.cs @@ -0,0 +1,55 @@ +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; + +namespace Artemis.VisualScripting.Nodes.Input.Screens; + +public class HotkeyToggleNodeCustomViewModel : CustomNodeViewModel +{ + private readonly HotkeyToggleNode _toggleNode; + private readonly INodeEditorService _nodeEditorService; + private Hotkey? _toggleHotkey; + private bool _updating; + + /// + public HotkeyToggleNodeCustomViewModel(HotkeyToggleNode toggleNode, INodeScript script, INodeEditorService nodeEditorService) : base(toggleNode, script) + { + _toggleNode = toggleNode; + _nodeEditorService = nodeEditorService; + + this.WhenActivated(d => + { + Observable.FromEventPattern(x => _toggleNode.StorageModified += x, x => _toggleNode.StorageModified -= x).Subscribe(_ => Update()).DisposeWith(d); + Update(); + }); + + } + + private void Update() + { + _updating = true; + ToggleHotkey = _toggleNode.Storage?.EnableHotkey != null ? new Hotkey(_toggleNode.Storage.EnableHotkey) : null; + _updating = false; + } + + public Hotkey? ToggleHotkey + { + get => _toggleHotkey; + set => this.RaiseAndSetIfChanged(ref _toggleHotkey, value); + } + + public void Save() + { + if (_updating) + return; + + _nodeEditorService.ExecuteCommand( + Script, + new UpdateStorage(_toggleNode, new HotkeyEnableDisableNodeEntity(ToggleHotkey, null), "hotkey") + ); + } +} \ No newline at end of file