1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-31 17:53:32 +00:00

Added keyframe creation outside the timeline

Added existing shape moving
This commit is contained in:
SpoinkyNL 2020-01-13 22:11:25 +01:00
parent a138ec916d
commit 7ddf816ca5
22 changed files with 178 additions and 148 deletions

View File

@ -21,68 +21,82 @@ namespace Artemis.Core.Models.Profile.LayerProperties
Name = name; Name = name;
Description = description; Description = description;
Type = type; Type = type;
CanUseKeyframes = true;
Children = new List<BaseLayerProperty>(); Children = new List<BaseLayerProperty>();
BaseKeyframes = new List<BaseKeyframe>(); BaseKeyframes = new List<BaseKeyframe>();
} }
/// <summary> /// <summary>
/// The layer this property applies to /// Gets the layer this property applies to
/// </summary> /// </summary>
public Layer Layer { get; } public Layer Layer { get; }
/// <summary> /// <summary>
/// The parent property of this property. /// Gets the parent property of this property.
/// </summary> /// </summary>
public BaseLayerProperty Parent { get; } public BaseLayerProperty Parent { get; }
/// <summary> /// <summary>
/// The child properties of this property. /// Gets or sets the child properties of this property.
/// <remarks>If the layer has children it cannot contain a value or keyframes.</remarks> /// <remarks>If the layer has children it cannot contain a value or keyframes.</remarks>
/// </summary> /// </summary>
public List<BaseLayerProperty> Children { get; set; } public List<BaseLayerProperty> Children { get; set; }
/// <summary> /// <summary>
/// A unique identifier for this property, a layer may not contain two properties with the same ID. /// Gets or sets a unique identifier for this property, a layer may not contain two properties with the same ID.
/// </summary> /// </summary>
public string Id { get; set; } public string Id { get; set; }
/// <summary> /// <summary>
/// The user-friendly name for this property, shown in the UI. /// Gets or sets the user-friendly name for this property, shown in the UI.
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// The user-friendly description for this property, shown in the UI. /// Gets or sets the user-friendly description for this property, shown in the UI.
/// </summary> /// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary> /// <summary>
/// Whether to expand this property by default, this is useful for important parent properties. /// Gets or sets whether to expand this property by default, this is useful for important parent properties.
/// </summary> /// </summary>
public bool ExpandByDefault { get; set; } public bool ExpandByDefault { get; set; }
/// <summary> /// <summary>
/// An optional input prefix to show before input elements in the UI. /// Gets or sets the an optional input prefix to show before input elements in the UI.
/// </summary> /// </summary>
public string InputPrefix { get; set; } public string InputPrefix { get; set; }
/// <summary> /// <summary>
/// An optional input affix to show behind input elements in the UI. /// Gets or sets an optional input affix to show behind input elements in the UI.
/// </summary> /// </summary>
public string InputAffix { get; set; } public string InputAffix { get; set; }
/// <summary> /// <summary>
/// The type of value this layer property contains. /// Gets or sets whether this property can use keyframes, True by default.
/// </summary> /// </summary>
public Type Type { get; set; } public bool CanUseKeyframes { get; set; }
/// <summary> /// <summary>
/// A list of keyframes defining different values of the property in time, this list contains the untyped /// Gets or sets whether this property is using keyframes.
/// </summary>
public bool IsUsingKeyframes { get; set; }
/// <summary>
/// Gets the type of value this layer property contains.
/// </summary>
public Type Type { get; protected set; }
/// <summary>
/// Gets a list of keyframes defining different values of the property in time, this list contains the untyped
/// <see cref="BaseKeyframe" />. /// <see cref="BaseKeyframe" />.
/// </summary> /// </summary>
public IReadOnlyCollection<BaseKeyframe> UntypedKeyframes => BaseKeyframes.AsReadOnly(); public IReadOnlyCollection<BaseKeyframe> UntypedKeyframes => BaseKeyframes.AsReadOnly();
/// <summary>
/// Gets or sets the keyframe engine instance of this property
/// </summary>
public KeyframeEngine KeyframeEngine { get; set; } public KeyframeEngine KeyframeEngine { get; set; }
protected List<BaseKeyframe> BaseKeyframes { get; set; } protected List<BaseKeyframe> BaseKeyframes { get; set; }
@ -113,6 +127,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
propertyEntity.ValueType = Type.Name; propertyEntity.ValueType = Type.Name;
propertyEntity.Value = JsonConvert.SerializeObject(BaseValue); propertyEntity.Value = JsonConvert.SerializeObject(BaseValue);
propertyEntity.IsUsingKeyframes = IsUsingKeyframes;
propertyEntity.KeyframeEntities.Clear(); propertyEntity.KeyframeEntities.Clear();
foreach (var baseKeyframe in BaseKeyframes) foreach (var baseKeyframe in BaseKeyframes)
@ -129,6 +144,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
internal void ApplyToProperty(PropertyEntity propertyEntity) internal void ApplyToProperty(PropertyEntity propertyEntity)
{ {
BaseValue = DeserializePropertyValue(propertyEntity.Value); BaseValue = DeserializePropertyValue(propertyEntity.Value);
IsUsingKeyframes = propertyEntity.IsUsingKeyframes;
BaseKeyframes.Clear(); BaseKeyframes.Clear();
foreach (var keyframeEntity in propertyEntity.KeyframeEntities.OrderBy(e => e.Position)) foreach (var keyframeEntity in propertyEntity.KeyframeEntities.OrderBy(e => e.Position))
@ -170,6 +186,33 @@ namespace Artemis.Core.Models.Profile.LayerProperties
BaseKeyframes.Clear(); BaseKeyframes.Clear();
} }
/// <summary>
/// Gets the current value using the regular value or keyframes.
/// </summary>
/// <param name="value">The value to set.</param>
/// <param name="time">
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
/// or existing keyframe.
/// </param>
public void SetCurrentValue(object value, TimeSpan? time)
{
if (value != null && value.GetType() != Type)
throw new ArtemisCoreException($"Cannot set value of type {value.GetType()} on property {this}, expected type is {Type}.");
if (time == null || !CanUseKeyframes || !IsUsingKeyframes)
BaseValue = value;
else
{
// If on a keyframe, update the keyframe
var currentKeyframe = UntypedKeyframes.FirstOrDefault(k => k.Position == time.Value);
// Create a new keyframe if none found
if (currentKeyframe == null)
currentKeyframe = CreateNewKeyframe(time.Value);
currentKeyframe.BaseValue = value;
}
}
internal void SortKeyframes() internal void SortKeyframes()
{ {
BaseKeyframes = BaseKeyframes.OrderBy(k => k.Position).ToList(); BaseKeyframes = BaseKeyframes.OrderBy(k => k.Position).ToList();

View File

@ -1,4 +1,5 @@
using System.Collections.ObjectModel; using System;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
namespace Artemis.Core.Models.Profile.LayerProperties namespace Artemis.Core.Models.Profile.LayerProperties
@ -10,7 +11,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
} }
/// <summary> /// <summary>
/// The value of the property without any keyframes applied /// Gets or sets the value of the property without any keyframes applied
/// </summary> /// </summary>
public T Value public T Value
{ {
@ -19,12 +20,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties
} }
/// <summary> /// <summary>
/// The value of the property with keyframes applied /// Gets the value of the property with keyframes applied
/// </summary> /// </summary>
public T CurrentValue => KeyframeEngine != null ? (T) KeyframeEngine.GetCurrentValue() : Value; public T CurrentValue => KeyframeEngine != null ? (T) KeyframeEngine.GetCurrentValue() : Value;
/// <summary> /// <summary>
/// A list of keyframes defining different values of the property in time, this list contains the strongly typed /// Gets a list of keyframes defining different values of the property in time, this list contains the strongly typed
/// <see cref="Keyframe{T}" /> /// <see cref="Keyframe{T}" />
/// </summary> /// </summary>
public ReadOnlyCollection<Keyframe<T>> Keyframes => BaseKeyframes.Cast<Keyframe<T>>().ToList().AsReadOnly(); public ReadOnlyCollection<Keyframe<T>> Keyframes => BaseKeyframes.Cast<Keyframe<T>>().ToList().AsReadOnly();
@ -50,7 +51,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
} }
/// <summary> /// <summary>
/// Gets the current value using the keyframes /// Gets the current value using the regular value or if present, keyframes
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public T GetCurrentValue() public T GetCurrentValue()

View File

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using SkiaSharp; using SkiaSharp;
@ -41,13 +42,14 @@ namespace Artemis.Core.Models.Profile.LayerShapes
/// <summary> /// <summary>
/// Updates Position and Size using the provided unscaled rectangle /// Updates Position and Size using the provided unscaled rectangle
/// </summary> /// </summary>
/// <param name="rect">An unscaled rectangle where 1px = 1mm</param> /// <param name="rect">An unscaled rectangle which is relative to the layer (1.0 being full width/height, 0.5 being half).</param>
public void SetFromUnscaledRectangle(SKRect rect) /// <param name="time">An optional timespan to indicate where to set the properties, if null the properties' base values will be used.</param>
public void SetFromUnscaledRectangle(SKRect rect, TimeSpan? time)
{ {
if (!Layer.Leds.Any()) if (!Layer.Leds.Any())
{ {
Layer.PositionProperty.Value = SKPoint.Empty; Layer.PositionProperty.SetCurrentValue(SKPoint.Empty, time);
Layer.SizeProperty.Value = SKSize.Empty; Layer.SizeProperty.SetCurrentValue(SKSize.Empty, time);
return; return;
} }
@ -56,10 +58,9 @@ namespace Artemis.Core.Models.Profile.LayerShapes
var width = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.X + l.RgbLed.AbsoluteLedRectangle.Size.Width) - x; var width = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.X + l.RgbLed.AbsoluteLedRectangle.Size.Width) - x;
var height = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.Y + l.RgbLed.AbsoluteLedRectangle.Size.Height) - y; var height = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.Y + l.RgbLed.AbsoluteLedRectangle.Size.Height) - y;
Layer.PositionProperty.Value = new SKPoint((float) (100f / width * (rect.Left - x)) / 100f, (float) (100f / height * (rect.Top - y)) / 100f); Layer.PositionProperty.SetCurrentValue(new SKPoint((float) (100f / width * (rect.Left - x)) / 100f, (float) (100f / height * (rect.Top - y)) / 100f), time);
Layer.SizeProperty.Value = new SKSize((float) (100f / width * rect.Width) / 100f, (float) (100f / height * rect.Height) / 100f); Layer.SizeProperty.SetCurrentValue(new SKSize((float) (100f / width * rect.Width) / 100f, (float) (100f / height * rect.Height) / 100f), time);
// TODO: Update keyframes
CalculateRenderProperties(Layer.PositionProperty.CurrentValue, Layer.SizeProperty.CurrentValue); CalculateRenderProperties(Layer.PositionProperty.CurrentValue, Layer.SizeProperty.CurrentValue);
} }

View File

@ -13,6 +13,7 @@ namespace Artemis.Storage.Entities.Profile
public string Id { get; set; } public string Id { get; set; }
public string ValueType { get; set; } public string ValueType { get; set; }
public string Value { get; set; } public string Value { get; set; }
public bool IsUsingKeyframes { get; set; }
public List<KeyframeEntity> KeyframeEntities { get; set; } public List<KeyframeEntity> KeyframeEntities { get; set; }
} }

View File

@ -40,25 +40,25 @@
<!-- Misc controls & time display --> <!-- Misc controls & time display -->
<DockPanel Grid.Row="0" VerticalAlignment="Center"> <DockPanel Grid.Row="0" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}" Focusable="False">
<materialDesign:PackIcon Kind="StepForward" /> <materialDesign:PackIcon Kind="StepForward" />
</Button> </Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
<StackPanel> <StackPanel>
<materialDesign:PackIcon Kind="Play" Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" /> <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}" /> <materialDesign:PackIcon Kind="Pause" Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}" Focusable="False">
<materialDesign:PackIcon Kind="SkipBackward" /> <materialDesign:PackIcon Kind="SkipBackward" />
</Button> </Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}" Focusable="False">
<materialDesign:PackIcon Kind="SkipForward" /> <materialDesign:PackIcon Kind="SkipForward" />
</Button> </Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}" Focusable="False">
<materialDesign:PackIcon Kind="SkipPrevious" /> <materialDesign:PackIcon Kind="SkipPrevious" />
</Button> </Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}"> <Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}" Focusable="False">
<materialDesign:PackIcon Kind="SkipNext" /> <materialDesign:PackIcon Kind="SkipNext" />
</Button> </Button>
</StackPanel> </StackPanel>

View File

@ -19,7 +19,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{ {
_kernel = kernel; _kernel = kernel;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_keyframesEnabled = layerProperty.UntypedKeyframes.Any(); _keyframesEnabled = layerProperty.IsUsingKeyframes;
LayerProperty = layerProperty; LayerProperty = layerProperty;
Parent = parent; Parent = parent;
@ -56,6 +56,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
LayerProperty.ClearKeyframes(); LayerProperty.ClearKeyframes();
// Force the keyframe engine to update, the new keyframe is the current keyframe // Force the keyframe engine to update, the new keyframe is the current keyframe
LayerProperty.IsUsingKeyframes = _keyframesEnabled;
LayerProperty.KeyframeEngine.Update(0); LayerProperty.KeyframeEngine.Update(0);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();

View File

@ -15,7 +15,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
public float FloatInputValue public float FloatInputValue
{ {
get => (float) InputValue; get => (float?) InputValue ?? 0f;
set => InputValue = value; set => InputValue = value;
} }
@ -23,17 +23,5 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
{ {
NotifyOfPropertyChange(() => FloatInputValue); NotifyOfPropertyChange(() => FloatInputValue);
} }
protected override void UpdateBaseValue(object value)
{
var layerProperty = (LayerProperty<float>) LayerPropertyViewModel.LayerProperty;
layerProperty.Value = (float) value;
}
protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
var keyframe = (Keyframe<float>) baseKeyframe;
keyframe.Value = (float) value;
}
} }
} }

View File

@ -15,7 +15,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
public int IntInputValue public int IntInputValue
{ {
get => (int) InputValue; get => (int?) InputValue ?? 0;
set => InputValue = value; set => InputValue = value;
} }
@ -23,17 +23,5 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
{ {
NotifyOfPropertyChange(() => IntInputValue); NotifyOfPropertyChange(() => IntInputValue);
} }
protected override void UpdateBaseValue(object value)
{
var layerProperty = (LayerProperty<int>) LayerPropertyViewModel.LayerProperty;
layerProperty.Value = (int) value;
}
protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
var keyframe = (Keyframe<int>) baseKeyframe;
keyframe.Value = (int) value;
}
} }
} }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.UI.Exceptions; using Artemis.UI.Exceptions;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Stylet; using Stylet;
@ -43,29 +42,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
private void UpdateInputValue(object value) private void UpdateInputValue(object value)
{ {
// If keyframes are disabled, update the base value LayerPropertyViewModel.LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime);
if (!LayerPropertyViewModel.KeyframesEnabled)
{
UpdateBaseValue(value);
return;
}
// If on a keyframe, update the keyframe
var currentKeyframe = LayerPropertyViewModel.LayerProperty.UntypedKeyframes.FirstOrDefault(k => k.Position == ProfileEditorService.CurrentTime);
// Create a new keyframe if none found
if (currentKeyframe == null)
currentKeyframe = LayerPropertyViewModel.LayerProperty.CreateNewKeyframe(ProfileEditorService.CurrentTime);
UpdateKeyframeValue(currentKeyframe, value);
// Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress // Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress
LayerPropertyViewModel.LayerProperty.KeyframeEngine.Update(0); LayerPropertyViewModel.LayerProperty.KeyframeEngine.Update(0);
ProfileEditorService.UpdateSelectedProfileElement(); ProfileEditorService.UpdateSelectedProfileElement();
} }
public abstract void Update(); public abstract void Update();
protected abstract void UpdateBaseValue(object value);
protected abstract void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value);
} }
} }

View File

@ -19,14 +19,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
[DependsOn(nameof(InputValue))] [DependsOn(nameof(InputValue))]
public float X public float X
{ {
get => ((SKPoint) InputValue).X; get => ((SKPoint?) InputValue)?.X ?? 0;
set => InputValue = new SKPoint(value, Y); set => InputValue = new SKPoint(value, Y);
} }
[DependsOn(nameof(InputValue))] [DependsOn(nameof(InputValue))]
public float Y public float Y
{ {
get => ((SKPoint) InputValue).Y; get => ((SKPoint?)InputValue)?.Y ?? 0;
set => InputValue = new SKPoint(X, value); set => InputValue = new SKPoint(X, value);
} }
@ -35,17 +35,5 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
NotifyOfPropertyChange(() => X); NotifyOfPropertyChange(() => X);
NotifyOfPropertyChange(() => Y); NotifyOfPropertyChange(() => Y);
} }
protected override void UpdateBaseValue(object value)
{
var layerProperty = (LayerProperty<SKPoint>) LayerPropertyViewModel.LayerProperty;
layerProperty.Value = (SKPoint) value;
}
protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
var keyframe = (Keyframe<SKPoint>) baseKeyframe;
keyframe.Value = (SKPoint) value;
}
} }
} }

View File

@ -19,14 +19,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
[DependsOn(nameof(InputValue))] [DependsOn(nameof(InputValue))]
public float Width public float Width
{ {
get => ((SKSize) InputValue).Width; get => ((SKSize?) InputValue)?.Width ?? 0;
set => InputValue = new SKSize(value, Height); set => InputValue = new SKSize(value, Height);
} }
[DependsOn(nameof(InputValue))] [DependsOn(nameof(InputValue))]
public float Height public float Height
{ {
get => ((SKSize) InputValue).Height; get => ((SKSize?)InputValue)?.Height ?? 0;
set => InputValue = new SKSize(Width, value); set => InputValue = new SKSize(Width, value);
} }
@ -35,17 +35,5 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
NotifyOfPropertyChange(() => Width); NotifyOfPropertyChange(() => Width);
NotifyOfPropertyChange(() => Height); NotifyOfPropertyChange(() => Height);
} }
protected override void UpdateBaseValue(object value)
{
var layerProperty = (LayerProperty<SKSize>) LayerPropertyViewModel.LayerProperty;
layerProperty.Value = (SKSize) value;
}
protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
var keyframe = (Keyframe<SKSize>) baseKeyframe;
keyframe.Value = (SKSize) value;
}
} }
} }

View File

@ -1,4 +1,5 @@
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
@ -10,9 +11,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
public FolderViewModel(ProfileElement folder, public FolderViewModel(ProfileElement folder,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IDialogService dialogService, IDialogService dialogService,
ILayerService layerService,
IFolderViewModelFactory folderViewModelFactory, IFolderViewModelFactory folderViewModelFactory,
ILayerViewModelFactory layerViewModelFactory) : ILayerViewModelFactory layerViewModelFactory) :
base(null, folder, profileEditorService, dialogService, folderViewModelFactory, layerViewModelFactory) base(null, folder, profileEditorService, dialogService, layerService, folderViewModelFactory, layerViewModelFactory)
{ {
} }
@ -20,9 +22,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
ProfileElement folder, ProfileElement folder,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IDialogService dialogService, IDialogService dialogService,
ILayerService layerService,
IFolderViewModelFactory folderViewModelFactory, IFolderViewModelFactory folderViewModelFactory,
ILayerViewModelFactory layerViewModelFactory) : ILayerViewModelFactory layerViewModelFactory) :
base(parent, folder, profileEditorService, dialogService, folderViewModelFactory, layerViewModelFactory) base(parent, folder, profileEditorService, dialogService, layerService, folderViewModelFactory, layerViewModelFactory)
{ {
} }

View File

@ -1,4 +1,5 @@
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
@ -10,9 +11,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
ProfileElement folder, ProfileElement folder,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IDialogService dialogService, IDialogService dialogService,
ILayerService layerService,
IFolderViewModelFactory folderViewModelFactory, IFolderViewModelFactory folderViewModelFactory,
ILayerViewModelFactory layerViewModelFactory) : ILayerViewModelFactory layerViewModelFactory) :
base(parent, folder, profileEditorService, dialogService, folderViewModelFactory, layerViewModelFactory) base(parent, folder, profileEditorService, dialogService, layerService, folderViewModelFactory, layerViewModelFactory)
{ {
} }

View File

@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Exceptions; using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Module.ProfileEditor.Dialogs; using Artemis.UI.Screens.Module.ProfileEditor.Dialogs;
@ -13,6 +14,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
public abstract class TreeItemViewModel : PropertyChangedBase public abstract class TreeItemViewModel : PropertyChangedBase
{ {
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly ILayerService _layerService;
private readonly IFolderViewModelFactory _folderViewModelFactory; private readonly IFolderViewModelFactory _folderViewModelFactory;
private readonly ILayerViewModelFactory _layerViewModelFactory; private readonly ILayerViewModelFactory _layerViewModelFactory;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
@ -21,11 +23,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
ProfileElement profileElement, ProfileElement profileElement,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IDialogService dialogService, IDialogService dialogService,
ILayerService layerService,
IFolderViewModelFactory folderViewModelFactory, IFolderViewModelFactory folderViewModelFactory,
ILayerViewModelFactory layerViewModelFactory) ILayerViewModelFactory layerViewModelFactory)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_dialogService = dialogService; _dialogService = dialogService;
_layerService = layerService;
_folderViewModelFactory = folderViewModelFactory; _folderViewModelFactory = folderViewModelFactory;
_layerViewModelFactory = layerViewModelFactory; _layerViewModelFactory = layerViewModelFactory;
@ -117,7 +121,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
if (!SupportsChildren) if (!SupportsChildren)
throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name); throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name);
ProfileElement.AddChild(new Layer(ProfileElement.Profile, ProfileElement, "New layer")); var layer = new Layer(ProfileElement.Profile, ProfileElement, "New layer");
foreach (var baseLayerProperty in layer.Properties)
_layerService.InstantiateKeyframeEngine(baseLayerProperty);
ProfileElement.AddChild(layer);
UpdateProfileElements(); UpdateProfileElements();
_profileEditorService.UpdateSelectedProfile(); _profileEditorService.UpdateSelectedProfile();
} }

View File

@ -28,9 +28,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated; Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated;
_profileEditorService.SelectedProfileElementChanged += OnSelectedProfileElementChanged; _profileEditorService.SelectedProfileElementChanged += OnSelectedProfileElementChanged;
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated; _profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
_profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; _profileEditorService.ProfilePreviewUpdated += ProfileEditorServiceOnProfilePreviewUpdated;
} }
public Layer Layer { get; } public Layer Layer { get; }
public Geometry LayerGeometry { get; set; } public Geometry LayerGeometry { get; set; }
@ -102,7 +103,7 @@ 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, Math.Max(0, skRect.Width), Math.Max(0,skRect.Height)); var rect = new Rect(skRect.Left, skRect.Top, Math.Max(0, skRect.Width), Math.Max(0, skRect.Height));
var shapeGeometry = Geometry.Empty; var shapeGeometry = Geometry.Empty;
switch (Layer.LayerShape) switch (Layer.LayerShape)
@ -208,7 +209,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
Update(); Update();
} }
private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e) private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e)
{ {
if (!IsSelected) if (!IsSelected)
return; return;

View File

@ -9,15 +9,20 @@
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:EditToolViewModel}}"> d:DataContext="{d:DesignInstance {x:Type local:EditToolViewModel}}">
<Canvas> <Canvas UseLayoutRounding="False">
<!-- The rectangle around the shape that allows modification --> <!-- The rectangle around the shape that allows modification -->
<Rectangle Width="{Binding ShapeSkRect.Width}" <Rectangle Width="{Binding ShapeSkRect.Width}"
Height="{Binding ShapeSkRect.Height}" Height="{Binding ShapeSkRect.Height}"
Canvas.Left="{Binding ShapeSkRect.Left}" Canvas.Left="{Binding ShapeSkRect.Left}"
Canvas.Top="{Binding ShapeSkRect.Top}" Canvas.Top="{Binding ShapeSkRect.Top}"
Fill="Transparent"
Stroke="{DynamicResource PrimaryHueMidBrush}" Stroke="{DynamicResource PrimaryHueMidBrush}"
StrokeThickness="1" StrokeThickness="1"
StrokeDashArray="2 2" /> StrokeDashArray="2 2"
Cursor="Hand"
MouseDown="{s:Action ShapeEditMouseDown}"
MouseUp="{s:Action ShapeEditMouseUp}"
MouseMove="{s:Action Move}"/>
<!-- Top left display --> <!-- Top left display -->
<Rectangle Width="4" Height="4" Canvas.Left="{Binding ShapeSkRect.Left}" Canvas.Top="{Binding ShapeSkRect.Top}" Fill="{DynamicResource SecondaryAccentBrush}" Margin="-2,-2,0,0" /> <Rectangle Width="4" Height="4" Canvas.Left="{Binding ShapeSkRect.Left}" Canvas.Top="{Binding ShapeSkRect.Top}" Fill="{DynamicResource SecondaryAccentBrush}" Margin="-2,-2,0,0" />

View File

@ -10,7 +10,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
{ {
public class EditToolViewModel : VisualizationToolViewModel public class EditToolViewModel : VisualizationToolViewModel
{ {
private bool _mouseDown; private bool _isDragging;
private double _dragOffsetY;
private double _dragOffsetX;
public EditToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) public EditToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService)
{ {
@ -19,7 +21,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
profileEditorService.SelectedProfileChanged += (sender, args) => Update(); profileEditorService.SelectedProfileChanged += (sender, args) => Update();
profileEditorService.SelectedProfileElementUpdated += (sender, args) => Update(); profileEditorService.SelectedProfileElementUpdated += (sender, args) => Update();
profileEditorService.CurrentTimeChanged += (sender, args) => Update(); profileEditorService.ProfilePreviewUpdated += (sender, args) => Update();
} }
private void Update() private void Update()
@ -34,41 +36,64 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
public void ShapeEditMouseDown(object sender, MouseButtonEventArgs e) public void ShapeEditMouseDown(object sender, MouseButtonEventArgs e)
{ {
if (_isDragging)
return;
((IInputElement) sender).CaptureMouse(); ((IInputElement) sender).CaptureMouse();
_mouseDown = true; if (ProfileEditorService.SelectedProfileElement is Layer layer)
{
var dragStartPosition = GetRelativePosition(sender, e);
var skRect = layer.LayerShape.GetUnscaledRectangle();
_dragOffsetX = skRect.Left - dragStartPosition.X;
_dragOffsetY = skRect.Top - dragStartPosition.Y;
}
_isDragging = true;
e.Handled = true; e.Handled = true;
} }
public void ShapeEditMouseUp(object sender, MouseButtonEventArgs e) public void ShapeEditMouseUp(object sender, MouseButtonEventArgs e)
{ {
((IInputElement) sender).ReleaseMouseCapture(); ((IInputElement) sender).ReleaseMouseCapture();
_mouseDown = false; ProfileEditorService.UpdateSelectedProfileElement();
_isDragging = false;
e.Handled = true; e.Handled = true;
} }
public void Move(object sender, MouseEventArgs e)
{
if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
var position = GetRelativePosition(sender, e);
var skRect = layer.LayerShape.GetUnscaledRectangle();
layer.LayerShape.SetFromUnscaledRectangle(
SKRect.Create((float) (position.X + _dragOffsetX), (float) (position.Y + _dragOffsetY), skRect.Width, skRect.Height),
ProfileEditorService.CurrentTime
);
ProfileEditorService.UpdateProfilePreview();
}
public void TopLeftRotate(object sender, MouseEventArgs e) public void TopLeftRotate(object sender, MouseEventArgs e)
{ {
} }
public void TopLeftResize(object sender, MouseEventArgs e) public void TopLeftResize(object sender, MouseEventArgs e)
{ {
} }
public void TopCenterResize(object sender, MouseEventArgs e) public void TopCenterResize(object sender, MouseEventArgs e)
{ {
if (!_mouseDown) if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
return; return;
var position = GetRelativePosition(sender, e); var position = GetRelativePosition(sender, e);
var skRect = layer.LayerShape.GetUnscaledRectangle(); var skRect = layer.LayerShape.GetUnscaledRectangle();
skRect.Top = (float) Math.Min(position.Y, skRect.Bottom); skRect.Top = (float) Math.Min(position.Y, skRect.Bottom);
layer.LayerShape.SetFromUnscaledRectangle(skRect); layer.LayerShape.SetFromUnscaledRectangle(skRect, ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
ProfileEditorService.UpdateSelectedProfileElement();
} }
public void TopRightRotate(object sender, MouseEventArgs e) public void TopRightRotate(object sender, MouseEventArgs e)
@ -81,18 +106,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
public void CenterRightResize(object sender, MouseEventArgs e) public void CenterRightResize(object sender, MouseEventArgs e)
{ {
if (!_mouseDown) if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
return; return;
var position = GetRelativePosition(sender, e); var position = GetRelativePosition(sender, e);
var skRect = layer.LayerShape.GetUnscaledRectangle(); var skRect = layer.LayerShape.GetUnscaledRectangle();
skRect.Right = (float) Math.Max(position.X, skRect.Left); skRect.Right = (float) Math.Max(position.X, skRect.Left);
layer.LayerShape.SetFromUnscaledRectangle(skRect); layer.LayerShape.SetFromUnscaledRectangle(skRect, ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
ProfileEditorService.UpdateSelectedProfileElement();
} }
private Point GetRelativePosition(object sender, MouseEventArgs mouseEventArgs) private Point GetRelativePosition(object sender, MouseEventArgs mouseEventArgs)
@ -111,18 +133,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
public void BottomCenterResize(object sender, MouseEventArgs e) public void BottomCenterResize(object sender, MouseEventArgs e)
{ {
if (!_mouseDown) if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
return; return;
var position = GetRelativePosition(sender, e); var position = GetRelativePosition(sender, e);
var skRect = layer.LayerShape.GetUnscaledRectangle(); var skRect = layer.LayerShape.GetUnscaledRectangle();
skRect.Bottom = (float) Math.Max(position.Y, skRect.Top); skRect.Bottom = (float) Math.Max(position.Y, skRect.Top);
layer.LayerShape.SetFromUnscaledRectangle(skRect); layer.LayerShape.SetFromUnscaledRectangle(skRect, ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
ProfileEditorService.UpdateSelectedProfileElement();
} }
public void BottomLeftRotate(object sender, MouseEventArgs e) public void BottomLeftRotate(object sender, MouseEventArgs e)
@ -135,18 +154,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
public void CenterLeftResize(object sender, MouseEventArgs e) public void CenterLeftResize(object sender, MouseEventArgs e)
{ {
if (!_mouseDown) if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
return; return;
var position = GetRelativePosition(sender, e); var position = GetRelativePosition(sender, e);
var skRect = layer.LayerShape.GetUnscaledRectangle(); var skRect = layer.LayerShape.GetUnscaledRectangle();
skRect.Left = (float) Math.Min(position.X, skRect.Right); skRect.Left = (float) Math.Min(position.X, skRect.Right);
layer.LayerShape.SetFromUnscaledRectangle(skRect); layer.LayerShape.SetFromUnscaledRectangle(skRect, ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
ProfileEditorService.UpdateSelectedProfileElement();
} }
} }
} }

View File

@ -48,7 +48,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
layer.LayerShape = new Ellipse(layer); layer.LayerShape = new Ellipse(layer);
// Apply the drag rectangle // Apply the drag rectangle
layer.LayerShape.SetFromUnscaledRectangle(DragRectangle.ToSKRect()); layer.LayerShape.SetFromUnscaledRectangle(DragRectangle.ToSKRect(), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateSelectedProfileElement(); ProfileEditorService.UpdateSelectedProfileElement();
} }
} }

View File

@ -48,7 +48,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
layer.LayerShape = new Rectangle(layer); layer.LayerShape = new Rectangle(layer);
// Apply the drag rectangle // Apply the drag rectangle
layer.LayerShape.SetFromUnscaledRectangle(DragRectangle.ToSKRect()); layer.LayerShape.SetFromUnscaledRectangle(DragRectangle.ToSKRect(), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateSelectedProfileElement(); ProfileEditorService.UpdateSelectedProfileElement();
} }
} }

View File

@ -55,7 +55,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
layer.AddLeds(selectedLeds); layer.AddLeds(selectedLeds);
// Restore the saved size // Restore the saved size
if (layer.LayerShape != null) if (layer.LayerShape != null)
layer.LayerShape.SetFromUnscaledRectangle(shapeSize); layer.LayerShape.SetFromUnscaledRectangle(shapeSize, ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateSelectedProfileElement(); ProfileEditorService.UpdateSelectedProfileElement();
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
namespace Artemis.UI.Services.Interfaces namespace Artemis.UI.Services.Interfaces
{ {
@ -13,6 +14,7 @@ namespace Artemis.UI.Services.Interfaces
void UpdateSelectedProfile(); void UpdateSelectedProfile();
void ChangeSelectedProfileElement(ProfileElement profileElement); void ChangeSelectedProfileElement(ProfileElement profileElement);
void UpdateSelectedProfileElement(); void UpdateSelectedProfileElement();
void UpdateProfilePreview();
/// <summary> /// <summary>
/// Occurs when a new profile is selected /// Occurs when a new profile is selected
@ -38,5 +40,10 @@ namespace Artemis.UI.Services.Interfaces
/// Occurs when the current editor time is changed /// Occurs when the current editor time is changed
/// </summary> /// </summary>
event EventHandler CurrentTimeChanged; event EventHandler CurrentTimeChanged;
/// <summary>
/// Occurs when the profile preview has been updated
/// </summary>
event EventHandler ProfilePreviewUpdated;
} }
} }

View File

@ -60,7 +60,7 @@ namespace Artemis.UI.Services
} }
private void UpdateProfilePreview() public void UpdateProfilePreview()
{ {
if (SelectedProfile == null) if (SelectedProfile == null)
return; return;
@ -78,6 +78,7 @@ namespace Artemis.UI.Services
} }
_lastUpdateTime = CurrentTime; _lastUpdateTime = CurrentTime;
OnProfilePreviewUpdated();
} }
public event EventHandler SelectedProfileChanged; public event EventHandler SelectedProfileChanged;
@ -85,6 +86,7 @@ namespace Artemis.UI.Services
public event EventHandler SelectedProfileElementChanged; public event EventHandler SelectedProfileElementChanged;
public event EventHandler SelectedProfileElementUpdated; public event EventHandler SelectedProfileElementUpdated;
public event EventHandler CurrentTimeChanged; public event EventHandler CurrentTimeChanged;
public event EventHandler ProfilePreviewUpdated;
protected virtual void OnSelectedProfileElementUpdated() protected virtual void OnSelectedProfileElementUpdated()
{ {
@ -110,5 +112,10 @@ namespace Artemis.UI.Services
{ {
CurrentTimeChanged?.Invoke(this, EventArgs.Empty); CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
} }
protected virtual void OnProfilePreviewUpdated()
{
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);
}
} }
} }