From 72d606f40d45aacf33f006ead98955676de9b669 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 24 Jul 2020 23:04:21 +0200 Subject: [PATCH] Profile editor - Redesigned adding/removing timeline segments General module - Added some basic window information to the data model Profiles - Don't render when opacity is 0 Profile editor - Render non-selected layers in their main segment Profile editor - Condition editor fixes --- src/Artemis.Core/Constants.cs | 5 +- .../Events/PropertyGroupUpdatingEventArgs.cs | 20 -- .../Conditions/DisplayConditionPredicate.cs | 4 +- src/Artemis.Core/Models/Profile/Folder.cs | 77 ++++++-- src/Artemis.Core/Models/Profile/Layer.cs | 74 ++++++-- .../LayerProperties/BaseLayerProperty.cs | 5 - .../Profile/LayerProperties/LayerProperty.cs | 25 +-- .../Models/Profile/LayerPropertyGroup.cs | 24 +-- .../Models/Profile/RenderProfileElement.cs | 12 +- .../Attributes/DataModelProperty.cs | 5 + src/Artemis.Core/Services/CoreService.cs | 10 +- .../Services/Interfaces/ICoreService.cs | 6 + .../Controls/DraggableFloat.xaml.cs | 2 +- .../DataModelListPropertiesViewModel.cs | 2 +- .../Shared/DataModelListViewModel.cs | 3 + .../Shared/DataModelPropertiesViewModel.cs | 17 +- .../Shared/DataModelPropertyViewModel.cs | 3 + .../Shared/DataModelVisualizationViewModel.cs | 70 ++++++- .../Services/DataModelVisualizationService.cs | 3 + .../Interfaces/IProfileEditorService.cs | 12 +- .../Services/ProfileEditorService.cs | 21 +-- .../Converters/InverseBooleanConverter.cs | 8 +- .../DisplayConditionGroupView.xaml | 28 ++- .../DisplayConditionGroupViewModel.cs | 14 ++ .../DisplayConditionPredicateView.xaml | 20 +- .../DisplayConditionPredicateViewModel.cs | 33 +++- .../DisplayConditionsView.xaml | 64 ++++--- .../DisplayConditionsViewModel.cs | 21 +++ .../LayerProperties/LayerPropertiesView.xaml | 171 ++++++++++++------ .../LayerPropertiesViewModel.cs | 90 ++++++--- .../Debug/Tabs/DataModelDebugView.xaml | 9 +- src/Artemis.UI/Screens/TrayViewModel.cs | 21 ++- .../FilterEffectProvider.cs | 1 + .../OpacityEffect.cs | 47 +++++ .../{ => DataModel}/GeneralDataModel.cs | 20 +- .../DataModel/Windows/WindowDataModel.cs | 26 +++ .../DataModel/Windows/WindowsDataModel.cs | 12 ++ .../GeneralModule.cs | 61 ++++++- .../Utilities/WindowMonitor.cs | 46 +++++ 39 files changed, 831 insertions(+), 261 deletions(-) delete mode 100644 src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs create mode 100644 src/Plugins/Artemis.Plugins.LayerEffects.Filter/OpacityEffect.cs rename src/Plugins/Artemis.Plugins.Modules.General/{ => DataModel}/GeneralDataModel.cs (76%) create mode 100644 src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs create mode 100644 src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowsDataModel.cs create mode 100644 src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowMonitor.cs diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 67ac8df30..3bdc41647 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Windows.Interop; using Artemis.Core.Plugins.Models; namespace Artemis.Core @@ -67,7 +68,9 @@ namespace Artemis.Core { typeof(float), typeof(double), - typeof(decimal) + typeof(decimal) }; + + public static IntPtr MainWindowHandle { get; internal set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs b/src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs deleted file mode 100644 index cb6b81529..000000000 --- a/src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Artemis.Core.Events -{ - public class PropertyGroupUpdatingEventArgs : EventArgs - { - public PropertyGroupUpdatingEventArgs(double deltaTime) - { - DeltaTime = deltaTime; - } - - public PropertyGroupUpdatingEventArgs(TimeSpan overrideTime) - { - OverrideTime = overrideTime; - } - - public double DeltaTime { get; } - public TimeSpan OverrideTime { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 04a1af178..7da7e87ca 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -159,7 +159,7 @@ namespace Artemis.Core.Models.Profile.Conditions if (DisplayConditionPredicateEntity.LeftDataModelGuid != null) { var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.LeftDataModelGuid.Value); - if (dataModel != null) + if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.LeftPropertyPath)) UpdateLeftSide(dataModel, DisplayConditionPredicateEntity.LeftPropertyPath); } @@ -175,7 +175,7 @@ namespace Artemis.Core.Models.Profile.Conditions if (DisplayConditionPredicateEntity.RightDataModelGuid != null) { var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.RightDataModelGuid.Value); - if (dataModel != null) + if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.RightPropertyPath)) UpdateRightSide(dataModel, DisplayConditionPredicateEntity.RightPropertyPath); } // Right side static diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 1cc4688fa..370a72196 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; @@ -69,8 +68,15 @@ namespace Artemis.Core.Models.Profile UpdateDisplayCondition(); + // Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back + // to it's start + var timelineDeltaTime = UpdateTimeline(deltaTime); + foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.Update(deltaTime); + { + baseLayerEffect.BaseProperties?.Update(); + baseLayerEffect.Update(timelineDeltaTime); + } // Iterate the children in reverse because that's how they must be rendered too for (var index = Children.Count - 1; index > -1; index--) @@ -80,9 +86,49 @@ namespace Artemis.Core.Models.Profile } } + public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment) + { + if (!Enabled) + return; + + var beginTime = TimelinePosition; + + if (stickToMainSegment) + { + if (!RepeatMainSegment) + { + var position = timeOverride + StartSegmentLength; + if (position > StartSegmentLength + EndSegmentLength) + TimelinePosition = StartSegmentLength + EndSegmentLength; + } + else + { + var progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds; + if (progress > 0) + TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength; + else + TimelinePosition = StartSegmentLength; + } + } + else + TimelinePosition = timeOverride; + + var delta = (TimelinePosition - beginTime).TotalSeconds; + + foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + { + baseLayerEffect.BaseProperties?.Update(); + baseLayerEffect.Update(delta); + } + } + public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { - if (!Enabled || Path == null || !Children.Any(c => c.Enabled)) + if (Path == null || !Enabled || !Children.Any(c => c.Enabled)) + return; + + // No need to render if at the end of the timeline + if (TimelinePosition > TimelineLength) return; if (_folderBitmap == null) @@ -103,6 +149,10 @@ namespace Artemis.Core.Models.Profile foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) baseLayerEffect.PreProcess(folderCanvas, _folderBitmap.Info, folderPath, folderPaint); + // No point rendering if the alpha was set to zero by one of the effects + if (folderPaint.Color.Alpha == 0) + return; + // Iterate the children in reverse because the first layer must be rendered last to end up on top for (var index = Children.Count - 1; index > -1; index--) { @@ -176,6 +226,7 @@ namespace Artemis.Core.Models.Profile OnRenderPropertiesUpdated(); } + internal override void ApplyToEntity() { FolderEntity.Id = EntityId; @@ -196,6 +247,16 @@ namespace Artemis.Core.Models.Profile DisplayConditionGroup?.ApplyToEntity(); } + internal void Deactivate() + { + _folderBitmap?.Dispose(); + _folderBitmap = null; + + var layerEffects = new List(LayerEffects); + foreach (var baseLayerEffect in layerEffects) + DeactivateLayerEffect(baseLayerEffect); + } + #region Events public event EventHandler RenderPropertiesUpdated; @@ -206,15 +267,5 @@ namespace Artemis.Core.Models.Profile } #endregion - - internal void Deactivate() - { - _folderBitmap?.Dispose(); - _folderBitmap = null; - - var layerEffects = new List(LayerEffects); - foreach (var baseLayerEffect in layerEffects) - DeactivateLayerEffect(baseLayerEffect); - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 7bdc53f35..f5be0698f 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -117,7 +117,7 @@ namespace Artemis.Core.Models.Profile get => _layerBrush; internal set => SetAndNotify(ref _layerBrush, value); } - + public override string ToString() { return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; @@ -134,7 +134,7 @@ namespace Artemis.Core.Models.Profile keyframes.AddRange(baseLayerProperty.BaseKeyframes); foreach (var baseLayerProperty in LayerBrush.BaseProperties.GetAllLayerProperties()) keyframes.AddRange(baseLayerProperty.BaseKeyframes); - + return keyframes; } @@ -214,34 +214,66 @@ namespace Artemis.Core.Models.Profile /// public override void Update(double deltaTime) { - if (!Enabled) - return; - - if (LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized) + if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized) return; + // Ensure the layer must still be displayed UpdateDisplayCondition(); - deltaTime = UpdateTimeline(deltaTime); - General.Update(deltaTime); - Transform.Update(deltaTime); - LayerBrush.BaseProperties?.Update(deltaTime); - LayerBrush.Update(deltaTime); + // TODO: No point updating further than this if the layer is not going to be rendered + + // Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back + // to it's start + var timelineDeltaTime = UpdateTimeline(deltaTime); + + General.Update(); + Transform.Update(); + LayerBrush.BaseProperties?.Update(); + LayerBrush.Update(timelineDeltaTime); foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) { - baseLayerEffect.BaseProperties?.Update(deltaTime); - baseLayerEffect.Update(deltaTime); + baseLayerEffect.BaseProperties?.Update(); + baseLayerEffect.Update(timelineDeltaTime); } } - public void OverrideProgress(TimeSpan timeOverride) + public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment) { - General.Override(timeOverride); - Transform.Override(timeOverride); - LayerBrush?.BaseProperties?.Override(timeOverride); - foreach (var baseLayerEffect in LayerEffects) - baseLayerEffect.BaseProperties?.Override(timeOverride); + if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized) + return; + + var beginTime = TimelinePosition; + + if (stickToMainSegment) + { + if (!RepeatMainSegment) + { + var position = timeOverride + StartSegmentLength; + if (position > StartSegmentLength + EndSegmentLength) + TimelinePosition = StartSegmentLength + EndSegmentLength; + } + else + { + var progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds; + TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength; + } + } + else + TimelinePosition = timeOverride; + + var delta = (TimelinePosition - beginTime).TotalSeconds; + + General.Update(); + Transform.Update(); + LayerBrush.BaseProperties?.Update(); + LayerBrush.Update(delta); + + foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + { + baseLayerEffect.BaseProperties?.Update(); + baseLayerEffect.Update(delta); + } } /// @@ -280,6 +312,10 @@ namespace Artemis.Core.Models.Profile foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) baseLayerEffect.PreProcess(layerCanvas, _layerBitmap.Info, layerPath, layerPaint); + // No point rendering if the alpha was set to zero by one of the effects + if (layerPaint.Color.Alpha == 0) + return; + layerCanvas.ClipPath(layerPath); if (!LayerBrush.SupportsTransformation) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 7051cce17..111b774ef 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -65,11 +65,6 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public bool IsLoadedFromStorage { get; internal set; } - /// - /// Gets the total progress on the timeline - /// - public TimeSpan TimelineProgress { get; internal set; } - /// /// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 8595a084a..2bc74e86b 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -101,7 +101,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties currentKeyframe.Value = value; // Update the property so that the new keyframe is reflected on the current value - Update(0); + Update(); } } @@ -184,17 +184,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties } /// - /// Updates the property, moving the timeline forwards by the provided + /// Updates the property, applying keyframes to the current value /// - /// The amount of time to move the timeline forwards - internal void Update(double deltaTime) + internal void Update() { - TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime)); if (!KeyframesSupported || !KeyframesEnabled) return; // The current keyframe is the last keyframe before the current time - CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); + CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= ProfileElement.TimelinePosition); // Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current var nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1; NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null; @@ -208,7 +206,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties else { var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; - var keyframeProgress = (float)((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); + var keyframeProgress = (float)((ProfileElement.TimelinePosition - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); var keyframeProgressEased = (float)Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); UpdateCurrentValue(keyframeProgress, keyframeProgressEased); } @@ -216,16 +214,6 @@ namespace Artemis.Core.Models.Profile.LayerProperties OnUpdated(); } - /// - /// Overrides the timeline progress to match the provided - /// - /// The new progress to set the layer property timeline to. - internal void OverrideProgress(TimeSpan overrideTime) - { - TimelineProgress = TimeSpan.Zero; - Update(overrideTime.TotalSeconds); - } - /// /// Sorts the keyframes in ascending order by position /// @@ -242,8 +230,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties PropertyEntity = entity; LayerPropertyGroup = layerPropertyGroup; - LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime); - LayerPropertyGroup.PropertyGroupOverriding += (sender, args) => OverrideProgress(args.OverrideTime); + LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(); try { diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 44d4503b3..2640a9c60 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -223,19 +223,13 @@ namespace Artemis.Core.Models.Profile } } - internal void Update(double deltaTime) + internal void Update() { // Since at this point we don't know what properties the group has without using reflection, // let properties subscribe to the update event and update themselves - OnPropertyGroupUpdating(new PropertyGroupUpdatingEventArgs(deltaTime)); + OnPropertyGroupUpdating(); } - - internal void Override(TimeSpan overrideTime) - { - // Same as above, but now the progress is overridden - OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime)); - } - + private void InitializeProperty(RenderProfileElement profileElement, string path, BaseLayerProperty instance) { Guid pluginGuid; @@ -260,8 +254,7 @@ namespace Artemis.Core.Models.Profile #region Events - internal event EventHandler PropertyGroupUpdating; - internal event EventHandler PropertyGroupOverriding; + internal event EventHandler PropertyGroupUpdating; public event EventHandler PropertyGroupInitialized; /// @@ -269,14 +262,9 @@ namespace Artemis.Core.Models.Profile /// public event EventHandler VisibilityChanged; - internal virtual void OnPropertyGroupUpdating(PropertyGroupUpdatingEventArgs e) + protected virtual void OnPropertyGroupUpdating() { - PropertyGroupUpdating?.Invoke(this, e); - } - - protected virtual void OnPropertyGroupOverriding(PropertyGroupUpdatingEventArgs e) - { - PropertyGroupOverriding?.Invoke(this, e); + PropertyGroupUpdating?.Invoke(this, EventArgs.Empty); } protected virtual void OnVisibilityChanged() diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index b5e34b35e..4156086be 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -164,7 +164,7 @@ namespace Artemis.Core.Models.Profile public TimeSpan TimelinePosition { get => _timelinePosition; - private set => SetAndNotify(ref _timelinePosition, value); + protected set => SetAndNotify(ref _timelinePosition, value); } /// @@ -212,13 +212,19 @@ namespace Artemis.Core.Models.Profile // Skip to the last segment if conditions are no longer met if (!AlwaysFinishTimeline && TimelinePosition < mainSegmentEnd) TimelinePosition = mainSegmentEnd; - else if (TimelinePosition >= TimelineLength) - TimelinePosition = TimelineLength; } return (TimelinePosition - oldPosition).TotalSeconds; } + + /// + /// Overrides the progress of the element + /// + /// + /// + public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment); + #endregion #region Effects diff --git a/src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs b/src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs index bccbdea3b..0f9bf18f0 100644 --- a/src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs +++ b/src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs @@ -34,5 +34,10 @@ namespace Artemis.Core.Plugins.Abstract.DataModels.Attributes /// Gets or sets an optional minimum value, this value is not enforced but used for percentage calculations. /// public object MinValue { get; set; } + + /// + /// Gets or sets whether this property resets the max depth of the data model, defaults to true + /// + public bool ResetsDepth { get; set; } = true; } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 0b72778af..f70ddbf13 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -47,7 +47,7 @@ namespace Artemis.Core.Services _profileService = profileService; _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); _frameStopWatch = new Stopwatch(); - + UpdatePluginCache(); ConfigureJsonConvert(); @@ -96,6 +96,12 @@ namespace Artemis.Core.Services OnInitialized(); } + + public void SetMainWindowHandle(IntPtr handle) + { + Constants.MainWindowHandle = handle; + } + protected virtual void OnFrameRendering(FrameRenderingEventArgs e) { FrameRendering?.Invoke(this, e); @@ -109,7 +115,7 @@ namespace Artemis.Core.Services private void UpdatePluginCache() { _modules = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); - _dataModelExpansions = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); + _dataModelExpansions = _pluginService.GetPluginsOfType().Where(p => p.Enabled).ToList(); } private void ConfigureJsonConvert() diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index 2f2283f35..b16b39904 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -50,5 +50,11 @@ namespace Artemis.Core.Services.Interfaces /// Occurs whenever a frame is finished rendering and processed by RGB.NET /// event EventHandler FrameRendered; + + /// + /// To be called by the UI to setup the main window handle + /// + /// + void SetMainWindowHandle(IntPtr handle); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs index f1bd0b456..27af9c662 100644 --- a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs @@ -167,7 +167,7 @@ namespace Artemis.UI.Shared.Controls private void Input_PreviewTextInput(object sender, TextCompositionEventArgs e) { var seperator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - var regex = new Regex("^[" + seperator + "][0-9]+$|^[0-9]*[" + seperator + "]{0,1}[0-9]*$"); + var regex = new Regex("^[" + seperator + "][-|0-9]+$|^-?[0-9]*[" + seperator + "]{0,1}[0-9]*$"); e.Handled = !regex.IsMatch(e.Text); } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs index 4af4f9b0d..e41083e86 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs @@ -58,7 +58,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared foreach (var propertyInfo in ListType.GetProperties()) { - var child = CreateChild(dataModelVisualizationService, propertyInfo); + var child = CreateChild(dataModelVisualizationService, propertyInfo, GetChildDepth()); if (child != null) Children.Add(child); } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs index 3cd9a68f6..1ef1f51ba 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs @@ -35,6 +35,9 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared public override void Update(IDataModelVisualizationService dataModelVisualizationService) { + if (Parent != null && !Parent.IsVisualizationExpanded) + return; + List = GetCurrentValue() as IList; if (List == null) return; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs index fcd487c6d..78c3aaadc 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs @@ -10,10 +10,16 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared internal DataModelPropertiesViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, PropertyInfo propertyInfo) : base(dataModel, parent, propertyInfo) { } - + public override void Update(IDataModelVisualizationService dataModelVisualizationService) { + // Always populate properties PopulateProperties(dataModelVisualizationService); + + // Only update children if the parent is expanded + if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel) + return; + foreach (var dataModelVisualizationViewModel in Children) dataModelVisualizationViewModel.Update(dataModelVisualizationService); } @@ -22,7 +28,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared { return Parent.IsRootViewModel ? DataModel : base.GetCurrentValue(); } - + private void PopulateProperties(IDataModelVisualizationService dataModelVisualizationService) { if (Children.Any()) @@ -31,10 +37,15 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared var modelType = Parent.IsRootViewModel ? DataModel.GetType() : PropertyInfo.PropertyType; foreach (var propertyInfo in modelType.GetProperties()) { - var child = CreateChild(dataModelVisualizationService, propertyInfo); + var child = CreateChild(dataModelVisualizationService, propertyInfo, GetChildDepth()); if (child != null) Children.Add(child); } } + + protected int GetChildDepth() + { + return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1; + } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs index 5ec5c3683..b99174856 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs @@ -49,6 +49,9 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared public override void Update(IDataModelVisualizationService dataModelVisualizationService) { + if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel) + return; + if (DisplayViewModel == null && dataModelVisualizationService.RegisteredDataModelDisplays.Any(d => d.SupportedType == PropertyInfo.PropertyType)) dataModelVisualizationService.GetDataModelDisplayViewModel(PropertyInfo.PropertyType); diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index c710a86b1..287ee237f 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -15,9 +15,11 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared { public abstract class DataModelVisualizationViewModel : PropertyChangedBase { + private const int MaxDepth = 4; private BindableCollection _children; private DataModel _dataModel; private bool _isMatchingFilteredTypes; + private bool _isVisualizationExpanded; private DataModelVisualizationViewModel _parent; private DataModelPropertyAttribute _propertyDescription; private PropertyInfo _propertyInfo; @@ -37,6 +39,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared } public bool IsRootViewModel { get; } + public int Depth { get; set; } public DataModel DataModel { @@ -74,6 +77,16 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared set => SetAndNotify(ref _isMatchingFilteredTypes, value); } + public bool IsVisualizationExpanded + { + get => _isVisualizationExpanded; + set + { + if (!SetAndNotify(ref _isVisualizationExpanded, value)) return; + RequestUpdate(); + } + } + public string PropertyPath { get @@ -104,11 +117,26 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared } } + /// + /// Updates the datamodel and if in an parent, any children + /// + /// public abstract void Update(IDataModelVisualizationService dataModelVisualizationService); public virtual object GetCurrentValue() { - return Parent == null ? null : PropertyInfo.GetValue(Parent.GetCurrentValue()); + try + { + if (PropertyInfo.GetGetMethod() == null) + return null; + + return Parent == null ? null : PropertyInfo.GetValue(Parent.GetCurrentValue()); + } + catch (Exception) + { + // ignored, who knows what kind of shit can go wrong here... + return null; + } } public void ApplyTypeFilter(bool looseMatch, params Type[] filteredTypes) @@ -157,6 +185,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared { if (predicate.LeftDataModel == null || predicate.LeftPropertyPath == null) return null; + return GetChildByPath(predicate.LeftDataModel.PluginInfo.Guid, predicate.LeftPropertyPath); } @@ -167,6 +196,14 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared public DataModelVisualizationViewModel GetChildByPath(Guid dataModelGuid, string propertyPath) { + // Ensure children are populated by requesting an update + if (!IsVisualizationExpanded) + { + IsVisualizationExpanded = true; + RequestUpdate(); + IsVisualizationExpanded = false; + } + var path = propertyPath.Split("."); var currentPart = path.First(); @@ -187,8 +224,10 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared } } - protected DataModelVisualizationViewModel CreateChild(IDataModelVisualizationService dataModelVisualizationService, PropertyInfo propertyInfo) + protected DataModelVisualizationViewModel CreateChild(IDataModelVisualizationService dataModelVisualizationService, PropertyInfo propertyInfo, int depth) { + if (depth > MaxDepth) + return null; // Skip properties decorated with DataModelIgnore if (Attribute.IsDefined(propertyInfo, typeof(DataModelIgnoreAttribute))) return null; @@ -196,19 +235,36 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared // If a display VM was found, prefer to use that in any case var typeViewModel = dataModelVisualizationService.GetDataModelDisplayViewModel(propertyInfo.PropertyType); if (typeViewModel != null) - return new DataModelPropertyViewModel(DataModel, this, propertyInfo) {DisplayViewModel = typeViewModel}; + return new DataModelPropertyViewModel(DataModel, this, propertyInfo) {DisplayViewModel = typeViewModel, Depth = depth}; // For primitives, create a property view model, it may be null that is fine if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType == typeof(string)) - return new DataModelPropertyViewModel(DataModel, this, propertyInfo); + return new DataModelPropertyViewModel(DataModel, this, propertyInfo) {Depth = depth}; if (typeof(IList).IsAssignableFrom(propertyInfo.PropertyType)) - return new DataModelListViewModel(DataModel, this, propertyInfo); + return new DataModelListViewModel(DataModel, this, propertyInfo) {Depth = depth}; // For other value types create a child view model if (propertyInfo.PropertyType.IsClass || propertyInfo.PropertyType.IsStruct()) - return new DataModelPropertiesViewModel(DataModel, this, propertyInfo); + return new DataModelPropertiesViewModel(DataModel, this, propertyInfo) {Depth = depth}; return null; } + #region Events + + public event EventHandler UpdateRequested; + + protected virtual void OnUpdateRequested() + { + UpdateRequested?.Invoke(this, EventArgs.Empty); + } + + #endregion + + private void RequestUpdate() + { + Parent?.RequestUpdate(); + OnUpdateRequested(); + } + private void GetDescription() { // If this is the first child of a root view model, use the data model description @@ -218,7 +274,7 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared else if (PropertyInfo != null) { PropertyDescription = (DataModelPropertyAttribute) Attribute.GetCustomAttribute(PropertyInfo, typeof(DataModelPropertyAttribute)) ?? - new DataModelPropertyAttribute {Name = PropertyInfo.Name.Humanize()}; + new DataModelPropertyAttribute {Name = PropertyInfo.Name.Humanize(), ResetsDepth = false}; } else throw new ArtemisSharedUIException("Failed to get property description because plugin info is null but the parent has a datamodel"); diff --git a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs index 6eadcd763..3c20dad59 100644 --- a/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelVisualizationService.cs @@ -42,6 +42,7 @@ namespace Artemis.UI.Shared.Services // Update to populate children viewModel.Update(this); + viewModel.UpdateRequested += (sender, args) => viewModel.Update(this); return viewModel; } @@ -56,6 +57,7 @@ namespace Artemis.UI.Shared.Services // Update to populate children viewModel.Update(this); + viewModel.UpdateRequested += (sender, args) => viewModel.Update(this); return viewModel; } @@ -185,6 +187,7 @@ namespace Artemis.UI.Shared.Services if (initialValue == null) initialValue = Activator.CreateInstance(registration.SupportedType); } + // This assumes the type can be converted, that has been checked when the VM was created if (initialValue != null && initialValue.GetType() != registration.SupportedType) initialValue = Convert.ChangeType(initialValue, registration.SupportedType); diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index c89aed716..93ad6b336 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; using Artemis.UI.Shared.Events; @@ -53,7 +54,7 @@ namespace Artemis.UI.Shared.Services.Interfaces /// Occurs when the current editor time is changed /// event EventHandler CurrentTimeChanged; - + /// /// Occurs when the pixels per second (zoom level) is changed /// @@ -76,11 +77,16 @@ namespace Artemis.UI.Shared.Services.Interfaces void RemovePropertyInput(PropertyInputRegistration registration); /// - /// Snaps the given time to the closest relevant element in the timeline, this can be the cursor, a keyframe or a segment end. + /// Snaps the given time to the closest relevant element in the timeline, this can be the cursor, a keyframe or a + /// segment end. /// /// /// How close the time must be to snap + /// Enable snapping to timeline segments + /// Enable snapping to the current time of the editor + /// Enable snapping to visible keyframes + /// A keyframe to exclude during keyframe snapping /// - TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes); + TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes, BaseLayerPropertyKeyframe excludedKeyframe = null); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 2920e79cb..1d09730f5 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -113,20 +113,11 @@ namespace Artemis.UI.Shared.Services if (SelectedProfile == null) return; - var delta = CurrentTime - _lastUpdateTime; - foreach (var folder in SelectedProfile.GetAllFolders()) - { - foreach (var baseLayerEffect in folder.LayerEffects) - baseLayerEffect.Update(delta.TotalSeconds); - } - - foreach (var layer in SelectedProfile.GetAllLayers()) - { - layer.OverrideProgress(CurrentTime); - layer.LayerBrush?.Update(delta.TotalSeconds); - foreach (var baseLayerEffect in layer.LayerEffects) - baseLayerEffect.Update(delta.TotalSeconds); - } + // Stick to the main segment for any element that is not currently selected + foreach (var folder in SelectedProfile.GetAllFolders()) + folder.OverrideProgress(CurrentTime, folder != SelectedProfileElement); + foreach (var layer in SelectedProfile.GetAllLayers()) + layer.OverrideProgress(CurrentTime, layer != SelectedProfileElement); _lastUpdateTime = CurrentTime; OnProfilePreviewUpdated(); @@ -205,7 +196,7 @@ namespace Artemis.UI.Shared.Services } } - public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes) + public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes, BaseLayerPropertyKeyframe excludedKeyframe = null) { if (snapToSegments) { diff --git a/src/Artemis.UI/Converters/InverseBooleanConverter.cs b/src/Artemis.UI/Converters/InverseBooleanConverter.cs index c183dcf51..2052a727b 100644 --- a/src/Artemis.UI/Converters/InverseBooleanConverter.cs +++ b/src/Artemis.UI/Converters/InverseBooleanConverter.cs @@ -12,10 +12,12 @@ namespace Artemis.UI.Converters public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (targetType != typeof(bool)) - throw new InvalidOperationException("The target must be a boolean"); + if (targetType == typeof(bool)) + return !(bool) value; + if (targetType == typeof(bool?)) + return !(bool?) value; - return !(bool) value; + throw new InvalidOperationException("The target must be a boolean"); } public object ConvertBack(object value, Type targetType, object parameter, diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml index dc181b20a..c1c22c6be 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml @@ -10,9 +10,33 @@ d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance Type=local:DisplayConditionGroupViewModel, IsDesignTimeCreatable=False}"> - + + + + + + + + - + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs index 8198d49a7..3d64c4334 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs @@ -1,9 +1,11 @@ using System; using System.Linq; +using System.Threading.Tasks; using Artemis.Core.Models.Profile.Conditions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.Abstract; using Humanizer; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { @@ -11,11 +13,17 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private bool _isRootGroup; + private bool _isInitialized; public DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent, IDisplayConditionsVmFactory displayConditionsVmFactory) : base( displayConditionGroup, parent) { _displayConditionsVmFactory = displayConditionsVmFactory; + Execute.PostToUIThread(async () => + { + await Task.Delay(50); + IsInitialized = true; + }); } public DisplayConditionGroup DisplayConditionGroup => (DisplayConditionGroup) Model; @@ -26,6 +34,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions set => SetAndNotify(ref _isRootGroup, value); } + public bool IsInitialized + { + get => _isInitialized; + set => SetAndNotify(ref _isInitialized, value); + } + public string SelectedBooleanOperator => DisplayConditionGroup.BooleanOperator.Humanize(); public void SelectBooleanOperator(string type) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml index c0b04af58..9b451960c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml @@ -20,11 +20,28 @@ + - + @@ -61,6 +78,7 @@ + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index 27a665a85..a2f8034b2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -28,12 +28,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions private DataModelPropertiesViewModel _rightSideDataModel; private DataModelInputViewModel _rightSideInputViewModel; private int _rightSideTransitionIndex; + private object _rightStaticValue; private DataModelVisualizationViewModel _selectedLeftSideProperty; private DisplayConditionOperator _selectedOperator; private DataModelVisualizationViewModel _selectedRightSideProperty; private List _supportedInputTypes; - private object _rightStaticValue; + private bool _isInitialized; public DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent, IProfileEditorService profileEditorService, IDataModelVisualizationService dataModelVisualizationService, IDataModelService dataModelService, IEventAggregator eventAggregator) @@ -56,7 +57,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public bool ShowRightSidePropertySelection => DisplayConditionPredicate.PredicateType == PredicateType.Dynamic; public bool CanActivateRightSideInputViewModel => SelectedLeftSideProperty?.PropertyInfo != null; - public bool IsInitialized { get; private set; } + public bool IsInitialized + { + get => _isInitialized; + private set => SetAndNotify(ref _isInitialized, value); + } public DataModelPropertiesViewModel LeftSideDataModel { @@ -156,6 +161,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions _supportedInputTypes = editors.Select(e => e.SupportedType).ToList(); _supportedInputTypes.AddRange(editors.Where(e => e.CompatibleConversionTypes != null).SelectMany(e => e.CompatibleConversionTypes)); + + LeftSideDataModel.UpdateRequested += LeftDataModelUpdateRequested; + RightSideDataModel.UpdateRequested += RightDataModelUpdateRequested; + IsInitialized = true; Update(); } @@ -192,6 +201,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public void ApplyLeftSide() { DisplayConditionPredicate.UpdateLeftSide(SelectedLeftSideProperty.DataModel, SelectedLeftSideProperty.PropertyPath); + _profileEditorService.UpdateSelectedProfileElement(); + SelectedOperator = DisplayConditionPredicate.Operator; Update(); } @@ -199,6 +210,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public void ApplyRightSideDynamic() { DisplayConditionPredicate.UpdateRightSide(SelectedRightSideProperty.DataModel, SelectedRightSideProperty.PropertyPath); + _profileEditorService.UpdateSelectedProfileElement(); + Update(); } @@ -207,6 +220,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions if (isSubmitted) { DisplayConditionPredicate.UpdateRightSide(value); + _profileEditorService.UpdateSelectedProfileElement(); + Update(); } @@ -219,6 +234,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions public void ApplyOperator() { DisplayConditionPredicate.UpdateOperator(SelectedOperator); + _profileEditorService.UpdateSelectedProfileElement(); + Update(); } @@ -237,6 +254,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions _eventAggregator.Subscribe(this); } + private void RightDataModelUpdateRequested(object sender, EventArgs e) + { + if (DisplayConditionPredicate.PredicateType == PredicateType.Dynamic) + SelectedRightSideProperty = LeftSideDataModel.GetChildForCondition(DisplayConditionPredicate, DisplayConditionSide.Right); + } + + private void LeftDataModelUpdateRequested(object sender, EventArgs e) + { + if (DisplayConditionPredicate.PredicateType == PredicateType.Static) + LeftSideDataModel.ApplyTypeFilter(false, _supportedInputTypes.ToArray()); + } + private void ExecuteSelectLeftProperty(object context) { if (!(context is DataModelVisualizationViewModel dataModelVisualizationViewModel)) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index b3a28bd2f..aa2ab3d81 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -1,73 +1,71 @@  - - - + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:s="https://github.com/canton7/Stylet" + x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionsView" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance {x:Type local:DisplayConditionsViewModel}}"> - - - - - - + + Display conditions + + - + - - - - - - - + + When conditions no longer met + - + + When conditions are no longer met, finish the timelines and then stop displaying. + - + + WAIT FOR FINISH + - + + When conditions are no longer met, stop displaying immediately. + - + + SKIP + - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index cbf8e7d86..16445dc45 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -9,12 +9,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { public class DisplayConditionsViewModel : ProfileEditorPanelViewModel { + private readonly IProfileEditorService _profileEditorService; private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private DisplayConditionGroupViewModel _rootGroup; private RenderProfileElement _renderProfileElement; public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory) { + _profileEditorService = profileEditorService; _displayConditionsVmFactory = displayConditionsVmFactory; profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; } @@ -31,9 +33,28 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions set => SetAndNotify(ref _renderProfileElement, value); } + public int ConditionBehaviourIndex + { + get => RenderProfileElement != null && RenderProfileElement.AlwaysFinishTimeline ? 0 : 1; + set + { + if (RenderProfileElement == null) + return; + + RenderProfileElement.AlwaysFinishTimeline = value == 0; + NotifyOfPropertyChange(nameof(ConditionBehaviourIndex)); + + _profileEditorService.UpdateSelectedProfileElement(); + } + } + + public bool ConditionBehaviourEnabled => RenderProfileElement != null; + private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e) { RenderProfileElement = e.RenderProfileElement; + NotifyOfPropertyChange(nameof(ConditionBehaviourIndex)); + NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled)); if (e.RenderProfileElement == null) { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index 1ec193a50..87a3ed132 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -8,7 +8,9 @@ xmlns:timeline="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors" - xmlns:Converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.LayerPropertiesView" + xmlns:Converters="clr-namespace:Artemis.UI.Converters" + xmlns:utilities="clr-namespace:Artemis.UI.Utilities" + x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.LayerPropertiesView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" @@ -16,6 +18,7 @@ behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True"> + + + + + + + + + + + + + + ADD SEGMENT + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index fc0f33c64..62e2df39e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -102,27 +102,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public Layer SelectedLayer => SelectedProfileElement as Layer; public Folder SelectedFolder => SelectedProfileElement as Folder; - public bool StartSegmentEnabled - { - get => SelectedProfileElement?.StartSegmentLength != TimeSpan.Zero; - set - { - SelectedProfileElement.StartSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero; - ProfileEditorService.UpdateSelectedProfileElement(); - NotifyOfPropertyChange(nameof(StartSegmentEnabled)); - } - } - - public bool EndSegmentEnabled - { - get => SelectedProfileElement?.EndSegmentLength != TimeSpan.Zero; - set - { - SelectedProfileElement.EndSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero; - ProfileEditorService.UpdateSelectedProfileElement(); - NotifyOfPropertyChange(nameof(EndSegmentEnabled)); - } - } public BindableCollection LayerPropertyGroups { @@ -586,10 +565,79 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties #region Segments + public bool StartSegmentEnabled + { + get => SelectedProfileElement?.StartSegmentLength != TimeSpan.Zero; + set + { + SelectedProfileElement.StartSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero; + ProfileEditorService.UpdateSelectedProfileElement(); + NotifyOfPropertyChange(nameof(StartSegmentEnabled)); + NotifyOfPropertyChange(nameof(CanAddSegment)); + } + } + + public bool MainSegmentEnabled + { + get => SelectedProfileElement?.MainSegmentLength != TimeSpan.Zero; + set + { + SelectedProfileElement.MainSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero; + ProfileEditorService.UpdateSelectedProfileElement(); + NotifyOfPropertyChange(nameof(MainSegmentEnabled)); + NotifyOfPropertyChange(nameof(CanAddSegment)); + } + } + + public bool EndSegmentEnabled + { + get => SelectedProfileElement?.EndSegmentLength != TimeSpan.Zero; + set + { + SelectedProfileElement.EndSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero; + ProfileEditorService.UpdateSelectedProfileElement(); + NotifyOfPropertyChange(nameof(EndSegmentEnabled)); + NotifyOfPropertyChange(nameof(CanAddSegment)); + } + } + + public bool CanAddSegment => !StartSegmentEnabled || !MainSegmentEnabled || !EndSegmentEnabled; + + public bool RepeatMainSegment + { + get => SelectedProfileElement?.RepeatMainSegment ?? false; + set + { + SelectedProfileElement.RepeatMainSegment = value; + ProfileEditorService.UpdateSelectedProfileElement(); + NotifyOfPropertyChange(nameof(RepeatMainSegment)); + } + } + private bool _draggingStartSegment; private bool _draggingMainSegment; private bool _draggingEndSegment; + public void DisableSegment(string segment) + { + if (segment == "Start") + StartSegmentEnabled = false; + else if (segment == "Main") + MainSegmentEnabled = false; + else if (segment == "End") + EndSegmentEnabled = false; + } + + public void EnableSegment(string segment) + { + if (segment == "Start") + StartSegmentEnabled = true; + else if (segment == "Main") + MainSegmentEnabled = true; + else if (segment == "End") + EndSegmentEnabled = true; + } + public void StartSegmentMouseDown(object sender, MouseButtonEventArgs e) { ((IInputElement) sender).CaptureMouse(); diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index 0dd684948..c0e7f5da0 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -54,8 +54,15 @@ - + + diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs index 0831806b0..f89c52f10 100644 --- a/src/Artemis.UI/Screens/TrayViewModel.cs +++ b/src/Artemis.UI/Screens/TrayViewModel.cs @@ -1,29 +1,36 @@ -using Artemis.Core.Services; +using System.Windows; +using System.Windows.Interop; +using Artemis.Core; +using Artemis.Core.Services; using Artemis.Core.Services.Interfaces; using Artemis.Core.Utilities; using Artemis.UI.Events; using Artemis.UI.Screens.Splash; using Artemis.UI.Shared.Controls; using Artemis.UI.Shared.Services.Interfaces; +using MaterialDesignExtensions.Controls; using Ninject; using Stylet; namespace Artemis.UI.Screens { - public class TrayViewModel : Screen + public class TrayViewModel : PropertyChangedBase, IViewAware { private readonly IEventAggregator _eventAggregator; + private readonly ICoreService _coreService; private readonly IKernel _kernel; private readonly IWindowManager _windowManager; private bool _setGradientPickerService; private SplashViewModel _splashViewModel; private bool _canShowRootViewModel; + private UIElement _view; public TrayViewModel(IKernel kernel, IWindowManager windowManager, IEventAggregator eventAggregator, ICoreService coreService, ISettingsService settingsService) { _kernel = kernel; _windowManager = windowManager; _eventAggregator = eventAggregator; + _coreService = coreService; CanShowRootViewModel = true; var autoRunning = Bootstrapper.StartupArguments.Contains("--autorun"); @@ -89,5 +96,15 @@ namespace Artemis.UI.Screens { CanShowRootViewModel = true; } + + public void AttachView(UIElement view) + { + View = view; + + var handle = new WindowInteropHelper((Window) view).EnsureHandle(); + _coreService.SetMainWindowHandle(handle); + } + + public UIElement View { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs index 050b4fcb5..83d73ac0a 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs @@ -12,6 +12,7 @@ namespace Artemis.Plugins.LayerEffects.Filter "BlurOn" ); AddLayerEffectDescriptor("Dilate", "A layer effect providing a dilation filter effect", "EyePlus"); + AddLayerEffectDescriptor("Opacity", "A layer effect letting you change the opacity of all children", "Opacity"); AddLayerEffectDescriptor("Erode", "A layer effect providing an erode filter effect", "EyeMinus"); AddLayerEffectDescriptor("Glow", "A layer effect providing a glow filter effect", "BoxShadow"); AddLayerEffectDescriptor("Gray-scale", "A layer effect providing a gray-scale filter effect", "InvertColors"); diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/OpacityEffect.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/OpacityEffect.cs new file mode 100644 index 000000000..5ef4210fa --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/OpacityEffect.cs @@ -0,0 +1,47 @@ +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Types; +using Artemis.Core.Plugins.LayerEffect.Abstract; +using SkiaSharp; + +namespace Artemis.Plugins.LayerEffects.Filter +{ + public class OpacityEffect : LayerEffect + { + public override void EnableLayerEffect() + { + } + + public override void DisableLayerEffect() + { + } + + public override void Update(double deltaTime) + { + } + + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + { + paint.Color = paint.Color.WithAlpha((byte) (Properties.Opacity.CurrentValue * 2.55f)); + } + + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + { + } + } + + public class OpacityEffectProperties : LayerPropertyGroup + { + [PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f)] + public FloatLayerProperty Opacity { get; set; } + + protected override void PopulateDefaults() + { + Opacity.DefaultValue = 100f; + } + + protected override void OnPropertiesInitialized() + { + } + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/GeneralDataModel.cs similarity index 76% rename from src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs rename to src/Plugins/Artemis.Plugins.Modules.General/DataModel/GeneralDataModel.cs index d18347777..3d7453178 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/GeneralDataModel.cs @@ -1,13 +1,27 @@ using System.Collections.Generic; -using Artemis.Core.Plugins.Abstract.DataModels; +using System.Diagnostics; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Artemis.Plugins.Modules.General.DataModel.Windows; using SkiaSharp; -namespace Artemis.Plugins.Modules.General +namespace Artemis.Plugins.Modules.General.DataModel { - public class GeneralDataModel : DataModel + public class GeneralDataModel : Core.Plugins.Abstract.DataModels.DataModel { + public TestDataModel TestDataModel { get; set; } + public WindowsDataModel Windows { get; set; } + + public GeneralDataModel() + { + TestDataModel = new TestDataModel(); + Windows = new WindowsDataModel(); + } + } + + public class TestDataModel + { + public TestDataModel() { PlayerInfo = new PlayerInfo(); IntsList = new List(); diff --git a/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs new file mode 100644 index 000000000..128295d79 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowDataModel.cs @@ -0,0 +1,26 @@ +using System.Diagnostics; +using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Artemis.Plugins.Modules.General.Utilities; + +namespace Artemis.Plugins.Modules.General.DataModel.Windows +{ + public class WindowDataModel + { + [DataModelIgnore] + public Process Process { get; } + + public WindowDataModel(Process process) + { + Process = process; + WindowTitle = process.MainWindowTitle; + ProcessName = process.ProcessName; + + // Accessing MainModule requires admin privileges, this way does not + ProgramLocation = WindowMonitor.GetProcessFilename(process); + } + + public string WindowTitle { get; set; } + public string ProcessName { get; set; } + public string ProgramLocation { get; set; } + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowsDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowsDataModel.cs new file mode 100644 index 000000000..e6b100d8b --- /dev/null +++ b/src/Plugins/Artemis.Plugins.Modules.General/DataModel/Windows/WindowsDataModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Artemis.Plugins.Modules.General.DataModel.Windows +{ + public class WindowsDataModel + { + public WindowDataModel ActiveWindow { get; set; } + public List OpenWindows { get; set; } + } +} diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs index 312cc144d..17062b515 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -1,8 +1,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Interop; +using Artemis.Core; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.ViewModels; using Artemis.Core.Plugins.Models; +using Artemis.Plugins.Modules.General.DataModel; +using Artemis.Plugins.Modules.General.DataModel.Windows; +using Artemis.Plugins.Modules.General.Utilities; using Artemis.Plugins.Modules.General.ViewModels; using SkiaSharp; @@ -13,6 +21,7 @@ namespace Artemis.Plugins.Modules.General private readonly PluginSettings _settings; private readonly Random _rand; + public GeneralModule(PluginSettings settings) { _settings = settings; @@ -26,12 +35,19 @@ namespace Artemis.Plugins.Modules.General public override void Update(double deltaTime) { - DataModel.UpdatesDividedByFour += 0.25; - DataModel.Updates += 1; - DataModel.PlayerInfo.Position = new SKPoint(_rand.Next(100), _rand.Next(100)); + DataModel.TestDataModel.UpdatesDividedByFour += 0.25; + DataModel.TestDataModel.Updates += 1; + DataModel.TestDataModel.PlayerInfo.Position = new SKPoint(_rand.Next(100), _rand.Next(100)); + DataModel.TestDataModel.PlayerInfo.Health++; + if (DataModel.TestDataModel.PlayerInfo.Health > 200) + DataModel.TestDataModel.PlayerInfo.Health = 0; + + DataModel.TestDataModel.IntsList[0] = _rand.Next(); + DataModel.TestDataModel.IntsList[2] = _rand.Next(); + + UpdateCurrentWindow(); + UpdateBackgroundWindows(); - DataModel.IntsList[0] = _rand.Next(); - DataModel.IntsList[2] = _rand.Next(); base.Update(deltaTime); } @@ -41,8 +57,8 @@ namespace Artemis.Plugins.Modules.General DisplayIcon = "AllInclusive"; ExpandsDataModel = true; - DataModel.IntsList = new List {_rand.Next(), _rand.Next(), _rand.Next()}; - DataModel.PlayerInfosList = new List {new PlayerInfo()}; + DataModel.TestDataModel.IntsList = new List {_rand.Next(), _rand.Next(), _rand.Next()}; + DataModel.TestDataModel.PlayerInfosList = new List {new PlayerInfo()}; var testSetting = _settings.GetSetting("TestSetting", DateTime.Now); } @@ -50,5 +66,36 @@ namespace Artemis.Plugins.Modules.General public override void DisablePlugin() { } + + #region Open windows + + private DateTime _lastBackgroundWindowsUpdate; + + public void UpdateCurrentWindow() + { + var processId = WindowMonitor.GetActiveProcessId(); + if (DataModel.Windows.ActiveWindow == null || DataModel.Windows.ActiveWindow.Process.Id != processId) + DataModel.Windows.ActiveWindow = new WindowDataModel(Process.GetProcessById(processId)); + } + + public void UpdateBackgroundWindows() + { + // This is kinda slow so lets not do it very often and lets do it in a task + if (DateTime.Now - _lastBackgroundWindowsUpdate < TimeSpan.FromSeconds(5)) + return; + + _lastBackgroundWindowsUpdate = DateTime.Now; + Task.Run(() => + { + // All processes with a main window handle are considered open windows + DataModel.Windows.OpenWindows = Process.GetProcesses() + .Where(p => p.MainWindowHandle != IntPtr.Zero) + .Select(p => new WindowDataModel(p)) + .Where(w => !string.IsNullOrEmpty(w.WindowTitle)) + .ToList(); + }); + } + + #endregion } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowMonitor.cs b/src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowMonitor.cs new file mode 100644 index 000000000..02a4c5377 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.Modules.General/Utilities/WindowMonitor.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Artemis.Plugins.Modules.General.Utilities +{ + public static class WindowMonitor + { + public static int GetActiveProcessId() + { + var hWnd = GetForegroundWindow(); // Get foreground window handle + GetWindowThreadProcessId(hWnd, out var processId); + return (int) processId; + } + + public static string GetProcessFilename(Process p) + { + var capacity = 2000; + var builder = new StringBuilder(capacity); + var ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id); + if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) return string.Empty; + + return builder.ToString(); + } + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("kernel32.dll")] + private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize); + + [DllImport("kernel32.dll")] + private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId); + + + [Flags] + private enum ProcessAccessFlags : uint + { + QueryLimitedInformation = 0x00001000 + } + } +} \ No newline at end of file