1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 02:03:32 +00:00

Timeline - Fix collapsed keyframes not showing

This commit is contained in:
Robert 2022-05-19 23:48:03 +02:00
parent 4809ebf969
commit a0260b53e5
12 changed files with 170 additions and 77 deletions

View File

@ -34,12 +34,14 @@
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/SidebarCategoryEditView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/SidebarCategoryEditView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Root/SplashView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Root/SplashView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
@ -47,6 +49,8 @@
<entry key="Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/Sidebar/SidebarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/SurfaceEditor/ListDeviceView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/VisualScripting/NodeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Artemis.UI/Screens/VisualScripting/NodeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />

View File

@ -0,0 +1,20 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Provides data for layer property events.
/// </summary>
public class LayerPropertyKeyframeEventArgs : EventArgs
{
internal LayerPropertyKeyframeEventArgs(ILayerPropertyKeyframe keyframe)
{
Keyframe = keyframe;
}
/// <summary>
/// Gets the keyframe this event is related to
/// </summary>
public ILayerPropertyKeyframe Keyframe { get; }
}
}

View File

@ -144,11 +144,11 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Occurs when a new keyframe was added to the layer property /// Occurs when a new keyframe was added to the layer property
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded; public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeAdded;
/// <summary> /// <summary>
/// Occurs when a keyframe was removed from the layer property /// Occurs when a keyframe was removed from the layer property
/// </summary> /// </summary>
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved; public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeRemoved;
} }
} }

View File

@ -322,7 +322,7 @@ namespace Artemis.Core
SortKeyframes(); SortKeyframes();
ReapplyUpdate(); ReapplyUpdate();
OnKeyframeAdded(); OnKeyframeAdded(keyframe);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -357,7 +357,7 @@ namespace Artemis.Core
SortKeyframes(); SortKeyframes();
ReapplyUpdate(); ReapplyUpdate();
OnKeyframeRemoved(); OnKeyframeRemoved(keyframe);
} }
/// <summary> /// <summary>
@ -604,10 +604,10 @@ namespace Artemis.Core
public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled; public event EventHandler<LayerPropertyEventArgs>? KeyframesToggled;
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeAdded; public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeAdded;
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<LayerPropertyEventArgs>? KeyframeRemoved; public event EventHandler<LayerPropertyKeyframeEventArgs>? KeyframeRemoved;
/// <summary> /// <summary>
/// Invokes the <see cref="Updated" /> event /// Invokes the <see cref="Updated" /> event
@ -645,17 +645,19 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Invokes the <see cref="KeyframeAdded" /> event /// Invokes the <see cref="KeyframeAdded" /> event
/// </summary> /// </summary>
protected virtual void OnKeyframeAdded() /// <param name="keyframe"></param>
protected virtual void OnKeyframeAdded(ILayerPropertyKeyframe keyframe)
{ {
KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); KeyframeAdded?.Invoke(this, new LayerPropertyKeyframeEventArgs(keyframe));
} }
/// <summary> /// <summary>
/// Invokes the <see cref="KeyframeRemoved" /> event /// Invokes the <see cref="KeyframeRemoved" /> event
/// </summary> /// </summary>
protected virtual void OnKeyframeRemoved() /// <param name="keyframe"></param>
protected virtual void OnKeyframeRemoved(ILayerPropertyKeyframe keyframe)
{ {
KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); KeyframeRemoved?.Invoke(this, new LayerPropertyKeyframeEventArgs(keyframe));
} }
#endregion #endregion

View File

@ -12,29 +12,32 @@ using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree; using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using DynamicData;
using DynamicData.Binding;
namespace Artemis.UI.Screens.ProfileEditor.Properties; namespace Artemis.UI.Screens.ProfileEditor.Properties;
public class PropertyGroupViewModel : ViewModelBase, IDisposable public class PropertyGroupViewModel : PropertyViewModelBase, IDisposable
{ {
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private readonly IPropertyInputService _propertyInputService; private readonly IPropertyInputService _propertyInputService;
private bool _hasChildren; private bool _hasChildren;
private bool _isExpanded; private bool _isExpanded;
private bool _isVisible; private bool _isVisible;
private ReadOnlyObservableCollection<ILayerPropertyKeyframe> _keyframes = null!;
private IDisposable _keyframeSubscription = null!;
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService) public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService)
{ {
_layerPropertyVmFactory = layerPropertyVmFactory; _layerPropertyVmFactory = layerPropertyVmFactory;
_propertyInputService = propertyInputService; _propertyInputService = propertyInputService;
Children = new ObservableCollection<ViewModelBase>();
LayerPropertyGroup = layerPropertyGroup; LayerPropertyGroup = layerPropertyGroup;
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this); TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this); TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden; _isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren(); PopulateChildren();
} }
@ -43,14 +46,13 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable
_layerPropertyVmFactory = layerPropertyVmFactory; _layerPropertyVmFactory = layerPropertyVmFactory;
_propertyInputService = propertyInputService; _propertyInputService = propertyInputService;
LayerBrush = layerBrush; LayerBrush = layerBrush;
Children = new ObservableCollection<ViewModelBase>();
LayerPropertyGroup = layerPropertyGroup; LayerPropertyGroup = layerPropertyGroup;
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this); TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this); TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden; _isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren(); PopulateChildren();
} }
@ -59,18 +61,17 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable
_layerPropertyVmFactory = layerPropertyVmFactory; _layerPropertyVmFactory = layerPropertyVmFactory;
_propertyInputService = propertyInputService; _propertyInputService = propertyInputService;
LayerEffect = layerEffect; LayerEffect = layerEffect;
Children = new ObservableCollection<ViewModelBase>();
LayerPropertyGroup = layerPropertyGroup; LayerPropertyGroup = layerPropertyGroup;
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this); TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this); TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden; _isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren(); PopulateChildren();
} }
public ObservableCollection<ViewModelBase> Children { get; } public ObservableCollection<PropertyViewModelBase> Children { get; private set; } = null!;
public LayerPropertyGroup LayerPropertyGroup { get; } public LayerPropertyGroup LayerPropertyGroup { get; }
public BaseLayerBrush? LayerBrush { get; } public BaseLayerBrush? LayerBrush { get; }
public BaseLayerEffect? LayerEffect { get; } public BaseLayerEffect? LayerEffect { get; }
@ -96,13 +97,32 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable
set => RaiseAndSetIfChanged(ref _hasChildren, value); set => RaiseAndSetIfChanged(ref _hasChildren, value);
} }
public override ReadOnlyObservableCollection<ILayerPropertyKeyframe> Keyframes => _keyframes;
public List<ILayerPropertyKeyframe> GetAllKeyframes(bool expandedOnly)
{
List<ILayerPropertyKeyframe> result = new();
if (expandedOnly && !IsExpanded)
return result;
foreach (PropertyViewModelBase child in Children)
{
if (child is PropertyViewModel profileElementPropertyViewModel)
result.AddRange(profileElementPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframes());
else if (child is PropertyGroupViewModel profileElementPropertyGroupViewModel)
result.AddRange(profileElementPropertyGroupViewModel.GetAllKeyframes(expandedOnly));
}
return result;
}
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels(bool expandedOnly) public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels(bool expandedOnly)
{ {
List<ITimelineKeyframeViewModel> result = new(); List<ITimelineKeyframeViewModel> result = new();
if (expandedOnly && !IsExpanded) if (expandedOnly && !IsExpanded)
return result; return result;
foreach (ViewModelBase child in Children) foreach (PropertyViewModelBase child in Children)
{ {
if (child is PropertyViewModel profileElementPropertyViewModel) if (child is PropertyViewModel profileElementPropertyViewModel)
result.AddRange(profileElementPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels()); result.AddRange(profileElementPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels());
@ -115,6 +135,8 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable
private void PopulateChildren() private void PopulateChildren()
{ {
Children = new ObservableCollection<PropertyViewModelBase>();
// Get all properties and property groups and create VMs for them // 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 // 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
// Sorting is done to ensure properties defined by the Core (such as on layers) are always on top. // Sorting is done to ensure properties defined by the Core (such as on layers) are always on top.
@ -138,7 +160,14 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable
} }
} }
HasChildren = Children.Any(i => i is PropertyViewModel {IsVisible: true} || i is PropertyGroupViewModel {IsVisible: true}); HasChildren = Children.Any(i => i is PropertyViewModel {IsVisible: true} or PropertyGroupViewModel {IsVisible: true});
_keyframeSubscription = Children
.ToObservableChangeSet()
.TransformMany(c => c.Keyframes)
.Bind(out ReadOnlyObservableCollection<ILayerPropertyKeyframe> keyframes)
.Subscribe();
_keyframes = keyframes;
} }
private void LayerPropertyGroupOnVisibilityChanged(object? sender, EventArgs e) private void LayerPropertyGroupOnVisibilityChanged(object? sender, EventArgs e)
@ -155,5 +184,7 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable
if (viewModelBase is IDisposable disposable) if (viewModelBase is IDisposable disposable)
disposable.Dispose(); disposable.Dispose();
} }
_keyframeSubscription.Dispose();
} }
} }

View File

@ -1,17 +1,20 @@
using System; using System;
using System.Collections.ObjectModel;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree; using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using DynamicData;
namespace Artemis.UI.Screens.ProfileEditor.Properties; namespace Artemis.UI.Screens.ProfileEditor.Properties;
public class PropertyViewModel : ViewModelBase, IDisposable public class PropertyViewModel : PropertyViewModelBase, IDisposable
{ {
private bool _isExpanded; private bool _isExpanded;
private bool _isHighlighted; private bool _isHighlighted;
private bool _isVisible; private bool _isVisible;
private readonly SourceList<ILayerPropertyKeyframe> _keyframes;
public PropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory) public PropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
{ {
@ -19,13 +22,22 @@ public class PropertyViewModel : ViewModelBase, IDisposable
TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this); TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this);
TimelinePropertyViewModel = propertyVmFactory.TimelinePropertyViewModel(LayerProperty, this); TimelinePropertyViewModel = propertyVmFactory.TimelinePropertyViewModel(LayerProperty, this);
LayerProperty.VisibilityChanged += LayerPropertyOnVisibilityChanged;
_isVisible = !LayerProperty.IsHidden; _isVisible = !LayerProperty.IsHidden;
_keyframes = new SourceList<ILayerPropertyKeyframe>();
_keyframes.Edit(k => k.AddRange(LayerProperty.UntypedKeyframes));
_keyframes.Connect().Bind(out ReadOnlyObservableCollection<ILayerPropertyKeyframe> keyframes).Subscribe();
Keyframes = keyframes;
LayerProperty.VisibilityChanged += LayerPropertyOnVisibilityChanged;
LayerProperty.KeyframeAdded += LayerPropertyOnKeyframeAdded;
LayerProperty.KeyframeRemoved += LayerPropertyOnKeyframeRemoved;
} }
public ILayerProperty LayerProperty { get; } public ILayerProperty LayerProperty { get; }
public ITreePropertyViewModel TreePropertyViewModel { get; } public ITreePropertyViewModel TreePropertyViewModel { get; }
public ITimelinePropertyViewModel TimelinePropertyViewModel { get; } public ITimelinePropertyViewModel TimelinePropertyViewModel { get; }
public override ReadOnlyObservableCollection<ILayerPropertyKeyframe> Keyframes { get; }
public bool IsVisible public bool IsVisible
{ {
@ -50,9 +62,21 @@ public class PropertyViewModel : ViewModelBase, IDisposable
IsVisible = !LayerProperty.IsHidden; IsVisible = !LayerProperty.IsHidden;
} }
private void LayerPropertyOnKeyframeAdded(object? sender, LayerPropertyKeyframeEventArgs e)
{
_keyframes.Add(e.Keyframe);
}
private void LayerPropertyOnKeyframeRemoved(object? sender, LayerPropertyKeyframeEventArgs e)
{
_keyframes.Remove(e.Keyframe);
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
LayerProperty.VisibilityChanged -= LayerPropertyOnVisibilityChanged; LayerProperty.VisibilityChanged -= LayerPropertyOnVisibilityChanged;
LayerProperty.KeyframeAdded -= LayerPropertyOnKeyframeAdded;
LayerProperty.KeyframeRemoved -= LayerPropertyOnKeyframeRemoved;
} }
} }

View File

@ -0,0 +1,10 @@
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.ProfileEditor.Properties;
public abstract class PropertyViewModelBase : ViewModelBase
{
public abstract ReadOnlyObservableCollection<ILayerPropertyKeyframe> Keyframes { get; }
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
using ReactiveUI; using ReactiveUI;
@ -7,6 +8,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public interface ITimelinePropertyViewModel : IReactiveObject public interface ITimelinePropertyViewModel : IReactiveObject
{ {
List<ILayerPropertyKeyframe> GetAllKeyframes();
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);

View File

@ -29,14 +29,14 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_isSelected = profileEditorService.ConnectToKeyframes().ToCollection().Select(keyframes => keyframes.Contains(LayerPropertyKeyframe)).ToProperty(this, vm => vm.IsSelected).DisposeWith(d); _isSelected = profileEditorService.ConnectToKeyframes()
.ToCollection()
.Select(keyframes => keyframes.Contains(LayerPropertyKeyframe))
.ToProperty(this, vm => vm.IsSelected)
.DisposeWith(d);
profileEditorService.ConnectToKeyframes(); profileEditorService.ConnectToKeyframes();
profileEditorService.PixelsPerSecond.Subscribe(p => profileEditorService.PixelsPerSecond.Subscribe(p => _pixelsPerSecond = p).DisposeWith(d);
{ profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
_pixelsPerSecond = p;
profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
}).DisposeWith(d);
this.WhenAnyValue(vm => vm.LayerPropertyKeyframe.Position).Subscribe(_ => Update()).DisposeWith(d); this.WhenAnyValue(vm => vm.LayerPropertyKeyframe.Position).Subscribe(_ => Update()).DisposeWith(d);
}); });
} }

View File

@ -1,48 +1,47 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline; namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineGroupViewModel : ActivatableViewModelBase public class TimelineGroupViewModel : ActivatableViewModelBase
{ {
private ObservableCollection<double>? _keyframePositions; private ReadOnlyObservableCollection<double> _keyframePositions;
private ObservableAsPropertyHelper<int>? _pixelsPerSecond; private int _pixelsPerSecond;
public TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel, IProfileEditorService profileEditorService) public TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel, IProfileEditorService profileEditorService)
{ {
PropertyGroupViewModel = propertyGroupViewModel; PropertyGroupViewModel = propertyGroupViewModel;
_keyframePositions = new ReadOnlyObservableCollection<double>(new ObservableCollection<double>());
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_pixelsPerSecond = profileEditorService.PixelsPerSecond.ToProperty(this, vm => vm.PixelsPerSecond).DisposeWith(d); profileEditorService.PixelsPerSecond.Subscribe(p => _pixelsPerSecond = p).DisposeWith(d);
profileEditorService.PixelsPerSecond.Subscribe(p => UpdateKeyframePositions()).DisposeWith(d);
PropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d); PropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
PropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => UpdateKeyframePositions()).DisposeWith(d); PropertyGroupViewModel.Keyframes
this.WhenAnyValue(vm => vm.PixelsPerSecond).Subscribe(_ => UpdateKeyframePositions()).DisposeWith(d); .ToObservableChangeSet()
UpdateKeyframePositions(); .AutoRefreshOnObservable(_ => profileEditorService.PixelsPerSecond)
.Transform(k => k.Position.TotalSeconds * _pixelsPerSecond, true)
.Bind(out ReadOnlyObservableCollection<double> keyframePositions)
.Subscribe()
.DisposeWith(d);
KeyframePositions = keyframePositions;
}); });
} }
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
public PropertyGroupViewModel PropertyGroupViewModel { get; } public PropertyGroupViewModel PropertyGroupViewModel { get; }
public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null; public ObservableCollection<PropertyViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
public ObservableCollection<double>? KeyframePositions public ReadOnlyObservableCollection<double> KeyframePositions
{ {
get => _keyframePositions; get => _keyframePositions;
set => RaiseAndSetIfChanged(ref _keyframePositions, value); set => RaiseAndSetIfChanged(ref _keyframePositions, value);
} }
private void UpdateKeyframePositions()
{
KeyframePositions = new ObservableCollection<double>(PropertyGroupViewModel
.GetAllKeyframeViewModels(false)
.Select(p => p.Position.TotalSeconds * PixelsPerSecond));
}
} }

View File

@ -8,6 +8,7 @@ using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using DynamicData;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline; namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
@ -15,60 +16,61 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelinePropertyViewModel public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelinePropertyViewModel
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly SourceList<LayerPropertyKeyframe<T>> _keyframes;
private ObservableAsPropertyHelper<bool>? _keyframesEnabled;
public TimelinePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService) public TimelinePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
LayerProperty = layerProperty; LayerProperty = layerProperty;
PropertyViewModel = propertyViewModel; PropertyViewModel = propertyViewModel;
KeyframeViewModels = new ObservableCollection<TimelineKeyframeViewModel<T>>();
_keyframes = new SourceList<LayerPropertyKeyframe<T>>();
_keyframes.Connect()
// Only show items when keyframes are enabled
.Filter(this.WhenAnyValue(vm => vm.KeyframesEnabled).Select(b => new Func<LayerPropertyKeyframe<T>, bool>(_ => b)))
.Transform(k => new TimelineKeyframeViewModel<T>(k, _profileEditorService))
.Bind(out ReadOnlyObservableCollection<TimelineKeyframeViewModel<T>> keyframeViewModels)
.Subscribe();
KeyframeViewModels = keyframeViewModels;
this.WhenActivated(d => this.WhenActivated(d =>
{ {
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.KeyframesToggled += x, x => LayerProperty.KeyframesToggled -= x) _keyframesEnabled = LayerProperty.WhenAnyValue(p => p.KeyframesEnabled).ToProperty(this, vm => vm.KeyframesEnabled).DisposeWith(d);
.Subscribe(_ => UpdateKeyframes()) Observable.FromEventPattern<LayerPropertyKeyframeEventArgs>(x => LayerProperty.KeyframeAdded += x, x => LayerProperty.KeyframeAdded -= x)
.Subscribe(e => _keyframes.Add((LayerPropertyKeyframe<T>) e.EventArgs.Keyframe))
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.KeyframeAdded += x, x => LayerProperty.KeyframeAdded -= x) Observable.FromEventPattern<LayerPropertyKeyframeEventArgs>(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x)
.Subscribe(_ => UpdateKeyframes()) .Subscribe(e => _keyframes.Remove((LayerPropertyKeyframe<T>) e.EventArgs.Keyframe))
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x)
.Subscribe(_ => UpdateKeyframes())
.DisposeWith(d); .DisposeWith(d);
UpdateKeyframes(); _keyframes.Edit(k =>
{
k.Clear();
k.AddRange(LayerProperty.Keyframes);
});
}); });
} }
public LayerProperty<T> LayerProperty { get; } public LayerProperty<T> LayerProperty { get; }
public PropertyViewModel PropertyViewModel { get; } public PropertyViewModel PropertyViewModel { get; }
public ObservableCollection<TimelineKeyframeViewModel<T>> KeyframeViewModels { get; } public ReadOnlyObservableCollection<TimelineKeyframeViewModel<T>> KeyframeViewModels { get; }
public bool KeyframesEnabled => _keyframesEnabled?.Value ?? false;
private void UpdateKeyframes() 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) foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in KeyframeViewModels)
timelineKeyframeViewModel.Update(); timelineKeyframeViewModel.Update();
} }
#region Implementation of ITimelinePropertyViewModel #region Implementation of ITimelinePropertyViewModel
public List<ILayerPropertyKeyframe> GetAllKeyframes()
{
return LayerProperty.KeyframesEnabled ? new List<ILayerPropertyKeyframe>(LayerProperty.Keyframes) : new List<ILayerPropertyKeyframe>();
}
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels() public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels()
{ {
return KeyframeViewModels.Cast<ITimelineKeyframeViewModel>().ToList(); return KeyframeViewModels.Cast<ITimelineKeyframeViewModel>().ToList();
@ -79,7 +81,6 @@ public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelineP
start ??= TimeSpan.Zero; start ??= TimeSpan.Zero;
end ??= TimeSpan.MaxValue; end ??= TimeSpan.MaxValue;
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList(); List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList();
foreach (LayerPropertyKeyframe<T> keyframe in toShift) foreach (LayerPropertyKeyframe<T> keyframe in toShift)
LayerProperty.RemoveKeyframe(keyframe); LayerProperty.RemoveKeyframe(keyframe);

View File

@ -56,7 +56,7 @@ public class TreeGroupViewModel : ActivatableViewModelBase
public BaseLayerBrush? LayerBrush => PropertyGroupViewModel.LayerBrush; public BaseLayerBrush? LayerBrush => PropertyGroupViewModel.LayerBrush;
public BaseLayerEffect? LayerEffect => PropertyGroupViewModel.LayerEffect; public BaseLayerEffect? LayerEffect => PropertyGroupViewModel.LayerEffect;
public LayerPropertyGroupType GroupType { get; private set; } public LayerPropertyGroupType GroupType { get; private set; }
public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null; public ObservableCollection<PropertyViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
public ReactiveCommand<Unit, Unit> OpenBrushSettings { get; } public ReactiveCommand<Unit, Unit> OpenBrushSettings { get; }
public ReactiveCommand<Unit, Unit> OpenEffectSettings { get; } public ReactiveCommand<Unit, Unit> OpenEffectSettings { get; }