mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Layer properties - Restored much functionality on the reworked VMs
This commit is contained in:
parent
ea66dcd39e
commit
221c8bc7e7
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Media.Animation;
|
||||
using Artemis.Core.Extensions;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
@ -37,6 +38,7 @@ namespace Artemis.Core.Models.Profile
|
||||
Transform = new LayerTransformProperties {IsCorePropertyGroup = true};
|
||||
|
||||
_leds = new List<ArtemisLed>();
|
||||
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
|
||||
}
|
||||
|
||||
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
|
||||
@ -52,6 +54,7 @@ namespace Artemis.Core.Models.Profile
|
||||
Transform = new LayerTransformProperties {IsCorePropertyGroup = true};
|
||||
|
||||
_leds = new List<ArtemisLed>();
|
||||
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
|
||||
}
|
||||
|
||||
internal LayerEntity LayerEntity { get; set; }
|
||||
@ -147,6 +150,18 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
#region Shape management
|
||||
|
||||
private void GeneralOnPropertyGroupInitialized(object sender, EventArgs e)
|
||||
{
|
||||
ApplyShapeType();
|
||||
General.ShapeType.BaseValueChanged -= ShapeTypeOnBaseValueChanged;
|
||||
General.ShapeType.BaseValueChanged += ShapeTypeOnBaseValueChanged;
|
||||
}
|
||||
|
||||
private void ShapeTypeOnBaseValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
ApplyShapeType();
|
||||
}
|
||||
|
||||
private void ApplyShapeType()
|
||||
{
|
||||
switch (General.ShapeType.CurrentValue)
|
||||
@ -164,34 +179,21 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
internal void InitializeProperties(ILayerService layerService)
|
||||
{
|
||||
PropertiesInitialized = true;
|
||||
|
||||
ApplyShapeType();
|
||||
}
|
||||
|
||||
public bool PropertiesInitialized { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rendering
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
if (LayerBrush == null)
|
||||
if (LayerBrush == null || !LayerBrush.BaseProperties.PropertiesInitialized)
|
||||
return;
|
||||
|
||||
var properties = new List<BaseLayerProperty>(General.GetAllLayerProperties());
|
||||
properties.AddRange(Transform.GetAllLayerProperties());
|
||||
properties.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
|
||||
var properties = new List<BaseLayerProperty>(General.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any()));
|
||||
properties.AddRange(Transform.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any()));
|
||||
properties.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any()));
|
||||
|
||||
// For now, reset all keyframe engines after the last keyframe was hit
|
||||
// This is a placeholder method of repeating the animation until repeat modes are implemented
|
||||
var timeLineEnd = properties.Max(p => p.BaseKeyframes.Max(k => k.Position));
|
||||
var timeLineEnd = properties.Any() ? properties.Max(p => p.BaseKeyframes.Max(k => k.Position)) : TimeSpan.MaxValue;
|
||||
if (properties.Any(p => p.TimelineProgress >= timeLineEnd))
|
||||
{
|
||||
General.Override(TimeSpan.Zero);
|
||||
@ -218,7 +220,7 @@ namespace Artemis.Core.Models.Profile
|
||||
/// <inheritdoc />
|
||||
public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo)
|
||||
{
|
||||
if (Path == null || LayerShape?.Path == null)
|
||||
if (Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized)
|
||||
return;
|
||||
|
||||
canvas.Save();
|
||||
@ -263,7 +265,8 @@ namespace Artemis.Core.Models.Profile
|
||||
canvas.Scale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y);
|
||||
canvas.Translate(x, y);
|
||||
|
||||
LayerBrush?.Render(canvas, canvasInfo, new SKPath(LayerShape.Path), paint);
|
||||
if (LayerBrush != null && LayerBrush.BaseProperties.PropertiesInitialized)
|
||||
LayerBrush.Render(canvas, canvasInfo, new SKPath(LayerShape.Path), paint);
|
||||
}
|
||||
|
||||
private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint)
|
||||
@ -405,6 +408,7 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
public event EventHandler RenderPropertiesUpdated;
|
||||
public event EventHandler ShapePropertiesUpdated;
|
||||
public event EventHandler LayerBrushUpdated;
|
||||
|
||||
private void OnRenderPropertiesUpdated()
|
||||
{
|
||||
@ -416,6 +420,11 @@ namespace Artemis.Core.Models.Profile
|
||||
ShapePropertiesUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal void OnLayerBrushUpdated()
|
||||
{
|
||||
LayerBrushUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
@ -9,10 +9,17 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// </summary>
|
||||
public abstract class BaseLayerProperty
|
||||
{
|
||||
private bool _keyframesEnabled;
|
||||
|
||||
internal BaseLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layer this property applies to
|
||||
/// </summary>
|
||||
public Layer Layer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent group of this layer property, set after construction
|
||||
/// </summary>
|
||||
@ -21,13 +28,22 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// <summary>
|
||||
/// Gets whether keyframes are supported on this property
|
||||
/// </summary>
|
||||
public bool KeyframesSupported { get; protected set; }
|
||||
public bool KeyframesSupported { get; protected set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether keyframes are enabled on this property, has no effect if <see cref="KeyframesSupported" /> is
|
||||
/// False
|
||||
/// </summary>
|
||||
public bool KeyframesEnabled { get; set; }
|
||||
public bool KeyframesEnabled
|
||||
{
|
||||
get => _keyframesEnabled;
|
||||
set
|
||||
{
|
||||
if (_keyframesEnabled == value) return;
|
||||
_keyframesEnabled = value;
|
||||
OnKeyframesToggled();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the property is hidden in the UI
|
||||
@ -57,17 +73,73 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
internal PropertyEntity PropertyEntity { get; set; }
|
||||
internal LayerPropertyGroup LayerPropertyGroup { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="layerPropertyGroup"></param>
|
||||
internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup);
|
||||
/// <param name="fromStorage"></param>
|
||||
internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the property to the underlying property entity that was configured when calling
|
||||
/// <see cref="ApplyToLayerProperty" />
|
||||
/// </summary>
|
||||
internal abstract void ApplyToEntity();
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs once every frame when the layer property is updated
|
||||
/// </summary>
|
||||
public event EventHandler Updated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the base value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler BaseValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when keyframes are enabled/disabled
|
||||
/// </summary>
|
||||
public event EventHandler KeyframesToggled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new keyframe was added to the layer property
|
||||
/// </summary>
|
||||
public event EventHandler KeyframeAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a keyframe was removed from the layer property
|
||||
/// </summary>
|
||||
public event EventHandler KeyframeRemoved;
|
||||
|
||||
protected virtual void OnUpdated()
|
||||
{
|
||||
Updated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnBaseValueChanged()
|
||||
{
|
||||
BaseValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframesToggled()
|
||||
{
|
||||
KeyframesToggled?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeAdded()
|
||||
{
|
||||
KeyframeAdded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeRemoved()
|
||||
{
|
||||
KeyframeRemoved?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -180,12 +180,13 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
|
||||
// The current keyframe is the last keyframe before the current time
|
||||
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress);
|
||||
// The next keyframe is the first keyframe that's after the current time
|
||||
NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress);
|
||||
// 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;
|
||||
|
||||
// No need to update the current value if either of the keyframes are null
|
||||
if (CurrentKeyframe == null)
|
||||
CurrentValue = BaseValue;
|
||||
CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue;
|
||||
else if (NextKeyframe == null)
|
||||
CurrentValue = CurrentKeyframe.Value;
|
||||
// Only determine progress and current value if both keyframes are present
|
||||
@ -218,7 +219,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
|
||||
}
|
||||
|
||||
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup)
|
||||
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage)
|
||||
{
|
||||
// Doubt this will happen but let's make sure
|
||||
if (_isInitialized)
|
||||
@ -231,8 +232,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
|
||||
try
|
||||
{
|
||||
IsLoadedFromStorage = true;
|
||||
BaseValue = JsonConvert.DeserializeObject<T>(entity.Value);
|
||||
if (entity.Value != null)
|
||||
BaseValue = JsonConvert.DeserializeObject<T>(entity.Value);
|
||||
|
||||
IsLoadedFromStorage = fromStorage;
|
||||
CurrentValue = BaseValue;
|
||||
KeyframesEnabled = entity.KeyframesEnabled;
|
||||
|
||||
@ -259,7 +262,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
if (_isInitialized)
|
||||
if (!_isInitialized)
|
||||
throw new ArtemisCoreException("Layer property is not yet initialized");
|
||||
|
||||
PropertyEntity.Value = JsonConvert.SerializeObject(BaseValue);
|
||||
@ -272,49 +275,5 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
EasingFunction = (int) k.EasingFunction
|
||||
}));
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs once every frame when the layer property is updated
|
||||
/// </summary>
|
||||
public event EventHandler Updated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the base value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler BaseValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new keyframe was added to the layer property
|
||||
/// </summary>
|
||||
public event EventHandler KeyframeAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a keyframe was removed from the layer property
|
||||
/// </summary>
|
||||
public event EventHandler KeyframeRemoved;
|
||||
|
||||
protected virtual void OnUpdated()
|
||||
{
|
||||
Updated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnBaseValueChanged()
|
||||
{
|
||||
BaseValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeAdded()
|
||||
{
|
||||
KeyframeAdded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeRemoved()
|
||||
{
|
||||
KeyframeRemoved?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -2,12 +2,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Annotations;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Plugins.Exceptions;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
@ -23,6 +25,11 @@ namespace Artemis.Core.Models.Profile
|
||||
_layerPropertyGroups = new List<LayerPropertyGroup>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layer this property group applies to
|
||||
/// </summary>
|
||||
public Layer Layer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent group of this layer property group, set after construction
|
||||
/// </summary>
|
||||
@ -60,12 +67,16 @@ namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
}
|
||||
|
||||
internal void InitializeProperties(ILayerService layerService, Layer layer, string path)
|
||||
internal void InitializeProperties(ILayerService layerService, Layer layer, [NotNull] string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
// Doubt this will happen but let's make sure
|
||||
if (PropertiesInitialized)
|
||||
throw new ArtemisCoreException("Layer property group already initialized, wut");
|
||||
|
||||
Layer = layer;
|
||||
|
||||
// Get all properties with a PropertyDescriptionAttribute
|
||||
foreach (var propertyInfo in GetType().GetProperties())
|
||||
{
|
||||
@ -77,7 +88,8 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true);
|
||||
instance.Parent = this;
|
||||
InitializeProperty(layer, path, instance);
|
||||
instance.Layer = layer;
|
||||
InitializeProperty(layer, path + propertyInfo.Name, instance);
|
||||
propertyInfo.SetValue(this, instance);
|
||||
_layerProperties.Add(instance);
|
||||
}
|
||||
@ -100,6 +112,8 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
OnPropertiesInitialized();
|
||||
PropertiesInitialized = true;
|
||||
|
||||
OnPropertyGroupInitialized();
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
@ -110,12 +124,16 @@ namespace Artemis.Core.Models.Profile
|
||||
var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
|
||||
if (propertyDescription != null)
|
||||
{
|
||||
var layerProperty = (BaseLayerProperty) propertyInfo.GetValue(this);
|
||||
layerProperty.ApplyToEntity();
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
|
||||
if (propertyGroupDescription != null)
|
||||
{
|
||||
var layerPropertyGroup = (LayerPropertyGroup) propertyInfo.GetValue(this);
|
||||
layerPropertyGroup.ApplyToEntity();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,14 +175,22 @@ namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
var pluginGuid = IsCorePropertyGroup || instance.IsCoreProperty ? Constants.CorePluginInfo.Guid : layer.LayerBrush.PluginInfo.Guid;
|
||||
var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path);
|
||||
if (entity != null)
|
||||
instance.ApplyToLayerProperty(entity, this);
|
||||
var fromStorage = true;
|
||||
if (entity == null)
|
||||
{
|
||||
fromStorage = false;
|
||||
entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path};
|
||||
layer.LayerEntity.PropertyEntities.Add(entity);
|
||||
}
|
||||
|
||||
instance.ApplyToLayerProperty(entity, this, fromStorage);
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
internal event EventHandler<PropertyGroupUpdatingEventArgs> PropertyGroupUpdating;
|
||||
internal event EventHandler<PropertyGroupUpdatingEventArgs> PropertyGroupOverriding;
|
||||
public event EventHandler PropertyGroupInitialized;
|
||||
|
||||
internal virtual void OnPropertyGroupUpdating(PropertyGroupUpdatingEventArgs e)
|
||||
{
|
||||
@ -177,5 +203,10 @@ namespace Artemis.Core.Models.Profile
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual void OnPropertyGroupInitialized()
|
||||
{
|
||||
PropertyGroupInitialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,6 +51,7 @@ namespace Artemis.Core.Plugins.LayerBrush
|
||||
|
||||
internal override void InitializeProperties(ILayerService layerService, string path)
|
||||
{
|
||||
Properties = Activator.CreateInstance<T>();
|
||||
Properties.InitializeProperties(layerService, Layer, path);
|
||||
OnPropertiesInitialized();
|
||||
PropertiesInitialized = true;
|
||||
|
||||
@ -27,8 +27,8 @@ namespace Artemis.Core.Services
|
||||
var layer = new Layer(profile, parent, name);
|
||||
|
||||
// Layers have two hardcoded property groups, instantiate them
|
||||
layer.General.InitializeProperties(this, layer, null);
|
||||
layer.Transform.InitializeProperties(this, layer, null);
|
||||
layer.General.InitializeProperties(this, layer, "General.");
|
||||
layer.Transform.InitializeProperties(this, layer, "Transform.");
|
||||
|
||||
// With the properties loaded, the layer brush can be instantiated
|
||||
InstantiateLayerBrush(layer);
|
||||
@ -58,11 +58,10 @@ namespace Artemis.Core.Services
|
||||
new ConstructorArgument("layer", layer),
|
||||
new ConstructorArgument("descriptor", descriptor)
|
||||
};
|
||||
var layerBrush = (BaseLayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments);
|
||||
layerBrush.InitializeProperties(this, null);
|
||||
layer.LayerBrush = layerBrush;
|
||||
|
||||
return layerBrush;
|
||||
layer.LayerBrush = (BaseLayerBrush)_kernel.Get(descriptor.LayerBrushType, arguments); ;
|
||||
layer.LayerBrush.InitializeProperties(this, "LayerBrush.");
|
||||
layer.OnLayerBrushUpdated();
|
||||
return layer.LayerBrush;
|
||||
}
|
||||
|
||||
public void RemoveLayerBrush(Layer layer)
|
||||
|
||||
@ -93,8 +93,8 @@ namespace Artemis.Core.Services.Storage
|
||||
module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface);
|
||||
if (profile != null)
|
||||
{
|
||||
InitializeCoreProperties(profile);
|
||||
InstantiateProfileLayerBrushes(profile);
|
||||
InitializeLayerProperties(profile);
|
||||
InstantiateLayerBrushes(profile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,18 +159,18 @@ namespace Artemis.Core.Services.Storage
|
||||
_logger.Debug("Redo profile update - Success");
|
||||
}
|
||||
|
||||
private void InitializeCoreProperties(Profile profile)
|
||||
private void InitializeLayerProperties(Profile profile)
|
||||
{
|
||||
foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null))
|
||||
{
|
||||
if (!layer.General.PropertiesInitialized)
|
||||
layer.General.InitializeProperties(_layerService, layer, null);
|
||||
layer.General.InitializeProperties(_layerService, layer, "General.");
|
||||
if (!layer.Transform.PropertiesInitialized)
|
||||
layer.Transform.InitializeProperties(_layerService, layer, null);
|
||||
layer.Transform.InitializeProperties(_layerService, layer, "Transform.");
|
||||
};
|
||||
}
|
||||
|
||||
private void InstantiateProfileLayerBrushes(Profile profile)
|
||||
private void InstantiateLayerBrushes(Profile profile)
|
||||
{
|
||||
// Only instantiate brushes for layers without an existing brush instance
|
||||
foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null))
|
||||
@ -188,7 +188,7 @@ namespace Artemis.Core.Services.Storage
|
||||
{
|
||||
var profileModules = _pluginService.GetPluginsOfType<ProfileModule>();
|
||||
foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList())
|
||||
InstantiateProfileLayerBrushes(profileModule.ActiveProfile);
|
||||
InstantiateLayerBrushes(profileModule.ActiveProfile);
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
@ -4,17 +4,25 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Style>
|
||||
<Style>
|
||||
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource MaterialDesignValidationErrorTemplate}" />
|
||||
</Style>
|
||||
</UserControl.Style>
|
||||
<StackPanel>
|
||||
<!-- Drag handle -->
|
||||
<Border x:Name="DragHandle" BorderThickness="0,0,0,1" Height="19">
|
||||
<Border.BorderBrush>
|
||||
<VisualBrush>
|
||||
<VisualBrush.Visual>
|
||||
<Rectangle StrokeDashArray="2 2" Stroke="{DynamicResource SecondaryAccentBrush}" StrokeThickness="1"
|
||||
<Rectangle x:Name="BorderVisual"
|
||||
StrokeDashArray="2 2" Stroke="{DynamicResource SecondaryAccentBrush}" StrokeThickness="1"
|
||||
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
|
||||
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}" />
|
||||
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}">
|
||||
</Rectangle>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Border.BorderBrush>
|
||||
@ -27,15 +35,14 @@
|
||||
Foreground="{DynamicResource SecondaryAccentBrush}"
|
||||
MouseDown="InputMouseDown"
|
||||
MouseUp="InputMouseUp"
|
||||
MouseMove="InputMouseMove"
|
||||
RequestBringIntoView="Input_OnRequestBringIntoView"/>
|
||||
MouseMove="InputMouseMove"
|
||||
RequestBringIntoView="Input_OnRequestBringIntoView" />
|
||||
</Border>
|
||||
|
||||
<!-- Input -->
|
||||
<TextBox x:Name="Input"
|
||||
Width="60"
|
||||
Height="20"
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{Binding Value, StringFormat=N3, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
LostFocus="InputLostFocus"
|
||||
|
||||
@ -104,6 +104,8 @@ namespace Artemis.UI.Shared.Controls
|
||||
var startX = new decimal(_mouseDragStartPoint.X);
|
||||
var x = new decimal(e.GetPosition((IInputElement) sender).X);
|
||||
var stepSize = new decimal(StepSize);
|
||||
if (stepSize == 0)
|
||||
stepSize = 0.1m;
|
||||
|
||||
Value = (float) UltimateRoundingFunction(startValue + stepSize * (x - startX), stepSize, 0.5m);
|
||||
}
|
||||
|
||||
@ -20,8 +20,8 @@ namespace Artemis.UI.Shared.Utilities
|
||||
var resultCallback = new HitTestResultCallback(r => HitTestResultBehavior.Continue);
|
||||
var filterCallback = new HitTestFilterCallback(e =>
|
||||
{
|
||||
if (e is FrameworkElement fe && fe.DataContext.GetType() == typeof(T) && !result.Contains((T) fe.DataContext))
|
||||
result.Add((T) fe.DataContext);
|
||||
if (e is FrameworkElement fe && fe.DataContext is T context && !result.Contains(context))
|
||||
result.Add(context);
|
||||
return HitTestFilterBehavior.Continue;
|
||||
});
|
||||
VisualTreeHelper.HitTest(container, filterCallback, resultCallback, hitTestParams);
|
||||
|
||||
@ -43,6 +43,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value.Left / ProfileEditorService.PixelsPerSecond);
|
||||
}
|
||||
|
||||
public Layer SelectedLayer { get; set; }
|
||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; set; }
|
||||
public TreeViewModel TreeViewModel { get; set; }
|
||||
public TimelineViewModel TimelineViewModel { get; set; }
|
||||
@ -86,9 +87,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
|
||||
private void PopulateProperties(ProfileElement profileElement)
|
||||
{
|
||||
if (SelectedLayer != null)
|
||||
{
|
||||
SelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated;
|
||||
SelectedLayer = null;
|
||||
}
|
||||
|
||||
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
|
||||
layerPropertyGroupViewModel.Dispose();
|
||||
LayerPropertyGroups.Clear();
|
||||
|
||||
if (profileElement is Layer layer)
|
||||
{
|
||||
SelectedLayer = layer;
|
||||
SelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated;
|
||||
|
||||
// Add the built-in root groups of the layer
|
||||
var generalAttribute = Attribute.GetCustomAttribute(
|
||||
layer.GetType().GetProperty(nameof(layer.General)),
|
||||
@ -113,11 +126,35 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layer.LayerBrush.BaseProperties, brushDescription));
|
||||
}
|
||||
}
|
||||
else
|
||||
SelectedLayer = null;
|
||||
|
||||
TreeViewModel = new TreeViewModel(this, LayerPropertyGroups);
|
||||
TimelineViewModel = new TimelineViewModel(this, LayerPropertyGroups);
|
||||
}
|
||||
|
||||
private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e)
|
||||
{
|
||||
// Get rid of the old layer properties group
|
||||
if (LayerPropertyGroups.Count == 3)
|
||||
{
|
||||
LayerPropertyGroups[2].Dispose();
|
||||
LayerPropertyGroups.RemoveAt(2);
|
||||
}
|
||||
|
||||
if (SelectedLayer.LayerBrush != null)
|
||||
{
|
||||
// Add the rout group of the brush
|
||||
// The root group of the brush has no attribute so let's pull one out of our sleeve
|
||||
var brushDescription = new PropertyGroupDescriptionAttribute
|
||||
{
|
||||
Name = SelectedLayer.LayerBrush.Descriptor.DisplayName,
|
||||
Description = SelectedLayer.LayerBrush.Descriptor.Description
|
||||
};
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, SelectedLayer.LayerBrush.BaseProperties, brushDescription));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controls
|
||||
|
||||
@ -50,6 +50,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
public override void Dispose()
|
||||
{
|
||||
TreePropertyViewModel.Dispose();
|
||||
TimelinePropertyViewModel.Dispose();
|
||||
}
|
||||
|
||||
public void SetCurrentValue(T value, bool saveChanges)
|
||||
|
||||
@ -18,7 +18,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
EasingFunction = easingFunction;
|
||||
Description = easingFunction.Humanize();
|
||||
|
||||
CreateGeometry();
|
||||
CreateEasingPoints();
|
||||
}
|
||||
|
||||
public Easings.Functions EasingFunction { get; }
|
||||
@ -36,7 +36,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateGeometry()
|
||||
private void CreateEasingPoints()
|
||||
{
|
||||
EasingPoints = new PointCollection();
|
||||
for (var i = 1; i <= 10; i++)
|
||||
|
||||
@ -45,7 +45,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
#endregion
|
||||
}
|
||||
|
||||
public abstract class TimelineKeyframeViewModel
|
||||
public abstract class TimelineKeyframeViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly TimelineViewModel _timelineViewModel;
|
||||
@ -56,6 +56,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
_profileEditorService = profileEditorService;
|
||||
_timelineViewModel = timelineViewModel;
|
||||
BaseLayerPropertyKeyframe = baseLayerPropertyKeyframe;
|
||||
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
|
||||
}
|
||||
|
||||
public BaseLayerPropertyKeyframe BaseLayerPropertyKeyframe { get; }
|
||||
@ -139,6 +140,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
|
||||
#region Easing
|
||||
|
||||
public void ContextMenuOpening()
|
||||
{
|
||||
CreateEasingViewModels();
|
||||
}
|
||||
|
||||
public void ContextMenuClosing()
|
||||
{
|
||||
EasingViewModels.Clear();
|
||||
}
|
||||
|
||||
private void CreateEasingViewModels()
|
||||
{
|
||||
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new TimelineEasingViewModel(this, v)));
|
||||
|
||||
@ -12,72 +12,82 @@
|
||||
d:DataContext="{d:DesignInstance local:TimelinePropertyGroupViewModel}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="1" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border Height="25" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource MaterialDesignDivider}" Grid.Row="0">
|
||||
<ItemsControl ItemsSource="{Binding TimelineKeyframeViewModels}"
|
||||
|
||||
<ItemsControl Grid.Row="0"
|
||||
Height="24"
|
||||
Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
|
||||
ItemsSource="{Binding TimelineKeyframeViewModels}"
|
||||
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
|
||||
Stroke="White"
|
||||
StrokeThickness="0"
|
||||
Width="10"
|
||||
Height="10"
|
||||
Margin="-5,6,0,0"
|
||||
s:View.ActionTarget="{Binding}">
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="{x:Type Ellipse}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
</Ellipse>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
<ItemsControl ItemsSource="{Binding LayerPropertyGroupViewModel.Children}" Grid.Row="1">
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="{x:Type Ellipse}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness"
|
||||
To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness"
|
||||
To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
</Ellipse>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Rectangle Grid.Row="1" HorizontalAlignment="Stretch" Fill="{DynamicResource MaterialDesignDivider}" Height="1" />
|
||||
|
||||
<ItemsControl Grid.Row="2"
|
||||
Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||
ItemsSource="{Binding LayerPropertyGroupViewModel.Children}">
|
||||
<ItemsControl.Resources>
|
||||
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}">
|
||||
<ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False" />
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False" />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}">
|
||||
<ContentControl s:View.Model="{Binding TimelinePropertyBaseViewModel}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False" />
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.Resources>
|
||||
</ItemsControl>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.TimelinePropertyView"
|
||||
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: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.LayerProperties.Timeline"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:TimelinePropertyViewModel}">
|
||||
<Border Height="25" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
@ -35,7 +35,9 @@
|
||||
s:View.ActionTarget="{Binding}"
|
||||
MouseDown="{s:Action KeyframeMouseDown}"
|
||||
MouseUp="{s:Action KeyframeMouseUp}"
|
||||
MouseMove="{s:Action KeyframeMouseMove}">
|
||||
MouseMove="{s:Action KeyframeMouseMove}"
|
||||
ContextMenuOpening="{s:Action ContextMenuOpening}"
|
||||
ContextMenuClosing="{s:Action ContextMenuClosing}">
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="{x:Type Ellipse}">
|
||||
<Style.Triggers>
|
||||
@ -72,6 +74,9 @@
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Creation" />
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||
<Setter Property="IsCheckable" Value="True" />
|
||||
@ -105,4 +110,4 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
@ -13,27 +15,53 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
||||
|
||||
LayerPropertyViewModel.LayerProperty.KeyframeAdded += LayerPropertyOnKeyframeModified;
|
||||
LayerPropertyViewModel.LayerProperty.KeyframeRemoved += LayerPropertyOnKeyframeModified;
|
||||
LayerPropertyViewModel.LayerProperty.KeyframesToggled += LayerPropertyOnKeyframeModified;
|
||||
}
|
||||
|
||||
private void LayerPropertyOnKeyframeModified(object sender, EventArgs e)
|
||||
{
|
||||
UpdateKeyframes();
|
||||
}
|
||||
|
||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||
|
||||
public override void UpdateKeyframes(TimelineViewModel timelineViewModel)
|
||||
public override void UpdateKeyframes()
|
||||
{
|
||||
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList();
|
||||
TimelineKeyframeViewModels.RemoveRange(
|
||||
TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe))
|
||||
);
|
||||
TimelineKeyframeViewModels.AddRange(
|
||||
keyframes.Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k))
|
||||
.Select(k => new TimelineKeyframeViewModel<T>(_profileEditorService, timelineViewModel, k))
|
||||
);
|
||||
if (TimelineViewModel == null)
|
||||
throw new ArtemisUIException("Timeline view model must be set before keyframes can be updated");
|
||||
|
||||
// Only show keyframes if they are enabled
|
||||
if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled)
|
||||
{
|
||||
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList();
|
||||
var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList();
|
||||
TimelineKeyframeViewModels.RemoveRange(toRemove);
|
||||
TimelineKeyframeViewModels.AddRange(
|
||||
keyframes.Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k))
|
||||
.Select(k => new TimelineKeyframeViewModel<T>(_profileEditorService, TimelineViewModel, k))
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
TimelineKeyframeViewModels.Clear();
|
||||
}
|
||||
|
||||
foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels)
|
||||
timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
LayerPropertyViewModel.LayerProperty.KeyframeAdded -= LayerPropertyOnKeyframeModified;
|
||||
LayerPropertyViewModel.LayerProperty.KeyframeRemoved -= LayerPropertyOnKeyframeModified;
|
||||
LayerPropertyViewModel.LayerProperty.KeyframesToggled -= LayerPropertyOnKeyframeModified;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TimelinePropertyViewModel
|
||||
public abstract class TimelinePropertyViewModel : IDisposable
|
||||
{
|
||||
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||
{
|
||||
@ -42,8 +70,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
}
|
||||
|
||||
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
|
||||
public TimelineViewModel TimelineViewModel { get; set; }
|
||||
public BindableCollection<TimelineKeyframeViewModel> TimelineKeyframeViewModels { get; set; }
|
||||
|
||||
public abstract void UpdateKeyframes(TimelineViewModel timelineViewModel);
|
||||
public abstract void UpdateKeyframes();
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,18 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.TimelineView"
|
||||
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: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.LayerProperties.Timeline"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="25"
|
||||
d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:TimelineViewModel}">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
||||
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
||||
MouseMove="{s:Action TimelineCanvasMouseMove}">
|
||||
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
||||
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
||||
MouseMove="{s:Action TimelineCanvasMouseMove}">
|
||||
<Grid.Triggers>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
@ -42,10 +42,11 @@
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||
<Path x:Name="MultiSelectionPath"
|
||||
Data="{Binding SelectionRectangle}"
|
||||
Opacity="0"
|
||||
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||
StrokeThickness="1"
|
||||
x:Name="MultiSelectionPath"
|
||||
IsHitTestVisible="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||
|
||||
@ -35,7 +35,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
foreach (var layerPropertyBaseViewModel in layerPropertyGroupViewModel.GetAllChildren())
|
||||
{
|
||||
if (layerPropertyBaseViewModel is LayerPropertyViewModel layerPropertyViewModel)
|
||||
layerPropertyViewModel.TimelinePropertyBaseViewModel.UpdateKeyframes(this);
|
||||
{
|
||||
layerPropertyViewModel.TimelinePropertyBaseViewModel.TimelineViewModel = this;
|
||||
layerPropertyViewModel.TimelinePropertyBaseViewModel.UpdateKeyframes();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,12 +11,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyI
|
||||
{
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated;
|
||||
UpdateInputValue();
|
||||
}
|
||||
|
||||
protected PropertyInputViewModel(LayerPropertyViewModel<T> layerPropertyViewModel, IModelValidator validator) : base(validator)
|
||||
{
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated;
|
||||
UpdateInputValue();
|
||||
}
|
||||
|
||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||
@ -38,6 +40,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyI
|
||||
LayerPropertyViewModel.LayerProperty.Updated -= LayerPropertyOnUpdated;
|
||||
}
|
||||
|
||||
protected virtual void OnInputValueApplied()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnInputValueChanged()
|
||||
{
|
||||
}
|
||||
@ -50,7 +56,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyI
|
||||
private void UpdateInputValue()
|
||||
{
|
||||
// Avoid unnecessary UI updates and validator cycles
|
||||
if (_inputValue.Equals(LayerPropertyViewModel.LayerProperty.CurrentValue))
|
||||
if (_inputValue != null && _inputValue.Equals(LayerPropertyViewModel.LayerProperty.CurrentValue) || _inputValue == null && LayerPropertyViewModel.LayerProperty.CurrentValue == null)
|
||||
return;
|
||||
|
||||
// Override the input value
|
||||
@ -61,20 +67,24 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyI
|
||||
NotifyOfPropertyChange(nameof(InputValue));
|
||||
|
||||
// Force the validator to run with the overridden value
|
||||
Validate();
|
||||
if (Validator != null)
|
||||
Validate();
|
||||
}
|
||||
|
||||
private void ApplyInputValue()
|
||||
{
|
||||
// Force the validator to run
|
||||
Validate();
|
||||
if (Validator != null)
|
||||
Validate();
|
||||
// Only apply the input value to the layer property if the validator found no errors
|
||||
if (!HasErrors)
|
||||
LayerPropertyViewModel.SetCurrentValue(_inputValue, !InputDragging);
|
||||
|
||||
OnInputValueChanged();
|
||||
OnInputValueApplied();
|
||||
}
|
||||
|
||||
|
||||
#region Event handlers
|
||||
|
||||
public void InputDragStarted(object sender, EventArgs e)
|
||||
|
||||
@ -27,7 +27,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyI
|
||||
}
|
||||
|
||||
public BindableCollection<ValueDescription> ComboboxValues { get; }
|
||||
|
||||
|
||||
public void UpdateEnumValues()
|
||||
{
|
||||
var layerBrushProviders = _pluginService.GetPluginsOfType<LayerBrushProvider>();
|
||||
@ -47,7 +47,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyI
|
||||
ComboboxValues.Clear();
|
||||
ComboboxValues.AddRange(enumValues);
|
||||
}
|
||||
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_pluginService.PluginLoaded -= PluginServiceOnPluginLoaded;
|
||||
@ -58,5 +58,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyI
|
||||
{
|
||||
UpdateEnumValues();
|
||||
}
|
||||
|
||||
protected override void OnInputValueApplied()
|
||||
{
|
||||
_layerService.InstantiateLayerBrush(LayerPropertyViewModel.LayerProperty.Layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,15 +8,17 @@
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
xmlns:propertyInput="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance propertyInput:FloatPropertyInputViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.PropertyDescription.InputPrefix}" />
|
||||
<controls:DraggableFloat Value="{Binding InputValue}"
|
||||
StepSize="{Binding LayerPropertyViewModel.PropertyDescription.InputStepSize}"
|
||||
DragStarted="{s:Action InputDragStarted}"
|
||||
DragEnded="{s:Action InputDragEnded}" />
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
StepSize="{Binding LayerPropertyViewModel.PropertyDescription.InputStepSize}"
|
||||
DragStarted="{s:Action InputDragStarted}"
|
||||
DragEnded="{s:Action InputDragEnded}" />
|
||||
<TextBlock Margin="5 0 0 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.PropertyDescription.InputAffix}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -8,12 +8,14 @@
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
xmlns:propertyInput="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance propertyInput:IntPropertyInputViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.PropertyDescription.InputPrefix}" />
|
||||
<controls:DraggableFloat Value="{Binding InputValue}"
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
StepSize="{Binding LayerPropertyViewModel.PropertyDescription.InputStepSize}"
|
||||
DragStarted="{s:Action InputDragStarted}"
|
||||
DragEnded="{s:Action InputDragEnded}" />
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:TreePropertyViewModel}}">
|
||||
<Grid Height="22">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@ -20,7 +21,7 @@
|
||||
ToolTip="Toggle key-framing"
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsChecked="{Binding LayerPropertyViewModel.LayerProperty.KeyframesEnabled}"
|
||||
IsChecked="{Binding KeyframesEnabled}"
|
||||
IsEnabled="{Binding LayerPropertyViewModel.LayerProperty.KeyframesSupported}"
|
||||
VerticalAlignment="Center" Padding="-25">
|
||||
<materialDesign:PackIcon Kind="Stopwatch" Height="13" Width="13" />
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Utilities;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
@ -20,10 +23,37 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||
public PropertyInputViewModel<T> PropertyInputViewModel { get; set; }
|
||||
|
||||
public bool KeyframesEnabled
|
||||
{
|
||||
get => LayerPropertyViewModel.LayerProperty.KeyframesEnabled;
|
||||
set => ApplyKeyframesEnabled(value);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
PropertyInputViewModel.Dispose();
|
||||
}
|
||||
|
||||
private void ApplyKeyframesEnabled(bool enable)
|
||||
{
|
||||
// If enabling keyframes for the first time, add a keyframe with the current value at the current position
|
||||
if (enable && !LayerPropertyViewModel.LayerProperty.Keyframes.Any())
|
||||
{
|
||||
LayerPropertyViewModel.LayerProperty.AddKeyframe(new LayerPropertyKeyframe<T>(
|
||||
LayerPropertyViewModel.LayerProperty.CurrentValue,
|
||||
_profileEditorService.CurrentTime,
|
||||
Easings.Functions.Linear,
|
||||
LayerPropertyViewModel.LayerProperty
|
||||
));
|
||||
}
|
||||
// If disabling keyframes, set the base value to the current value
|
||||
else if (!enable && LayerPropertyViewModel.LayerProperty.Keyframes.Any())
|
||||
LayerPropertyViewModel.LayerProperty.BaseValue = LayerPropertyViewModel.LayerProperty.CurrentValue;
|
||||
|
||||
LayerPropertyViewModel.LayerProperty.KeyframesEnabled = enable;
|
||||
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TreePropertyViewModel : IDisposable
|
||||
|
||||
@ -35,8 +35,7 @@
|
||||
BorderThickness="0,0,0,1"
|
||||
Height="25"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<Grid
|
||||
Margin="{Binding Converter={StaticResource lengthConverter}, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<Grid Margin="{Binding Converter={StaticResource lengthConverter}, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="19" />
|
||||
<ColumnDefinition />
|
||||
@ -94,8 +93,7 @@
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}"
|
||||
ItemsSource="{Binding Children}">
|
||||
<HierarchicalDataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding PropertyGroupDescription.Name}"
|
||||
ToolTip="{Binding PropertyGroupDescription.Description}" Margin="5 0"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Windows;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Stylet;
|
||||
@ -19,7 +20,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
||||
|
||||
public void PropertyTreePreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (e.Handled || !(sender is TreeView))
|
||||
if (e.Handled || !(sender is System.Windows.Controls.TreeView))
|
||||
return;
|
||||
|
||||
e.Handled = true;
|
||||
|
||||
@ -103,19 +103,23 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
OpacityGeometry = opacityGeometry;
|
||||
|
||||
// Render the store as a bitmap
|
||||
var drawingImage = new DrawingImage(new GeometryDrawing(new SolidColorBrush(Colors.Black), null, LayerGeometry));
|
||||
var image = new Image {Source = drawingImage};
|
||||
var bitmap = new RenderTargetBitmap(
|
||||
(int) (LayerGeometry.Bounds.Width * 2.5),
|
||||
(int) (LayerGeometry.Bounds.Height * 2.5),
|
||||
96,
|
||||
96,
|
||||
PixelFormats.Pbgra32
|
||||
);
|
||||
image.Arrange(new Rect(0, 0, bitmap.Width, bitmap.Height));
|
||||
bitmap.Render(image);
|
||||
bitmap.Freeze();
|
||||
LayerGeometryBitmap = bitmap;
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
var drawingImage = new DrawingImage(new GeometryDrawing(new SolidColorBrush(Colors.Black), null, LayerGeometry));
|
||||
var image = new Image { Source = drawingImage };
|
||||
var bitmap = new RenderTargetBitmap(
|
||||
(int)(LayerGeometry.Bounds.Width * 2.5),
|
||||
(int)(LayerGeometry.Bounds.Height * 2.5),
|
||||
96,
|
||||
96,
|
||||
PixelFormats.Pbgra32
|
||||
);
|
||||
image.Arrange(new Rect(0, 0, bitmap.Width, bitmap.Height));
|
||||
bitmap.Render(image);
|
||||
bitmap.Freeze();
|
||||
LayerGeometryBitmap = bitmap;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void CreateShapeGeometry()
|
||||
|
||||
@ -139,8 +139,7 @@ namespace Artemis.UI.Services
|
||||
UpdateProfilePreview();
|
||||
OnSelectedProfileElementUpdated(new ProfileElementEventArgs(SelectedProfileElement));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void UpdateProfilePreview()
|
||||
{
|
||||
if (SelectedProfile == null)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user