diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
index 6bf4af171..78be76ec5 100644
--- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
+++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
@@ -73,7 +73,4 @@
-
-
-
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs b/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs
index db8d7825a..3cd56734a 100644
--- a/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs
@@ -44,9 +44,11 @@ namespace Artemis.UI.Shared.Controls
AvaloniaProperty.Register(nameof(InputElement), notifying: OnInputElementChanged);
private Rect? _displayRect;
+ private Rect? _absoluteRect;
private IControl? _oldInputElement;
private Point _startPosition;
-
+ private Point _absoluteStartPosition;
+
///
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();
diff --git a/src/Avalonia/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs b/src/Avalonia/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs
index 67b33b39a..f522cd3fe 100644
--- a/src/Avalonia/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs
@@ -13,17 +13,23 @@ namespace Artemis.UI.Shared.Events
///
/// Creates a new instance of the class.
///
- public SelectionRectangleEventArgs(Rect rectangle, KeyModifiers keyModifiers)
+ public SelectionRectangleEventArgs(Rect rectangle, Rect absoluteRectangle, KeyModifiers keyModifiers)
{
KeyModifiers = keyModifiers;
Rectangle = rectangle;
+ AbsoluteRectangle = absoluteRectangle;
}
///
- /// Gets the rectangle that was selected when the event occurred.
+ /// Gets the rectangle relative to the parent that was selected when the event occurred.
///
public Rect Rectangle { get; }
+ ///
+ /// Gets the rectangle relative to the window that was selected when the event occurred.
+ ///
+ public Rect AbsoluteRectangle { get; }
+
///
/// Gets the key modifiers that where pressed when the event occurred.
///
diff --git a/src/Avalonia/Artemis.UI.Shared/Extensions/DataValidationErrorsExtensions.cs b/src/Avalonia/Artemis.UI.Shared/Extensions/ControlExtensions.cs
similarity index 100%
rename from src/Avalonia/Artemis.UI.Shared/Extensions/DataValidationErrorsExtensions.cs
rename to src/Avalonia/Artemis.UI.Shared/Extensions/ControlExtensions.cs
diff --git a/src/Avalonia/Artemis.UI.Shared/Extensions/VisualExtensions.cs b/src/Avalonia/Artemis.UI.Shared/Extensions/VisualExtensions.cs
new file mode 100644
index 000000000..d01649eb0
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Extensions/VisualExtensions.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia;
+using Avalonia.VisualTree;
+
+namespace Artemis.UI.Shared.Extensions;
+
+///
+/// Provides extension methods for Avalonia's type
+///
+public static class VisualExtensions
+{
+ ///
+ /// Returns a recursive list of all visual children of type .
+ ///
+ /// The type the children should have.
+ /// The root visual at which to start searching.
+ /// A recursive list of all visual children of type .
+ public static List GetVisualChildrenOfType(this IVisual root)
+ {
+ List result = new();
+
+ List? 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(visualChild));
+ }
+
+ return result;
+ }
+
+ ///
+ /// Returns a recursive list of all visual children with a data context of type .
+ ///
+ /// The type of data context the children should have.
+ /// The root visual at which to start searching.
+ /// A recursive list of all visual children with a data context of type .
+ public static List GetVisualChildrenOfDataContextType(this IVisual root)
+ {
+ List result = new();
+
+ List? 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(visualChild));
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ToggleLayerPropertyKeyframes.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ToggleLayerPropertyKeyframes.cs
new file mode 100644
index 000000000..ceb9b6cb3
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ToggleLayerPropertyKeyframes.cs
@@ -0,0 +1,41 @@
+using Artemis.Core;
+
+namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
+
+///
+/// Represents a profile editor command that can be used to enable or disable keyframes on a layer property.
+///
+///
+public class ToggleLayerPropertyKeyframes : IProfileEditorCommand
+{
+ private readonly bool _enable;
+ private readonly LayerProperty _layerProperty;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public ToggleLayerPropertyKeyframes(LayerProperty layerProperty, bool enable)
+ {
+ _layerProperty = layerProperty;
+ _enable = enable;
+ }
+
+ #region Implementation of IProfileEditorCommand
+
+ ///
+ public string DisplayName => _enable ? "Enable keyframes" : "Disable keyframes";
+
+ ///
+ public void Execute()
+ {
+ _layerProperty.KeyframesEnabled = _enable;
+ }
+
+ ///
+ public void Undo()
+ {
+ _layerProperty.KeyframesEnabled = !_enable;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs b/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs
index a7a8f18ef..0b2e9f253 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs
@@ -22,6 +22,7 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel
[AllowNull] private T _inputValue;
private TimeSpan _time;
+ private bool _updating;
///
/// Creates a new instance of the class
@@ -156,6 +157,9 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel
///
protected virtual void ApplyInputValue()
{
+ if (_updating)
+ return;
+
if (InputDragging)
ProfileEditorService.ChangeTime(_time);
else if (ValidationContext.IsValid)
@@ -164,16 +168,25 @@ public abstract class PropertyInputViewModel : 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()
diff --git a/src/Avalonia/Artemis.UI/Artemis.UI.csproj b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
index f6b0cd9c0..a3a0e83e1 100644
--- a/src/Avalonia/Artemis.UI/Artemis.UI.csproj
+++ b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
@@ -48,4 +48,9 @@
+
+
+ PropertiesView.axaml
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs b/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs
index ccc0676ae..d1a290b01 100644
--- a/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs
+++ b/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs
@@ -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;
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
index 41059cafa..eb04a5a1f 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
@@ -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;
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml
index a60310ccc..2891a9276 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml
@@ -12,6 +12,7 @@
Value="{Binding InputValue}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
AcceptsExpression="True"
+ SimpleNumberFormat="F3"
VerticalAlignment="Center"/>
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml
index 9ffc8374f..8fcd7a37b 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml
@@ -13,11 +13,13 @@
Value="{Binding X}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
ToolTip.Tip="X-coordinate (horizontal)"
+ SimpleNumberFormat="F3"
VerticalAlignment="Center" />
,
,
propertyGroupViewModels);
+ TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
// TreeViewModel TreeViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel, IObservableCollection 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);
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs b/src/Avalonia/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs
index ab31a993e..8f20274c1 100644
--- a/src/Avalonia/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs
+++ b/src/Avalonia/Artemis.UI/Ninject/InstanceProviders/LayerPropertyViewModelInstanceProvider.cs
@@ -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
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineKeyframeViewModel.cs
deleted file mode 100644
index 1bc1eaa7c..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineKeyframeViewModel.cs
+++ /dev/null
@@ -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 : ActivatableViewModelBase, ITimelineKeyframeViewModel
- {
-
- private bool _isSelected;
- private string _timestamp;
- private double _x;
- private readonly IProfileEditorService _profileEditorService;
-
- public TimelineKeyframeViewModel(LayerPropertyKeyframe layerPropertyKeyframe, IProfileEditorService profileEditorService)
- {
- _profileEditorService = profileEditorService;
- LayerPropertyKeyframe = layerPropertyKeyframe;
- EasingViewModels = new ObservableCollection();
-
- 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 LayerPropertyKeyframe { get; }
- public ObservableCollection 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()
- .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
- }
-}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelinePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelinePropertyViewModel.cs
deleted file mode 100644
index 63bb39e76..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelinePropertyViewModel.cs
+++ /dev/null
@@ -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 : ActivatableViewModelBase, ITimelinePropertyViewModel
- {
- private readonly IProfileEditorService _profileEditorService;
- public LayerProperty LayerProperty { get; }
- public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
- public ObservableCollection> KeyframeViewModels { get; }
-
- public TimelinePropertyViewModel(LayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel, IProfileEditorService profileEditorService)
- {
- _profileEditorService = profileEditorService;
- LayerProperty = layerProperty;
- ProfileElementPropertyViewModel = profileElementPropertyViewModel;
- KeyframeViewModels = new ObservableCollection>();
- }
-
- #region Implementation of ITimelinePropertyViewModel
-
- public List GetAllKeyframeViewModels()
- {
- return KeyframeViewModels.Cast().ToList();
- }
-
- public void WipeKeyframes(TimeSpan? start, TimeSpan? end)
- {
- start ??= TimeSpan.Zero;
- end ??= TimeSpan.MaxValue;
-
-
- List> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList();
- foreach (LayerPropertyKeyframe keyframe in toShift)
- LayerProperty.RemoveKeyframe(keyframe);
-
- UpdateKeyframes();
- }
-
- public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
- {
- start ??= TimeSpan.Zero;
- end ??= TimeSpan.MaxValue;
-
- List> toShift = LayerProperty.Keyframes.Where(k => k.Position > start && k.Position < end).ToList();
- foreach (LayerPropertyKeyframe keyframe in toShift)
- keyframe.Position += amount;
-
- UpdateKeyframes();
- }
-
- #endregion
-
- private void UpdateKeyframes()
- {
- // Only show keyframes if they are enabled
- if (LayerProperty.KeyframesEnabled)
- {
- List> keyframes = LayerProperty.Keyframes.ToList();
-
- List> toRemove = KeyframeViewModels.Where(t => !keyframes.Contains(t.LayerPropertyKeyframe)).ToList();
- foreach (TimelineKeyframeViewModel timelineKeyframeViewModel in toRemove)
- KeyframeViewModels.Remove(timelineKeyframeViewModel);
- List> toAdd = keyframes.Where(k => KeyframeViewModels.All(t => t.LayerPropertyKeyframe != k)).Select(k => new TimelineKeyframeViewModel(k, _profileEditorService)).ToList();
- foreach (TimelineKeyframeViewModel timelineKeyframeViewModel in toAdd)
- KeyframeViewModels.Add(timelineKeyframeViewModel);
- }
- else
- KeyframeViewModels.Clear();
-
- foreach (TimelineKeyframeViewModel timelineKeyframeViewModel in KeyframeViewModels)
- timelineKeyframeViewModel.Update();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineView.axaml
deleted file mode 100644
index f02e22ad9..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineView.axaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineView.axaml.cs
deleted file mode 100644
index 276fb2637..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineView.axaml.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Avalonia.Markup.Xaml;
-using Avalonia.ReactiveUI;
-
-namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline
-{
- public partial class TimelineView : ReactiveUserControl
- {
- public TimelineView()
- {
- InitializeComponent();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
- }
-}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineViewModel.cs
deleted file mode 100644
index d36490fca..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineViewModel.cs
+++ /dev/null
@@ -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? _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? snapTimes = null)
- {
- return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/Dialogs/LayerBrushPresetViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/Dialogs/LayerBrushPresetViewModel.cs
deleted file mode 100644
index a379909aa..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/Dialogs/LayerBrushPresetViewModel.cs
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml.cs
deleted file mode 100644
index 8926536a3..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml.cs
+++ /dev/null
@@ -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
- {
- 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;
- }
- }
-}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyView.axaml.cs
deleted file mode 100644
index 2783ec52e..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyView.axaml.cs
+++ /dev/null
@@ -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
- {
- public TreePropertyView()
- {
- this.WhenActivated(d =>
- {
- if (ViewModel != null)
- {
- Observable.FromEventPattern(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e)
- .Subscribe(_ => this.BringIntoView())
- .DisposeWith(d);
- }
- });
- InitializeComponent();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyViewModel.cs
deleted file mode 100644
index 7b41088a0..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyViewModel.cs
+++ /dev/null
@@ -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 : ActivatableViewModelBase, ITreePropertyViewModel
-{
- public TreePropertyViewModel(LayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel, IPropertyInputService propertyInputService)
- {
- LayerProperty = layerProperty;
- ProfileElementPropertyViewModel = profileElementPropertyViewModel;
- PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
- }
-
- public LayerProperty LayerProperty { get; }
- public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
- public PropertyInputViewModel? 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;
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowView.axaml
deleted file mode 100644
index 353d93d61..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowView.axaml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowView.axaml
deleted file mode 100644
index ed573d41a..000000000
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowView.axaml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml
similarity index 76%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml
index bd8454752..20d51558a 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml
@@ -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">
-
+
-
+
@@ -61,11 +60,11 @@
PointerMoved="TimelineCaret_OnPointerMoved"
Points="-8,0 -8,8 0,20, 8,8 8,0"
Fill="{DynamicResource SystemAccentColorLight1}">
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
+
+
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml.cs
similarity index 89%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml.cs
index e3ce4eb5b..58953aed1 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml.cs
@@ -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
+public class PropertiesView : ReactiveUserControl
{
private readonly Polygon _timelineCaret;
private readonly Line _timelineLine;
- public ProfileElementPropertiesView()
+ public PropertiesView()
{
InitializeComponent();
_timelineCaret = this.Get("TimelineCaret");
@@ -37,8 +36,8 @@ public class ProfileElementPropertiesView : ReactiveUserControl _cachedViewModels;
- private readonly IProfileEditorService _profileEditorService;
+ private readonly Dictionary _cachedViewModels;
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
- private ObservableAsPropertyHelper? _profileElement;
+ private readonly IProfileEditorService _profileEditorService;
private ObservableAsPropertyHelper? _pixelsPerSecond;
- private ObservableCollection _propertyGroupViewModels;
+ private ObservableAsPropertyHelper? _profileElement;
///
- 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();
- _cachedViewModels = new Dictionary();
+ PropertyGroupViewModels = new ObservableCollection();
+ _cachedViewModels = new Dictionary();
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 Playing => _profileEditorService.Playing;
- public ObservableCollection PropertyGroupViewModels
- {
- get => _propertyGroupViewModels;
- set => this.RaiseAndSetIfChanged(ref _propertyGroupViewModels, value);
- }
-
+ public ObservableCollection PropertyGroupViewModels { get; }
+
private void UpdateGroups()
{
if (ProfileElement == null)
@@ -80,7 +75,7 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
return;
}
- ObservableCollection viewModels = new();
+ ObservableCollection 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;
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyGroupViewModel.cs
similarity index 73%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyGroupViewModel.cs
index 03e34276a..71e47c7a0 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyGroupViewModel.cs
@@ -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();
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});
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyViewModel.cs
similarity index 75%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyViewModel.cs
index b7e5305f7..3f6e00d25 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyViewModel.cs
@@ -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);
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/ITimelineKeyframeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelineKeyframeViewModel.cs
similarity index 88%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/ITimelineKeyframeViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelineKeyframeViewModel.cs
index 8cc49f77b..4dfaa86d2 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/ITimelineKeyframeViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelineKeyframeViewModel.cs
@@ -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
{
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/ITimelinePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelinePropertyViewModel.cs
similarity index 80%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/ITimelinePropertyViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelinePropertyViewModel.cs
index 21cc94893..791587907 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/ITimelinePropertyViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelinePropertyViewModel.cs
@@ -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
{
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineEasingViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingViewModel.cs
similarity index 94%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineEasingViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingViewModel.cs
index 23be99e6a..d43aaf9c6 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Timeline/TimelineEasingViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingViewModel.cs
@@ -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
{
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml
new file mode 100644
index 000000000..22bc2b8fb
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml.cs
new file mode 100644
index 000000000..8999b791a
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml.cs
@@ -0,0 +1,17 @@
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
+
+public class TimelineGroupView : ReactiveUserControl
+{
+ public TimelineGroupView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupViewModel.cs
new file mode 100644
index 000000000..e6ea9e6d4
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupViewModel.cs
@@ -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? _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? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
+
+ public ObservableCollection? KeyframePosition
+ {
+ get => _keyframePosition;
+ set => this.RaiseAndSetIfChanged(ref _keyframePosition, value);
+ }
+
+ private void UpdateKeyframePositions()
+ {
+ KeyframePosition = new ObservableCollection(PropertyGroupViewModel
+ .GetAllKeyframeViewModels(false)
+ .Select(p => p.Position.TotalSeconds * _pixelsPerSecond));
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml
new file mode 100644
index 000000000..fb1782854
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml.cs
new file mode 100644
index 000000000..2067d97c5
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml.cs
@@ -0,0 +1,17 @@
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
+
+public class TimelineKeyframeView : ReactiveUserControl
+{
+ public TimelineKeyframeView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeViewModel.cs
new file mode 100644
index 000000000..6c57d5063
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeViewModel.cs
@@ -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 : ActivatableViewModelBase, ITimelineKeyframeViewModel
+{
+ private bool _isSelected;
+ private string _timestamp;
+ private double _x;
+
+ public TimelineKeyframeViewModel(LayerPropertyKeyframe layerPropertyKeyframe, IProfileEditorService profileEditorService)
+ {
+ IProfileEditorService profileEditorService1 = profileEditorService;
+ _timestamp = "0.000";
+ LayerPropertyKeyframe = layerPropertyKeyframe;
+ EasingViewModels = new ObservableCollection();
+
+ 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 LayerPropertyKeyframe { get; }
+ public ObservableCollection 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()
+ .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
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml
new file mode 100644
index 000000000..6a69dbb07
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml.cs
new file mode 100644
index 000000000..082ad8d15
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml.cs
@@ -0,0 +1,17 @@
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
+
+public class TimelinePropertyView : ReactiveUserControl
+{
+ public TimelinePropertyView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyViewModel.cs
new file mode 100644
index 000000000..d908f3e02
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyViewModel.cs
@@ -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 : ActivatableViewModelBase, ITimelinePropertyViewModel
+{
+ private readonly IProfileEditorService _profileEditorService;
+
+ public TimelinePropertyViewModel(LayerProperty layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService)
+ {
+ _profileEditorService = profileEditorService;
+ LayerProperty = layerProperty;
+ PropertyViewModel = propertyViewModel;
+ KeyframeViewModels = new ObservableCollection>();
+
+ this.WhenActivated(d =>
+ {
+ Observable.FromEventPattern(x => LayerProperty.KeyframeAdded += x, x => LayerProperty.KeyframeAdded -= x)
+ .Subscribe(_ => UpdateKeyframes())
+ .DisposeWith(d);
+ Observable.FromEventPattern(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x)
+ .Subscribe(_ => UpdateKeyframes())
+ .DisposeWith(d);
+
+ UpdateKeyframes();
+ });
+ }
+
+ public LayerProperty LayerProperty { get; }
+ public PropertyViewModel PropertyViewModel { get; }
+ public ObservableCollection> KeyframeViewModels { get; }
+
+ private void UpdateKeyframes()
+ {
+ // Only show keyframes if they are enabled
+ if (LayerProperty.KeyframesEnabled)
+ {
+ List> keyframes = LayerProperty.Keyframes.ToList();
+
+ List> toRemove = KeyframeViewModels.Where(t => !keyframes.Contains(t.LayerPropertyKeyframe)).ToList();
+ foreach (TimelineKeyframeViewModel timelineKeyframeViewModel in toRemove)
+ KeyframeViewModels.Remove(timelineKeyframeViewModel);
+ List> toAdd = keyframes.Where(k => KeyframeViewModels.All(t => t.LayerPropertyKeyframe != k))
+ .Select(k => new TimelineKeyframeViewModel(k, _profileEditorService)).ToList();
+ foreach (TimelineKeyframeViewModel timelineKeyframeViewModel in toAdd)
+ KeyframeViewModels.Add(timelineKeyframeViewModel);
+ }
+ else
+ {
+ KeyframeViewModels.Clear();
+ }
+
+ foreach (TimelineKeyframeViewModel timelineKeyframeViewModel in KeyframeViewModels)
+ timelineKeyframeViewModel.Update();
+ }
+
+ #region Implementation of ITimelinePropertyViewModel
+
+ public List GetAllKeyframeViewModels()
+ {
+ return KeyframeViewModels.Cast().ToList();
+ }
+
+ public void WipeKeyframes(TimeSpan? start, TimeSpan? end)
+ {
+ start ??= TimeSpan.Zero;
+ end ??= TimeSpan.MaxValue;
+
+
+ List> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList();
+ foreach (LayerPropertyKeyframe keyframe in toShift)
+ LayerProperty.RemoveKeyframe(keyframe);
+
+ UpdateKeyframes();
+ }
+
+ public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
+ {
+ start ??= TimeSpan.Zero;
+ end ??= TimeSpan.MaxValue;
+
+ List> toShift = LayerProperty.Keyframes.Where(k => k.Position > start && k.Position < end).ToList();
+ foreach (LayerPropertyKeyframe keyframe in toShift)
+ keyframe.Position += amount;
+
+ UpdateKeyframes();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml
new file mode 100644
index 000000000..f23e942a5
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml
@@ -0,0 +1,25 @@
+
+
+ 28
+ 29
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml.cs
new file mode 100644
index 000000000..46c75eefa
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml.cs
@@ -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
+{
+ 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 keyframeViews = this.GetVisualChildrenOfType().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().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);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineViewModel.cs
new file mode 100644
index 000000000..ff1944188
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineViewModel.cs
@@ -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? _caretPosition;
+
+ public TimelineViewModel(ObservableCollection 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 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? snapTimes = null)
+ {
+ return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
+ }
+
+ public void SelectKeyframes(List keyframes, bool expand)
+ {
+ List expandedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).ToList();
+ List 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 expandedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).ToList();
+ List 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetViewModel.cs
new file mode 100644
index 000000000..9b614e488
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/Dialogs/LayerBrushPresetViewModel.cs
@@ -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; }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/ITreePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ITreePropertyViewModel.cs
similarity index 73%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/ITreePropertyViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ITreePropertyViewModel.cs
index 15e9f84a3..66027d37c 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/ITreePropertyViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ITreePropertyViewModel.cs
@@ -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
{
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml
similarity index 90%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml
index 5f693289b..999fc6af2 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml
@@ -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">
-
+
-
-
-
+
-
-
-
+
-
+ IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.None}}" />
-
-
+
+
-
-
+
+
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml.cs
new file mode 100644
index 000000000..2e6219049
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml.cs
@@ -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
+{
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupViewModel.cs
similarity index 81%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupViewModel.cs
index 896c52c0c..04e33559d 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupViewModel.cs
@@ -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? Children => ProfileElementPropertyGroupViewModel.IsExpanded ? ProfileElementPropertyGroupViewModel.Children : null;
+ public ObservableCollection? 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;
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml
similarity index 63%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyView.axaml
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml
index e7934cced..edda3d7d9 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml
@@ -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">
-
-
-
-
-
+
+
+
+
@@ -24,10 +23,10 @@
IsChecked="{Binding KeyframesEnabled}"
IsEnabled="{Binding LayerProperty.KeyframesSupported}"
VerticalAlignment="Center" Padding="-25">
-
-
+
+
-
-
+ ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}" />
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml.cs
new file mode 100644
index 000000000..dc16daa37
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml.cs
@@ -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
+{
+ public TreePropertyView()
+ {
+ this.WhenActivated(d =>
+ {
+ if (ViewModel != null)
+ Observable.FromEventPattern(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e)
+ .Subscribe(_ => this.BringIntoView())
+ .DisposeWith(d);
+ });
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyViewModel.cs
new file mode 100644
index 000000000..ada3ad296
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyViewModel.cs
@@ -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 : ActivatableViewModelBase, ITreePropertyViewModel
+{
+ private readonly IProfileEditorService _profileEditorService;
+
+ public TreePropertyViewModel(LayerProperty 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 LayerProperty { get; }
+ public PropertyViewModel PropertyViewModel { get; }
+ public PropertyInputViewModel? 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(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;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml
new file mode 100644
index 000000000..12bd2cd1e
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml.cs
similarity index 87%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowView.axaml.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml.cs
index 000f65596..42011416c 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml.cs
@@ -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
{
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowViewModel.cs
similarity index 89%
rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowViewModel.cs
rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowViewModel.cs
index 51d8c380a..f8a7a8383 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/BrushConfigurationWindowViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowViewModel.cs
@@ -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
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
index 8d52bfbfd..e75dfb043 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
@@ -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;