diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 8baeec089..fa1c89fff 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -364,6 +364,7 @@ + diff --git a/Artemis/Artemis/Managers/KeybindManager.cs b/Artemis/Artemis/Managers/KeybindManager.cs index 65c7e37dc..263d299df 100644 --- a/Artemis/Artemis/Managers/KeybindManager.cs +++ b/Artemis/Artemis/Managers/KeybindManager.cs @@ -1,7 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using System.Windows.Forms; using System.Windows.Input; +using Artemis.Models; using Artemis.Utilities.Keyboard; using MahApps.Metro.Controls; using KeyEventArgs = System.Windows.Forms.KeyEventArgs; @@ -10,76 +11,77 @@ namespace Artemis.Managers { public static class KeybindManager { - private static readonly Dictionary HotKeys; + private static readonly List KeybindModels = new List(); static KeybindManager() { KeyboardHook.KeyDownCallback += KeyboardHookOnKeyDownCallback; - HotKeys = new Dictionary(); } private static void KeyboardHookOnKeyDownCallback(KeyEventArgs e) { - // Don't trigger if none of the modifiers are held down - if (!e.Alt && !e.Control && !e.Shift) - return; - // Don't trigger if the key itself is a modifier if (e.KeyCode == Keys.LShiftKey || e.KeyCode == Keys.RShiftKey || e.KeyCode == Keys.LControlKey || e.KeyCode == Keys.RControlKey || e.KeyCode == Keys.LMenu || e.KeyCode == Keys.RMenu) return; + // Create a WPF ModifierKeys enum + var modifiers = ModifierKeysFromBooleans(e.Alt, e.Control, e.Shift); + + // Create a HotKey object for comparison + var hotKey = new HotKey(KeyInterop.KeyFromVirtualKey(e.KeyValue), modifiers); + + foreach (var keybindModel in KeybindModels) + keybindModel.InvokeIfMatched(hotKey); + } + + public static void AddOrUpdate(KeybindModel keybindModel) + { + var existing = KeybindModels.FirstOrDefault(k => k.Name == keybindModel.Name); + if (existing != null) + KeybindModels.Remove(existing); + + KeybindModels.Add(keybindModel); + } + + public static void Remove(KeybindModel keybindModel) + { + if (KeybindModels.Contains(keybindModel)) + KeybindModels.Remove(keybindModel); + } + + public static void Remove(string name) + { + var existing = KeybindModels.FirstOrDefault(k => k.Name == name); + if (existing != null) + KeybindModels.Remove(existing); + } + + public static void Clear() + { + // TODO: Re-add future global keybinds here or just exclude them from the clear + KeybindModels.Clear(); + } + + public static ModifierKeys ModifierKeysFromBooleans(bool alt, bool control, bool shift) + { // Create a WPF ModifierKeys enum var modifiers = ModifierKeys.None; - if (e.Alt) + if (alt) modifiers = ModifierKeys.Alt; - if (e.Control) + if (control) if (modifiers == ModifierKeys.None) modifiers = ModifierKeys.Control; else modifiers |= ModifierKeys.Control; - if (e.Shift) + if (shift) if (modifiers == ModifierKeys.None) modifiers = ModifierKeys.Shift; else modifiers |= ModifierKeys.Shift; - // Create a HotKey object for comparison - var hotKey = new HotKey(KeyInterop.KeyFromVirtualKey(e.KeyValue), modifiers); - - // If the hotkey is present, invoke the action related to it - if (HotKeys.ContainsKey(hotKey)) - HotKeys[hotKey].Invoke(); - } - - /// - /// Registers a hotkey and executes the provided action when the hotkey is pressed - /// - /// The hotkey to register - /// The action to invoke on press - /// Returns true if key registed, false if already in use - public static bool RegisterHotkey(HotKey hotKey, Action action) - { - if (HotKeys.ContainsKey(hotKey)) - return false; - - HotKeys.Add(hotKey, action); - return true; - } - - /// - /// Unregister the given hotkey - /// - /// The hotkey to unregister - /// Returns true if unregistered, false if not found - public static bool UnregisterHotkey(HotKey hotKey) - { - if (!HotKeys.ContainsKey(hotKey)) - return false; - - HotKeys.Remove(hotKey); - return true; + return modifiers; } } } \ No newline at end of file diff --git a/Artemis/Artemis/Models/KeybindModel.cs b/Artemis/Artemis/Models/KeybindModel.cs new file mode 100644 index 000000000..685894c0c --- /dev/null +++ b/Artemis/Artemis/Models/KeybindModel.cs @@ -0,0 +1,25 @@ +using System; +using MahApps.Metro.Controls; + +namespace Artemis.Models +{ + public class KeybindModel + { + public KeybindModel(string name, HotKey hotKey, Action action) + { + Name = name; + HotKey = hotKey; + Action = action; + } + + public string Name { get; set; } + public HotKey HotKey { get; set; } + public Action Action { get; set; } + + public void InvokeIfMatched(HotKey hotKey) + { + if (hotKey.Equals(HotKey)) + Action.Invoke(); + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs b/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs index 255b059b8..190d7e1e3 100644 --- a/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs +++ b/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Windows; +using System.Windows.Forms; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; @@ -8,21 +10,52 @@ namespace Artemis.Profiles.Layers.Conditions { public class DataModelCondition : ILayerCondition { + private DateTime _lastKeypress; + public bool HotKeyMet { get; set; } + private static readonly TimeSpan Delay = TimeSpan.FromMilliseconds((SystemParameters.KeyboardDelay + 1) * 250); + public bool ConditionsMet(LayerModel layerModel, ModuleDataModel dataModel) { lock (layerModel.Properties.Conditions) { + var checkConditions = layerModel.Properties.Conditions.Where(c => !c.Field.Contains("hotkey")); + var conditionMet = false; switch (layerModel.Properties.ConditionType) { case ConditionType.AnyMet: - return layerModel.Properties.Conditions.Any(cm => cm.ConditionMet(dataModel)); + conditionMet = HotKeyMet || checkConditions.Any(cm => cm.ConditionMet(dataModel)); + break; case ConditionType.AllMet: - return layerModel.Properties.Conditions.All(cm => cm.ConditionMet(dataModel)); + conditionMet = HotKeyMet && checkConditions.All(cm => cm.ConditionMet(dataModel)); + break; case ConditionType.NoneMet: - return !layerModel.Properties.Conditions.Any(cm => cm.ConditionMet(dataModel)); - default: - return false; + conditionMet = !HotKeyMet && !checkConditions.Any(cm => cm.ConditionMet(dataModel)); + break; } + + // If there is a held down keybind on it, reset every 2 frames, after 500 ms + if (layerModel.Properties.Conditions.Any(c => c.Operator == "held") && DateTime.Now - _lastKeypress > Delay) + HotKeyMet = false; + + return conditionMet; + } + } + + + public void KeybindTask(LayerConditionModel condition) + { + _lastKeypress = DateTime.Now; + switch (condition.Field) + { + case "hotkeyEnable": + HotKeyMet = true; + break; + case "hotkeyDisable": + HotKeyMet = false; + break; + case "hotkeyToggle": + HotKeyMet = !HotKeyMet; + break; } } } diff --git a/Artemis/Artemis/Profiles/Layers/Conditions/EventCondition.cs b/Artemis/Artemis/Profiles/Layers/Conditions/EventCondition.cs index 5db52f958..699f9f810 100644 --- a/Artemis/Artemis/Profiles/Layers/Conditions/EventCondition.cs +++ b/Artemis/Artemis/Profiles/Layers/Conditions/EventCondition.cs @@ -1,37 +1,60 @@ -using System; -using System.Linq; +using System.Linq; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; +using Newtonsoft.Json; namespace Artemis.Profiles.Layers.Conditions { public class EventCondition : ILayerCondition { + [JsonIgnore] + public bool HotKeyMet { get;set; } + public bool ConditionsMet(LayerModel layerModel, ModuleDataModel dataModel) { lock (layerModel.Properties.Conditions) { + var checkConditions = layerModel.Properties.Conditions.Where(c => !c.Field.Contains("hotkey")); var conditionsMet = false; switch (layerModel.Properties.ConditionType) { case ConditionType.AnyMet: - conditionsMet = layerModel.Properties.Conditions.Any(cm => cm.ConditionMet(dataModel)); + conditionsMet = HotKeyMet || checkConditions.Any(cm => cm.ConditionMet(dataModel)); break; case ConditionType.AllMet: - conditionsMet = layerModel.Properties.Conditions.All(cm => cm.ConditionMet(dataModel)); + conditionsMet = HotKeyMet && checkConditions.All(cm => cm.ConditionMet(dataModel)); break; case ConditionType.NoneMet: - conditionsMet = !layerModel.Properties.Conditions.Any(cm => cm.ConditionMet(dataModel)); + conditionsMet = !HotKeyMet && !checkConditions.Any(cm => cm.ConditionMet(dataModel)); break; } + layerModel.EventProperties.Update(layerModel, conditionsMet); - if (conditionsMet && layerModel.EventProperties.CanTrigger) - layerModel.EventProperties.TriggerEvent(layerModel); + if (conditionsMet) + layerModel.EventProperties.TriggerEvent(layerModel); + if (layerModel.EventProperties.MustStop(layerModel)) + HotKeyMet = false; return layerModel.EventProperties.MustDraw; } } + + public void KeybindTask(LayerConditionModel condition) + { + switch (condition.Field) + { + case "hotkeyEnable": + HotKeyMet = true; + break; + case "hotkeyDisable": + HotKeyMet = false; + break; + case "hotkeyToggle": + HotKeyMet = !HotKeyMet; + break; + } + } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerCondition.cs b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerCondition.cs index ee2ff364b..ab19a5dcc 100644 --- a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerCondition.cs +++ b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerCondition.cs @@ -6,5 +6,6 @@ namespace Artemis.Profiles.Layers.Interfaces public interface ILayerCondition { bool ConditionsMet(LayerModel layerModel, ModuleDataModel dataModel); + void KeybindTask(LayerConditionModel condition); } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs b/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs index 71de0a2ce..560d74408 100644 --- a/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs @@ -3,6 +3,7 @@ using System.Globalization; using Artemis.Modules.Abstract; using Artemis.Utilities; using DynamicExpresso; +using MahApps.Metro.Controls; namespace Artemis.Profiles.Layers.Models { @@ -20,6 +21,7 @@ namespace Artemis.Profiles.Layers.Models public string Value { get; set; } public string Operator { get; set; } public string Type { get; set; } + public HotKey HotKey { get; set; } public bool ConditionMet(ModuleDataModel subject) { diff --git a/Artemis/Artemis/Profiles/ProfileModel.cs b/Artemis/Artemis/Profiles/ProfileModel.cs index 610e44749..9cf4c7fd4 100644 --- a/Artemis/Artemis/Profiles/ProfileModel.cs +++ b/Artemis/Artemis/Profiles/ProfileModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; @@ -184,11 +185,13 @@ namespace Artemis.Profiles public void Activate(LuaManager luaManager) { + ApplyKeybinds(); luaManager.SetupLua(this); } public void Deactivate(LuaManager luaManager) { + KeybindManager.Clear(); luaManager.ClearLua(); } @@ -210,6 +213,24 @@ namespace Artemis.Profiles return layer; } + public void ApplyKeybinds() + { + foreach (var layerModel in Layers) + { + for (var index = 0; index < layerModel.Properties.Conditions.Count; index++) + { + var condition = layerModel.Properties.Conditions[index]; + if (condition.Field == null || !condition.Field.Contains("hotkey")) + continue; + + // Create an action for the layer, the layer's specific condition type handles activation + var action = new Action(() => layerModel.LayerCondition.KeybindTask(condition)); + var kb = new KeybindModel($"{GameName}-{Name}-{layerModel.Name}-{index}", condition.HotKey, action); + KeybindManager.AddOrUpdate(kb); + } + } + } + #region Compare protected bool Equals(ProfileModel other) diff --git a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 5fbb1bfed..1092c51d9 100644 --- a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -319,6 +319,9 @@ namespace Artemis.ViewModels { Thread.Sleep(100); SelectedLayer = selectModel; + + // Let the profile reapply keybinds after messing with layers + SelectedProfile.ApplyKeybinds(); }); } diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs index b70d8792c..031447180 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs @@ -3,6 +3,7 @@ using System.Linq; using Artemis.Profiles.Layers.Models; using Artemis.Utilities; using Caliburn.Micro; +using MahApps.Metro.Controls; namespace Artemis.ViewModels.Profiles { @@ -18,14 +19,14 @@ namespace Artemis.ViewModels.Profiles private readonly NamedOperator[] _hotkeyOperators = { new NamedOperator("Pressed", "enable"), - new NamedOperator("Held down", "disable") + new NamedOperator("Held down", "held") }; private readonly GeneralHelpers.PropertyCollection[] _hotkeyProperties = { - new GeneralHelpers.PropertyCollection {Display = "Enable when hotkey", Type = "hotkeyEnable"}, - new GeneralHelpers.PropertyCollection {Display = "Disable when hotkey", Type = "hotkeyDisable"}, - new GeneralHelpers.PropertyCollection {Display = "Toggle when hotkey", Type = "hotkeyToggle"} + new GeneralHelpers.PropertyCollection {Display = "Enable when hotkey", Type = "hotkeyEnable", Path = "hotkeyEnable"}, + new GeneralHelpers.PropertyCollection {Display = "Disable when hotkey", Type = "hotkeyDisable", Path = "hotkeyDisable"}, + new GeneralHelpers.PropertyCollection {Display = "Toggle when hotkey", Type = "hotkeyToggle", Path = "hotkeyToggle"} }; private readonly NamedOperator[] _int32Operators = @@ -53,11 +54,14 @@ namespace Artemis.ViewModels.Profiles new NamedOperator("Ends with", ".EndsWith") }; - private bool _enumValueIsVisible; + private HotKey _hotKey; + private bool _keybindIsVisible; private GeneralHelpers.PropertyCollection _selectedDataModelProp; - private string _selectedEnum; + private string _selectedDropdownValue; private NamedOperator _selectedOperator; + + private bool _userDropdownValueIsVisible; private string _userValue; private bool _userValueIsVisible; @@ -67,7 +71,7 @@ namespace Artemis.ViewModels.Profiles ConditionModel = conditionModel; Operators = new BindableCollection(); - Enums = new BindableCollection(); + DropdownValues = new BindableCollection(); DataModelProps = new BindableCollection(_hotkeyProperties); DataModelProps.AddRange(editorViewModel.DataModelProps); @@ -80,7 +84,7 @@ namespace Artemis.ViewModels.Profiles public BindableCollection DataModelProps { get; set; } public BindableCollection Operators { get; set; } - public BindableCollection Enums { get; set; } + public BindableCollection DropdownValues { get; set; } public string UserValue { @@ -93,6 +97,17 @@ namespace Artemis.ViewModels.Profiles } } + public HotKey HotKey + { + get { return _hotKey; } + set + { + if (Equals(value, _hotKey)) return; + _hotKey = value; + NotifyOfPropertyChange(() => HotKey); + } + } + public GeneralHelpers.PropertyCollection SelectedDataModelProp { get { return _selectedDataModelProp; } @@ -116,14 +131,14 @@ namespace Artemis.ViewModels.Profiles } } - public bool EnumValueIsVisible + public bool UserDropdownValueIsVisible { - get { return _enumValueIsVisible; } + get { return _userDropdownValueIsVisible; } set { - if (value == _enumValueIsVisible) return; - _enumValueIsVisible = value; - NotifyOfPropertyChange(() => EnumValueIsVisible); + if (value == _userDropdownValueIsVisible) return; + _userDropdownValueIsVisible = value; + NotifyOfPropertyChange(() => UserDropdownValueIsVisible); } } @@ -149,14 +164,14 @@ namespace Artemis.ViewModels.Profiles } } - public string SelectedEnum + public string SelectedDropdownValue { - get { return _selectedEnum; } + get { return _selectedDropdownValue; } set { - if (value == _selectedEnum) return; - _selectedEnum = value; - NotifyOfPropertyChange(() => SelectedEnum); + if (value == _selectedDropdownValue) return; + _selectedDropdownValue = value; + NotifyOfPropertyChange(() => SelectedDropdownValue); } } @@ -168,9 +183,10 @@ namespace Artemis.ViewModels.Profiles SelectedDataModelProp = DataModelProps.FirstOrDefault(m => m.Path == ConditionModel.Field); // Select the operator SelectedOperator = Operators.FirstOrDefault(o => o.Value == ConditionModel.Operator); + HotKey = ConditionModel.HotKey; if (ConditionModel.Type == "Enum" || ConditionModel.Type == "Boolean") - SelectedEnum = ConditionModel.Value; + SelectedDropdownValue = ConditionModel.Value; else UserValue = ConditionModel.Value; @@ -182,9 +198,10 @@ namespace Artemis.ViewModels.Profiles ConditionModel.Field = SelectedDataModelProp.Path; ConditionModel.Operator = SelectedOperator.Value; ConditionModel.Type = SelectedDataModelProp.Type; + ConditionModel.HotKey = HotKey; if (ConditionModel.Type == "Enum" || ConditionModel.Type == "Boolean") - ConditionModel.Value = SelectedEnum; + ConditionModel.Value = SelectedDropdownValue; else ConditionModel.Value = UserValue; } @@ -197,7 +214,7 @@ namespace Artemis.ViewModels.Profiles public void SetupPropertyInput() { Operators.Clear(); - Enums.Clear(); + DropdownValues.Clear(); switch (SelectedDataModelProp.Type) { @@ -208,9 +225,9 @@ namespace Artemis.ViewModels.Profiles break; case "Boolean": Operators.AddRange(_boolOperators); - Enums.Add("True"); - Enums.Add("False"); - EnumValueIsVisible = true; + DropdownValues.Add("True"); + DropdownValues.Add("False"); + UserDropdownValueIsVisible = true; break; case "String": Operators.AddRange(_stringOperators); @@ -242,14 +259,14 @@ namespace Artemis.ViewModels.Profiles Operators.Add(new NamedOperator("Increased", "increased")); } } - + SetupUserValueInput(); } private void SetupUserValueInput() { UserValueIsVisible = false; - EnumValueIsVisible = false; + UserDropdownValueIsVisible = false; KeybindIsVisible = false; // Event operators don't have any form of input @@ -257,19 +274,19 @@ namespace Artemis.ViewModels.Profiles SelectedOperator.Value == "increased") return; - if (SelectedDataModelProp.Type.Contains("hotkey")) + if (SelectedDataModelProp.Type != null && SelectedDataModelProp.Type.Contains("hotkey")) { KeybindIsVisible = true; } else if (SelectedDataModelProp.Type == "Boolean") { - EnumValueIsVisible = true; + UserDropdownValueIsVisible = true; } else if (SelectedDataModelProp.EnumValues != null) { - Enums.Clear(); - Enums.AddRange(SelectedDataModelProp.EnumValues); - EnumValueIsVisible = true; + DropdownValues.Clear(); + DropdownValues.AddRange(SelectedDataModelProp.EnumValues); + UserDropdownValueIsVisible = true; } else { diff --git a/Artemis/Artemis/Views/Profiles/LayerConditionView.xaml b/Artemis/Artemis/Views/Profiles/LayerConditionView.xaml index 77ea18a79..a2981b7f6 100644 --- a/Artemis/Artemis/Views/Profiles/LayerConditionView.xaml +++ b/Artemis/Artemis/Views/Profiles/LayerConditionView.xaml @@ -52,13 +52,13 @@ - + - - +