mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile editor - Implemented timeline segments
Profile editor - Added snapping to multiple elements of the timeline
This commit is contained in:
parent
c5219bd224
commit
527fef3dc6
@ -1,6 +1,7 @@
|
|||||||
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.Core.Plugins.LayerEffect.Abstract;
|
using Artemis.Core.Plugins.LayerEffect.Abstract;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
@ -24,6 +25,7 @@ namespace Artemis.Core.Models.Profile
|
|||||||
|
|
||||||
_layerEffects = new List<BaseLayerEffect>();
|
_layerEffects = new List<BaseLayerEffect>();
|
||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
|
ApplyRenderElementDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
|
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
|
||||||
@ -42,8 +44,6 @@ namespace Artemis.Core.Models.Profile
|
|||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
_expandedPropertyGroups.AddRange(folderEntity.ExpandedPropertyGroups);
|
_expandedPropertyGroups.AddRange(folderEntity.ExpandedPropertyGroups);
|
||||||
|
|
||||||
// TODO: Load conditions
|
|
||||||
|
|
||||||
// Load child folders
|
// Load child folders
|
||||||
foreach (var childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
|
foreach (var childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
|
||||||
ChildrenList.Add(new Folder(profile, this, childFolder));
|
ChildrenList.Add(new Folder(profile, this, childFolder));
|
||||||
@ -55,6 +55,8 @@ namespace Artemis.Core.Models.Profile
|
|||||||
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
|
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
|
||||||
for (var index = 0; index < ChildrenList.Count; index++)
|
for (var index = 0; index < ChildrenList.Count; index++)
|
||||||
ChildrenList[index].Order = index + 1;
|
ChildrenList[index].Order = index + 1;
|
||||||
|
|
||||||
|
ApplyRenderElementEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal FolderEntity FolderEntity { get; set; }
|
internal FolderEntity FolderEntity { get; set; }
|
||||||
@ -187,7 +189,7 @@ namespace Artemis.Core.Models.Profile
|
|||||||
FolderEntity.ExpandedPropertyGroups.Clear();
|
FolderEntity.ExpandedPropertyGroups.Clear();
|
||||||
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
|
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
|
||||||
|
|
||||||
ApplyLayerEffectsToEntity();
|
ApplyRenderElementToEntity();
|
||||||
|
|
||||||
// Conditions
|
// Conditions
|
||||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity;
|
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity;
|
||||||
|
|||||||
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Extensions;
|
using Artemis.Core.Extensions;
|
||||||
using Artemis.Core.Models.Profile.Conditions;
|
|
||||||
using Artemis.Core.Models.Profile.LayerProperties;
|
using Artemis.Core.Models.Profile.LayerProperties;
|
||||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||||
using Artemis.Core.Models.Profile.LayerShapes;
|
using Artemis.Core.Models.Profile.LayerShapes;
|
||||||
@ -48,6 +47,7 @@ namespace Artemis.Core.Models.Profile
|
|||||||
_expandedPropertyGroups = new List<string>();
|
_expandedPropertyGroups = new List<string>();
|
||||||
|
|
||||||
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
|
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
|
||||||
|
ApplyRenderElementDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
|
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
|
||||||
@ -69,6 +69,8 @@ namespace Artemis.Core.Models.Profile
|
|||||||
_expandedPropertyGroups.AddRange(layerEntity.ExpandedPropertyGroups);
|
_expandedPropertyGroups.AddRange(layerEntity.ExpandedPropertyGroups);
|
||||||
|
|
||||||
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
|
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
|
||||||
|
ApplyRenderElementEntity();
|
||||||
|
ApplyRenderElementDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal LayerEntity LayerEntity { get; set; }
|
internal LayerEntity LayerEntity { get; set; }
|
||||||
@ -115,14 +117,27 @@ namespace Artemis.Core.Models.Profile
|
|||||||
get => _layerBrush;
|
get => _layerBrush;
|
||||||
internal set => SetAndNotify(ref _layerBrush, value);
|
internal set => SetAndNotify(ref _layerBrush, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override List<BaseLayerPropertyKeyframe> GetAllKeyframes()
|
||||||
|
{
|
||||||
|
var keyframes = base.GetAllKeyframes();
|
||||||
|
|
||||||
|
foreach (var baseLayerProperty in General.GetAllLayerProperties())
|
||||||
|
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
|
||||||
|
foreach (var baseLayerProperty in Transform.GetAllLayerProperties())
|
||||||
|
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
|
||||||
|
foreach (var baseLayerProperty in LayerBrush.BaseProperties.GetAllLayerProperties())
|
||||||
|
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
|
||||||
|
|
||||||
|
return keyframes;
|
||||||
|
}
|
||||||
|
|
||||||
#region Storage
|
#region Storage
|
||||||
|
|
||||||
internal override void ApplyToEntity()
|
internal override void ApplyToEntity()
|
||||||
@ -142,7 +157,7 @@ namespace Artemis.Core.Models.Profile
|
|||||||
LayerBrush?.BaseProperties.ApplyToEntity();
|
LayerBrush?.BaseProperties.ApplyToEntity();
|
||||||
|
|
||||||
// Effects
|
// Effects
|
||||||
ApplyLayerEffectsToEntity();
|
ApplyRenderElementToEntity();
|
||||||
|
|
||||||
// LEDs
|
// LEDs
|
||||||
LayerEntity.Leds.Clear();
|
LayerEntity.Leds.Clear();
|
||||||
@ -206,34 +221,18 @@ namespace Artemis.Core.Models.Profile
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
UpdateDisplayCondition();
|
UpdateDisplayCondition();
|
||||||
|
deltaTime = UpdateTimeline(deltaTime);
|
||||||
|
|
||||||
// TODO: Remove, this is slow and stupid
|
General.Update(deltaTime);
|
||||||
// For now, reset all keyframe engines after the last keyframe was hit
|
Transform.Update(deltaTime);
|
||||||
// This is a placeholder method of repeating the animation until repeat modes are implemented
|
LayerBrush.BaseProperties?.Update(deltaTime);
|
||||||
var properties = new List<BaseLayerProperty>(General.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any()));
|
|
||||||
properties.AddRange(Transform.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any()));
|
|
||||||
properties.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any()));
|
|
||||||
var timeLineEnd = properties.Any() ? properties.Max(p => p.BaseKeyframes.Max(k => k.Position)) : TimeSpan.MaxValue;
|
|
||||||
if (properties.Any(p => p.TimelineProgress >= timeLineEnd))
|
|
||||||
{
|
|
||||||
General.Override(TimeSpan.Zero);
|
|
||||||
Transform.Override(TimeSpan.Zero);
|
|
||||||
LayerBrush.BaseProperties.Override(TimeSpan.Zero);
|
|
||||||
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
||||||
baseLayerEffect.BaseProperties?.Override(TimeSpan.Zero);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
General.Update(deltaTime);
|
|
||||||
Transform.Update(deltaTime);
|
|
||||||
LayerBrush.BaseProperties.Update(deltaTime);
|
|
||||||
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
||||||
baseLayerEffect.BaseProperties?.Update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
LayerBrush.Update(deltaTime);
|
LayerBrush.Update(deltaTime);
|
||||||
|
|
||||||
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
||||||
|
{
|
||||||
|
baseLayerEffect.BaseProperties?.Update(deltaTime);
|
||||||
baseLayerEffect.Update(deltaTime);
|
baseLayerEffect.Update(deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OverrideProgress(TimeSpan timeOverride)
|
public void OverrideProgress(TimeSpan timeOverride)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using Artemis.Core.Annotations;
|
using Artemis.Core.Annotations;
|
||||||
using Artemis.Core.Models.Profile.Conditions;
|
using Artemis.Core.Models.Profile.Conditions;
|
||||||
using Artemis.Core.Models.Profile.LayerProperties;
|
using Artemis.Core.Models.Profile.LayerProperties;
|
||||||
|
using Artemis.Core.Plugins.LayerBrush.Abstract;
|
||||||
using Artemis.Core.Plugins.LayerEffect.Abstract;
|
using Artemis.Core.Plugins.LayerEffect.Abstract;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.Storage.Entities.Profile.Abstract;
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
@ -14,6 +15,62 @@ namespace Artemis.Core.Models.Profile
|
|||||||
{
|
{
|
||||||
public abstract class RenderProfileElement : ProfileElement
|
public abstract class RenderProfileElement : ProfileElement
|
||||||
{
|
{
|
||||||
|
protected void ApplyRenderElementDefaults()
|
||||||
|
{
|
||||||
|
if (MainSegmentLength <= TimeSpan.Zero)
|
||||||
|
MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ApplyRenderElementEntity()
|
||||||
|
{
|
||||||
|
StartSegmentLength = RenderElementEntity.StartSegmentLength;
|
||||||
|
MainSegmentLength = RenderElementEntity.MainSegmentLength;
|
||||||
|
EndSegmentLength = RenderElementEntity.EndSegmentLength;
|
||||||
|
RepeatMainSegment = RenderElementEntity.RepeatMainSegment;
|
||||||
|
AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ApplyRenderElementToEntity()
|
||||||
|
{
|
||||||
|
RenderElementEntity.StartSegmentLength = StartSegmentLength;
|
||||||
|
RenderElementEntity.MainSegmentLength = MainSegmentLength;
|
||||||
|
RenderElementEntity.EndSegmentLength = EndSegmentLength;
|
||||||
|
RenderElementEntity.RepeatMainSegment = RepeatMainSegment;
|
||||||
|
RenderElementEntity.AlwaysFinishTimeline = AlwaysFinishTimeline;
|
||||||
|
|
||||||
|
RenderElementEntity.LayerEffects.Clear();
|
||||||
|
foreach (var layerEffect in LayerEffects)
|
||||||
|
{
|
||||||
|
var layerEffectEntity = new LayerEffectEntity
|
||||||
|
{
|
||||||
|
Id = layerEffect.EntityId,
|
||||||
|
PluginGuid = layerEffect.PluginInfo.Guid,
|
||||||
|
EffectType = layerEffect.GetType().Name,
|
||||||
|
Name = layerEffect.Name,
|
||||||
|
Enabled = layerEffect.Enabled,
|
||||||
|
HasBeenRenamed = layerEffect.HasBeenRenamed,
|
||||||
|
Order = layerEffect.Order
|
||||||
|
};
|
||||||
|
RenderElementEntity.LayerEffects.Add(layerEffectEntity);
|
||||||
|
layerEffect.BaseProperties.ApplyToEntity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of all keyframes on all properties and effects of this layer
|
||||||
|
/// </summary>
|
||||||
|
public virtual List<BaseLayerPropertyKeyframe> GetAllKeyframes()
|
||||||
|
{
|
||||||
|
var keyframes = new List<BaseLayerPropertyKeyframe>();
|
||||||
|
foreach (var layerEffect in LayerEffects)
|
||||||
|
{
|
||||||
|
foreach (var baseLayerProperty in layerEffect.BaseProperties.GetAllLayerProperties())
|
||||||
|
keyframes.AddRange(baseLayerProperty.BaseKeyframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyframes;
|
||||||
|
}
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
private SKPath _path;
|
private SKPath _path;
|
||||||
@ -101,6 +158,15 @@ namespace Artemis.Core.Models.Profile
|
|||||||
set => SetAndNotify(ref _endSegmentLength, value);
|
set => SetAndNotify(ref _endSegmentLength, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current timeline position
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan TimelinePosition
|
||||||
|
{
|
||||||
|
get => _timelinePosition;
|
||||||
|
private set => SetAndNotify(ref _timelinePosition, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total combined length of all three segments
|
/// Gets the total combined length of all three segments
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -124,6 +190,35 @@ namespace Artemis.Core.Models.Profile
|
|||||||
set => SetAndNotify(ref _alwaysFinishTimeline, value);
|
set => SetAndNotify(ref _alwaysFinishTimeline, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected double UpdateTimeline(double deltaTime)
|
||||||
|
{
|
||||||
|
var oldPosition = _timelinePosition;
|
||||||
|
var deltaTimeSpan = TimeSpan.FromSeconds(deltaTime);
|
||||||
|
var mainSegmentEnd = StartSegmentLength + MainSegmentLength;
|
||||||
|
|
||||||
|
TimelinePosition += deltaTimeSpan;
|
||||||
|
|
||||||
|
// Manage segments while the condition is met
|
||||||
|
if (DisplayConditionMet)
|
||||||
|
{
|
||||||
|
// If we are at the end of the main timeline, wrap around back to the beginning
|
||||||
|
if (RepeatMainSegment && TimelinePosition >= mainSegmentEnd)
|
||||||
|
TimelinePosition = StartSegmentLength + (mainSegmentEnd - TimelinePosition);
|
||||||
|
else if (TimelinePosition >= TimelineLength)
|
||||||
|
TimelinePosition = TimelineLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Skip to the last segment if conditions are no longer met
|
||||||
|
if (!AlwaysFinishTimeline && TimelinePosition < mainSegmentEnd)
|
||||||
|
TimelinePosition = mainSegmentEnd;
|
||||||
|
else if (TimelinePosition >= TimelineLength)
|
||||||
|
TimelinePosition = TimelineLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TimelinePosition - oldPosition).TotalSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Effects
|
#region Effects
|
||||||
@ -135,26 +230,6 @@ namespace Artemis.Core.Models.Profile
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyCollection<BaseLayerEffect> LayerEffects => _layerEffects.AsReadOnly();
|
public ReadOnlyCollection<BaseLayerEffect> LayerEffects => _layerEffects.AsReadOnly();
|
||||||
|
|
||||||
protected void ApplyLayerEffectsToEntity()
|
|
||||||
{
|
|
||||||
RenderElementEntity.LayerEffects.Clear();
|
|
||||||
foreach (var layerEffect in LayerEffects)
|
|
||||||
{
|
|
||||||
var layerEffectEntity = new LayerEffectEntity
|
|
||||||
{
|
|
||||||
Id = layerEffect.EntityId,
|
|
||||||
PluginGuid = layerEffect.PluginInfo.Guid,
|
|
||||||
EffectType = layerEffect.GetType().Name,
|
|
||||||
Name = layerEffect.Name,
|
|
||||||
Enabled = layerEffect.Enabled,
|
|
||||||
HasBeenRenamed = layerEffect.HasBeenRenamed,
|
|
||||||
Order = layerEffect.Order
|
|
||||||
};
|
|
||||||
RenderElementEntity.LayerEffects.Add(layerEffectEntity);
|
|
||||||
layerEffect.BaseProperties.ApplyToEntity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void RemoveLayerEffect([NotNull] BaseLayerEffect effect)
|
internal void RemoveLayerEffect([NotNull] BaseLayerEffect effect)
|
||||||
{
|
{
|
||||||
if (effect == null) throw new ArgumentNullException(nameof(effect));
|
if (effect == null) throw new ArgumentNullException(nameof(effect));
|
||||||
@ -192,8 +267,20 @@ namespace Artemis.Core.Models.Profile
|
|||||||
|
|
||||||
#region Conditions
|
#region Conditions
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the display conditions applied to this layer where met or not during last update
|
||||||
|
/// <para>Always true if the layer has no display conditions</para>
|
||||||
|
/// </summary>
|
||||||
|
public bool DisplayConditionMet
|
||||||
|
{
|
||||||
|
get => _displayConditionMet;
|
||||||
|
private set => SetAndNotify(ref _displayConditionMet, value);
|
||||||
|
}
|
||||||
|
|
||||||
private DisplayConditionGroup _displayConditionGroup;
|
private DisplayConditionGroup _displayConditionGroup;
|
||||||
|
private TimeSpan _timelinePosition;
|
||||||
|
private bool _displayConditionMet;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the root display condition group
|
/// Gets or sets the root display condition group
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -205,7 +292,11 @@ namespace Artemis.Core.Models.Profile
|
|||||||
|
|
||||||
public void UpdateDisplayCondition()
|
public void UpdateDisplayCondition()
|
||||||
{
|
{
|
||||||
var rootGroupResult = DisplayConditionGroup.Evaluate();
|
var conditionMet = DisplayConditionGroup == null || DisplayConditionGroup.Evaluate();
|
||||||
|
if (conditionMet && !DisplayConditionMet)
|
||||||
|
TimelinePosition = TimeSpan.Zero;
|
||||||
|
|
||||||
|
DisplayConditionMet = conditionMet;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -98,7 +98,7 @@ namespace Artemis.Core.Plugins.LayerBrush.Abstract
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called before rendering every frame, write your update logic here
|
/// Called before rendering every frame, write your update logic here
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deltaTime"></param>
|
/// <param name="deltaTime">Seconds passed since last update</param>
|
||||||
public abstract void Update(double deltaTime);
|
public abstract void Update(double deltaTime);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Artemis.Storage.Entities.Profile.Abstract
|
namespace Artemis.Storage.Entities.Profile.Abstract
|
||||||
{
|
{
|
||||||
public abstract class RenderElementEntity
|
public abstract class RenderElementEntity
|
||||||
{
|
{
|
||||||
|
public TimeSpan StartSegmentLength { get; set; }
|
||||||
|
public TimeSpan MainSegmentLength { get; set; }
|
||||||
|
public TimeSpan EndSegmentLength { get; set; }
|
||||||
|
public bool RepeatMainSegment { get; set; }
|
||||||
|
public bool AlwaysFinishTimeline { get; set; }
|
||||||
|
|
||||||
public List<LayerEffectEntity> LayerEffects { get; set; }
|
public List<LayerEffectEntity> LayerEffects { get; set; }
|
||||||
public List<PropertyEntity> PropertyEntities { get; set; }
|
public List<PropertyEntity> PropertyEntities { get; set; }
|
||||||
public List<string> ExpandedPropertyGroups { get; set; }
|
public List<string> ExpandedPropertyGroups { get; set; }
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Threading;
|
||||||
using Artemis.Core.Models.Surface;
|
using Artemis.Core.Models.Surface;
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
@ -28,17 +29,20 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
private readonly DrawingGroup _backingStore;
|
private readonly DrawingGroup _backingStore;
|
||||||
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
||||||
private BitmapImage _deviceImage;
|
private BitmapImage _deviceImage;
|
||||||
private bool _subscribed;
|
|
||||||
private ArtemisDevice _oldDevice;
|
private ArtemisDevice _oldDevice;
|
||||||
private Task _lastRenderTask;
|
private readonly DispatcherTimer _timer;
|
||||||
|
|
||||||
public DeviceVisualizer()
|
public DeviceVisualizer()
|
||||||
{
|
{
|
||||||
_backingStore = new DrawingGroup();
|
_backingStore = new DrawingGroup();
|
||||||
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
||||||
|
|
||||||
Loaded += (sender, args) => SubscribeToUpdate(true);
|
// Run an update timer at 25 fps
|
||||||
Unloaded += (sender, args) => SubscribeToUpdate(false);
|
_timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(40)};
|
||||||
|
_timer.Tick += TimerOnTick;
|
||||||
|
|
||||||
|
Loaded += (sender, args) => _timer.Start();
|
||||||
|
Unloaded += (sender, args) => _timer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArtemisDevice Device
|
public ArtemisDevice Device
|
||||||
@ -61,7 +65,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
RGBSurface.Instance.Updated -= RgbSurfaceOnUpdated;
|
_timer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnRender(DrawingContext drawingContext)
|
protected override void OnRender(DrawingContext drawingContext)
|
||||||
@ -73,7 +77,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
var measureSize = MeasureOverride(Size.Empty);
|
var measureSize = MeasureOverride(Size.Empty);
|
||||||
var scale = Math.Min(DesiredSize.Width / measureSize.Width, DesiredSize.Height / measureSize.Height);
|
var scale = Math.Min(DesiredSize.Width / measureSize.Width, DesiredSize.Height / measureSize.Height);
|
||||||
var scaledRect = new Rect(0, 0, measureSize.Width * scale, measureSize.Height * scale);
|
var scaledRect = new Rect(0, 0, measureSize.Width * scale, measureSize.Height * scale);
|
||||||
|
|
||||||
// Center and scale the visualization in the desired bounding box
|
// Center and scale the visualization in the desired bounding box
|
||||||
if (DesiredSize.Width > 0 && DesiredSize.Height > 0)
|
if (DesiredSize.Width > 0 && DesiredSize.Height > 0)
|
||||||
{
|
{
|
||||||
@ -95,7 +99,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
// Render device and LED images
|
// Render device and LED images
|
||||||
if (_deviceImage != null)
|
if (_deviceImage != null)
|
||||||
drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height));
|
drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height));
|
||||||
|
|
||||||
foreach (var deviceVisualizerLed in _deviceVisualizerLeds)
|
foreach (var deviceVisualizerLed in _deviceVisualizerLeds)
|
||||||
deviceVisualizerLed.RenderImage(drawingContext);
|
deviceVisualizerLed.RenderImage(drawingContext);
|
||||||
|
|
||||||
@ -113,26 +117,18 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
return rotationRect.Size;
|
return rotationRect.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TimerOnTick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (ShowColors && Visibility == Visibility.Visible)
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateTransform()
|
private void UpdateTransform()
|
||||||
{
|
{
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
InvalidateMeasure();
|
InvalidateMeasure();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SubscribeToUpdate(bool subscribe)
|
|
||||||
{
|
|
||||||
if (_subscribed == subscribe)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (subscribe)
|
|
||||||
RGBSurface.Instance.Updated += RgbSurfaceOnUpdated;
|
|
||||||
else
|
|
||||||
RGBSurface.Instance.Updated -= RgbSurfaceOnUpdated;
|
|
||||||
|
|
||||||
_subscribed = subscribe;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
var deviceVisualizer = (DeviceVisualizer) d;
|
var deviceVisualizer = (DeviceVisualizer) d;
|
||||||
@ -208,17 +204,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
if (e.PropertyName == nameof(Device.RgbDevice.Scale) || e.PropertyName == nameof(Device.RgbDevice.Rotation))
|
if (e.PropertyName == nameof(Device.RgbDevice.Scale) || e.PropertyName == nameof(Device.RgbDevice.Rotation))
|
||||||
UpdateTransform();
|
UpdateTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RgbSurfaceOnUpdated(UpdatedEventArgs e)
|
|
||||||
{
|
|
||||||
_lastRenderTask?.Wait();
|
|
||||||
|
|
||||||
_lastRenderTask = Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
if (ShowColors && Visibility == Visibility.Visible)
|
|
||||||
Render();
|
|
||||||
}).Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Render()
|
private void Render()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -44,7 +44,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
var r = Led.RgbLed.Color.GetR();
|
var r = Led.RgbLed.Color.GetR();
|
||||||
var g = Led.RgbLed.Color.GetG();
|
var g = Led.RgbLed.Color.GetG();
|
||||||
var b = Led.RgbLed.Color.GetB();
|
var b = Led.RgbLed.Color.GetB();
|
||||||
|
|
||||||
drawingContext.DrawRectangle(isDimmed
|
drawingContext.DrawRectangle(isDimmed
|
||||||
? new SolidColorBrush(Color.FromArgb(100, r, g, b))
|
? new SolidColorBrush(Color.FromArgb(100, r, g, b))
|
||||||
: new SolidColorBrush(Color.FromRgb(r, g, b)), null, LedRect);
|
: new SolidColorBrush(Color.FromRgb(r, g, b)), null, LedRect);
|
||||||
|
|||||||
@ -74,5 +74,13 @@ namespace Artemis.UI.Shared.Services.Interfaces
|
|||||||
PropertyInputRegistration RegisterPropertyInput<T>(PluginInfo pluginInfo) where T : PropertyInputViewModel;
|
PropertyInputRegistration RegisterPropertyInput<T>(PluginInfo pluginInfo) where T : PropertyInputViewModel;
|
||||||
|
|
||||||
void RemovePropertyInput(PropertyInputRegistration registration);
|
void RemovePropertyInput(PropertyInputRegistration registration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Snaps the given time to the closest relevant element in the timeline, this can be the cursor, a keyframe or a segment end.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time"></param>
|
||||||
|
/// <param name="tolerance">How close the time must be to snap</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||||
|
|
||||||
Kernel = kernel;
|
Kernel = kernel;
|
||||||
PixelsPerSecond = 31;
|
PixelsPerSecond = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IKernel Kernel { get; }
|
public IKernel Kernel { get; }
|
||||||
@ -48,7 +48,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (_currentTime.Equals(value)) return;
|
if (_currentTime.Equals(value)) return;
|
||||||
if (value > SelectedProfileElement.TimelineLength)
|
if (SelectedProfileElement != null && value > SelectedProfileElement.TimelineLength)
|
||||||
_currentTime = SelectedProfileElement.TimelineLength;
|
_currentTime = SelectedProfileElement.TimelineLength;
|
||||||
else
|
else
|
||||||
_currentTime = value;
|
_currentTime = value;
|
||||||
@ -205,6 +205,47 @@ namespace Artemis.UI.Shared.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes)
|
||||||
|
{
|
||||||
|
if (snapToSegments)
|
||||||
|
{
|
||||||
|
// Snap to the end of the start segment
|
||||||
|
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.StartSegmentLength.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
|
return SelectedProfileElement.StartSegmentLength;
|
||||||
|
|
||||||
|
// Snap to the end of the main segment
|
||||||
|
var mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
|
||||||
|
if (Math.Abs(time.TotalMilliseconds - mainSegmentEnd.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
|
return mainSegmentEnd;
|
||||||
|
|
||||||
|
// Snap to the end of the end segment (end of the timeline)
|
||||||
|
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.TimelineLength.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
|
return SelectedProfileElement.TimelineLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapToCurrentTime)
|
||||||
|
{
|
||||||
|
// Snap to the current time
|
||||||
|
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
|
return SelectedProfileElement.StartSegmentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapToKeyframes)
|
||||||
|
{
|
||||||
|
// Get all visible keyframes
|
||||||
|
var keyframes = SelectedProfileElement.GetAllKeyframes()
|
||||||
|
.Where(k => SelectedProfileElement.IsPropertyGroupExpanded(k.BaseLayerProperty.Parent))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Find the closest keyframe
|
||||||
|
var closeKeyframe = keyframes.FirstOrDefault(k => Math.Abs(time.TotalMilliseconds - k.Position.TotalMilliseconds) < tolerance.TotalMilliseconds);
|
||||||
|
if (closeKeyframe != null)
|
||||||
|
return closeKeyframe.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
public Module GetCurrentModule()
|
public Module GetCurrentModule()
|
||||||
{
|
{
|
||||||
return (Module) SelectedProfile?.PluginInfo.Instance;
|
return (Module) SelectedProfile?.PluginInfo.Instance;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionsView"
|
<UserControl
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
@ -6,8 +6,13 @@
|
|||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions"
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
|
xmlns:Converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionsView"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance {x:Type local:DisplayConditionsViewModel}}">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
|
||||||
|
</UserControl.Resources>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
@ -19,9 +24,7 @@
|
|||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4">
|
<TextBlock Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4"><Run Text="Display conditions"/></TextBlock>
|
||||||
Display conditions
|
|
||||||
</TextBlock>
|
|
||||||
<Separator Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
|
<Separator Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
|
||||||
|
|
||||||
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
|
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
|
||||||
@ -31,38 +34,36 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Grid.Row="3" Grid.Column="0" Margin="13" Orientation="Horizontal" VerticalAlignment="Bottom" ToolTip="When conditions are met, only go through the entire timeline once.">
|
<StackPanel Grid.Row="3" Grid.Column="0" Margin="13" Orientation="Horizontal" VerticalAlignment="Bottom" ToolTip="When conditions are met, only go through the entire timeline once.">
|
||||||
<CheckBox Style="{StaticResource MaterialDesignCheckBox}">
|
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" Content="Play once" IsChecked="{Binding RenderProfileElement.RepeatMainSegment, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}"/>
|
||||||
Play once
|
|
||||||
</CheckBox>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right">
|
<StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right">
|
||||||
<TextBlock>When conditions no longer met</TextBlock>
|
<TextBlock><Run Text="When conditions no longer met"/></TextBlock>
|
||||||
<ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="{Binding CurrentTimelineIndex}" Height="20" Margin="0 5 0 0">
|
<ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="{Binding CurrentTimelineIndex}" Height="20" Margin="0 5 0 0">
|
||||||
<ListBoxItem Padding="10 0">
|
<ListBoxItem Padding="10 0">
|
||||||
<ListBoxItem.ToolTip>
|
<ListBoxItem.ToolTip>
|
||||||
<ToolTip Placement="Top" VerticalOffset="-5">
|
<ToolTip Placement="Top" VerticalOffset="-5">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock>When conditions are no longer met, finish the timelines and then stop displaying.</TextBlock>
|
<TextBlock><Run Text="When conditions are no longer met, finish the timelines and then stop displaying."/></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</ListBoxItem.ToolTip>
|
</ListBoxItem.ToolTip>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<materialDesign:PackIcon Kind="PlayArrow" Width="20" Height="20" Margin="0 -4" />
|
<materialDesign:PackIcon Kind="PlayArrow" Width="20" Height="20" Margin="0 -4" />
|
||||||
<TextBlock Margin="5 0 0 0" FontSize="11">WAIT FOR FINISH</TextBlock>
|
<TextBlock Margin="5 0 0 0" FontSize="11"><Run Text="WAIT FOR FINISH"/></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ListBoxItem>
|
</ListBoxItem>
|
||||||
<ListBoxItem Padding="10 0">
|
<ListBoxItem Padding="10 0">
|
||||||
<ListBoxItem.ToolTip>
|
<ListBoxItem.ToolTip>
|
||||||
<ToolTip Placement="Top" VerticalOffset="-5">
|
<ToolTip Placement="Top" VerticalOffset="-5">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock>When conditions are no longer met, stop displaying immediately.</TextBlock>
|
<TextBlock><Run Text="When conditions are no longer met, stop displaying immediately."/></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</ListBoxItem.ToolTip>
|
</ListBoxItem.ToolTip>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<materialDesign:PackIcon Kind="SkipNext" Width="20" Height="20" Margin="0 -4" />
|
<materialDesign:PackIcon Kind="SkipNext" Width="20" Height="20" Margin="0 -4" />
|
||||||
<TextBlock Margin="5 0 0 0" FontSize="11">SKIP</TextBlock>
|
<TextBlock Margin="5 0 0 0" FontSize="11"><Run Text="SKIP"/></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ListBoxItem>
|
</ListBoxItem>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
using Artemis.Core.Models.Profile.Conditions;
|
using Artemis.Core.Models.Profile;
|
||||||
|
using Artemis.Core.Models.Profile.Conditions;
|
||||||
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Shared.Events;
|
using Artemis.UI.Shared.Events;
|
||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
@ -9,6 +11,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
|||||||
{
|
{
|
||||||
private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory;
|
private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory;
|
||||||
private DisplayConditionGroupViewModel _rootGroup;
|
private DisplayConditionGroupViewModel _rootGroup;
|
||||||
|
private RenderProfileElement _renderProfileElement;
|
||||||
|
|
||||||
public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory)
|
public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory)
|
||||||
{
|
{
|
||||||
@ -22,8 +25,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
|
|||||||
set => SetAndNotify(ref _rootGroup, value);
|
set => SetAndNotify(ref _rootGroup, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RenderProfileElement RenderProfileElement
|
||||||
|
{
|
||||||
|
get => _renderProfileElement;
|
||||||
|
set => SetAndNotify(ref _renderProfileElement, value);
|
||||||
|
}
|
||||||
|
|
||||||
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
||||||
{
|
{
|
||||||
|
RenderProfileElement = e.RenderProfileElement;
|
||||||
|
|
||||||
if (e.RenderProfileElement == null)
|
if (e.RenderProfileElement == null)
|
||||||
{
|
{
|
||||||
RootGroup = null;
|
RootGroup = null;
|
||||||
|
|||||||
@ -199,26 +199,58 @@
|
|||||||
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" />
|
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" />
|
||||||
|
|
||||||
<!-- Start segment -->
|
<!-- Start segment -->
|
||||||
<TextBlock Grid.Column="0" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
|
<TextBlock Grid.Column="0"
|
||||||
|
TextAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Padding="0 3"
|
||||||
|
FontSize="11"
|
||||||
|
Background="{StaticResource MaterialDesignPaper}"
|
||||||
|
Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||||
|
ToolTip="This segment is played when a layer starts displaying because it's conditions are met">
|
||||||
Start
|
Start
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|
||||||
<!-- Main segment -->
|
<!-- Main segment -->
|
||||||
<TextBlock Grid.Column="1" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
|
<TextBlock Grid.Column="1"
|
||||||
|
TextAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Padding="0 3"
|
||||||
|
FontSize="11"
|
||||||
|
Background="{StaticResource MaterialDesignPaper}"
|
||||||
|
ToolTip="This segment is played while a condition is met, either once or on a repeating loop">
|
||||||
Main
|
Main
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|
||||||
<!-- End segment -->
|
<!-- End segment -->
|
||||||
<TextBlock Grid.Column="2" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
|
<TextBlock Grid.Column="2"
|
||||||
|
TextAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Padding="0 3"
|
||||||
|
FontSize="11"
|
||||||
|
Background="{StaticResource MaterialDesignPaper}"
|
||||||
|
Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||||
|
ToolTip="This segment is played once a condition is no longer met">
|
||||||
End
|
End
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|
||||||
|
<!-- Segment movement display -->
|
||||||
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
||||||
HorizontalAlignment="Right" Cursor="SizeWE" />
|
HorizontalAlignment="Right" Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
||||||
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
||||||
HorizontalAlignment="Right" Cursor="SizeWE" />
|
HorizontalAlignment="Right" />
|
||||||
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
|
||||||
HorizontalAlignment="Right" Cursor="SizeWE" />
|
HorizontalAlignment="Right" Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
||||||
|
|
||||||
|
<!-- Segment movement handles -->
|
||||||
|
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="20" VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||||
|
MouseDown="{s:Action StartSegmentMouseDown}" MouseUp="{s:Action StartSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}"/>
|
||||||
|
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="20" VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Right" Cursor="SizeWE"
|
||||||
|
MouseDown="{s:Action MainSegmentMouseDown}" MouseUp="{s:Action MainSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}"/>
|
||||||
|
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="Transparent" Margin="0 -1 -6 0" Width="16" Height="20" VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Right" Cursor="SizeWE" Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||||
|
MouseDown="{s:Action EndSegmentMouseDown}" MouseUp="{s:Action EndSegmentMouseUp}" MouseMove="{s:Action SegmentMouseMove}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
@ -320,6 +352,16 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||||
|
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding StartSegmentEnabled}">
|
||||||
|
Enable start segment
|
||||||
|
</CheckBox>
|
||||||
|
|
||||||
|
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" Margin="10 0" IsChecked="{Binding EndSegmentEnabled}">
|
||||||
|
Enable end segment
|
||||||
|
</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Zoom control -->
|
<!-- Zoom control -->
|
||||||
<Slider Grid.Column="1"
|
<Slider Grid.Column="1"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
@ -329,6 +371,7 @@
|
|||||||
Maximum="350"
|
Maximum="350"
|
||||||
TickFrequency="1"
|
TickFrequency="1"
|
||||||
IsSnapToTickEnabled="True"
|
IsSnapToTickEnabled="True"
|
||||||
|
AutoToolTipPlacement="TopLeft"
|
||||||
Value="{Binding ProfileEditorService.PixelsPerSecond}"
|
Value="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||||
Width="319" />
|
Width="319" />
|
||||||
|
|
||||||
|
|||||||
@ -94,12 +94,36 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
if (!SetAndNotify(ref _selectedProfileElement, value)) return;
|
if (!SetAndNotify(ref _selectedProfileElement, value)) return;
|
||||||
NotifyOfPropertyChange(nameof(SelectedLayer));
|
NotifyOfPropertyChange(nameof(SelectedLayer));
|
||||||
NotifyOfPropertyChange(nameof(SelectedFolder));
|
NotifyOfPropertyChange(nameof(SelectedFolder));
|
||||||
|
NotifyOfPropertyChange(nameof(StartSegmentEnabled));
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentEnabled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Layer SelectedLayer => SelectedProfileElement as Layer;
|
public Layer SelectedLayer => SelectedProfileElement as Layer;
|
||||||
public Folder SelectedFolder => SelectedProfileElement as Folder;
|
public Folder SelectedFolder => SelectedProfileElement as Folder;
|
||||||
|
|
||||||
|
public bool StartSegmentEnabled
|
||||||
|
{
|
||||||
|
get => SelectedProfileElement?.StartSegmentLength != TimeSpan.Zero;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SelectedProfileElement.StartSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero;
|
||||||
|
ProfileEditorService.UpdateSelectedProfileElement();
|
||||||
|
NotifyOfPropertyChange(nameof(StartSegmentEnabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EndSegmentEnabled
|
||||||
|
{
|
||||||
|
get => SelectedProfileElement?.EndSegmentLength != TimeSpan.Zero;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SelectedProfileElement.EndSegmentLength = value ? TimeSpan.FromSeconds(1) : TimeSpan.Zero;
|
||||||
|
ProfileEditorService.UpdateSelectedProfileElement();
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentEnabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups
|
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups
|
||||||
{
|
{
|
||||||
get => _layerPropertyGroups;
|
get => _layerPropertyGroups;
|
||||||
@ -123,7 +147,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
get => _timelineViewModel;
|
get => _timelineViewModel;
|
||||||
set => SetAndNotify(ref _timelineViewModel, value);
|
set => SetAndNotify(ref _timelineViewModel, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnInitialActivate()
|
protected override void OnInitialActivate()
|
||||||
{
|
{
|
||||||
PopulateProperties(ProfileEditorService.SelectedProfileElement);
|
PopulateProperties(ProfileEditorService.SelectedProfileElement);
|
||||||
@ -159,14 +183,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
|
|
||||||
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
||||||
{
|
{
|
||||||
// Placeholder
|
|
||||||
if (e.RenderProfileElement != null)
|
|
||||||
{
|
|
||||||
e.RenderProfileElement.StartSegmentLength = TimeSpan.FromMilliseconds(1000);
|
|
||||||
e.RenderProfileElement.MainSegmentLength = TimeSpan.FromMilliseconds(5000);
|
|
||||||
e.RenderProfileElement.EndSegmentLength = TimeSpan.FromMilliseconds(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
PopulateProperties(e.RenderProfileElement);
|
PopulateProperties(e.RenderProfileElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +192,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
NotifyOfPropertyChange(nameof(TimeCaretPosition));
|
NotifyOfPropertyChange(nameof(TimeCaretPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
|
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
NotifyOfPropertyChange(nameof(TimeCaretPosition));
|
NotifyOfPropertyChange(nameof(TimeCaretPosition));
|
||||||
@ -538,18 +553,23 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
else
|
else
|
||||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||||
|
|
||||||
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
// If holding down shift, snap to the closest segment or keyframe
|
||||||
|
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||||
{
|
{
|
||||||
ProfileEditorService.CurrentTime = newTime;
|
var snappedTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), true, false, true);
|
||||||
|
ProfileEditorService.CurrentTime = snappedTime;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var visibleKeyframes = GetKeyframes(true);
|
// If holding down control, round to the closest 50ms
|
||||||
|
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||||
|
{
|
||||||
|
var roundedTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0);
|
||||||
|
ProfileEditorService.CurrentTime = roundedTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Take a tolerance of 5 pixels (half a keyframe width)
|
ProfileEditorService.CurrentTime = newTime;
|
||||||
var tolerance = 1000f / ProfileEditorService.PixelsPerSecond * 5;
|
|
||||||
var closeKeyframe = visibleKeyframes.FirstOrDefault(k => Math.Abs(k.Position.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance);
|
|
||||||
ProfileEditorService.CurrentTime = closeKeyframe?.Position ?? newTime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,5 +583,98 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Segments
|
||||||
|
|
||||||
|
private bool _draggingStartSegment;
|
||||||
|
private bool _draggingMainSegment;
|
||||||
|
private bool _draggingEndSegment;
|
||||||
|
|
||||||
|
public void StartSegmentMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
|
_draggingStartSegment = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartSegmentMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
|
_draggingStartSegment = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MainSegmentMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
|
_draggingMainSegment = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MainSegmentMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
|
_draggingMainSegment = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndSegmentMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
|
_draggingEndSegment = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndSegmentMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
|
_draggingEndSegment = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SegmentMouseMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
{
|
||||||
|
// 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 / ProfileEditorService.PixelsPerSecond);
|
||||||
|
|
||||||
|
// Round the time to something that fits the current zoom level
|
||||||
|
if (ProfileEditorService.PixelsPerSecond < 200)
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||||
|
else if (ProfileEditorService.PixelsPerSecond < 500)
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||||
|
else
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||||
|
|
||||||
|
// If holding down shift, snap to the closest element on the timeline
|
||||||
|
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||||
|
{
|
||||||
|
newTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), false, true, true);
|
||||||
|
}
|
||||||
|
// If holding down control, round to the closest 50ms
|
||||||
|
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||||
|
{
|
||||||
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_draggingStartSegment)
|
||||||
|
{
|
||||||
|
if (newTime < TimeSpan.FromMilliseconds(100))
|
||||||
|
newTime = TimeSpan.FromMilliseconds(100);
|
||||||
|
SelectedProfileElement.StartSegmentLength = newTime;
|
||||||
|
}
|
||||||
|
else if (_draggingMainSegment)
|
||||||
|
{
|
||||||
|
if (newTime < SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100))
|
||||||
|
newTime = SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100);
|
||||||
|
SelectedProfileElement.MainSegmentLength = newTime - SelectedProfileElement.StartSegmentLength;
|
||||||
|
}
|
||||||
|
else if (_draggingEndSegment)
|
||||||
|
{
|
||||||
|
if (newTime < SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100))
|
||||||
|
newTime = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100);
|
||||||
|
SelectedProfileElement.EndSegmentLength = newTime - SelectedProfileElement.StartSegmentLength - SelectedProfileElement.MainSegmentLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,10 +145,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
subds = new object[] {6d, 6d, 30d};
|
subds = new object[] {6d, 6d, 30d};
|
||||||
else if (PixelsPerSecond > 150)
|
else if (PixelsPerSecond > 150)
|
||||||
subds = new object[] {4d, 4d, 20d};
|
subds = new object[] {4d, 4d, 20d};
|
||||||
else if (PixelsPerSecond > 100)
|
else if (PixelsPerSecond > 140)
|
||||||
subds = new object[] {4d, 4d, 8d};
|
subds = new object[] {4d, 4d, 20d};
|
||||||
else if (PixelsPerSecond > 90)
|
else if (PixelsPerSecond > 90)
|
||||||
subds = new object[] {4d, 4d, 8d};
|
subds = new object[] {2d, 4d, 20d};
|
||||||
else if (PixelsPerSecond > 60)
|
else if (PixelsPerSecond > 60)
|
||||||
subds = new object[] {2d, 4d, 8d};
|
subds = new object[] {2d, 4d, 8d};
|
||||||
else if (PixelsPerSecond > 40)
|
else if (PixelsPerSecond > 40)
|
||||||
|
|||||||
@ -119,29 +119,51 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
#region Movement
|
#region Movement
|
||||||
|
|
||||||
private bool _movementReleased = true;
|
private TimeSpan? _offset;
|
||||||
private TimeSpan _startOffset;
|
|
||||||
|
|
||||||
public void ApplyMovement(TimeSpan cursorTime)
|
public void ApplyMovement(TimeSpan cursorTime)
|
||||||
{
|
{
|
||||||
if (_movementReleased)
|
UpdatePosition(cursorTime);
|
||||||
{
|
Update(_pixelsPerSecond);
|
||||||
_movementReleased = false;
|
|
||||||
_startOffset = cursorTime - BaseLayerPropertyKeyframe.Position;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BaseLayerPropertyKeyframe.Position = cursorTime - _startOffset;
|
|
||||||
if (BaseLayerPropertyKeyframe.Position < TimeSpan.Zero)
|
|
||||||
BaseLayerPropertyKeyframe.Position = TimeSpan.Zero;
|
|
||||||
|
|
||||||
Update(_pixelsPerSecond);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReleaseMovement()
|
public void ReleaseMovement()
|
||||||
{
|
{
|
||||||
_movementReleased = true;
|
_offset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel)
|
||||||
|
{
|
||||||
|
if (keyframeViewModel == this)
|
||||||
|
{
|
||||||
|
_offset = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_offset != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_offset = BaseLayerPropertyKeyframe.Position - keyframeViewModel.BaseLayerPropertyKeyframe.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel)
|
||||||
|
{
|
||||||
|
if (keyframeViewModel == this || _offset == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdatePosition(keyframeViewModel.BaseLayerPropertyKeyframe.Position + _offset.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePosition(TimeSpan position)
|
||||||
|
{
|
||||||
|
if (position < TimeSpan.Zero)
|
||||||
|
BaseLayerPropertyKeyframe.Position = TimeSpan.Zero;
|
||||||
|
else if (position > _profileEditorService.SelectedProfileElement.TimelineLength)
|
||||||
|
BaseLayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.TimelineLength;
|
||||||
|
else
|
||||||
|
BaseLayerPropertyKeyframe.Position = position;
|
||||||
|
|
||||||
|
Update(_pixelsPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -13,7 +13,8 @@
|
|||||||
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||||
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
||||||
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
||||||
MouseMove="{s:Action TimelineCanvasMouseMove}">
|
MouseMove="{s:Action TimelineCanvasMouseMove}"
|
||||||
|
Margin="0 0 -1 0">
|
||||||
<Grid.Triggers>
|
<Grid.Triggers>
|
||||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||||
<BeginStoryboard>
|
<BeginStoryboard>
|
||||||
@ -32,7 +33,7 @@
|
|||||||
</Grid.Triggers>
|
</Grid.Triggers>
|
||||||
|
|
||||||
<ItemsControl ItemsSource="{Binding LayerPropertyGroups}"
|
<ItemsControl ItemsSource="{Binding LayerPropertyGroups}"
|
||||||
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}"
|
MinWidth="{Binding TotalTimelineWidth}"
|
||||||
HorizontalAlignment="Left">
|
HorizontalAlignment="Left">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
@ -48,7 +49,8 @@
|
|||||||
X2="{Binding StartSegmentEndPosition}"
|
X2="{Binding StartSegmentEndPosition}"
|
||||||
Y1="0"
|
Y1="0"
|
||||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
|
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
|
||||||
HorizontalAlignment="Left" />
|
HorizontalAlignment="Left"
|
||||||
|
Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"/>
|
||||||
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
||||||
Opacity="0.5"
|
Opacity="0.5"
|
||||||
StrokeDashArray="4 2"
|
StrokeDashArray="4 2"
|
||||||
@ -62,7 +64,8 @@
|
|||||||
X1="{Binding EndSegmentEndPosition}"
|
X1="{Binding EndSegmentEndPosition}"
|
||||||
X2="{Binding EndSegmentEndPosition}"
|
X2="{Binding EndSegmentEndPosition}"
|
||||||
Y1="0"
|
Y1="0"
|
||||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}" />
|
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
|
||||||
|
Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"/>
|
||||||
|
|
||||||
<!-- Multi-selection rectangle -->
|
<!-- Multi-selection rectangle -->
|
||||||
<Path x:Name="MultiSelectionPath"
|
<Path x:Name="MultiSelectionPath"
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
@ -27,6 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
LayerPropertyGroups = layerPropertyGroups;
|
LayerPropertyGroups = layerPropertyGroups;
|
||||||
SelectionRectangle = new RectangleGeometry();
|
SelectionRectangle = new RectangleGeometry();
|
||||||
|
|
||||||
|
_profileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
|
||||||
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
@ -46,10 +48,42 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
public double MainSegmentEndPosition => StartSegmentWidth + MainSegmentWidth;
|
public double MainSegmentEndPosition => StartSegmentWidth + MainSegmentWidth;
|
||||||
public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.EndSegmentLength.TotalSeconds ?? 0;
|
public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.EndSegmentLength.TotalSeconds ?? 0;
|
||||||
public double EndSegmentEndPosition => StartSegmentWidth + MainSegmentWidth + EndSegmentWidth;
|
public double EndSegmentEndPosition => StartSegmentWidth + MainSegmentWidth + EndSegmentWidth;
|
||||||
|
public double TotalTimelineWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.TimelineLength.TotalSeconds ?? 0;
|
||||||
|
|
||||||
|
public bool StartSegmentEnabled => _profileEditorService.SelectedProfileElement?.StartSegmentLength != TimeSpan.Zero;
|
||||||
|
public bool EndSegmentEnabled => _profileEditorService.SelectedProfileElement?.EndSegmentLength != TimeSpan.Zero;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||||
|
_profileEditorService.SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(_profileEditorService.SelectedProfileElement.StartSegmentLength))
|
||||||
|
{
|
||||||
|
NotifyOfPropertyChange(nameof(StartSegmentWidth));
|
||||||
|
NotifyOfPropertyChange(nameof(StartSegmentEndPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(StartSegmentEnabled));
|
||||||
|
NotifyOfPropertyChange(nameof(TotalTimelineWidth));
|
||||||
|
}
|
||||||
|
else if (e.PropertyName == nameof(_profileEditorService.SelectedProfileElement.MainSegmentLength))
|
||||||
|
{
|
||||||
|
NotifyOfPropertyChange(nameof(MainSegmentWidth));
|
||||||
|
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(TotalTimelineWidth));
|
||||||
|
}
|
||||||
|
else if (e.PropertyName == nameof(_profileEditorService.SelectedProfileElement.EndSegmentLength))
|
||||||
|
{
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentWidth));
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(EndSegmentEnabled));
|
||||||
|
NotifyOfPropertyChange(nameof(TotalTimelineWidth));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
@ -74,6 +108,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
|
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
|
||||||
NotifyOfPropertyChange(nameof(EndSegmentWidth));
|
NotifyOfPropertyChange(nameof(EndSegmentWidth));
|
||||||
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
|
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
|
||||||
|
NotifyOfPropertyChange(nameof(TotalTimelineWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Command handlers
|
#region Command handlers
|
||||||
@ -108,8 +143,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
public void KeyframeMouseMove(object sender, MouseEventArgs e)
|
public void KeyframeMouseMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
|
var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel;
|
||||||
|
if (viewModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (e.LeftButton == MouseButtonState.Pressed)
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
MoveSelectedKeyframes(GetCursorTime(e.GetPosition(View)));
|
MoveSelectedKeyframes(GetCursorTime(e.GetPosition(View)), viewModel);
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
@ -161,6 +200,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
var tolerance = 1000f / _profileEditorService.PixelsPerSecond * 5;
|
var tolerance = 1000f / _profileEditorService.PixelsPerSecond * 5;
|
||||||
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
|
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
|
||||||
time = _profileEditorService.CurrentTime;
|
time = _profileEditorService.CurrentTime;
|
||||||
|
else if (Math.Abs(_profileEditorService.SelectedProfileElement.StartSegmentLength.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
|
||||||
|
time = _profileEditorService.SelectedProfileElement.StartSegmentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
return time;
|
return time;
|
||||||
@ -170,14 +211,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
#region Keyframe movement
|
#region Keyframe movement
|
||||||
|
|
||||||
public void MoveSelectedKeyframes(TimeSpan cursorTime)
|
public void MoveSelectedKeyframes(TimeSpan cursorTime, TimelineKeyframeViewModel sourceKeyframeViewModel)
|
||||||
{
|
{
|
||||||
// Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging
|
// Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging
|
||||||
SelectionRectangle.Rect = new Rect();
|
SelectionRectangle.Rect = new Rect();
|
||||||
|
|
||||||
var keyframeViewModels = GetAllKeyframeViewModels();
|
var keyframeViewModels = GetAllKeyframeViewModels();
|
||||||
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||||
keyframeViewModel.ApplyMovement(cursorTime);
|
keyframeViewModel.SaveOffsetToKeyframe(sourceKeyframeViewModel);
|
||||||
|
|
||||||
|
sourceKeyframeViewModel.ApplyMovement(cursorTime);
|
||||||
|
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||||
|
keyframeViewModel.ApplyOffsetToKeyframe(sourceKeyframeViewModel);
|
||||||
|
|
||||||
_layerPropertiesViewModel.ProfileEditorService.UpdateProfilePreview();
|
_layerPropertiesViewModel.ProfileEditorService.UpdateProfilePreview();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user