mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile editor service - Make keyframe selection an editor concern
This commit is contained in:
parent
98180df5f2
commit
6f269af8d4
@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
@ -52,6 +54,11 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string Path { get; }
|
string Path { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read-only list of all the keyframes on this layer property
|
||||||
|
/// </summary>
|
||||||
|
ReadOnlyCollection<ILayerPropertyKeyframe> UntypedKeyframes { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of the property
|
/// Gets the type of the property
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -258,6 +258,9 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyCollection<LayerPropertyKeyframe<T>> Keyframes { get; }
|
public ReadOnlyCollection<LayerPropertyKeyframe<T>> Keyframes { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ReadOnlyCollection<ILayerPropertyKeyframe> UntypedKeyframes => new(Keyframes.Cast<ILayerPropertyKeyframe>().ToList());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current keyframe in the timeline according to the current progress
|
/// Gets the current keyframe in the timeline according to the current progress
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -201,8 +201,8 @@
|
|||||||
},
|
},
|
||||||
"DynamicData": {
|
"DynamicData": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "7.4.3",
|
"resolved": "7.4.9",
|
||||||
"contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==",
|
"contentHash": "bzw9n1WgfflkhsScIaC7tzPlKFTJkfWVTOg2pjJjqzVqxF63ztaJ7HH306Iyx6bs+pC77fQbtE53UoPTpt+8dQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Reactive": "5.0.0"
|
"System.Reactive": "5.0.0"
|
||||||
}
|
}
|
||||||
@ -1752,6 +1752,7 @@
|
|||||||
"Avalonia.Diagnostics": "0.10.11",
|
"Avalonia.Diagnostics": "0.10.11",
|
||||||
"Avalonia.ReactiveUI": "0.10.11",
|
"Avalonia.ReactiveUI": "0.10.11",
|
||||||
"Avalonia.Svg.Skia": "0.10.11",
|
"Avalonia.Svg.Skia": "0.10.11",
|
||||||
|
"DynamicData": "7.4.9",
|
||||||
"FluentAvaloniaUI": "1.1.8",
|
"FluentAvaloniaUI": "1.1.8",
|
||||||
"Flurl.Http": "3.2.0",
|
"Flurl.Http": "3.2.0",
|
||||||
"Live.Avalonia": "1.3.1",
|
"Live.Avalonia": "1.3.1",
|
||||||
@ -1774,6 +1775,7 @@
|
|||||||
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
||||||
|
"DynamicData": "7.4.9",
|
||||||
"FluentAvaloniaUI": "1.1.8",
|
"FluentAvaloniaUI": "1.1.8",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
"RGB.NET.Core": "1.0.0-prerelease7",
|
"RGB.NET.Core": "1.0.0-prerelease7",
|
||||||
|
|||||||
@ -201,8 +201,8 @@
|
|||||||
},
|
},
|
||||||
"DynamicData": {
|
"DynamicData": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "7.4.3",
|
"resolved": "7.4.9",
|
||||||
"contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==",
|
"contentHash": "bzw9n1WgfflkhsScIaC7tzPlKFTJkfWVTOg2pjJjqzVqxF63ztaJ7HH306Iyx6bs+pC77fQbtE53UoPTpt+8dQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Reactive": "5.0.0"
|
"System.Reactive": "5.0.0"
|
||||||
}
|
}
|
||||||
@ -1752,6 +1752,7 @@
|
|||||||
"Avalonia.Diagnostics": "0.10.11",
|
"Avalonia.Diagnostics": "0.10.11",
|
||||||
"Avalonia.ReactiveUI": "0.10.11",
|
"Avalonia.ReactiveUI": "0.10.11",
|
||||||
"Avalonia.Svg.Skia": "0.10.11",
|
"Avalonia.Svg.Skia": "0.10.11",
|
||||||
|
"DynamicData": "7.4.9",
|
||||||
"FluentAvaloniaUI": "1.1.8",
|
"FluentAvaloniaUI": "1.1.8",
|
||||||
"Flurl.Http": "3.2.0",
|
"Flurl.Http": "3.2.0",
|
||||||
"Live.Avalonia": "1.3.1",
|
"Live.Avalonia": "1.3.1",
|
||||||
@ -1774,6 +1775,7 @@
|
|||||||
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
||||||
|
"DynamicData": "7.4.9",
|
||||||
"FluentAvaloniaUI": "1.1.8",
|
"FluentAvaloniaUI": "1.1.8",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
"RGB.NET.Core": "1.0.0-prerelease7",
|
"RGB.NET.Core": "1.0.0-prerelease7",
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.11.5" />
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.11.5" />
|
||||||
<PackageReference Include="Avalonia.Xaml.Interactions" Version="0.10.11.5" />
|
<PackageReference Include="Avalonia.Xaml.Interactions" Version="0.10.11.5" />
|
||||||
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="0.10.11.5" />
|
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="0.10.11.5" />
|
||||||
|
<PackageReference Include="DynamicData" Version="7.4.9" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.1.8" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.1.8" />
|
||||||
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
||||||
<PackageReference Include="ReactiveUI" Version="16.3.10" />
|
<PackageReference Include="ReactiveUI" Version="16.3.10" />
|
||||||
|
|||||||
@ -6,8 +6,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Controls
|
namespace Artemis.UI.Shared.Controls;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -35,7 +35,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
/// Defines the <see cref="BorderRadius" /> property.
|
/// Defines the <see cref="BorderRadius" /> property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly StyledProperty<double> BorderRadiusProperty =
|
public static readonly StyledProperty<double> BorderRadiusProperty =
|
||||||
AvaloniaProperty.Register<SelectionRectangle, double>(nameof(BorderRadius), 0);
|
AvaloniaProperty.Register<SelectionRectangle, double>(nameof(BorderRadius));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the <see cref="InputElement" /> property.
|
/// Defines the <see cref="InputElement" /> property.
|
||||||
@ -43,11 +43,18 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
public static readonly StyledProperty<IControl?> InputElementProperty =
|
public static readonly StyledProperty<IControl?> InputElementProperty =
|
||||||
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
|
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
|
||||||
|
|
||||||
private Rect? _displayRect;
|
/// <summary>
|
||||||
|
/// Defines the read-only <see cref="IsSelecting" /> property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DirectProperty<SelectionRectangle, bool> IsSelectingProperty = AvaloniaProperty.RegisterDirect<SelectionRectangle, bool>(nameof(IsSelecting), o => o.IsSelecting);
|
||||||
|
|
||||||
private Rect? _absoluteRect;
|
private Rect? _absoluteRect;
|
||||||
|
private Point _absoluteStartPosition;
|
||||||
|
|
||||||
|
private Rect? _displayRect;
|
||||||
|
private bool _isSelecting;
|
||||||
private IControl? _oldInputElement;
|
private IControl? _oldInputElement;
|
||||||
private Point _startPosition;
|
private Point _startPosition;
|
||||||
private Point _absoluteStartPosition;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public SelectionRectangle()
|
public SelectionRectangle()
|
||||||
@ -101,6 +108,15 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
set => SetValue(InputElementProperty, value);
|
set => SetValue(InputElementProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the selection rectangle is currently performing a selection.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSelecting
|
||||||
|
{
|
||||||
|
get => _isSelecting;
|
||||||
|
private set => SetAndRaise(IsSelectingProperty, ref _isSelecting, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the selection rect is being updated, indicating the user is dragging.
|
/// Occurs when the selection rect is being updated, indicating the user is dragging.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -165,6 +181,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
|
|
||||||
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
|
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
|
IsSelecting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParentOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void ParentOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
@ -182,6 +199,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
|
|
||||||
_displayRect = null;
|
_displayRect = null;
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
|
IsSelecting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SubscribeToInputElement()
|
private void SubscribeToInputElement()
|
||||||
@ -235,4 +253,3 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
using DynamicData;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.ProfileEditor;
|
namespace Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
|
||||||
@ -41,6 +42,12 @@ public interface IProfileEditorService : IArtemisSharedUIService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IObservable<int> PixelsPerSecond { get; }
|
IObservable<int> PixelsPerSecond { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connect to the observable list of keyframes and observe any changes starting with the list's initial items.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An observable which emits the change set.</returns>
|
||||||
|
IObservable<IChangeSet<ILayerPropertyKeyframe>> ConnectToKeyframes();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the selected profile by its <see cref="Core.ProfileConfiguration" />.
|
/// Changes the selected profile by its <see cref="Core.ProfileConfiguration" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -65,6 +72,21 @@ public interface IProfileEditorService : IArtemisSharedUIService
|
|||||||
/// <param name="pixelsPerSecond">The new pixels per second.</param>
|
/// <param name="pixelsPerSecond">The new pixels per second.</param>
|
||||||
void ChangePixelsPerSecond(int pixelsPerSecond);
|
void ChangePixelsPerSecond(int pixelsPerSecond);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects the provided keyframe.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyframe">The keyframe to select.</param>
|
||||||
|
/// <param name="expand">If <see langword="true"/> expands the current selection; otherwise replaces it with only the provided <paramref name="keyframe"/>.</param>
|
||||||
|
/// <param name="toggle">If <see langword="true"/> toggles the selection and only for the provided <paramref name="keyframe"/>.</param>
|
||||||
|
void SelectKeyframe(ILayerPropertyKeyframe? keyframe, bool expand, bool toggle);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects the provided keyframes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyframes">The keyframes to select.</param>
|
||||||
|
/// <param name="expand">If <see langword="true"/> expands the current selection; otherwise replaces it with only the provided <paramref name="keyframes"/>.</param>
|
||||||
|
void SelectKeyframes(IEnumerable<ILayerPropertyKeyframe> keyframes, bool expand);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Snaps the given time to the closest relevant element in the timeline, this can be the cursor, a keyframe or a
|
/// Snaps the given time to the closest relevant element in the timeline, this can be the cursor, a keyframe or a
|
||||||
/// segment end.
|
/// segment end.
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
@ -7,6 +8,7 @@ using System.Threading.Tasks;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
using DynamicData;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.ProfileEditor;
|
namespace Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
@ -20,6 +22,8 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
private readonly BehaviorSubject<bool> _playingSubject = new(false);
|
private readonly BehaviorSubject<bool> _playingSubject = new(false);
|
||||||
private readonly BehaviorSubject<bool> _suspendedEditingSubject = new(false);
|
private readonly BehaviorSubject<bool> _suspendedEditingSubject = new(false);
|
||||||
private readonly BehaviorSubject<int> _pixelsPerSecondSubject = new(120);
|
private readonly BehaviorSubject<int> _pixelsPerSecondSubject = new(120);
|
||||||
|
private readonly SourceList<ILayerPropertyKeyframe> _selectedKeyframes = new();
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
private readonly IModuleService _moduleService;
|
private readonly IModuleService _moduleService;
|
||||||
@ -60,6 +64,7 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
public IObservable<bool> Playing { get; }
|
public IObservable<bool> Playing { get; }
|
||||||
public IObservable<bool> SuspendedEditing { get; }
|
public IObservable<bool> SuspendedEditing { get; }
|
||||||
public IObservable<int> PixelsPerSecond { get; }
|
public IObservable<int> PixelsPerSecond { get; }
|
||||||
|
public IObservable<IChangeSet<ILayerPropertyKeyframe>> ConnectToKeyframes() => _selectedKeyframes.Connect();
|
||||||
|
|
||||||
public void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration)
|
public void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration)
|
||||||
{
|
{
|
||||||
@ -97,11 +102,13 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
_moduleService.SetActivationOverride(null);
|
_moduleService.SetActivationOverride(null);
|
||||||
_profileService.RenderForEditor = false;
|
_profileService.RenderForEditor = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_profileConfigurationSubject.OnNext(profileConfiguration);
|
_profileConfigurationSubject.OnNext(profileConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement)
|
public void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement)
|
||||||
{
|
{
|
||||||
|
_selectedKeyframes.Clear();
|
||||||
_profileElementSubject.OnNext(renderProfileElement);
|
_profileElementSubject.OnNext(renderProfileElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +118,57 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
_timeSubject.OnNext(time);
|
_timeSubject.OnNext(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SelectKeyframe(ILayerPropertyKeyframe? keyframe, bool expand, bool toggle)
|
||||||
|
{
|
||||||
|
if (keyframe == null)
|
||||||
|
{
|
||||||
|
if (!expand)
|
||||||
|
_selectedKeyframes.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toggle)
|
||||||
|
{
|
||||||
|
// Toggle only the clicked keyframe, leave others alone
|
||||||
|
if (_selectedKeyframes.Items.Contains(keyframe))
|
||||||
|
_selectedKeyframes.Remove(keyframe);
|
||||||
|
else
|
||||||
|
_selectedKeyframes.Add(keyframe);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (expand)
|
||||||
|
{
|
||||||
|
_selectedKeyframes.Add(keyframe);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_selectedKeyframes.Edit(l =>
|
||||||
|
{
|
||||||
|
l.Clear();
|
||||||
|
l.Add(keyframe);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectKeyframes(IEnumerable<ILayerPropertyKeyframe> keyframes, bool expand)
|
||||||
|
{
|
||||||
|
if (expand)
|
||||||
|
{
|
||||||
|
List<ILayerPropertyKeyframe> toAdd = keyframes.Except(_selectedKeyframes.Items).ToList();
|
||||||
|
_selectedKeyframes.AddRange(toAdd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_selectedKeyframes.Edit(l =>
|
||||||
|
{
|
||||||
|
l.Clear();
|
||||||
|
l.AddRange(keyframes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? snapTimes = null)
|
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? snapTimes = null)
|
||||||
{
|
{
|
||||||
RenderProfileElement? profileElement = _profileElementSubject.Value;
|
RenderProfileElement? profileElement = _profileElementSubject.Value;
|
||||||
|
|||||||
@ -70,6 +70,15 @@
|
|||||||
"Avalonia": "0.10.11"
|
"Avalonia": "0.10.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DynamicData": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[7.4.9, )",
|
||||||
|
"resolved": "7.4.9",
|
||||||
|
"contentHash": "bzw9n1WgfflkhsScIaC7tzPlKFTJkfWVTOg2pjJjqzVqxF63ztaJ7HH306Iyx6bs+pC77fQbtE53UoPTpt+8dQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"System.Reactive": "5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"FluentAvaloniaUI": {
|
"FluentAvaloniaUI": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[1.1.8, )",
|
"requested": "[1.1.8, )",
|
||||||
@ -240,14 +249,6 @@
|
|||||||
"System.Xml.XmlDocument": "4.3.0"
|
"System.Xml.XmlDocument": "4.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DynamicData": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "7.4.3",
|
|
||||||
"contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==",
|
|
||||||
"dependencies": {
|
|
||||||
"System.Reactive": "5.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"EmbedIO": {
|
"EmbedIO": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.4.3",
|
"resolved": "3.4.3",
|
||||||
|
|||||||
@ -217,8 +217,8 @@
|
|||||||
},
|
},
|
||||||
"DynamicData": {
|
"DynamicData": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "7.4.3",
|
"resolved": "7.4.9",
|
||||||
"contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==",
|
"contentHash": "bzw9n1WgfflkhsScIaC7tzPlKFTJkfWVTOg2pjJjqzVqxF63ztaJ7HH306Iyx6bs+pC77fQbtE53UoPTpt+8dQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Reactive": "5.0.0"
|
"System.Reactive": "5.0.0"
|
||||||
}
|
}
|
||||||
@ -1768,6 +1768,7 @@
|
|||||||
"Avalonia.Diagnostics": "0.10.11",
|
"Avalonia.Diagnostics": "0.10.11",
|
||||||
"Avalonia.ReactiveUI": "0.10.11",
|
"Avalonia.ReactiveUI": "0.10.11",
|
||||||
"Avalonia.Svg.Skia": "0.10.11",
|
"Avalonia.Svg.Skia": "0.10.11",
|
||||||
|
"DynamicData": "7.4.9",
|
||||||
"FluentAvaloniaUI": "1.1.8",
|
"FluentAvaloniaUI": "1.1.8",
|
||||||
"Flurl.Http": "3.2.0",
|
"Flurl.Http": "3.2.0",
|
||||||
"Live.Avalonia": "1.3.1",
|
"Live.Avalonia": "1.3.1",
|
||||||
@ -1790,6 +1791,7 @@
|
|||||||
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
||||||
|
"DynamicData": "7.4.9",
|
||||||
"FluentAvaloniaUI": "1.1.8",
|
"FluentAvaloniaUI": "1.1.8",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
"RGB.NET.Core": "1.0.0-prerelease7",
|
"RGB.NET.Core": "1.0.0-prerelease7",
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.11" />
|
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.11" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.11" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.11" />
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.11" />
|
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.11" />
|
||||||
|
<PackageReference Include="DynamicData" Version="7.4.9" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.1.8" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.1.8" />
|
||||||
<PackageReference Include="Flurl.Http" Version="3.2.0" />
|
<PackageReference Include="Flurl.Http" Version="3.2.0" />
|
||||||
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
|
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
|
||||||
|
|||||||
@ -5,12 +5,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
|||||||
|
|
||||||
public interface ITimelineKeyframeViewModel
|
public interface ITimelineKeyframeViewModel
|
||||||
{
|
{
|
||||||
bool IsSelected { get; set; }
|
bool IsSelected { get; }
|
||||||
TimeSpan Position { get; }
|
TimeSpan Position { get; }
|
||||||
ILayerPropertyKeyframe Keyframe { get; }
|
ILayerPropertyKeyframe Keyframe { get; }
|
||||||
|
|
||||||
#region Movement
|
#region Movement
|
||||||
|
|
||||||
|
void Select(bool expand, bool toggle);
|
||||||
|
// void StartMovement();
|
||||||
|
// void FinishMovement();
|
||||||
|
|
||||||
void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
||||||
void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
||||||
void UpdatePosition(TimeSpan position);
|
void UpdatePosition(TimeSpan position);
|
||||||
|
|||||||
@ -13,7 +13,10 @@
|
|||||||
Height="10"
|
Height="10"
|
||||||
Margin="-5,0,0,0"
|
Margin="-5,0,0,0"
|
||||||
ToolTip.Tip="{Binding Timestamp}"
|
ToolTip.Tip="{Binding Timestamp}"
|
||||||
Classes.selected="{Binding IsSelected}">
|
Classes.selected="{Binding IsSelected}"
|
||||||
|
PointerPressed="InputElement_OnPointerPressed"
|
||||||
|
PointerReleased="InputElement_OnPointerReleased"
|
||||||
|
PointerMoved="InputElement_OnPointerMoved">
|
||||||
<Ellipse.Styles>
|
<Ellipse.Styles>
|
||||||
<Style Selector="Ellipse">
|
<Style Selector="Ellipse">
|
||||||
<Setter Property="StrokeThickness" Value="0" />
|
<Setter Property="StrokeThickness" Value="0" />
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
@ -5,6 +6,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
|||||||
|
|
||||||
public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewModel>
|
public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewModel>
|
||||||
{
|
{
|
||||||
|
private bool _moved;
|
||||||
|
|
||||||
public TimelineKeyframeView()
|
public TimelineKeyframeView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -14,4 +17,30 @@ public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewMod
|
|||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Pointer.Capture((IInputElement?) sender);
|
||||||
|
e.Handled = true;
|
||||||
|
|
||||||
|
_moved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputElement_OnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Pointer.Captured != sender)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_moved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Pointer.Capture(null);
|
||||||
|
e.Handled = true;
|
||||||
|
|
||||||
|
// Select the keyframe if the user didn't move
|
||||||
|
if (!_moved)
|
||||||
|
ViewModel?.Select(e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,41 +1,46 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
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;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Disposable = System.Reactive.Disposables.Disposable;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
|
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
|
||||||
{
|
{
|
||||||
private bool _isSelected;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private string _timestamp;
|
|
||||||
private double _x;
|
private double _x;
|
||||||
|
private string _timestamp;
|
||||||
|
private ObservableAsPropertyHelper<bool>? _isSelected;
|
||||||
|
|
||||||
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
|
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
|
||||||
{
|
{
|
||||||
IProfileEditorService profileEditorService1 = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_timestamp = "0.000";
|
_timestamp = "0.000";
|
||||||
LayerPropertyKeyframe = layerPropertyKeyframe;
|
LayerPropertyKeyframe = layerPropertyKeyframe;
|
||||||
EasingViewModels = new ObservableCollection<TimelineEasingViewModel>();
|
EasingViewModels = new ObservableCollection<TimelineEasingViewModel>();
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
profileEditorService1.PixelsPerSecond.Subscribe(p =>
|
profileEditorService.PixelsPerSecond.Subscribe(p =>
|
||||||
{
|
{
|
||||||
_pixelsPerSecond = p;
|
_pixelsPerSecond = p;
|
||||||
profileEditorService1.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
|
profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
|
||||||
Disposable.Create(() =>
|
System.Reactive.Disposables.Disposable.Create(() =>
|
||||||
{
|
{
|
||||||
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
||||||
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
|
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
|
||||||
}).DisposeWith(d);
|
}).DisposeWith(d);
|
||||||
}).DisposeWith(d);
|
}).DisposeWith(d);
|
||||||
|
|
||||||
|
_isSelected = profileEditorService.ConnectToKeyframes().ToCollection().Select(keyframes => keyframes.Contains(LayerPropertyKeyframe)).ToProperty(this, vm => vm.IsSelected).DisposeWith(d);
|
||||||
|
profileEditorService.ConnectToKeyframes();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,21 +59,22 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
|
|||||||
set => this.RaiseAndSetIfChanged(ref _timestamp, value);
|
set => this.RaiseAndSetIfChanged(ref _timestamp, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSelected => _isSelected?.Value ?? false;
|
||||||
|
public TimeSpan Position => LayerPropertyKeyframe.Position;
|
||||||
|
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
|
X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
|
||||||
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
|
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSelected
|
/// <inheritdoc />
|
||||||
|
public void Select(bool expand, bool toggle)
|
||||||
{
|
{
|
||||||
get => _isSelected;
|
_profileEditorService.SelectKeyframe(Keyframe, expand, toggle);
|
||||||
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan Position => LayerPropertyKeyframe.Position;
|
|
||||||
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
|
|
||||||
|
|
||||||
#region Context menu actions
|
#region Context menu actions
|
||||||
|
|
||||||
public void Delete(bool save = true)
|
public void Delete(bool save = true)
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<x:Double x:Key="RailsHeight">28</x:Double>
|
<x:Double x:Key="RailsHeight">28</x:Double>
|
||||||
<x:Double x:Key="RailsBorderHeight">29</x:Double>
|
<x:Double x:Key="RailsBorderHeight">29</x:Double>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Grid Background="Transparent" PointerMoved="InputElement_OnPointerMoved" PointerReleased="InputElement_OnPointerReleased">
|
<Grid Background="Transparent" PointerReleased="InputElement_OnPointerReleased">
|
||||||
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
|
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</TreeDataTemplate>
|
</TreeDataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
<controls:SelectionRectangle InputElement="{Binding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished"></controls:SelectionRectangle>
|
<controls:SelectionRectangle Name="SelectionRectangle" InputElement="{Binding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished"></controls:SelectionRectangle>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Artemis.UI.Shared.Controls;
|
||||||
using Artemis.UI.Shared.Events;
|
using Artemis.UI.Shared.Events;
|
||||||
using Artemis.UI.Shared.Extensions;
|
using Artemis.UI.Shared.Extensions;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
@ -11,11 +13,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
|||||||
|
|
||||||
public class TimelineView : ReactiveUserControl<TimelineViewModel>
|
public class TimelineView : ReactiveUserControl<TimelineViewModel>
|
||||||
{
|
{
|
||||||
private bool _draggedCursor;
|
private readonly SelectionRectangle _selectionRectangle;
|
||||||
|
|
||||||
public TimelineView()
|
public TimelineView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_selectionRectangle = this.Get<SelectionRectangle>("SelectionRectangle");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
@ -34,35 +37,14 @@ public class TimelineView : ReactiveUserControl<TimelineViewModel>
|
|||||||
return e.AbsoluteRectangle.Intersects(hitTestRect);
|
return e.AbsoluteRectangle.Intersects(hitTestRect);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
ViewModel.SelectKeyframes(keyframeViews.Where(kv => kv.ViewModel != null).Select(kv => kv.ViewModel!).ToList(), e.KeyModifiers.HasFlag(KeyModifiers.Shift));
|
ViewModel.SelectKeyframes(keyframeViews.Where(kv => kv.ViewModel != null).Select(kv => kv.ViewModel!), e.KeyModifiers.HasFlag(KeyModifiers.Shift));
|
||||||
}
|
|
||||||
|
|
||||||
private void InputElement_OnPointerMoved(object? sender, PointerEventArgs e)
|
|
||||||
{
|
|
||||||
if (_draggedCursor)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_draggedCursor = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
if (ViewModel == null)
|
if (_selectionRectangle.IsSelecting)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_draggedCursor)
|
ViewModel?.SelectKeyframes(new List<ITimelineKeyframeViewModel>(), false);
|
||||||
{
|
|
||||||
_draggedCursor = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point position = e.GetPosition(VisualRoot);
|
|
||||||
TimelineKeyframeView? keyframeView = this.GetVisualChildrenOfType<TimelineKeyframeView>().Where(k =>
|
|
||||||
{
|
|
||||||
Rect hitTestRect = k.TransformedBounds != null ? k.TransformedBounds.Value.Bounds.TransformToAABB(k.TransformedBounds.Value.Transform) : Rect.Empty;
|
|
||||||
return hitTestRect.Contains(position);
|
|
||||||
}).FirstOrDefault(kv => kv.ViewModel != null);
|
|
||||||
|
|
||||||
ViewModel.SelectKeyframe(keyframeView?.ViewModel, e.KeyModifiers.HasFlag(KeyModifiers.Shift), false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,69 +43,8 @@ public class TimelineViewModel : ActivatableViewModelBase
|
|||||||
return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
|
return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectKeyframes(List<ITimelineKeyframeViewModel> keyframes, bool expand)
|
public void SelectKeyframes(IEnumerable<ITimelineKeyframeViewModel> keyframes, bool expand)
|
||||||
{
|
{
|
||||||
List<ITimelineKeyframeViewModel> expandedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).ToList();
|
_profileEditorService.SelectKeyframes(keyframes.Select(k => k.Keyframe), expand);
|
||||||
List<ITimelineKeyframeViewModel> collapsedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(false)).Except(expandedKeyframes).ToList();
|
|
||||||
|
|
||||||
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in collapsedKeyframes)
|
|
||||||
timelineKeyframeViewModel.IsSelected = false;
|
|
||||||
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in expandedKeyframes)
|
|
||||||
{
|
|
||||||
if (timelineKeyframeViewModel.IsSelected && expand)
|
|
||||||
continue;
|
|
||||||
timelineKeyframeViewModel.IsSelected = keyframes.Contains(timelineKeyframeViewModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SelectKeyframe(ITimelineKeyframeViewModel? clicked, bool selectBetween, bool toggle)
|
|
||||||
{
|
|
||||||
List<ITimelineKeyframeViewModel> expandedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).ToList();
|
|
||||||
List<ITimelineKeyframeViewModel> collapsedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(false)).Except(expandedKeyframes).ToList();
|
|
||||||
|
|
||||||
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in collapsedKeyframes)
|
|
||||||
timelineKeyframeViewModel.IsSelected = false;
|
|
||||||
|
|
||||||
if (clicked == null)
|
|
||||||
{
|
|
||||||
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in expandedKeyframes)
|
|
||||||
timelineKeyframeViewModel.IsSelected = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectBetween)
|
|
||||||
{
|
|
||||||
int selectedIndex = expandedKeyframes.FindIndex(k => k.IsSelected);
|
|
||||||
// If nothing is selected, select only the clicked
|
|
||||||
if (selectedIndex == -1)
|
|
||||||
{
|
|
||||||
clicked.IsSelected = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes)
|
|
||||||
keyframeViewModel.IsSelected = false;
|
|
||||||
|
|
||||||
int clickedIndex = expandedKeyframes.IndexOf(clicked);
|
|
||||||
if (clickedIndex < selectedIndex)
|
|
||||||
foreach (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1))
|
|
||||||
keyframeViewModel.IsSelected = true;
|
|
||||||
else
|
|
||||||
foreach (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes.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 (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes)
|
|
||||||
keyframeViewModel.IsSelected = false;
|
|
||||||
clicked.IsSelected = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,6 +74,15 @@
|
|||||||
"Svg.Skia": "0.5.11"
|
"Svg.Skia": "0.5.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DynamicData": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[7.4.9, )",
|
||||||
|
"resolved": "7.4.9",
|
||||||
|
"contentHash": "bzw9n1WgfflkhsScIaC7tzPlKFTJkfWVTOg2pjJjqzVqxF63ztaJ7HH306Iyx6bs+pC77fQbtE53UoPTpt+8dQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"System.Reactive": "5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"FluentAvaloniaUI": {
|
"FluentAvaloniaUI": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[1.1.8, )",
|
"requested": "[1.1.8, )",
|
||||||
@ -287,14 +296,6 @@
|
|||||||
"System.Xml.XmlDocument": "4.3.0"
|
"System.Xml.XmlDocument": "4.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DynamicData": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "7.4.3",
|
|
||||||
"contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==",
|
|
||||||
"dependencies": {
|
|
||||||
"System.Reactive": "5.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"EmbedIO": {
|
"EmbedIO": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.4.3",
|
"resolved": "3.4.3",
|
||||||
@ -1762,6 +1763,7 @@
|
|||||||
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
"Avalonia.Xaml.Behaviors": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
"Avalonia.Xaml.Interactions": "0.10.11.5",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
"Avalonia.Xaml.Interactivity": "0.10.11.5",
|
||||||
|
"DynamicData": "7.4.9",
|
||||||
"FluentAvaloniaUI": "1.1.8",
|
"FluentAvaloniaUI": "1.1.8",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
"RGB.NET.Core": "1.0.0-prerelease7",
|
"RGB.NET.Core": "1.0.0-prerelease7",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user