mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-01-01 02:03:32 +00:00
Profile editor - Ported keyframe display and selection
This commit is contained in:
parent
66a2e51979
commit
98180df5f2
@ -73,7 +73,4 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Extensions\" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@ -44,9 +44,11 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
|
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
|
||||||
|
|
||||||
private Rect? _displayRect;
|
private Rect? _displayRect;
|
||||||
|
private Rect? _absoluteRect;
|
||||||
private IControl? _oldInputElement;
|
private IControl? _oldInputElement;
|
||||||
private Point _startPosition;
|
private Point _startPosition;
|
||||||
|
private Point _absoluteStartPosition;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public SelectionRectangle()
|
public SelectionRectangle()
|
||||||
{
|
{
|
||||||
@ -140,6 +142,7 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
e.Pointer.Capture(this);
|
e.Pointer.Capture(this);
|
||||||
|
|
||||||
_startPosition = e.GetPosition(Parent);
|
_startPosition = e.GetPosition(Parent);
|
||||||
|
_absoluteStartPosition = e.GetPosition(VisualRoot);
|
||||||
_displayRect = null;
|
_displayRect = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,11 +152,18 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Point currentPosition = e.GetPosition(Parent);
|
Point currentPosition = e.GetPosition(Parent);
|
||||||
|
Point absoluteCurrentPosition = e.GetPosition(VisualRoot);
|
||||||
|
|
||||||
_displayRect = new Rect(
|
_displayRect = new Rect(
|
||||||
new Point(Math.Min(_startPosition.X, currentPosition.X), Math.Min(_startPosition.Y, currentPosition.Y)),
|
new Point(Math.Min(_startPosition.X, currentPosition.X), Math.Min(_startPosition.Y, currentPosition.Y)),
|
||||||
new Point(Math.Max(_startPosition.X, currentPosition.X), Math.Max(_startPosition.Y, currentPosition.Y))
|
new Point(Math.Max(_startPosition.X, currentPosition.X), Math.Max(_startPosition.Y, currentPosition.Y))
|
||||||
);
|
);
|
||||||
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, e.KeyModifiers));
|
_absoluteRect = new Rect(
|
||||||
|
new Point(Math.Min(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Min(_absoluteStartPosition.Y, absoluteCurrentPosition.Y)),
|
||||||
|
new Point(Math.Max(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Max(_absoluteStartPosition.Y, absoluteCurrentPosition.Y))
|
||||||
|
);
|
||||||
|
|
||||||
|
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,8 +174,11 @@ namespace Artemis.UI.Shared.Controls
|
|||||||
|
|
||||||
e.Pointer.Capture(null);
|
e.Pointer.Capture(null);
|
||||||
|
|
||||||
if (_displayRect != null)
|
if (_displayRect != null && _absoluteRect != null)
|
||||||
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, e.KeyModifiers));
|
{
|
||||||
|
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
_displayRect = null;
|
_displayRect = null;
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
|
|||||||
@ -13,17 +13,23 @@ namespace Artemis.UI.Shared.Events
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="SelectionRectangleEventArgs" /> class.
|
/// Creates a new instance of the <see cref="SelectionRectangleEventArgs" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SelectionRectangleEventArgs(Rect rectangle, KeyModifiers keyModifiers)
|
public SelectionRectangleEventArgs(Rect rectangle, Rect absoluteRectangle, KeyModifiers keyModifiers)
|
||||||
{
|
{
|
||||||
KeyModifiers = keyModifiers;
|
KeyModifiers = keyModifiers;
|
||||||
Rectangle = rectangle;
|
Rectangle = rectangle;
|
||||||
|
AbsoluteRectangle = absoluteRectangle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the rectangle that was selected when the event occurred.
|
/// Gets the rectangle relative to the parent that was selected when the event occurred.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Rect Rectangle { get; }
|
public Rect Rectangle { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the rectangle relative to the window that was selected when the event occurred.
|
||||||
|
/// </summary>
|
||||||
|
public Rect AbsoluteRectangle { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the key modifiers that where pressed when the event occurred.
|
/// Gets the key modifiers that where pressed when the event occurred.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for Avalonia's <see cref="IVisual" /> type
|
||||||
|
/// </summary>
|
||||||
|
public static class VisualExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a recursive list of all visual children of type <typeparamref name="T" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type the children should have.</typeparam>
|
||||||
|
/// <param name="root">The root visual at which to start searching.</param>
|
||||||
|
/// <returns>A recursive list of all visual children of type <typeparamref name="T" />.</returns>
|
||||||
|
public static List<T> GetVisualChildrenOfType<T>(this IVisual root)
|
||||||
|
{
|
||||||
|
List<T> result = new();
|
||||||
|
|
||||||
|
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList();
|
||||||
|
if (visualChildren == null || !visualChildren.Any())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
foreach (IVisual visualChild in visualChildren)
|
||||||
|
{
|
||||||
|
if (visualChild is T toFind)
|
||||||
|
result.Add(toFind);
|
||||||
|
|
||||||
|
result.AddRange(GetVisualChildrenOfType<T>(visualChild));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a recursive list of all visual children with a data context of type <typeparamref name="T" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of data context the children should have.</typeparam>
|
||||||
|
/// <param name="root">The root visual at which to start searching.</param>
|
||||||
|
/// <returns>A recursive list of all visual children with a data context of type <typeparamref name="T" />.</returns>
|
||||||
|
public static List<T> GetVisualChildrenOfDataContextType<T>(this IVisual root)
|
||||||
|
{
|
||||||
|
List<T> result = new();
|
||||||
|
|
||||||
|
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList();
|
||||||
|
if (visualChildren == null || !visualChildren.Any())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
foreach (IVisual visualChild in visualChildren)
|
||||||
|
{
|
||||||
|
if (visualChild is IDataContextProvider dataContextProvider && dataContextProvider.DataContext is T toFind)
|
||||||
|
result.Add(toFind);
|
||||||
|
|
||||||
|
result.AddRange(GetVisualChildrenOfType<T>(visualChild));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a profile editor command that can be used to enable or disable keyframes on a layer property.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class ToggleLayerPropertyKeyframes<T> : IProfileEditorCommand
|
||||||
|
{
|
||||||
|
private readonly bool _enable;
|
||||||
|
private readonly LayerProperty<T> _layerProperty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ToggleLayerPropertyKeyframes{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public ToggleLayerPropertyKeyframes(LayerProperty<T> layerProperty, bool enable)
|
||||||
|
{
|
||||||
|
_layerProperty = layerProperty;
|
||||||
|
_enable = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IProfileEditorCommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => _enable ? "Enable keyframes" : "Disable keyframes";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_layerProperty.KeyframesEnabled = _enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_layerProperty.KeyframesEnabled = !_enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -22,6 +22,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
|||||||
[AllowNull] private T _inputValue;
|
[AllowNull] private T _inputValue;
|
||||||
|
|
||||||
private TimeSpan _time;
|
private TimeSpan _time;
|
||||||
|
private bool _updating;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="PropertyInputViewModel{T}" /> class
|
/// Creates a new instance of the <see cref="PropertyInputViewModel{T}" /> class
|
||||||
@ -156,6 +157,9 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void ApplyInputValue()
|
protected virtual void ApplyInputValue()
|
||||||
{
|
{
|
||||||
|
if (_updating)
|
||||||
|
return;
|
||||||
|
|
||||||
if (InputDragging)
|
if (InputDragging)
|
||||||
ProfileEditorService.ChangeTime(_time);
|
ProfileEditorService.ChangeTime(_time);
|
||||||
else if (ValidationContext.IsValid)
|
else if (ValidationContext.IsValid)
|
||||||
@ -164,16 +168,25 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
|||||||
|
|
||||||
private void UpdateInputValue()
|
private void UpdateInputValue()
|
||||||
{
|
{
|
||||||
// Avoid unnecessary UI updates and validator cycles
|
try
|
||||||
if (_inputValue != null && _inputValue.Equals(LayerProperty.CurrentValue) || _inputValue == null && LayerProperty.CurrentValue == null)
|
{
|
||||||
return;
|
_updating = true;
|
||||||
|
// Avoid unnecessary UI updates and validator cycles
|
||||||
|
if (_inputValue != null && _inputValue.Equals(LayerProperty.CurrentValue) || _inputValue == null && LayerProperty.CurrentValue == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Override the input value
|
// Override the input value
|
||||||
_inputValue = LayerProperty.CurrentValue;
|
_inputValue = LayerProperty.CurrentValue;
|
||||||
|
|
||||||
// Notify a change in the input value
|
// Notify a change in the input value
|
||||||
OnInputValueChanged();
|
OnInputValueChanged();
|
||||||
this.RaisePropertyChanged(nameof(InputValue));
|
this.RaisePropertyChanged(nameof(InputValue));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updating = false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateDataBinding()
|
private void UpdateDataBinding()
|
||||||
|
|||||||
@ -48,4 +48,9 @@
|
|||||||
<Resource Include="Assets\Images\Logo\bow.ico" />
|
<Resource Include="Assets\Images\Logo\bow.ico" />
|
||||||
<Resource Include="Assets\Images\Logo\bow.svg" />
|
<Resource Include="Assets\Images\Logo\bow.svg" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Screens\ProfileEditor\Panels\Properties\PropertiesView.axaml.cs">
|
||||||
|
<DependentUpon>PropertiesView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ using System.Reactive.Linq;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.LayerBrushes;
|
using Artemis.Core.LayerBrushes;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.Dialogs;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs;
|
||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
Value="{Binding InputValue}"
|
Value="{Binding InputValue}"
|
||||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||||
AcceptsExpression="True"
|
AcceptsExpression="True"
|
||||||
|
SimpleNumberFormat="F3"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center"/>
|
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@ -13,11 +13,13 @@
|
|||||||
Value="{Binding X}"
|
Value="{Binding X}"
|
||||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||||
ToolTip.Tip="X-coordinate (horizontal)"
|
ToolTip.Tip="X-coordinate (horizontal)"
|
||||||
|
SimpleNumberFormat="F3"
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center" />
|
||||||
<TextBlock VerticalAlignment="Center">,</TextBlock>
|
<TextBlock VerticalAlignment="Center">,</TextBlock>
|
||||||
<controls:NumberBox Classes="condensed"
|
<controls:NumberBox Classes="condensed"
|
||||||
Value="{Binding Y}"
|
Value="{Binding Y}"
|
||||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||||
|
SimpleNumberFormat="F3"
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center" />
|
||||||
<TextBlock Width="25"
|
<TextBlock Width="25"
|
||||||
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
|
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
|
||||||
|
|||||||
@ -13,11 +13,13 @@
|
|||||||
Value="{Binding Height}"
|
Value="{Binding Height}"
|
||||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||||
ToolTip.Tip="Height"
|
ToolTip.Tip="Height"
|
||||||
|
SimpleNumberFormat="F3"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<TextBlock VerticalAlignment="Center">,</TextBlock>
|
<TextBlock VerticalAlignment="Center">,</TextBlock>
|
||||||
<controls:NumberBox Classes="condensed"
|
<controls:NumberBox Classes="condensed"
|
||||||
Value="{Binding Width}"
|
Value="{Binding Width}"
|
||||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||||
|
SimpleNumberFormat="F3"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
<TextBlock Width="25"
|
<TextBlock Width="25"
|
||||||
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
|
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
|
||||||
|
|||||||
@ -5,10 +5,10 @@ using Artemis.Core.LayerEffects;
|
|||||||
using Artemis.UI.Screens.Device;
|
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.Timeline;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
using Artemis.UI.Screens.Settings;
|
using Artemis.UI.Screens.Settings;
|
||||||
using Artemis.UI.Screens.Sidebar;
|
using Artemis.UI.Screens.Sidebar;
|
||||||
using Artemis.UI.Screens.SurfaceEditor;
|
using Artemis.UI.Screens.SurfaceEditor;
|
||||||
@ -67,13 +67,15 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
|
|
||||||
public interface ILayerPropertyVmFactory : IVmFactory
|
public interface ILayerPropertyVmFactory : IVmFactory
|
||||||
{
|
{
|
||||||
ProfileElementPropertyViewModel ProfileElementPropertyViewModel(ILayerProperty layerProperty);
|
PropertyViewModel PropertyViewModel(ILayerProperty layerProperty);
|
||||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
|
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
|
||||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
|
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
|
||||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
|
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
|
||||||
|
|
||||||
TreeGroupViewModel TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel);
|
TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
||||||
// TimelineGroupViewModel TimelineGroupViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
|
|
||||||
|
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
|
||||||
|
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
||||||
|
|
||||||
// TreeViewModel TreeViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
|
// TreeViewModel TreeViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
|
||||||
// EffectsViewModel EffectsViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
|
// EffectsViewModel EffectsViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
|
||||||
@ -83,7 +85,7 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
|
|
||||||
public interface IPropertyVmFactory
|
public interface IPropertyVmFactory
|
||||||
{
|
{
|
||||||
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
|
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
||||||
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
|
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
using Ninject.Extensions.Factory;
|
using Ninject.Extensions.Factory;
|
||||||
|
|
||||||
namespace Artemis.UI.Ninject.InstanceProviders
|
namespace Artemis.UI.Ninject.InstanceProviders
|
||||||
|
|||||||
@ -1,169 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline.TimelineView">
|
|
||||||
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
|
||||||
<ContentControl Content="{Binding TimelineGroupViewModel}" />
|
|
||||||
</TreeDataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Avalonia.ReactiveUI;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline
|
|
||||||
{
|
|
||||||
public partial class TimelineView : ReactiveUserControl<TimelineViewModel>
|
|
||||||
{
|
|
||||||
public TimelineView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using Artemis.UI.Shared;
|
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
|
||||||
using Avalonia.Controls.Mixins;
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
|
||||||
|
|
||||||
public class TimelineViewModel : ActivatableViewModelBase
|
|
||||||
{
|
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
|
||||||
private ObservableAsPropertyHelper<double>? _caretPosition;
|
|
||||||
|
|
||||||
public TimelineViewModel(IProfileEditorService profileEditorService)
|
|
||||||
{
|
|
||||||
_profileEditorService = profileEditorService;
|
|
||||||
this.WhenActivated(d =>
|
|
||||||
{
|
|
||||||
_caretPosition = _profileEditorService.Time
|
|
||||||
.CombineLatest(_profileEditorService.PixelsPerSecond, (t, p) => t.TotalSeconds * p)
|
|
||||||
.ToProperty(this, vm => vm.CaretPosition)
|
|
||||||
.DisposeWith(d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public double CaretPosition => _caretPosition?.Value ?? 0.0;
|
|
||||||
|
|
||||||
public void ChangeTime(TimeSpan newTime)
|
|
||||||
{
|
|
||||||
_profileEditorService.ChangeTime(newTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? snapTimes = null)
|
|
||||||
{
|
|
||||||
return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using Artemis.Core.LayerBrushes;
|
|
||||||
using Artemis.UI.Shared;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.Dialogs
|
|
||||||
{
|
|
||||||
public class LayerBrushPresetViewModel : ContentDialogViewModelBase
|
|
||||||
{
|
|
||||||
public BaseLayerBrush LayerBrush { get; }
|
|
||||||
|
|
||||||
public LayerBrushPresetViewModel(BaseLayerBrush layerBrush)
|
|
||||||
{
|
|
||||||
LayerBrush = layerBrush;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Avalonia.ReactiveUI;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree
|
|
||||||
{
|
|
||||||
public partial class TreeGroupView : ReactiveUserControl<TreeGroupViewModel>
|
|
||||||
{
|
|
||||||
public TreeGroupView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
||||||
{
|
|
||||||
if (ViewModel != null)
|
|
||||||
ViewModel.ProfileElementPropertyGroupViewModel.IsExpanded = !ViewModel.ProfileElementPropertyGroupViewModel.IsExpanded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Controls.Mixins;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Avalonia.ReactiveUI;
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree
|
|
||||||
{
|
|
||||||
public partial class TreePropertyView : ReactiveUserControl<ITreePropertyViewModel>
|
|
||||||
{
|
|
||||||
public TreePropertyView()
|
|
||||||
{
|
|
||||||
this.WhenActivated(d =>
|
|
||||||
{
|
|
||||||
if (ViewModel != null)
|
|
||||||
{
|
|
||||||
Observable.FromEventPattern<LayerPropertyEventArgs>(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e)
|
|
||||||
.Subscribe(_ => this.BringIntoView())
|
|
||||||
.DisposeWith(d);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Shared;
|
|
||||||
using Artemis.UI.Shared.Services.PropertyInput;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
|
||||||
|
|
||||||
internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropertyViewModel
|
|
||||||
{
|
|
||||||
public TreePropertyViewModel(LayerProperty<T> layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel, IPropertyInputService propertyInputService)
|
|
||||||
{
|
|
||||||
LayerProperty = layerProperty;
|
|
||||||
ProfileElementPropertyViewModel = profileElementPropertyViewModel;
|
|
||||||
PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayerProperty<T> LayerProperty { get; }
|
|
||||||
public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
|
|
||||||
public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
|
|
||||||
|
|
||||||
public ILayerProperty BaseLayerProperty => LayerProperty;
|
|
||||||
public bool HasDataBinding => LayerProperty.HasDataBinding;
|
|
||||||
|
|
||||||
public double GetDepth()
|
|
||||||
{
|
|
||||||
int depth = 0;
|
|
||||||
LayerPropertyGroup? current = LayerProperty.LayerPropertyGroup;
|
|
||||||
while (current != null)
|
|
||||||
{
|
|
||||||
depth++;
|
|
||||||
current = current.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignWidth="800"
|
|
||||||
d:DesignHeight="450"
|
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows.BrushConfigurationWindowView"
|
|
||||||
Title="Artemis | Brush configuration"
|
|
||||||
Width="{Binding Configuration.DialogWidth}"
|
|
||||||
Height="{Binding Configuration.DialogHeight}">
|
|
||||||
<Panel>
|
|
||||||
<ContentControl Content="{Binding ConfigurationViewModel}"></ContentControl>
|
|
||||||
</Panel>
|
|
||||||
</controls:CoreWindow>
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignWidth="800"
|
|
||||||
d:DesignHeight="450"
|
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows.EffectConfigurationWindowView"
|
|
||||||
Title="Artemis | Effect configuration"
|
|
||||||
Width="{Binding Configuration.DialogWidth}"
|
|
||||||
Height="{Binding Configuration.DialogHeight}">
|
|
||||||
<Panel>
|
|
||||||
<ContentControl Content="{Binding ConfigurationViewModel}"></ContentControl>
|
|
||||||
</Panel>
|
|
||||||
</controls:CoreWindow>
|
|
||||||
@ -2,14 +2,13 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
|
||||||
xmlns:controls="clr-namespace:Artemis.UI.Controls"
|
xmlns:controls="clr-namespace:Artemis.UI.Controls"
|
||||||
|
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.ProfileElementPropertiesView">
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView">
|
||||||
<Grid ColumnDefinitions="*,Auto,*" Name="ContainerGrid">
|
<Grid ColumnDefinitions="*,Auto,*" Name="ContainerGrid">
|
||||||
<Grid RowDefinitions="48,*">
|
<Grid RowDefinitions="48,*">
|
||||||
<ContentControl Grid.Row="0" Content="{Binding PlaybackViewModel}"></ContentControl>
|
<ContentControl Grid.Row="0" Content="{Binding PlaybackViewModel}" />
|
||||||
|
|
||||||
<ScrollViewer Grid.Row="1"
|
<ScrollViewer Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@ -18,7 +17,7 @@
|
|||||||
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
||||||
<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:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||||
<ContentControl Content="{Binding TreeGroupViewModel}" />
|
<ContentControl Content="{Binding TreeGroupViewModel}" />
|
||||||
</TreeDataTemplate>
|
</TreeDataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
@ -61,11 +60,11 @@
|
|||||||
PointerMoved="TimelineCaret_OnPointerMoved"
|
PointerMoved="TimelineCaret_OnPointerMoved"
|
||||||
Points="-8,0 -8,8 0,20, 8,8 8,0"
|
Points="-8,0 -8,8 0,20, 8,8 8,0"
|
||||||
Fill="{DynamicResource SystemAccentColorLight1}">
|
Fill="{DynamicResource SystemAccentColorLight1}">
|
||||||
<Polygon.Transitions>
|
<!-- <Polygon.Transitions> -->
|
||||||
<Transitions>
|
<!-- <Transitions> -->
|
||||||
<DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition>
|
<!-- <DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition> -->
|
||||||
</Transitions>
|
<!-- </Transitions> -->
|
||||||
</Polygon.Transitions>
|
<!-- </Polygon.Transitions> -->
|
||||||
</Polygon>
|
</Polygon>
|
||||||
<Line Name="TimelineLine"
|
<Line Name="TimelineLine"
|
||||||
Canvas.Left="{Binding TimelineViewModel.CaretPosition}"
|
Canvas.Left="{Binding TimelineViewModel.CaretPosition}"
|
||||||
@ -78,25 +77,24 @@
|
|||||||
StrokeThickness="2"
|
StrokeThickness="2"
|
||||||
Stroke="{DynamicResource SystemAccentColorLight1}"
|
Stroke="{DynamicResource SystemAccentColorLight1}"
|
||||||
RenderTransformOrigin="0,0">
|
RenderTransformOrigin="0,0">
|
||||||
<Line.Transitions>
|
<!-- <Line.Transitions> -->
|
||||||
<Transitions>
|
<!-- <Transitions> -->
|
||||||
<DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition>
|
<!-- <DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition> -->
|
||||||
</Transitions>
|
<!-- </Transitions> -->
|
||||||
</Line.Transitions>
|
<!-- </Line.Transitions> -->
|
||||||
<Line.RenderTransform>
|
<Line.RenderTransform>
|
||||||
<ScaleTransform ScaleX="1" ScaleY="{Binding #ContainerGrid.Bounds.Height}"></ScaleTransform>
|
<ScaleTransform ScaleX="1" ScaleY="{Binding #ContainerGrid.Bounds.Height}" />
|
||||||
</Line.RenderTransform>
|
</Line.RenderTransform>
|
||||||
</Line>
|
</Line>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
<!-- Horizontal scrolling -->
|
<!-- Horizontal scrolling -->
|
||||||
<ScrollViewer Grid.Row="1">
|
<ScrollViewer Grid.Row="1"
|
||||||
<ScrollViewer Name="TimelineScrollViewer"
|
Name="TimelineScrollViewer"
|
||||||
Offset="{Binding #TreeScrollViewer.Offset, Mode=OneWay}"
|
Offset="{Binding #TreeScrollViewer.Offset, Mode=OneWay}"
|
||||||
VerticalScrollBarVisibility="Hidden"
|
VerticalScrollBarVisibility="Hidden"
|
||||||
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
||||||
<ContentControl Content="{Binding TimelineViewModel}"></ContentControl>
|
<ContentControl Content="{Binding TimelineViewModel}" />
|
||||||
</ScrollViewer>
|
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<!-- TODO: Databindings here -->
|
<!-- TODO: Databindings here -->
|
||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia.Animation;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Shapes;
|
using Avalonia.Controls.Shapes;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
@ -9,14 +8,14 @@ using Avalonia.Markup.Xaml;
|
|||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
|
|
||||||
public class ProfileElementPropertiesView : ReactiveUserControl<ProfileElementPropertiesViewModel>
|
public class PropertiesView : ReactiveUserControl<PropertiesViewModel>
|
||||||
{
|
{
|
||||||
private readonly Polygon _timelineCaret;
|
private readonly Polygon _timelineCaret;
|
||||||
private readonly Line _timelineLine;
|
private readonly Line _timelineLine;
|
||||||
|
|
||||||
public ProfileElementPropertiesView()
|
public PropertiesView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_timelineCaret = this.Get<Polygon>("TimelineCaret");
|
_timelineCaret = this.Get<Polygon>("TimelineCaret");
|
||||||
@ -37,8 +36,8 @@ public class ProfileElementPropertiesView : ReactiveUserControl<ProfileElementPr
|
|||||||
// }
|
// }
|
||||||
// else
|
// else
|
||||||
// {
|
// {
|
||||||
((DoubleTransition) _timelineCaret.Transitions![0]).Duration = TimeSpan.Zero;
|
// ((DoubleTransition) _timelineCaret.Transitions![0]).Duration = TimeSpan.Zero;
|
||||||
((DoubleTransition) _timelineLine.Transitions![0]).Duration = TimeSpan.Zero;
|
// ((DoubleTransition) _timelineLine.Transitions![0]).Duration = TimeSpan.Zero;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10,31 +10,30 @@ using Artemis.Core.LayerBrushes;
|
|||||||
using Artemis.Core.LayerEffects;
|
using Artemis.Core.LayerEffects;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Playback;
|
using Artemis.UI.Screens.ProfileEditor.Playback;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
|
|
||||||
public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
public class PropertiesViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly Dictionary<LayerPropertyGroup, ProfileElementPropertyGroupViewModel> _cachedViewModels;
|
private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedViewModels;
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
|
||||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||||
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
|
private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
|
||||||
private ObservableCollection<ProfileElementPropertyGroupViewModel> _propertyGroupViewModels;
|
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel, TimelineViewModel timelineViewModel)
|
public PropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel)
|
||||||
{
|
{
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_layerPropertyVmFactory = layerPropertyVmFactory;
|
_layerPropertyVmFactory = layerPropertyVmFactory;
|
||||||
_propertyGroupViewModels = new ObservableCollection<ProfileElementPropertyGroupViewModel>();
|
PropertyGroupViewModels = new ObservableCollection<PropertyGroupViewModel>();
|
||||||
_cachedViewModels = new Dictionary<LayerPropertyGroup, ProfileElementPropertyGroupViewModel>();
|
_cachedViewModels = new Dictionary<LayerPropertyGroup, PropertyGroupViewModel>();
|
||||||
PlaybackViewModel = playbackViewModel;
|
PlaybackViewModel = playbackViewModel;
|
||||||
TimelineViewModel = timelineViewModel;
|
TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels);
|
||||||
|
|
||||||
// Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940
|
// Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940
|
||||||
this.WhenAnyValue(vm => vm.ProfileElement)
|
this.WhenAnyValue(vm => vm.ProfileElement)
|
||||||
@ -66,12 +65,8 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
|||||||
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
|
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
|
||||||
public IObservable<bool> Playing => _profileEditorService.Playing;
|
public IObservable<bool> Playing => _profileEditorService.Playing;
|
||||||
|
|
||||||
public ObservableCollection<ProfileElementPropertyGroupViewModel> PropertyGroupViewModels
|
public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; }
|
||||||
{
|
|
||||||
get => _propertyGroupViewModels;
|
|
||||||
set => this.RaiseAndSetIfChanged(ref _propertyGroupViewModels, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateGroups()
|
private void UpdateGroups()
|
||||||
{
|
{
|
||||||
if (ProfileElement == null)
|
if (ProfileElement == null)
|
||||||
@ -80,7 +75,7 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ObservableCollection<ProfileElementPropertyGroupViewModel> viewModels = new();
|
ObservableCollection<PropertyGroupViewModel> viewModels = new();
|
||||||
if (Layer != null)
|
if (Layer != null)
|
||||||
{
|
{
|
||||||
// Add base VMs
|
// Add base VMs
|
||||||
@ -100,29 +95,29 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
|||||||
// Map the most recent collection of VMs to the current list of VMs, making as little changes to the collection as possible
|
// Map the most recent collection of VMs to the current list of VMs, making as little changes to the collection as possible
|
||||||
for (int index = 0; index < viewModels.Count; index++)
|
for (int index = 0; index < viewModels.Count; index++)
|
||||||
{
|
{
|
||||||
ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel = viewModels[index];
|
PropertyGroupViewModel propertyGroupViewModel = viewModels[index];
|
||||||
if (index > PropertyGroupViewModels.Count - 1)
|
if (index > PropertyGroupViewModels.Count - 1)
|
||||||
PropertyGroupViewModels.Add(profileElementPropertyGroupViewModel);
|
PropertyGroupViewModels.Add(propertyGroupViewModel);
|
||||||
else if (!ReferenceEquals(PropertyGroupViewModels[index], profileElementPropertyGroupViewModel))
|
else if (!ReferenceEquals(PropertyGroupViewModels[index], propertyGroupViewModel))
|
||||||
PropertyGroupViewModels[index] = profileElementPropertyGroupViewModel;
|
PropertyGroupViewModels[index] = propertyGroupViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (PropertyGroupViewModels.Count > viewModels.Count)
|
while (PropertyGroupViewModels.Count > viewModels.Count)
|
||||||
PropertyGroupViewModels.RemoveAt(PropertyGroupViewModels.Count - 1);
|
PropertyGroupViewModels.RemoveAt(PropertyGroupViewModels.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProfileElementPropertyGroupViewModel GetOrCreateViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect)
|
private PropertyGroupViewModel GetOrCreateViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect)
|
||||||
{
|
{
|
||||||
if (_cachedViewModels.TryGetValue(layerPropertyGroup, out ProfileElementPropertyGroupViewModel? cachedVm))
|
if (_cachedViewModels.TryGetValue(layerPropertyGroup, out PropertyGroupViewModel? cachedVm))
|
||||||
return cachedVm;
|
return cachedVm;
|
||||||
|
|
||||||
ProfileElementPropertyGroupViewModel createdVm;
|
PropertyGroupViewModel createdVm;
|
||||||
if (layerBrush != null)
|
if (layerBrush != null)
|
||||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerBrush);
|
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup, layerBrush);
|
||||||
else if (layerEffect != null)
|
else if (layerEffect != null)
|
||||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerEffect);
|
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup, layerEffect);
|
||||||
else
|
else
|
||||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup);
|
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup);
|
||||||
|
|
||||||
_cachedViewModels[layerPropertyGroup] = createdVm;
|
_cachedViewModels[layerPropertyGroup] = createdVm;
|
||||||
return createdVm;
|
return createdVm;
|
||||||
@ -7,15 +7,15 @@ using Artemis.Core;
|
|||||||
using Artemis.Core.LayerBrushes;
|
using Artemis.Core.LayerBrushes;
|
||||||
using Artemis.Core.LayerEffects;
|
using Artemis.Core.LayerEffects;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.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 ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
|
|
||||||
public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
public class PropertyGroupViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||||
private readonly IPropertyInputService _propertyInputService;
|
private readonly IPropertyInputService _propertyInputService;
|
||||||
@ -23,13 +23,14 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
|||||||
private bool _isExpanded;
|
private bool _isExpanded;
|
||||||
private bool _isVisible;
|
private bool _isVisible;
|
||||||
|
|
||||||
public ProfileElementPropertyGroupViewModel(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>();
|
Children = new ObservableCollection<ViewModelBase>();
|
||||||
LayerPropertyGroup = layerPropertyGroup;
|
LayerPropertyGroup = layerPropertyGroup;
|
||||||
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
|
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
|
||||||
|
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
|
||||||
|
|
||||||
// TODO: Centralize visibility updating or do it here and dispose
|
// TODO: Centralize visibility updating or do it here and dispose
|
||||||
_isVisible = !LayerPropertyGroup.IsHidden;
|
_isVisible = !LayerPropertyGroup.IsHidden;
|
||||||
@ -37,14 +38,14 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
|||||||
PopulateChildren();
|
PopulateChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
||||||
BaseLayerBrush layerBrush)
|
BaseLayerBrush layerBrush)
|
||||||
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
||||||
{
|
{
|
||||||
LayerBrush = layerBrush;
|
LayerBrush = layerBrush;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
||||||
BaseLayerEffect layerEffect)
|
BaseLayerEffect layerEffect)
|
||||||
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
||||||
{
|
{
|
||||||
@ -57,6 +58,7 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
|||||||
public BaseLayerEffect? LayerEffect { get; }
|
public BaseLayerEffect? LayerEffect { get; }
|
||||||
|
|
||||||
public TreeGroupViewModel TreeGroupViewModel { get; }
|
public TreeGroupViewModel TreeGroupViewModel { get; }
|
||||||
|
public TimelineGroupViewModel TimelineGroupViewModel { get; }
|
||||||
|
|
||||||
public bool IsVisible
|
public bool IsVisible
|
||||||
{
|
{
|
||||||
@ -83,9 +85,9 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
|||||||
return result;
|
return result;
|
||||||
|
|
||||||
foreach (ViewModelBase child in Children)
|
foreach (ViewModelBase child in Children)
|
||||||
if (child is ProfileElementPropertyViewModel profileElementPropertyViewModel)
|
if (child is PropertyViewModel profileElementPropertyViewModel)
|
||||||
result.AddRange(profileElementPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels());
|
result.AddRange(profileElementPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels());
|
||||||
else if (child is ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel)
|
else if (child is PropertyGroupViewModel profileElementPropertyGroupViewModel)
|
||||||
result.AddRange(profileElementPropertyGroupViewModel.GetAllKeyframeViewModels(expandedOnly));
|
result.AddRange(profileElementPropertyGroupViewModel.GetAllKeyframeViewModels(expandedOnly));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -105,16 +107,16 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
|||||||
ILayerProperty? value = (ILayerProperty?) propertyInfo.GetValue(LayerPropertyGroup);
|
ILayerProperty? value = (ILayerProperty?) propertyInfo.GetValue(LayerPropertyGroup);
|
||||||
// Ensure a supported input VM was found, otherwise don't add it
|
// Ensure a supported input VM was found, otherwise don't add it
|
||||||
if (value != null && _propertyInputService.CanCreatePropertyInputViewModel(value))
|
if (value != null && _propertyInputService.CanCreatePropertyInputViewModel(value))
|
||||||
Children.Add(_layerPropertyVmFactory.ProfileElementPropertyViewModel(value));
|
Children.Add(_layerPropertyVmFactory.PropertyViewModel(value));
|
||||||
}
|
}
|
||||||
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||||
{
|
{
|
||||||
LayerPropertyGroup? value = (LayerPropertyGroup?) propertyInfo.GetValue(LayerPropertyGroup);
|
LayerPropertyGroup? value = (LayerPropertyGroup?) propertyInfo.GetValue(LayerPropertyGroup);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
Children.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(value));
|
Children.Add(_layerPropertyVmFactory.PropertyGroupViewModel(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HasChildren = Children.Any(i => i is ProfileElementPropertyViewModel {IsVisible: true} || i is ProfileElementPropertyGroupViewModel {IsVisible: true});
|
HasChildren = Children.Any(i => i is PropertyViewModel {IsVisible: true} || i is PropertyGroupViewModel {IsVisible: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,19 +1,19 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
|
|
||||||
public class ProfileElementPropertyViewModel : ViewModelBase
|
public class PropertyViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private bool _isExpanded;
|
private bool _isExpanded;
|
||||||
private bool _isHighlighted;
|
private bool _isHighlighted;
|
||||||
private bool _isVisible;
|
private bool _isVisible;
|
||||||
|
|
||||||
public ProfileElementPropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
|
public PropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
|
||||||
{
|
{
|
||||||
LayerProperty = layerProperty;
|
LayerProperty = layerProperty;
|
||||||
TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this);
|
TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this);
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
public interface ITimelineKeyframeViewModel
|
public interface ITimelineKeyframeViewModel
|
||||||
{
|
{
|
||||||
@ -2,7 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
public interface ITimelinePropertyViewModel : IReactiveObject
|
public interface ITimelinePropertyViewModel : IReactiveObject
|
||||||
{
|
{
|
||||||
@ -5,7 +5,7 @@ using Artemis.UI.Shared;
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
public class TimelineEasingViewModel : ViewModelBase
|
public class TimelineEasingViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:properties="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineGroupView">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="28" />
|
||||||
|
<RowDefinition Height="1" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<ItemsControl Grid.Row="0"
|
||||||
|
Height="{DynamicResource RailsHeight}"
|
||||||
|
IsVisible="{Binding !PropertyGroupViewModel.IsExpanded}"
|
||||||
|
Items="{Binding KeyframePositions}"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.Styles>
|
||||||
|
<Style Selector="ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.Styles>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Ellipse Fill="{DynamicResource ControlFillColorDisabledBrush}"
|
||||||
|
Stroke="White"
|
||||||
|
StrokeThickness="0"
|
||||||
|
Width="10"
|
||||||
|
Height="10"
|
||||||
|
Margin="-5,6,0,0" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<Rectangle Grid.Row="1" HorizontalAlignment="Stretch" Fill="{DynamicResource ButtonBorderBrush}" Height="1" />
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="2"
|
||||||
|
Items="{Binding Children}"
|
||||||
|
IsVisible="{Binding PropertyGroupViewModel.IsExpanded}"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<ItemsControl.DataTemplates>
|
||||||
|
<DataTemplate DataType="properties:PropertyGroupViewModel">
|
||||||
|
<ContentControl Content="{Binding TimelineGroupViewModel}" IsVisible="{Binding IsVisible}" />
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate DataType="properties:PropertyViewModel">
|
||||||
|
<ContentControl Content="{Binding TimelinePropertyViewModel}" IsVisible="{Binding IsVisible}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.DataTemplates>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelineGroupView : ReactiveUserControl<TimelineGroupViewModel>
|
||||||
|
{
|
||||||
|
public TimelineGroupView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelineGroupViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private ObservableCollection<double>? _keyframePosition;
|
||||||
|
private int _pixelsPerSecond;
|
||||||
|
|
||||||
|
public TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel, IProfileEditorService profileEditorService)
|
||||||
|
{
|
||||||
|
PropertyGroupViewModel = propertyGroupViewModel;
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
profileEditorService.PixelsPerSecond.Subscribe(p =>
|
||||||
|
{
|
||||||
|
_pixelsPerSecond = p;
|
||||||
|
UpdateKeyframePositions();
|
||||||
|
});
|
||||||
|
this.WhenAnyValue(vm => vm.PropertyGroupViewModel.IsExpanded).Subscribe(_ => UpdateKeyframePositions()).DisposeWith(d);
|
||||||
|
PropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyGroupViewModel PropertyGroupViewModel { get; }
|
||||||
|
|
||||||
|
public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
|
||||||
|
|
||||||
|
public ObservableCollection<double>? KeyframePosition
|
||||||
|
{
|
||||||
|
get => _keyframePosition;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _keyframePosition, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateKeyframePositions()
|
||||||
|
{
|
||||||
|
KeyframePosition = new ObservableCollection<double>(PropertyGroupViewModel
|
||||||
|
.GetAllKeyframeViewModels(false)
|
||||||
|
.Select(p => p.Position.TotalSeconds * _pixelsPerSecond));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineKeyframeView"
|
||||||
|
ClipToBounds="False"
|
||||||
|
Height="{DynamicResource RailsHeight}">
|
||||||
|
<Ellipse Fill="{DynamicResource SystemAccentColorLight2}"
|
||||||
|
Stroke="White"
|
||||||
|
Width="10"
|
||||||
|
Height="10"
|
||||||
|
Margin="-5,0,0,0"
|
||||||
|
ToolTip.Tip="{Binding Timestamp}"
|
||||||
|
Classes.selected="{Binding IsSelected}">
|
||||||
|
<Ellipse.Styles>
|
||||||
|
<Style Selector="Ellipse">
|
||||||
|
<Setter Property="StrokeThickness" Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Ellipse.selected">
|
||||||
|
<Setter Property="StrokeThickness" Value="1" />
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Styles>
|
||||||
|
<Ellipse.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<DoubleTransition Property="StrokeThickness" Duration="0:0:0.25" />
|
||||||
|
</Transitions>
|
||||||
|
</Ellipse.Transitions>
|
||||||
|
<Ellipse.ContextFlyout>
|
||||||
|
<MenuFlyout>
|
||||||
|
<MenuItem Header="Easing" Items="{Binding EasingViewModels}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<avalonia:MaterialIcon Kind="Creation" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
<MenuItem.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Points="{Binding EasingPoints}"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="0 0 10 0" />
|
||||||
|
<TextBlock Text="{Binding Description}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</MenuItem.ItemTemplate>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Duplicate" Command="{Binding DuplicateKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+D">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<avalonia:MaterialIcon Kind="ContentDuplicate" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Copy" Command="{Binding CopyKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+C">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<avalonia:MaterialIcon Kind="ContentCopy" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Paste" Command="{Binding PasteKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+V">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<avalonia:MaterialIcon Kind="ContentPaste" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Delete" Command="{Binding DeleteKeyframes}" InputGesture="Delete">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<avalonia:MaterialIcon Kind="Delete" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
</MenuFlyout>
|
||||||
|
</Ellipse.ContextFlyout>
|
||||||
|
</Ellipse>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewModel>
|
||||||
|
{
|
||||||
|
public TimelineKeyframeView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,167 @@
|
|||||||
|
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.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
|
||||||
|
{
|
||||||
|
private bool _isSelected;
|
||||||
|
private string _timestamp;
|
||||||
|
private double _x;
|
||||||
|
|
||||||
|
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
|
||||||
|
{
|
||||||
|
IProfileEditorService profileEditorService1 = profileEditorService;
|
||||||
|
_timestamp = "0.000";
|
||||||
|
LayerPropertyKeyframe = layerPropertyKeyframe;
|
||||||
|
EasingViewModels = new ObservableCollection<TimelineEasingViewModel>();
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
profileEditorService1.PixelsPerSecond.Subscribe(p =>
|
||||||
|
{
|
||||||
|
_pixelsPerSecond = p;
|
||||||
|
profileEditorService1.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 void Update()
|
||||||
|
{
|
||||||
|
X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
|
||||||
|
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => _isSelected;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Position => LayerPropertyKeyframe.Position;
|
||||||
|
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
|
||||||
|
|
||||||
|
#region Context menu actions
|
||||||
|
|
||||||
|
public void Delete(bool save = true)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelinePropertyView">
|
||||||
|
<Border Height="{DynamicResource RailsBorderHeight}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource ButtonBorderBrush}">
|
||||||
|
<ItemsControl Items="{Binding KeyframeViewModels}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.Styles>
|
||||||
|
<Style Selector="ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.Styles>
|
||||||
|
</ItemsControl>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelinePropertyView : ReactiveUserControl<ITimelinePropertyViewModel>
|
||||||
|
{
|
||||||
|
public TimelinePropertyView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelinePropertyViewModel
|
||||||
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
|
public TimelinePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService)
|
||||||
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
LayerProperty = layerProperty;
|
||||||
|
PropertyViewModel = propertyViewModel;
|
||||||
|
KeyframeViewModels = new ObservableCollection<TimelineKeyframeViewModel<T>>();
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.KeyframeAdded += x, x => LayerProperty.KeyframeAdded -= x)
|
||||||
|
.Subscribe(_ => UpdateKeyframes())
|
||||||
|
.DisposeWith(d);
|
||||||
|
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x)
|
||||||
|
.Subscribe(_ => UpdateKeyframes())
|
||||||
|
.DisposeWith(d);
|
||||||
|
|
||||||
|
UpdateKeyframes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerProperty<T> LayerProperty { get; }
|
||||||
|
public PropertyViewModel PropertyViewModel { get; }
|
||||||
|
public ObservableCollection<TimelineKeyframeViewModel<T>> KeyframeViewModels { get; }
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
|
||||||
|
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineView">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<x:Double x:Key="RailsHeight">28</x:Double>
|
||||||
|
<x:Double x:Key="RailsBorderHeight">29</x:Double>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid Background="Transparent" PointerMoved="InputElement_OnPointerMoved" PointerReleased="InputElement_OnPointerReleased">
|
||||||
|
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||||
|
<ContentControl Content="{Binding TimelineGroupViewModel}" />
|
||||||
|
</TreeDataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<controls:SelectionRectangle InputElement="{Binding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished"></controls:SelectionRectangle>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.UI.Shared.Events;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelineView : ReactiveUserControl<TimelineViewModel>
|
||||||
|
{
|
||||||
|
private bool _draggedCursor;
|
||||||
|
|
||||||
|
public TimelineView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<TimelineKeyframeView> keyframeViews = this.GetVisualChildrenOfType<TimelineKeyframeView>().Where(k =>
|
||||||
|
{
|
||||||
|
Rect hitTestRect = k.TransformedBounds != null ? k.TransformedBounds.Value.Bounds.TransformToAABB(k.TransformedBounds.Value.Transform) : Rect.Empty;
|
||||||
|
return e.AbsoluteRectangle.Intersects(hitTestRect);
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
ViewModel.SelectKeyframes(keyframeViews.Where(kv => kv.ViewModel != null).Select(kv => kv.ViewModel!).ToList(), 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)
|
||||||
|
{
|
||||||
|
if (ViewModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_draggedCursor)
|
||||||
|
{
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
|
||||||
|
public class TimelineViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private ObservableAsPropertyHelper<double>? _caretPosition;
|
||||||
|
|
||||||
|
public TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels, IProfileEditorService profileEditorService)
|
||||||
|
{
|
||||||
|
PropertyGroupViewModels = propertyGroupViewModels;
|
||||||
|
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
_caretPosition = _profileEditorService.Time
|
||||||
|
.CombineLatest(_profileEditorService.PixelsPerSecond, (t, p) => t.TotalSeconds * p)
|
||||||
|
.ToProperty(this, vm => vm.CaretPosition)
|
||||||
|
.DisposeWith(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; }
|
||||||
|
|
||||||
|
public double CaretPosition => _caretPosition?.Value ?? 0.0;
|
||||||
|
|
||||||
|
public void ChangeTime(TimeSpan newTime)
|
||||||
|
{
|
||||||
|
_profileEditorService.ChangeTime(newTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? snapTimes = null)
|
||||||
|
{
|
||||||
|
return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectKeyframes(List<ITimelineKeyframeViewModel> keyframes, bool expand)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Artemis.Core.LayerBrushes;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs;
|
||||||
|
|
||||||
|
public class LayerBrushPresetViewModel : ContentDialogViewModelBase
|
||||||
|
{
|
||||||
|
public LayerBrushPresetViewModel(BaseLayerBrush layerBrush)
|
||||||
|
{
|
||||||
|
LayerBrush = layerBrush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseLayerBrush LayerBrush { get; }
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
|
||||||
public interface ITreePropertyViewModel : IReactiveObject
|
public interface ITreePropertyViewModel : IReactiveObject
|
||||||
{
|
{
|
||||||
@ -4,30 +4,30 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||||
xmlns:viewModel="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree"
|
|
||||||
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||||
xmlns:profileElementProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
|
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
|
xmlns:viewModel="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree"
|
||||||
|
xmlns:properties="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreeGroupView">
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreeGroupView">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20"/>
|
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
|
||||||
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
|
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
|
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
|
||||||
<Setter Property="RenderTransform" Value="rotate(-90deg)" />
|
<Setter Property="RenderTransform" Value="rotate(-90deg)" />
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Border Name="Bd"
|
<Border Name="Bd"
|
||||||
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||||
BorderThickness="0,0,0,1"
|
BorderThickness="0,0,0,1"
|
||||||
Height="29">
|
Height="29">
|
||||||
<Grid Margin="{Binding Converter={StaticResource PropertyTreeMarginConverter}}" ColumnDefinitions="19,*">
|
<Grid Margin="{Binding Converter={StaticResource PropertyTreeMarginConverter}}" ColumnDefinitions="19,*">
|
||||||
|
|
||||||
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding !ProfileElementPropertyGroupViewModel.IsExpanded}"
|
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding !PropertyGroupViewModel.IsExpanded}"
|
||||||
IsVisible="{Binding ProfileElementPropertyGroupViewModel.HasChildren}"
|
IsVisible="{Binding PropertyGroupViewModel.HasChildren}"
|
||||||
Kind="ChevronDown"
|
Kind="ChevronDown"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="5 0"
|
Margin="5 0"
|
||||||
@ -40,15 +40,14 @@
|
|||||||
</avalonia:MaterialIcon.Transitions>
|
</avalonia:MaterialIcon.Transitions>
|
||||||
</avalonia:MaterialIcon>
|
</avalonia:MaterialIcon>
|
||||||
|
|
||||||
<StackPanel Grid.Column="1">
|
<StackPanel Grid.Column="1">
|
||||||
<!-- Type: None -->
|
<!-- Type: None -->
|
||||||
<TextBlock Text="{Binding LayerPropertyGroup.GroupDescription.Name}"
|
<TextBlock Text="{Binding LayerPropertyGroup.GroupDescription.Name}"
|
||||||
ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}"
|
ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Margin="3 5 0 5"
|
Margin="3 5 0 5"
|
||||||
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.None}}">
|
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.None}}" />
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<!-- Type: General -->
|
<!-- Type: General -->
|
||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
@ -178,18 +177,18 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Do not bind directly to the ProfileElementPropertyGroupViewModel.Children collection
|
Do not bind directly to the PropertyGroupViewModel.Children collection
|
||||||
Instead use a reference provided by the VM that is null when collapsed, virtualization for noobs
|
Instead use a reference provided by the VM that is null when collapsed, virtualization for noobs
|
||||||
-->
|
-->
|
||||||
<ItemsControl Items="{Binding Children}"
|
<ItemsControl Items="{Binding Children}"
|
||||||
IsVisible="{Binding ProfileElementPropertyGroupViewModel.IsExpanded}"
|
IsVisible="{Binding PropertyGroupViewModel.IsExpanded}"
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
<ItemsControl.DataTemplates>
|
<ItemsControl.DataTemplates>
|
||||||
<DataTemplate DataType="profileElementProperties:ProfileElementPropertyGroupViewModel">
|
<DataTemplate DataType="properties:PropertyGroupViewModel">
|
||||||
<ContentControl Content="{Binding TreeGroupViewModel}" IsVisible="{Binding IsVisible}"></ContentControl>
|
<ContentControl Content="{Binding TreeGroupViewModel}" IsVisible="{Binding IsVisible}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate DataType="profileElementProperties:ProfileElementPropertyViewModel">
|
<DataTemplate DataType="properties:PropertyViewModel">
|
||||||
<ContentControl Content="{Binding TreePropertyViewModel}" IsVisible="{Binding IsVisible}"></ContentControl>
|
<ContentControl Content="{Binding TreePropertyViewModel}" IsVisible="{Binding IsVisible}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.DataTemplates>
|
</ItemsControl.DataTemplates>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
|
||||||
|
public class TreeGroupView : ReactiveUserControl<TreeGroupViewModel>
|
||||||
|
{
|
||||||
|
public TreeGroupView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel != null)
|
||||||
|
ViewModel.PropertyGroupViewModel.IsExpanded = !ViewModel.PropertyGroupViewModel.IsExpanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@ using Artemis.Core;
|
|||||||
using Artemis.Core.LayerBrushes;
|
using Artemis.Core.LayerBrushes;
|
||||||
using Artemis.Core.LayerEffects;
|
using Artemis.Core.LayerEffects;
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.LayerBrushes;
|
using Artemis.UI.Shared.LayerBrushes;
|
||||||
using Artemis.UI.Shared.LayerEffects;
|
using Artemis.UI.Shared.LayerEffects;
|
||||||
@ -18,7 +18,7 @@ using Ninject;
|
|||||||
using Ninject.Parameters;
|
using Ninject.Parameters;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
|
||||||
public class TreeGroupViewModel : ActivatableViewModelBase
|
public class TreeGroupViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
@ -27,16 +27,16 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
|||||||
private BrushConfigurationWindowViewModel? _brushConfigurationWindowViewModel;
|
private BrushConfigurationWindowViewModel? _brushConfigurationWindowViewModel;
|
||||||
private EffectConfigurationWindowViewModel? _effectConfigurationWindowViewModel;
|
private EffectConfigurationWindowViewModel? _effectConfigurationWindowViewModel;
|
||||||
|
|
||||||
public TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel, IWindowService windowService, IProfileEditorService profileEditorService)
|
public TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel, IWindowService windowService, IProfileEditorService profileEditorService)
|
||||||
{
|
{
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
ProfileElementPropertyGroupViewModel = profileElementPropertyGroupViewModel;
|
PropertyGroupViewModel = propertyGroupViewModel;
|
||||||
DetermineGroupType();
|
DetermineGroupType();
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
ProfileElementPropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
|
PropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
|
||||||
Disposable.Create(CloseViewModels).DisposeWith(d);
|
Disposable.Create(CloseViewModels).DisposeWith(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,12 +44,12 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel { get; }
|
public PropertyGroupViewModel PropertyGroupViewModel { get; }
|
||||||
public LayerPropertyGroup LayerPropertyGroup => ProfileElementPropertyGroupViewModel.LayerPropertyGroup;
|
public LayerPropertyGroup LayerPropertyGroup => PropertyGroupViewModel.LayerPropertyGroup;
|
||||||
public BaseLayerBrush? LayerBrush => ProfileElementPropertyGroupViewModel.LayerBrush;
|
public BaseLayerBrush? LayerBrush => PropertyGroupViewModel.LayerBrush;
|
||||||
public BaseLayerEffect? LayerEffect => ProfileElementPropertyGroupViewModel.LayerEffect;
|
public BaseLayerEffect? LayerEffect => PropertyGroupViewModel.LayerEffect;
|
||||||
|
|
||||||
public ObservableCollection<ViewModelBase>? Children => ProfileElementPropertyGroupViewModel.IsExpanded ? ProfileElementPropertyGroupViewModel.Children : null;
|
public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
|
||||||
|
|
||||||
public LayerPropertyGroupType GroupType { get; private set; }
|
public LayerPropertyGroupType GroupType { get; private set; }
|
||||||
|
|
||||||
@ -148,9 +148,9 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
|||||||
GroupType = LayerPropertyGroupType.General;
|
GroupType = LayerPropertyGroupType.General;
|
||||||
else if (LayerPropertyGroup is LayerTransformProperties)
|
else if (LayerPropertyGroup is LayerTransformProperties)
|
||||||
GroupType = LayerPropertyGroupType.Transform;
|
GroupType = LayerPropertyGroupType.Transform;
|
||||||
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerBrush != null)
|
else if (LayerPropertyGroup.Parent == null && PropertyGroupViewModel.LayerBrush != null)
|
||||||
GroupType = LayerPropertyGroupType.LayerBrushRoot;
|
GroupType = LayerPropertyGroupType.LayerBrushRoot;
|
||||||
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerEffect != null)
|
else if (LayerPropertyGroup.Parent == null && PropertyGroupViewModel.LayerEffect != null)
|
||||||
GroupType = LayerPropertyGroupType.LayerEffectRoot;
|
GroupType = LayerPropertyGroupType.LayerEffectRoot;
|
||||||
else
|
else
|
||||||
GroupType = LayerPropertyGroupType.None;
|
GroupType = LayerPropertyGroupType.None;
|
||||||
@ -6,12 +6,11 @@
|
|||||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreePropertyView">
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreePropertyView">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
|
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
|
||||||
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
|
</UserControl.Resources>
|
||||||
</UserControl.Resources>
|
<Border Name="Bd"
|
||||||
<Border Name="Bd"
|
|
||||||
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||||
BorderThickness="0,0,0,1"
|
BorderThickness="0,0,0,1"
|
||||||
Height="29">
|
Height="29">
|
||||||
@ -24,10 +23,10 @@
|
|||||||
IsChecked="{Binding KeyframesEnabled}"
|
IsChecked="{Binding KeyframesEnabled}"
|
||||||
IsEnabled="{Binding LayerProperty.KeyframesSupported}"
|
IsEnabled="{Binding LayerProperty.KeyframesSupported}"
|
||||||
VerticalAlignment="Center" Padding="-25">
|
VerticalAlignment="Center" Padding="-25">
|
||||||
<avalonia:MaterialIcon Kind="Stopwatch" />
|
<avalonia:MaterialIcon Kind="Stopwatch" />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Margin="5,0,0,0"
|
Margin="5,0,0,0"
|
||||||
Padding="0,0,5,0"
|
Padding="0,0,5,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@ -36,30 +35,30 @@
|
|||||||
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"
|
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"
|
||||||
HorizontalAlignment="Left" />
|
HorizontalAlignment="Left" />
|
||||||
|
|
||||||
<ContentControl Grid.Column="2"
|
<ContentControl Grid.Column="2"
|
||||||
Margin="5 0"
|
Margin="5 0"
|
||||||
Content="{Binding PropertyInputViewModel}"
|
Content="{Binding PropertyInputViewModel}"
|
||||||
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"/>
|
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}" />
|
||||||
|
|
||||||
<Button Grid.Column="3"
|
<Button Grid.Column="3"
|
||||||
Command="{Binding ResetToDefault}"
|
Command="{Binding ResetToDefault}"
|
||||||
Classes="icon-button"
|
Classes="icon-button"
|
||||||
ToolTip.Tip="Reset the property to its default value."
|
ToolTip.Tip="Reset the property to its default value."
|
||||||
Width="24"
|
Width="24"
|
||||||
Height="24">
|
Height="24">
|
||||||
<avalonia:MaterialIcon Kind="BackupRestore" />
|
<avalonia:MaterialIcon Kind="BackupRestore" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ToggleButton Grid.Column="4" Classes="icon-button"
|
<ToggleButton Grid.Column="4" Classes="icon-button"
|
||||||
ToolTip.Tip="Change the property's data binding"
|
ToolTip.Tip="Change the property's data binding"
|
||||||
Width="24"
|
Width="24"
|
||||||
Height="24"
|
Height="24"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
|
IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
|
||||||
IsChecked="{Binding HasDataBinding, Mode=OneWay}">
|
IsChecked="{Binding HasDataBinding, Mode=OneWay}">
|
||||||
<avalonia:MaterialIcon Kind="VectorLink" />
|
<avalonia:MaterialIcon Kind="VectorLink" />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
|
||||||
|
public class TreePropertyView : ReactiveUserControl<ITreePropertyViewModel>
|
||||||
|
{
|
||||||
|
public TreePropertyView()
|
||||||
|
{
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
if (ViewModel != null)
|
||||||
|
Observable.FromEventPattern<LayerPropertyEventArgs>(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e)
|
||||||
|
.Subscribe(_ => this.BringIntoView())
|
||||||
|
.DisposeWith(d);
|
||||||
|
});
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
|
using Artemis.UI.Shared.Services.PropertyInput;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
|
||||||
|
internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropertyViewModel
|
||||||
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
|
public TreePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService,
|
||||||
|
IPropertyInputService propertyInputService)
|
||||||
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
|
||||||
|
LayerProperty = layerProperty;
|
||||||
|
PropertyViewModel = propertyViewModel;
|
||||||
|
PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
|
||||||
|
|
||||||
|
this.WhenActivated(d => this.WhenAnyValue(vm => vm.LayerProperty.KeyframesEnabled).Subscribe(_ => this.RaisePropertyChanged(nameof(KeyframesEnabled))).DisposeWith(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerProperty<T> LayerProperty { get; }
|
||||||
|
public PropertyViewModel PropertyViewModel { get; }
|
||||||
|
public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
|
||||||
|
|
||||||
|
public bool KeyframesEnabled
|
||||||
|
{
|
||||||
|
get => LayerProperty.KeyframesEnabled;
|
||||||
|
set => UpdateKeyframesEnabled(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateKeyframesEnabled(bool value)
|
||||||
|
{
|
||||||
|
if (value == LayerProperty.KeyframesEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_profileEditorService.ExecuteCommand(new ToggleLayerPropertyKeyframes<T>(LayerProperty, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILayerProperty BaseLayerProperty => LayerProperty;
|
||||||
|
public bool HasDataBinding => LayerProperty.HasDataBinding;
|
||||||
|
|
||||||
|
public double GetDepth()
|
||||||
|
{
|
||||||
|
int depth = 0;
|
||||||
|
LayerPropertyGroup? current = LayerProperty.LayerPropertyGroup;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
depth++;
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.BrushConfigurationWindowView"
|
||||||
|
Title="Artemis | Brush configuration"
|
||||||
|
Width="{Binding Configuration.DialogWidth}"
|
||||||
|
Height="{Binding Configuration.DialogHeight}">
|
||||||
|
<Panel>
|
||||||
|
<ContentControl Content="{Binding ConfigurationViewModel}" />
|
||||||
|
</Panel>
|
||||||
|
</controls:CoreWindow>
|
||||||
@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||||
|
|
||||||
public class BrushConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
|
public class BrushConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
|
||||||
{
|
{
|
||||||
@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.LayerBrushes;
|
using Artemis.UI.Shared.LayerBrushes;
|
||||||
using Artemis.UI.Shared.LayerEffects;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||||
|
|
||||||
public class BrushConfigurationWindowViewModel : DialogViewModelBase<object?>
|
public class BrushConfigurationWindowViewModel : DialogViewModelBase<object?>
|
||||||
{
|
{
|
||||||
@ -12,7 +11,7 @@ public class BrushConfigurationWindowViewModel : DialogViewModelBase<object?>
|
|||||||
{
|
{
|
||||||
ConfigurationViewModel = configurationViewModel;
|
ConfigurationViewModel = configurationViewModel;
|
||||||
Configuration = configuration;
|
Configuration = configuration;
|
||||||
|
|
||||||
ConfigurationViewModel.CloseRequested += ConfigurationViewModelOnCloseRequested;
|
ConfigurationViewModel.CloseRequested += ConfigurationViewModelOnCloseRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.EffectConfigurationWindowView"
|
||||||
|
Title="Artemis | Effect configuration"
|
||||||
|
Width="{Binding Configuration.DialogWidth}"
|
||||||
|
Height="{Binding Configuration.DialogHeight}">
|
||||||
|
<Panel>
|
||||||
|
<ContentControl Content="{Binding ConfigurationViewModel}" />
|
||||||
|
</Panel>
|
||||||
|
</controls:CoreWindow>
|
||||||
@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||||
|
|
||||||
public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
|
public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
|
||||||
{
|
{
|
||||||
@ -3,7 +3,7 @@ using Artemis.UI.Shared;
|
|||||||
using Artemis.UI.Shared.LayerEffects;
|
using Artemis.UI.Shared.LayerEffects;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||||
|
|
||||||
public class EffectConfigurationWindowViewModel : DialogViewModelBase<object?>
|
public class EffectConfigurationWindowViewModel : DialogViewModelBase<object?>
|
||||||
{
|
{
|
||||||
@ -22,7 +22,7 @@ public class EffectConfigurationWindowViewModel : DialogViewModelBase<object?>
|
|||||||
{
|
{
|
||||||
return ConfigurationViewModel.CanClose() && Dispatcher.UIThread.InvokeAsync(async () => await ConfigurationViewModel.CanCloseAsync()).GetAwaiter().GetResult();
|
return ConfigurationViewModel.CanClose() && Dispatcher.UIThread.InvokeAsync(async () => await ConfigurationViewModel.CanCloseAsync()).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigurationViewModelOnCloseRequested(object? sender, EventArgs e)
|
private void ConfigurationViewModelOnCloseRequested(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (CanClose())
|
if (CanClose())
|
||||||
@ -67,7 +67,7 @@
|
|||||||
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
|
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
|
||||||
|
|
||||||
<Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True">
|
<Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True">
|
||||||
<ContentControl Content="{Binding ProfileElementPropertiesViewModel}"/>
|
<ContentControl Content="{Binding PropertiesViewModel}"/>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Screens.ProfileEditor.MenuBar;
|
using Artemis.UI.Screens.ProfileEditor.MenuBar;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
using Artemis.UI.Screens.ProfileEditor.StatusBar;
|
using Artemis.UI.Screens.ProfileEditor.StatusBar;
|
||||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
using Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
@ -25,13 +25,13 @@ namespace Artemis.UI.Screens.ProfileEditor
|
|||||||
ProfileTreeViewModel profileTreeViewModel,
|
ProfileTreeViewModel profileTreeViewModel,
|
||||||
ProfileEditorTitleBarViewModel profileEditorTitleBarViewModel,
|
ProfileEditorTitleBarViewModel profileEditorTitleBarViewModel,
|
||||||
MenuBarViewModel menuBarViewModel,
|
MenuBarViewModel menuBarViewModel,
|
||||||
ProfileElementPropertiesViewModel profileElementPropertiesViewModel,
|
PropertiesViewModel propertiesViewModel,
|
||||||
StatusBarViewModel statusBarViewModel)
|
StatusBarViewModel statusBarViewModel)
|
||||||
: base(hostScreen, "profile-editor")
|
: base(hostScreen, "profile-editor")
|
||||||
{
|
{
|
||||||
VisualEditorViewModel = visualEditorViewModel;
|
VisualEditorViewModel = visualEditorViewModel;
|
||||||
ProfileTreeViewModel = profileTreeViewModel;
|
ProfileTreeViewModel = profileTreeViewModel;
|
||||||
ProfileElementPropertiesViewModel = profileElementPropertiesViewModel;
|
PropertiesViewModel = propertiesViewModel;
|
||||||
StatusBarViewModel = statusBarViewModel;
|
StatusBarViewModel = statusBarViewModel;
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
@ -46,7 +46,7 @@ namespace Artemis.UI.Screens.ProfileEditor
|
|||||||
public VisualEditorViewModel VisualEditorViewModel { get; }
|
public VisualEditorViewModel VisualEditorViewModel { get; }
|
||||||
public ProfileTreeViewModel ProfileTreeViewModel { get; }
|
public ProfileTreeViewModel ProfileTreeViewModel { get; }
|
||||||
public MenuBarViewModel? MenuBarViewModel { get; }
|
public MenuBarViewModel? MenuBarViewModel { get; }
|
||||||
public ProfileElementPropertiesViewModel ProfileElementPropertiesViewModel { get; }
|
public PropertiesViewModel PropertiesViewModel { get; }
|
||||||
public StatusBarViewModel StatusBarViewModel { get; }
|
public StatusBarViewModel StatusBarViewModel { get; }
|
||||||
|
|
||||||
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
|
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user