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