mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Keyframes - Copy/paste WIP
Timeline - Improved sizing, avoid unnecessary scrolling Timeline - Fix selection rectangle appearing on mousedown
This commit is contained in:
parent
7c955d1134
commit
f110383ed4
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
|
namespace Artemis.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a keyframe on a <see cref="ILayerProperty" /> containing a value and a timestamp
|
||||||
|
/// </summary>
|
||||||
|
public interface ILayerPropertyKeyframe
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an untyped reference to the layer property of this keyframe
|
||||||
|
/// </summary>
|
||||||
|
ILayerProperty UntypedLayerProperty { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The position of this keyframe in the timeline
|
||||||
|
/// </summary>
|
||||||
|
TimeSpan Position { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The easing function applied on the value of the keyframe
|
||||||
|
/// </summary>
|
||||||
|
Easings.Functions EasingFunction { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity this keyframe uses for persistent storage
|
||||||
|
/// </summary>
|
||||||
|
KeyframeEntity GetKeyframeEntity();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the keyframe from the layer property
|
||||||
|
/// </summary>
|
||||||
|
void Remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -559,12 +559,7 @@ namespace Artemis.Core
|
|||||||
Entity.Value = CoreJson.SerializeObject(BaseValue);
|
Entity.Value = CoreJson.SerializeObject(BaseValue);
|
||||||
Entity.KeyframesEnabled = KeyframesEnabled;
|
Entity.KeyframesEnabled = KeyframesEnabled;
|
||||||
Entity.KeyframeEntities.Clear();
|
Entity.KeyframeEntities.Clear();
|
||||||
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity
|
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
|
||||||
{
|
|
||||||
Value = CoreJson.SerializeObject(k.Value),
|
|
||||||
Position = k.Position,
|
|
||||||
EasingFunction = (int) k.EasingFunction
|
|
||||||
}));
|
|
||||||
|
|
||||||
Entity.DataBindingEntities.Clear();
|
Entity.DataBindingEntities.Clear();
|
||||||
foreach (IDataBinding dataBinding in _dataBindings)
|
foreach (IDataBinding dataBinding in _dataBindings)
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a keyframe on a <see cref="LayerProperty{T}" /> containing a value and a timestamp
|
/// Represents a keyframe on a <see cref="LayerProperty{T}" /> containing a value and a timestamp
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LayerPropertyKeyframe<T> : CorePropertyChanged
|
public class LayerPropertyKeyframe<T> : CorePropertyChanged, ILayerPropertyKeyframe
|
||||||
{
|
{
|
||||||
private LayerProperty<T> _layerProperty;
|
private LayerProperty<T> _layerProperty;
|
||||||
private TimeSpan _position;
|
private TimeSpan _position;
|
||||||
@ -45,10 +46,10 @@ namespace Artemis.Core
|
|||||||
set => SetAndNotify(ref _value, value);
|
set => SetAndNotify(ref _value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ILayerProperty UntypedLayerProperty => LayerProperty;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The position of this keyframe in the timeline
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan Position
|
public TimeSpan Position
|
||||||
{
|
{
|
||||||
get => _position;
|
get => _position;
|
||||||
@ -59,14 +60,21 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The easing function applied on the value of the keyframe
|
|
||||||
/// </summary>
|
|
||||||
public Easings.Functions EasingFunction { get; set; }
|
public Easings.Functions EasingFunction { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Removes the keyframe from the layer property
|
public KeyframeEntity GetKeyframeEntity()
|
||||||
/// </summary>
|
{
|
||||||
|
return new KeyframeEntity
|
||||||
|
{
|
||||||
|
Value = CoreJson.SerializeObject(Value),
|
||||||
|
Position = Position,
|
||||||
|
EasingFunction = (int) EasingFunction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Remove()
|
public void Remove()
|
||||||
{
|
{
|
||||||
LayerProperty.RemoveKeyframe(this);
|
LayerProperty.RemoveKeyframe(this);
|
||||||
|
|||||||
@ -123,7 +123,7 @@ namespace Artemis.Core
|
|||||||
/// Adds a profile element to the <see cref="Children" /> collection, optionally at the given position (1-based)
|
/// Adds a profile element to the <see cref="Children" /> collection, optionally at the given position (1-based)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="child">The profile element to add</param>
|
/// <param name="child">The profile element to add</param>
|
||||||
/// <param name="order">The order where to place the child (1-based), defaults to the end of the collection</param>
|
/// <param name="order">The order where to place the child (0-based), defaults to the end of the collection</param>
|
||||||
public virtual void AddChild(ProfileElement child, int? order = null)
|
public virtual void AddChild(ProfileElement child, int? order = null)
|
||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
@ -136,31 +136,19 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
// Add to the end of the list
|
// Add to the end of the list
|
||||||
if (order == null)
|
if (order == null)
|
||||||
{
|
|
||||||
ChildrenList.Add(child);
|
ChildrenList.Add(child);
|
||||||
child.Order = ChildrenList.Count;
|
// Insert at the given index
|
||||||
}
|
|
||||||
// Shift everything after the given order
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (order < 0)
|
if (order < 0)
|
||||||
order = 0;
|
order = 0;
|
||||||
foreach (ProfileElement profileElement in ChildrenList.Where(c => c.Order >= order).ToList())
|
if (order > ChildrenList.Count)
|
||||||
profileElement.Order++;
|
order = ChildrenList.Count;
|
||||||
|
ChildrenList.Insert(order.Value, child);
|
||||||
int targetIndex;
|
|
||||||
if (order == 0)
|
|
||||||
targetIndex = 0;
|
|
||||||
else if (order > ChildrenList.Count)
|
|
||||||
targetIndex = ChildrenList.Count;
|
|
||||||
else
|
|
||||||
targetIndex = ChildrenList.FindIndex(c => c.Order == order + 1);
|
|
||||||
|
|
||||||
ChildrenList.Insert(targetIndex, child);
|
|
||||||
child.Order = order.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
child.Parent = this;
|
child.Parent = this;
|
||||||
|
StreamlineOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
OnChildAdded();
|
OnChildAdded();
|
||||||
@ -178,10 +166,7 @@ namespace Artemis.Core
|
|||||||
lock (ChildrenList)
|
lock (ChildrenList)
|
||||||
{
|
{
|
||||||
ChildrenList.Remove(child);
|
ChildrenList.Remove(child);
|
||||||
|
StreamlineOrder();
|
||||||
// Shift everything after the given order
|
|
||||||
foreach (ProfileElement profileElement in ChildrenList.Where(c => c.Order > child.Order).ToList())
|
|
||||||
profileElement.Order--;
|
|
||||||
|
|
||||||
child.Parent = null;
|
child.Parent = null;
|
||||||
}
|
}
|
||||||
@ -189,6 +174,12 @@ namespace Artemis.Core
|
|||||||
OnChildRemoved();
|
OnChildRemoved();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StreamlineOrder()
|
||||||
|
{
|
||||||
|
for (int index = 0; index < ChildrenList.Count; index++)
|
||||||
|
ChildrenList[index].Order = index;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a flattened list of all child folders
|
/// Returns a flattened list of all child folders
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -156,6 +156,11 @@ namespace Artemis.UI.Shared.Services
|
|||||||
/// <returns>The pasted render element</returns>
|
/// <returns>The pasted render element</returns>
|
||||||
ProfileElement? PasteProfileElement(Folder target, int position);
|
ProfileElement? PasteProfileElement(Folder target, int position);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether a profile element is on the clipboard
|
||||||
|
/// </summary>
|
||||||
|
bool GetCanPasteProfileElement();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a new profile is selected
|
/// Occurs when a new profile is selected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -384,6 +384,12 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return clipboardObject != null ? PasteClipboardData(clipboardObject, target, position) : null;
|
return clipboardObject != null ? PasteClipboardData(clipboardObject, target, position) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool GetCanPasteProfileElement()
|
||||||
|
{
|
||||||
|
object? clipboardObject = JsonClipboard.GetData();
|
||||||
|
return clipboardObject is LayerEntity || clipboardObject is FolderClipboardModel;
|
||||||
|
}
|
||||||
|
|
||||||
private RenderProfileElement? PasteClipboardData(object clipboardObject, Folder target, int position)
|
private RenderProfileElement? PasteClipboardData(object clipboardObject, Folder target, int position)
|
||||||
{
|
{
|
||||||
RenderProfileElement? pasted = null;
|
RenderProfileElement? pasted = null;
|
||||||
|
|||||||
@ -160,7 +160,9 @@
|
|||||||
VerticalScrollBarVisibility="Hidden"
|
VerticalScrollBarVisibility="Hidden"
|
||||||
ScrollChanged="TimelineScrollChanged">
|
ScrollChanged="TimelineScrollChanged">
|
||||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||||
<ContentControl s:View.Model="{Binding TreeViewModel}" />
|
<ContentControl s:View.Model="{Binding TreeViewModel}"
|
||||||
|
shared:SizeObserver.Observe="True"
|
||||||
|
shared:SizeObserver.ObservedHeight="{Binding TreeViewModelHeight, Mode=OneWayToSource}"/>
|
||||||
</Border>
|
</Border>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<materialDesign:TransitionerSlide>
|
<materialDesign:TransitionerSlide>
|
||||||
|
|||||||
@ -35,6 +35,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
|||||||
private int _rightSideIndex;
|
private int _rightSideIndex;
|
||||||
private RenderProfileElement _selectedProfileElement;
|
private RenderProfileElement _selectedProfileElement;
|
||||||
private DateTime _lastEffectsViewModelToggle;
|
private DateTime _lastEffectsViewModelToggle;
|
||||||
|
private double _treeViewModelHeight;
|
||||||
|
|
||||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
|
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
|
||||||
ICoreService coreService,
|
ICoreService coreService,
|
||||||
@ -157,6 +158,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
|||||||
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 double TreeViewModelHeight
|
||||||
|
{
|
||||||
|
get => _treeViewModelHeight;
|
||||||
|
set => SetAndNotify(ref _treeViewModelHeight, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#region Segments
|
#region Segments
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Models
|
||||||
|
{
|
||||||
|
public class KeyframeClipboardModel
|
||||||
|
{
|
||||||
|
public Dictionary<string, KeyframeEntity> KeyframeEntities { get; set; }
|
||||||
|
public KeyframeClipboardModel(List<ILayerPropertyKeyframe> keyframes)
|
||||||
|
{
|
||||||
|
KeyframeEntities = new Dictionary<string, KeyframeEntity>();
|
||||||
|
foreach (ILayerPropertyKeyframe keyframe in keyframes)
|
||||||
|
{
|
||||||
|
KeyframeEntities.Add(keyframe.UntypedLayerProperty.Path, );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Paste(RenderProfileElement target, TimeSpan pastePosition)
|
||||||
|
{
|
||||||
|
if (target == null) throw new ArgumentNullException(nameof(target));
|
||||||
|
if (HasBeenPasted)
|
||||||
|
throw new ArtemisUIException("Clipboard model can only be pasted once");
|
||||||
|
|
||||||
|
HasBeenPasted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasBeenPasted { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,6 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan Position => LayerPropertyKeyframe.Position;
|
public TimeSpan Position => LayerPropertyKeyframe.Position;
|
||||||
|
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@ -159,34 +160,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
#region Context menu actions
|
#region Context menu actions
|
||||||
|
|
||||||
public void Copy()
|
public void Delete(bool save = true)
|
||||||
{
|
|
||||||
LayerPropertyKeyframe<T> newKeyframe = new LayerPropertyKeyframe<T>(
|
|
||||||
LayerPropertyKeyframe.Value,
|
|
||||||
LayerPropertyKeyframe.Position,
|
|
||||||
LayerPropertyKeyframe.EasingFunction,
|
|
||||||
LayerPropertyKeyframe.LayerProperty
|
|
||||||
);
|
|
||||||
// If possible, shift the keyframe to the right by 11 pixels
|
|
||||||
TimeSpan desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
|
|
||||||
if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.Timeline.Length)
|
|
||||||
newKeyframe.Position = desiredPosition;
|
|
||||||
// Otherwise if possible shift it to the left by 11 pixels
|
|
||||||
else
|
|
||||||
{
|
|
||||||
desiredPosition = newKeyframe.Position - TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
|
|
||||||
if (desiredPosition > TimeSpan.Zero)
|
|
||||||
newKeyframe.Position = desiredPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe);
|
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete()
|
|
||||||
{
|
{
|
||||||
LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe);
|
LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe);
|
||||||
_profileEditorService.UpdateSelectedProfileElement();
|
if (save)
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -196,6 +174,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
{
|
{
|
||||||
bool IsSelected { get; set; }
|
bool IsSelected { get; set; }
|
||||||
TimeSpan Position { get; }
|
TimeSpan Position { get; }
|
||||||
|
ILayerPropertyKeyframe Keyframe { get; }
|
||||||
|
|
||||||
#region Movement
|
#region Movement
|
||||||
|
|
||||||
@ -210,8 +189,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
void PopulateEasingViewModels();
|
void PopulateEasingViewModels();
|
||||||
void ClearEasingViewModels();
|
void ClearEasingViewModels();
|
||||||
void Copy();
|
void Delete(bool save = true);
|
||||||
void Delete();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,8 +36,8 @@
|
|||||||
MouseDown="{s:Action KeyframeMouseDown}"
|
MouseDown="{s:Action KeyframeMouseDown}"
|
||||||
MouseUp="{s:Action KeyframeMouseUp}"
|
MouseUp="{s:Action KeyframeMouseUp}"
|
||||||
MouseMove="{s:Action KeyframeMouseMove}"
|
MouseMove="{s:Action KeyframeMouseMove}"
|
||||||
ContextMenuOpening="{s:Action ContextMenuOpening}"
|
ContextMenuOpening="{s:Action KeyframeContextMenuOpening}"
|
||||||
ContextMenuClosing="{s:Action ContextMenuClosing}">
|
ContextMenuClosing="{s:Action KeyframeContextMenuClosing}">
|
||||||
<Ellipse.Style>
|
<Ellipse.Style>
|
||||||
<Style TargetType="{x:Type Ellipse}">
|
<Style TargetType="{x:Type Ellipse}">
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
@ -62,17 +62,6 @@
|
|||||||
</Ellipse.Style>
|
</Ellipse.Style>
|
||||||
<Ellipse.ContextMenu>
|
<Ellipse.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="Copy" Command="{s:Action Copy}" CommandParameter="{Binding}">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<materialDesign:PackIcon Kind="ContentCopy" />
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Delete" Command="{s:Action Delete}" CommandParameter="{Binding}">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<materialDesign:PackIcon Kind="Delete" />
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
|
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="Creation" />
|
<materialDesign:PackIcon Kind="Creation" />
|
||||||
@ -98,6 +87,28 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</MenuItem.ItemTemplate>
|
</MenuItem.ItemTemplate>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Duplicate" Command="{s:Action DuplicateKeyframes}" CommandParameter="{Binding}" InputGestureText="Ctrl+D">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentDuplicate" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Copy" Command="{s:Action CopyKeyframes}" CommandParameter="{Binding}" InputGestureText="Ctrl+C">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Paste" Command="{s:Action PasteKeyframes}" CommandParameter="{Binding}" InputGestureText="Ctrl+V">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentPaste" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Delete" Command="{s:Action DeleteKeyframes}" InputGestureText="Del">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="Delete" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Ellipse.ContextMenu>
|
</Ellipse.ContextMenu>
|
||||||
</Ellipse>
|
</Ellipse>
|
||||||
|
|||||||
@ -5,33 +5,67 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline"
|
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline"
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="25"
|
d:DesignHeight="25"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DataContext="{d:DesignInstance local:TimelineViewModel}">
|
d:DataContext="{d:DesignInstance local:TimelineViewModel}">
|
||||||
<Grid x:Name="TimelineContainerGrid"
|
<Grid 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">
|
ContextMenuOpening="{s:Action ContextMenuOpening}"
|
||||||
|
ContextMenuClosing="{s:Action ContextMenuClosing}"
|
||||||
|
Height="{Binding LayerPropertiesViewModel.TreeViewModelHeight}"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Focusable="True">
|
||||||
|
<Grid.InputBindings>
|
||||||
|
<KeyBinding Key="Delete" Command="{s:Action DeleteKeyframes}" />
|
||||||
|
<KeyBinding Key="D" Modifiers="Control" Command="{s:Action DuplicateKeyframes}" />
|
||||||
|
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyKeyframes}" />
|
||||||
|
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteKeyframes}" />
|
||||||
|
</Grid.InputBindings>
|
||||||
<Grid.Triggers>
|
<Grid.Triggers>
|
||||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||||
<BeginStoryboard>
|
<BeginStoryboard>
|
||||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||||
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
<DoubleAnimation To="1" Duration="0:0:0.1" />
|
||||||
</Storyboard>
|
</Storyboard>
|
||||||
</BeginStoryboard>
|
</BeginStoryboard>
|
||||||
</EventTrigger>
|
</EventTrigger>
|
||||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
|
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
|
||||||
<BeginStoryboard>
|
<BeginStoryboard>
|
||||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||||
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
<DoubleAnimation To="0" Duration="0:0:0.2" />
|
||||||
</Storyboard>
|
</Storyboard>
|
||||||
</BeginStoryboard>
|
</BeginStoryboard>
|
||||||
</EventTrigger>
|
</EventTrigger>
|
||||||
</Grid.Triggers>
|
</Grid.Triggers>
|
||||||
|
<Grid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="Duplicate" Command="{s:Action DuplicateKeyframes}" CommandParameter="{Binding}" InputGestureText="Ctrl+D" IsEnabled="False">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentDuplicate" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Copy" Command="{s:Action CopyKeyframes}" CommandParameter="{Binding}" InputGestureText="Ctrl+C" IsEnabled="False">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Paste" Command="{s:Action PasteKeyframes}" CommandParameter="{Binding}" InputGestureText="Ctrl+V" >
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentPaste" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Delete" Command="{s:Action DeleteKeyframes}" InputGestureText="Del" IsEnabled="False">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="Delete" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
</ContextMenu>
|
||||||
|
</Grid.ContextMenu>
|
||||||
<ItemsControl ItemsSource="{Binding LayerPropertyGroups}"
|
<ItemsControl ItemsSource="{Binding LayerPropertyGroups}"
|
||||||
MinWidth="{Binding TotalTimelineWidth}"
|
MinWidth="{Binding TotalTimelineWidth}"
|
||||||
HorizontalAlignment="Left">
|
HorizontalAlignment="Left">
|
||||||
@ -48,7 +82,7 @@
|
|||||||
X1="{Binding StartSegmentEndPosition}"
|
X1="{Binding StartSegmentEndPosition}"
|
||||||
X2="{Binding StartSegmentEndPosition}"
|
X2="{Binding StartSegmentEndPosition}"
|
||||||
Y1="0"
|
Y1="0"
|
||||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
|
Y2="{Binding LayerPropertiesViewModel.TreeViewModelHeight}"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Visibility="{Binding LayerPropertiesViewModel.StartTimelineSegmentViewModel.SegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
Visibility="{Binding LayerPropertiesViewModel.StartTimelineSegmentViewModel.SegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
||||||
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
||||||
@ -57,14 +91,14 @@
|
|||||||
X1="{Binding MainSegmentEndPosition}"
|
X1="{Binding MainSegmentEndPosition}"
|
||||||
X2="{Binding MainSegmentEndPosition}"
|
X2="{Binding MainSegmentEndPosition}"
|
||||||
Y1="0"
|
Y1="0"
|
||||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}" />
|
Y2="{Binding LayerPropertiesViewModel.TreeViewModelHeight}" />
|
||||||
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
|
||||||
Opacity="0.5"
|
Opacity="0.5"
|
||||||
StrokeDashArray="4 2"
|
StrokeDashArray="4 2"
|
||||||
X1="{Binding EndSegmentEndPosition}"
|
X1="{Binding EndSegmentEndPosition}"
|
||||||
X2="{Binding EndSegmentEndPosition}"
|
X2="{Binding EndSegmentEndPosition}"
|
||||||
Y1="0"
|
Y1="0"
|
||||||
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
|
Y2="{Binding LayerPropertiesViewModel.TreeViewModelHeight}"
|
||||||
Visibility="{Binding LayerPropertiesViewModel.EndTimelineSegmentViewModel.SegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
Visibility="{Binding LayerPropertiesViewModel.EndTimelineSegmentViewModel.SegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" />
|
||||||
|
|
||||||
<!-- Multi-selection rectangle -->
|
<!-- Multi-selection rectangle -->
|
||||||
|
|||||||
@ -3,11 +3,15 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Models;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Models;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
||||||
@ -151,31 +155,108 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
#region Context menu actions
|
#region Context menu actions
|
||||||
|
|
||||||
public void ContextMenuOpening(object sender, EventArgs e)
|
public bool CanDuplicateKeyframes => GetAllKeyframeViewModels().Any(k => k.IsSelected);
|
||||||
|
public bool CanCopyKeyframes => GetAllKeyframeViewModels().Any(k => k.IsSelected);
|
||||||
|
public bool CanDeleteKeyframes => GetAllKeyframeViewModels().Any(k => k.IsSelected);
|
||||||
|
public bool CanPasteKeyframes => JsonClipboard.GetData() is KeyframeClipboardModel;
|
||||||
|
|
||||||
|
private TimeSpan? _contextMenuOpenPosition;
|
||||||
|
|
||||||
|
public void ContextMenuOpening(object sender, ContextMenuEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Ellipse ellipse && ellipse.DataContext is ITimelineKeyframeViewModel viewModel)
|
_contextMenuOpenPosition = GetCursorTime(new Point(e.CursorLeft, e.CursorTop));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContextMenuClosing(object sender, ContextMenuEventArgs e)
|
||||||
|
{
|
||||||
|
_contextMenuOpenPosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KeyframeContextMenuOpening(object sender, ContextMenuEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is FrameworkElement fe && fe.DataContext is ITimelineKeyframeViewModel viewModel)
|
||||||
viewModel.PopulateEasingViewModels();
|
viewModel.PopulateEasingViewModels();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ContextMenuClosing(object sender, EventArgs e)
|
public void KeyframeContextMenuClosing(object sender, ContextMenuEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Ellipse ellipse && ellipse.DataContext is ITimelineKeyframeViewModel viewModel)
|
if (sender is Ellipse ellipse && ellipse.DataContext is ITimelineKeyframeViewModel viewModel)
|
||||||
viewModel.ClearEasingViewModels();
|
viewModel.ClearEasingViewModels();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Copy(ITimelineKeyframeViewModel viewModel)
|
public void DeleteKeyframes()
|
||||||
{
|
{
|
||||||
// viewModel.Copy();
|
List<ITimelineKeyframeViewModel> keyframeViewModels = GetAllKeyframeViewModels().Where(k => k.IsSelected).ToList();
|
||||||
List<ITimelineKeyframeViewModel> keyframeViewModels = GetAllKeyframeViewModels();
|
foreach (ITimelineKeyframeViewModel keyframeViewModel in keyframeViewModels)
|
||||||
foreach (ITimelineKeyframeViewModel keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
keyframeViewModel.Delete(false);
|
||||||
keyframeViewModel.Copy();
|
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(ITimelineKeyframeViewModel viewModel)
|
public void DuplicateKeyframes(ITimelineKeyframeViewModel viewModel = null)
|
||||||
{
|
{
|
||||||
List<ITimelineKeyframeViewModel> keyframeViewModels = GetAllKeyframeViewModels();
|
TimeSpan pastePosition = GetPastePosition(viewModel);
|
||||||
foreach (ITimelineKeyframeViewModel keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
|
||||||
keyframeViewModel.Delete();
|
List<ILayerPropertyKeyframe> keyframes = GetAllKeyframeViewModels().Where(k => k.IsSelected).Select(k => k.Keyframe).ToList();
|
||||||
|
DuplicateKeyframes(keyframes, pastePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyKeyframes()
|
||||||
|
{
|
||||||
|
List<ILayerPropertyKeyframe> keyframes = GetAllKeyframeViewModels().Where(k => k.IsSelected).Select(k => k.Keyframe).ToList();
|
||||||
|
CopyKeyframes(keyframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PasteKeyframes(ITimelineKeyframeViewModel viewModel = null)
|
||||||
|
{
|
||||||
|
TimeSpan pastePosition = GetPastePosition(viewModel);
|
||||||
|
PasteKeyframes(pastePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan GetPastePosition(ITimelineKeyframeViewModel viewModel)
|
||||||
|
{
|
||||||
|
TimeSpan pastePosition = _profileEditorService.CurrentTime;
|
||||||
|
|
||||||
|
// If a keyframe VM is provided, paste onto there
|
||||||
|
if (viewModel != null)
|
||||||
|
pastePosition = viewModel.Position;
|
||||||
|
// Paste at the position the context menu was opened
|
||||||
|
else if (_contextMenuOpenPosition != null)
|
||||||
|
pastePosition = _contextMenuOpenPosition.Value;
|
||||||
|
|
||||||
|
return pastePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ILayerPropertyKeyframe> DuplicateKeyframes(List<ILayerPropertyKeyframe> keyframes, TimeSpan pastePosition)
|
||||||
|
{
|
||||||
|
KeyframeClipboardModel clipboardModel = CoreJson.DeserializeObject<KeyframeClipboardModel>(CoreJson.SerializeObject(new KeyframeClipboardModel(keyframes), true), true);
|
||||||
|
return PasteClipboardData(clipboardModel, pastePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyKeyframes(List<ILayerPropertyKeyframe> keyframes)
|
||||||
|
{
|
||||||
|
KeyframeClipboardModel clipboardModel = new KeyframeClipboardModel(keyframes);
|
||||||
|
JsonClipboard.SetObject(clipboardModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ILayerPropertyKeyframe> PasteKeyframes(TimeSpan pastePosition)
|
||||||
|
{
|
||||||
|
KeyframeClipboardModel clipboardObject = JsonClipboard.GetData<KeyframeClipboardModel>();
|
||||||
|
return PasteClipboardData(clipboardObject, pastePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ILayerPropertyKeyframe> PasteClipboardData(KeyframeClipboardModel clipboardModel, TimeSpan pastePosition)
|
||||||
|
{
|
||||||
|
List<ILayerPropertyKeyframe> pasted = new List<ILayerPropertyKeyframe>();
|
||||||
|
if (clipboardModel == null)
|
||||||
|
return pasted;
|
||||||
|
RenderProfileElement target = _profileEditorService.SelectedProfileElement;
|
||||||
|
if (target == null)
|
||||||
|
return pasted;
|
||||||
|
|
||||||
|
clipboardModel.Paste(target, pastePosition);
|
||||||
|
|
||||||
|
return pasted;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -254,6 +335,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
// ReSharper disable once UnusedMember.Global - Called from view
|
// ReSharper disable once UnusedMember.Global - Called from view
|
||||||
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
|
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
// Workaround for focus not being applied to the grid causing keybinds not to function
|
||||||
|
((IInputElement) sender).Focus();
|
||||||
|
|
||||||
if (e.LeftButton == MouseButtonState.Released)
|
if (e.LeftButton == MouseButtonState.Released)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,8 @@
|
|||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
dd:DragDrop.IsDragSource="True"
|
dd:DragDrop.IsDragSource="True"
|
||||||
dd:DragDrop.IsDropTarget="True"
|
dd:DragDrop.IsDropTarget="True"
|
||||||
dd:DragDrop.DropHandler="{Binding}">
|
dd:DragDrop.DropHandler="{Binding}"
|
||||||
|
ContextMenuOpening="{s:Action ContextMenuOpening}">
|
||||||
<TreeView.InputBindings>
|
<TreeView.InputBindings>
|
||||||
<KeyBinding Key="F2" Command="{s:Action RenameElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
<KeyBinding Key="F2" Command="{s:Action RenameElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
<KeyBinding Key="Delete" Command="{s:Action DeleteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
<KeyBinding Key="Delete" Command="{s:Action DeleteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
@ -40,6 +41,47 @@
|
|||||||
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
</TreeView.InputBindings>
|
</TreeView.InputBindings>
|
||||||
|
<TreeView.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="Add new folder" Command="{s:Action AddFolder}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="CreateNewFolder" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Add new layer" Command="{s:Action AddLayer}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="LayersPlus" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}" InputGestureText="Ctrl+D" IsEnabled="False">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentDuplicate" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Copy" Command="{s:Action CopyElement}" InputGestureText="Ctrl+C" IsEnabled="False">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Paste" Command="{s:Action PasteElement}" InputGestureText="Ctrl+V">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentPaste" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Rename" Command="{s:Action RenameElement}" InputGestureText="F2" IsEnabled="False">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="RenameBox" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Delete" Command="{s:Action DeleteElement}" InputGestureText="Del" IsEnabled="False">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="TrashCan" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
</ContextMenu>
|
||||||
|
</TreeView.ContextMenu>
|
||||||
<b:Interaction.Behaviors>
|
<b:Interaction.Behaviors>
|
||||||
<behaviors:TreeViewSelectionBehavior ExpandSelected="True" SelectedItem="{Binding SelectedTreeItem}" />
|
<behaviors:TreeViewSelectionBehavior ExpandSelected="True" SelectedItem="{Binding SelectedTreeItem}" />
|
||||||
</b:Interaction.Behaviors>
|
</b:Interaction.Behaviors>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
@ -41,17 +42,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once UnusedMember.Global - Called from view
|
public bool CanPasteElement => _profileEditorService.GetCanPaste();
|
||||||
public void AddFolder()
|
|
||||||
{
|
|
||||||
ActiveItem?.AddFolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once UnusedMember.Global - Called from view
|
|
||||||
public void AddLayer()
|
|
||||||
{
|
|
||||||
ActiveItem?.AddLayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnInitialActivate()
|
protected override void OnInitialActivate()
|
||||||
{
|
{
|
||||||
@ -79,10 +70,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
_updatingTree = false;
|
_updatingTree = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region IDropTarget
|
||||||
|
|
||||||
private static DragDropType GetDragDropType(IDropInfo dropInfo)
|
private static DragDropType GetDragDropType(IDropInfo dropInfo)
|
||||||
{
|
{
|
||||||
TreeItemViewModel source = (TreeItemViewModel) dropInfo.Data;
|
TreeItemViewModel source = (TreeItemViewModel)dropInfo.Data;
|
||||||
TreeItemViewModel target = (TreeItemViewModel) dropInfo.TargetItem;
|
TreeItemViewModel target = (TreeItemViewModel)dropInfo.TargetItem;
|
||||||
if (source == target)
|
if (source == target)
|
||||||
return DragDropType.None;
|
return DragDropType.None;
|
||||||
|
|
||||||
@ -128,14 +121,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
|
|
||||||
public void Drop(IDropInfo dropInfo)
|
public void Drop(IDropInfo dropInfo)
|
||||||
{
|
{
|
||||||
TreeItemViewModel source = (TreeItemViewModel) dropInfo.Data;
|
TreeItemViewModel source = (TreeItemViewModel)dropInfo.Data;
|
||||||
TreeItemViewModel target = (TreeItemViewModel) dropInfo.TargetItem;
|
TreeItemViewModel target = (TreeItemViewModel)dropInfo.TargetItem;
|
||||||
|
|
||||||
DragDropType dragDropType = GetDragDropType(dropInfo);
|
DragDropType dragDropType = GetDragDropType(dropInfo);
|
||||||
switch (dragDropType)
|
switch (dragDropType)
|
||||||
{
|
{
|
||||||
case DragDropType.Add:
|
case DragDropType.Add:
|
||||||
((TreeItemViewModel) source.Parent).RemoveExistingElement(source);
|
((TreeItemViewModel)source.Parent).RemoveExistingElement(source);
|
||||||
target.AddExistingElement(source);
|
target.AddExistingElement(source);
|
||||||
break;
|
break;
|
||||||
case DragDropType.InsertBefore:
|
case DragDropType.InsertBefore:
|
||||||
@ -151,6 +144,34 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
Subscribe();
|
Subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Context menu
|
||||||
|
|
||||||
|
public void AddFolder()
|
||||||
|
{
|
||||||
|
ActiveItem?.AddFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLayer()
|
||||||
|
{
|
||||||
|
ActiveItem?.AddLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PasteElement()
|
||||||
|
{
|
||||||
|
Folder rootFolder = _profileEditorService.SelectedProfile?.GetRootFolder();
|
||||||
|
if (rootFolder != null)
|
||||||
|
_profileEditorService.PasteProfileElement(rootFolder, rootFolder.Children.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContextMenuOpening(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
NotifyOfPropertyChange(nameof(CanPasteElement));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Event handlers
|
#region Event handlers
|
||||||
|
|
||||||
private void Subscribe()
|
private void Subscribe()
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
d:DesignHeight="450" d:DesignWidth="800"
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
d:DataContext="{d:DesignInstance {x:Type treeItem1:FolderViewModel}}">
|
d:DataContext="{d:DesignInstance {x:Type treeItem1:FolderViewModel}}">
|
||||||
<!-- Capture clicks on full tree view item -->
|
<!-- Capture clicks on full tree view item -->
|
||||||
<StackPanel Margin="-10" Background="Transparent">
|
<StackPanel Margin="-10" Background="Transparent" ContextMenuOpening="{s:Action ContextMenuOpening}">
|
||||||
<StackPanel.ContextMenu>
|
<StackPanel.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="Add new folder" Command="{s:Action AddFolder}">
|
<MenuItem Header="Add new folder" Command="{s:Action AddFolder}">
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
d:DesignHeight="450" d:DesignWidth="800"
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
d:DataContext="{d:DesignInstance {x:Type treeItem1:LayerViewModel}}">
|
d:DataContext="{d:DesignInstance {x:Type treeItem1:LayerViewModel}}">
|
||||||
<!-- Capture clicks on full tree view item -->
|
<!-- Capture clicks on full tree view item -->
|
||||||
<StackPanel Margin="-10" Background="Transparent">
|
<StackPanel Margin="-10" Background="Transparent" ContextMenuOpening="{s:Action ContextMenuOpening}">
|
||||||
<StackPanel.ContextMenu>
|
<StackPanel.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}" InputGestureText="Ctrl+D">
|
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}" InputGestureText="Ctrl+D">
|
||||||
|
|||||||
@ -48,6 +48,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
set => SetAndNotify(ref _profileElement, value);
|
set => SetAndNotify(ref _profileElement, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanPasteElement => _profileEditorService.GetCanPaste();
|
||||||
|
|
||||||
public abstract bool SupportsChildren { get; }
|
public abstract bool SupportsChildren { get; }
|
||||||
|
|
||||||
public List<TreeItemViewModel> GetAllChildren()
|
public List<TreeItemViewModel> GetAllChildren()
|
||||||
@ -254,6 +256,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
_profileEditorService.UpdateSelectedProfile();
|
_profileEditorService.UpdateSelectedProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ContextMenuOpening(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
NotifyOfPropertyChange(nameof(CanPasteElement));
|
||||||
|
}
|
||||||
|
|
||||||
private void Subscribe()
|
private void Subscribe()
|
||||||
{
|
{
|
||||||
ProfileElement.ChildAdded += ProfileElementOnChildAdded;
|
ProfileElement.ChildAdded += ProfileElementOnChildAdded;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user