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 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 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 Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Utilities;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
{
|
||||
@ -24,10 +25,17 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
public TimeSpan Progress { get; private set; }
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
/// The current keyframe
|
||||
/// </summary>
|
||||
@ -68,28 +76,35 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
var keyframes = LayerProperty.UntypedKeyframes.ToList();
|
||||
Progress = Progress.Add(TimeSpan.FromMilliseconds(deltaTime));
|
||||
|
||||
// TODO Keep them sorted somewhere else, iterating all keyframes multiple times sucks
|
||||
var sortedKeyframes = LayerProperty.UntypedKeyframes.ToList().OrderBy(k => k.Position).ToList();
|
||||
// The current keyframe is the last keyframe before the current time
|
||||
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)
|
||||
{
|
||||
KeyframeProgress = 0;
|
||||
KeyframeProgressEased = 0;
|
||||
}
|
||||
else if (NextKeyframe == null)
|
||||
{
|
||||
KeyframeProgress = 1;
|
||||
KeyframeProgressEased = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the engine's progress to the provided value
|
||||
/// </summary>
|
||||
@ -106,6 +121,8 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
/// <returns></returns>
|
||||
public object GetCurrentValue()
|
||||
{
|
||||
if (CurrentKeyframe == null && LayerProperty.UntypedKeyframes.Any())
|
||||
return LayerProperty.UntypedKeyframes.First().BaseValue;
|
||||
if (CurrentKeyframe == null)
|
||||
return LayerProperty.BaseValue;
|
||||
if (NextKeyframe == null)
|
||||
|
||||
@ -17,7 +17,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
|
||||
var xDiff = nextKeyframe.Value.X - currentKeyframe.Value.X;
|
||||
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 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>();
|
||||
_properties = new Dictionary<string, BaseLayerProperty>();
|
||||
|
||||
// TODO: Load properties from entity instead of creating the defaults
|
||||
CreateDefaultProperties();
|
||||
|
||||
switch (layerEntity.ShapeEntity?.Type)
|
||||
@ -355,7 +354,7 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
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.");
|
||||
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 = "%"};
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using Artemis.Core.Utilities;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
public class BaseKeyframe
|
||||
{
|
||||
private TimeSpan _position;
|
||||
|
||||
protected BaseKeyframe(Layer layer, BaseLayerProperty property)
|
||||
{
|
||||
Layer = layer;
|
||||
@ -11,9 +14,20 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
}
|
||||
|
||||
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 internal object BaseValue { get; set; }
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Models.Profile.KeyframeEngines;
|
||||
using Artemis.Core.Utilities;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -56,6 +57,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
/// </summary>
|
||||
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>
|
||||
/// An optional input prefix to show before input elements in the UI.
|
||||
/// </summary>
|
||||
@ -114,7 +120,8 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
propertyEntity.KeyframeEntities.Add(new KeyframeEntity
|
||||
{
|
||||
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);
|
||||
|
||||
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
|
||||
var keyframeType = typeof(Keyframe<>);
|
||||
var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this);
|
||||
keyframe.Position = keyframeEntity.Position;
|
||||
keyframe.BaseValue = DeserializePropertyValue(keyframeEntity.Value);
|
||||
keyframe.EasingFunction = (Easings.Functions) keyframeEntity.EasingFunction;
|
||||
|
||||
BaseKeyframes.Add(keyframe);
|
||||
}
|
||||
}
|
||||
@ -146,18 +156,25 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
keyframe.Position = position;
|
||||
keyframe.BaseValue = BaseValue;
|
||||
BaseKeyframes.Add(keyframe);
|
||||
SortKeyframes();
|
||||
|
||||
return keyframe;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all keyframes from the property.
|
||||
/// Removes all keyframes from the property and sets the base value to the current value.
|
||||
/// </summary>
|
||||
public void ClearKeyframes()
|
||||
{
|
||||
BaseValue = KeyframeEngine.GetCurrentValue();
|
||||
BaseKeyframes.Clear();
|
||||
}
|
||||
|
||||
internal void SortKeyframes()
|
||||
{
|
||||
BaseKeyframes = BaseKeyframes.OrderBy(k => k.Position).ToList();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
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)
|
||||
{
|
||||
BaseKeyframes.Add(keyframe);
|
||||
SortKeyframes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -45,6 +46,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
public void RemoveKeyframe(Keyframe<T> keyframe)
|
||||
{
|
||||
BaseKeyframes.Remove(keyframe);
|
||||
SortKeyframes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -21,5 +21,6 @@ namespace Artemis.Storage.Entities.Profile
|
||||
{
|
||||
public TimeSpan Position { get; set; }
|
||||
public string Value { get; set; }
|
||||
public int EasingFunction { get; set; }
|
||||
}
|
||||
}
|
||||
@ -60,6 +60,12 @@
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<!-- Disable tab stop/focusable on all content controls -->
|
||||
<Style TargetType="ContentControl">
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@ -144,6 +144,7 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="Behaviors\InputBindingBehavior.cs" />
|
||||
<Compile Include="Bootstrapper.cs" />
|
||||
<Compile Include="Converters\ColorToDrawingColorConverter.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\PropertyTreeParentViewModel.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\PropertyTimelineHeader.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);
|
||||
}
|
||||
|
||||
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:s="https://github.com/canton7/Stylet"
|
||||
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"
|
||||
d:DesignHeight="450"
|
||||
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.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
@ -28,9 +38,34 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Misc controls & time display -->
|
||||
<StackPanel Grid.Row="0" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
||||
</StackPanel>
|
||||
<DockPanel Grid.Row="0" VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<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 -->
|
||||
<ScrollViewer Grid.Row="1" x:Name="PropertyTreeScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
|
||||
@ -50,15 +85,14 @@
|
||||
|
||||
<!-- Timeline header -->
|
||||
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
||||
<Grid MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<Grid Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<!-- Caret -->
|
||||
<Canvas ZIndex="1"
|
||||
Margin="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseEnter="{s:Action CaretMouseEnter}"
|
||||
MouseLeave="{s:Action CaretMouseLeave}">
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}">
|
||||
<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}" />
|
||||
</Canvas>
|
||||
@ -74,13 +108,13 @@
|
||||
|
||||
<!-- Timeline rails -->
|
||||
<ScrollViewer Grid.Row="1" x:Name="TimelineRailsScrollViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
|
||||
<Grid MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseMove="{s:Action TimelineMouseMove}">
|
||||
<Grid>
|
||||
<Canvas ZIndex="1"
|
||||
Margin="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseEnter="{s:Action CaretMouseEnter}"
|
||||
MouseLeave="{s:Action CaretMouseLeave}">
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
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}" />
|
||||
</Canvas>
|
||||
<ContentControl x:Name="PropertyTimeLine" s:View.Model="{Binding PropertyTimeline}" />
|
||||
@ -102,11 +136,10 @@
|
||||
<Slider Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="10"
|
||||
TickFrequency="5"
|
||||
Minimum="1"
|
||||
Maximum="600"
|
||||
Minimum="31"
|
||||
Maximum="350"
|
||||
Value="{Binding PixelsPerSecond}"
|
||||
Width="600" />
|
||||
Width="319" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
{
|
||||
|
||||
@ -2,28 +2,39 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
{
|
||||
public class LayerPropertiesViewModel : ProfileEditorPanelViewModel
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly ILayerPropertyViewModelFactory _layerPropertyViewModelFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
|
||||
ICoreService coreService,
|
||||
ISettingsService settingsService,
|
||||
ILayerPropertyViewModelFactory layerPropertyViewModelFactory,
|
||||
IPropertyTreeViewModelFactory propertyTreeViewModelFactory,
|
||||
IPropertyTimelineViewModelFactory propertyTimelineViewModelFactory)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_coreService = coreService;
|
||||
_settingsService = settingsService;
|
||||
_layerPropertyViewModelFactory = layerPropertyViewModelFactory;
|
||||
|
||||
PixelsPerSecond = 1;
|
||||
PixelsPerSecond = 31;
|
||||
PropertyTree = propertyTreeViewModelFactory.Create(this);
|
||||
PropertyTimeline = propertyTimelineViewModelFactory.Create(this);
|
||||
|
||||
@ -33,17 +44,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
_profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
|
||||
}
|
||||
|
||||
public string FormattedCurrentTime
|
||||
{
|
||||
get
|
||||
{
|
||||
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 bool Playing { get; set; }
|
||||
|
||||
public string FormattedCurrentTime => $"{Math.Floor(_profileEditorService.CurrentTime.TotalSeconds):00}.{_profileEditorService.CurrentTime.Milliseconds:000}";
|
||||
|
||||
public int PixelsPerSecond
|
||||
{
|
||||
@ -90,48 +93,145 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
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
|
||||
|
||||
private double _caretStartMouseStartOffset;
|
||||
private bool _mouseOverCaret;
|
||||
private int _pixelsPerSecond;
|
||||
|
||||
public void TimelineMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// TODO Preserve mouse offset
|
||||
_caretStartMouseStartOffset = e.GetPosition((IInputElement) sender).X - TimeCaretPosition.Left;
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
}
|
||||
|
||||
public void CaretMouseEnter(object sender, MouseEventArgs e)
|
||||
public void TimelineMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_mouseOverCaret = true;
|
||||
}
|
||||
|
||||
public void CaretMouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton != MouseButtonState.Pressed)
|
||||
_mouseOverCaret = false;
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
public void TimelineMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (_mouseOverCaret && e.LeftButton == MouseButtonState.Pressed)
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
// Snap to visible keyframes
|
||||
var visibleKeyframes = PropertyTimeline.PropertyTrackViewModels.Where(t => t.LayerPropertyViewModel.Parent != null &&
|
||||
t.LayerPropertyViewModel.Parent.IsExpanded)
|
||||
// Get the parent grid, need that for our position
|
||||
var parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
|
||||
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);
|
||||
|
||||
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)
|
||||
var tolerance = 1000f / PixelsPerSecond * 5;
|
||||
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;
|
||||
_profileEditorService.CurrentTime = closeKeyframe?.Keyframe.Position ?? newTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
LayerProperty = layerProperty;
|
||||
Parent = parent;
|
||||
Children = new List<LayerPropertyViewModel>();
|
||||
IsExpanded = layerProperty.ExpandByDefault;
|
||||
|
||||
foreach (var child in layerProperty.Children)
|
||||
Children.Add(layerPropertyViewModelFactory.Create(child, this));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
@ -9,10 +10,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
public class PropertyTimelineViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly IPropertyTrackViewModelFactory _propertyTrackViewModelFactory;
|
||||
|
||||
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService)
|
||||
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel,
|
||||
IProfileEditorService profileEditorService,
|
||||
IPropertyTrackViewModelFactory propertyTrackViewModelFactory)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_propertyTrackViewModelFactory = propertyTrackViewModelFactory;
|
||||
|
||||
LayerPropertiesViewModel = layerPropertiesViewModel;
|
||||
PropertyTrackViewModels = new BindableCollection<PropertyTrackViewModel>();
|
||||
@ -50,7 +55,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
|
||||
private void CreateViewModels(LayerPropertyViewModel property)
|
||||
{
|
||||
PropertyTrackViewModels.Add(new PropertyTrackViewModel(this, property));
|
||||
PropertyTrackViewModels.Add(_propertyTrackViewModelFactory.Create(this, property));
|
||||
foreach (var child in property.Children)
|
||||
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.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Utilities;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
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;
|
||||
EasingViewModels = new BindableCollection<PropertyTrackEasingViewModel>();
|
||||
CreateEasingViewModels();
|
||||
}
|
||||
|
||||
public BaseKeyframe Keyframe { get; }
|
||||
|
||||
public BindableCollection<PropertyTrackEasingViewModel> EasingViewModels { get; set; }
|
||||
public double X { get; set; }
|
||||
public string Timestamp { get; set; }
|
||||
|
||||
public UIElement ParentView { get; set; }
|
||||
|
||||
|
||||
public void Update(int pixelsPerSecond)
|
||||
{
|
||||
_pixelsPerSecond = pixelsPerSecond;
|
||||
|
||||
X = pixelsPerSecond * Keyframe.Position.TotalSeconds;
|
||||
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.ItemTemplate>
|
||||
<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>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
using System.Linq;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Stylet;
|
||||
|
||||
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;
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
|
||||
@ -33,7 +39,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
if (KeyframeViewModels.Any(k => k.Keyframe == keyframe))
|
||||
continue;
|
||||
KeyframeViewModels.Add(new PropertyTrackKeyframeViewModel(keyframe));
|
||||
KeyframeViewModels.Add(_propertyTrackKeyframeViewModelFactory.Create(keyframe));
|
||||
}
|
||||
|
||||
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
|
||||
@ -42,7 +48,17 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
public void UpdateKeyframes(int pixelsPerSecond)
|
||||
{
|
||||
foreach (var keyframeViewModel in KeyframeViewModels)
|
||||
{
|
||||
keyframeViewModel.ParentView = View;
|
||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileDeviceViewModel}}"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileDeviceViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
|
||||
@ -5,7 +5,9 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
||||
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>
|
||||
<Style TargetType="{x:Type Canvas}" x:Key="SelectedStyle">
|
||||
<Style.Triggers>
|
||||
@ -61,5 +63,13 @@
|
||||
<SolidColorBrush Color="{StaticResource Accent400}" />
|
||||
</Path.Stroke>
|
||||
</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>
|
||||
</UserControl>
|
||||
@ -33,6 +33,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
public Geometry LayerGeometry { get; set; }
|
||||
public Geometry OpacityGeometry { get; set; }
|
||||
public Geometry ShapeGeometry { get; set; }
|
||||
public Rect ShapeRectangle { get; set; }
|
||||
public Rect ViewportRectangle { get; set; }
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
@ -99,22 +100,22 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
}
|
||||
|
||||
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;
|
||||
switch (Layer.LayerShape)
|
||||
{
|
||||
case Ellipse _:
|
||||
shapeGeometry = new EllipseGeometry(rect);
|
||||
shapeGeometry = new EllipseGeometry(ShapeRectangle);
|
||||
break;
|
||||
case Fill _:
|
||||
shapeGeometry = LayerGeometry;
|
||||
break;
|
||||
case Polygon _:
|
||||
// TODO
|
||||
shapeGeometry = new RectangleGeometry(rect);
|
||||
shapeGeometry = new RectangleGeometry(ShapeRectangle);
|
||||
break;
|
||||
case Rectangle _:
|
||||
shapeGeometry = new RectangleGeometry(rect);
|
||||
shapeGeometry = new RectangleGeometry(ShapeRectangle);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -114,6 +114,33 @@
|
||||
</VisualBrush>
|
||||
</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.RenderTransform>
|
||||
<TransformGroup>
|
||||
@ -141,6 +168,8 @@
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10">
|
||||
<materialDesign:Card Padding="8">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Models.Profile;
|
||||
@ -45,6 +46,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
Execute.OnUIThreadSync(() =>
|
||||
{
|
||||
CanvasViewModels = new ObservableCollection<CanvasViewModel>();
|
||||
DeviceViewModels = new ObservableCollection<ProfileDeviceViewModel>();
|
||||
PanZoomViewModel = new PanZoomViewModel();
|
||||
});
|
||||
|
||||
@ -62,16 +64,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
|
||||
public bool IsInitializing { get; private set; }
|
||||
public ObservableCollection<CanvasViewModel> CanvasViewModels { get; set; }
|
||||
public ObservableCollection<ProfileDeviceViewModel> DeviceViewModels { get; set; }
|
||||
public PanZoomViewModel PanZoomViewModel { get; set; }
|
||||
public PluginSetting<bool> HighlightSelectedLayer { 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
|
||||
{
|
||||
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 layers = _profileEditorService.SelectedProfile?.GetAllLayers() ?? new List<Layer>();
|
||||
|
||||
|
||||
// Add new layers missing a VM
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
@ -156,74 +152,51 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
profileLayerViewModel.Dispose();
|
||||
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)
|
||||
{
|
||||
var devices = new List<ArtemisDevice>();
|
||||
devices.AddRange(surface.Devices);
|
||||
|
||||
// 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(() =>
|
||||
{
|
||||
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);
|
||||
CanvasViewModels.Move(CanvasViewModels.IndexOf(device), newIndex);
|
||||
// Create VMs for missing devices
|
||||
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)
|
||||
{
|
||||
lock (CanvasViewModels)
|
||||
lock (DeviceViewModels)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -232,12 +205,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var led in Devices.SelectMany(d => d.Leds))
|
||||
foreach (var led in DeviceViewModels.SelectMany(d => d.Leds))
|
||||
led.IsDimmed = false;
|
||||
}
|
||||
}
|
||||
@ -316,11 +289,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
|
||||
public void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
ActiveToolViewModel?.MouseDown(sender, e);
|
||||
}
|
||||
|
||||
public void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
ActiveToolViewModel?.MouseUp(sender, e);
|
||||
}
|
||||
|
||||
@ -351,26 +326,26 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
return;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
foreach (var ledVm in Devices.SelectMany(d => d.Leds))
|
||||
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds))
|
||||
ledVm.IsSelected = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
foreach (var ledVm in Devices.SelectMany(d => d.Leds))
|
||||
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds))
|
||||
ledVm.IsSelected = false;
|
||||
}
|
||||
|
||||
@ -421,7 +396,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
|
||||
public void Handle(MainWindowKeyEvent message)
|
||||
{
|
||||
Debug.WriteLine(message.KeyDown);
|
||||
if (message.KeyDown)
|
||||
{
|
||||
if (ActiveToolIndex != 0)
|
||||
|
||||
@ -23,7 +23,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var selectedRect = new Rect(MouseDownStartPosition, position);
|
||||
|
||||
foreach (var device in ProfileViewModel.Devices)
|
||||
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||
{
|
||||
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 selectedRect = new Rect(MouseDownStartPosition, position);
|
||||
|
||||
foreach (var device in ProfileViewModel.Devices)
|
||||
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||
{
|
||||
foreach (var ledViewModel in device.Leds)
|
||||
{
|
||||
|
||||
@ -33,7 +33,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
|
||||
// Get selected LEDs
|
||||
var selectedLeds = new List<ArtemisLed>();
|
||||
foreach (var device in ProfileViewModel.Devices)
|
||||
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||
{
|
||||
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 selectedRect = new Rect(MouseDownStartPosition, position);
|
||||
|
||||
foreach (var device in ProfileViewModel.Devices)
|
||||
foreach (var device in ProfileViewModel.DeviceViewModels)
|
||||
{
|
||||
foreach (var ledViewModel in device.Leds)
|
||||
{
|
||||
|
||||
@ -64,7 +64,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
ApplySelectedSurfaceConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ArtemisSurface CreateSurfaceConfiguration(string name)
|
||||
{
|
||||
var config = _surfaceService.CreateSurfaceConfiguration(name);
|
||||
@ -111,22 +111,32 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
private void ApplySelectedSurfaceConfiguration()
|
||||
{
|
||||
// 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(() =>
|
||||
{
|
||||
foreach (var device in Devices.OrderBy(d => d.Device.ZIndex).ToList())
|
||||
Devices.Move(Devices.IndexOf(device), device.Device.ZIndex - 1);
|
||||
lock (Devices)
|
||||
{
|
||||
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);
|
||||
@ -189,6 +199,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||
}
|
||||
|
||||
public void BringForward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
@ -202,6 +213,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||
}
|
||||
|
||||
public void SendToBack(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
@ -212,6 +224,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||
}
|
||||
|
||||
public void SendBackward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
@ -224,6 +237,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||
}
|
||||
|
||||
public async Task ViewProperties(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
@ -248,6 +262,11 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
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)
|
||||
return;
|
||||
|
||||
|
||||
@ -35,12 +35,14 @@ namespace Artemis.UI.Services
|
||||
public void ChangeSelectedProfile(Profile profile)
|
||||
{
|
||||
SelectedProfile = profile;
|
||||
UpdateProfilePreview();
|
||||
OnSelectedProfileChanged();
|
||||
}
|
||||
|
||||
public void UpdateSelectedProfile()
|
||||
{
|
||||
_profileService.UpdateProfile(SelectedProfile, false);
|
||||
UpdateProfilePreview();
|
||||
OnSelectedProfileElementUpdated();
|
||||
}
|
||||
|
||||
@ -53,12 +55,15 @@ namespace Artemis.UI.Services
|
||||
public void UpdateSelectedProfileElement()
|
||||
{
|
||||
_profileService.UpdateProfile(SelectedProfile, true);
|
||||
UpdateProfilePreview();
|
||||
OnSelectedProfileElementUpdated();
|
||||
}
|
||||
|
||||
|
||||
private void UpdateProfilePreview()
|
||||
{
|
||||
if (SelectedProfile == null)
|
||||
return;
|
||||
var delta = CurrentTime - _lastUpdateTime;
|
||||
foreach (var layer in SelectedProfile.GetAllLayers())
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user