1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

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
This commit is contained in:
SpoinkyNL 2020-07-24 23:04:21 +02:00
parent 527fef3dc6
commit 72d606f40d
39 changed files with 831 additions and 261 deletions

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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

View File

@ -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<BaseLayerEffect>(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<BaseLayerEffect>(LayerEffects);
foreach (var baseLayerEffect in layerEffects)
DeactivateLayerEffect(baseLayerEffect);
}
}
}

View File

@ -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
/// <inheritdoc />
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);
}
}
/// <inheritdoc />
@ -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)

View File

@ -65,11 +65,6 @@ namespace Artemis.Core.Models.Profile.LayerProperties
/// </summary>
public bool IsLoadedFromStorage { get; internal set; }
/// <summary>
/// Gets the total progress on the timeline
/// </summary>
public TimeSpan TimelineProgress { get; internal set; }
/// <summary>
/// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID
/// </summary>

View File

@ -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
}
/// <summary>
/// Updates the property, moving the timeline forwards by the provided <paramref name="deltaTime" />
/// Updates the property, applying keyframes to the current value
/// </summary>
/// <param name="deltaTime">The amount of time to move the timeline forwards</param>
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();
}
/// <summary>
/// Overrides the timeline progress to match the provided <paramref name="overrideTime" />
/// </summary>
/// <param name="overrideTime">The new progress to set the layer property timeline to.</param>
internal void OverrideProgress(TimeSpan overrideTime)
{
TimelineProgress = TimeSpan.Zero;
Update(overrideTime.TotalSeconds);
}
/// <summary>
/// Sorts the keyframes in ascending order by position
/// </summary>
@ -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
{

View File

@ -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<PropertyGroupUpdatingEventArgs> PropertyGroupUpdating;
internal event EventHandler<PropertyGroupUpdatingEventArgs> PropertyGroupOverriding;
internal event EventHandler PropertyGroupUpdating;
public event EventHandler PropertyGroupInitialized;
/// <summary>
@ -269,14 +262,9 @@ namespace Artemis.Core.Models.Profile
/// </summary>
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()

View File

@ -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);
}
/// <summary>
@ -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;
}
/// <summary>
/// Overrides the progress of the element
/// </summary>
/// <param name="timeOverride"></param>
/// <param name="stickToMainSegment"></param>
public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment);
#endregion
#region Effects

View File

@ -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.
/// </summary>
public object MinValue { get; set; }
/// <summary>
/// Gets or sets whether this property resets the max depth of the data model, defaults to true
/// </summary>
public bool ResetsDepth { get; set; } = true;
}
}

View File

@ -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<Module>().Where(p => p.Enabled).ToList();
_dataModelExpansions = _pluginService.GetPluginsOfType<BaseDataModelExpansion>().Where(p => p.Enabled).ToList();
_dataModelExpansions = _pluginService.GetPluginsOfType<BaseDataModelExpansion>().Where(p => p.Enabled).ToList();
}
private void ConfigureJsonConvert()

View File

@ -50,5 +50,11 @@ namespace Artemis.Core.Services.Interfaces
/// Occurs whenever a frame is finished rendering and processed by RGB.NET
/// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered;
/// <summary>
/// To be called by the UI to setup the main window handle
/// </summary>
/// <param name="handle"></param>
void SetMainWindowHandle(IntPtr handle);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -15,9 +15,11 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
public abstract class DataModelVisualizationViewModel : PropertyChangedBase
{
private const int MaxDepth = 4;
private BindableCollection<DataModelVisualizationViewModel> _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
}
}
/// <summary>
/// Updates the datamodel and if in an parent, any children
/// </summary>
/// <param name="dataModelVisualizationService"></param>
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");

View File

@ -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);

View File

@ -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
/// </summary>
event EventHandler CurrentTimeChanged;
/// <summary>
/// Occurs when the pixels per second (zoom level) is changed
/// </summary>
@ -76,11 +77,16 @@ namespace Artemis.UI.Shared.Services.Interfaces
void RemovePropertyInput(PropertyInputRegistration registration);
/// <summary>
/// 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.
/// </summary>
/// <param name="time"></param>
/// <param name="tolerance">How close the time must be to snap</param>
/// <param name="snapToSegments">Enable snapping to timeline segments</param>
/// <param name="snapToCurrentTime">Enable snapping to the current time of the editor</param>
/// <param name="snapToKeyframes">Enable snapping to visible keyframes</param>
/// <param name="excludedKeyframe">A keyframe to exclude during keyframe snapping</param>
/// <returns></returns>
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);
}
}

View File

@ -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)
{

View File

@ -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,

View File

@ -10,9 +10,33 @@
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:DisplayConditionGroupViewModel, IsDesignTimeCreatable=False}">
<UserControl.Resources>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DisplayConditions.xaml" />
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/DisplayConditions.xaml" />
<ResourceDictionary>
<Style TargetType="Grid" x:Key="InitializingFade">
<Style.Triggers>
<DataTrigger Binding="{Binding IsInitialized}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.25" BeginTime="0:0:0.1">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid Style="{StaticResource InitializingFade}" Opacity="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />

View File

@ -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)

View File

@ -20,11 +20,28 @@
<ResourceDictionary>
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
<Style TargetType="Grid" x:Key="InitializingFade">
<Style.Triggers>
<DataTrigger Binding="{Binding IsInitialized}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.25" BeginTime="0:0:0.1">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="0 3">
<Grid Margin="0 3" Style="{StaticResource InitializingFade}" Opacity="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
@ -61,6 +78,7 @@
<Setter Property="CommandParameter" Value="{Binding}" />
<Setter Property="CommandTarget" Value="{Binding}" />
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes}" />
<Setter Property="IsSubmenuOpen" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>

View File

@ -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<Type> _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))

View File

@ -1,73 +1,71 @@
<UserControl
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"
xmlns:Converters="clr-namespace:Artemis.UI.Converters" 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}}">
<UserControl.Resources>
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
</UserControl.Resources>
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}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4"><Run Text="Display conditions"/></TextBlock>
<Separator Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4">
Display conditions
</TextBlock>
<Separator Grid.Row="1" Grid.Column="0" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<Grid Grid.Row="2" Grid.Column="0">
<ScrollViewer Margin="8 0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl s:View.Model="{Binding RootGroup}" />
</ScrollViewer>
</Grid>
<StackPanel Grid.Row="3" Grid.Column="0" Margin="13" Orientation="Horizontal" VerticalAlignment="Bottom" ToolTip="When conditions are met, only go through the entire timeline once.">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" Content="Play once" IsChecked="{Binding RenderProfileElement.RepeatMainSegment, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}"/>
</StackPanel>
<StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right">
<TextBlock><Run Text="When conditions no longer met"/></TextBlock>
<ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="{Binding CurrentTimelineIndex}" Height="20" Margin="0 5 0 0">
<StackPanel Grid.Row="3" Margin="10" HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="0 0 10 0">When conditions no longer met</TextBlock>
<ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="{Binding ConditionBehaviourIndex}" IsEnabled="{Binding ConditionBehaviourEnabled}" Height="22">
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<StackPanel>
<TextBlock><Run Text="When conditions are no longer met, finish the timelines and then stop displaying."/></TextBlock>
<TextBlock>
When conditions are no longer met, finish the timelines and then stop displaying.
</TextBlock>
</StackPanel>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="PlayArrow" Width="20" Height="20" Margin="0 -4" />
<TextBlock Margin="5 0 0 0" FontSize="11"><Run Text="WAIT FOR FINISH"/></TextBlock>
<TextBlock Margin="5 0 0 0" FontSize="11">
WAIT FOR FINISH
</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<StackPanel>
<TextBlock><Run Text="When conditions are no longer met, stop displaying immediately."/></TextBlock>
<TextBlock>
When conditions are no longer met, stop displaying immediately.
</TextBlock>
</StackPanel>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="SkipNext" Width="20" Height="20" Margin="0 -4" />
<TextBlock Margin="5 0 0 0" FontSize="11"><Run Text="SKIP"/></TextBlock>
<TextBlock Margin="5 0 0 0" FontSize="11">
SKIP
</TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
</UserControl>

View File

@ -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)
{

View File

@ -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">
<UserControl.Resources>
<Converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
<Style x:Key="SvStyle" TargetType="{x:Type ScrollViewer}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
@ -199,58 +202,78 @@
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" />
<!-- Start segment -->
<TextBlock Grid.Column="0"
TextAlignment="Center"
VerticalAlignment="Top"
Padding="0 3"
FontSize="11"
Background="{StaticResource MaterialDesignPaper}"
Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
ToolTip="This segment is played when a layer starts displaying because it's conditions are met">
Start
</TextBlock>
<Grid Grid.Column="0" VerticalAlignment="Top" Background="{StaticResource MaterialDesignPaper}"
Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock TextAlignment="Center" Padding="0 3" FontSize="12" ToolTip="This segment is played when a layer starts displaying because it's conditions are met">
Start
</TextBlock>
<Button Grid.Column="1" Style="{StaticResource MaterialDesignIconButton}" ToolTip="Disable start segment" Width="20" Height="20" Margin="0 0 6 0"
Command="{s:Action DisableSegment}" CommandParameter="Start">
<materialDesign:PackIcon Kind="Close" Height="18" Width="18" />
</Button>
</Grid>
<!-- Main segment -->
<TextBlock Grid.Column="1"
TextAlignment="Center"
VerticalAlignment="Top"
Padding="0 3"
FontSize="11"
Background="{StaticResource MaterialDesignPaper}"
ToolTip="This segment is played while a condition is met, either once or on a repeating loop">
Main
</TextBlock>
<Grid Grid.Column="1" VerticalAlignment="Top" Background="{StaticResource MaterialDesignPaper}"
Visibility="{Binding MainSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock TextAlignment="Center" Padding="0 3" FontSize="12" ToolTip="This segment is played while a condition is met, either once or on a repeating loop">
Main
</TextBlock>
<ToggleButton Grid.Column="1" Style="{StaticResource MaterialDesignFlatToggleButton}" ToolTip="Toggle main segment repeat" Width="16" Height="16"
IsChecked="{Binding RepeatMainSegment}" VerticalAlignment="Center">
<materialDesign:PackIcon Kind="Repeat" Height="12" Width="12" />
</ToggleButton>
<Button Grid.Column="2" Style="{StaticResource MaterialDesignIconButton}" ToolTip="Remove" Width="20" Height="20" Margin="5 0 8 0"
Command="{s:Action DisableSegment}" CommandParameter="Main">
<materialDesign:PackIcon Kind="Close" Height="18" Width="18" />
</Button>
</Grid>
<!-- End segment -->
<TextBlock Grid.Column="2"
TextAlignment="Center"
VerticalAlignment="Top"
Padding="0 3"
FontSize="11"
Background="{StaticResource MaterialDesignPaper}"
Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
ToolTip="This segment is played once a condition is no longer met">
End
</TextBlock>
<Grid Grid.Column="2" VerticalAlignment="Top" Background="{StaticResource MaterialDesignPaper}"
Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock TextAlignment="Center" Padding="0 3" FontSize="12" ToolTip="This segment is played once a condition is no longer met">
End
</TextBlock>
<Button Grid.Column="1" Style="{StaticResource MaterialDesignIconButton}" ToolTip="Disable end segment" Width="20" Height="20" Margin="0 0 6 0"
Command="{s:Action DisableSegment}" CommandParameter="End">
<materialDesign:PackIcon Kind="Close" Height="18" Width="18" />
</Button>
</Grid>
<!-- Segment movement display -->
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -2 -2 0" Width="5" Height="24" VerticalAlignment="Top"
HorizontalAlignment="Right" Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
HorizontalAlignment="Right" />
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -2 -2 0" Width="5" Height="24" VerticalAlignment="Top"
HorizontalAlignment="Right" Visibility="{Binding MainSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -2 -2 0" Width="5" Height="24" VerticalAlignment="Top"
HorizontalAlignment="Right" Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
<!-- Segment movement handles -->
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="20" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
MouseDown="{s:Action StartSegmentMouseDown}" MouseUp="{s:Action StartSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}"/>
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="20" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE"
MouseDown="{s:Action MainSegmentMouseDown}" MouseUp="{s:Action MainSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}"/>
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="20" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
MouseDown="{s:Action EndSegmentMouseDown}" MouseUp="{s:Action EndSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}"/>
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="25" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
MouseDown="{s:Action StartSegmentMouseDown}" MouseUp="{s:Action StartSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}" />
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="25" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding MainSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
MouseDown="{s:Action MainSegmentMouseDown}" MouseUp="{s:Action MainSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}" />
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="25" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
MouseDown="{s:Action EndSegmentMouseDown}" MouseUp="{s:Action EndSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}" />
</Grid>
</ScrollViewer>
@ -348,19 +371,59 @@
Background="{DynamicResource MaterialDesignCardBackground}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding StartSegmentEnabled}">
Enable start segment
</CheckBox>
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" Margin="10 0" IsChecked="{Binding EndSegmentEnabled}">
Enable end segment
</CheckBox>
</StackPanel>
<Button Grid.Column="0"
Margin="5 -2 0 -2"
Padding="10 0"
Height="20"
Width="110"
ToolTip="Select an effect to add"
VerticalAlignment="Center"
Visibility="{Binding PropertyTreeVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
IsEnabled="{Binding CanAddSegment}">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource MaterialDesignFlatMidBgButton}">
<Style.Triggers>
<EventTrigger RoutedEvent="Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
</ContextMenu.Resources>
<MenuItem Header="Start"
Command="{s:Action EnableSegment}"
CommandParameter="Start"
IsEnabled="{Binding Data.StartSegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
<MenuItem Header="Main"
Command="{s:Action EnableSegment}"
CommandParameter="Main"
IsEnabled="{Binding Data.MainSegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
<MenuItem Header="End"
Command="{s:Action EnableSegment}"
CommandParameter="End"
IsEnabled="{Binding Data.EndSegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
</ContextMenu>
</Button.ContextMenu>
<TextBlock FontSize="11">
ADD SEGMENT
</TextBlock>
</Button>
<!-- Zoom control -->
<Slider Grid.Column="1"
@ -371,7 +434,7 @@
Maximum="350"
TickFrequency="1"
IsSnapToTickEnabled="True"
AutoToolTipPlacement="TopLeft"
AutoToolTipPlacement="TopLeft"
Value="{Binding ProfileEditorService.PixelsPerSecond}"
Width="319" />

View File

@ -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<LayerPropertyGroupViewModel> 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();

View File

@ -54,8 +54,15 @@
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<TreeView Grid.Row="1" ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch">
<TreeView Grid.Row="1"
ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource MaterialDesignTreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}">
</Setter>
</Style>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelPropertiesViewModel}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
</HierarchicalDataTemplate>

View File

@ -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; }
}
}

View File

@ -12,6 +12,7 @@ namespace Artemis.Plugins.LayerEffects.Filter
"BlurOn"
);
AddLayerEffectDescriptor<DilateEffect>("Dilate", "A layer effect providing a dilation filter effect", "EyePlus");
AddLayerEffectDescriptor<OpacityEffect>("Opacity", "A layer effect letting you change the opacity of all children", "Opacity");
AddLayerEffectDescriptor<ErodeEffect>("Erode", "A layer effect providing an erode filter effect", "EyeMinus");
AddLayerEffectDescriptor<GlowEffect>("Glow", "A layer effect providing a glow filter effect", "BoxShadow");
AddLayerEffectDescriptor<GrayScaleEffect>("Gray-scale", "A layer effect providing a gray-scale filter effect", "InvertColors");

View File

@ -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<OpacityEffectProperties>
{
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()
{
}
}
}

View File

@ -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<int>();

View File

@ -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; }
}
}

View File

@ -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<WindowDataModel> OpenWindows { get; set; }
}
}

View File

@ -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<int> {_rand.Next(), _rand.Next(), _rand.Next()};
DataModel.PlayerInfosList = new List<PlayerInfo> {new PlayerInfo()};
DataModel.TestDataModel.IntsList = new List<int> {_rand.Next(), _rand.Next(), _rand.Next()};
DataModel.TestDataModel.PlayerInfosList = new List<PlayerInfo> {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
}
}

View File

@ -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
}
}
}