mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
UI - Further restructuring
This commit is contained in:
parent
54081b591b
commit
af21d83487
@ -72,6 +72,33 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
|
||||
public override IReadOnlyList<BaseLayerPropertyKeyframe> BaseKeyframes => _keyframes.Cast<BaseLayerPropertyKeyframe>().ToList().AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current value, using either keyframes if enabled or the base value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <param name="time">
|
||||
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
|
||||
/// or existing keyframe.
|
||||
/// </param>
|
||||
public void SetCurrentValue(T value, TimeSpan? time)
|
||||
{
|
||||
if (time == null || !KeyframesEnabled || !KeyframesSupported)
|
||||
BaseValue = value;
|
||||
else
|
||||
{
|
||||
// If on a keyframe, update the keyframe
|
||||
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
|
||||
// Create a new keyframe if none found
|
||||
if (currentKeyframe == null)
|
||||
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear));
|
||||
else
|
||||
currentKeyframe.Value = value;
|
||||
|
||||
// Update the property so that the new keyframe is reflected on the current value
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a keyframe to the layer property
|
||||
/// </summary>
|
||||
@ -220,9 +247,24 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs once every frame when the layer property is updated
|
||||
/// </summary>
|
||||
public event EventHandler Updated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the base value of the layer property was updated
|
||||
/// </summary>
|
||||
public event EventHandler BaseValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new keyframe was added to the layer property
|
||||
/// </summary>
|
||||
public event EventHandler KeyframeAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a keyframe was removed from the layer property
|
||||
/// </summary>
|
||||
public event EventHandler KeyframeRemoved;
|
||||
|
||||
protected virtual void OnUpdated()
|
||||
|
||||
@ -33,6 +33,11 @@ namespace Artemis.Core.Models.Profile
|
||||
/// </summary>
|
||||
public bool IsCorePropertyGroup { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the property is hidden in the UI
|
||||
/// </summary>
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of all layer properties in this group
|
||||
/// </summary>
|
||||
|
||||
@ -47,29 +47,4 @@ namespace Artemis.UI.Ninject.Factories
|
||||
{
|
||||
ProfileLayerViewModel Create(Layer layer);
|
||||
}
|
||||
|
||||
public interface ILayerPropertyVmFactory : IVmFactory
|
||||
{
|
||||
LayerPropertyViewModel Create(BaseLayerProperty layerProperty, LayerPropertyViewModel parent);
|
||||
}
|
||||
|
||||
public interface IPropertyTreeVmFactory : IVmFactory
|
||||
{
|
||||
PropertyTreeViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
|
||||
}
|
||||
|
||||
public interface IPropertyTimelineVmFactory : IVmFactory
|
||||
{
|
||||
PropertyTimelineViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
|
||||
}
|
||||
|
||||
public interface IPropertyTrackVmFactory : IVmFactory
|
||||
{
|
||||
PropertyTrackViewModel Create(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel);
|
||||
}
|
||||
|
||||
public interface IPropertyTrackKeyframeVmFactory : IVmFactory
|
||||
{
|
||||
PropertyTrackKeyframeViewModel<T> Create<T>(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe<T> keyframe);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract
|
||||
{
|
||||
public abstract class LayerPropertyBaseViewModel : PropertyChangedBase
|
||||
public abstract class LayerPropertyBaseViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
protected LayerPropertyBaseViewModel()
|
||||
{
|
||||
Children = new List<LayerPropertyBaseViewModel>();
|
||||
}
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public abstract bool IsVisible { get; }
|
||||
|
||||
public List<LayerPropertyBaseViewModel> Children { get; set; }
|
||||
|
||||
public abstract List<BaseLayerPropertyKeyframe> GetKeyframes(bool visibleOnly);
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
||||
@ -11,9 +11,8 @@ using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.UI.Events;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
@ -22,22 +21,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
public class LayerPropertiesViewModel : ProfileEditorPanelViewModel
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IPropertyTreeVmFactory _propertyTreeVmFactory;
|
||||
private readonly IPropertyTimelineVmFactory _propertyTimelineVmFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
|
||||
ICoreService coreService,
|
||||
ISettingsService settingsService,
|
||||
IPropertyTreeVmFactory propertyTreeVmFactory,
|
||||
IPropertyTimelineVmFactory propertyTimelineVmFactory)
|
||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_coreService = coreService;
|
||||
_settingsService = settingsService;
|
||||
_propertyTreeVmFactory = propertyTreeVmFactory;
|
||||
_propertyTimelineVmFactory = propertyTimelineVmFactory;
|
||||
|
||||
PixelsPerSecond = 31;
|
||||
}
|
||||
@ -63,13 +54,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
}
|
||||
|
||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; set; }
|
||||
public PropertyTreeViewModel PropertyTree { get; set; }
|
||||
public PropertyTimelineViewModel PropertyTimeline { get; set; }
|
||||
public TreeViewModel TreeViewModel { get; set; }
|
||||
public TimelineViewModel TimelineViewModel { get; set; }
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
PropertyTree = _propertyTreeVmFactory.Create(this);
|
||||
PropertyTimeline = _propertyTimelineVmFactory.Create(this);
|
||||
TreeViewModel = new TreeViewModel(LayerPropertyGroups);
|
||||
TimelineViewModel = new TimelineViewModel(LayerPropertyGroups);
|
||||
|
||||
PopulateProperties(_profileEditorService.SelectedProfileElement);
|
||||
|
||||
@ -84,10 +75,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
_profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
|
||||
_profileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
|
||||
|
||||
PropertyTree?.Dispose();
|
||||
PropertyTimeline?.Dispose();
|
||||
PropertyTree = null;
|
||||
PropertyTimeline = null;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
@ -124,8 +111,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
layer.GetType().GetProperty(nameof(layer.Transform)),
|
||||
typeof(PropertyGroupDescriptionAttribute)
|
||||
);
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.General, (PropertyGroupDescriptionAttribute) generalAttribute));
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute));
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.General, (PropertyGroupDescriptionAttribute) generalAttribute));
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute));
|
||||
|
||||
// Add the rout group of the brush
|
||||
// The root group of the brush has no attribute so let's pull one out of our sleeve
|
||||
@ -134,7 +121,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
Name = layer.LayerBrush.Descriptor.DisplayName,
|
||||
Description = layer.LayerBrush.Descriptor.Description
|
||||
};
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.LayerBrush.BaseProperties, brushDescription));
|
||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.LayerBrush.BaseProperties, brushDescription));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,27 +4,37 @@ using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
{
|
||||
public class LayerPropertyGroupViewModel : LayerPropertyBaseViewModel
|
||||
{
|
||||
public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription)
|
||||
public LayerPropertyGroupViewModel(IProfileEditorService profileEditorService, LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription)
|
||||
{
|
||||
ProfileEditorService = profileEditorService;
|
||||
|
||||
LayerPropertyGroup = layerPropertyGroup;
|
||||
PropertyGroupDescription = propertyGroupDescription;
|
||||
|
||||
IsExpanded = PropertyGroupDescription.ExpandByDefault;
|
||||
|
||||
Children = new List<LayerPropertyBaseViewModel>();
|
||||
TreePropertyGroupViewModel = new TreePropertyGroupViewModel(this);
|
||||
TimelinePropertyGroupViewModel = new TimelinePropertyGroupViewModel(this);
|
||||
|
||||
PopulateChildren();
|
||||
}
|
||||
|
||||
public override bool IsVisible => !LayerPropertyGroup.IsHidden;
|
||||
|
||||
public IProfileEditorService ProfileEditorService { get; }
|
||||
|
||||
public LayerPropertyGroup LayerPropertyGroup { get; }
|
||||
public PropertyGroupDescriptionAttribute PropertyGroupDescription { get; }
|
||||
public bool IsExpanded { get; set; }
|
||||
|
||||
public List<LayerPropertyBaseViewModel> Children { get; set; }
|
||||
public TreePropertyGroupViewModel TreePropertyGroupViewModel { get; set; }
|
||||
public TimelinePropertyGroupViewModel TimelinePropertyGroupViewModel { get; set; }
|
||||
|
||||
private void PopulateChildren()
|
||||
{
|
||||
@ -36,18 +46,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
var value = propertyInfo.GetValue(LayerPropertyGroup);
|
||||
|
||||
// Create VMs for properties on the group
|
||||
if (propertyAttribute != null && value is BaseLayerProperty)
|
||||
if (propertyAttribute != null && value is BaseLayerProperty baseLayerProperty)
|
||||
{
|
||||
// Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line
|
||||
var genericType = propertyInfo.PropertyType.GetGenericArguments()[0];
|
||||
var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType);
|
||||
var instance = Activator.CreateInstance(genericViewModel, value, propertyAttribute);
|
||||
Children.Add((LayerPropertyBaseViewModel) instance);
|
||||
var viewModel = ProfileEditorService.CreateLayerPropertyViewModel(baseLayerProperty, propertyAttribute);
|
||||
if (viewModel != null)
|
||||
Children.Add(viewModel);
|
||||
}
|
||||
// Create VMs for child groups on this group, resulting in a nested structure
|
||||
else if (groupAttribute != null && value is LayerPropertyGroup layerPropertyGroup)
|
||||
{
|
||||
Children.Add(new LayerPropertyGroupViewModel(layerPropertyGroup, groupAttribute));
|
||||
Children.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layerPropertyGroup, groupAttribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,5 +71,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (var layerPropertyBaseViewModel in Children)
|
||||
layerPropertyBaseViewModel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
{
|
||||
public class LayerKeyframeViewModel<T>
|
||||
{
|
||||
public LayerKeyframeViewModel(LayerPropertyKeyframe<T> keyframe)
|
||||
{
|
||||
Keyframe = keyframe;
|
||||
}
|
||||
|
||||
public LayerPropertyKeyframe<T> Keyframe { get; }
|
||||
}
|
||||
}
|
||||
@ -3,23 +3,68 @@ using System.Linq;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
{
|
||||
public class LayerPropertyViewModel<T> : LayerPropertyBaseViewModel
|
||||
public class LayerPropertyViewModel<T> : LayerPropertyViewModel
|
||||
{
|
||||
public LayerPropertyViewModel(LayerProperty<T> layerProperty, PropertyDescriptionAttribute propertyDescription)
|
||||
public LayerPropertyViewModel(IProfileEditorService profileEditorService, LayerProperty<T> layerProperty, PropertyDescriptionAttribute propertyDescription)
|
||||
: base(profileEditorService, layerProperty)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
PropertyDescription = propertyDescription;
|
||||
|
||||
TreePropertyViewModel = new TreePropertyViewModel<T>(this);
|
||||
TimelinePropertyViewModel = new TimelinePropertyViewModel<T>(this);
|
||||
|
||||
TreePropertyBaseViewModel = TreePropertyViewModel;
|
||||
TimelinePropertyBaseViewModel = TimelinePropertyViewModel;
|
||||
}
|
||||
|
||||
public override bool IsVisible => !LayerProperty.IsHidden;
|
||||
|
||||
public LayerProperty<T> LayerProperty { get; }
|
||||
public PropertyDescriptionAttribute PropertyDescription { get; }
|
||||
|
||||
public TreePropertyViewModel<T> TreePropertyViewModel { get; set; }
|
||||
public TimelinePropertyViewModel<T> TimelinePropertyViewModel { get; set; }
|
||||
|
||||
public override List<BaseLayerPropertyKeyframe> GetKeyframes(bool visibleOnly)
|
||||
{
|
||||
return LayerProperty.BaseKeyframes.ToList();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
TreePropertyViewModel.Dispose();
|
||||
TimelinePropertyViewModel.Dispose();
|
||||
}
|
||||
|
||||
public void SetCurrentValue(T value, bool saveChanges)
|
||||
{
|
||||
LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime);
|
||||
if (saveChanges)
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
else
|
||||
ProfileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class LayerPropertyViewModel : LayerPropertyBaseViewModel
|
||||
{
|
||||
public IProfileEditorService ProfileEditorService { get; }
|
||||
public BaseLayerProperty BaseLayerProperty { get; }
|
||||
|
||||
protected LayerPropertyViewModel(IProfileEditorService profileEditorService, BaseLayerProperty baseLayerProperty)
|
||||
{
|
||||
ProfileEditorService = profileEditorService;
|
||||
BaseLayerProperty = baseLayerProperty;
|
||||
}
|
||||
|
||||
public PropertyDescriptionAttribute PropertyDescription { get; protected set; }
|
||||
public TreePropertyViewModel TreePropertyBaseViewModel { get; set; }
|
||||
public TimelinePropertyViewModel TimelinePropertyBaseViewModel { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
|
||||
{
|
||||
public abstract class PropertyInputViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
protected PropertyInputViewModel(IProfileEditorService profileEditorService)
|
||||
{
|
||||
ProfileEditorService = profileEditorService;
|
||||
}
|
||||
|
||||
protected IProfileEditorService ProfileEditorService { get; }
|
||||
public abstract List<Type> CompatibleTypes { get; }
|
||||
|
||||
public bool Initialized { get; private set; }
|
||||
public bool InputDragging { get; private set; }
|
||||
public LayerPropertyViewModel LayerPropertyViewModel { get; private set; }
|
||||
|
||||
protected object InputValue
|
||||
{
|
||||
get => LayerPropertyViewModel.LayerProperty.GetCurrentValue();
|
||||
set => UpdateInputValue(value);
|
||||
}
|
||||
|
||||
public void Initialize(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
var type = layerPropertyViewModel.LayerProperty.Type;
|
||||
if (type.IsEnum)
|
||||
type = typeof(Enum);
|
||||
if (Initialized)
|
||||
throw new ArtemisUIException("Cannot initialize the same property input VM twice");
|
||||
if (!CompatibleTypes.Contains(type))
|
||||
throw new ArtemisUIException($"This input VM does not support the provided type {type.Name}");
|
||||
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
LayerPropertyViewModel.LayerProperty.ValueChanged += LayerPropertyOnValueChanged;
|
||||
Update();
|
||||
|
||||
Initialized = true;
|
||||
|
||||
OnInitialized();
|
||||
}
|
||||
|
||||
public abstract void Update();
|
||||
|
||||
protected virtual void OnInitialized()
|
||||
{
|
||||
}
|
||||
|
||||
private void LayerPropertyOnValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
private void UpdateInputValue(object value)
|
||||
{
|
||||
LayerPropertyViewModel.LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime);
|
||||
// Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress
|
||||
LayerPropertyViewModel.LayerProperty.KeyframeEngine?.Update(0);
|
||||
|
||||
if (!InputDragging)
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
else
|
||||
ProfileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
public void InputDragStarted(object sender, EventArgs e)
|
||||
{
|
||||
InputDragging = true;
|
||||
}
|
||||
|
||||
public void InputDragEnded(object sender, EventArgs e)
|
||||
{
|
||||
InputDragging = false;
|
||||
ProfileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (LayerPropertyViewModel != null)
|
||||
LayerPropertyViewModel.LayerProperty.ValueChanged -= LayerPropertyOnValueChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyTreeChildView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="19"
|
||||
d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PropertyTreeChildViewModel}">
|
||||
<Grid Height="22">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton Grid.Column="0"
|
||||
Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||
ToolTip="Toggle key-framing"
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsChecked="{Binding LayerPropertyViewModel.KeyframesEnabled}"
|
||||
IsEnabled="{Binding LayerPropertyViewModel.LayerProperty.CanUseKeyframes}"
|
||||
VerticalAlignment="Center" Padding="-25">
|
||||
<materialDesign:PackIcon Kind="Stopwatch" Height="13" Width="13" />
|
||||
</ToggleButton>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Margin="5,0,0,0"
|
||||
Padding="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{Binding LayerPropertyViewModel.LayerProperty.Name}"
|
||||
ToolTip="{Binding LayerPropertyViewModel.LayerProperty.Description}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<ContentControl Grid.Column="2" Margin="20 0" s:View.Model="{Binding PropertyInputViewModel}" />
|
||||
|
||||
<Button Grid.Column="3"
|
||||
Style="{StaticResource MaterialDesignOutlinedButton}"
|
||||
Margin="0,1,0,1.2"
|
||||
Padding="0"
|
||||
Width="80"
|
||||
Height="20"
|
||||
ToolTip="Change the property's data binding"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="False">
|
||||
<TextBlock FontSize="10">DATA BINDING</TextBlock>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -1,42 +0,0 @@
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
|
||||
{
|
||||
public class PropertyTreeChildViewModel : PropertyTreeItemViewModel
|
||||
{
|
||||
public PropertyTreeChildViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel)
|
||||
{
|
||||
PropertyInputViewModel = layerPropertyViewModel.GetPropertyInputViewModel();
|
||||
}
|
||||
|
||||
public PropertyInputViewModel PropertyInputViewModel { get; set; }
|
||||
|
||||
public override void Update(bool forceUpdate)
|
||||
{
|
||||
if (forceUpdate)
|
||||
PropertyInputViewModel?.Update();
|
||||
else
|
||||
{
|
||||
// Only update if visible and if keyframes are enabled
|
||||
if (LayerPropertyViewModel.Parent.IsExpanded && LayerPropertyViewModel.KeyframesEnabled)
|
||||
PropertyInputViewModel?.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
PropertyInputViewModel?.Dispose();
|
||||
PropertyInputViewModel = null;
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
|
||||
{
|
||||
public abstract class PropertyTreeItemViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
protected PropertyTreeItemViewModel(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyViewModel LayerPropertyViewModel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tree item's input if it is visible and has keyframes enabled
|
||||
/// </summary>
|
||||
/// <param name="forceUpdate">Force update regardless of visibility and keyframes</param>
|
||||
public abstract void Update(bool forceUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the layer property recursively
|
||||
/// </summary>
|
||||
/// <param name="layerPropertyViewModel"></param>
|
||||
public abstract void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the layer property recursively
|
||||
/// </summary>
|
||||
/// <param name="layerPropertyViewModel"></param>
|
||||
public abstract void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
using System.Linq;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
|
||||
{
|
||||
public class PropertyTreeParentViewModel : PropertyTreeItemViewModel
|
||||
{
|
||||
public PropertyTreeParentViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel)
|
||||
{
|
||||
Children = new BindableCollection<PropertyTreeItemViewModel>();
|
||||
}
|
||||
|
||||
public BindableCollection<PropertyTreeItemViewModel> Children { get; set; }
|
||||
|
||||
public override void Update(bool forceUpdate)
|
||||
{
|
||||
foreach (var child in Children)
|
||||
child.Update(forceUpdate);
|
||||
}
|
||||
|
||||
// TODO: Change this to not add one by one, this raises far too many events
|
||||
public override void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
if (layerPropertyViewModel.Parent == LayerPropertyViewModel)
|
||||
{
|
||||
lock (Children)
|
||||
{
|
||||
var index = layerPropertyViewModel.LayerProperty.Parent.Children.IndexOf(layerPropertyViewModel.LayerProperty);
|
||||
if (index > Children.Count)
|
||||
index = Children.Count;
|
||||
if (layerPropertyViewModel.Children.Any())
|
||||
Children.Insert(index, new PropertyTreeParentViewModel(layerPropertyViewModel));
|
||||
else
|
||||
Children.Insert(index, new PropertyTreeChildViewModel(layerPropertyViewModel));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var propertyTreeItemViewModel in Children)
|
||||
propertyTreeItemViewModel.AddLayerProperty(layerPropertyViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Change this to not remove one by one, this raises far too many events
|
||||
public override void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
foreach (var child in Children.ToList())
|
||||
{
|
||||
if (child.LayerPropertyViewModel == layerPropertyViewModel)
|
||||
{
|
||||
Children.Remove(child);
|
||||
child.Dispose();
|
||||
}
|
||||
else
|
||||
child.RemoveLayerProperty(layerPropertyViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (var child in Children.ToList())
|
||||
child.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Artemis.UI.Events;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
|
||||
{
|
||||
public class PropertyTreeViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
|
||||
public PropertyTreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
|
||||
LayerPropertiesViewModel = layerPropertiesViewModel;
|
||||
PropertyTreeItemViewModels = new BindableCollection<PropertyTreeItemViewModel>();
|
||||
|
||||
_profileEditorService.CurrentTimeChanged += OnCurrentTimeChanged;
|
||||
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
|
||||
}
|
||||
|
||||
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
|
||||
public BindableCollection<PropertyTreeItemViewModel> PropertyTreeItemViewModels { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_profileEditorService.CurrentTimeChanged -= OnCurrentTimeChanged;
|
||||
_profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated;
|
||||
|
||||
foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels)
|
||||
propertyTreeItemViewModel.Dispose();
|
||||
}
|
||||
|
||||
// TODO: Change this to not add one by one, this raises far too many events
|
||||
public void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
// Add as a root VM
|
||||
if (layerPropertyViewModel.Parent == null)
|
||||
PropertyTreeItemViewModels.Add(new PropertyTreeParentViewModel(layerPropertyViewModel));
|
||||
// Add recursively to one of the child VMs
|
||||
else
|
||||
{
|
||||
foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels)
|
||||
propertyTreeItemViewModel.AddLayerProperty(layerPropertyViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Change this to not remove one by one, this raises far too many events
|
||||
public void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
// Remove a root VM
|
||||
var rootVm = PropertyTreeItemViewModels.FirstOrDefault(vm => vm.LayerPropertyViewModel == layerPropertyViewModel);
|
||||
if (rootVm != null)
|
||||
PropertyTreeItemViewModels.Remove(rootVm);
|
||||
// Remove recursively from one of the child VMs
|
||||
else
|
||||
{
|
||||
foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels)
|
||||
propertyTreeItemViewModel.RemoveLayerProperty(layerPropertyViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tree item's input if it is visible and has keyframes enabled
|
||||
/// </summary>
|
||||
/// <param name="forceUpdate">Force update regardless of visibility and keyframes</param>
|
||||
public void Update(bool forceUpdate)
|
||||
{
|
||||
foreach (var viewModel in PropertyTreeItemViewModels)
|
||||
viewModel.Update(forceUpdate);
|
||||
}
|
||||
|
||||
public void PropertyTreePreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (e.Handled || !(sender is TreeView))
|
||||
return;
|
||||
|
||||
e.Handled = true;
|
||||
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
|
||||
{
|
||||
RoutedEvent = UIElement.MouseWheelEvent,
|
||||
Source = sender
|
||||
};
|
||||
var parent = ((Control) sender).Parent as UIElement;
|
||||
parent?.RaiseEvent(eventArg);
|
||||
}
|
||||
|
||||
private void OnCurrentTimeChanged(object sender, EventArgs e)
|
||||
{
|
||||
Update(false);
|
||||
}
|
||||
|
||||
private void OnSelectedProfileElementUpdated(object sender, ProfileElementEventArgs e)
|
||||
{
|
||||
Update(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.PropertyTimelineView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="25"
|
||||
d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PropertyTimelineViewModel}">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
||||
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
||||
MouseMove="{s:Action TimelineCanvasMouseMove}">
|
||||
<Grid.Triggers>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
|
||||
<BeginStoryboard>
|
||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Grid.Triggers>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding PropertyTrackViewModels}"
|
||||
Width="{Binding Width}"
|
||||
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}"
|
||||
HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||
StrokeThickness="1"
|
||||
x:Name="MultiSelectionPath"
|
||||
IsHitTestVisible="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -1,245 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.UI.Events;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Artemis.UI.Utilities;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class PropertyTimelineViewModel : PropertyChangedBase, IDisposable
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly IPropertyTrackVmFactory _propertyTrackVmFactory;
|
||||
|
||||
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel,
|
||||
IProfileEditorService profileEditorService,
|
||||
IPropertyTrackVmFactory propertyTrackVmFactory)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_propertyTrackVmFactory = propertyTrackVmFactory;
|
||||
|
||||
LayerPropertiesViewModel = layerPropertiesViewModel;
|
||||
PropertyTrackViewModels = new BindableCollection<PropertyTrackViewModel>();
|
||||
|
||||
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
|
||||
LayerPropertiesViewModel.PixelsPerSecondChanged += OnPixelsPerSecondChanged;
|
||||
|
||||
Execute.PostToUIThread(() => SelectionRectangle = new RectangleGeometry());
|
||||
}
|
||||
|
||||
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
|
||||
|
||||
public double Width { get; set; }
|
||||
public BindableCollection<PropertyTrackViewModel> PropertyTrackViewModels { get; set; }
|
||||
public RectangleGeometry SelectionRectangle { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated;
|
||||
LayerPropertiesViewModel.PixelsPerSecondChanged -= OnPixelsPerSecondChanged;
|
||||
}
|
||||
|
||||
public void UpdateEndTime()
|
||||
{
|
||||
// End time is the last keyframe + 10 sec
|
||||
var lastKeyFrame = PropertyTrackViewModels.SelectMany(r => r.KeyframeViewModels).OrderByDescending(t => t.Keyframe.Position).FirstOrDefault();
|
||||
var endTime = lastKeyFrame?.Keyframe.Position.Add(new TimeSpan(0, 0, 0, 10)) ?? TimeSpan.FromSeconds(10);
|
||||
|
||||
Width = endTime.TotalSeconds * LayerPropertiesViewModel.PixelsPerSecond;
|
||||
|
||||
// Ensure the caret isn't outside the end time
|
||||
if (_profileEditorService.CurrentTime > endTime)
|
||||
_profileEditorService.CurrentTime = endTime;
|
||||
}
|
||||
|
||||
public void PopulateProperties(List<LayerPropertyViewModel> properties)
|
||||
{
|
||||
var newViewModels = new List<PropertyTrackViewModel>();
|
||||
foreach (var property in properties)
|
||||
newViewModels.AddRange(CreateViewModels(property));
|
||||
|
||||
PropertyTrackViewModels.Clear();
|
||||
PropertyTrackViewModels.AddRange(newViewModels);
|
||||
UpdateEndTime();
|
||||
}
|
||||
|
||||
public void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
// Determine the index by flattening all the layer's properties
|
||||
var index = layerPropertyViewModel.LayerProperty.GetFlattenedIndex();
|
||||
if (index > PropertyTrackViewModels.Count)
|
||||
index = PropertyTrackViewModels.Count;
|
||||
PropertyTrackViewModels.Insert(index, _propertyTrackVmFactory.Create(this, layerPropertyViewModel));
|
||||
}
|
||||
|
||||
public void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
|
||||
{
|
||||
var vm = PropertyTrackViewModels.FirstOrDefault(v => v.LayerPropertyViewModel == layerPropertyViewModel);
|
||||
if (vm != null)
|
||||
PropertyTrackViewModels.Remove(vm);
|
||||
}
|
||||
|
||||
public void UpdateKeyframePositions()
|
||||
{
|
||||
foreach (var viewModel in PropertyTrackViewModels)
|
||||
viewModel.UpdateKeyframes(LayerPropertiesViewModel.PixelsPerSecond);
|
||||
|
||||
UpdateEndTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the time line's keyframes
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
foreach (var viewModel in PropertyTrackViewModels)
|
||||
viewModel.PopulateKeyframes();
|
||||
|
||||
UpdateEndTime();
|
||||
}
|
||||
|
||||
private void OnSelectedProfileElementUpdated(object sender, ProfileElementEventArgs e)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnPixelsPerSecondChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateKeyframePositions();
|
||||
}
|
||||
|
||||
private List<PropertyTrackViewModel> CreateViewModels(LayerPropertyViewModel property)
|
||||
{
|
||||
var result = new List<PropertyTrackViewModel> {_propertyTrackVmFactory.Create(this, property)};
|
||||
foreach (var child in property.Children)
|
||||
result.AddRange(CreateViewModels(child));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Keyframe movement
|
||||
|
||||
public void MoveSelectedKeyframes(TimeSpan cursorTime)
|
||||
{
|
||||
// Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging
|
||||
SelectionRectangle.Rect = new Rect();
|
||||
|
||||
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
|
||||
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||
keyframeViewModel.ApplyMovement(cursorTime);
|
||||
|
||||
_profileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
|
||||
|
||||
public void ReleaseSelectedKeyframes()
|
||||
{
|
||||
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
|
||||
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||
keyframeViewModel.ReleaseMovement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keyframe selection
|
||||
|
||||
private Point _mouseDragStartPoint;
|
||||
private bool _mouseDragging;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Released)
|
||||
return;
|
||||
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
|
||||
SelectionRectangle.Rect = new Rect();
|
||||
_mouseDragStartPoint = e.GetPosition((IInputElement) sender);
|
||||
_mouseDragging = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void TimelineCanvasMouseUp(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_mouseDragging)
|
||||
return;
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
SelectionRectangle.Rect = selectedRect;
|
||||
|
||||
var selectedKeyframes = HitTestUtilities.GetHitViewModels<PropertyTrackKeyframeViewModel>((Visual) sender, SelectionRectangle);
|
||||
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
|
||||
foreach (var keyframeViewModel in keyframeViewModels)
|
||||
keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel);
|
||||
|
||||
_mouseDragging = false;
|
||||
e.Handled = true;
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
public void TimelineCanvasMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (_mouseDragging && e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
SelectionRectangle.Rect = selectedRect;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectKeyframe(PropertyTrackKeyframeViewModel clicked, bool selectBetween, bool toggle)
|
||||
{
|
||||
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
|
||||
if (selectBetween)
|
||||
{
|
||||
var selectedIndex = keyframeViewModels.FindIndex(k => k.IsSelected);
|
||||
// If nothing is selected, select only the clicked
|
||||
if (selectedIndex == -1)
|
||||
{
|
||||
clicked.IsSelected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var keyframeViewModel in keyframeViewModels)
|
||||
keyframeViewModel.IsSelected = false;
|
||||
|
||||
var clickedIndex = keyframeViewModels.IndexOf(clicked);
|
||||
if (clickedIndex < selectedIndex)
|
||||
{
|
||||
foreach (var keyframeViewModel in keyframeViewModels.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1))
|
||||
keyframeViewModel.IsSelected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var keyframeViewModel in keyframeViewModels.Skip(selectedIndex).Take(clickedIndex - selectedIndex + 1))
|
||||
keyframeViewModel.IsSelected = true;
|
||||
}
|
||||
}
|
||||
else if (toggle)
|
||||
{
|
||||
// Toggle only the clicked keyframe, leave others alone
|
||||
clicked.IsSelected = !clicked.IsSelected;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only select the clicked keyframe
|
||||
foreach (var keyframeViewModel in keyframeViewModels)
|
||||
keyframeViewModel.IsSelected = false;
|
||||
clicked.IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core.Utilities;
|
||||
using Humanizer;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class PropertyTrackEasingViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly PropertyTrackKeyframeViewModel _keyframeViewModel;
|
||||
private bool _isEasingModeSelected;
|
||||
|
||||
public PropertyTrackEasingViewModel(PropertyTrackKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction)
|
||||
{
|
||||
_keyframeViewModel = keyframeViewModel;
|
||||
_isEasingModeSelected = keyframeViewModel.Keyframe.EasingFunction == easingFunction;
|
||||
|
||||
EasingFunction = easingFunction;
|
||||
Description = easingFunction.Humanize();
|
||||
|
||||
CreateGeometry();
|
||||
}
|
||||
|
||||
public Easings.Functions EasingFunction { get; }
|
||||
public PointCollection EasingPoints { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public bool IsEasingModeSelected
|
||||
{
|
||||
get => _isEasingModeSelected;
|
||||
set
|
||||
{
|
||||
_isEasingModeSelected = value;
|
||||
if (_isEasingModeSelected)
|
||||
_keyframeViewModel.SelectEasingMode(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateGeometry()
|
||||
{
|
||||
EasingPoints = new PointCollection();
|
||||
for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
var x = i;
|
||||
var y = Easings.Interpolate(i / 10.0, EasingFunction) * 10;
|
||||
EasingPoints.Add(new Point(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,176 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Utilities;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class PropertyTrackKeyframeViewModel<T> : PropertyTrackKeyframeViewModel
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private int _pixelsPerSecond;
|
||||
|
||||
public PropertyTrackKeyframeViewModel(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe<T> keyframe, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
|
||||
PropertyTrackViewModel = propertyTrackViewModel;
|
||||
Keyframe = keyframe;
|
||||
EasingViewModels = new BindableCollection<PropertyTrackEasingViewModel>();
|
||||
CreateEasingViewModels();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
public PropertyTrackViewModel PropertyTrackViewModel { get; }
|
||||
public LayerPropertyKeyframe<T> Keyframe { get; }
|
||||
public BindableCollection<PropertyTrackEasingViewModel> EasingViewModels { get; set; }
|
||||
public double X { get; set; }
|
||||
public string Timestamp { get; set; }
|
||||
|
||||
public UIElement ParentView { get; set; }
|
||||
|
||||
|
||||
public void Update(int pixelsPerSecond)
|
||||
{
|
||||
_pixelsPerSecond = pixelsPerSecond;
|
||||
|
||||
X = pixelsPerSecond * Keyframe.Position.TotalSeconds;
|
||||
Timestamp = $"{Math.Floor(Keyframe.Position.TotalSeconds):00}.{Keyframe.Position.Milliseconds:000}";
|
||||
}
|
||||
|
||||
#region Keyframe movement
|
||||
|
||||
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Released)
|
||||
return;
|
||||
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !IsSelected)
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, true, false);
|
||||
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, true);
|
||||
else if (!IsSelected)
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, false);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public void KeyframeMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.ReleaseSelectedKeyframes();
|
||||
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
public void KeyframeMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
PropertyTrackViewModel.PropertyTimelineViewModel.MoveSelectedKeyframes(GetCursorTime(e.GetPosition(ParentView)));
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private TimeSpan GetCursorTime(Point position)
|
||||
{
|
||||
// Get the parent grid, need that for our position
|
||||
var x = Math.Max(0, position.X);
|
||||
var time = TimeSpan.FromSeconds(x / _pixelsPerSecond);
|
||||
|
||||
// Round the time to something that fits the current zoom level
|
||||
if (_pixelsPerSecond < 200)
|
||||
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0);
|
||||
else if (_pixelsPerSecond < 500)
|
||||
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0);
|
||||
else
|
||||
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds));
|
||||
|
||||
// If shift is held, snap to the current time
|
||||
// Take a tolerance of 5 pixels (half a keyframe width)
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
{
|
||||
var tolerance = 1000f / _pixelsPerSecond * 5;
|
||||
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
|
||||
time = _profileEditorService.CurrentTime;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Context menu actions
|
||||
|
||||
public void Copy()
|
||||
{
|
||||
var keyframe = PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.CreateNewKeyframe(Keyframe.Position, Keyframe.BaseValue);
|
||||
keyframe.EasingFunction = Keyframe.EasingFunction;
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.RemoveKeyframe(Keyframe);
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Easing
|
||||
|
||||
private void CreateEasingViewModels()
|
||||
{
|
||||
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new PropertyTrackEasingViewModel(this, v)));
|
||||
}
|
||||
|
||||
public void SelectEasingMode(PropertyTrackEasingViewModel easingViewModel)
|
||||
{
|
||||
Keyframe.EasingFunction = easingViewModel.EasingFunction;
|
||||
// Set every selection to false except on the VM that made the change
|
||||
foreach (var propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
|
||||
propertyTrackEasingViewModel.IsEasingModeSelected = false;
|
||||
|
||||
_profileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Movement
|
||||
|
||||
private bool _movementReleased = true;
|
||||
private TimeSpan _startOffset;
|
||||
|
||||
public void ApplyMovement(TimeSpan cursorTime)
|
||||
{
|
||||
if (_movementReleased)
|
||||
{
|
||||
_movementReleased = false;
|
||||
_startOffset = cursorTime - Keyframe.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Keyframe.Position = cursorTime - _startOffset;
|
||||
if (Keyframe.Position < TimeSpan.Zero)
|
||||
Keyframe.Position = TimeSpan.Zero;
|
||||
|
||||
Update(_pixelsPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseMovement()
|
||||
{
|
||||
_movementReleased = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public abstract class PropertyTrackKeyframeViewModel : PropertyChangedBase
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.PropertyTrackView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="20" d:DesignWidth="600"
|
||||
d:DataContext="{d:DesignInstance local:PropertyTrackViewModel}">
|
||||
<Border Height="25"
|
||||
BorderThickness="0,0,0,1"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}"
|
||||
Visibility="{Binding MustDisplay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl ItemsSource="{Binding KeyframeViewModels}"
|
||||
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
|
||||
Stroke="White"
|
||||
StrokeThickness="0"
|
||||
Width="10"
|
||||
Height="10"
|
||||
Margin="-5,6,0,0"
|
||||
ToolTip="{Binding Timestamp}"
|
||||
s:View.ActionTarget="{Binding}"
|
||||
MouseDown="{s:Action KeyframeMouseDown}"
|
||||
MouseUp="{s:Action KeyframeMouseUp}"
|
||||
MouseMove="{s:Action KeyframeMouseMove}">
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="{x:Type Ellipse}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
<Ellipse.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Copy" Command="{s:Action Copy}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Delete" Command="{s:Action Delete}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Delete" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
|
||||
<MenuItem.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||
<Setter Property="IsCheckable" Value="True" />
|
||||
<Setter Property="IsChecked" Value="{Binding Path=IsEasingModeSelected, Mode=TwoWay}" />
|
||||
</Style>
|
||||
</MenuItem.ItemContainerStyle>
|
||||
<MenuItem.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
|
||||
StrokeThickness="1"
|
||||
Points="{Binding EasingPoints}"
|
||||
Stretch="Uniform"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="0 0 10 0" />
|
||||
<TextBlock Text="{Binding Description}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</MenuItem.ItemTemplate>
|
||||
</MenuItem>
|
||||
<!-- <MenuItem Header="Easing mode" IsEnabled="{Binding CanSelectEasingMode}"> -->
|
||||
<!-- <MenuItem Header="Ease in" Command="{s:Action SetEasingMode}" CommandParameter="EaseIn" /> -->
|
||||
<!-- <MenuItem Header="Ease out" Command="{s:Action SetEasingMode}" CommandParameter="EaseOut" /> -->
|
||||
<!-- <MenuItem Header="Ease in and out" Command="{s:Action SetEasingMode}" CommandParameter="EaseInOut" /> -->
|
||||
<!-- </MenuItem> -->
|
||||
</ContextMenu>
|
||||
</Ellipse.ContextMenu>
|
||||
</Ellipse>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@ -1,91 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class PropertyTrackViewModel : Screen
|
||||
{
|
||||
private readonly IPropertyTrackKeyframeVmFactory _propertyTrackKeyframeVmFactory;
|
||||
|
||||
public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel,
|
||||
IPropertyTrackKeyframeVmFactory propertyTrackKeyframeVmFactory)
|
||||
{
|
||||
_propertyTrackKeyframeVmFactory = propertyTrackKeyframeVmFactory;
|
||||
PropertyTimelineViewModel = propertyTimelineViewModel;
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
|
||||
|
||||
PopulateKeyframes();
|
||||
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
|
||||
|
||||
LayerPropertyViewModel.ExpandedStateChanged += (sender, args) => UpdateMustDisplay();
|
||||
LayerPropertyViewModel.LayerProperty.VisibilityChanged += (sender, args) => UpdateMustDisplay();
|
||||
UpdateMustDisplay();
|
||||
}
|
||||
|
||||
|
||||
public PropertyTimelineViewModel PropertyTimelineViewModel { get; }
|
||||
public LayerPropertyViewModel LayerPropertyViewModel { get; }
|
||||
public BindableCollection<PropertyTrackKeyframeViewModel> KeyframeViewModels { get; set; }
|
||||
public bool MustDisplay { get; set; }
|
||||
|
||||
private void UpdateMustDisplay()
|
||||
{
|
||||
var expandedTest = LayerPropertyViewModel.Parent;
|
||||
while (expandedTest != null)
|
||||
{
|
||||
if (!expandedTest.IsExpanded)
|
||||
{
|
||||
MustDisplay = false;
|
||||
return;
|
||||
}
|
||||
expandedTest = expandedTest.Parent;
|
||||
}
|
||||
|
||||
var visibilityTest = LayerPropertyViewModel.LayerProperty;
|
||||
while (visibilityTest != null)
|
||||
{
|
||||
if (visibilityTest.IsHidden)
|
||||
{
|
||||
MustDisplay = false;
|
||||
return;
|
||||
}
|
||||
visibilityTest = visibilityTest.Parent;
|
||||
}
|
||||
|
||||
MustDisplay = true;
|
||||
}
|
||||
|
||||
public void PopulateKeyframes()
|
||||
{
|
||||
// Remove old keyframes
|
||||
KeyframeViewModels.RemoveRange(KeyframeViewModels.ToList().Where(vm => !LayerPropertyViewModel.LayerProperty.UntypedKeyframes.Contains(vm.Keyframe)));
|
||||
|
||||
// Add new keyframes
|
||||
KeyframeViewModels.AddRange(
|
||||
LayerPropertyViewModel.LayerProperty.UntypedKeyframes
|
||||
.Where(k => KeyframeViewModels.All(vm => vm.Keyframe != k))
|
||||
.Select(k => _propertyTrackKeyframeVmFactory.Create(this, k))
|
||||
);
|
||||
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
|
||||
}
|
||||
|
||||
public void UpdateKeyframes(int pixelsPerSecond)
|
||||
{
|
||||
foreach (var keyframeViewModel in KeyframeViewModels)
|
||||
{
|
||||
keyframeViewModel.ParentView = View;
|
||||
keyframeViewModel.Update(pixelsPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
foreach (var keyframeViewModel in KeyframeViewModels)
|
||||
keyframeViewModel.ParentView = View;
|
||||
base.OnViewLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelinePropertyGroupViewModel
|
||||
{
|
||||
public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelinePropertyViewModel<T> : TimelinePropertyViewModel
|
||||
{
|
||||
public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TimelinePropertyViewModel : IDisposable
|
||||
{
|
||||
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelineViewModel
|
||||
{
|
||||
public TimelineViewModel(BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
|
||||
{
|
||||
LayerPropertyGroups = layerPropertyGroups;
|
||||
}
|
||||
|
||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
|
||||
}
|
||||
}
|
||||
@ -5,18 +5,20 @@ using Artemis.Core.Events;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Plugins.LayerBrush;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
|
||||
{
|
||||
public class BrushPropertyInputViewModel : PropertyInputViewModel
|
||||
public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference>
|
||||
{
|
||||
private readonly ILayerService _layerService;
|
||||
private readonly IPluginService _pluginService;
|
||||
|
||||
public BrushPropertyInputViewModel(IProfileEditorService profileEditorService, ILayerService layerService, IPluginService pluginService) : base(profileEditorService)
|
||||
public BrushPropertyInputViewModel(LayerPropertyViewModel<LayerBrushReference> layerPropertyViewModel, ILayerService layerService, IPluginService pluginService)
|
||||
: base(layerPropertyViewModel)
|
||||
{
|
||||
_layerService = layerService;
|
||||
_pluginService = pluginService;
|
||||
@ -26,9 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
|
||||
}
|
||||
|
||||
public BindableCollection<ValueDescription> EnumValues { get; }
|
||||
|
||||
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(LayerBrushReference)};
|
||||
|
||||
|
||||
public LayerBrushReference BrushInputValue
|
||||
{
|
||||
get => (LayerBrushReference) InputValue;
|
||||
@ -18,7 +18,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
|
||||
|
||||
public object EnumInputValue
|
||||
{
|
||||
get => InputValue ?? Enum.GetValues(LayerPropertyViewModel.LayerProperty.Type).Cast<object>().First();
|
||||
get => InputValue ?? Enum.GetValues(GetLayerPropertyEnumType()).Cast<object>().First();
|
||||
set => InputValue = value;
|
||||
}
|
||||
|
||||
@ -29,7 +29,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
EnumValues = EnumUtilities.GetAllValuesAndDescriptions(LayerPropertyViewModel.LayerProperty.Type);
|
||||
EnumValues = EnumUtilities.GetAllValuesAndDescriptions(GetLayerPropertyEnumType());
|
||||
}
|
||||
|
||||
|
||||
private Type GetLayerPropertyEnumType()
|
||||
{
|
||||
return LayerPropertyViewModel.BaseLayerProperty.GetType().GetGenericTypeDefinition();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput
|
||||
{
|
||||
public abstract class PropertyInputViewModel<T> : PropertyChangedBase, IDisposable
|
||||
{
|
||||
protected PropertyInputViewModel(LayerPropertyViewModel<T> layerPropertyViewModel)
|
||||
{
|
||||
LayerPropertyViewModel = layerPropertyViewModel;
|
||||
LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated;
|
||||
}
|
||||
|
||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||
|
||||
public bool InputDragging { get; private set; }
|
||||
|
||||
protected T InputValue
|
||||
{
|
||||
get => LayerPropertyViewModel.LayerProperty.CurrentValue;
|
||||
set => LayerPropertyViewModel.SetCurrentValue(value, !InputDragging);
|
||||
}
|
||||
|
||||
public abstract void Update();
|
||||
|
||||
private void LayerPropertyOnUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
public void InputDragStarted(object sender, EventArgs e)
|
||||
{
|
||||
InputDragging = true;
|
||||
}
|
||||
|
||||
public void InputDragEnded(object sender, EventArgs e)
|
||||
{
|
||||
InputDragging = false;
|
||||
LayerPropertyViewModel.ProfileEditorService.UpdateSelectedProfileElement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LayerPropertyViewModel.LayerProperty.Updated -= LayerPropertyOnUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
||||
{
|
||||
public class TreePropertyGroupViewModel
|
||||
{
|
||||
public TreePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel)layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
||||
{
|
||||
public class TreePropertyViewModel<T> : TreePropertyViewModel
|
||||
{
|
||||
public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
||||
PropertyInputViewModel = CreatePropertyInputViewModel();
|
||||
}
|
||||
|
||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||
public PropertyInputViewModel<T> PropertyInputViewModel { get; set; }
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
PropertyInputViewModel.Dispose();
|
||||
}
|
||||
|
||||
private PropertyInputViewModel<T> CreatePropertyInputViewModel()
|
||||
{
|
||||
if (!IsPropertySupported(typeof(T)))
|
||||
throw new ArtemisUIException($"Failed to create a property input view model, type {typeof(T).Name} is not supported.");
|
||||
|
||||
return (PropertyInputViewModel<T>) Activator.CreateInstance(SupportedTypes[typeof(T)], this);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TreePropertyViewModel : IDisposable
|
||||
{
|
||||
public static ReadOnlyDictionary<Type, Type> SupportedTypes = new ReadOnlyDictionary<Type, Type>(new Dictionary<Type, Type>
|
||||
{
|
||||
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)},
|
||||
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)},
|
||||
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)},
|
||||
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}
|
||||
});
|
||||
|
||||
public static void RegisterPropertyInputViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static bool IsPropertySupported(Type type)
|
||||
{
|
||||
return SupportedTypes.ContainsKey(type);
|
||||
}
|
||||
|
||||
protected TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||
{
|
||||
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
|
||||
}
|
||||
|
||||
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,16 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyTreeView"
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.TreeView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:abstract="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract"
|
||||
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:TreeViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="PropertyTreeStyle" TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
@ -33,7 +35,8 @@
|
||||
BorderThickness="0,0,0,1"
|
||||
Height="25"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<Grid Margin="{Binding Converter={StaticResource lengthConverter}, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<Grid
|
||||
Margin="{Binding Converter={StaticResource lengthConverter}, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="19" />
|
||||
<ColumnDefinition />
|
||||
@ -79,30 +82,30 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<TreeView ItemsSource="{Binding PropertyTreeItemViewModels}"
|
||||
<TreeView ItemsSource="{Binding LayerPropertyGroups}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
PreviewMouseWheel="{s:Action PropertyTreePreviewMouseWheel}"
|
||||
Margin="0 -1">
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style TargetType="TreeViewItem" BasedOn="{StaticResource PropertyTreeStyle}">
|
||||
<Setter Property="IsExpanded" Value="{Binding LayerPropertyViewModel.IsExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="Visibility" Value="{Binding LayerPropertyViewModel.LayerProperty.IsHidden, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}" />
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="Visibility" Value="{Binding IsVisible}" />
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
<TreeView.Resources>
|
||||
<!-- Parents only show their name and children -->
|
||||
<HierarchicalDataTemplate DataType="{x:Type local:PropertyTreeParentViewModel}" ItemsSource="{Binding Children}">
|
||||
<HierarchicalDataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}"
|
||||
ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding LayerPropertyViewModel.LayerProperty.Name}"
|
||||
ToolTip="{Binding LayerPropertyViewModel.LayerProperty.Description}"
|
||||
Margin="5 0"
|
||||
<TextBlock Text="{Binding PropertyGroupDescription.Name}"
|
||||
ToolTip="{Binding PropertyGroupDescription.Description}" Margin="5 0"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</HierarchicalDataTemplate>
|
||||
<!-- Children show their full view -->
|
||||
<DataTemplate DataType="{x:Type local:PropertyTreeChildViewModel}">
|
||||
<ContentControl s:View.Model="{Binding}" />
|
||||
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}">
|
||||
<!-- <ContentControl s:View.Model="{Binding TreePropertyBaseViewModel}" /> -->
|
||||
<TextBlock Text="{Binding PropertyDescription.Name}"
|
||||
ToolTip="{Binding PropertyDescription.Description}" Margin="5 0" VerticalAlignment="Center" />
|
||||
</DataTemplate>
|
||||
</TreeView.Resources>
|
||||
</TreeView>
|
||||
@ -0,0 +1,14 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
||||
{
|
||||
public class TreeViewModel
|
||||
{
|
||||
public TreeViewModel(BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
|
||||
{
|
||||
LayerPropertyGroups = layerPropertyGroups;
|
||||
}
|
||||
|
||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,7 @@
|
||||
</TextBlock>
|
||||
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<TreeView Grid.Row="1"
|
||||
ItemsSource="{Binding RootFolder.Children}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
@ -39,6 +39,7 @@
|
||||
<HierarchicalDataTemplate DataType="{x:Type treeItem:FolderViewModel}" ItemsSource="{Binding Children}">
|
||||
<ContentControl s:View.Model="{Binding}" />
|
||||
</HierarchicalDataTemplate>
|
||||
<!-- TODO: Ensure this item source is required -->
|
||||
<HierarchicalDataTemplate DataType="{x:Type treeItem:LayerViewModel}" ItemsSource="{Binding Children}">
|
||||
<ContentControl s:View.Model="{Binding}" />
|
||||
</HierarchicalDataTemplate>
|
||||
|
||||
@ -122,9 +122,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
|
||||
if (!SupportsChildren)
|
||||
throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name);
|
||||
|
||||
var layer = new Layer(ProfileElement.Profile, ProfileElement, "New layer");
|
||||
foreach (var baseLayerProperty in layer.Properties)
|
||||
_layerService.InstantiateKeyframeEngine(baseLayerProperty);
|
||||
var layer = _layerService.CreateLayer(ProfileElement.Profile, ProfileElement, "New layer");
|
||||
ProfileElement.AddChild(layer);
|
||||
UpdateProfileElements();
|
||||
_profileEditorService.UpdateSelectedProfile();
|
||||
@ -174,7 +172,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
|
||||
}
|
||||
|
||||
// Ensure every child element has an up-to-date VM
|
||||
if (ProfileElement.Children == null)
|
||||
if (ProfileElement.Children == null)
|
||||
return;
|
||||
|
||||
var newChildren = new List<TreeItemViewModel>();
|
||||
@ -192,7 +190,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
|
||||
}
|
||||
}
|
||||
|
||||
if (!newChildren.Any())
|
||||
if (!newChildren.Any())
|
||||
return;
|
||||
|
||||
// Add the new children in one call, prevent extra UI events
|
||||
|
||||
@ -94,15 +94,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
difference += 360;
|
||||
else if (difference > 350)
|
||||
difference -= 360;
|
||||
newRotation = layer.Properties.Rotation.CurrentValue + difference;
|
||||
|
||||
newRotation = layer.Transform.Rotation.CurrentValue + difference;
|
||||
|
||||
// Round the end-result to increments of 5 as well, to avoid staying on an offset
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
newRotation = (float) Math.Round(newRotation / 5f) * 5f;
|
||||
else
|
||||
newRotation = (float) Math.Round(newRotation, 2, MidpointRounding.AwayFromZero);
|
||||
|
||||
layer.Properties.Rotation.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime);
|
||||
layer.Transform.Rotation.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime);
|
||||
ProfileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
var dragStart = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint();
|
||||
_dragOffset = _layerEditorService.GetDragOffset(layer, dragStart);
|
||||
_dragStart = dragStart + _dragOffset;
|
||||
_dragStartScale = layer.Properties.Scale.CurrentValue;
|
||||
_dragStartScale = layer.Transform.Scale.CurrentValue;
|
||||
|
||||
_isResizing = true;
|
||||
}
|
||||
@ -159,19 +159,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
break;
|
||||
case ShapeControlPoint.TopCenter:
|
||||
height = VerticalResize(layer, position, ResizeOrigin.Top);
|
||||
width = layer.Properties.Scale.CurrentValue.Width;
|
||||
width = layer.Transform.Scale.CurrentValue.Width;
|
||||
break;
|
||||
case ShapeControlPoint.RightCenter:
|
||||
width = HorizontalResize(layer, position, ResizeOrigin.Right);
|
||||
height = layer.Properties.Scale.CurrentValue.Height;
|
||||
height = layer.Transform.Scale.CurrentValue.Height;
|
||||
break;
|
||||
case ShapeControlPoint.BottomCenter:
|
||||
width = layer.Properties.Scale.CurrentValue.Width;
|
||||
width = layer.Transform.Scale.CurrentValue.Width;
|
||||
height = VerticalResize(layer, position, ResizeOrigin.Bottom);
|
||||
break;
|
||||
case ShapeControlPoint.LeftCenter:
|
||||
width = HorizontalResize(layer, position, ResizeOrigin.Left);
|
||||
height = layer.Properties.Scale.CurrentValue.Height;
|
||||
height = layer.Transform.Scale.CurrentValue.Height;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
@ -186,7 +186,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
height = (float) Math.Round(1.0 / bounds.Height * smallestSide, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
layer.Properties.Scale.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime);
|
||||
layer.Transform.Scale.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime);
|
||||
ProfileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
|
||||
@ -319,7 +319,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
// Scale down the resulting position and make it relative
|
||||
var scaled = _layerEditorService.GetScaledPoint(layer, position, true);
|
||||
// Round and update the position property
|
||||
layer.Properties.Position.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
|
||||
layer.Transform.Position.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
|
||||
|
||||
ProfileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
@ -338,13 +338,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
var scaled = _layerEditorService.GetScaledPoint(layer, countered[1], false);
|
||||
|
||||
// Update the anchor point, this causes the shape to move
|
||||
layer.Properties.AnchorPoint.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
|
||||
layer.Transform.AnchorPoint.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
|
||||
// TopLeft is not updated yet and acts as a snapshot of the top-left before changing the anchor
|
||||
var path = _layerEditorService.GetLayerPath(layer, true, true, true);
|
||||
// Calculate the (scaled) difference between the old and now position
|
||||
var difference = _layerEditorService.GetScaledPoint(layer, _topLeft - path.Points[0], false);
|
||||
// Apply the difference so that the shape effectively stays in place
|
||||
layer.Properties.Position.SetCurrentValue(RoundPoint(layer.Properties.Position.CurrentValue + difference, 3), ProfileEditorService.CurrentTime);
|
||||
layer.Transform.Position.SetCurrentValue(RoundPoint(layer.Transform.Position.CurrentValue + difference, 3), ProfileEditorService.CurrentTime);
|
||||
|
||||
ProfileEditorService.UpdateProfilePreview();
|
||||
}
|
||||
@ -362,9 +362,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
|
||||
{
|
||||
var counterRotatePath = new SKPath();
|
||||
counterRotatePath.AddPoly(skPoints, false);
|
||||
counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Properties.Rotation.CurrentValue * -1, pivot.X, pivot.Y));
|
||||
counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y));
|
||||
if (includeScale)
|
||||
counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Properties.Scale.CurrentValue.Width / 100f), 1f / (layer.Properties.Scale.CurrentValue.Height / 100f)));
|
||||
counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Transform.Scale.CurrentValue.Width / 100f), 1f / (layer.Transform.Scale.CurrentValue.Height / 100f)));
|
||||
|
||||
return counterRotatePath.Points;
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
using Artemis.UI.Events;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.UI.Services.Interfaces
|
||||
{
|
||||
@ -11,6 +14,7 @@ namespace Artemis.UI.Services.Interfaces
|
||||
ProfileElement SelectedProfileElement { get; }
|
||||
TimeSpan CurrentTime { get; set; }
|
||||
|
||||
LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription);
|
||||
void ChangeSelectedProfile(Profile profile);
|
||||
void UpdateSelectedProfile();
|
||||
void ChangeSelectedProfileElement(ProfileElement profileElement);
|
||||
@ -18,6 +22,8 @@ namespace Artemis.UI.Services.Interfaces
|
||||
void UpdateProfilePreview();
|
||||
void UndoUpdateProfile(ProfileModule module);
|
||||
void RedoUpdateProfile(ProfileModule module);
|
||||
void StopRegularRender();
|
||||
void ResumeRegularRender();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile is selected
|
||||
@ -48,8 +54,5 @@ namespace Artemis.UI.Services.Interfaces
|
||||
/// Occurs when the profile preview has been updated
|
||||
/// </summary>
|
||||
event EventHandler ProfilePreviewUpdated;
|
||||
|
||||
void StopRegularRender();
|
||||
void ResumeRegularRender();
|
||||
}
|
||||
}
|
||||
@ -35,7 +35,7 @@ namespace Artemis.UI.Services
|
||||
public Point GetLayerAnchorPosition(Layer layer, SKPoint? positionOverride = null)
|
||||
{
|
||||
var layerBounds = GetLayerBounds(layer).ToSKRect();
|
||||
var positionProperty = layer.Properties.Position.CurrentValue;
|
||||
var positionProperty = layer.Transform.Position.CurrentValue;
|
||||
if (positionOverride != null)
|
||||
positionProperty = positionOverride.Value;
|
||||
|
||||
@ -59,7 +59,7 @@ namespace Artemis.UI.Services
|
||||
// the layer using the structure of the XAML while the Core has to deal with that by applying the layer
|
||||
// position to the translation
|
||||
var anchorPosition = GetLayerAnchorPosition(layer);
|
||||
var anchorProperty = layer.Properties.AnchorPoint.CurrentValue;
|
||||
var anchorProperty = layer.Transform.AnchorPoint.CurrentValue;
|
||||
|
||||
// Translation originates from the unscaled center of the shape and is tied to the anchor
|
||||
var x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width;
|
||||
@ -67,8 +67,8 @@ namespace Artemis.UI.Services
|
||||
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new TranslateTransform(x, y));
|
||||
transformGroup.Children.Add(new ScaleTransform(layer.Properties.Scale.CurrentValue.Width / 100f, layer.Properties.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
|
||||
transformGroup.Children.Add(new RotateTransform(layer.Properties.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
|
||||
transformGroup.Children.Add(new ScaleTransform(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
|
||||
transformGroup.Children.Add(new RotateTransform(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
|
||||
|
||||
return transformGroup;
|
||||
}
|
||||
@ -83,7 +83,7 @@ namespace Artemis.UI.Services
|
||||
if (anchorOverride != null)
|
||||
anchorPosition = anchorOverride.Value;
|
||||
|
||||
var anchorProperty = layer.Properties.AnchorPoint.CurrentValue;
|
||||
var anchorProperty = layer.Transform.AnchorPoint.CurrentValue;
|
||||
|
||||
// Translation originates from the unscaled center of the shape and is tied to the anchor
|
||||
var x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width;
|
||||
@ -94,9 +94,9 @@ namespace Artemis.UI.Services
|
||||
if (includeTranslation)
|
||||
path.Transform(SKMatrix.MakeTranslation(x, y));
|
||||
if (includeScale)
|
||||
path.Transform(SKMatrix.MakeScale(layer.Properties.Scale.CurrentValue.Width / 100f, layer.Properties.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
|
||||
path.Transform(SKMatrix.MakeScale(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
|
||||
if (includeRotation)
|
||||
path.Transform(SKMatrix.MakeRotationDegrees(layer.Properties.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
|
||||
path.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Core.Services.Storage.Interfaces;
|
||||
using Artemis.UI.Events;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
|
||||
namespace Artemis.UI.Services
|
||||
{
|
||||
@ -13,15 +22,21 @@ namespace Artemis.UI.Services
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly List<Type> _registeredProfileEditors;
|
||||
private TimeSpan _currentTime;
|
||||
private TimeSpan _lastUpdateTime;
|
||||
|
||||
public ProfileEditorService(ICoreService coreService, IProfileService profileService)
|
||||
public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel)
|
||||
{
|
||||
_coreService = coreService;
|
||||
_profileService = profileService;
|
||||
_kernel = kernel;
|
||||
_registeredProfileEditors = new List<Type>();
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<Type> RegisteredPropertyEditors => _registeredProfileEditors.AsReadOnly();
|
||||
|
||||
public Profile SelectedProfile { get; private set; }
|
||||
public ProfileElement SelectedProfileElement { get; private set; }
|
||||
|
||||
@ -38,6 +53,23 @@ namespace Artemis.UI.Services
|
||||
}
|
||||
}
|
||||
|
||||
public LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription)
|
||||
{
|
||||
// Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line
|
||||
var genericType = baseLayerProperty.GetType().GetGenericArguments()[0];
|
||||
// Only create entries for types supported by a tree input VM
|
||||
if (!TreePropertyViewModel.IsPropertySupported(genericType))
|
||||
return null;
|
||||
var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType);
|
||||
var parameters = new IParameter[]
|
||||
{
|
||||
new ConstructorArgument("layerProperty", baseLayerProperty),
|
||||
new ConstructorArgument("propertyDescription", propertyDescription)
|
||||
};
|
||||
|
||||
return (LayerPropertyBaseViewModel) _kernel.Get(genericViewModel, parameters);
|
||||
}
|
||||
|
||||
public void ChangeSelectedProfile(Profile profile)
|
||||
{
|
||||
ChangeSelectedProfileElement(null);
|
||||
|
||||
@ -35,7 +35,7 @@ namespace Artemis.Plugins.LayerBrushes.Color
|
||||
GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged;
|
||||
}
|
||||
|
||||
private void GradientTypeOnBaseValueChanged(object? sender, EventArgs e)
|
||||
private void GradientTypeOnBaseValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
Color.IsHidden = GradientType.BaseValue != LayerBrushes.Color.GradientType.Solid;
|
||||
Gradient.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid;
|
||||
|
||||
@ -57,7 +57,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
ColorType.BaseValueChanged += ColorTypeOnBaseValueChanged;
|
||||
}
|
||||
|
||||
private void ColorTypeOnBaseValueChanged(object? sender, EventArgs e)
|
||||
private void ColorTypeOnBaseValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
GradientColor.IsHidden = ColorType.BaseValue != ColorMappingType.Gradient;
|
||||
MainColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user