mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile editor - Ported most VMs of the properties tree and timeline
This commit is contained in:
parent
bf1aad1549
commit
c04bff1f48
@ -249,6 +249,7 @@ namespace Artemis.Core
|
|||||||
if (_keyframesEnabled == value) return;
|
if (_keyframesEnabled == value) return;
|
||||||
_keyframesEnabled = value;
|
_keyframesEnabled = value;
|
||||||
OnKeyframesToggled();
|
OnKeyframesToggled();
|
||||||
|
OnPropertyChanged(nameof(KeyframesEnabled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,8 +115,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|||||||
|
|
||||||
public interface ITimelinePropertyViewModel : IScreen
|
public interface ITimelinePropertyViewModel : IScreen
|
||||||
{
|
{
|
||||||
List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels();
|
List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels();
|
||||||
void WipeKeyframes(TimeSpan? start, TimeSpan? end);
|
void WipeKeyframes(TimeSpan? start, TimeSpan? end);
|
||||||
void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount);
|
void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -11,6 +11,7 @@ namespace Artemis.UI.Shared.Services.ProfileEditor
|
|||||||
IObservable<RenderProfileElement?> ProfileElement { get; }
|
IObservable<RenderProfileElement?> ProfileElement { get; }
|
||||||
IObservable<ProfileEditorHistory?> History { get; }
|
IObservable<ProfileEditorHistory?> History { get; }
|
||||||
IObservable<TimeSpan> Time { get; }
|
IObservable<TimeSpan> Time { get; }
|
||||||
|
IObservable<double> PixelsPerSecond { get; }
|
||||||
|
|
||||||
void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration);
|
void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration);
|
||||||
void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement);
|
void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement);
|
||||||
|
|||||||
@ -15,6 +15,7 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
private readonly Dictionary<ProfileConfiguration, ProfileEditorHistory> _profileEditorHistories = new();
|
private readonly Dictionary<ProfileConfiguration, ProfileEditorHistory> _profileEditorHistories = new();
|
||||||
private readonly BehaviorSubject<RenderProfileElement?> _profileElementSubject = new(null);
|
private readonly BehaviorSubject<RenderProfileElement?> _profileElementSubject = new(null);
|
||||||
private readonly BehaviorSubject<TimeSpan> _timeSubject = new(TimeSpan.Zero);
|
private readonly BehaviorSubject<TimeSpan> _timeSubject = new(TimeSpan.Zero);
|
||||||
|
private readonly BehaviorSubject<double> _pixelsPerSecondSubject = new(300);
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
|
|
||||||
@ -22,9 +23,12 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
{
|
{
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
|
|
||||||
ProfileConfiguration = _profileConfigurationSubject.AsObservable();
|
ProfileConfiguration = _profileConfigurationSubject.AsObservable();
|
||||||
ProfileElement = _profileElementSubject.AsObservable();
|
ProfileElement = _profileElementSubject.AsObservable();
|
||||||
History = Observable.Defer(() => Observable.Return(GetHistory(_profileConfigurationSubject.Value))).Concat(ProfileConfiguration.Select(GetHistory));
|
History = Observable.Defer(() => Observable.Return(GetHistory(_profileConfigurationSubject.Value))).Concat(ProfileConfiguration.Select(GetHistory));
|
||||||
|
Time = _timeSubject.AsObservable();
|
||||||
|
PixelsPerSecond = _pixelsPerSecondSubject.AsObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
|
private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
|
||||||
@ -43,6 +47,7 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
public IObservable<RenderProfileElement?> ProfileElement { get; }
|
public IObservable<RenderProfileElement?> ProfileElement { get; }
|
||||||
public IObservable<ProfileEditorHistory?> History { get; }
|
public IObservable<ProfileEditorHistory?> History { get; }
|
||||||
public IObservable<TimeSpan> Time { get; }
|
public IObservable<TimeSpan> Time { get; }
|
||||||
|
public IObservable<double> PixelsPerSecond { get; }
|
||||||
|
|
||||||
public void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration)
|
public void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration)
|
||||||
{
|
{
|
||||||
@ -59,6 +64,11 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
_timeSubject.OnNext(time);
|
_timeSubject.OnNext(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangePixelsPerSecond(double pixelsPerSecond)
|
||||||
|
{
|
||||||
|
_pixelsPerSecondSubject.OnNext(pixelsPerSecond);
|
||||||
|
}
|
||||||
|
|
||||||
public void ExecuteCommand(IProfileEditorCommand command)
|
public void ExecuteCommand(IProfileEditorCommand command)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.PropertyInput;
|
||||||
|
|
||||||
|
public interface IPropertyInputService : IArtemisSharedUIService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read-only collection of all registered property editors
|
||||||
|
/// </summary>
|
||||||
|
ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
||||||
|
/// <see cref="PropertyInputViewModel{T}" />
|
||||||
|
/// <para>Note: DataBindingProperty will remove itself on plugin disable so you don't have to</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
PropertyInputRegistration RegisterPropertyInput<T>(Plugin plugin) where T : PropertyInputViewModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
||||||
|
/// <see cref="PropertyInputViewModel{T}" />
|
||||||
|
/// <para>Note: DataBindingProperty will remove itself on plugin disable so you don't have to</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="viewModelType"></param>
|
||||||
|
/// <param name="plugin"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
PropertyInputRegistration RegisterPropertyInput(Type viewModelType, Plugin plugin);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the property input view model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="registration"></param>
|
||||||
|
void RemovePropertyInput(PropertyInputRegistration registration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if there is a matching registration for the provided layer property
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="layerProperty">The layer property to try to find a view model for</param>
|
||||||
|
bool CanCreatePropertyInputViewModel(ILayerProperty layerProperty);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a matching registration is found, creates a new <see cref="PropertyInputViewModel{T}" /> supporting
|
||||||
|
/// <typeparamref name="T" />
|
||||||
|
/// </summary>
|
||||||
|
PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty);
|
||||||
|
}
|
||||||
@ -2,99 +2,118 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Ninject;
|
||||||
|
using Ninject.Parameters;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.PropertyInput
|
namespace Artemis.UI.Shared.Services.PropertyInput;
|
||||||
|
|
||||||
|
internal class PropertyInputService : IPropertyInputService
|
||||||
{
|
{
|
||||||
internal class PropertyInputService : IPropertyInputService
|
private readonly IKernel _kernel;
|
||||||
|
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
||||||
|
|
||||||
|
public PropertyInputService(IKernel kernel)
|
||||||
{
|
{
|
||||||
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
_kernel = kernel;
|
||||||
|
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||||
|
RegisteredPropertyEditors = new ReadOnlyCollection<PropertyInputRegistration>(_registeredPropertyEditors);
|
||||||
|
}
|
||||||
|
|
||||||
public PropertyInputService()
|
/// <inheritdoc />
|
||||||
|
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors { get; }
|
||||||
|
|
||||||
|
public PropertyInputRegistration RegisterPropertyInput<T>(Plugin plugin) where T : PropertyInputViewModel
|
||||||
|
{
|
||||||
|
return RegisterPropertyInput(typeof(T), plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyInputRegistration RegisterPropertyInput(Type viewModelType, Plugin plugin)
|
||||||
|
{
|
||||||
|
if (!typeof(PropertyInputViewModel).IsAssignableFrom(viewModelType))
|
||||||
|
throw new ArtemisSharedUIException($"Property input VM type must implement {nameof(PropertyInputViewModel)}");
|
||||||
|
|
||||||
|
lock (_registeredPropertyEditors)
|
||||||
{
|
{
|
||||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
// Indirectly checked if there's a BaseType above
|
||||||
RegisteredPropertyEditors = new ReadOnlyCollection<PropertyInputRegistration>(_registeredPropertyEditors);
|
Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0];
|
||||||
}
|
// If the supported type is a generic, assume there is a base type
|
||||||
|
if (supportedType.IsGenericParameter)
|
||||||
|
{
|
||||||
|
if (supportedType.BaseType == null)
|
||||||
|
throw new ArtemisSharedUIException("Generic property input VM type must have a type constraint");
|
||||||
|
supportedType = supportedType.BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
PropertyInputRegistration? existing = _registeredPropertyEditors.FirstOrDefault(r => r.SupportedType == supportedType);
|
||||||
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors { get; }
|
if (existing != null)
|
||||||
|
{
|
||||||
|
if (existing.Plugin != plugin)
|
||||||
|
throw new ArtemisSharedUIException($"Cannot register property editor for type {supportedType.Name} because an editor was already " +
|
||||||
|
$"registered by {existing.Plugin}");
|
||||||
|
|
||||||
/// <inheritdoc />
|
return existing;
|
||||||
public PropertyInputRegistration RegisterPropertyInput<T>(Plugin plugin) where T : PropertyInputViewModel
|
}
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
_kernel.Bind(viewModelType).ToSelf();
|
||||||
public PropertyInputRegistration RegisterPropertyInput(Type viewModelType, Plugin plugin)
|
PropertyInputRegistration registration = new(this, plugin, supportedType, viewModelType);
|
||||||
{
|
_registeredPropertyEditors.Add(registration);
|
||||||
throw new NotImplementedException();
|
return registration;
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void RemovePropertyInput(PropertyInputRegistration registration)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool CanCreatePropertyInputViewModel(ILayerProperty layerProperty)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPropertyInputService : IArtemisSharedUIService
|
public void RemovePropertyInput(PropertyInputRegistration registration)
|
||||||
{
|
{
|
||||||
/// <summary>
|
lock (_registeredPropertyEditors)
|
||||||
/// Gets a read-only collection of all registered property editors
|
{
|
||||||
/// </summary>
|
if (_registeredPropertyEditors.Contains(registration))
|
||||||
ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors { get; }
|
{
|
||||||
|
registration.Unsubscribe();
|
||||||
|
_registeredPropertyEditors.Remove(registration);
|
||||||
|
|
||||||
/// <summary>
|
_kernel.Unbind(registration.ViewModelType);
|
||||||
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
}
|
||||||
/// <see cref="PropertyInputViewModel{T}" />
|
}
|
||||||
/// <para>Note: DataBindingProperty will remove itself on plugin disable so you don't have to</para>
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="plugin"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
PropertyInputRegistration RegisterPropertyInput<T>(Plugin plugin) where T : PropertyInputViewModel;
|
|
||||||
|
|
||||||
/// <summary>
|
public bool CanCreatePropertyInputViewModel(ILayerProperty layerProperty)
|
||||||
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
{
|
||||||
/// <see cref="PropertyInputViewModel{T}" />
|
PropertyInputRegistration? registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == layerProperty.PropertyType);
|
||||||
/// <para>Note: DataBindingProperty will remove itself on plugin disable so you don't have to</para>
|
if (registration == null && layerProperty.PropertyType.IsEnum)
|
||||||
/// </summary>
|
registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(Enum));
|
||||||
/// <param name="viewModelType"></param>
|
|
||||||
/// <param name="plugin"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
PropertyInputRegistration RegisterPropertyInput(Type viewModelType, Plugin plugin);
|
|
||||||
|
|
||||||
/// <summary>
|
return registration != null;
|
||||||
/// Removes the property input view model
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="registration"></param>
|
|
||||||
void RemovePropertyInput(PropertyInputRegistration registration);
|
|
||||||
|
|
||||||
/// <summary>
|
public PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty)
|
||||||
/// Determines if there is a matching registration for the provided layer property
|
{
|
||||||
/// </summary>
|
Type? viewModelType = null;
|
||||||
/// <param name="layerProperty">The layer property to try to find a view model for</param>
|
PropertyInputRegistration? registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T));
|
||||||
bool CanCreatePropertyInputViewModel(ILayerProperty layerProperty);
|
|
||||||
|
|
||||||
/// <summary>
|
// Check for enums if no supported type was found
|
||||||
/// If a matching registration is found, creates a new <see cref="PropertyInputViewModel{T}" /> supporting
|
if (registration == null && typeof(T).IsEnum)
|
||||||
/// <typeparamref name="T" />
|
{
|
||||||
/// </summary>
|
// The enum VM will likely be a generic, that requires creating a generic type matching the layer property
|
||||||
PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty);
|
registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(Enum));
|
||||||
|
if (registration != null && registration.ViewModelType.IsGenericType)
|
||||||
|
viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments);
|
||||||
|
}
|
||||||
|
else if (registration != null)
|
||||||
|
{
|
||||||
|
viewModelType = registration.ViewModelType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewModelType == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ConstructorArgument parameter = new("layerProperty", layerProperty);
|
||||||
|
// ReSharper disable once InconsistentlySynchronizedField
|
||||||
|
// When you've just spent the last 2 hours trying to figure out a deadlock and reach this line, I'm so, so sorry. I thought this would be fine.
|
||||||
|
IKernel kernel = registration?.Plugin.Kernel ?? _kernel;
|
||||||
|
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ using Artemis.UI.Screens.Device;
|
|||||||
using Artemis.UI.Screens.Plugins;
|
using Artemis.UI.Screens.Plugins;
|
||||||
using Artemis.UI.Screens.ProfileEditor;
|
using Artemis.UI.Screens.ProfileEditor;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
||||||
using Artemis.UI.Screens.Settings;
|
using Artemis.UI.Screens.Settings;
|
||||||
@ -75,4 +76,10 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
// TimelineViewModel TimelineViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
|
// TimelineViewModel TimelineViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
|
||||||
// TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
|
// TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IPropertyVmFactory
|
||||||
|
{
|
||||||
|
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
|
||||||
|
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||||
|
using Ninject.Extensions.Factory;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Ninject.InstanceProviders
|
||||||
|
{
|
||||||
|
public class LayerPropertyViewModelInstanceProvider : StandardInstanceProvider
|
||||||
|
{
|
||||||
|
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
|
||||||
|
{
|
||||||
|
if (methodInfo.ReturnType != typeof(ITreePropertyViewModel) && methodInfo.ReturnType != typeof(ITimelinePropertyViewModel))
|
||||||
|
return base.GetType(methodInfo, arguments);
|
||||||
|
|
||||||
|
// Find LayerProperty type
|
||||||
|
Type? layerPropertyType = arguments[0].GetType();
|
||||||
|
while (layerPropertyType != null && (!layerPropertyType.IsGenericType || layerPropertyType.GetGenericTypeDefinition() != typeof(LayerProperty<>)))
|
||||||
|
layerPropertyType = layerPropertyType.BaseType;
|
||||||
|
if (layerPropertyType == null)
|
||||||
|
return base.GetType(methodInfo, arguments);
|
||||||
|
|
||||||
|
if (methodInfo.ReturnType == typeof(ITreePropertyViewModel))
|
||||||
|
return typeof(TreePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments());
|
||||||
|
if (methodInfo.ReturnType == typeof(ITimelinePropertyViewModel))
|
||||||
|
return typeof(TimelinePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments());
|
||||||
|
|
||||||
|
return base.GetType(methodInfo, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
|
using Artemis.UI.Ninject.InstanceProviders;
|
||||||
using Artemis.UI.Screens;
|
using Artemis.UI.Screens;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Shared.PlatformSupport;
|
using Avalonia.Shared.PlatformSupport;
|
||||||
using Ninject.Extensions.Conventions;
|
using Ninject.Extensions.Conventions;
|
||||||
|
using Ninject.Extensions.Factory;
|
||||||
using Ninject.Modules;
|
using Ninject.Modules;
|
||||||
using Ninject.Planning.Bindings.Resolvers;
|
using Ninject.Planning.Bindings.Resolvers;
|
||||||
|
|
||||||
@ -46,6 +48,8 @@ namespace Artemis.UI.Ninject
|
|||||||
.BindToFactory();
|
.BindToFactory();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Kernel.Bind<IPropertyVmFactory>().ToFactory(() => new LayerPropertyViewModelInstanceProvider());
|
||||||
|
|
||||||
// Bind all UI services as singletons
|
// Bind all UI services as singletons
|
||||||
Kernel.Bind(x =>
|
Kernel.Bind(x =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,28 +1,63 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.PropertyInput;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
||||||
|
|
||||||
public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||||
|
private readonly IPropertyInputService _propertyInputService;
|
||||||
private bool _isVisible;
|
private bool _isVisible;
|
||||||
private bool _isExpanded;
|
private bool _isExpanded;
|
||||||
|
private bool _hasChildren;
|
||||||
|
|
||||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory)
|
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService)
|
||||||
{
|
{
|
||||||
Children = new ObservableCollection<ActivatableViewModelBase>();
|
_layerPropertyVmFactory = layerPropertyVmFactory;
|
||||||
|
_propertyInputService = propertyInputService;
|
||||||
|
Children = new ObservableCollection<ViewModelBase>();
|
||||||
LayerPropertyGroup = layerPropertyGroup;
|
LayerPropertyGroup = layerPropertyGroup;
|
||||||
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
|
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
|
||||||
|
|
||||||
IsVisible = !LayerPropertyGroup.IsHidden;
|
PopulateChildren();
|
||||||
// TODO: Update visiblity on change, can't do it atm because not sure how to unsubscribe from the event
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<ActivatableViewModelBase> Children { get; }
|
private void PopulateChildren()
|
||||||
|
{
|
||||||
|
// Get all properties and property groups and create VMs for them
|
||||||
|
// The group has methods for getting this without reflection but then we lose the order of the properties as they are defined on the group
|
||||||
|
foreach (PropertyInfo propertyInfo in LayerPropertyGroup.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
if (Attribute.IsDefined(propertyInfo, typeof(LayerPropertyIgnoreAttribute)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
|
||||||
|
{
|
||||||
|
ILayerProperty? value = (ILayerProperty?) propertyInfo.GetValue(LayerPropertyGroup);
|
||||||
|
// Ensure a supported input VM was found, otherwise don't add it
|
||||||
|
if (value != null && _propertyInputService.CanCreatePropertyInputViewModel(value))
|
||||||
|
Children.Add(_layerPropertyVmFactory.ProfileElementPropertyViewModel(value));
|
||||||
|
}
|
||||||
|
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||||
|
{
|
||||||
|
LayerPropertyGroup? value = (LayerPropertyGroup?) propertyInfo.GetValue(LayerPropertyGroup);
|
||||||
|
if (value != null)
|
||||||
|
Children.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HasChildren = Children.Any(i => i is ProfileElementPropertyViewModel {IsVisible: true} || i is ProfileElementPropertyGroupViewModel {IsVisible: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<ViewModelBase> Children { get; }
|
||||||
public LayerPropertyGroup LayerPropertyGroup { get; }
|
public LayerPropertyGroup LayerPropertyGroup { get; }
|
||||||
public TreeGroupViewModel TreeGroupViewModel { get; }
|
public TreeGroupViewModel TreeGroupViewModel { get; }
|
||||||
|
|
||||||
@ -37,4 +72,10 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
|||||||
get => _isExpanded;
|
get => _isExpanded;
|
||||||
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
|
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasChildren
|
||||||
|
{
|
||||||
|
get => _hasChildren;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _hasChildren, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,20 +1,44 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
||||||
|
|
||||||
public class ProfileElementPropertyViewModel
|
public class ProfileElementPropertyViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
private bool _isExpanded;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private bool _isHighlighted;
|
||||||
|
private bool _isVisible;
|
||||||
|
|
||||||
public ProfileElementPropertyViewModel(ILayerProperty layerProperty, IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory)
|
public ProfileElementPropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
|
||||||
{
|
{
|
||||||
LayerProperty = layerProperty;
|
LayerProperty = layerProperty;
|
||||||
_profileEditorService = profileEditorService;
|
TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this);
|
||||||
_layerPropertyVmFactory = layerPropertyVmFactory;
|
TimelinePropertyViewModel = propertyVmFactory.TimelinePropertyViewModel(LayerProperty, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILayerProperty LayerProperty { get; }
|
public ILayerProperty LayerProperty { get; }
|
||||||
|
public ITreePropertyViewModel TreePropertyViewModel { get; }
|
||||||
|
public ITimelinePropertyViewModel TimelinePropertyViewModel { get; }
|
||||||
|
|
||||||
|
public bool IsVisible
|
||||||
|
{
|
||||||
|
get => _isVisible;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _isVisible, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHighlighted
|
||||||
|
{
|
||||||
|
get => _isHighlighted;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _isHighlighted, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => _isExpanded;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||||
|
|
||||||
|
public interface ITimelineKeyframeViewModel
|
||||||
|
{
|
||||||
|
bool IsSelected { get; set; }
|
||||||
|
TimeSpan Position { get; }
|
||||||
|
ILayerPropertyKeyframe Keyframe { get; }
|
||||||
|
|
||||||
|
#region Movement
|
||||||
|
|
||||||
|
void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
||||||
|
void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
||||||
|
void UpdatePosition(TimeSpan position);
|
||||||
|
void ReleaseMovement();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Context menu actions
|
||||||
|
|
||||||
|
void PopulateEasingViewModels();
|
||||||
|
void ClearEasingViewModels();
|
||||||
|
void Delete(bool save = true);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||||
|
|
||||||
|
public interface ITimelinePropertyViewModel : IReactiveObject
|
||||||
|
{
|
||||||
|
List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels();
|
||||||
|
void WipeKeyframes(TimeSpan? start, TimeSpan? end);
|
||||||
|
void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount);
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Avalonia;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||||
|
|
||||||
|
public class TimelineEasingViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private bool _isEasingModeSelected;
|
||||||
|
|
||||||
|
public TimelineEasingViewModel(Easings.Functions easingFunction, bool isSelected)
|
||||||
|
{
|
||||||
|
_isEasingModeSelected = isSelected;
|
||||||
|
|
||||||
|
EasingFunction = easingFunction;
|
||||||
|
Description = easingFunction.Humanize();
|
||||||
|
|
||||||
|
EasingPoints = new List<Point>();
|
||||||
|
for (int i = 1; i <= 10; i++)
|
||||||
|
{
|
||||||
|
int x = i;
|
||||||
|
double y = Easings.Interpolate(i / 10.0, EasingFunction) * 10;
|
||||||
|
EasingPoints.Add(new Point(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Easings.Functions EasingFunction { get; }
|
||||||
|
public List<Point> EasingPoints { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
public bool IsEasingModeSelected
|
||||||
|
{
|
||||||
|
get => _isEasingModeSelected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isEasingModeSelected = value;
|
||||||
|
if (value) OnEasingModeSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler EasingModeSelected;
|
||||||
|
|
||||||
|
protected virtual void OnEasingModeSelected()
|
||||||
|
{
|
||||||
|
EasingModeSelected?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,169 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using DynamicData;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Disposable = System.Reactive.Disposables.Disposable;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline
|
||||||
|
{
|
||||||
|
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
|
||||||
|
{
|
||||||
|
|
||||||
|
private bool _isSelected;
|
||||||
|
private string _timestamp;
|
||||||
|
private double _x;
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
|
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
|
||||||
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
LayerPropertyKeyframe = layerPropertyKeyframe;
|
||||||
|
EasingViewModels = new ObservableCollection<TimelineEasingViewModel>();
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
_profileEditorService.PixelsPerSecond.Subscribe(p =>
|
||||||
|
{
|
||||||
|
_pixelsPerSecond = p;
|
||||||
|
_profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
|
||||||
|
Disposable.Create(() =>
|
||||||
|
{
|
||||||
|
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
||||||
|
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
|
||||||
|
}).DisposeWith(d);
|
||||||
|
}).DisposeWith(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
|
||||||
|
public ObservableCollection<TimelineEasingViewModel> EasingViewModels { get; }
|
||||||
|
|
||||||
|
public double X
|
||||||
|
{
|
||||||
|
get => _x;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _x, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Timestamp
|
||||||
|
{
|
||||||
|
get => _timestamp;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _timestamp, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => _isSelected;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Position => LayerPropertyKeyframe.Position;
|
||||||
|
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
|
||||||
|
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Movement
|
||||||
|
|
||||||
|
private TimeSpan? _offset;
|
||||||
|
private double _pixelsPerSecond;
|
||||||
|
|
||||||
|
public void ReleaseMovement()
|
||||||
|
{
|
||||||
|
_offset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source)
|
||||||
|
{
|
||||||
|
if (source == this)
|
||||||
|
{
|
||||||
|
_offset = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_offset != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_offset = LayerPropertyKeyframe.Position - source.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source)
|
||||||
|
{
|
||||||
|
if (source == this || _offset == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdatePosition(source.Position + _offset.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePosition(TimeSpan position)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
// if (position < TimeSpan.Zero)
|
||||||
|
// LayerPropertyKeyframe.Position = TimeSpan.Zero;
|
||||||
|
// else if (position > _profileEditorService.SelectedProfileElement.Timeline.Length)
|
||||||
|
// LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.Timeline.Length;
|
||||||
|
// else
|
||||||
|
// LayerPropertyKeyframe.Position = position;
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Easing
|
||||||
|
|
||||||
|
public void PopulateEasingViewModels()
|
||||||
|
{
|
||||||
|
if (EasingViewModels.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
|
||||||
|
.Cast<Easings.Functions>()
|
||||||
|
.Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction)));
|
||||||
|
|
||||||
|
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
||||||
|
timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearEasingViewModels()
|
||||||
|
{
|
||||||
|
EasingViewModels.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimelineEasingViewModelOnEasingModeSelected(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is TimelineEasingViewModel timelineEasingViewModel)
|
||||||
|
SelectEasingMode(timelineEasingViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
|
||||||
|
// Set every selection to false except on the VM that made the change
|
||||||
|
foreach (TimelineEasingViewModel propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
|
||||||
|
propertyTrackEasingViewModel.IsEasingModeSelected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Context menu actions
|
||||||
|
|
||||||
|
public void Delete(bool save = true)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline
|
||||||
|
{
|
||||||
|
public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelinePropertyViewModel
|
||||||
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
public LayerProperty<T> LayerProperty { get; }
|
||||||
|
public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
|
||||||
|
public ObservableCollection<TimelineKeyframeViewModel<T>> KeyframeViewModels { get; }
|
||||||
|
|
||||||
|
public TimelinePropertyViewModel(LayerProperty<T> layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel, IProfileEditorService profileEditorService)
|
||||||
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
LayerProperty = layerProperty;
|
||||||
|
ProfileElementPropertyViewModel = profileElementPropertyViewModel;
|
||||||
|
KeyframeViewModels = new ObservableCollection<TimelineKeyframeViewModel<T>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of ITimelinePropertyViewModel
|
||||||
|
|
||||||
|
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels()
|
||||||
|
{
|
||||||
|
return KeyframeViewModels.Cast<ITimelineKeyframeViewModel>().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WipeKeyframes(TimeSpan? start, TimeSpan? end)
|
||||||
|
{
|
||||||
|
start ??= TimeSpan.Zero;
|
||||||
|
end ??= TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
|
||||||
|
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList();
|
||||||
|
foreach (LayerPropertyKeyframe<T> keyframe in toShift)
|
||||||
|
LayerProperty.RemoveKeyframe(keyframe);
|
||||||
|
|
||||||
|
UpdateKeyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
|
||||||
|
{
|
||||||
|
start ??= TimeSpan.Zero;
|
||||||
|
end ??= TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position > start && k.Position < end).ToList();
|
||||||
|
foreach (LayerPropertyKeyframe<T> keyframe in toShift)
|
||||||
|
keyframe.Position += amount;
|
||||||
|
|
||||||
|
UpdateKeyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void UpdateKeyframes()
|
||||||
|
{
|
||||||
|
// Only show keyframes if they are enabled
|
||||||
|
if (LayerProperty.KeyframesEnabled)
|
||||||
|
{
|
||||||
|
List<LayerPropertyKeyframe<T>> keyframes = LayerProperty.Keyframes.ToList();
|
||||||
|
|
||||||
|
List<TimelineKeyframeViewModel<T>> toRemove = KeyframeViewModels.Where(t => !keyframes.Contains(t.LayerPropertyKeyframe)).ToList();
|
||||||
|
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in toRemove)
|
||||||
|
KeyframeViewModels.Remove(timelineKeyframeViewModel);
|
||||||
|
List<TimelineKeyframeViewModel<T>> toAdd = keyframes.Where(k => KeyframeViewModels.All(t => t.LayerPropertyKeyframe != k)).Select(k => new TimelineKeyframeViewModel<T>(k, _profileEditorService)).ToList();
|
||||||
|
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in toAdd)
|
||||||
|
KeyframeViewModels.Add(timelineKeyframeViewModel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
KeyframeViewModels.Clear();
|
||||||
|
|
||||||
|
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in KeyframeViewModels)
|
||||||
|
timelineKeyframeViewModel.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||||
|
|
||||||
|
public interface ITreePropertyViewModel : IReactiveObject
|
||||||
|
{
|
||||||
|
bool HasDataBinding { get; }
|
||||||
|
double GetDepth();
|
||||||
|
}
|
||||||
@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
@ -24,8 +22,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
|||||||
|
|
||||||
public class TreeGroupViewModel : ActivatableViewModelBase
|
public class TreeGroupViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly IWindowService _windowService;
|
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
private BrushConfigurationWindowViewModel? _brushConfigurationWindowViewModel;
|
private BrushConfigurationWindowViewModel? _brushConfigurationWindowViewModel;
|
||||||
private EffectConfigurationWindowViewModel? _effectConfigurationWindowViewModel;
|
private EffectConfigurationWindowViewModel? _effectConfigurationWindowViewModel;
|
||||||
|
|
||||||
@ -41,12 +39,14 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
|||||||
ProfileElementPropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
|
ProfileElementPropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
|
||||||
Disposable.Create(CloseViewModels).DisposeWith(d);
|
Disposable.Create(CloseViewModels).DisposeWith(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: Update ProfileElementPropertyGroupViewModel visibility on change (can remove the sub on line 41 as well then)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel { get; }
|
public ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel { get; }
|
||||||
public LayerPropertyGroup LayerPropertyGroup => ProfileElementPropertyGroupViewModel.LayerPropertyGroup;
|
public LayerPropertyGroup LayerPropertyGroup => ProfileElementPropertyGroupViewModel.LayerPropertyGroup;
|
||||||
public ObservableCollection<ActivatableViewModelBase>? Children => ProfileElementPropertyGroupViewModel.IsExpanded ? ProfileElementPropertyGroupViewModel.Children : null;
|
public ObservableCollection<ViewModelBase>? Children => ProfileElementPropertyGroupViewModel.IsExpanded ? ProfileElementPropertyGroupViewModel.Children : null;
|
||||||
|
|
||||||
public LayerPropertyGroupType GroupType { get; private set; }
|
public LayerPropertyGroupType GroupType { get; private set; }
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
|||||||
// Find the BaseLayerEffect parameter, it is required by the base constructor so its there for sure
|
// Find the BaseLayerEffect parameter, it is required by the base constructor so its there for sure
|
||||||
ParameterInfo effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType));
|
ParameterInfo effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType));
|
||||||
ConstructorArgument argument = new(effectParameter.Name!, layerEffect);
|
ConstructorArgument argument = new(effectParameter.Name!, layerEffect);
|
||||||
EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel)layerEffect.Descriptor.Provider.Plugin.Kernel!.Get(configurationViewModel.Type, argument);
|
EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel) layerEffect.Descriptor.Provider.Plugin.Kernel!.Get(configurationViewModel.Type, argument);
|
||||||
|
|
||||||
_effectConfigurationWindowViewModel = new EffectConfigurationWindowViewModel(viewModel, configurationViewModel);
|
_effectConfigurationWindowViewModel = new EffectConfigurationWindowViewModel(viewModel, configurationViewModel);
|
||||||
await _windowService.ShowDialogAsync(_effectConfigurationWindowViewModel);
|
await _windowService.ShowDialogAsync(_effectConfigurationWindowViewModel);
|
||||||
@ -138,7 +138,7 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
|||||||
_effectConfigurationWindowViewModel?.Close(null);
|
_effectConfigurationWindowViewModel?.Close(null);
|
||||||
_brushConfigurationWindowViewModel?.Close(null);
|
_brushConfigurationWindowViewModel?.Close(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DetermineGroupType()
|
private void DetermineGroupType()
|
||||||
{
|
{
|
||||||
if (LayerPropertyGroup is LayerGeneralProperties)
|
if (LayerPropertyGroup is LayerGeneralProperties)
|
||||||
|
|||||||
@ -4,16 +4,32 @@ using Artemis.UI.Shared.Services.PropertyInput;
|
|||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||||
|
|
||||||
internal class TreePropertyViewModel<T> : ActivatableViewModelBase
|
internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropertyViewModel
|
||||||
{
|
{
|
||||||
public TreePropertyViewModel(LayerProperty<T> layerProperty, ProfileElementPropertyViewModel layerPropertyViewModel, IPropertyInputService propertyInputService)
|
public TreePropertyViewModel(LayerProperty<T> layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel, IPropertyInputService propertyInputService)
|
||||||
{
|
{
|
||||||
LayerProperty = layerProperty;
|
LayerProperty = layerProperty;
|
||||||
LayerPropertyViewModel = layerPropertyViewModel;
|
ProfileElementPropertyViewModel = profileElementPropertyViewModel;
|
||||||
PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
|
PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
|
||||||
|
|
||||||
|
// TODO: Update ProfileElementPropertyViewModel visibility on change
|
||||||
}
|
}
|
||||||
|
|
||||||
public LayerProperty<T> LayerProperty { get; }
|
public LayerProperty<T> LayerProperty { get; }
|
||||||
public ProfileElementPropertyViewModel LayerPropertyViewModel { get; }
|
public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
|
||||||
public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
|
public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
|
||||||
|
public bool HasDataBinding => LayerProperty.HasDataBinding;
|
||||||
|
|
||||||
|
public double GetDepth()
|
||||||
|
{
|
||||||
|
int depth = 0;
|
||||||
|
LayerPropertyGroup? current = LayerProperty.LayerPropertyGroup;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
depth++;
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user