From afb6315ec660379cfb6485c3333bd69e828f017a Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 14 Jan 2017 00:26:55 +0100 Subject: [PATCH] Added ProfileEditorModel Refactored some namespaces Got rid of ProfileViewModel --- Artemis/Artemis/Artemis.csproj | 15 +- .../Artemis/InjectionModules/BaseModules.cs | 10 +- Artemis/Artemis/Models/LayerEditorModel.cs | 12 + Artemis/Artemis/Models/ProfileEditorModel.cs | 354 +++++ .../Modules/Abstract/ModuleViewModel.cs | 1 + .../Abstract/LayerPropertiesViewModel.cs | 1 + .../Profiles/Layers/Interfaces/ILayerType.cs | 1 + .../AmbientLightPropertiesViewModel.cs | 1 + .../Types/AmbientLight/AmbientLightType.cs | 1 + .../Types/Audio/AudioPropertiesViewModel.cs | 1 + .../Profiles/Layers/Types/Audio/AudioType.cs | 1 + .../Types/Folder/FolderPropertiesViewModel.cs | 1 + .../Layers/Types/Folder/FolderType.cs | 1 + .../Generic/GenericPropertiesViewModel.cs | 1 + .../Layers/Types/Generic/GenericType.cs | 1 + .../Headset/HeadsetPropertiesViewModel.cs | 1 + .../Layers/Types/Headset/HeadsetType.cs | 1 + .../KeyPress/KeyPressPropertiesViewModel.cs | 1 + .../Layers/Types/KeyPress/KeyPressType.cs | 1 + .../Keyboard/KeyboardPropertiesViewModel.cs | 1 + .../Layers/Types/Keyboard/KeyboardType.cs | 1 + .../Types/KeyboardGif/KeyboardGifType.cs | 1 + .../Types/Mouse/MousePropertiesViewModel.cs | 1 + .../Profiles/Layers/Types/Mouse/MouseType.cs | 1 + .../Mousemat/MousematPropertiesViewModel.cs | 1 + .../Layers/Types/Mousemat/MousematType.cs | 1 + .../{Profiles => }/LayerEditorViewModel.cs | 13 +- .../{Profiles => }/ProfileEditorViewModel.cs | 1178 +++++++++-------- .../ViewModels/Profiles/ProfileViewModel.cs | 382 ------ .../Views/{Profiles => }/LayerEditorView.xaml | 2 +- .../{Profiles => }/LayerEditorView.xaml.cs | 2 +- .../{Profiles => }/ProfileEditorView.xaml | 583 ++++---- .../{Profiles => }/ProfileEditorView.xaml.cs | 2 +- 33 files changed, 1303 insertions(+), 1272 deletions(-) create mode 100644 Artemis/Artemis/Models/LayerEditorModel.cs create mode 100644 Artemis/Artemis/Models/ProfileEditorModel.cs rename Artemis/Artemis/ViewModels/{Profiles => }/LayerEditorViewModel.cs (95%) rename Artemis/Artemis/ViewModels/{Profiles => }/ProfileEditorViewModel.cs (52%) delete mode 100644 Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs rename Artemis/Artemis/Views/{Profiles => }/LayerEditorView.xaml (99%) rename Artemis/Artemis/Views/{Profiles => }/LayerEditorView.xaml.cs (89%) rename Artemis/Artemis/Views/{Profiles => }/ProfileEditorView.xaml (93%) rename Artemis/Artemis/Views/{Profiles => }/ProfileEditorView.xaml.cs (89%) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 3500de5f3..6452856de 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -356,6 +356,8 @@ + + @@ -654,17 +656,16 @@ - - + - + @@ -709,7 +710,7 @@ LayerDynamicPropertiesView.xaml - + LayerEditorView.xaml @@ -721,7 +722,7 @@ LayerTweenView.xaml - + ProfileEditorView.xaml @@ -952,7 +953,7 @@ Designer MSBuild:Compile - + MSBuild:Compile Designer @@ -968,7 +969,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile diff --git a/Artemis/Artemis/InjectionModules/BaseModules.cs b/Artemis/Artemis/InjectionModules/BaseModules.cs index bd0108284..eb4bc0bad 100644 --- a/Artemis/Artemis/InjectionModules/BaseModules.cs +++ b/Artemis/Artemis/InjectionModules/BaseModules.cs @@ -1,4 +1,5 @@ using Artemis.DeviceProviders; +using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; @@ -8,7 +9,6 @@ using Artemis.Utilities.DataReaders; using Artemis.Utilities.GameState; using Artemis.ViewModels; using Artemis.ViewModels.Abstract; -using Artemis.ViewModels.Profiles; using Ninject.Extensions.Conventions; using Ninject.Modules; @@ -18,10 +18,16 @@ namespace Artemis.InjectionModules { public override void Load() { + #region Models + + Bind().ToSelf(); + Bind().ToSelf(); + + #endregion + #region ViewModels Bind().ToSelf().InSingletonScope(); - Bind().ToSelf(); Bind().ToSelf(); Bind().ToSelf().InSingletonScope(); Kernel.Bind(x => diff --git a/Artemis/Artemis/Models/LayerEditorModel.cs b/Artemis/Artemis/Models/LayerEditorModel.cs new file mode 100644 index 000000000..c8c374310 --- /dev/null +++ b/Artemis/Artemis/Models/LayerEditorModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.Models +{ + public class LayerEditorModel + { + } +} diff --git a/Artemis/Artemis/Models/ProfileEditorModel.cs b/Artemis/Artemis/Models/ProfileEditorModel.cs new file mode 100644 index 000000000..caefa2926 --- /dev/null +++ b/Artemis/Artemis/Models/ProfileEditorModel.cs @@ -0,0 +1,354 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Artemis.DAL; +using Artemis.Managers; +using Artemis.Modules.Abstract; +using Artemis.Profiles; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.Folder; +using Artemis.Properties; +using Artemis.Services; +using Artemis.Utilities; +using Artemis.ViewModels; +using Ninject.Parameters; + +namespace Artemis.Models +{ + public class ProfileEditorModel : IDisposable + { + private readonly DeviceManager _deviceManager; + private readonly LuaManager _luaManager; + private readonly DialogService _dialogService; + private readonly WindowService _windowService; + private FileSystemWatcher _watcher; + private ProfileModel _luaProfile; + + public ProfileEditorModel(WindowService windowService, MetroDialogService dialogService, + DeviceManager deviceManager, LuaManager luaManager) + { + _windowService = windowService; + _dialogService = dialogService; + _deviceManager = deviceManager; + _luaManager = luaManager; + } + + #region Layers + + /// + /// Opens a new LayerEditorView for the given layer + /// + /// The layer to open the view for + /// The datamodel to bind the editor to + public void EditLayer(LayerModel layer, ModuleDataModel dataModel) + { + IParameter[] args = + { + new ConstructorArgument("dataModel", dataModel), + new ConstructorArgument("layer", layer) + }; + _windowService.ShowDialog(args); + + // If the layer was a folder, but isn't anymore, assign it's children to it's parent. + if (layer.LayerType is FolderType || !layer.Children.Any()) + return; + + while (layer.Children.Any()) + { + var child = layer.Children[0]; + layer.Children.Remove(child); + if (layer.Parent != null) + { + layer.Parent.Children.Add(child); + layer.Parent.FixOrder(); + } + else + { + layer.Profile.Layers.Add(child); + layer.Profile.FixOrder(); + } + } + } + + /// + /// Removes the given layer from the profile + /// + /// The layer to remove + /// The profile to remove it from + public void RemoveLayer(LayerModel layer, ProfileModel profileModel) + { + if (layer == null) + return; + + if (layer.Parent != null) + { + var parent = layer.Parent; + layer.Parent.Children.Remove(layer); + parent.FixOrder(); + } + else if (layer.Profile != null) + { + var profile = layer.Profile; + layer.Profile.Layers.Remove(layer); + profile.FixOrder(); + } + + // Extra cleanup in case of a wonky layer that has no parent + if (profileModel.Layers.Contains(layer)) + profileModel.Layers.Remove(layer); + } + + #endregion + + #region Profiles + + public async Task AddProfile(ModuleModel moduleModel) + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot add profile.", + "To add a profile, please select a keyboard in the options menu first."); + return null; + } + + var name = await GetValidProfileName("Name profile", "Please enter a unique name for your new profile"); + // User cancelled + if (name == null) + return null; + + var profile = new ProfileModel + { + Name = name, + KeyboardSlug = _deviceManager.ActiveKeyboard.Slug, + Width = _deviceManager.ActiveKeyboard.Width, + Height = _deviceManager.ActiveKeyboard.Height, + GameName = moduleModel.Name + }; + + if (!ProfileProvider.IsProfileUnique(profile)) + { + var overwrite = await _dialogService.ShowQuestionMessageBox("Overwrite existing profile", + "A profile with this name already exists for this game. Would you like to overwrite it?"); + if (!overwrite.Value) + return null; + } + + ProfileProvider.AddOrUpdate(profile); + return profile; + } + + public async Task RenameProfile(ProfileModel profileModel) + { + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); + if (!doRename) + return; + + ProfileProvider.RenameProfile(profileModel, profileModel.Name); + } + + public async Task DuplicateProfile(ProfileModel selectedProfile) + { + var newProfile = GeneralHelpers.Clone(selectedProfile); + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(newProfile, name, newProfile.Name); + if (!doRename) + return; + + // Make sure it's not default, in case of copying a default profile + newProfile.IsDefault = false; + ProfileProvider.AddOrUpdate(newProfile); + } + + public async Task DeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) + { + var confirm = await _dialogService.ShowQuestionMessageBox("Delete profile", + $"Are you sure you want to delete the profile named: {selectedProfile.Name}?\n\n" + + "This cannot be undone."); + if (!confirm.Value) + return; + + var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, moduleModel, "Default"); + var deleteProfile = selectedProfile; + + moduleModel.ChangeProfile(defaultProfile); + ProfileProvider.DeleteProfile(deleteProfile); + } + + public async Task ImportProfile(ModuleModel moduleModel) + { + var dialog = new OpenFileDialog {Filter = "Artemis profile (*.json)|*.json"}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + var profileModel = ProfileProvider.LoadProfileIfValid(dialog.FileName); + if (profileModel == null) + { + _dialogService.ShowErrorMessageBox("Oh noes, the profile you provided is invalid. " + + "If this keeps happening, please make an issue on GitHub and provide the profile."); + return; + } + + // Verify the game + if (profileModel.GameName != moduleModel.Name) + { + _dialogService.ShowErrorMessageBox( + $"Oh oops! This profile is ment for {profileModel.GameName}, not {moduleModel.Name} :c"); + return; + } + + // Verify the keyboard + var deviceManager = _deviceManager; + if (profileModel.KeyboardSlug != deviceManager.ActiveKeyboard.Slug) + { + var adjustKeyboard = await _dialogService.ShowQuestionMessageBox("Profile not made for this keyboard", + $"Watch out, this profile wasn't ment for this keyboard, but for the {profileModel.KeyboardSlug}. " + + "You can still import it but you'll probably have to do some adjusting\n\n" + + "Continue?"); + if (!adjustKeyboard.Value) + return; + + // Resize layers that are on the full keyboard width + profileModel.ResizeLayers(deviceManager.ActiveKeyboard); + // Put layers back into the canvas if they fell outside it + profileModel.FixBoundaries(deviceManager.ActiveKeyboard.KeyboardRectangle(1)); + + // Setup profile metadata to match the new keyboard + profileModel.KeyboardSlug = deviceManager.ActiveKeyboard.Slug; + profileModel.Width = deviceManager.ActiveKeyboard.Width; + profileModel.Height = deviceManager.ActiveKeyboard.Height; + } + + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); + if (!doRename) + return; + + profileModel.IsDefault = false; + ProfileProvider.AddOrUpdate(profileModel); + } + + private async Task GetValidProfileName(string title, string text) + { + var name = await _dialogService.ShowInputDialog(title, text); + + // Null when the user cancelled + if (name == null) + return null; + + if (name.Length >= 2) + return name; + + _dialogService.ShowMessageBox("Invalid profile name", + "Please provide a valid profile name that's longer than 2 symbols"); + + return await GetValidProfileName(title, text); + } + + private async Task MakeProfileUnique(ProfileModel profileModel, string name, string oldName) + { + profileModel.Name = name; + if (ProfileProvider.IsProfileUnique(profileModel)) + return true; + + name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + if (name != null) + return await MakeProfileUnique(profileModel, name, oldName); + + // If cancelled, restore old name and stop + profileModel.Name = oldName; + return false; + } + + #endregion + + #region LUA + + public void OpenLuaEditor(ProfileModel profileModel) + { + // Clean up old environment + DisposeLuaWatcher(); + + // Create a temp file + var fileName = Guid.NewGuid() + ".lua"; + var file = File.Create(Path.GetTempPath() + fileName); + file.Dispose(); + + // Add instructions to LUA script if it's a new file + if (string.IsNullOrEmpty(profileModel.LuaScript)) + profileModel.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); + File.WriteAllText(Path.GetTempPath() + fileName, profileModel.LuaScript); + + // Watch the file for changes + _luaProfile = profileModel; + _watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); + _watcher.Changed += LuaFileChanged; + _watcher.EnableRaisingEvents = true; + _watcher.Path = Path.GetTempPath(); + _watcher.Filter = fileName; + + // Open the temp file with the default editor + System.Diagnostics.Process.Start(Path.GetTempPath() + fileName); + } + + private void LuaFileChanged(object sender, FileSystemEventArgs args) + { + if (_luaProfile == null) + { + DisposeLuaWatcher(); + return; + } + + if (args.ChangeType != WatcherChangeTypes.Changed) + return; + + lock (_luaProfile) + { + using (var fs = new FileStream(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (var sr = new StreamReader(fs)) + { + _luaProfile.LuaScript = sr.ReadToEnd(); + } + } + + ProfileProvider.AddOrUpdate(_luaProfile); + _luaManager.SetupLua(_luaProfile); + } + } + + private void DisposeLuaWatcher() + { + if (_watcher == null) return; + _watcher.Changed -= LuaFileChanged; + _watcher.Dispose(); + _watcher = null; + } + + public void Dispose() + { + DisposeLuaWatcher(); + } + + #endregion + + #region Rendering + + + + #endregion + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index 55fdfef5d..fa0eeaca5 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -2,6 +2,7 @@ using Artemis.Managers; using Artemis.Services; using Artemis.Settings; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; using Ninject; diff --git a/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs index 33ea330e7..3669713a0 100644 --- a/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs @@ -1,5 +1,6 @@ using System.Windows.Media; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs index 83f8c9185..0faba2b3c 100644 --- a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs +++ b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs @@ -2,6 +2,7 @@ using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Newtonsoft.Json; diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs index 7e71757d0..d4fcf84c1 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.AmbientLight diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs index d1170138b..01c4922ee 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs @@ -13,6 +13,7 @@ using Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions; using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Newtonsoft.Json; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs index ca276c666..079b386b4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index e983d0969..a115f9bcb 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -9,6 +9,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Audio diff --git a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs index 318662690..ca33f411a 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Folder diff --git a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs index 52f5668c2..cfec51a00 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs @@ -6,6 +6,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Folder diff --git a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs index 80c7aa4ec..3de180e31 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs index 04a01016e..f7ca251d5 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Generic diff --git a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs index e6db95ab0..5e81c97c5 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs index 1df4777bc..3c99e55f4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Headset diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs index 9efbd9907..352f7c45f 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyPress diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs index 74f4d7cb0..a2a366ec4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs @@ -13,6 +13,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; using Artemis.Utilities.Keyboard; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyPress diff --git a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs index c84b3c3aa..eeb7943b7 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs @@ -3,6 +3,7 @@ using System.Windows.Forms; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs index 6e6627d43..2ace6c684 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs @@ -5,6 +5,7 @@ using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Keyboard diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs index bd0527a47..ef3e201e2 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs @@ -9,6 +9,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyboardGif diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs index 00d683cc8..d88b692f9 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs index df6d46592..31a2d4fa1 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Mouse diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs index 65c42d6a2..b960966e9 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs index 58583e475..f1379e754 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Mousemat diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs b/Artemis/Artemis/ViewModels/LayerEditorViewModel.cs similarity index 95% rename from Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs rename to Artemis/Artemis/ViewModels/LayerEditorViewModel.cs index 035fd42ad..1c387abe2 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/LayerEditorViewModel.cs @@ -10,13 +10,14 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Profiles.Layers.Types.KeyboardGif; using Artemis.Services; +using Artemis.Utilities; +using Artemis.ViewModels.Profiles; using Artemis.ViewModels.Profiles.Events; using Caliburn.Micro; using Newtonsoft.Json; using Ninject; -using static Artemis.Utilities.GeneralHelpers; -namespace Artemis.ViewModels.Profiles +namespace Artemis.ViewModels { public sealed class LayerEditorViewModel : Screen { @@ -30,13 +31,13 @@ namespace Artemis.ViewModels.Profiles IEnumerable layerAnimations) { Layer = layer; - ProposedLayer = Clone(layer); + ProposedLayer = GeneralHelpers.Clone(layer); ProposedLayer.Children.Clear(); DataModel = DataModel; LayerTypes = new BindableCollection(types.OrderBy(t => t.Name)); LayerAnimations = layerAnimations.OrderBy(l => l.Name).ToList(); - DataModelProps = new BindableCollection(GenerateTypeMap(dataModel)); + DataModelProps = new BindableCollection(GeneralHelpers.GenerateTypeMap(dataModel)); if (Layer.Properties == null) Layer.SetupProperties(); @@ -57,7 +58,7 @@ namespace Artemis.ViewModels.Profiles public MetroDialogService DialogService { get; set; } public BindableCollection LayerTypes { get; set; } - public BindableCollection DataModelProps { get; set; } + public BindableCollection DataModelProps { get; set; } public BindableCollection LayerConditionVms { get; set; } public bool KeyboardGridIsVisible => ProposedLayer.LayerType is KeyboardType; public bool GifGridIsVisible => ProposedLayer.LayerType is KeyboardGifType; @@ -215,7 +216,7 @@ namespace Artemis.ViewModels.Profiles // Ignore the children, can't just temporarily add them to the proposed layer because // that would upset the child layers' relations (sounds like Dr. Phil amirite?) - var currentObj = Clone(Layer); + var currentObj = GeneralHelpers.Clone(Layer); currentObj.Children.Clear(); // Apply the IsEvent boolean diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs similarity index 52% rename from Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs rename to Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 734598963..2b1da49ae 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; @@ -13,8 +14,10 @@ using Artemis.DAL; using Artemis.DeviceProviders; using Artemis.Events; using Artemis.Managers; +using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles; +using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Folder; using Artemis.Properties; @@ -22,86 +25,84 @@ using Artemis.Services; using Artemis.Styles.DropTargetAdorners; using Artemis.Utilities; using Caliburn.Micro; +using Castle.Components.DictionaryAdapter; using GongSolutions.Wpf.DragDrop; +using MahApps.Metro; using MahApps.Metro.Controls.Dialogs; -using Ninject.Parameters; using NuGet; +using Application = System.Windows.Application; +using Cursor = System.Windows.Input.Cursor; +using Cursors = System.Windows.Input.Cursors; using DragDropEffects = System.Windows.DragDropEffects; using IDropTarget = GongSolutions.Wpf.DragDrop.IDropTarget; using MouseEventArgs = System.Windows.Input.MouseEventArgs; using Screen = Caliburn.Micro.Screen; -namespace Artemis.ViewModels.Profiles +namespace Artemis.ViewModels { public sealed class ProfileEditorViewModel : Screen, IDropTarget, IDisposable { private readonly DeviceManager _deviceManager; private readonly MetroDialogService _dialogService; - private readonly LuaManager _luaManager; + private readonly LoopManager _loopManager; private readonly ModuleModel _moduleModel; - private readonly WindowService _windowService; private ImageSource _keyboardPreview; private ObservableCollection _layers; private ObservableCollection _profileNames; private bool _saving; - private FileSystemWatcher _watcher; - private ProfileViewModel _profileViewModel; private LayerModel _selectedLayer; + private bool _showAll; - public ProfileEditorViewModel(DeviceManager deviceManager, LuaManager luaManager, ModuleModel moduleModel, - ProfileViewModel profileViewModel, MetroDialogService dialogService, WindowService windowService) + public ProfileEditorViewModel(ProfileEditorModel profileEditorModel, DeviceManager deviceManager, + LoopManager loopManager, ModuleModel moduleModel, MetroDialogService dialogService) { _deviceManager = deviceManager; - _luaManager = luaManager; + _loopManager = loopManager; _moduleModel = moduleModel; _dialogService = dialogService; - _windowService = windowService; ProfileNames = new ObservableCollection(); Layers = new ObservableCollection(); - ProfileViewModel = profileViewModel; - - ProfileViewModel.ModuleModel = _moduleModel; + ProfileEditorModel = profileEditorModel; PropertyChanged += EditorStateHandler; _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; + _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; LoadProfiles(); } - public ProfileViewModel ProfileViewModel + public void Dispose() { - get { return _profileViewModel; } - set + SaveSelectedProfile(); + ProfileEditorModel.Dispose(); + _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; + _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; + } + + #region LUA + + public void EditLua() + { + if (SelectedProfile == null) + return; + try { - if (Equals(value, _profileViewModel)) return; - _profileViewModel = value; - NotifyOfPropertyChange(); - NotifyOfPropertyChange(nameof(LayerSelected)); + ProfileEditorModel.OpenLuaEditor(SelectedProfile); + } + catch (Exception e) + { + _dialogService.ShowMessageBox("Couldn't open LUA file", + "Please make sure you have a text editor associated with the .lua extension.\n\n" + + "Windows error message: \n" + e.Message); } } - public bool EditorEnabled - => - SelectedProfile != null && !SelectedProfile.IsDefault && - _deviceManager.ActiveKeyboard != null; + #endregion - public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; - public LayerModel SelectedLayer - { - get { return _selectedLayer; } - set - { - if (_profileViewModel != null) - _profileViewModel.SelectedLayer = value; - if (Equals(value, _selectedLayer)) - return; - - _selectedLayer = value; - NotifyOfPropertyChange(() => SelectedLayer); - NotifyOfPropertyChange(() => LayerSelected); - } - } + #region Properties + + public ProfileEditorModel ProfileEditorModel { get; } public ObservableCollection ProfileNames { @@ -125,6 +126,29 @@ namespace Artemis.ViewModels.Profiles } } + + public ImageSource KeyboardPreview + { + get { return _keyboardPreview; } + set + { + if (Equals(value, _keyboardPreview)) return; + _keyboardPreview = value; + NotifyOfPropertyChange(() => KeyboardPreview); + } + } + + public bool ShowAll + { + get { return _showAll; } + set + { + if (value == _showAll) return; + _showAll = value; + NotifyOfPropertyChange(); + } + } + public string SelectedProfileName { get { return SelectedProfile?.Name; } @@ -138,22 +162,542 @@ namespace Artemis.ViewModels.Profiles } } - public ImageSource KeyboardPreview + public LayerModel SelectedLayer { - get { return _keyboardPreview; } + get { return _selectedLayer; } set { - if (Equals(value, _keyboardPreview)) return; - _keyboardPreview = value; - NotifyOfPropertyChange(() => KeyboardPreview); + if (Equals(value, _selectedLayer)) + return; + + _selectedLayer = value; + NotifyOfPropertyChange(() => SelectedLayer); + NotifyOfPropertyChange(() => LayerSelected); } } - public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; + public ImageSource KeyboardImage => ImageUtilities.BitmapToBitmapImage( + _deviceManager.ActiveKeyboard?.PreviewSettings.Image ?? Resources.none); + public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; + public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; public bool ProfileSelected => SelectedProfile != null; public bool LayerSelected => SelectedProfile != null && SelectedLayer != null; + public bool EditorEnabled => SelectedProfile != null && !SelectedProfile.IsDefault && + _deviceManager.ActiveKeyboard != null; + + #endregion + + #region Layers + + public void EditLayerFromDoubleClick() + { + if (SelectedLayer?.LayerType is FolderType) + return; + + EditLayer(); + } + + public void EditLayer() + { + if (SelectedLayer == null) + return; + + ProfileEditorModel.EditLayer(SelectedLayer, _moduleModel.DataModel); + UpdateLayerList(SelectedLayer); + } + + public LayerModel AddLayer() + { + if (SelectedProfile == null) + return null; + + var layer = SelectedProfile.AddLayer(SelectedLayer); + UpdateLayerList(layer); + + return layer; + } + + public LayerModel AddFolder() + { + if (SelectedProfile == null) + return null; + + var layer = AddLayer(); + if (layer == null) + return null; + + layer.Name = "New folder"; + layer.LayerType = new FolderType(); + layer.LayerType.SetupProperties(layer); + + return layer; + } + + public void RemoveLayer() + { + RemoveLayer(SelectedLayer); + } + + public void RemoveLayer(LayerModel layer) + { + ProfileEditorModel.RemoveLayer(layer, SelectedProfile); + UpdateLayerList(null); + } + + public async void RenameLayer(LayerModel layer) + { + if (layer == null) + return; + + var newName = await _dialogService.ShowInputDialog("Rename layer", "Please enter a name for the layer", + new MetroDialogSettings {DefaultText = layer.Name}); + + // Null when the user cancelled + if (string.IsNullOrEmpty(newName)) + return; + + layer.Name = newName; + UpdateLayerList(layer); + } + + /// + /// Clones the currently selected layer and adds it to the profile, after the original + /// + public void CloneLayer() + { + if (SelectedLayer == null) + return; + + CloneLayer(SelectedLayer); + } + + /// + /// Clones the given layer and adds it to the profile, after the original + /// + /// + public void CloneLayer(LayerModel layer) + { + var clone = GeneralHelpers.Clone(layer); + layer.InsertAfter(clone); + + UpdateLayerList(clone); + } + + private void UpdateLayerList(LayerModel selectModel) + { + // Update the UI + Layers.Clear(); + SelectedLayer = null; + + if (SelectedProfile != null) + Layers.AddRange(SelectedProfile.Layers); + + if (selectModel == null) + return; + + // A small delay to allow the profile list to rebuild + Task.Factory.StartNew(() => + { + Thread.Sleep(100); + SelectedLayer = selectModel; + }); + } + + #endregion + + #region Profiles + + /// + /// Loads all profiles for the current game and keyboard + /// + private void LoadProfiles() + { + Execute.OnUIThread(() => + { + ProfileNames.Clear(); + if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) + ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); + + NotifyOfPropertyChange(() => SelectedProfile); + }); + } + + public void SaveSelectedProfile() + { + if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) + return; + + _saving = true; + try + { + ProfileProvider.AddOrUpdate(SelectedProfile); + } + catch (Exception) + { + // ignored + } + _saving = false; + } + + public async void AddProfile() + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot add profile.", + "To add a profile, please select a keyboard in the options menu first."); + return; + } + + var profile = await ProfileEditorModel.AddProfile(_moduleModel); + if (profile == null) + return; + + LoadProfiles(); + } + + public async void RenameProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.RenameProfile(SelectedProfile); + LoadProfiles(); + } + + public async void DuplicateProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.DuplicateProfile(SelectedProfile); + LoadProfiles(); + } + + public async void DeleteProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.DeleteProfile(SelectedProfile, _moduleModel); + LoadProfiles(); + } + + public async void ImportProfile() + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot import profile.", + "To import a profile, please select a keyboard in the options menu first."); + return; + } + + await ProfileEditorModel.ImportProfile(_moduleModel); + LoadProfiles(); + } + + public void ExportProfile() + { + if (SelectedProfile == null) + return; + + var dialog = new SaveFileDialog {Filter = "Artemis profile (*.json)|*.json"}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); + } + + #endregion + + #region Rendering + + private void LoopManagerOnRenderCompleted(object sender, EventArgs eventArgs) + { + // Besides the usual checks, also check if the ActiveKeyboard isn't the NoneKeyboard + if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null || + _deviceManager.ActiveKeyboard.Slug == "none") + { + KeyboardPreview = null; + + // Setup layers for the next frame + if (_moduleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) + _moduleModel.PreviewLayers = new List(); + + return; + } + + var renderLayers = GetRenderLayers(); + // Draw the current frame to the preview + var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); + var visual = new DrawingVisual(); + using (var drawingContext = visual.RenderOpen()) + { + // Setup the DrawingVisual's size + drawingContext.PushClip(new RectangleGeometry(keyboardRect)); + drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); + + // Draw the layers + foreach (var layer in renderLayers) + { + layer.Update(null, true, false); + if (layer.LayerType.ShowInEdtor) + layer.Draw(null, drawingContext, true, false); + } + + // Get the selection color + var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; + if (accentColor == null) + { + var preview = new DrawingImage(); + preview.Freeze(); + KeyboardPreview = preview; + return; + } + + var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); + + // Draw the selection outline and resize indicator + if (SelectedLayer != null && SelectedLayer.MustDraw()) + { + var layerRect = SelectedLayer.Properties.PropertiesRect(); + // Deflate the rect so that the border is drawn on the inside + layerRect.Inflate(-0.2, -0.2); + + // Draw an outline around the selected layer + drawingContext.DrawRectangle(null, pen, layerRect); + // Draw a resize indicator in the bottom-right + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); + } + + SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, + drawingContext)); + + // Remove the clip + drawingContext.Pop(); + } + var drawnPreview = new DrawingImage(visual.Drawing); + drawnPreview.Freeze(); + KeyboardPreview = drawnPreview; + + // Setup layers for the next frame + if (_moduleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) + _moduleModel.PreviewLayers = renderLayers; + } + + public List GetRenderLayers() + { + // Get the layers that must be drawn + List drawLayers; + if (ShowAll) + return SelectedProfile.GetRenderLayers(null, false, true); + + if (SelectedLayer == null || !SelectedLayer.Enabled) + return new EditableList(); + + if (SelectedLayer.LayerType is FolderType) + drawLayers = SelectedLayer.GetRenderLayers(null, false, true); + else + drawLayers = new List {SelectedLayer}; + + return drawLayers; + } + + #endregion + + #region Mouse actions + + private DateTime _downTime; + private LayerModel _draggingLayer; + private Point? _draggingLayerOffset; + private Cursor _keyboardPreviewCursor; + private bool _resizing; + + /// + /// Handler for clicking + /// + /// + public void MouseDownKeyboardPreview(MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + _downTime = DateTime.Now; + } + + /// + /// Second handler for clicking, selects a the layer the user clicked on. + /// + /// + public void MouseUpKeyboardPreview(MouseButtonEventArgs e) + { + if (SelectedProfile == null || SelectedProfile.IsDefault) + return; + + var timeSinceDown = DateTime.Now - _downTime; + if (!(timeSinceDown.TotalMilliseconds < 500)) + return; + if (_draggingLayer != null) + return; + + var keyboard = _deviceManager.ActiveKeyboard; + var pos = e.GetPosition((Image) e.OriginalSource); + var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); + var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); + + var hoverLayer = GetLayers().Where(l => l.MustDraw()) + .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); + + if (hoverLayer != null) + SelectedLayer = hoverLayer; + } + + /// + /// Handler for resizing and moving the currently selected layer + /// + /// + public void MouseMoveKeyboardPreview(MouseEventArgs e) + { + if (SelectedProfile == null) + return; + + var pos = e.GetPosition((Image) e.OriginalSource); + var keyboard = _deviceManager.ActiveKeyboard; + var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); + var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); + var hoverLayer = GetLayers().Where(l => l.MustDraw()) + .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); + + HandleDragging(e, x, y, hoverLayer); + + if (hoverLayer == null) + { + KeyboardPreviewCursor = Cursors.Arrow; + return; + } + + // Turn the mouse pointer into a hand if hovering over an active layer + if (hoverLayer == SelectedLayer) + { + var rect = hoverLayer.Properties.PropertiesRect(1); + KeyboardPreviewCursor = + Math.Sqrt(Math.Pow(x - rect.BottomRight.X, 2) + Math.Pow(y - rect.BottomRight.Y, 2)) < 0.6 + ? Cursors.SizeNWSE + : Cursors.SizeAll; + } + else + { + KeyboardPreviewCursor = Cursors.Hand; + } + } + + public Cursor KeyboardPreviewCursor + { + get { return _keyboardPreviewCursor; } + set + { + if (Equals(value, _keyboardPreviewCursor)) return; + _keyboardPreviewCursor = value; + NotifyOfPropertyChange(() => KeyboardPreviewCursor); + } + } + + /// + /// Handles dragging the given layer + /// + /// + /// + /// + /// + private void HandleDragging(MouseEventArgs e, double x, double y, LayerModel hoverLayer) + { + // Reset the dragging state on mouse release + if (e.LeftButton == MouseButtonState.Released || + _draggingLayer != null && SelectedLayer != _draggingLayer) + { + _draggingLayerOffset = null; + _draggingLayer = null; + return; + } + + if (SelectedLayer == null || SelectedLayer.LayerType != null && !SelectedLayer.LayerType.ShowInEdtor) + return; + + // Setup the dragging state on mouse press + if (_draggingLayerOffset == null && hoverLayer != null && e.LeftButton == MouseButtonState.Pressed) + { + var layerRect = hoverLayer.Properties.PropertiesRect(1); + + _draggingLayerOffset = new Point(x - SelectedLayer.Properties.X, y - SelectedLayer.Properties.Y); + _draggingLayer = hoverLayer; + // Detect dragging if cursor is in the bottom right + _resizing = Math.Sqrt(Math.Pow(x - layerRect.BottomRight.X, 2) + + Math.Pow(y - layerRect.BottomRight.Y, 2)) < 0.6; + } + + if (_draggingLayerOffset == null || _draggingLayer == null || _draggingLayer != SelectedLayer) + return; + + var draggingProps = _draggingLayer.Properties; + // If no setup or reset was done, handle the actual dragging action + if (_resizing) + { + var newWidth = Math.Round(x - draggingProps.X); + var newHeight = Math.Round(y - draggingProps.Y); + + // Ensure the layer doesn't leave the canvas + if (newWidth < 1 || draggingProps.X + newWidth <= 0) + newWidth = draggingProps.Width; + if (newHeight < 1 || draggingProps.Y + newHeight <= 0) + newHeight = draggingProps.Height; + + draggingProps.Width = newWidth; + draggingProps.Height = newHeight; + } + else + { + var newX = Math.Round(x - _draggingLayerOffset.Value.X); + var newY = Math.Round(y - _draggingLayerOffset.Value.Y); + + // Ensure the layer doesn't leave the canvas + if (newX >= SelectedProfile.Width || newX + draggingProps.Width <= 0) + newX = draggingProps.X; + if (newY >= SelectedProfile.Height || newY + draggingProps.Height <= 0) + newY = draggingProps.Y; + + draggingProps.X = newX; + draggingProps.Y = newY; + } + } + + private List GetLayers() + { + if (ShowAll) + return SelectedProfile.GetLayers(); + if (SelectedLayer == null) + return new List(); + + lock (SelectedLayer) + { + // Get the layers that must be drawn + if (SelectedLayer.LayerType is FolderType) + return SelectedLayer.GetLayers().ToList(); + return new List {SelectedLayer}; + } + } + + #endregion + + #region Event handles + public void DragOver(IDropInfo dropInfo) { var source = dropInfo.Data as LayerModel; @@ -226,7 +770,6 @@ namespace Artemis.ViewModels.Profiles { NotifyOfPropertyChange(() => SelectedProfileName); NotifyOfPropertyChange(() => SelectedProfile); - NotifyOfPropertyChange(() => ProfileViewModel.SelectedProfile); } /// @@ -235,461 +778,10 @@ namespace Artemis.ViewModels.Profiles private void DeviceManagerOnOnKeyboardChanged(object sender, KeyboardChangedEventArgs e) { NotifyOfPropertyChange(() => PreviewSettings); + NotifyOfPropertyChange(() => KeyboardImage); LoadProfiles(); } - /// - /// Loads all profiles for the current game and keyboard - /// - private void LoadProfiles() - { - Execute.OnUIThread(() => - { - ProfileNames.Clear(); - if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) - ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); - - NotifyOfPropertyChange(() => SelectedProfile); - }); - } - - - public void EditLayerFromDoubleClick() - { - if (SelectedLayer?.LayerType is FolderType) - return; - - EditLayer(); - } - - public void EditLayer() - { - if (SelectedLayer == null) - return; - - var selectedLayer = SelectedLayer; - EditLayer(selectedLayer); - } - - /// - /// Opens a new LayerEditorView for the given layer - /// - /// The layer to open the view for - public void EditLayer(LayerModel layer) - { - if (layer == null) - return; - - IParameter[] args = - { - new ConstructorArgument("dataModel", _moduleModel.DataModel), - new ConstructorArgument("layer", layer) - }; - _windowService.ShowDialog(args); - - // If the layer was a folder, but isn't anymore, assign it's children to it's parent. - if (!(layer.LayerType is FolderType) && layer.Children.Any()) - while (layer.Children.Any()) - { - var child = layer.Children[0]; - layer.Children.Remove(child); - if (layer.Parent != null) - { - layer.Parent.Children.Add(child); - layer.Parent.FixOrder(); - } - else - { - layer.Profile.Layers.Add(child); - layer.Profile.FixOrder(); - } - } - - UpdateLayerList(layer); - } - - /// - /// Adds a new layer to the profile and selects it - /// - public LayerModel AddLayer() - { - if (SelectedProfile == null) - return null; - - var layer = SelectedProfile.AddLayer(SelectedLayer); - UpdateLayerList(layer); - - return layer; - } - - public LayerModel AddFolder() - { - var layer = AddLayer(); - if (layer == null) - return null; - - layer.Name = "New folder"; - layer.LayerType = new FolderType(); - layer.LayerType.SetupProperties(layer); - - return layer; - } - - /// - /// Removes the currently selected layer from the profile - /// - public void RemoveLayer() - { - RemoveLayer(SelectedLayer); - } - - /// - /// Removes the given layer from the profile - /// - /// - public void RemoveLayer(LayerModel layer) - { - if (layer == null) - return; - - if (layer.Parent != null) - { - var parent = layer.Parent; - layer.Parent.Children.Remove(layer); - parent.FixOrder(); - } - else if (layer.Profile != null) - { - var profile = layer.Profile; - layer.Profile.Layers.Remove(layer); - profile.FixOrder(); - } - - // Extra cleanup in case of a wonky layer that has no parent - if (SelectedProfile.Layers.Contains(layer)) - SelectedProfile.Layers.Remove(layer); - - UpdateLayerList(null); - } - - public async void RenameLayer(LayerModel layer) - { - if (layer == null) - return; - - var newName = - await - _dialogService.ShowInputDialog("Rename layer", "Please enter a name for the layer", - new MetroDialogSettings {DefaultText = layer.Name}); - // Null when the user cancelled - if (string.IsNullOrEmpty(newName)) - return; - - layer.Name = newName; - UpdateLayerList(layer); - } - - /// - /// Clones the currently selected layer and adds it to the profile, after the original - /// - public void CloneLayer() - { - if (SelectedLayer == null) - return; - - CloneLayer(SelectedLayer); - } - - /// - /// Clones the given layer and adds it to the profile, after the original - /// - /// - public void CloneLayer(LayerModel layer) - { - var clone = GeneralHelpers.Clone(layer); - layer.InsertAfter(clone); - - UpdateLayerList(clone); - } - - private void UpdateLayerList(LayerModel selectModel) - { - // Update the UI - Layers.Clear(); - SelectedLayer = null; - - if (SelectedProfile != null) - Layers.AddRange(SelectedProfile.Layers); - - if (selectModel == null) - return; - - // A small delay to allow the profile list to rebuild - Task.Factory.StartNew(() => - { - Thread.Sleep(100); - SelectedLayer = selectModel; - }); - } - - /// - /// Handler for clicking - /// - /// - public void MouseDownKeyboardPreview(MouseButtonEventArgs e) - { - ProfileViewModel.MouseDownKeyboardPreview(e); - } - - /// - /// Second handler for clicking, selects a the layer the user clicked on - /// if the used clicked on an empty spot, deselects the current layer - /// - /// - public void MouseUpKeyboardPreview(MouseButtonEventArgs e) - { - ProfileViewModel.MouseUpKeyboardPreview(e); - } - - /// - /// Handler for resizing and moving the currently selected layer - /// - /// - public void MouseMoveKeyboardPreview(MouseEventArgs e) - { - ProfileViewModel.MouseMoveKeyboardPreview(e); - } - - /// - /// Adds a new profile to the current game and keyboard - /// - public async void AddProfile() - { - if (_deviceManager.ActiveKeyboard == null) - { - _dialogService.ShowMessageBox("Cannot add profile.", - "To add a profile, please select a keyboard in the options menu first."); - return; - } - - var name = await _dialogService.ShowInputDialog("Add new profile", - "Please provide a profile name unique to this game and keyboard."); - - // Null when the user cancelled - if (name == null) - return; - - if (name.Length < 2) - { - _dialogService.ShowMessageBox("Invalid profile name", "Please provide a valid profile name"); - return; - } - - var profile = new ProfileModel - { - Name = name, - KeyboardSlug = _deviceManager.ActiveKeyboard.Slug, - Width = _deviceManager.ActiveKeyboard.Width, - Height = _deviceManager.ActiveKeyboard.Height, - GameName = _moduleModel.Name - }; - - if (!ProfileProvider.IsProfileUnique(profile)) - { - var overwrite = await _dialogService.ShowQuestionMessageBox("Overwrite existing profile", - "A profile with this name already exists for this game. Would you like to overwrite it?"); - if (!overwrite.Value) - return; - } - - ProfileProvider.AddOrUpdate(profile); - - LoadProfiles(); - } - - public async void RenameProfile() - { - if (SelectedProfile == null) - return; - - var oldName = SelectedProfile.Name; - var name = await _dialogService.ShowInputDialog("Rename profile", "Please enter a unique new profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) - return; - - SelectedProfile.Name = name; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(SelectedProfile)) - { - name = await _dialogService - .ShowInputDialog("Name already in use", "Please enter a unique new profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) - { - SelectedProfile.Name = oldName; - return; - } - SelectedProfile.Name = name; - } - - var profile = SelectedProfile; - ProfileProvider.RenameProfile(profile, name); - - LoadProfiles(); - } - - public async void DuplicateProfile() - { - if (SelectedProfile == null) - return; - - var newProfile = GeneralHelpers.Clone(SelectedProfile); - newProfile.Name = await _dialogService - .ShowInputDialog("Duplicate profile", "Please enter a unique profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(newProfile.Name)) - return; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(newProfile)) - { - newProfile.Name = await _dialogService - .ShowInputDialog("Name already in use", "Please enter a unique profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(newProfile.Name)) - return; - } - - newProfile.IsDefault = false; - ProfileProvider.AddOrUpdate(newProfile); - LoadProfiles(); - } - - public async void DeleteProfile() - { - if (SelectedProfile == null) - return; - - var confirm = await - _dialogService.ShowQuestionMessageBox("Delete profile", - $"Are you sure you want to delete the profile named: {SelectedProfile.Name}?\n\n" + - "This cannot be undone."); - if (!confirm.Value) - return; - - var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, _moduleModel, "Default"); - var deleteProfile = SelectedProfile; - - _moduleModel.ChangeProfile(defaultProfile); - ProfileProvider.DeleteProfile(deleteProfile); - - LoadProfiles(); - } - - public async void ImportProfile() - { - if (_deviceManager.ActiveKeyboard == null) - { - _dialogService.ShowMessageBox("Cannot import profile.", - "To import a profile, please select a keyboard in the options menu first."); - return; - } - var dialog = new OpenFileDialog {Filter = "Artemis profile (*.json)|*.json"}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - var profile = ProfileProvider.LoadProfileIfValid(dialog.FileName); - if (profile == null) - { - _dialogService.ShowErrorMessageBox("Oh noes, the profile you provided is invalid. " + - "If this keeps happening, please make an issue on GitHub and provide the profile."); - return; - } - - // Verify the game - if (profile.GameName != _moduleModel.Name) - { - _dialogService.ShowErrorMessageBox( - $"Oh oops! This profile is ment for {profile.GameName}, not {_moduleModel.Name} :c"); - return; - } - - // Verify the keyboard - var deviceManager = _deviceManager; - if (profile.KeyboardSlug != deviceManager.ActiveKeyboard.Slug) - { - var adjustKeyboard = await _dialogService.ShowQuestionMessageBox( - "Profile not inteded for this keyboard", - $"Watch out, this profile wasn't ment for this keyboard, but for the {profile.KeyboardSlug}. " + - "You can still import it but you'll probably have to do some adjusting\n\n" + - "Continue?"); - if (!adjustKeyboard.Value) - return; - - // Resize layers that are on the full keyboard width - profile.ResizeLayers(deviceManager.ActiveKeyboard); - // Put layers back into the canvas if they fell outside it - profile.FixBoundaries(deviceManager.ActiveKeyboard.KeyboardRectangle(1)); - - // Setup profile metadata to match the new keyboard - profile.KeyboardSlug = deviceManager.ActiveKeyboard.Slug; - profile.Width = deviceManager.ActiveKeyboard.Width; - profile.Height = deviceManager.ActiveKeyboard.Height; - } - - profile.IsDefault = false; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(profile)) - { - profile.Name = await _dialogService.ShowInputDialog("Rename imported profile", - "A profile with this name already exists for this game. Please enter a new name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(profile.Name)) - return; - } - - ProfileProvider.AddOrUpdate(profile); - LoadProfiles(); - } - - public void ExportProfile() - { - if (SelectedProfile == null) - return; - - var dialog = new SaveFileDialog {Filter = "Artemis profile (*.json)|*.json"}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); - } - - public void EditLua() - { - if (SelectedProfile == null) - return; - try - { - OpenEditor(); - } - catch (Exception e) - { - _dialogService.ShowMessageBox("Couldn't open LUA file", - "Please make sure you have a text editor associated with the .lua extension.\n\n" + - "Windows error message: \n" + e.Message); - } - } - private void EditorStateHandler(object sender, PropertyChangedEventArgs e) { if (e.PropertyName != "SelectedProfile") @@ -706,90 +798,6 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => ProfileSelected); } - public void SaveSelectedProfile() - { - if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) - return; - - _saving = true; - try - { - ProfileProvider.AddOrUpdate(SelectedProfile); - } - catch (Exception) - { - // ignored - } - _saving = false; - } - - #region LUA Editor - - public void OpenEditor() - { - if (SelectedProfile == null) - return; - - // Create a temp file - var fileName = Guid.NewGuid() + ".lua"; - var file = File.Create(Path.GetTempPath() + fileName); - file.Dispose(); - - // Add instructions to LUA script if it's a new file - if (string.IsNullOrEmpty(SelectedProfile.LuaScript)) - SelectedProfile.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); - File.WriteAllText(Path.GetTempPath() + fileName, SelectedProfile.LuaScript); - - // Watch the file for changes - SetupWatcher(Path.GetTempPath(), fileName); - - // Open the temp file with the default editor - System.Diagnostics.Process.Start(Path.GetTempPath() + fileName); - } - - private void SetupWatcher(string path, string fileName) - { - if (_watcher == null) - { - _watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); - _watcher.Changed += LuaFileChanged; - _watcher.EnableRaisingEvents = true; - } - - _watcher.Path = path; - _watcher.Filter = fileName; - } - - private void LuaFileChanged(object sender, FileSystemEventArgs args) - { - if (args.ChangeType != WatcherChangeTypes.Changed) - return; - - if (SelectedProfile == null) - return; - - lock (SelectedProfile) - { - using (var fs = new FileStream(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - using (var sr = new StreamReader(fs)) - { - SelectedProfile.LuaScript = sr.ReadToEnd(); - } - } - - ProfileProvider.AddOrUpdate(SelectedProfile); - _luaManager.SetupLua(SelectedProfile); - } - } - - public void Dispose() - { - SaveSelectedProfile(); - ProfileViewModel.Dispose(); - _watcher?.Dispose(); - } - #endregion } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs deleted file mode 100644 index 9487b026a..000000000 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using Artemis.Events; -using Artemis.Managers; -using Artemis.Modules.Abstract; -using Artemis.Profiles; -using Artemis.Profiles.Layers.Interfaces; -using Artemis.Profiles.Layers.Models; -using Artemis.Profiles.Layers.Types.Folder; -using Artemis.Properties; -using Artemis.Utilities; -using Caliburn.Micro; -using Castle.Components.DictionaryAdapter; -using MahApps.Metro; - -namespace Artemis.ViewModels.Profiles -{ - public class ProfileViewModel : PropertyChangedBase, IDisposable - { - private readonly DeviceManager _deviceManager; - private readonly LoopManager _loopManager; - private double _blurProgress; - private double _blurRadius; - private DateTime _downTime; - private LayerModel _draggingLayer; - private Point? _draggingLayerOffset; - private DrawingImage _keyboardPreview; - private Cursor _keyboardPreviewCursor; - private bool _resizing; - private bool _showAll; - - public ProfileViewModel(DeviceManager deviceManager, LoopManager loopManager) - { - _deviceManager = deviceManager; - _loopManager = loopManager; - - ShowAll = false; - - _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; - _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; - } - - public ModuleModel ModuleModel { get; set; } - public LayerModel SelectedLayer { get; set; } - - public ProfileModel SelectedProfile => ModuleModel?.ProfileModel; - - public DrawingImage KeyboardPreview - { - get { return _keyboardPreview; } - set - { - if (Equals(value, _keyboardPreview)) return; - _keyboardPreview = value; - NotifyOfPropertyChange(() => KeyboardPreview); - } - } - - public double BlurRadius - { - get { return _blurRadius; } - set - { - if (value.Equals(_blurRadius)) return; - _blurRadius = value; - NotifyOfPropertyChange(() => BlurRadius); - } - } - - public bool ShowAll - { - get { return _showAll; } - set - { - if (value == _showAll) return; - _showAll = value; - NotifyOfPropertyChange(() => ShowAll); - } - } - - public ImageSource KeyboardImage => ImageUtilities - .BitmapToBitmapImage(_deviceManager.ActiveKeyboard?.PreviewSettings.Image ?? Resources.none); - - private void LoopManagerOnRenderCompleted(object sender, EventArgs eventArgs) - { - // Update the glowing effect around the keyboard - if (_blurProgress > 2) - _blurProgress = 0; - _blurProgress = _blurProgress + 0.025; - BlurRadius = (Math.Sin(_blurProgress * Math.PI) + 1) * 10 + 10; - - // Besides the usual checks, also check if the ActiveKeyboard isn't the NoneKeyboard - if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null || _deviceManager.ActiveKeyboard.Slug == "none") - { - KeyboardPreview = null; - - // Setup layers for the next frame - if (ModuleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) - ModuleModel.PreviewLayers = new List(); - - return; - } - - var renderLayers = GetRenderLayers(); - // Draw the current frame to the preview - var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); - var visual = new DrawingVisual(); - using (var drawingContext = visual.RenderOpen()) - { - // Setup the DrawingVisual's size - drawingContext.PushClip(new RectangleGeometry(keyboardRect)); - drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); - - // Draw the layers - foreach (var layer in renderLayers) - { - layer.Update(null, true, false); - if (layer.LayerType.ShowInEdtor) - layer.Draw(null, drawingContext, true, false); - } - - // Get the selection color - var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; - if (accentColor == null) - { - var preview = new DrawingImage(); - preview.Freeze(); - KeyboardPreview = preview; - return; - } - - var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); - - // Draw the selection outline and resize indicator - if (SelectedLayer != null && SelectedLayer.MustDraw()) - { - var layerRect = SelectedLayer.Properties.PropertiesRect(); - // Deflate the rect so that the border is drawn on the inside - layerRect.Inflate(-0.2, -0.2); - - // Draw an outline around the selected layer - drawingContext.DrawRectangle(null, pen, layerRect); - // Draw a resize indicator in the bottom-right - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); - } - - SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, drawingContext)); - - // Remove the clip - drawingContext.Pop(); - } - var drawnPreview = new DrawingImage(visual.Drawing); - drawnPreview.Freeze(); - KeyboardPreview = drawnPreview; - - // Setup layers for the next frame - if (ModuleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) - ModuleModel.PreviewLayers = renderLayers; - } - - private void DeviceManagerOnOnKeyboardChanged(object sender, KeyboardChangedEventArgs e) - { - NotifyOfPropertyChange(() => KeyboardImage); - } - - #region Processing - - /// - /// Handler for clicking - /// - /// - public void MouseDownKeyboardPreview(MouseButtonEventArgs e) - { - if (e.LeftButton == MouseButtonState.Pressed) - _downTime = DateTime.Now; - } - - /// - /// Second handler for clicking, selects a the layer the user clicked on. - /// - /// - public void MouseUpKeyboardPreview(MouseButtonEventArgs e) - { - if (SelectedProfile == null || SelectedProfile.IsDefault) - return; - - var timeSinceDown = DateTime.Now - _downTime; - if (!(timeSinceDown.TotalMilliseconds < 500)) - return; - if (_draggingLayer != null) - return; - - var keyboard = _deviceManager.ActiveKeyboard; - var pos = e.GetPosition((Image) e.OriginalSource); - var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); - var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); - - var hoverLayer = GetLayers().Where(l => l.MustDraw()) - .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); - - if (hoverLayer != null) - SelectedLayer = hoverLayer; - } - - /// - /// Handler for resizing and moving the currently selected layer - /// - /// - public void MouseMoveKeyboardPreview(MouseEventArgs e) - { - if (SelectedProfile == null) - return; - - var pos = e.GetPosition((Image) e.OriginalSource); - var keyboard = _deviceManager.ActiveKeyboard; - var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); - var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); - var hoverLayer = GetLayers().Where(l => l.MustDraw()) - .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); - - HandleDragging(e, x, y, hoverLayer); - - if (hoverLayer == null) - { - KeyboardPreviewCursor = Cursors.Arrow; - return; - } - - // Turn the mouse pointer into a hand if hovering over an active layer - if (hoverLayer == SelectedLayer) - { - var rect = hoverLayer.Properties.PropertiesRect(1); - KeyboardPreviewCursor = - Math.Sqrt(Math.Pow(x - rect.BottomRight.X, 2) + Math.Pow(y - rect.BottomRight.Y, 2)) < 0.6 - ? Cursors.SizeNWSE - : Cursors.SizeAll; - } - else - { - KeyboardPreviewCursor = Cursors.Hand; - } - } - - public Cursor KeyboardPreviewCursor - { - get { return _keyboardPreviewCursor; } - set - { - if (Equals(value, _keyboardPreviewCursor)) return; - _keyboardPreviewCursor = value; - NotifyOfPropertyChange(() => KeyboardPreviewCursor); - } - } - - /// - /// Handles dragging the given layer - /// - /// - /// - /// - /// - private void HandleDragging(MouseEventArgs e, double x, double y, LayerModel hoverLayer) - { - // Reset the dragging state on mouse release - if (e.LeftButton == MouseButtonState.Released || - _draggingLayer != null && SelectedLayer != _draggingLayer) - { - _draggingLayerOffset = null; - _draggingLayer = null; - return; - } - - if (SelectedLayer == null || SelectedLayer.LayerType != null && !SelectedLayer.LayerType.ShowInEdtor) - return; - - // Setup the dragging state on mouse press - if (_draggingLayerOffset == null && hoverLayer != null && e.LeftButton == MouseButtonState.Pressed) - { - var layerRect = hoverLayer.Properties.PropertiesRect(1); - - _draggingLayerOffset = new Point(x - SelectedLayer.Properties.X, y - SelectedLayer.Properties.Y); - _draggingLayer = hoverLayer; - // Detect dragging if cursor is in the bottom right - _resizing = Math.Sqrt(Math.Pow(x - layerRect.BottomRight.X, 2) + - Math.Pow(y - layerRect.BottomRight.Y, 2)) < 0.6; - } - - if (_draggingLayerOffset == null || _draggingLayer == null || _draggingLayer != SelectedLayer) - return; - - var draggingProps = _draggingLayer.Properties; - // If no setup or reset was done, handle the actual dragging action - if (_resizing) - { - var newWidth = Math.Round(x - draggingProps.X); - var newHeight = Math.Round(y - draggingProps.Y); - - // Ensure the layer doesn't leave the canvas - if (newWidth < 1 || draggingProps.X + newWidth <= 0) - newWidth = draggingProps.Width; - if (newHeight < 1 || draggingProps.Y + newHeight <= 0) - newHeight = draggingProps.Height; - - draggingProps.Width = newWidth; - draggingProps.Height = newHeight; - } - else - { - var newX = Math.Round(x - _draggingLayerOffset.Value.X); - var newY = Math.Round(y - _draggingLayerOffset.Value.Y); - - // Ensure the layer doesn't leave the canvas - if (newX >= SelectedProfile.Width || newX + draggingProps.Width <= 0) - newX = draggingProps.X; - if (newY >= SelectedProfile.Height || newY + draggingProps.Height <= 0) - newY = draggingProps.Y; - - draggingProps.X = newX; - draggingProps.Y = newY; - } - } - - public List GetRenderLayers() - { - // Get the layers that must be drawn - List drawLayers; - if (ShowAll) - return SelectedProfile.GetRenderLayers(null, false, true); - - if (SelectedLayer == null || !SelectedLayer.Enabled) - return new EditableList(); - - if (SelectedLayer.LayerType is FolderType) - drawLayers = SelectedLayer.GetRenderLayers(null, false, true); - else - drawLayers = new List {SelectedLayer}; - - return drawLayers; - } - - - private List GetLayers() - { - if (ShowAll) - return SelectedProfile.GetLayers(); - if (SelectedLayer == null) - return new List(); - - lock (SelectedLayer) - { - // Get the layers that must be drawn - if (SelectedLayer.LayerType is FolderType) - return SelectedLayer.GetLayers().ToList(); - return new List {SelectedLayer}; - } - } - - public void Dispose() - { - Disposed = true; - _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; - _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; - } - - public bool Disposed { get; set; } - - #endregion - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Views/Profiles/LayerEditorView.xaml b/Artemis/Artemis/Views/LayerEditorView.xaml similarity index 99% rename from Artemis/Artemis/Views/Profiles/LayerEditorView.xaml rename to Artemis/Artemis/Views/LayerEditorView.xaml index dabe59270..8c7adca13 100644 --- a/Artemis/Artemis/Views/Profiles/LayerEditorView.xaml +++ b/Artemis/Artemis/Views/LayerEditorView.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for LayerEditorView.xaml diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml b/Artemis/Artemis/Views/ProfileEditorView.xaml similarity index 93% rename from Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml rename to Artemis/Artemis/Views/ProfileEditorView.xaml index 4a3c9e8b7..7c138d830 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml +++ b/Artemis/Artemis/Views/ProfileEditorView.xaml @@ -1,288 +1,297 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs b/Artemis/Artemis/Views/ProfileEditorView.xaml.cs similarity index 89% rename from Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs rename to Artemis/Artemis/Views/ProfileEditorView.xaml.cs index b2abd5ee2..35d68c535 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs +++ b/Artemis/Artemis/Views/ProfileEditorView.xaml.cs @@ -1,6 +1,6 @@ using System.Windows.Controls; -namespace Artemis.Views.Profiles +namespace Artemis.Views { /// /// Interaction logic for ProfileEditorView.xaml