mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Implemented keyframe easing
This commit is contained in:
parent
bda77b12f0
commit
0ff71c9d3b
@ -15,7 +15,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
|||||||
var nextKeyframe = (Keyframe<float>) NextKeyframe;
|
var nextKeyframe = (Keyframe<float>) NextKeyframe;
|
||||||
|
|
||||||
var diff = nextKeyframe.Value - currentKeyframe.Value;
|
var diff = nextKeyframe.Value - currentKeyframe.Value;
|
||||||
return currentKeyframe.Value + diff * KeyframeProgress;
|
return currentKeyframe.Value + diff * KeyframeProgressEased;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
|||||||
var nextKeyframe = (Keyframe<int>) NextKeyframe;
|
var nextKeyframe = (Keyframe<int>) NextKeyframe;
|
||||||
|
|
||||||
var diff = nextKeyframe.Value - currentKeyframe.Value;
|
var diff = nextKeyframe.Value - currentKeyframe.Value;
|
||||||
return currentKeyframe.Value + diff * KeyframeProgress;
|
return currentKeyframe.Value + diff * KeyframeProgressEased;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Exceptions;
|
using Artemis.Core.Exceptions;
|
||||||
using Artemis.Core.Models.Profile.LayerProperties;
|
using Artemis.Core.Models.Profile.LayerProperties;
|
||||||
|
using Artemis.Core.Utilities;
|
||||||
|
|
||||||
namespace Artemis.Core.Models.Profile.KeyframeEngines
|
namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||||
{
|
{
|
||||||
@ -24,10 +25,17 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
|||||||
public TimeSpan Progress { get; private set; }
|
public TimeSpan Progress { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The progress from the current keyframe to the next 0 to 1
|
/// The progress from the current keyframe to the next.
|
||||||
|
/// <para>Range 0.0 to 1.0.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float KeyframeProgress { get; private set; }
|
public float KeyframeProgress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The progress from the current keyframe to the next with the current keyframes easing function applied.
|
||||||
|
/// <para>Range 0.0 to 1.0 but can be higher than 1.0 depending on easing function.</para>
|
||||||
|
/// </summary>
|
||||||
|
public float KeyframeProgressEased { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current keyframe
|
/// The current keyframe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -68,28 +76,35 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
|||||||
if (!Initialized)
|
if (!Initialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var keyframes = LayerProperty.UntypedKeyframes.ToList();
|
||||||
Progress = Progress.Add(TimeSpan.FromMilliseconds(deltaTime));
|
Progress = Progress.Add(TimeSpan.FromMilliseconds(deltaTime));
|
||||||
|
|
||||||
// TODO Keep them sorted somewhere else, iterating all keyframes multiple times sucks
|
// The current keyframe is the last keyframe before the current time
|
||||||
var sortedKeyframes = LayerProperty.UntypedKeyframes.ToList().OrderBy(k => k.Position).ToList();
|
CurrentKeyframe = keyframes.LastOrDefault(k => k.Position <= Progress);
|
||||||
|
// The next keyframe is the first keyframe that's after the current time
|
||||||
|
NextKeyframe = keyframes.FirstOrDefault(k => k.Position > Progress);
|
||||||
|
|
||||||
CurrentKeyframe = sortedKeyframes.LastOrDefault(k => k.Position <= Progress);
|
|
||||||
NextKeyframe = sortedKeyframes.FirstOrDefault(k => k.Position > Progress);
|
|
||||||
if (CurrentKeyframe == null)
|
if (CurrentKeyframe == null)
|
||||||
|
{
|
||||||
KeyframeProgress = 0;
|
KeyframeProgress = 0;
|
||||||
|
KeyframeProgressEased = 0;
|
||||||
|
}
|
||||||
else if (NextKeyframe == null)
|
else if (NextKeyframe == null)
|
||||||
|
{
|
||||||
KeyframeProgress = 1;
|
KeyframeProgress = 1;
|
||||||
|
KeyframeProgressEased = 1;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
||||||
KeyframeProgress = (float) ((Progress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
|
KeyframeProgress = (float) ((Progress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
|
||||||
|
KeyframeProgressEased = (float) Easings.Interpolate(KeyframeProgress, CurrentKeyframe.EasingFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Apply easing and store it separately
|
|
||||||
|
|
||||||
// LayerProperty determines what's next: reset, stop, continue
|
// LayerProperty determines what's next: reset, stop, continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overrides the engine's progress to the provided value
|
/// Overrides the engine's progress to the provided value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -106,6 +121,8 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public object GetCurrentValue()
|
public object GetCurrentValue()
|
||||||
{
|
{
|
||||||
|
if (CurrentKeyframe == null && LayerProperty.UntypedKeyframes.Any())
|
||||||
|
return LayerProperty.UntypedKeyframes.First().BaseValue;
|
||||||
if (CurrentKeyframe == null)
|
if (CurrentKeyframe == null)
|
||||||
return LayerProperty.BaseValue;
|
return LayerProperty.BaseValue;
|
||||||
if (NextKeyframe == null)
|
if (NextKeyframe == null)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
|||||||
|
|
||||||
var xDiff = nextKeyframe.Value.X - currentKeyframe.Value.X;
|
var xDiff = nextKeyframe.Value.X - currentKeyframe.Value.X;
|
||||||
var yDiff = nextKeyframe.Value.Y - currentKeyframe.Value.Y;
|
var yDiff = nextKeyframe.Value.Y - currentKeyframe.Value.Y;
|
||||||
return new SKPoint(currentKeyframe.Value.X + xDiff * KeyframeProgress, currentKeyframe.Value.Y + yDiff * KeyframeProgress);
|
return new SKPoint(currentKeyframe.Value.X + xDiff * KeyframeProgressEased, currentKeyframe.Value.Y + yDiff * KeyframeProgressEased);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
|||||||
|
|
||||||
var widthDiff = nextKeyframe.Value.Width - currentKeyframe.Value.Width;
|
var widthDiff = nextKeyframe.Value.Width - currentKeyframe.Value.Width;
|
||||||
var heightDiff = nextKeyframe.Value.Height - currentKeyframe.Value.Height;
|
var heightDiff = nextKeyframe.Value.Height - currentKeyframe.Value.Height;
|
||||||
return new SKSize(currentKeyframe.Value.Width + widthDiff * KeyframeProgress, currentKeyframe.Value.Height + heightDiff * KeyframeProgress);
|
return new SKSize(currentKeyframe.Value.Width + widthDiff * KeyframeProgressEased, currentKeyframe.Value.Height + heightDiff * KeyframeProgressEased);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +48,6 @@ namespace Artemis.Core.Models.Profile
|
|||||||
_leds = new List<ArtemisLed>();
|
_leds = new List<ArtemisLed>();
|
||||||
_properties = new Dictionary<string, BaseLayerProperty>();
|
_properties = new Dictionary<string, BaseLayerProperty>();
|
||||||
|
|
||||||
// TODO: Load properties from entity instead of creating the defaults
|
|
||||||
CreateDefaultProperties();
|
CreateDefaultProperties();
|
||||||
|
|
||||||
switch (layerEntity.ShapeEntity?.Type)
|
switch (layerEntity.ShapeEntity?.Type)
|
||||||
@ -355,7 +354,7 @@ namespace Artemis.Core.Models.Profile
|
|||||||
|
|
||||||
private void CreateDefaultProperties()
|
private void CreateDefaultProperties()
|
||||||
{
|
{
|
||||||
var transformProperty = new LayerProperty<object>(this, null, "Core.Transform", "Transform", "The default properties collection every layer has, allows you to transform the shape.");
|
var transformProperty = new LayerProperty<object>(this, null, "Core.Transform", "Transform", "The default properties collection every layer has, allows you to transform the shape.") {ExpandByDefault = true};
|
||||||
AnchorPointProperty = new LayerProperty<SKPoint>(this, transformProperty, "Core.AnchorPoint", "Anchor Point", "The point at which the shape is attached to its position.");
|
AnchorPointProperty = new LayerProperty<SKPoint>(this, transformProperty, "Core.AnchorPoint", "Anchor Point", "The point at which the shape is attached to its position.");
|
||||||
PositionProperty = new LayerProperty<SKPoint>(this, transformProperty, "Core.Position", "Position", "The position of the shape.");
|
PositionProperty = new LayerProperty<SKPoint>(this, transformProperty, "Core.Position", "Position", "The position of the shape.");
|
||||||
SizeProperty = new LayerProperty<SKSize>(this, transformProperty, "Core.Size", "Size", "The size of the shape.") {InputAffix = "%"};
|
SizeProperty = new LayerProperty<SKSize>(this, transformProperty, "Core.Size", "Size", "The size of the shape.") {InputAffix = "%"};
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Core.Utilities;
|
||||||
|
|
||||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||||
{
|
{
|
||||||
public class BaseKeyframe
|
public class BaseKeyframe
|
||||||
{
|
{
|
||||||
|
private TimeSpan _position;
|
||||||
|
|
||||||
protected BaseKeyframe(Layer layer, BaseLayerProperty property)
|
protected BaseKeyframe(Layer layer, BaseLayerProperty property)
|
||||||
{
|
{
|
||||||
Layer = layer;
|
Layer = layer;
|
||||||
@ -11,9 +14,20 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Layer Layer { get; set; }
|
public Layer Layer { get; set; }
|
||||||
public TimeSpan Position { get; set; }
|
|
||||||
|
public TimeSpan Position
|
||||||
|
{
|
||||||
|
get => _position;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _position) return;
|
||||||
|
_position = value;
|
||||||
|
BaseProperty.SortKeyframes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected BaseLayerProperty BaseProperty { get; }
|
protected BaseLayerProperty BaseProperty { get; }
|
||||||
protected internal object BaseValue { get; set; }
|
protected internal object BaseValue { get; set; }
|
||||||
|
public Easings.Functions EasingFunction { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Exceptions;
|
using Artemis.Core.Exceptions;
|
||||||
using Artemis.Core.Models.Profile.KeyframeEngines;
|
using Artemis.Core.Models.Profile.KeyframeEngines;
|
||||||
|
using Artemis.Core.Utilities;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
@ -56,6 +57,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to expand this property by default, this is useful for important parent properties.
|
||||||
|
/// </summary>
|
||||||
|
public bool ExpandByDefault { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An optional input prefix to show before input elements in the UI.
|
/// An optional input prefix to show before input elements in the UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -114,7 +120,8 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
propertyEntity.KeyframeEntities.Add(new KeyframeEntity
|
propertyEntity.KeyframeEntities.Add(new KeyframeEntity
|
||||||
{
|
{
|
||||||
Position = baseKeyframe.Position,
|
Position = baseKeyframe.Position,
|
||||||
Value = JsonConvert.SerializeObject(baseKeyframe.BaseValue)
|
Value = JsonConvert.SerializeObject(baseKeyframe.BaseValue),
|
||||||
|
EasingFunction = (int) baseKeyframe.EasingFunction
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,12 +131,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
BaseValue = DeserializePropertyValue(propertyEntity.Value);
|
BaseValue = DeserializePropertyValue(propertyEntity.Value);
|
||||||
|
|
||||||
BaseKeyframes.Clear();
|
BaseKeyframes.Clear();
|
||||||
foreach (var keyframeEntity in propertyEntity.KeyframeEntities)
|
foreach (var keyframeEntity in propertyEntity.KeyframeEntities.OrderBy(e => e.Position))
|
||||||
{
|
{
|
||||||
// Create a strongly typed keyframe or else it cannot be cast later on
|
// Create a strongly typed keyframe or else it cannot be cast later on
|
||||||
var keyframeType = typeof(Keyframe<>);
|
var keyframeType = typeof(Keyframe<>);
|
||||||
var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this);
|
var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this);
|
||||||
|
keyframe.Position = keyframeEntity.Position;
|
||||||
keyframe.BaseValue = DeserializePropertyValue(keyframeEntity.Value);
|
keyframe.BaseValue = DeserializePropertyValue(keyframeEntity.Value);
|
||||||
|
keyframe.EasingFunction = (Easings.Functions) keyframeEntity.EasingFunction;
|
||||||
|
|
||||||
BaseKeyframes.Add(keyframe);
|
BaseKeyframes.Add(keyframe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,18 +156,25 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
keyframe.Position = position;
|
keyframe.Position = position;
|
||||||
keyframe.BaseValue = BaseValue;
|
keyframe.BaseValue = BaseValue;
|
||||||
BaseKeyframes.Add(keyframe);
|
BaseKeyframes.Add(keyframe);
|
||||||
|
SortKeyframes();
|
||||||
|
|
||||||
return keyframe;
|
return keyframe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes all keyframes from the property.
|
/// Removes all keyframes from the property and sets the base value to the current value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ClearKeyframes()
|
public void ClearKeyframes()
|
||||||
{
|
{
|
||||||
|
BaseValue = KeyframeEngine.GetCurrentValue();
|
||||||
BaseKeyframes.Clear();
|
BaseKeyframes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void SortKeyframes()
|
||||||
|
{
|
||||||
|
BaseKeyframes = BaseKeyframes.OrderBy(k => k.Position).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}, {nameof(Description)}: {Description}";
|
return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}, {nameof(Description)}: {Description}";
|
||||||
|
|||||||
@ -36,6 +36,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
public void AddKeyframe(Keyframe<T> keyframe)
|
public void AddKeyframe(Keyframe<T> keyframe)
|
||||||
{
|
{
|
||||||
BaseKeyframes.Add(keyframe);
|
BaseKeyframes.Add(keyframe);
|
||||||
|
SortKeyframes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -45,6 +46,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
public void RemoveKeyframe(Keyframe<T> keyframe)
|
public void RemoveKeyframe(Keyframe<T> keyframe)
|
||||||
{
|
{
|
||||||
BaseKeyframes.Remove(keyframe);
|
BaseKeyframes.Remove(keyframe);
|
||||||
|
SortKeyframes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -21,5 +21,6 @@ namespace Artemis.Storage.Entities.Profile
|
|||||||
{
|
{
|
||||||
public TimeSpan Position { get; set; }
|
public TimeSpan Position { get; set; }
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
|
public int EasingFunction { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +60,12 @@
|
|||||||
</DrawingGroup>
|
</DrawingGroup>
|
||||||
</DrawingImage.Drawing>
|
</DrawingImage.Drawing>
|
||||||
</DrawingImage>
|
</DrawingImage>
|
||||||
|
|
||||||
|
<!-- Disable tab stop/focusable on all content controls -->
|
||||||
|
<Style TargetType="ContentControl">
|
||||||
|
<Setter Property="IsTabStop" Value="False"/>
|
||||||
|
<Setter Property="Focusable" Value="False"/>
|
||||||
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
@ -144,6 +144,7 @@
|
|||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
</ApplicationDefinition>
|
</ApplicationDefinition>
|
||||||
|
<Compile Include="Behaviors\InputBindingBehavior.cs" />
|
||||||
<Compile Include="Bootstrapper.cs" />
|
<Compile Include="Bootstrapper.cs" />
|
||||||
<Compile Include="Converters\ColorToDrawingColorConverter.cs" />
|
<Compile Include="Converters\ColorToDrawingColorConverter.cs" />
|
||||||
<Compile Include="Converters\ColorToSolidColorBrushConverter.cs" />
|
<Compile Include="Converters\ColorToSolidColorBrushConverter.cs" />
|
||||||
@ -171,6 +172,7 @@
|
|||||||
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\PropertyTree\PropertyTreeItemViewModel.cs" />
|
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\PropertyTree\PropertyTreeItemViewModel.cs" />
|
||||||
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\PropertyTree\PropertyTreeParentViewModel.cs" />
|
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\PropertyTree\PropertyTreeParentViewModel.cs" />
|
||||||
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\PropertyTree\PropertyTreeViewModel.cs" />
|
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\PropertyTree\PropertyTreeViewModel.cs" />
|
||||||
|
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\Timeline\PropertyTrackEasingViewModel.cs" />
|
||||||
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\Timeline\PropertyTrackKeyframeViewModel.cs" />
|
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\Timeline\PropertyTrackKeyframeViewModel.cs" />
|
||||||
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\Timeline\PropertyTimelineHeader.cs" />
|
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\Timeline\PropertyTimelineHeader.cs" />
|
||||||
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\Timeline\PropertyTrackViewModel.cs" />
|
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\Timeline\PropertyTrackViewModel.cs" />
|
||||||
|
|||||||
43
src/Artemis.UI/Behaviors/InputBindingBehavior.cs
Normal file
43
src/Artemis.UI/Behaviors/InputBindingBehavior.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Behaviors
|
||||||
|
{
|
||||||
|
public class InputBindingBehavior
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty PropagateInputBindingsToWindowProperty =
|
||||||
|
DependencyProperty.RegisterAttached("PropagateInputBindingsToWindow", typeof(bool), typeof(InputBindingBehavior),
|
||||||
|
new PropertyMetadata(false, OnPropagateInputBindingsToWindowChanged));
|
||||||
|
|
||||||
|
public static bool GetPropagateInputBindingsToWindow(FrameworkElement obj)
|
||||||
|
{
|
||||||
|
return (bool) obj.GetValue(PropagateInputBindingsToWindowProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetPropagateInputBindingsToWindow(FrameworkElement obj, bool value)
|
||||||
|
{
|
||||||
|
obj.SetValue(PropagateInputBindingsToWindowProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
((FrameworkElement) d).Loaded += OnLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var frameworkElement = (FrameworkElement)sender;
|
||||||
|
frameworkElement.Loaded -= OnLoaded;
|
||||||
|
|
||||||
|
var window = Window.GetWindow(frameworkElement);
|
||||||
|
if (window == null) return;
|
||||||
|
|
||||||
|
// Move input bindings from the FrameworkElement to the window.
|
||||||
|
for (var i = frameworkElement.InputBindings.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var inputBinding = frameworkElement.InputBindings[i];
|
||||||
|
window.InputBindings.Add(inputBinding);
|
||||||
|
frameworkElement.InputBindings.Remove(inputBinding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -62,4 +62,14 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
{
|
{
|
||||||
PropertyTimelineViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
|
PropertyTimelineViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IPropertyTrackViewModelFactory : IViewModelFactory
|
||||||
|
{
|
||||||
|
PropertyTrackViewModel Create(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPropertyTrackKeyframeViewModelFactory : IViewModelFactory
|
||||||
|
{
|
||||||
|
PropertyTrackKeyframeViewModel Create(BaseKeyframe keyframe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -6,10 +6,20 @@
|
|||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties"
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties"
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
xmlns:timeline="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
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"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DataContext="{d:DesignInstance local:LayerPropertiesViewModel}">
|
d:DataContext="{d:DesignInstance local:LayerPropertiesViewModel}"
|
||||||
|
behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True">
|
||||||
|
<UserControl.InputBindings>
|
||||||
|
<KeyBinding Command="{s:Action Play}" Key="Space"></KeyBinding>
|
||||||
|
<KeyBinding Command="{s:Action PlayFromStart}" Modifiers="Shift" Key="Space"></KeyBinding>
|
||||||
|
</UserControl.InputBindings>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<s:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||||
|
</UserControl.Resources>
|
||||||
<Grid x:Name="ContainerGrid">
|
<Grid x:Name="ContainerGrid">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@ -28,9 +38,34 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Misc controls & time display -->
|
<!-- Misc controls & time display -->
|
||||||
<StackPanel Grid.Row="0" VerticalAlignment="Center">
|
<DockPanel Grid.Row="0" VerticalAlignment="Center">
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
<StackPanel Orientation="Horizontal">
|
||||||
</StackPanel>
|
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}">
|
||||||
|
<materialDesign:PackIcon Kind="StepForward" />
|
||||||
|
</Button>
|
||||||
|
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}">
|
||||||
|
<StackPanel>
|
||||||
|
<materialDesign:PackIcon Kind="Play" Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||||
|
<materialDesign:PackIcon Kind="Pause" Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}">
|
||||||
|
<materialDesign:PackIcon Kind="SkipBackward" />
|
||||||
|
</Button>
|
||||||
|
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}">
|
||||||
|
<materialDesign:PackIcon Kind="SkipForward" />
|
||||||
|
</Button>
|
||||||
|
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}">
|
||||||
|
<materialDesign:PackIcon Kind="SkipPrevious" />
|
||||||
|
</Button>
|
||||||
|
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}">
|
||||||
|
<materialDesign:PackIcon Kind="SkipNext" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel VerticalAlignment="Center">
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
<!-- Properties tree -->
|
<!-- Properties tree -->
|
||||||
<ScrollViewer Grid.Row="1" x:Name="PropertyTreeScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
|
<ScrollViewer Grid.Row="1" x:Name="PropertyTreeScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
|
||||||
@ -50,15 +85,14 @@
|
|||||||
|
|
||||||
<!-- Timeline header -->
|
<!-- Timeline header -->
|
||||||
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
||||||
<Grid MouseDown="{s:Action TimelineMouseDown}"
|
<Grid Background="{DynamicResource MaterialDesignCardBackground}">
|
||||||
MouseMove="{s:Action TimelineMouseMove}"
|
|
||||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
|
||||||
<!-- Caret -->
|
<!-- Caret -->
|
||||||
<Canvas ZIndex="1"
|
<Canvas ZIndex="1"
|
||||||
Margin="{Binding TimeCaretPosition}"
|
Margin="{Binding TimeCaretPosition}"
|
||||||
Cursor="SizeWE"
|
Cursor="SizeWE"
|
||||||
MouseEnter="{s:Action CaretMouseEnter}"
|
MouseDown="{s:Action TimelineMouseDown}"
|
||||||
MouseLeave="{s:Action CaretMouseLeave}">
|
MouseUp="{s:Action TimelineMouseUp}"
|
||||||
|
MouseMove="{s:Action TimelineMouseMove}">
|
||||||
<Polygon Points="-10,0 0,20, 10,00" Fill="{StaticResource SecondaryAccentBrush}" />
|
<Polygon Points="-10,0 0,20, 10,00" Fill="{StaticResource SecondaryAccentBrush}" />
|
||||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
@ -74,13 +108,13 @@
|
|||||||
|
|
||||||
<!-- Timeline rails -->
|
<!-- Timeline rails -->
|
||||||
<ScrollViewer Grid.Row="1" x:Name="TimelineRailsScrollViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
<ScrollViewer Grid.Row="1" x:Name="TimelineRailsScrollViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
||||||
<Grid MouseDown="{s:Action TimelineMouseDown}"
|
<Grid>
|
||||||
MouseMove="{s:Action TimelineMouseMove}">
|
|
||||||
<Canvas ZIndex="1"
|
<Canvas ZIndex="1"
|
||||||
Margin="{Binding TimeCaretPosition}"
|
Margin="{Binding TimeCaretPosition}"
|
||||||
Cursor="SizeWE"
|
Cursor="SizeWE"
|
||||||
MouseEnter="{s:Action CaretMouseEnter}"
|
MouseDown="{s:Action TimelineMouseDown}"
|
||||||
MouseLeave="{s:Action CaretMouseLeave}">
|
MouseUp="{s:Action TimelineMouseUp}"
|
||||||
|
MouseMove="{s:Action TimelineMouseMove}">
|
||||||
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
<ContentControl x:Name="PropertyTimeLine" s:View.Model="{Binding PropertyTimeline}" />
|
<ContentControl x:Name="PropertyTimeLine" s:View.Model="{Binding PropertyTimeline}" />
|
||||||
@ -102,11 +136,10 @@
|
|||||||
<Slider Orientation="Horizontal"
|
<Slider Orientation="Horizontal"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Margin="10"
|
Margin="10"
|
||||||
TickFrequency="5"
|
Minimum="31"
|
||||||
Minimum="1"
|
Maximum="350"
|
||||||
Maximum="600"
|
|
||||||
Value="{Binding PixelsPerSecond}"
|
Value="{Binding PixelsPerSecond}"
|
||||||
Width="600" />
|
Width="319" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,28 +2,39 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Artemis.Core.Events;
|
||||||
using Artemis.Core.Models.Profile;
|
using Artemis.Core.Models.Profile;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.Core.Services.Interfaces;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||||
{
|
{
|
||||||
public class LayerPropertiesViewModel : ProfileEditorPanelViewModel
|
public class LayerPropertiesViewModel : ProfileEditorPanelViewModel
|
||||||
{
|
{
|
||||||
|
private readonly ICoreService _coreService;
|
||||||
private readonly ILayerPropertyViewModelFactory _layerPropertyViewModelFactory;
|
private readonly ILayerPropertyViewModelFactory _layerPropertyViewModelFactory;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
|
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
|
||||||
|
ICoreService coreService,
|
||||||
|
ISettingsService settingsService,
|
||||||
ILayerPropertyViewModelFactory layerPropertyViewModelFactory,
|
ILayerPropertyViewModelFactory layerPropertyViewModelFactory,
|
||||||
IPropertyTreeViewModelFactory propertyTreeViewModelFactory,
|
IPropertyTreeViewModelFactory propertyTreeViewModelFactory,
|
||||||
IPropertyTimelineViewModelFactory propertyTimelineViewModelFactory)
|
IPropertyTimelineViewModelFactory propertyTimelineViewModelFactory)
|
||||||
{
|
{
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
|
_coreService = coreService;
|
||||||
|
_settingsService = settingsService;
|
||||||
_layerPropertyViewModelFactory = layerPropertyViewModelFactory;
|
_layerPropertyViewModelFactory = layerPropertyViewModelFactory;
|
||||||
|
|
||||||
PixelsPerSecond = 1;
|
PixelsPerSecond = 31;
|
||||||
PropertyTree = propertyTreeViewModelFactory.Create(this);
|
PropertyTree = propertyTreeViewModelFactory.Create(this);
|
||||||
PropertyTimeline = propertyTimelineViewModelFactory.Create(this);
|
PropertyTimeline = propertyTimelineViewModelFactory.Create(this);
|
||||||
|
|
||||||
@ -33,17 +44,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
_profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
|
_profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FormattedCurrentTime
|
public bool Playing { get; set; }
|
||||||
{
|
|
||||||
get
|
public string FormattedCurrentTime => $"{Math.Floor(_profileEditorService.CurrentTime.TotalSeconds):00}.{_profileEditorService.CurrentTime.Milliseconds:000}";
|
||||||
{
|
|
||||||
if (PixelsPerSecond > 200)
|
|
||||||
return $"{Math.Floor(_profileEditorService.CurrentTime.TotalSeconds):00}.{_profileEditorService.CurrentTime.Milliseconds:000}";
|
|
||||||
if (PixelsPerSecond > 60)
|
|
||||||
return $"{Math.Floor(_profileEditorService.CurrentTime.TotalSeconds):00}.{_profileEditorService.CurrentTime.Milliseconds:000}";
|
|
||||||
return $"{Math.Floor(_profileEditorService.CurrentTime.TotalMinutes):0}:{_profileEditorService.CurrentTime.Seconds:00}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int PixelsPerSecond
|
public int PixelsPerSecond
|
||||||
{
|
{
|
||||||
@ -90,48 +93,145 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
NotifyOfPropertyChange(() => TimeCaretPosition);
|
NotifyOfPropertyChange(() => TimeCaretPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDeactivate()
|
||||||
|
{
|
||||||
|
Pause();
|
||||||
|
base.OnDeactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Controls
|
||||||
|
|
||||||
|
public void PlayFromStart()
|
||||||
|
{
|
||||||
|
if (!IsActive)
|
||||||
|
return;
|
||||||
|
if (Playing)
|
||||||
|
Pause();
|
||||||
|
|
||||||
|
_profileEditorService.CurrentTime = TimeSpan.Zero;
|
||||||
|
Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play()
|
||||||
|
{
|
||||||
|
if (!IsActive)
|
||||||
|
return;
|
||||||
|
if (Playing)
|
||||||
|
{
|
||||||
|
Pause();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_coreService.FrameRendering += CoreServiceOnFrameRendering;
|
||||||
|
Playing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pause()
|
||||||
|
{
|
||||||
|
if (!Playing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_coreService.FrameRendering -= CoreServiceOnFrameRendering;
|
||||||
|
Playing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void GoToStart()
|
||||||
|
{
|
||||||
|
_profileEditorService.CurrentTime = TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoToEnd()
|
||||||
|
{
|
||||||
|
_profileEditorService.CurrentTime = CalculateEndTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoToPreviousFrame()
|
||||||
|
{
|
||||||
|
var frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
||||||
|
var newTime = Math.Max(0, Math.Round((_profileEditorService.CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
|
||||||
|
_profileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoToNextFrame()
|
||||||
|
{
|
||||||
|
var frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
||||||
|
var newTime = Math.Round((_profileEditorService.CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
|
||||||
|
newTime = Math.Min(newTime, CalculateEndTime().TotalMilliseconds);
|
||||||
|
_profileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan CalculateEndTime()
|
||||||
|
{
|
||||||
|
// End time is the last keyframe + 10 sec
|
||||||
|
var lastKeyFrame = PropertyTimeline.PropertyTrackViewModels.SelectMany(r => r.KeyframeViewModels).OrderByDescending(t => t.Keyframe.Position).FirstOrDefault();
|
||||||
|
return lastKeyFrame?.Keyframe.Position.Add(new TimeSpan(0, 0, 0, 10)) ?? TimeSpan.FromSeconds(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
|
||||||
|
{
|
||||||
|
Execute.PostToUIThread(() =>
|
||||||
|
{
|
||||||
|
var newTime = _profileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
|
||||||
|
if (newTime > CalculateEndTime())
|
||||||
|
{
|
||||||
|
newTime = CalculateEndTime();
|
||||||
|
Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
_profileEditorService.CurrentTime = newTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Caret movement
|
#region Caret movement
|
||||||
|
|
||||||
private double _caretStartMouseStartOffset;
|
|
||||||
private bool _mouseOverCaret;
|
|
||||||
private int _pixelsPerSecond;
|
private int _pixelsPerSecond;
|
||||||
|
|
||||||
public void TimelineMouseDown(object sender, MouseButtonEventArgs e)
|
public void TimelineMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
// TODO Preserve mouse offset
|
((IInputElement) sender).CaptureMouse();
|
||||||
_caretStartMouseStartOffset = e.GetPosition((IInputElement) sender).X - TimeCaretPosition.Left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CaretMouseEnter(object sender, MouseEventArgs e)
|
public void TimelineMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
_mouseOverCaret = true;
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
}
|
|
||||||
|
|
||||||
public void CaretMouseLeave(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.LeftButton != MouseButtonState.Pressed)
|
|
||||||
_mouseOverCaret = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TimelineMouseMove(object sender, MouseEventArgs e)
|
public void TimelineMouseMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (_mouseOverCaret && e.LeftButton == MouseButtonState.Pressed)
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
{
|
{
|
||||||
// Snap to visible keyframes
|
// Get the parent grid, need that for our position
|
||||||
var visibleKeyframes = PropertyTimeline.PropertyTrackViewModels.Where(t => t.LayerPropertyViewModel.Parent != null &&
|
var parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
|
||||||
t.LayerPropertyViewModel.Parent.IsExpanded)
|
var x = Math.Max(0, e.GetPosition(parent).X);
|
||||||
|
var newTime = TimeSpan.FromSeconds(x / PixelsPerSecond);
|
||||||
|
|
||||||
|
// Round the time to something that fits the current zoom level
|
||||||
|
if (PixelsPerSecond < 200)
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||||
|
else if (PixelsPerSecond < 500)
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||||
|
else
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||||
|
|
||||||
|
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||||
|
{
|
||||||
|
_profileEditorService.CurrentTime = newTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If shift is held, snap to closest keyframe
|
||||||
|
var visibleKeyframes = PropertyTimeline.PropertyTrackViewModels
|
||||||
|
.Where(t => t.LayerPropertyViewModel.Parent != null && t.LayerPropertyViewModel.Parent.IsExpanded)
|
||||||
.SelectMany(t => t.KeyframeViewModels);
|
.SelectMany(t => t.KeyframeViewModels);
|
||||||
|
|
||||||
TimeCaretPosition = new Thickness(Math.Max(0, e.GetPosition((IInputElement) sender).X + _caretStartMouseStartOffset), 0, 0, 0);
|
|
||||||
|
|
||||||
|
|
||||||
// Take a tolerance of 5 pixels (half a keyframe width)
|
// Take a tolerance of 5 pixels (half a keyframe width)
|
||||||
var tolerance = 1000f / PixelsPerSecond * 5;
|
var tolerance = 1000f / PixelsPerSecond * 5;
|
||||||
var closeKeyframe = visibleKeyframes.FirstOrDefault(
|
var closeKeyframe = visibleKeyframes.FirstOrDefault(
|
||||||
kf => Math.Abs(kf.Keyframe.Position.TotalMilliseconds - _profileEditorService.CurrentTime.TotalMilliseconds) < tolerance
|
kf => Math.Abs(kf.Keyframe.Position.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance
|
||||||
);
|
);
|
||||||
if (closeKeyframe != null)
|
_profileEditorService.CurrentTime = closeKeyframe?.Keyframe.Position ?? newTime;
|
||||||
_profileEditorService.CurrentTime = closeKeyframe.Keyframe.Position;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
LayerProperty = layerProperty;
|
LayerProperty = layerProperty;
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
Children = new List<LayerPropertyViewModel>();
|
Children = new List<LayerPropertyViewModel>();
|
||||||
|
IsExpanded = layerProperty.ExpandByDefault;
|
||||||
|
|
||||||
foreach (var child in layerProperty.Children)
|
foreach (var child in layerProperty.Children)
|
||||||
Children.Add(layerPropertyViewModelFactory.Create(child, this));
|
Children.Add(layerPropertyViewModelFactory.Create(child, this));
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
@ -9,10 +10,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
public class PropertyTimelineViewModel : PropertyChangedBase
|
public class PropertyTimelineViewModel : PropertyChangedBase
|
||||||
{
|
{
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private readonly IPropertyTrackViewModelFactory _propertyTrackViewModelFactory;
|
||||||
|
|
||||||
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService)
|
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel,
|
||||||
|
IProfileEditorService profileEditorService,
|
||||||
|
IPropertyTrackViewModelFactory propertyTrackViewModelFactory)
|
||||||
{
|
{
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
|
_propertyTrackViewModelFactory = propertyTrackViewModelFactory;
|
||||||
|
|
||||||
LayerPropertiesViewModel = layerPropertiesViewModel;
|
LayerPropertiesViewModel = layerPropertiesViewModel;
|
||||||
PropertyTrackViewModels = new BindableCollection<PropertyTrackViewModel>();
|
PropertyTrackViewModels = new BindableCollection<PropertyTrackViewModel>();
|
||||||
@ -50,7 +55,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
private void CreateViewModels(LayerPropertyViewModel property)
|
private void CreateViewModels(LayerPropertyViewModel property)
|
||||||
{
|
{
|
||||||
PropertyTrackViewModels.Add(new PropertyTrackViewModel(this, property));
|
PropertyTrackViewModels.Add(_propertyTrackViewModelFactory.Create(this, property));
|
||||||
foreach (var child in property.Children)
|
foreach (var child in property.Children)
|
||||||
CreateViewModels(child);
|
CreateViewModels(child);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Artemis.Core.Utilities;
|
||||||
|
using Humanizer;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||||
|
{
|
||||||
|
public class PropertyTrackEasingViewModel : PropertyChangedBase
|
||||||
|
{
|
||||||
|
private readonly PropertyTrackKeyframeViewModel _keyframeViewModel;
|
||||||
|
private bool _isEasingModeSelected;
|
||||||
|
|
||||||
|
public PropertyTrackEasingViewModel(PropertyTrackKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction)
|
||||||
|
{
|
||||||
|
_keyframeViewModel = keyframeViewModel;
|
||||||
|
_isEasingModeSelected = keyframeViewModel.Keyframe.EasingFunction == easingFunction;
|
||||||
|
|
||||||
|
EasingFunction = easingFunction;
|
||||||
|
Description = easingFunction.Humanize();
|
||||||
|
|
||||||
|
CreateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Easings.Functions EasingFunction { get; }
|
||||||
|
public PointCollection EasingPoints { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public bool IsEasingModeSelected
|
||||||
|
{
|
||||||
|
get => _isEasingModeSelected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isEasingModeSelected = value;
|
||||||
|
if (_isEasingModeSelected)
|
||||||
|
_keyframeViewModel.SelectEasingMode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateGeometry()
|
||||||
|
{
|
||||||
|
EasingPoints = new PointCollection();
|
||||||
|
for (var i = 1; i <= 10; i++)
|
||||||
|
{
|
||||||
|
var x = i;
|
||||||
|
var y = Easings.Interpolate(i / 10.0, EasingFunction) * 10;
|
||||||
|
EasingPoints.Add(new Point(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,25 +1,114 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
using Artemis.Core.Models.Profile.LayerProperties;
|
using Artemis.Core.Models.Profile.LayerProperties;
|
||||||
|
using Artemis.Core.Utilities;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||||
{
|
{
|
||||||
public class PropertyTrackKeyframeViewModel : PropertyChangedBase
|
public class PropertyTrackKeyframeViewModel : PropertyChangedBase
|
||||||
{
|
{
|
||||||
public PropertyTrackKeyframeViewModel(BaseKeyframe keyframe)
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private int _pixelsPerSecond;
|
||||||
|
|
||||||
|
public PropertyTrackKeyframeViewModel(BaseKeyframe keyframe, IProfileEditorService profileEditorService)
|
||||||
{
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
|
||||||
Keyframe = keyframe;
|
Keyframe = keyframe;
|
||||||
|
EasingViewModels = new BindableCollection<PropertyTrackEasingViewModel>();
|
||||||
|
CreateEasingViewModels();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseKeyframe Keyframe { get; }
|
public BaseKeyframe Keyframe { get; }
|
||||||
|
public BindableCollection<PropertyTrackEasingViewModel> EasingViewModels { get; set; }
|
||||||
public double X { get; set; }
|
public double X { get; set; }
|
||||||
public string Timestamp { get; set; }
|
public string Timestamp { get; set; }
|
||||||
|
|
||||||
|
public UIElement ParentView { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public void Update(int pixelsPerSecond)
|
public void Update(int pixelsPerSecond)
|
||||||
{
|
{
|
||||||
|
_pixelsPerSecond = pixelsPerSecond;
|
||||||
|
|
||||||
X = pixelsPerSecond * Keyframe.Position.TotalSeconds;
|
X = pixelsPerSecond * Keyframe.Position.TotalSeconds;
|
||||||
Timestamp = $"{Math.Floor(Keyframe.Position.TotalSeconds):00}.{Keyframe.Position.Milliseconds:000}";
|
Timestamp = $"{Math.Floor(Keyframe.Position.TotalSeconds):00}.{Keyframe.Position.Milliseconds:000}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Keyframe movement
|
||||||
|
|
||||||
|
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KeyframeMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KeyframeMouseMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
{
|
||||||
|
// Get the parent grid, need that for our position
|
||||||
|
var x = Math.Max(0, e.GetPosition(ParentView).X);
|
||||||
|
var newTime = TimeSpan.FromSeconds(x / _pixelsPerSecond);
|
||||||
|
|
||||||
|
// Round the time to something that fits the current zoom level
|
||||||
|
if (_pixelsPerSecond < 200)
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||||
|
else if (_pixelsPerSecond < 500)
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||||
|
else
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||||
|
|
||||||
|
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||||
|
{
|
||||||
|
Keyframe.Position = newTime;
|
||||||
|
|
||||||
|
Update(_pixelsPerSecond);
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If shift is held, snap to the current time
|
||||||
|
// Take a tolerance of 5 pixels (half a keyframe width)
|
||||||
|
var tolerance = 1000f / _pixelsPerSecond * 5;
|
||||||
|
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance)
|
||||||
|
Keyframe.Position = _profileEditorService.CurrentTime;
|
||||||
|
else
|
||||||
|
Keyframe.Position = newTime;
|
||||||
|
|
||||||
|
Update(_pixelsPerSecond);
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Easing
|
||||||
|
|
||||||
|
private void CreateEasingViewModels()
|
||||||
|
{
|
||||||
|
foreach (Easings.Functions value in Enum.GetValues(typeof(Easings.Functions)))
|
||||||
|
EasingViewModels.Add(new PropertyTrackEasingViewModel(this, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectEasingMode(PropertyTrackEasingViewModel easingViewModel)
|
||||||
|
{
|
||||||
|
Keyframe.EasingFunction = easingViewModel.EasingFunction;
|
||||||
|
// Set every selection to false except on the VM that made the change
|
||||||
|
foreach (var propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
|
||||||
|
propertyTrackEasingViewModel.IsEasingModeSelected = false;
|
||||||
|
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +28,48 @@
|
|||||||
</ItemsControl.ItemContainerStyle>
|
</ItemsControl.ItemContainerStyle>
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}" Width="10" Height="10" Margin="-5,6,0,0" ToolTip="{Binding Timestamp}" />
|
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
|
||||||
|
Width="10"
|
||||||
|
Height="10"
|
||||||
|
Margin="-5,6,0,0"
|
||||||
|
ToolTip="{Binding Timestamp}"
|
||||||
|
s:View.ActionTarget="{Binding}"
|
||||||
|
MouseDown="{s:Action KeyframeMouseDown}"
|
||||||
|
MouseUp="{s:Action KeyframeMouseUp}"
|
||||||
|
MouseMove="{s:Action KeyframeMouseMove}">
|
||||||
|
<Ellipse.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="Copy" />
|
||||||
|
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
|
||||||
|
<MenuItem.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||||
|
<Setter Property="IsCheckable" Value="True" />
|
||||||
|
<Setter Property="IsChecked" Value="{Binding Path=IsEasingModeSelected, Mode=TwoWay}" />
|
||||||
|
</Style>
|
||||||
|
</MenuItem.ItemContainerStyle>
|
||||||
|
<MenuItem.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Points="{Binding EasingPoints}"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="0 0 10 0" />
|
||||||
|
<TextBlock Text="{Binding Description}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</MenuItem.ItemTemplate>
|
||||||
|
</MenuItem>
|
||||||
|
<!-- <MenuItem Header="Easing mode" IsEnabled="{Binding CanSelectEasingMode}"> -->
|
||||||
|
<!-- <MenuItem Header="Ease in" Command="{s:Action SetEasingMode}" CommandParameter="EaseIn" /> -->
|
||||||
|
<!-- <MenuItem Header="Ease out" Command="{s:Action SetEasingMode}" CommandParameter="EaseOut" /> -->
|
||||||
|
<!-- <MenuItem Header="Ease in and out" Command="{s:Action SetEasingMode}" CommandParameter="EaseInOut" /> -->
|
||||||
|
<!-- </MenuItem> -->
|
||||||
|
</ContextMenu>
|
||||||
|
</Ellipse.ContextMenu>
|
||||||
|
</Ellipse>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||||
{
|
{
|
||||||
public class PropertyTrackViewModel : PropertyChangedBase
|
public class PropertyTrackViewModel : Screen
|
||||||
{
|
{
|
||||||
public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel)
|
private readonly IPropertyTrackKeyframeViewModelFactory _propertyTrackKeyframeViewModelFactory;
|
||||||
|
|
||||||
|
public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel,
|
||||||
|
LayerPropertyViewModel layerPropertyViewModel,
|
||||||
|
IPropertyTrackKeyframeViewModelFactory propertyTrackKeyframeViewModelFactory)
|
||||||
{
|
{
|
||||||
|
_propertyTrackKeyframeViewModelFactory = propertyTrackKeyframeViewModelFactory;
|
||||||
PropertyTimelineViewModel = propertyTimelineViewModel;
|
PropertyTimelineViewModel = propertyTimelineViewModel;
|
||||||
LayerPropertyViewModel = layerPropertyViewModel;
|
LayerPropertyViewModel = layerPropertyViewModel;
|
||||||
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
|
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
|
||||||
@ -33,7 +39,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
{
|
{
|
||||||
if (KeyframeViewModels.Any(k => k.Keyframe == keyframe))
|
if (KeyframeViewModels.Any(k => k.Keyframe == keyframe))
|
||||||
continue;
|
continue;
|
||||||
KeyframeViewModels.Add(new PropertyTrackKeyframeViewModel(keyframe));
|
KeyframeViewModels.Add(_propertyTrackKeyframeViewModelFactory.Create(keyframe));
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
|
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
|
||||||
@ -42,7 +48,17 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
public void UpdateKeyframes(int pixelsPerSecond)
|
public void UpdateKeyframes(int pixelsPerSecond)
|
||||||
{
|
{
|
||||||
foreach (var keyframeViewModel in KeyframeViewModels)
|
foreach (var keyframeViewModel in KeyframeViewModels)
|
||||||
|
{
|
||||||
|
keyframeViewModel.ParentView = View;
|
||||||
keyframeViewModel.Update(pixelsPerSecond);
|
keyframeViewModel.Update(pixelsPerSecond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnViewLoaded()
|
||||||
|
{
|
||||||
|
foreach (var keyframeViewModel in KeyframeViewModels)
|
||||||
|
keyframeViewModel.ParentView = View;
|
||||||
|
base.OnViewLoaded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,8 +8,9 @@
|
|||||||
xmlns:visualization="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
xmlns:visualization="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileDeviceViewModel}}"
|
d:DesignHeight="450"
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileDeviceViewModel}}">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||||
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance {x:Type local:ProfileLayerViewModel}}">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<Style TargetType="{x:Type Canvas}" x:Key="SelectedStyle">
|
<Style TargetType="{x:Type Canvas}" x:Key="SelectedStyle">
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
@ -61,5 +63,13 @@
|
|||||||
<SolidColorBrush Color="{StaticResource Accent400}" />
|
<SolidColorBrush Color="{StaticResource Accent400}" />
|
||||||
</Path.Stroke>
|
</Path.Stroke>
|
||||||
</Path>
|
</Path>
|
||||||
|
|
||||||
|
<!-- The rectangle around the shape that allows modification -->
|
||||||
|
<Rectangle Width="{Binding ShapeRectangle.Width}"
|
||||||
|
Height="{Binding ShapeRectangle.Height}"
|
||||||
|
Canvas.Left="{Binding ShapeRectangle.X}"
|
||||||
|
Canvas.Top="{Binding ShapeRectangle.Y}" Stroke="{DynamicResource PrimaryHueMidBrush}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
StrokeDashArray="2 2" />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -33,6 +33,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
public Geometry LayerGeometry { get; set; }
|
public Geometry LayerGeometry { get; set; }
|
||||||
public Geometry OpacityGeometry { get; set; }
|
public Geometry OpacityGeometry { get; set; }
|
||||||
public Geometry ShapeGeometry { get; set; }
|
public Geometry ShapeGeometry { get; set; }
|
||||||
|
public Rect ShapeRectangle { get; set; }
|
||||||
public Rect ViewportRectangle { get; set; }
|
public Rect ViewportRectangle { get; set; }
|
||||||
public bool IsSelected { get; set; }
|
public bool IsSelected { get; set; }
|
||||||
|
|
||||||
@ -99,22 +100,22 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
}
|
}
|
||||||
|
|
||||||
var skRect = Layer.LayerShape.GetUnscaledRectangle();
|
var skRect = Layer.LayerShape.GetUnscaledRectangle();
|
||||||
var rect = new Rect(skRect.Left, skRect.Top, skRect.Width, skRect.Height);
|
ShapeRectangle = new Rect(skRect.Left, skRect.Top, skRect.Width, skRect.Height);
|
||||||
var shapeGeometry = Geometry.Empty;
|
var shapeGeometry = Geometry.Empty;
|
||||||
switch (Layer.LayerShape)
|
switch (Layer.LayerShape)
|
||||||
{
|
{
|
||||||
case Ellipse _:
|
case Ellipse _:
|
||||||
shapeGeometry = new EllipseGeometry(rect);
|
shapeGeometry = new EllipseGeometry(ShapeRectangle);
|
||||||
break;
|
break;
|
||||||
case Fill _:
|
case Fill _:
|
||||||
shapeGeometry = LayerGeometry;
|
shapeGeometry = LayerGeometry;
|
||||||
break;
|
break;
|
||||||
case Polygon _:
|
case Polygon _:
|
||||||
// TODO
|
// TODO
|
||||||
shapeGeometry = new RectangleGeometry(rect);
|
shapeGeometry = new RectangleGeometry(ShapeRectangle);
|
||||||
break;
|
break;
|
||||||
case Rectangle _:
|
case Rectangle _:
|
||||||
shapeGeometry = new RectangleGeometry(rect);
|
shapeGeometry = new RectangleGeometry(ShapeRectangle);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -114,6 +114,33 @@
|
|||||||
</VisualBrush>
|
</VisualBrush>
|
||||||
</Grid.Background>
|
</Grid.Background>
|
||||||
|
|
||||||
|
<Grid Name="DeviceDisplayGrid">
|
||||||
|
<Grid.RenderTransform>
|
||||||
|
<TransformGroup>
|
||||||
|
<ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}" ScaleY="{Binding PanZoomViewModel.Zoom}" />
|
||||||
|
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
|
||||||
|
</TransformGroup>
|
||||||
|
</Grid.RenderTransform>
|
||||||
|
<ItemsControl ItemsSource="{Binding DeviceViewModels}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style TargetType="ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||||
|
<Setter Property="Canvas.Top" Value="{Binding Y}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ContentControl s:View.Model="{Binding}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid Name="EditorDisplayGrid">
|
<Grid Name="EditorDisplayGrid">
|
||||||
<Grid.RenderTransform>
|
<Grid.RenderTransform>
|
||||||
<TransformGroup>
|
<TransformGroup>
|
||||||
@ -141,6 +168,8 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10">
|
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10">
|
||||||
<materialDesign:Card Padding="8">
|
<materialDesign:Card Padding="8">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Artemis.Core.Events;
|
using Artemis.Core.Events;
|
||||||
using Artemis.Core.Models.Profile;
|
using Artemis.Core.Models.Profile;
|
||||||
@ -45,6 +46,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
Execute.OnUIThreadSync(() =>
|
Execute.OnUIThreadSync(() =>
|
||||||
{
|
{
|
||||||
CanvasViewModels = new ObservableCollection<CanvasViewModel>();
|
CanvasViewModels = new ObservableCollection<CanvasViewModel>();
|
||||||
|
DeviceViewModels = new ObservableCollection<ProfileDeviceViewModel>();
|
||||||
PanZoomViewModel = new PanZoomViewModel();
|
PanZoomViewModel = new PanZoomViewModel();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,16 +64,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
|
|
||||||
public bool IsInitializing { get; private set; }
|
public bool IsInitializing { get; private set; }
|
||||||
public ObservableCollection<CanvasViewModel> CanvasViewModels { get; set; }
|
public ObservableCollection<CanvasViewModel> CanvasViewModels { get; set; }
|
||||||
|
public ObservableCollection<ProfileDeviceViewModel> DeviceViewModels { get; set; }
|
||||||
public PanZoomViewModel PanZoomViewModel { get; set; }
|
public PanZoomViewModel PanZoomViewModel { get; set; }
|
||||||
public PluginSetting<bool> HighlightSelectedLayer { get; set; }
|
public PluginSetting<bool> HighlightSelectedLayer { get; set; }
|
||||||
public PluginSetting<bool> PauseRenderingOnFocusLoss { get; set; }
|
public PluginSetting<bool> PauseRenderingOnFocusLoss { get; set; }
|
||||||
|
|
||||||
public ReadOnlyCollection<ProfileDeviceViewModel> Devices => CanvasViewModels
|
|
||||||
.Where(vm => vm is ProfileDeviceViewModel)
|
|
||||||
.Cast<ProfileDeviceViewModel>()
|
|
||||||
.ToList()
|
|
||||||
.AsReadOnly();
|
|
||||||
|
|
||||||
public VisualizationToolViewModel ActiveToolViewModel
|
public VisualizationToolViewModel ActiveToolViewModel
|
||||||
{
|
{
|
||||||
get => _activeToolViewModel;
|
get => _activeToolViewModel;
|
||||||
@ -141,7 +138,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
var layerViewModels = CanvasViewModels.Where(vm => vm is ProfileLayerViewModel).Cast<ProfileLayerViewModel>().ToList();
|
var layerViewModels = CanvasViewModels.Where(vm => vm is ProfileLayerViewModel).Cast<ProfileLayerViewModel>().ToList();
|
||||||
var layers = _profileEditorService.SelectedProfile?.GetAllLayers() ?? new List<Layer>();
|
var layers = _profileEditorService.SelectedProfile?.GetAllLayers() ?? new List<Layer>();
|
||||||
|
|
||||||
|
|
||||||
// Add new layers missing a VM
|
// Add new layers missing a VM
|
||||||
foreach (var layer in layers)
|
foreach (var layer in layers)
|
||||||
{
|
{
|
||||||
@ -156,74 +152,51 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
profileLayerViewModel.Dispose();
|
profileLayerViewModel.Dispose();
|
||||||
CanvasViewModels.Remove(profileLayerViewModel);
|
CanvasViewModels.Remove(profileLayerViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the devices by ZIndex
|
|
||||||
Execute.PostToUIThread(() =>
|
|
||||||
{
|
|
||||||
foreach (var device in Devices.ToList())
|
|
||||||
CanvasViewModels.Move(CanvasViewModels.IndexOf(device), device.Device.ZIndex - 1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplySurfaceConfiguration(ArtemisSurface surface)
|
private void ApplySurfaceConfiguration(ArtemisSurface surface)
|
||||||
{
|
{
|
||||||
var devices = new List<ArtemisDevice>();
|
|
||||||
devices.AddRange(surface.Devices);
|
|
||||||
|
|
||||||
// Make sure all devices have an up-to-date VM
|
// Make sure all devices have an up-to-date VM
|
||||||
foreach (var surfaceDeviceConfiguration in devices)
|
|
||||||
{
|
|
||||||
// Create VMs for missing devices
|
|
||||||
ProfileDeviceViewModel viewModel;
|
|
||||||
lock (CanvasViewModels)
|
|
||||||
{
|
|
||||||
viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (viewModel == null)
|
|
||||||
{
|
|
||||||
// Create outside the UI thread to avoid slowdowns as much as possible
|
|
||||||
var profileDeviceViewModel = new ProfileDeviceViewModel(surfaceDeviceConfiguration);
|
|
||||||
Execute.PostToUIThread(() =>
|
|
||||||
{
|
|
||||||
// Gotta call IsInitializing on the UI thread or its never gets picked up
|
|
||||||
IsInitializing = true;
|
|
||||||
lock (CanvasViewModels)
|
|
||||||
{
|
|
||||||
CanvasViewModels.Add(profileDeviceViewModel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Update existing devices
|
|
||||||
else
|
|
||||||
viewModel.Device = surfaceDeviceConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Sort the devices by ZIndex
|
|
||||||
Execute.PostToUIThread(() =>
|
Execute.PostToUIThread(() =>
|
||||||
{
|
{
|
||||||
lock (CanvasViewModels)
|
lock (DeviceViewModels)
|
||||||
{
|
{
|
||||||
foreach (var device in Devices.OrderBy(d => d.ZIndex).ToList())
|
var existing = DeviceViewModels.ToList();
|
||||||
|
var deviceViewModels = new List<ProfileDeviceViewModel>();
|
||||||
|
|
||||||
|
// Add missing/update existing
|
||||||
|
foreach (var surfaceDeviceConfiguration in surface.Devices.OrderBy(d => d.ZIndex).ToList())
|
||||||
{
|
{
|
||||||
var newIndex = Math.Max(device.ZIndex - 1, CanvasViewModels.Count - 1);
|
// Create VMs for missing devices
|
||||||
CanvasViewModels.Move(CanvasViewModels.IndexOf(device), newIndex);
|
var viewModel = existing.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
|
||||||
|
if (viewModel == null)
|
||||||
|
{
|
||||||
|
IsInitializing = true;
|
||||||
|
viewModel = new ProfileDeviceViewModel(surfaceDeviceConfiguration);
|
||||||
|
}
|
||||||
|
// Update existing devices
|
||||||
|
else
|
||||||
|
viewModel.Device = surfaceDeviceConfiguration;
|
||||||
|
|
||||||
|
// Add the viewModel to the list of VMs we want to keep
|
||||||
|
deviceViewModels.Add(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeviceViewModels = new ObservableCollection<ProfileDeviceViewModel>(deviceViewModels);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateLeds(object sender, CustomUpdateData customUpdateData)
|
private void UpdateLeds(object sender, CustomUpdateData customUpdateData)
|
||||||
{
|
{
|
||||||
lock (CanvasViewModels)
|
lock (DeviceViewModels)
|
||||||
{
|
{
|
||||||
if (IsInitializing)
|
if (IsInitializing)
|
||||||
IsInitializing = Devices.Any(d => !d.AddedLeds);
|
IsInitializing = DeviceViewModels.Any(d => !d.AddedLeds);
|
||||||
|
|
||||||
foreach (var profileDeviceViewModel in Devices)
|
foreach (var profileDeviceViewModel in DeviceViewModels)
|
||||||
profileDeviceViewModel.Update();
|
profileDeviceViewModel.Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,12 +205,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
{
|
{
|
||||||
if (HighlightSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer)
|
if (HighlightSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer)
|
||||||
{
|
{
|
||||||
foreach (var led in Devices.SelectMany(d => d.Leds))
|
foreach (var led in DeviceViewModels.SelectMany(d => d.Leds))
|
||||||
led.IsDimmed = !layer.Leds.Contains(led.Led);
|
led.IsDimmed = !layer.Leds.Contains(led.Led);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var led in Devices.SelectMany(d => d.Leds))
|
foreach (var led in DeviceViewModels.SelectMany(d => d.Leds))
|
||||||
led.IsDimmed = false;
|
led.IsDimmed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,11 +289,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
|
|
||||||
public void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
public void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
ActiveToolViewModel?.MouseDown(sender, e);
|
ActiveToolViewModel?.MouseDown(sender, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
public void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
ActiveToolViewModel?.MouseUp(sender, e);
|
ActiveToolViewModel?.MouseUp(sender, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,26 +326,26 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
layer.ClearLeds();
|
layer.ClearLeds();
|
||||||
layer.AddLeds(Devices.SelectMany(d => d.Leds).Where(vm => vm.IsSelected).Select(vm => vm.Led));
|
layer.AddLeds(DeviceViewModels.SelectMany(d => d.Leds).Where(vm => vm.IsSelected).Select(vm => vm.Led));
|
||||||
|
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectAll()
|
public void SelectAll()
|
||||||
{
|
{
|
||||||
foreach (var ledVm in Devices.SelectMany(d => d.Leds))
|
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds))
|
||||||
ledVm.IsSelected = true;
|
ledVm.IsSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InverseSelection()
|
public void InverseSelection()
|
||||||
{
|
{
|
||||||
foreach (var ledVm in Devices.SelectMany(d => d.Leds))
|
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds))
|
||||||
ledVm.IsSelected = !ledVm.IsSelected;
|
ledVm.IsSelected = !ledVm.IsSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearSelection()
|
public void ClearSelection()
|
||||||
{
|
{
|
||||||
foreach (var ledVm in Devices.SelectMany(d => d.Leds))
|
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds))
|
||||||
ledVm.IsSelected = false;
|
ledVm.IsSelected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,7 +396,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
|||||||
|
|
||||||
public void Handle(MainWindowKeyEvent message)
|
public void Handle(MainWindowKeyEvent message)
|
||||||
{
|
{
|
||||||
Debug.WriteLine(message.KeyDown);
|
|
||||||
if (message.KeyDown)
|
if (message.KeyDown)
|
||||||
{
|
{
|
||||||
if (ActiveToolIndex != 0)
|
if (ActiveToolIndex != 0)
|
||||||
|
|||||||
@ -23,7 +23,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
|||||||
var position = e.GetPosition((IInputElement) sender);
|
var position = e.GetPosition((IInputElement) sender);
|
||||||
var selectedRect = new Rect(MouseDownStartPosition, position);
|
var selectedRect = new Rect(MouseDownStartPosition, position);
|
||||||
|
|
||||||
foreach (var device in ProfileViewModel.Devices)
|
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||||
{
|
{
|
||||||
foreach (var ledViewModel in device.Leds)
|
foreach (var ledViewModel in device.Leds)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -23,7 +23,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
|||||||
var position = e.GetPosition((IInputElement) sender);
|
var position = e.GetPosition((IInputElement) sender);
|
||||||
var selectedRect = new Rect(MouseDownStartPosition, position);
|
var selectedRect = new Rect(MouseDownStartPosition, position);
|
||||||
|
|
||||||
foreach (var device in ProfileViewModel.Devices)
|
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||||
{
|
{
|
||||||
foreach (var ledViewModel in device.Leds)
|
foreach (var ledViewModel in device.Leds)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -33,7 +33,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
|||||||
|
|
||||||
// Get selected LEDs
|
// Get selected LEDs
|
||||||
var selectedLeds = new List<ArtemisLed>();
|
var selectedLeds = new List<ArtemisLed>();
|
||||||
foreach (var device in ProfileViewModel.Devices)
|
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||||
{
|
{
|
||||||
foreach (var ledViewModel in device.Leds)
|
foreach (var ledViewModel in device.Leds)
|
||||||
{
|
{
|
||||||
@ -90,7 +90,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
|||||||
var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||||
var selectedRect = new Rect(MouseDownStartPosition, position);
|
var selectedRect = new Rect(MouseDownStartPosition, position);
|
||||||
|
|
||||||
foreach (var device in ProfileViewModel.Devices)
|
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||||
{
|
{
|
||||||
foreach (var ledViewModel in device.Leds)
|
foreach (var ledViewModel in device.Leds)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -64,7 +64,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
ApplySelectedSurfaceConfiguration();
|
ApplySelectedSurfaceConfiguration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArtemisSurface CreateSurfaceConfiguration(string name)
|
public ArtemisSurface CreateSurfaceConfiguration(string name)
|
||||||
{
|
{
|
||||||
var config = _surfaceService.CreateSurfaceConfiguration(name);
|
var config = _surfaceService.CreateSurfaceConfiguration(name);
|
||||||
@ -111,22 +111,32 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
private void ApplySelectedSurfaceConfiguration()
|
private void ApplySelectedSurfaceConfiguration()
|
||||||
{
|
{
|
||||||
// Make sure all devices have an up-to-date VM
|
// Make sure all devices have an up-to-date VM
|
||||||
foreach (var surfaceDeviceConfiguration in SelectedSurface.Devices)
|
|
||||||
{
|
|
||||||
// Create VMs for missing devices
|
|
||||||
var viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
|
|
||||||
if (viewModel == null)
|
|
||||||
Execute.PostToUIThread(() => Devices.Add(new SurfaceDeviceViewModel(surfaceDeviceConfiguration)));
|
|
||||||
// Update existing devices
|
|
||||||
else
|
|
||||||
viewModel.Device = surfaceDeviceConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the devices by ZIndex
|
|
||||||
Execute.PostToUIThread(() =>
|
Execute.PostToUIThread(() =>
|
||||||
{
|
{
|
||||||
foreach (var device in Devices.OrderBy(d => d.Device.ZIndex).ToList())
|
lock (Devices)
|
||||||
Devices.Move(Devices.IndexOf(device), device.Device.ZIndex - 1);
|
{
|
||||||
|
var existing = Devices.ToList();
|
||||||
|
var deviceViewModels = new List<SurfaceDeviceViewModel>();
|
||||||
|
|
||||||
|
// Add missing/update existing
|
||||||
|
foreach (var surfaceDeviceConfiguration in SelectedSurface.Devices.OrderBy(d => d.ZIndex).ToList())
|
||||||
|
{
|
||||||
|
// Create VMs for missing devices
|
||||||
|
var viewModel = existing.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
|
||||||
|
if (viewModel == null)
|
||||||
|
{
|
||||||
|
viewModel = new SurfaceDeviceViewModel(surfaceDeviceConfiguration);
|
||||||
|
}
|
||||||
|
// Update existing devices
|
||||||
|
else
|
||||||
|
viewModel.Device = surfaceDeviceConfiguration;
|
||||||
|
|
||||||
|
// Add the viewModel to the list of VMs we want to keep
|
||||||
|
deviceViewModels.Add(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Devices = new ObservableCollection<SurfaceDeviceViewModel>(deviceViewModels);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_surfaceService.SetActiveSurfaceConfiguration(SelectedSurface);
|
_surfaceService.SetActiveSurfaceConfiguration(SelectedSurface);
|
||||||
@ -189,6 +199,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
var deviceViewModel = Devices[i];
|
var deviceViewModel = Devices[i];
|
||||||
deviceViewModel.Device.ZIndex = i + 1;
|
deviceViewModel.Device.ZIndex = i + 1;
|
||||||
}
|
}
|
||||||
|
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BringForward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
public void BringForward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||||
@ -202,6 +213,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
var deviceViewModel = Devices[i];
|
var deviceViewModel = Devices[i];
|
||||||
deviceViewModel.Device.ZIndex = i + 1;
|
deviceViewModel.Device.ZIndex = i + 1;
|
||||||
}
|
}
|
||||||
|
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendToBack(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
public void SendToBack(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||||
@ -212,6 +224,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
var deviceViewModel = Devices[i];
|
var deviceViewModel = Devices[i];
|
||||||
deviceViewModel.Device.ZIndex = i + 1;
|
deviceViewModel.Device.ZIndex = i + 1;
|
||||||
}
|
}
|
||||||
|
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendBackward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
public void SendBackward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||||
@ -224,6 +237,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
var deviceViewModel = Devices[i];
|
var deviceViewModel = Devices[i];
|
||||||
deviceViewModel.Device.ZIndex = i + 1;
|
deviceViewModel.Device.ZIndex = i + 1;
|
||||||
}
|
}
|
||||||
|
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ViewProperties(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
public async Task ViewProperties(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||||
@ -248,6 +262,11 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
// ReSharper disable once UnusedMember.Global - Called from view
|
// ReSharper disable once UnusedMember.Global - Called from view
|
||||||
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)
|
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
|
else
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
|
|
||||||
if (IsPanKeyDown() || e.ChangedButton == MouseButton.Right)
|
if (IsPanKeyDown() || e.ChangedButton == MouseButton.Right)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@ -35,12 +35,14 @@ namespace Artemis.UI.Services
|
|||||||
public void ChangeSelectedProfile(Profile profile)
|
public void ChangeSelectedProfile(Profile profile)
|
||||||
{
|
{
|
||||||
SelectedProfile = profile;
|
SelectedProfile = profile;
|
||||||
|
UpdateProfilePreview();
|
||||||
OnSelectedProfileChanged();
|
OnSelectedProfileChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSelectedProfile()
|
public void UpdateSelectedProfile()
|
||||||
{
|
{
|
||||||
_profileService.UpdateProfile(SelectedProfile, false);
|
_profileService.UpdateProfile(SelectedProfile, false);
|
||||||
|
UpdateProfilePreview();
|
||||||
OnSelectedProfileElementUpdated();
|
OnSelectedProfileElementUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,12 +55,15 @@ namespace Artemis.UI.Services
|
|||||||
public void UpdateSelectedProfileElement()
|
public void UpdateSelectedProfileElement()
|
||||||
{
|
{
|
||||||
_profileService.UpdateProfile(SelectedProfile, true);
|
_profileService.UpdateProfile(SelectedProfile, true);
|
||||||
|
UpdateProfilePreview();
|
||||||
OnSelectedProfileElementUpdated();
|
OnSelectedProfileElementUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void UpdateProfilePreview()
|
private void UpdateProfilePreview()
|
||||||
{
|
{
|
||||||
|
if (SelectedProfile == null)
|
||||||
|
return;
|
||||||
var delta = CurrentTime - _lastUpdateTime;
|
var delta = CurrentTime - _lastUpdateTime;
|
||||||
foreach (var layer in SelectedProfile.GetAllLayers())
|
foreach (var layer in SelectedProfile.GetAllLayers())
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user