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 { @@ -12,7 +11,7 @@ public class BrushConfigurationWindowViewModel : DialogViewModelBase { ConfigurationViewModel = configurationViewModel; Configuration = configuration; - + ConfigurationViewModel.CloseRequested += ConfigurationViewModelOnCloseRequested; } diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml new file mode 100644 index 000000000..3c11ca511 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml.cs similarity index 87% rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowView.axaml.cs rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml.cs index 2e03a2339..8f1683152 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowView.axaml.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.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 EffectConfigurationWindowView : ReactiveCoreWindow { diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowViewModel.cs similarity index 92% rename from src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowViewModel.cs rename to src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowViewModel.cs index f9289f450..979cab5c1 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Windows/EffectConfigurationWindowViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowViewModel.cs @@ -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 { @@ -22,7 +22,7 @@ public class EffectConfigurationWindowViewModel : DialogViewModelBase { return ConfigurationViewModel.CanClose() && Dispatcher.UIThread.InvokeAsync(async () => await ConfigurationViewModel.CanCloseAsync()).GetAwaiter().GetResult(); } - + private void ConfigurationViewModelOnCloseRequested(object? sender, EventArgs e) { if (CanClose()) diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml index db6236efc..f0a87752a 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml @@ -67,7 +67,7 @@ - + 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;