mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile editor - Ported keyframe display and selection
This commit is contained in:
parent
66a2e51979
commit
98180df5f2
@ -73,7 +73,4 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Extensions\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -44,9 +44,11 @@ namespace Artemis.UI.Shared.Controls
|
||||
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
|
||||
|
||||
private Rect? _displayRect;
|
||||
private Rect? _absoluteRect;
|
||||
private IControl? _oldInputElement;
|
||||
private Point _startPosition;
|
||||
|
||||
private Point _absoluteStartPosition;
|
||||
|
||||
/// <inheritdoc />
|
||||
public SelectionRectangle()
|
||||
{
|
||||
@ -140,6 +142,7 @@ namespace Artemis.UI.Shared.Controls
|
||||
e.Pointer.Capture(this);
|
||||
|
||||
_startPosition = e.GetPosition(Parent);
|
||||
_absoluteStartPosition = e.GetPosition(VisualRoot);
|
||||
_displayRect = null;
|
||||
}
|
||||
|
||||
@ -149,11 +152,18 @@ namespace Artemis.UI.Shared.Controls
|
||||
return;
|
||||
|
||||
Point currentPosition = e.GetPosition(Parent);
|
||||
Point absoluteCurrentPosition = e.GetPosition(VisualRoot);
|
||||
|
||||
_displayRect = new Rect(
|
||||
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))
|
||||
);
|
||||
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();
|
||||
}
|
||||
|
||||
@ -164,8 +174,11 @@ namespace Artemis.UI.Shared.Controls
|
||||
|
||||
e.Pointer.Capture(null);
|
||||
|
||||
if (_displayRect != null)
|
||||
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, e.KeyModifiers));
|
||||
if (_displayRect != null && _absoluteRect != null)
|
||||
{
|
||||
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
_displayRect = null;
|
||||
InvalidateVisual();
|
||||
|
||||
@ -13,17 +13,23 @@ namespace Artemis.UI.Shared.Events
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SelectionRectangleEventArgs" /> class.
|
||||
/// </summary>
|
||||
public SelectionRectangleEventArgs(Rect rectangle, KeyModifiers keyModifiers)
|
||||
public SelectionRectangleEventArgs(Rect rectangle, Rect absoluteRectangle, KeyModifiers keyModifiers)
|
||||
{
|
||||
KeyModifiers = keyModifiers;
|
||||
Rectangle = rectangle;
|
||||
AbsoluteRectangle = absoluteRectangle;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
/// Gets the key modifiers that where pressed when the event occurred.
|
||||
/// </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;
|
||||
|
||||
private TimeSpan _time;
|
||||
private bool _updating;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="PropertyInputViewModel{T}" /> class
|
||||
@ -156,6 +157,9 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
||||
/// </summary>
|
||||
protected virtual void ApplyInputValue()
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
|
||||
if (InputDragging)
|
||||
ProfileEditorService.ChangeTime(_time);
|
||||
else if (ValidationContext.IsValid)
|
||||
@ -164,16 +168,25 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
||||
|
||||
private void UpdateInputValue()
|
||||
{
|
||||
// Avoid unnecessary UI updates and validator cycles
|
||||
if (_inputValue != null && _inputValue.Equals(LayerProperty.CurrentValue) || _inputValue == null && LayerProperty.CurrentValue == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
_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
|
||||
_inputValue = LayerProperty.CurrentValue;
|
||||
// Override the input value
|
||||
_inputValue = LayerProperty.CurrentValue;
|
||||
|
||||
// Notify a change in the input value
|
||||
OnInputValueChanged();
|
||||
this.RaisePropertyChanged(nameof(InputValue));
|
||||
// Notify a change in the input value
|
||||
OnInputValueChanged();
|
||||
this.RaisePropertyChanged(nameof(InputValue));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateDataBinding()
|
||||
|
||||
@ -48,4 +48,9 @@
|
||||
<Resource Include="Assets\Images\Logo\bow.ico" />
|
||||
<Resource Include="Assets\Images\Logo\bow.svg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Screens\ProfileEditor\Panels\Properties\PropertiesView.axaml.cs">
|
||||
<DependentUpon>PropertiesView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
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.ProfileEditor;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
Value="{Binding InputValue}"
|
||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||
AcceptsExpression="True"
|
||||
SimpleNumberFormat="F3"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
@ -13,11 +13,13 @@
|
||||
Value="{Binding X}"
|
||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||
ToolTip.Tip="X-coordinate (horizontal)"
|
||||
SimpleNumberFormat="F3"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center">,</TextBlock>
|
||||
<controls:NumberBox Classes="condensed"
|
||||
Value="{Binding Y}"
|
||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||
SimpleNumberFormat="F3"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Width="25"
|
||||
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
|
||||
|
||||
@ -13,11 +13,13 @@
|
||||
Value="{Binding Height}"
|
||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||
ToolTip.Tip="Height"
|
||||
SimpleNumberFormat="F3"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock VerticalAlignment="Center">,</TextBlock>
|
||||
<controls:NumberBox Classes="condensed"
|
||||
Value="{Binding Width}"
|
||||
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
|
||||
SimpleNumberFormat="F3"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Width="25"
|
||||
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
|
||||
|
||||
@ -5,10 +5,10 @@ using Artemis.Core.LayerEffects;
|
||||
using Artemis.UI.Screens.Device;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
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.Properties;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
using Artemis.UI.Screens.Settings;
|
||||
using Artemis.UI.Screens.Sidebar;
|
||||
using Artemis.UI.Screens.SurfaceEditor;
|
||||
@ -67,13 +67,15 @@ namespace Artemis.UI.Ninject.Factories
|
||||
|
||||
public interface ILayerPropertyVmFactory : IVmFactory
|
||||
{
|
||||
ProfileElementPropertyViewModel ProfileElementPropertyViewModel(ILayerProperty layerProperty);
|
||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
|
||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
|
||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
|
||||
PropertyViewModel PropertyViewModel(ILayerProperty layerProperty);
|
||||
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
|
||||
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
|
||||
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
|
||||
|
||||
TreeGroupViewModel TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel);
|
||||
// TimelineGroupViewModel TimelineGroupViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
|
||||
TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
||||
|
||||
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
|
||||
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
||||
|
||||
// TreeViewModel TreeViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
|
||||
// EffectsViewModel EffectsViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
|
||||
@ -83,7 +85,7 @@ namespace Artemis.UI.Ninject.Factories
|
||||
|
||||
public interface IPropertyVmFactory
|
||||
{
|
||||
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
|
||||
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
|
||||
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
||||
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
using Ninject.Extensions.Factory;
|
||||
|
||||
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: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"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.ProfileElementPropertiesView">
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView">
|
||||
<Grid ColumnDefinitions="*,Auto,*" Name="ContainerGrid">
|
||||
<Grid RowDefinitions="48,*">
|
||||
<ContentControl Grid.Row="0" Content="{Binding PlaybackViewModel}"></ContentControl>
|
||||
<ContentControl Grid.Row="0" Content="{Binding PlaybackViewModel}" />
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
@ -18,7 +17,7 @@
|
||||
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
||||
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||
<ContentControl Content="{Binding TreeGroupViewModel}" />
|
||||
</TreeDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
@ -61,11 +60,11 @@
|
||||
PointerMoved="TimelineCaret_OnPointerMoved"
|
||||
Points="-8,0 -8,8 0,20, 8,8 8,0"
|
||||
Fill="{DynamicResource SystemAccentColorLight1}">
|
||||
<Polygon.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition>
|
||||
</Transitions>
|
||||
</Polygon.Transitions>
|
||||
<!-- <Polygon.Transitions> -->
|
||||
<!-- <Transitions> -->
|
||||
<!-- <DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition> -->
|
||||
<!-- </Transitions> -->
|
||||
<!-- </Polygon.Transitions> -->
|
||||
</Polygon>
|
||||
<Line Name="TimelineLine"
|
||||
Canvas.Left="{Binding TimelineViewModel.CaretPosition}"
|
||||
@ -78,25 +77,24 @@
|
||||
StrokeThickness="2"
|
||||
Stroke="{DynamicResource SystemAccentColorLight1}"
|
||||
RenderTransformOrigin="0,0">
|
||||
<Line.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition>
|
||||
</Transitions>
|
||||
</Line.Transitions>
|
||||
<!-- <Line.Transitions> -->
|
||||
<!-- <Transitions> -->
|
||||
<!-- <DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition> -->
|
||||
<!-- </Transitions> -->
|
||||
<!-- </Line.Transitions> -->
|
||||
<Line.RenderTransform>
|
||||
<ScaleTransform ScaleX="1" ScaleY="{Binding #ContainerGrid.Bounds.Height}"></ScaleTransform>
|
||||
<ScaleTransform ScaleX="1" ScaleY="{Binding #ContainerGrid.Bounds.Height}" />
|
||||
</Line.RenderTransform>
|
||||
</Line>
|
||||
</Canvas>
|
||||
|
||||
<!-- Horizontal scrolling -->
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ScrollViewer Name="TimelineScrollViewer"
|
||||
Offset="{Binding #TreeScrollViewer.Offset, Mode=OneWay}"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
||||
<ContentControl Content="{Binding TimelineViewModel}"></ContentControl>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer Grid.Row="1"
|
||||
Name="TimelineScrollViewer"
|
||||
Offset="{Binding #TreeScrollViewer.Offset, Mode=OneWay}"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
||||
<ContentControl Content="{Binding TimelineViewModel}" />
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- TODO: Databindings here -->
|
||||
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Input;
|
||||
@ -9,14 +8,14 @@ using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
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 Line _timelineLine;
|
||||
|
||||
public ProfileElementPropertiesView()
|
||||
public PropertiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
_timelineCaret = this.Get<Polygon>("TimelineCaret");
|
||||
@ -37,8 +36,8 @@ public class ProfileElementPropertiesView : ReactiveUserControl<ProfileElementPr
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
((DoubleTransition) _timelineCaret.Transitions![0]).Duration = TimeSpan.Zero;
|
||||
((DoubleTransition) _timelineLine.Transitions![0]).Duration = TimeSpan.Zero;
|
||||
// ((DoubleTransition) _timelineCaret.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.UI.Ninject.Factories;
|
||||
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.Services.ProfileEditor;
|
||||
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 IProfileEditorService _profileEditorService;
|
||||
private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedViewModels;
|
||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
|
||||
private ObservableCollection<ProfileElementPropertyGroupViewModel> _propertyGroupViewModels;
|
||||
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel, TimelineViewModel timelineViewModel)
|
||||
public PropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_layerPropertyVmFactory = layerPropertyVmFactory;
|
||||
_propertyGroupViewModels = new ObservableCollection<ProfileElementPropertyGroupViewModel>();
|
||||
_cachedViewModels = new Dictionary<LayerPropertyGroup, ProfileElementPropertyGroupViewModel>();
|
||||
PropertyGroupViewModels = new ObservableCollection<PropertyGroupViewModel>();
|
||||
_cachedViewModels = new Dictionary<LayerPropertyGroup, PropertyGroupViewModel>();
|
||||
PlaybackViewModel = playbackViewModel;
|
||||
TimelineViewModel = timelineViewModel;
|
||||
TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels);
|
||||
|
||||
// Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940
|
||||
this.WhenAnyValue(vm => vm.ProfileElement)
|
||||
@ -66,12 +65,8 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
||||
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
|
||||
public IObservable<bool> Playing => _profileEditorService.Playing;
|
||||
|
||||
public ObservableCollection<ProfileElementPropertyGroupViewModel> PropertyGroupViewModels
|
||||
{
|
||||
get => _propertyGroupViewModels;
|
||||
set => this.RaiseAndSetIfChanged(ref _propertyGroupViewModels, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; }
|
||||
|
||||
private void UpdateGroups()
|
||||
{
|
||||
if (ProfileElement == null)
|
||||
@ -80,7 +75,7 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
||||
return;
|
||||
}
|
||||
|
||||
ObservableCollection<ProfileElementPropertyGroupViewModel> viewModels = new();
|
||||
ObservableCollection<PropertyGroupViewModel> viewModels = new();
|
||||
if (Layer != null)
|
||||
{
|
||||
// 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
|
||||
for (int index = 0; index < viewModels.Count; index++)
|
||||
{
|
||||
ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel = viewModels[index];
|
||||
PropertyGroupViewModel propertyGroupViewModel = viewModels[index];
|
||||
if (index > PropertyGroupViewModels.Count - 1)
|
||||
PropertyGroupViewModels.Add(profileElementPropertyGroupViewModel);
|
||||
else if (!ReferenceEquals(PropertyGroupViewModels[index], profileElementPropertyGroupViewModel))
|
||||
PropertyGroupViewModels[index] = profileElementPropertyGroupViewModel;
|
||||
PropertyGroupViewModels.Add(propertyGroupViewModel);
|
||||
else if (!ReferenceEquals(PropertyGroupViewModels[index], propertyGroupViewModel))
|
||||
PropertyGroupViewModels[index] = propertyGroupViewModel;
|
||||
}
|
||||
|
||||
while (PropertyGroupViewModels.Count > viewModels.Count)
|
||||
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;
|
||||
|
||||
ProfileElementPropertyGroupViewModel createdVm;
|
||||
PropertyGroupViewModel createdVm;
|
||||
if (layerBrush != null)
|
||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerBrush);
|
||||
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup, layerBrush);
|
||||
else if (layerEffect != null)
|
||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerEffect);
|
||||
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup, layerEffect);
|
||||
else
|
||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup);
|
||||
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup);
|
||||
|
||||
_cachedViewModels[layerPropertyGroup] = createdVm;
|
||||
return createdVm;
|
||||
@ -7,15 +7,15 @@ using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.PropertyInput;
|
||||
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 IPropertyInputService _propertyInputService;
|
||||
@ -23,13 +23,14 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
||||
private bool _isExpanded;
|
||||
private bool _isVisible;
|
||||
|
||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService)
|
||||
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService)
|
||||
{
|
||||
_layerPropertyVmFactory = layerPropertyVmFactory;
|
||||
_propertyInputService = propertyInputService;
|
||||
Children = new ObservableCollection<ViewModelBase>();
|
||||
LayerPropertyGroup = layerPropertyGroup;
|
||||
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
|
||||
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
|
||||
|
||||
// TODO: Centralize visibility updating or do it here and dispose
|
||||
_isVisible = !LayerPropertyGroup.IsHidden;
|
||||
@ -37,14 +38,14 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
||||
PopulateChildren();
|
||||
}
|
||||
|
||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
||||
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
||||
BaseLayerBrush layerBrush)
|
||||
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
||||
{
|
||||
LayerBrush = layerBrush;
|
||||
}
|
||||
|
||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
||||
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
|
||||
BaseLayerEffect layerEffect)
|
||||
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
||||
{
|
||||
@ -57,6 +58,7 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
||||
public BaseLayerEffect? LayerEffect { get; }
|
||||
|
||||
public TreeGroupViewModel TreeGroupViewModel { get; }
|
||||
public TimelineGroupViewModel TimelineGroupViewModel { get; }
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
@ -83,9 +85,9 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
||||
return result;
|
||||
|
||||
foreach (ViewModelBase child in Children)
|
||||
if (child is ProfileElementPropertyViewModel profileElementPropertyViewModel)
|
||||
if (child is PropertyViewModel profileElementPropertyViewModel)
|
||||
result.AddRange(profileElementPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels());
|
||||
else if (child is ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel)
|
||||
else if (child is PropertyGroupViewModel profileElementPropertyGroupViewModel)
|
||||
result.AddRange(profileElementPropertyGroupViewModel.GetAllKeyframeViewModels(expandedOnly));
|
||||
|
||||
return result;
|
||||
@ -105,16 +107,16 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
||||
ILayerProperty? value = (ILayerProperty?) propertyInfo.GetValue(LayerPropertyGroup);
|
||||
// Ensure a supported input VM was found, otherwise don't add it
|
||||
if (value != null && _propertyInputService.CanCreatePropertyInputViewModel(value))
|
||||
Children.Add(_layerPropertyVmFactory.ProfileElementPropertyViewModel(value));
|
||||
Children.Add(_layerPropertyVmFactory.PropertyViewModel(value));
|
||||
}
|
||||
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
LayerPropertyGroup? value = (LayerPropertyGroup?) propertyInfo.GetValue(LayerPropertyGroup);
|
||||
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.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
using Artemis.UI.Shared;
|
||||
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 _isHighlighted;
|
||||
private bool _isVisible;
|
||||
|
||||
public ProfileElementPropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
|
||||
public PropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this);
|
||||
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
|
||||
public interface ITimelineKeyframeViewModel
|
||||
{
|
||||
@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
|
||||
public interface ITimelinePropertyViewModel : IReactiveObject
|
||||
{
|
||||
@ -5,7 +5,7 @@ using Artemis.UI.Shared;
|
||||
using Avalonia;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
|
||||
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 ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
|
||||
public interface ITreePropertyViewModel : IReactiveObject
|
||||
{
|
||||
@ -4,30 +4,30 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
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:profileElementProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreeGroupView">
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreeGroupView">
|
||||
<UserControl.Resources>
|
||||
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20"/>
|
||||
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
|
||||
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
|
||||
</UserControl.Resources>
|
||||
<UserControl.Styles>
|
||||
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
|
||||
<Setter Property="RenderTransform" Value="rotate(-90deg)" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
</UserControl.Styles>
|
||||
<StackPanel>
|
||||
<Border Name="Bd"
|
||||
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Height="29">
|
||||
<Grid Margin="{Binding Converter={StaticResource PropertyTreeMarginConverter}}" ColumnDefinitions="19,*">
|
||||
|
||||
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding !ProfileElementPropertyGroupViewModel.IsExpanded}"
|
||||
IsVisible="{Binding ProfileElementPropertyGroupViewModel.HasChildren}"
|
||||
|
||||
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding !PropertyGroupViewModel.IsExpanded}"
|
||||
IsVisible="{Binding PropertyGroupViewModel.HasChildren}"
|
||||
Kind="ChevronDown"
|
||||
Grid.Column="0"
|
||||
Margin="5 0"
|
||||
@ -40,15 +40,14 @@
|
||||
</avalonia:MaterialIcon.Transitions>
|
||||
</avalonia:MaterialIcon>
|
||||
|
||||
<StackPanel Grid.Column="1">
|
||||
<StackPanel Grid.Column="1">
|
||||
<!-- Type: None -->
|
||||
<TextBlock Text="{Binding LayerPropertyGroup.GroupDescription.Name}"
|
||||
ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="3 5 0 5"
|
||||
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.None}}">
|
||||
</TextBlock>
|
||||
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.None}}" />
|
||||
|
||||
<!-- Type: General -->
|
||||
<StackPanel Orientation="Horizontal"
|
||||
@ -178,18 +177,18 @@
|
||||
</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
|
||||
-->
|
||||
<ItemsControl Items="{Binding Children}"
|
||||
IsVisible="{Binding ProfileElementPropertyGroupViewModel.IsExpanded}"
|
||||
IsVisible="{Binding PropertyGroupViewModel.IsExpanded}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.DataTemplates>
|
||||
<DataTemplate DataType="profileElementProperties:ProfileElementPropertyGroupViewModel">
|
||||
<ContentControl Content="{Binding TreeGroupViewModel}" IsVisible="{Binding IsVisible}"></ContentControl>
|
||||
<DataTemplate DataType="properties:PropertyGroupViewModel">
|
||||
<ContentControl Content="{Binding TreeGroupViewModel}" IsVisible="{Binding IsVisible}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="profileElementProperties:ProfileElementPropertyViewModel">
|
||||
<ContentControl Content="{Binding TreePropertyViewModel}" IsVisible="{Binding IsVisible}"></ContentControl>
|
||||
<DataTemplate DataType="properties:PropertyViewModel">
|
||||
<ContentControl Content="{Binding TreePropertyViewModel}" IsVisible="{Binding IsVisible}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.DataTemplates>
|
||||
</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.LayerEffects;
|
||||
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.LayerBrushes;
|
||||
using Artemis.UI.Shared.LayerEffects;
|
||||
@ -18,7 +18,7 @@ using Ninject;
|
||||
using Ninject.Parameters;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
|
||||
public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
{
|
||||
@ -27,16 +27,16 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
private BrushConfigurationWindowViewModel? _brushConfigurationWindowViewModel;
|
||||
private EffectConfigurationWindowViewModel? _effectConfigurationWindowViewModel;
|
||||
|
||||
public TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel, IWindowService windowService, IProfileEditorService profileEditorService)
|
||||
public TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel, IWindowService windowService, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_windowService = windowService;
|
||||
_profileEditorService = profileEditorService;
|
||||
ProfileElementPropertyGroupViewModel = profileElementPropertyGroupViewModel;
|
||||
PropertyGroupViewModel = propertyGroupViewModel;
|
||||
DetermineGroupType();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@ -44,12 +44,12 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
}
|
||||
|
||||
|
||||
public ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel { get; }
|
||||
public LayerPropertyGroup LayerPropertyGroup => ProfileElementPropertyGroupViewModel.LayerPropertyGroup;
|
||||
public BaseLayerBrush? LayerBrush => ProfileElementPropertyGroupViewModel.LayerBrush;
|
||||
public BaseLayerEffect? LayerEffect => ProfileElementPropertyGroupViewModel.LayerEffect;
|
||||
public PropertyGroupViewModel PropertyGroupViewModel { get; }
|
||||
public LayerPropertyGroup LayerPropertyGroup => PropertyGroupViewModel.LayerPropertyGroup;
|
||||
public BaseLayerBrush? LayerBrush => PropertyGroupViewModel.LayerBrush;
|
||||
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; }
|
||||
|
||||
@ -148,9 +148,9 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
GroupType = LayerPropertyGroupType.General;
|
||||
else if (LayerPropertyGroup is LayerTransformProperties)
|
||||
GroupType = LayerPropertyGroupType.Transform;
|
||||
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerBrush != null)
|
||||
else if (LayerPropertyGroup.Parent == null && PropertyGroupViewModel.LayerBrush != null)
|
||||
GroupType = LayerPropertyGroupType.LayerBrushRoot;
|
||||
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerEffect != null)
|
||||
else if (LayerPropertyGroup.Parent == null && PropertyGroupViewModel.LayerEffect != null)
|
||||
GroupType = LayerPropertyGroupType.LayerEffectRoot;
|
||||
else
|
||||
GroupType = LayerPropertyGroupType.None;
|
||||
@ -6,12 +6,11 @@
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreePropertyView">
|
||||
<UserControl.Resources>
|
||||
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
|
||||
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
|
||||
</UserControl.Resources>
|
||||
<Border Name="Bd"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreePropertyView">
|
||||
<UserControl.Resources>
|
||||
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
|
||||
</UserControl.Resources>
|
||||
<Border Name="Bd"
|
||||
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Height="29">
|
||||
@ -24,10 +23,10 @@
|
||||
IsChecked="{Binding KeyframesEnabled}"
|
||||
IsEnabled="{Binding LayerProperty.KeyframesSupported}"
|
||||
VerticalAlignment="Center" Padding="-25">
|
||||
<avalonia:MaterialIcon Kind="Stopwatch" />
|
||||
</ToggleButton>
|
||||
<avalonia:MaterialIcon Kind="Stopwatch" />
|
||||
</ToggleButton>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
<TextBlock Grid.Column="1"
|
||||
Margin="5,0,0,0"
|
||||
Padding="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
@ -36,30 +35,30 @@
|
||||
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<ContentControl Grid.Column="2"
|
||||
<ContentControl Grid.Column="2"
|
||||
Margin="5 0"
|
||||
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}"
|
||||
Classes="icon-button"
|
||||
ToolTip.Tip="Reset the property to its default value."
|
||||
Width="24"
|
||||
Height="24">
|
||||
<avalonia:MaterialIcon Kind="BackupRestore" />
|
||||
</Button>
|
||||
<avalonia:MaterialIcon Kind="BackupRestore" />
|
||||
</Button>
|
||||
|
||||
<ToggleButton Grid.Column="4" Classes="icon-button"
|
||||
ToolTip.Tip="Change the property's data binding"
|
||||
Width="24"
|
||||
Height="24"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
|
||||
IsChecked="{Binding HasDataBinding, Mode=OneWay}">
|
||||
<avalonia:MaterialIcon Kind="VectorLink" />
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
<ToggleButton Grid.Column="4" Classes="icon-button"
|
||||
ToolTip.Tip="Change the property's data binding"
|
||||
Width="24"
|
||||
Height="24"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
|
||||
IsChecked="{Binding HasDataBinding, Mode=OneWay}">
|
||||
<avalonia:MaterialIcon Kind="VectorLink" />
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
</UserControl>
|
||||
</Border>
|
||||
</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.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||
|
||||
public class BrushConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
|
||||
{
|
||||
@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.LayerBrushes;
|
||||
using Artemis.UI.Shared.LayerEffects;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||
|
||||
public class BrushConfigurationWindowViewModel : DialogViewModelBase<object?>
|
||||
{
|
||||
@ -12,7 +11,7 @@ public class BrushConfigurationWindowViewModel : DialogViewModelBase<object?>
|
||||
{
|
||||
ConfigurationViewModel = configurationViewModel;
|
||||
Configuration = configuration;
|
||||
|
||||
|
||||
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.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||
|
||||
public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
|
||||
{
|
||||
@ -3,7 +3,7 @@ using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.LayerEffects;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
private void ConfigurationViewModelOnCloseRequested(object? sender, EventArgs e)
|
||||
{
|
||||
if (CanClose())
|
||||
@ -67,7 +67,7 @@
|
||||
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
|
||||
|
||||
<Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True">
|
||||
<ContentControl Content="{Binding ProfileElementPropertiesViewModel}"/>
|
||||
<ContentControl Content="{Binding PropertiesViewModel}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Screens.ProfileEditor.MenuBar;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties;
|
||||
using Artemis.UI.Screens.ProfileEditor.StatusBar;
|
||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
@ -25,13 +25,13 @@ namespace Artemis.UI.Screens.ProfileEditor
|
||||
ProfileTreeViewModel profileTreeViewModel,
|
||||
ProfileEditorTitleBarViewModel profileEditorTitleBarViewModel,
|
||||
MenuBarViewModel menuBarViewModel,
|
||||
ProfileElementPropertiesViewModel profileElementPropertiesViewModel,
|
||||
PropertiesViewModel propertiesViewModel,
|
||||
StatusBarViewModel statusBarViewModel)
|
||||
: base(hostScreen, "profile-editor")
|
||||
{
|
||||
VisualEditorViewModel = visualEditorViewModel;
|
||||
ProfileTreeViewModel = profileTreeViewModel;
|
||||
ProfileElementPropertiesViewModel = profileElementPropertiesViewModel;
|
||||
PropertiesViewModel = propertiesViewModel;
|
||||
StatusBarViewModel = statusBarViewModel;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
@ -46,7 +46,7 @@ namespace Artemis.UI.Screens.ProfileEditor
|
||||
public VisualEditorViewModel VisualEditorViewModel { get; }
|
||||
public ProfileTreeViewModel ProfileTreeViewModel { get; }
|
||||
public MenuBarViewModel? MenuBarViewModel { get; }
|
||||
public ProfileElementPropertiesViewModel ProfileElementPropertiesViewModel { get; }
|
||||
public PropertiesViewModel PropertiesViewModel { get; }
|
||||
public StatusBarViewModel StatusBarViewModel { get; }
|
||||
|
||||
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user