1
0
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:
SpoinkyNL 2020-07-18 23:52:42 +02:00
parent c5219bd224
commit 527fef3dc6
17 changed files with 527 additions and 154 deletions

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.LayerEffect.Abstract;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
@ -24,6 +25,7 @@ namespace Artemis.Core.Models.Profile
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
ApplyRenderElementDefaults();
}
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
@ -42,8 +44,6 @@ namespace Artemis.Core.Models.Profile
_expandedPropertyGroups = new List<string>();
_expandedPropertyGroups.AddRange(folderEntity.ExpandedPropertyGroups);
// TODO: Load conditions
// Load child folders
foreach (var childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(profile, this, childFolder));
@ -55,6 +55,8 @@ namespace Artemis.Core.Models.Profile
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList();
for (var index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
ApplyRenderElementEntity();
}
internal FolderEntity FolderEntity { get; set; }
@ -187,7 +189,7 @@ namespace Artemis.Core.Models.Profile
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups);
ApplyLayerEffectsToEntity();
ApplyRenderElementToEntity();
// Conditions
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity;

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.Extensions;
using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.Core.Models.Profile.LayerShapes;
@ -48,6 +47,7 @@ namespace Artemis.Core.Models.Profile
_expandedPropertyGroups = new List<string>();
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
ApplyRenderElementDefaults();
}
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
@ -69,6 +69,8 @@ namespace Artemis.Core.Models.Profile
_expandedPropertyGroups.AddRange(layerEntity.ExpandedPropertyGroups);
General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized;
ApplyRenderElementEntity();
ApplyRenderElementDefaults();
}
internal LayerEntity LayerEntity { get; set; }
@ -115,14 +117,27 @@ namespace Artemis.Core.Models.Profile
get => _layerBrush;
internal set => SetAndNotify(ref _layerBrush, value);
}
public override string ToString()
{
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
internal override void ApplyToEntity()
@ -142,7 +157,7 @@ namespace Artemis.Core.Models.Profile
LayerBrush?.BaseProperties.ApplyToEntity();
// Effects
ApplyLayerEffectsToEntity();
ApplyRenderElementToEntity();
// LEDs
LayerEntity.Leds.Clear();
@ -206,34 +221,18 @@ namespace Artemis.Core.Models.Profile
return;
UpdateDisplayCondition();
deltaTime = UpdateTimeline(deltaTime);
// TODO: Remove, this is slow and stupid
// For now, reset all keyframe engines after the last keyframe was hit
// This is a placeholder method of repeating the animation until repeat modes are implemented
var 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);
}
General.Update(deltaTime);
Transform.Update(deltaTime);
LayerBrush.BaseProperties?.Update(deltaTime);
LayerBrush.Update(deltaTime);
foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{
baseLayerEffect.BaseProperties?.Update(deltaTime);
baseLayerEffect.Update(deltaTime);
}
}
public void OverrideProgress(TimeSpan timeOverride)

View File

@ -5,6 +5,7 @@ using System.Linq;
using Artemis.Core.Annotations;
using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.LayerBrush.Abstract;
using Artemis.Core.Plugins.LayerEffect.Abstract;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
@ -14,6 +15,62 @@ namespace Artemis.Core.Models.Profile
{
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
private SKPath _path;
@ -101,6 +158,15 @@ namespace Artemis.Core.Models.Profile
set => SetAndNotify(ref _endSegmentLength, value);
}
/// <summary>
/// Gets the current timeline position
/// </summary>
public TimeSpan TimelinePosition
{
get => _timelinePosition;
private set => SetAndNotify(ref _timelinePosition, value);
}
/// <summary>
/// Gets the total combined length of all three segments
/// </summary>
@ -124,6 +190,35 @@ namespace Artemis.Core.Models.Profile
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
#region Effects
@ -135,26 +230,6 @@ namespace Artemis.Core.Models.Profile
/// </summary>
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)
{
if (effect == null) throw new ArgumentNullException(nameof(effect));
@ -192,8 +267,20 @@ namespace Artemis.Core.Models.Profile
#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 TimeSpan _timelinePosition;
private bool _displayConditionMet;
/// <summary>
/// Gets or sets the root display condition group
/// </summary>
@ -205,7 +292,11 @@ namespace Artemis.Core.Models.Profile
public void UpdateDisplayCondition()
{
var rootGroupResult = DisplayConditionGroup.Evaluate();
var conditionMet = DisplayConditionGroup == null || DisplayConditionGroup.Evaluate();
if (conditionMet && !DisplayConditionMet)
TimelinePosition = TimeSpan.Zero;
DisplayConditionMet = conditionMet;
}
#endregion

View File

@ -98,7 +98,7 @@ namespace Artemis.Core.Plugins.LayerBrush.Abstract
/// <summary>
/// Called before rendering every frame, write your update logic here
/// </summary>
/// <param name="deltaTime"></param>
/// <param name="deltaTime">Seconds passed since last update</param>
public abstract void Update(double deltaTime);
/// <summary>

View File

@ -1,9 +1,16 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.Abstract
{
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<PropertyEntity> PropertyEntities { get; set; }
public List<string> ExpandedPropertyGroups { get; set; }

View File

@ -8,6 +8,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Artemis.Core.Models.Surface;
using RGB.NET.Core;
using Size = System.Windows.Size;
@ -28,17 +29,20 @@ namespace Artemis.UI.Shared.Controls
private readonly DrawingGroup _backingStore;
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
private BitmapImage _deviceImage;
private bool _subscribed;
private ArtemisDevice _oldDevice;
private Task _lastRenderTask;
private readonly DispatcherTimer _timer;
public DeviceVisualizer()
{
_backingStore = new DrawingGroup();
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
Loaded += (sender, args) => SubscribeToUpdate(true);
Unloaded += (sender, args) => SubscribeToUpdate(false);
// Run an update timer at 25 fps
_timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(40)};
_timer.Tick += TimerOnTick;
Loaded += (sender, args) => _timer.Start();
Unloaded += (sender, args) => _timer.Stop();
}
public ArtemisDevice Device
@ -61,7 +65,7 @@ namespace Artemis.UI.Shared.Controls
public void Dispose()
{
RGBSurface.Instance.Updated -= RgbSurfaceOnUpdated;
_timer.Stop();
}
protected override void OnRender(DrawingContext drawingContext)
@ -73,7 +77,7 @@ namespace Artemis.UI.Shared.Controls
var measureSize = MeasureOverride(Size.Empty);
var scale = Math.Min(DesiredSize.Width / measureSize.Width, DesiredSize.Height / measureSize.Height);
var scaledRect = new Rect(0, 0, measureSize.Width * scale, measureSize.Height * scale);
// Center and scale the visualization in the desired bounding box
if (DesiredSize.Width > 0 && DesiredSize.Height > 0)
{
@ -95,7 +99,7 @@ namespace Artemis.UI.Shared.Controls
// Render device and LED images
if (_deviceImage != null)
drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height));
foreach (var deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderImage(drawingContext);
@ -113,26 +117,18 @@ namespace Artemis.UI.Shared.Controls
return rotationRect.Size;
}
private void TimerOnTick(object sender, EventArgs e)
{
if (ShowColors && Visibility == Visibility.Visible)
Render();
}
private void UpdateTransform()
{
InvalidateVisual();
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)
{
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))
UpdateTransform();
}
private void RgbSurfaceOnUpdated(UpdatedEventArgs e)
{
_lastRenderTask?.Wait();
_lastRenderTask = Dispatcher.InvokeAsync(() =>
{
if (ShowColors && Visibility == Visibility.Visible)
Render();
}).Task;
}
private void Render()
{

View File

@ -44,7 +44,7 @@ namespace Artemis.UI.Shared.Controls
var r = Led.RgbLed.Color.GetR();
var g = Led.RgbLed.Color.GetG();
var b = Led.RgbLed.Color.GetB();
drawingContext.DrawRectangle(isDimmed
? new SolidColorBrush(Color.FromArgb(100, r, g, b))
: new SolidColorBrush(Color.FromRgb(r, g, b)), null, LedRect);

View File

@ -74,5 +74,13 @@ namespace Artemis.UI.Shared.Services.Interfaces
PropertyInputRegistration RegisterPropertyInput<T>(PluginInfo pluginInfo) where T : PropertyInputViewModel;
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);
}
}

View File

@ -34,7 +34,7 @@ namespace Artemis.UI.Shared.Services
_registeredPropertyEditors = new List<PropertyInputRegistration>();
Kernel = kernel;
PixelsPerSecond = 31;
PixelsPerSecond = 100;
}
public IKernel Kernel { get; }
@ -48,7 +48,7 @@ namespace Artemis.UI.Shared.Services
set
{
if (_currentTime.Equals(value)) return;
if (value > SelectedProfileElement.TimelineLength)
if (SelectedProfileElement != null && value > SelectedProfileElement.TimelineLength)
_currentTime = SelectedProfileElement.TimelineLength;
else
_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()
{
return (Module) SelectedProfile?.PluginInfo.Instance;

View File

@ -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:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -6,8 +6,13 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
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"
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.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -19,9 +24,7 @@
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4">
Display conditions
</TextBlock>
<TextBlock Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="10 5 0 -4"><Run Text="Display conditions"/></TextBlock>
<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">
@ -31,38 +34,36 @@
</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.">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}">
Play once
</CheckBox>
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" Content="Play once" IsChecked="{Binding RenderProfileElement.RepeatMainSegment, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}"/>
</StackPanel>
<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">
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<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>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<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>
</ListBoxItem>
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<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>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<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>
</ListBoxItem>
</ListBox>

View File

@ -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.Shared.Events;
using Artemis.UI.Shared.Services.Interfaces;
@ -9,6 +11,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
{
private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory;
private DisplayConditionGroupViewModel _rootGroup;
private RenderProfileElement _renderProfileElement;
public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory)
{
@ -22,8 +25,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions
set => SetAndNotify(ref _rootGroup, value);
}
public RenderProfileElement RenderProfileElement
{
get => _renderProfileElement;
set => SetAndNotify(ref _renderProfileElement, value);
}
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
{
RenderProfileElement = e.RenderProfileElement;
if (e.RenderProfileElement == null)
{
RootGroup = null;

View File

@ -199,26 +199,58 @@
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" />
<!-- 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
</TextBlock>
<!-- 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
</TextBlock>
<!-- 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
</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"
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"
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"
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>
</ScrollViewer>
@ -320,6 +352,16 @@
<ColumnDefinition Width="Auto" />
</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 -->
<Slider Grid.Column="1"
Orientation="Horizontal"
@ -329,6 +371,7 @@
Maximum="350"
TickFrequency="1"
IsSnapToTickEnabled="True"
AutoToolTipPlacement="TopLeft"
Value="{Binding ProfileEditorService.PixelsPerSecond}"
Width="319" />

View File

@ -94,12 +94,36 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
if (!SetAndNotify(ref _selectedProfileElement, value)) return;
NotifyOfPropertyChange(nameof(SelectedLayer));
NotifyOfPropertyChange(nameof(SelectedFolder));
NotifyOfPropertyChange(nameof(StartSegmentEnabled));
NotifyOfPropertyChange(nameof(EndSegmentEnabled));
}
}
public Layer SelectedLayer => SelectedProfileElement as Layer;
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
{
get => _layerPropertyGroups;
@ -123,7 +147,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
get => _timelineViewModel;
set => SetAndNotify(ref _timelineViewModel, value);
}
protected override void OnInitialActivate()
{
PopulateProperties(ProfileEditorService.SelectedProfileElement);
@ -159,14 +183,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
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);
}
@ -176,7 +192,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
NotifyOfPropertyChange(nameof(TimeCaretPosition));
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(TimeCaretPosition));
@ -538,18 +553,23 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
else
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;
}
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)
var tolerance = 1000f / ProfileEditorService.PixelsPerSecond * 5;
var closeKeyframe = visibleKeyframes.FirstOrDefault(k => Math.Abs(k.Position.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance);
ProfileEditorService.CurrentTime = closeKeyframe?.Position ?? newTime;
ProfileEditorService.CurrentTime = newTime;
}
}
@ -563,5 +583,98 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
#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
}
}

View File

@ -145,10 +145,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
subds = new object[] {6d, 6d, 30d};
else if (PixelsPerSecond > 150)
subds = new object[] {4d, 4d, 20d};
else if (PixelsPerSecond > 100)
subds = new object[] {4d, 4d, 8d};
else if (PixelsPerSecond > 140)
subds = new object[] {4d, 4d, 20d};
else if (PixelsPerSecond > 90)
subds = new object[] {4d, 4d, 8d};
subds = new object[] {2d, 4d, 20d};
else if (PixelsPerSecond > 60)
subds = new object[] {2d, 4d, 8d};
else if (PixelsPerSecond > 40)

View File

@ -119,29 +119,51 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
#region Movement
private bool _movementReleased = true;
private TimeSpan _startOffset;
private TimeSpan? _offset;
public void ApplyMovement(TimeSpan cursorTime)
{
if (_movementReleased)
{
_movementReleased = false;
_startOffset = cursorTime - BaseLayerPropertyKeyframe.Position;
}
else
{
BaseLayerPropertyKeyframe.Position = cursorTime - _startOffset;
if (BaseLayerPropertyKeyframe.Position < TimeSpan.Zero)
BaseLayerPropertyKeyframe.Position = TimeSpan.Zero;
Update(_pixelsPerSecond);
}
UpdatePosition(cursorTime);
Update(_pixelsPerSecond);
}
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

View File

@ -13,7 +13,8 @@
Background="{DynamicResource MaterialDesignToolBarBackground}"
MouseDown="{s:Action TimelineCanvasMouseDown}"
MouseUp="{s:Action TimelineCanvasMouseUp}"
MouseMove="{s:Action TimelineCanvasMouseMove}">
MouseMove="{s:Action TimelineCanvasMouseMove}"
Margin="0 0 -1 0">
<Grid.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
<BeginStoryboard>
@ -32,7 +33,7 @@
</Grid.Triggers>
<ItemsControl ItemsSource="{Binding LayerPropertyGroups}"
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}"
MinWidth="{Binding TotalTimelineWidth}"
HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
@ -48,7 +49,8 @@
X2="{Binding StartSegmentEndPosition}"
Y1="0"
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
Visibility="{Binding StartSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"/>
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
Opacity="0.5"
StrokeDashArray="4 2"
@ -62,7 +64,8 @@
X1="{Binding EndSegmentEndPosition}"
X2="{Binding EndSegmentEndPosition}"
Y1="0"
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}" />
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
Visibility="{Binding EndSegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"/>
<!-- Multi-selection rectangle -->
<Path x:Name="MultiSelectionPath"

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@ -27,6 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
LayerPropertyGroups = layerPropertyGroups;
SelectionRectangle = new RectangleGeometry();
_profileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
Update();
@ -46,10 +48,42 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public double MainSegmentEndPosition => StartSegmentWidth + MainSegmentWidth;
public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.EndSegmentLength.TotalSeconds ?? 0;
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()
{
_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()
@ -74,6 +108,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
NotifyOfPropertyChange(nameof(EndSegmentWidth));
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
NotifyOfPropertyChange(nameof(TotalTimelineWidth));
}
#region Command handlers
@ -108,8 +143,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public void KeyframeMouseMove(object sender, MouseEventArgs e)
{
var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel;
if (viewModel == null)
return;
if (e.LeftButton == MouseButtonState.Pressed)
MoveSelectedKeyframes(GetCursorTime(e.GetPosition(View)));
MoveSelectedKeyframes(GetCursorTime(e.GetPosition(View)), viewModel);
e.Handled = true;
}
@ -161,6 +200,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
var tolerance = 1000f / _profileEditorService.PixelsPerSecond * 5;
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
time = _profileEditorService.CurrentTime;
else if (Math.Abs(_profileEditorService.SelectedProfileElement.StartSegmentLength.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
time = _profileEditorService.SelectedProfileElement.StartSegmentLength;
}
return time;
@ -170,14 +211,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
#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
SelectionRectangle.Rect = new Rect();
var keyframeViewModels = GetAllKeyframeViewModels();
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();
}