mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Profile editor - Implemented timeline segments
Profile editor - Added snapping to multiple elements of the timeline
This commit is contained in:
1 parent
c5219bd224
commit
527fef3dc6
17 files changed
+527
-154
No files matched your search
+14
-13
@@ -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>
|
||||
|
||||
+12
-1
@@ -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;
|
||||
|
||||
+49
-6
@@ -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" />
|
||||
|
||||
|
||||
+130
-17
@@ -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
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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)
|
||||
|
||||
+38
-16
@@ -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
|
||||
|
||||
+7
-4
@@ -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"
|
||||
|
||||
+49
-3
@@ -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();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user