1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profile editor - Ported keyframe display and selection

This commit is contained in:
Robert 2022-01-18 23:27:35 +01:00
parent 66a2e51979
commit 98180df5f2
62 changed files with 1199 additions and 641 deletions

View File

@ -73,7 +73,4 @@
<ItemGroup>
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
</ItemGroup>
<ItemGroup>
<Folder Include="Extensions\" />
</ItemGroup>
</Project>

View File

@ -44,9 +44,11 @@ namespace Artemis.UI.Shared.Controls
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
private Rect? _displayRect;
private Rect? _absoluteRect;
private IControl? _oldInputElement;
private Point _startPosition;
private Point _absoluteStartPosition;
/// <inheritdoc />
public SelectionRectangle()
{
@ -140,6 +142,7 @@ namespace Artemis.UI.Shared.Controls
e.Pointer.Capture(this);
_startPosition = e.GetPosition(Parent);
_absoluteStartPosition = e.GetPosition(VisualRoot);
_displayRect = null;
}
@ -149,11 +152,18 @@ namespace Artemis.UI.Shared.Controls
return;
Point currentPosition = e.GetPosition(Parent);
Point absoluteCurrentPosition = e.GetPosition(VisualRoot);
_displayRect = new Rect(
new Point(Math.Min(_startPosition.X, currentPosition.X), Math.Min(_startPosition.Y, currentPosition.Y)),
new Point(Math.Max(_startPosition.X, currentPosition.X), Math.Max(_startPosition.Y, currentPosition.Y))
);
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, e.KeyModifiers));
_absoluteRect = new Rect(
new Point(Math.Min(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Min(_absoluteStartPosition.Y, absoluteCurrentPosition.Y)),
new Point(Math.Max(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Max(_absoluteStartPosition.Y, absoluteCurrentPosition.Y))
);
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
InvalidateVisual();
}
@ -164,8 +174,11 @@ namespace Artemis.UI.Shared.Controls
e.Pointer.Capture(null);
if (_displayRect != null)
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, e.KeyModifiers));
if (_displayRect != null && _absoluteRect != null)
{
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
e.Handled = true;
}
_displayRect = null;
InvalidateVisual();

View File

@ -13,17 +13,23 @@ namespace Artemis.UI.Shared.Events
/// <summary>
/// Creates a new instance of the <see cref="SelectionRectangleEventArgs" /> class.
/// </summary>
public SelectionRectangleEventArgs(Rect rectangle, KeyModifiers keyModifiers)
public SelectionRectangleEventArgs(Rect rectangle, Rect absoluteRectangle, KeyModifiers keyModifiers)
{
KeyModifiers = keyModifiers;
Rectangle = rectangle;
AbsoluteRectangle = absoluteRectangle;
}
/// <summary>
/// Gets the rectangle that was selected when the event occurred.
/// Gets the rectangle relative to the parent that was selected when the event occurred.
/// </summary>
public Rect Rectangle { get; }
/// <summary>
/// Gets the rectangle relative to the window that was selected when the event occurred.
/// </summary>
public Rect AbsoluteRectangle { get; }
/// <summary>
/// Gets the key modifiers that where pressed when the event occurred.
/// </summary>

View File

@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.VisualTree;
namespace Artemis.UI.Shared.Extensions;
/// <summary>
/// Provides extension methods for Avalonia's <see cref="IVisual" /> type
/// </summary>
public static class VisualExtensions
{
/// <summary>
/// Returns a recursive list of all visual children of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type the children should have.</typeparam>
/// <param name="root">The root visual at which to start searching.</param>
/// <returns>A recursive list of all visual children of type <typeparamref name="T" />.</returns>
public static List<T> GetVisualChildrenOfType<T>(this IVisual root)
{
List<T> result = new();
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList();
if (visualChildren == null || !visualChildren.Any())
return result;
foreach (IVisual visualChild in visualChildren)
{
if (visualChild is T toFind)
result.Add(toFind);
result.AddRange(GetVisualChildrenOfType<T>(visualChild));
}
return result;
}
/// <summary>
/// Returns a recursive list of all visual children with a data context of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type of data context the children should have.</typeparam>
/// <param name="root">The root visual at which to start searching.</param>
/// <returns>A recursive list of all visual children with a data context of type <typeparamref name="T" />.</returns>
public static List<T> GetVisualChildrenOfDataContextType<T>(this IVisual root)
{
List<T> result = new();
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList();
if (visualChildren == null || !visualChildren.Any())
return result;
foreach (IVisual visualChild in visualChildren)
{
if (visualChild is IDataContextProvider dataContextProvider && dataContextProvider.DataContext is T toFind)
result.Add(toFind);
result.AddRange(GetVisualChildrenOfType<T>(visualChild));
}
return result;
}
}

View File

@ -0,0 +1,41 @@
using Artemis.Core;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// <summary>
/// Represents a profile editor command that can be used to enable or disable keyframes on a layer property.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ToggleLayerPropertyKeyframes<T> : IProfileEditorCommand
{
private readonly bool _enable;
private readonly LayerProperty<T> _layerProperty;
/// <summary>
/// Creates a new instance of the <see cref="ToggleLayerPropertyKeyframes{T}"/> class.
/// </summary>
public ToggleLayerPropertyKeyframes(LayerProperty<T> layerProperty, bool enable)
{
_layerProperty = layerProperty;
_enable = enable;
}
#region Implementation of IProfileEditorCommand
/// <inheritdoc />
public string DisplayName => _enable ? "Enable keyframes" : "Disable keyframes";
/// <inheritdoc />
public void Execute()
{
_layerProperty.KeyframesEnabled = _enable;
}
/// <inheritdoc />
public void Undo()
{
_layerProperty.KeyframesEnabled = !_enable;
}
#endregion
}

View File

@ -22,6 +22,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
[AllowNull] private T _inputValue;
private TimeSpan _time;
private bool _updating;
/// <summary>
/// Creates a new instance of the <see cref="PropertyInputViewModel{T}" /> class
@ -156,6 +157,9 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
/// </summary>
protected virtual void ApplyInputValue()
{
if (_updating)
return;
if (InputDragging)
ProfileEditorService.ChangeTime(_time);
else if (ValidationContext.IsValid)
@ -164,16 +168,25 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
private void UpdateInputValue()
{
// Avoid unnecessary UI updates and validator cycles
if (_inputValue != null && _inputValue.Equals(LayerProperty.CurrentValue) || _inputValue == null && LayerProperty.CurrentValue == null)
return;
try
{
_updating = true;
// Avoid unnecessary UI updates and validator cycles
if (_inputValue != null && _inputValue.Equals(LayerProperty.CurrentValue) || _inputValue == null && LayerProperty.CurrentValue == null)
return;
// Override the input value
_inputValue = LayerProperty.CurrentValue;
// Override the input value
_inputValue = LayerProperty.CurrentValue;
// Notify a change in the input value
OnInputValueChanged();
this.RaisePropertyChanged(nameof(InputValue));
// Notify a change in the input value
OnInputValueChanged();
this.RaisePropertyChanged(nameof(InputValue));
}
finally
{
_updating = false;
}
}
private void UpdateDataBinding()

View File

@ -48,4 +48,9 @@
<Resource Include="Assets\Images\Logo\bow.ico" />
<Resource Include="Assets\Images\Logo\bow.svg" />
</ItemGroup>
<ItemGroup>
<Compile Update="Screens\ProfileEditor\Panels\Properties\PropertiesView.axaml.cs">
<DependentUpon>PropertiesView.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@ -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;

View File

@ -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;

View File

@ -12,6 +12,7 @@
Value="{Binding InputValue}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
AcceptsExpression="True"
SimpleNumberFormat="F3"
VerticalAlignment="Center"/>
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center"/>
</StackPanel>

View File

@ -13,11 +13,13 @@
Value="{Binding X}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
ToolTip.Tip="X-coordinate (horizontal)"
SimpleNumberFormat="F3"
VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">,</TextBlock>
<controls:NumberBox Classes="condensed"
Value="{Binding Y}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
SimpleNumberFormat="F3"
VerticalAlignment="Center" />
<TextBlock Width="25"
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"

View File

@ -13,11 +13,13 @@
Value="{Binding Height}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
ToolTip.Tip="Height"
SimpleNumberFormat="F3"
VerticalAlignment="Center"/>
<TextBlock VerticalAlignment="Center">,</TextBlock>
<controls:NumberBox Classes="condensed"
Value="{Binding Width}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
SimpleNumberFormat="F3"
VerticalAlignment="Center"/>
<TextBlock Width="25"
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"

View File

@ -5,10 +5,10 @@ using Artemis.Core.LayerEffects;
using Artemis.UI.Screens.Device;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.ProfileEditor;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
using Artemis.UI.Screens.ProfileEditor.Properties;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
using Artemis.UI.Screens.Settings;
using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Screens.SurfaceEditor;
@ -67,13 +67,15 @@ namespace Artemis.UI.Ninject.Factories
public interface ILayerPropertyVmFactory : IVmFactory
{
ProfileElementPropertyViewModel ProfileElementPropertyViewModel(ILayerProperty layerProperty);
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
PropertyViewModel PropertyViewModel(ILayerProperty layerProperty);
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
TreeGroupViewModel TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel);
// TimelineGroupViewModel TimelineGroupViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
// TreeViewModel TreeViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel, IObservableCollection<ProfileElementPropertyGroupViewModel> profileElementPropertyGroups);
// EffectsViewModel EffectsViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
@ -83,7 +85,7 @@ namespace Artemis.UI.Ninject.Factories
public interface IPropertyVmFactory
{
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel);
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
}
}

View File

@ -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

View File

@ -1,169 +0,0 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins;
using DynamicData;
using ReactiveUI;
using Disposable = System.Reactive.Disposables.Disposable;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline
{
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
{
private bool _isSelected;
private string _timestamp;
private double _x;
private readonly IProfileEditorService _profileEditorService;
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerPropertyKeyframe = layerPropertyKeyframe;
EasingViewModels = new ObservableCollection<TimelineEasingViewModel>();
this.WhenActivated(d =>
{
_profileEditorService.PixelsPerSecond.Subscribe(p =>
{
_pixelsPerSecond = p;
_profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
Disposable.Create(() =>
{
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
}).DisposeWith(d);
}).DisposeWith(d);
});
}
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
public ObservableCollection<TimelineEasingViewModel> EasingViewModels { get; }
public double X
{
get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value);
}
public string Timestamp
{
get => _timestamp;
set => this.RaiseAndSetIfChanged(ref _timestamp, value);
}
public bool IsSelected
{
get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
}
public TimeSpan Position => LayerPropertyKeyframe.Position;
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
public void Update()
{
X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
}
#region Movement
private TimeSpan? _offset;
private double _pixelsPerSecond;
public void ReleaseMovement()
{
_offset = null;
}
public void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source)
{
if (source == this)
{
_offset = null;
return;
}
if (_offset != null)
return;
_offset = LayerPropertyKeyframe.Position - source.Position;
}
public void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source)
{
if (source == this || _offset == null)
return;
UpdatePosition(source.Position + _offset.Value);
}
public void UpdatePosition(TimeSpan position)
{
throw new NotImplementedException();
// if (position < TimeSpan.Zero)
// LayerPropertyKeyframe.Position = TimeSpan.Zero;
// else if (position > _profileEditorService.SelectedProfileElement.Timeline.Length)
// LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.Timeline.Length;
// else
// LayerPropertyKeyframe.Position = position;
Update();
}
#endregion
#region Easing
public void PopulateEasingViewModels()
{
if (EasingViewModels.Any())
return;
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
.Cast<Easings.Functions>()
.Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction)));
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected;
}
public void ClearEasingViewModels()
{
EasingViewModels.Clear();
}
private void TimelineEasingViewModelOnEasingModeSelected(object? sender, EventArgs e)
{
if (sender is TimelineEasingViewModel timelineEasingViewModel)
SelectEasingMode(timelineEasingViewModel);
}
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
{
throw new NotImplementedException();
LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
// Set every selection to false except on the VM that made the change
foreach (TimelineEasingViewModel propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
propertyTrackEasingViewModel.IsEasingModeSelected = false;
}
#endregion
#region Context menu actions
public void Delete(bool save = true)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@ -1,81 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline
{
public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelinePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
public LayerProperty<T> LayerProperty { get; }
public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
public ObservableCollection<TimelineKeyframeViewModel<T>> KeyframeViewModels { get; }
public TimelinePropertyViewModel(LayerProperty<T> layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerProperty = layerProperty;
ProfileElementPropertyViewModel = profileElementPropertyViewModel;
KeyframeViewModels = new ObservableCollection<TimelineKeyframeViewModel<T>>();
}
#region Implementation of ITimelinePropertyViewModel
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels()
{
return KeyframeViewModels.Cast<ITimelineKeyframeViewModel>().ToList();
}
public void WipeKeyframes(TimeSpan? start, TimeSpan? end)
{
start ??= TimeSpan.Zero;
end ??= TimeSpan.MaxValue;
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList();
foreach (LayerPropertyKeyframe<T> keyframe in toShift)
LayerProperty.RemoveKeyframe(keyframe);
UpdateKeyframes();
}
public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
{
start ??= TimeSpan.Zero;
end ??= TimeSpan.MaxValue;
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position > start && k.Position < end).ToList();
foreach (LayerPropertyKeyframe<T> keyframe in toShift)
keyframe.Position += amount;
UpdateKeyframes();
}
#endregion
private void UpdateKeyframes()
{
// Only show keyframes if they are enabled
if (LayerProperty.KeyframesEnabled)
{
List<LayerPropertyKeyframe<T>> keyframes = LayerProperty.Keyframes.ToList();
List<TimelineKeyframeViewModel<T>> toRemove = KeyframeViewModels.Where(t => !keyframes.Contains(t.LayerPropertyKeyframe)).ToList();
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in toRemove)
KeyframeViewModels.Remove(timelineKeyframeViewModel);
List<TimelineKeyframeViewModel<T>> toAdd = keyframes.Where(k => KeyframeViewModels.All(t => t.LayerPropertyKeyframe != k)).Select(k => new TimelineKeyframeViewModel<T>(k, _profileEditorService)).ToList();
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in toAdd)
KeyframeViewModels.Add(timelineKeyframeViewModel);
}
else
KeyframeViewModels.Clear();
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in KeyframeViewModels)
timelineKeyframeViewModel.Update();
}
}
}

View File

@ -1,15 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline.TimelineView">
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
<ItemsControl.ItemTemplate>
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
<ContentControl Content="{Binding TimelineGroupViewModel}" />
</TreeDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>

View File

@ -1,18 +0,0 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline
{
public partial class TimelineView : ReactiveUserControl<TimelineViewModel>
{
public TimelineView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
public class TimelineViewModel : ActivatableViewModelBase
{
private readonly IProfileEditorService _profileEditorService;
private ObservableAsPropertyHelper<double>? _caretPosition;
public TimelineViewModel(IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
this.WhenActivated(d =>
{
_caretPosition = _profileEditorService.Time
.CombineLatest(_profileEditorService.PixelsPerSecond, (t, p) => t.TotalSeconds * p)
.ToProperty(this, vm => vm.CaretPosition)
.DisposeWith(d);
});
}
public double CaretPosition => _caretPosition?.Value ?? 0.0;
public void ChangeTime(TimeSpan newTime)
{
_profileEditorService.ChangeTime(newTime);
}
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? snapTimes = null)
{
return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
}
}

View File

@ -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;
}
}
}

View File

@ -1,25 +0,0 @@
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree
{
public partial class TreeGroupView : ReactiveUserControl<TreeGroupViewModel>
{
public TreeGroupView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (ViewModel != null)
ViewModel.ProfileElementPropertyGroupViewModel.IsExpanded = !ViewModel.ProfileElementPropertyGroupViewModel.IsExpanded;
}
}
}

View File

@ -1,33 +0,0 @@
using System;
using System.Reactive.Linq;
using Artemis.Core;
using Avalonia.Controls;
using Avalonia.Controls.Mixins;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree
{
public partial class TreePropertyView : ReactiveUserControl<ITreePropertyViewModel>
{
public TreePropertyView()
{
this.WhenActivated(d =>
{
if (ViewModel != null)
{
Observable.FromEventPattern<LayerPropertyEventArgs>(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e)
.Subscribe(_ => this.BringIntoView())
.DisposeWith(d);
}
});
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -1,35 +0,0 @@
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.PropertyInput;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropertyViewModel
{
public TreePropertyViewModel(LayerProperty<T> layerProperty, ProfileElementPropertyViewModel profileElementPropertyViewModel, IPropertyInputService propertyInputService)
{
LayerProperty = layerProperty;
ProfileElementPropertyViewModel = profileElementPropertyViewModel;
PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
}
public LayerProperty<T> LayerProperty { get; }
public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
public ILayerProperty BaseLayerProperty => LayerProperty;
public bool HasDataBinding => LayerProperty.HasDataBinding;
public double GetDepth()
{
int depth = 0;
LayerPropertyGroup? current = LayerProperty.LayerPropertyGroup;
while (current != null)
{
depth++;
current = current.Parent;
}
return depth;
}
}

View File

@ -1,16 +0,0 @@
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows.BrushConfigurationWindowView"
Title="Artemis | Brush configuration"
Width="{Binding Configuration.DialogWidth}"
Height="{Binding Configuration.DialogHeight}">
<Panel>
<ContentControl Content="{Binding ConfigurationViewModel}"></ContentControl>
</Panel>
</controls:CoreWindow>

View File

@ -1,16 +0,0 @@
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows.EffectConfigurationWindowView"
Title="Artemis | Effect configuration"
Width="{Binding Configuration.DialogWidth}"
Height="{Binding Configuration.DialogHeight}">
<Panel>
<ContentControl Content="{Binding ConfigurationViewModel}"></ContentControl>
</Panel>
</controls:CoreWindow>

View File

@ -2,14 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:Artemis.UI.Controls"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.ProfileElementPropertiesView">
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView">
<Grid ColumnDefinitions="*,Auto,*" Name="ContainerGrid">
<Grid RowDefinitions="48,*">
<ContentControl Grid.Row="0" Content="{Binding PlaybackViewModel}"></ContentControl>
<ContentControl Grid.Row="0" Content="{Binding PlaybackViewModel}" />
<ScrollViewer Grid.Row="1"
Grid.Column="0"
@ -18,7 +17,7 @@
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
<ItemsControl.ItemTemplate>
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{Binding Children}">
<ContentControl Content="{Binding TreeGroupViewModel}" />
</TreeDataTemplate>
</ItemsControl.ItemTemplate>
@ -61,11 +60,11 @@
PointerMoved="TimelineCaret_OnPointerMoved"
Points="-8,0 -8,8 0,20, 8,8 8,0"
Fill="{DynamicResource SystemAccentColorLight1}">
<Polygon.Transitions>
<Transitions>
<DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition>
</Transitions>
</Polygon.Transitions>
<!-- <Polygon.Transitions> -->
<!-- <Transitions> -->
<!-- <DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition> -->
<!-- </Transitions> -->
<!-- </Polygon.Transitions> -->
</Polygon>
<Line Name="TimelineLine"
Canvas.Left="{Binding TimelineViewModel.CaretPosition}"
@ -78,25 +77,24 @@
StrokeThickness="2"
Stroke="{DynamicResource SystemAccentColorLight1}"
RenderTransformOrigin="0,0">
<Line.Transitions>
<Transitions>
<DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition>
</Transitions>
</Line.Transitions>
<!-- <Line.Transitions> -->
<!-- <Transitions> -->
<!-- <DoubleTransition Property="Canvas.Left" Duration="0.05"></DoubleTransition> -->
<!-- </Transitions> -->
<!-- </Line.Transitions> -->
<Line.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="{Binding #ContainerGrid.Bounds.Height}"></ScaleTransform>
<ScaleTransform ScaleX="1" ScaleY="{Binding #ContainerGrid.Bounds.Height}" />
</Line.RenderTransform>
</Line>
</Canvas>
<!-- Horizontal scrolling -->
<ScrollViewer Grid.Row="1">
<ScrollViewer Name="TimelineScrollViewer"
Offset="{Binding #TreeScrollViewer.Offset, Mode=OneWay}"
VerticalScrollBarVisibility="Hidden"
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
<ContentControl Content="{Binding TimelineViewModel}"></ContentControl>
</ScrollViewer>
<ScrollViewer Grid.Row="1"
Name="TimelineScrollViewer"
Offset="{Binding #TreeScrollViewer.Offset, Mode=OneWay}"
VerticalScrollBarVisibility="Hidden"
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
<ContentControl Content="{Binding TimelineViewModel}" />
</ScrollViewer>
<!-- TODO: Databindings here -->

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
@ -9,14 +8,14 @@ using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.VisualTree;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
namespace Artemis.UI.Screens.ProfileEditor.Properties;
public class ProfileElementPropertiesView : ReactiveUserControl<ProfileElementPropertiesViewModel>
public class PropertiesView : ReactiveUserControl<PropertiesViewModel>
{
private readonly Polygon _timelineCaret;
private readonly Line _timelineLine;
public ProfileElementPropertiesView()
public PropertiesView()
{
InitializeComponent();
_timelineCaret = this.Get<Polygon>("TimelineCaret");
@ -37,8 +36,8 @@ public class ProfileElementPropertiesView : ReactiveUserControl<ProfileElementPr
// }
// else
// {
((DoubleTransition) _timelineCaret.Transitions![0]).Duration = TimeSpan.Zero;
((DoubleTransition) _timelineLine.Transitions![0]).Duration = TimeSpan.Zero;
// ((DoubleTransition) _timelineCaret.Transitions![0]).Duration = TimeSpan.Zero;
// ((DoubleTransition) _timelineLine.Transitions![0]).Duration = TimeSpan.Zero;
// }
}

View File

@ -10,31 +10,30 @@ using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Playback;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
namespace Artemis.UI.Screens.ProfileEditor.Properties;
public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
public class PropertiesViewModel : ActivatableViewModelBase
{
private readonly Dictionary<LayerPropertyGroup, ProfileElementPropertyGroupViewModel> _cachedViewModels;
private readonly IProfileEditorService _profileEditorService;
private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedViewModels;
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
private readonly IProfileEditorService _profileEditorService;
private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
private ObservableCollection<ProfileElementPropertyGroupViewModel> _propertyGroupViewModels;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
/// <inheritdoc />
public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel, TimelineViewModel timelineViewModel)
public PropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel)
{
_profileEditorService = profileEditorService;
_layerPropertyVmFactory = layerPropertyVmFactory;
_propertyGroupViewModels = new ObservableCollection<ProfileElementPropertyGroupViewModel>();
_cachedViewModels = new Dictionary<LayerPropertyGroup, ProfileElementPropertyGroupViewModel>();
PropertyGroupViewModels = new ObservableCollection<PropertyGroupViewModel>();
_cachedViewModels = new Dictionary<LayerPropertyGroup, PropertyGroupViewModel>();
PlaybackViewModel = playbackViewModel;
TimelineViewModel = timelineViewModel;
TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels);
// Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940
this.WhenAnyValue(vm => vm.ProfileElement)
@ -66,12 +65,8 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
public IObservable<bool> Playing => _profileEditorService.Playing;
public ObservableCollection<ProfileElementPropertyGroupViewModel> PropertyGroupViewModels
{
get => _propertyGroupViewModels;
set => this.RaiseAndSetIfChanged(ref _propertyGroupViewModels, value);
}
public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; }
private void UpdateGroups()
{
if (ProfileElement == null)
@ -80,7 +75,7 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
return;
}
ObservableCollection<ProfileElementPropertyGroupViewModel> viewModels = new();
ObservableCollection<PropertyGroupViewModel> viewModels = new();
if (Layer != null)
{
// Add base VMs
@ -100,29 +95,29 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
// Map the most recent collection of VMs to the current list of VMs, making as little changes to the collection as possible
for (int index = 0; index < viewModels.Count; index++)
{
ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel = viewModels[index];
PropertyGroupViewModel propertyGroupViewModel = viewModels[index];
if (index > PropertyGroupViewModels.Count - 1)
PropertyGroupViewModels.Add(profileElementPropertyGroupViewModel);
else if (!ReferenceEquals(PropertyGroupViewModels[index], profileElementPropertyGroupViewModel))
PropertyGroupViewModels[index] = profileElementPropertyGroupViewModel;
PropertyGroupViewModels.Add(propertyGroupViewModel);
else if (!ReferenceEquals(PropertyGroupViewModels[index], propertyGroupViewModel))
PropertyGroupViewModels[index] = propertyGroupViewModel;
}
while (PropertyGroupViewModels.Count > viewModels.Count)
PropertyGroupViewModels.RemoveAt(PropertyGroupViewModels.Count - 1);
}
private ProfileElementPropertyGroupViewModel GetOrCreateViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect)
private PropertyGroupViewModel GetOrCreateViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect)
{
if (_cachedViewModels.TryGetValue(layerPropertyGroup, out ProfileElementPropertyGroupViewModel? cachedVm))
if (_cachedViewModels.TryGetValue(layerPropertyGroup, out PropertyGroupViewModel? cachedVm))
return cachedVm;
ProfileElementPropertyGroupViewModel createdVm;
PropertyGroupViewModel createdVm;
if (layerBrush != null)
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerBrush);
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup, layerBrush);
else if (layerEffect != null)
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerEffect);
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup, layerEffect);
else
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup);
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup);
_cachedViewModels[layerPropertyGroup] = createdVm;
return createdVm;

View File

@ -7,15 +7,15 @@ using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Timeline;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.PropertyInput;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
namespace Artemis.UI.Screens.ProfileEditor.Properties;
public class ProfileElementPropertyGroupViewModel : ViewModelBase
public class PropertyGroupViewModel : ViewModelBase
{
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private readonly IPropertyInputService _propertyInputService;
@ -23,13 +23,14 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
private bool _isExpanded;
private bool _isVisible;
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService)
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService)
{
_layerPropertyVmFactory = layerPropertyVmFactory;
_propertyInputService = propertyInputService;
Children = new ObservableCollection<ViewModelBase>();
LayerPropertyGroup = layerPropertyGroup;
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
// TODO: Centralize visibility updating or do it here and dispose
_isVisible = !LayerPropertyGroup.IsHidden;
@ -37,14 +38,14 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
PopulateChildren();
}
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
BaseLayerBrush layerBrush)
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
{
LayerBrush = layerBrush;
}
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService,
BaseLayerEffect layerEffect)
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
{
@ -57,6 +58,7 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
public BaseLayerEffect? LayerEffect { get; }
public TreeGroupViewModel TreeGroupViewModel { get; }
public TimelineGroupViewModel TimelineGroupViewModel { get; }
public bool IsVisible
{
@ -83,9 +85,9 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
return result;
foreach (ViewModelBase child in Children)
if (child is ProfileElementPropertyViewModel profileElementPropertyViewModel)
if (child is PropertyViewModel profileElementPropertyViewModel)
result.AddRange(profileElementPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels());
else if (child is ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel)
else if (child is PropertyGroupViewModel profileElementPropertyGroupViewModel)
result.AddRange(profileElementPropertyGroupViewModel.GetAllKeyframeViewModels(expandedOnly));
return result;
@ -105,16 +107,16 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
ILayerProperty? value = (ILayerProperty?) propertyInfo.GetValue(LayerPropertyGroup);
// Ensure a supported input VM was found, otherwise don't add it
if (value != null && _propertyInputService.CanCreatePropertyInputViewModel(value))
Children.Add(_layerPropertyVmFactory.ProfileElementPropertyViewModel(value));
Children.Add(_layerPropertyVmFactory.PropertyViewModel(value));
}
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
{
LayerPropertyGroup? value = (LayerPropertyGroup?) propertyInfo.GetValue(LayerPropertyGroup);
if (value != null)
Children.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(value));
Children.Add(_layerPropertyVmFactory.PropertyGroupViewModel(value));
}
}
HasChildren = Children.Any(i => i is ProfileElementPropertyViewModel {IsVisible: true} || i is ProfileElementPropertyGroupViewModel {IsVisible: true});
HasChildren = Children.Any(i => i is PropertyViewModel {IsVisible: true} || i is PropertyGroupViewModel {IsVisible: true});
}
}

View File

@ -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);

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -0,0 +1,58 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:properties="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineGroupView">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0"
Height="{DynamicResource RailsHeight}"
IsVisible="{Binding !PropertyGroupViewModel.IsExpanded}"
Items="{Binding KeyframePositions}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Styles>
<Style Selector="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding}" />
</Style>
</ItemsControl.Styles>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="{DynamicResource ControlFillColorDisabledBrush}"
Stroke="White"
StrokeThickness="0"
Width="10"
Height="10"
Margin="-5,6,0,0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Rectangle Grid.Row="1" HorizontalAlignment="Stretch" Fill="{DynamicResource ButtonBorderBrush}" Height="1" />
<ItemsControl Grid.Row="2"
Items="{Binding Children}"
IsVisible="{Binding PropertyGroupViewModel.IsExpanded}"
HorizontalAlignment="Stretch">
<ItemsControl.DataTemplates>
<DataTemplate DataType="properties:PropertyGroupViewModel">
<ContentControl Content="{Binding TimelineGroupViewModel}" IsVisible="{Binding IsVisible}" />
</DataTemplate>
<DataTemplate DataType="properties:PropertyViewModel">
<ContentControl Content="{Binding TimelinePropertyViewModel}" IsVisible="{Binding IsVisible}" />
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
</Grid>
</UserControl>

View File

@ -0,0 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineGroupView : ReactiveUserControl<TimelineGroupViewModel>
{
public TimelineGroupView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineGroupViewModel : ActivatableViewModelBase
{
private ObservableCollection<double>? _keyframePosition;
private int _pixelsPerSecond;
public TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel, IProfileEditorService profileEditorService)
{
PropertyGroupViewModel = propertyGroupViewModel;
this.WhenActivated(d =>
{
profileEditorService.PixelsPerSecond.Subscribe(p =>
{
_pixelsPerSecond = p;
UpdateKeyframePositions();
});
this.WhenAnyValue(vm => vm.PropertyGroupViewModel.IsExpanded).Subscribe(_ => UpdateKeyframePositions()).DisposeWith(d);
PropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
});
}
public PropertyGroupViewModel PropertyGroupViewModel { get; }
public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
public ObservableCollection<double>? KeyframePosition
{
get => _keyframePosition;
set => this.RaiseAndSetIfChanged(ref _keyframePosition, value);
}
private void UpdateKeyframePositions()
{
KeyframePosition = new ObservableCollection<double>(PropertyGroupViewModel
.GetAllKeyframeViewModels(false)
.Select(p => p.Position.TotalSeconds * _pixelsPerSecond));
}
}

View File

@ -0,0 +1,76 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineKeyframeView"
ClipToBounds="False"
Height="{DynamicResource RailsHeight}">
<Ellipse Fill="{DynamicResource SystemAccentColorLight2}"
Stroke="White"
Width="10"
Height="10"
Margin="-5,0,0,0"
ToolTip.Tip="{Binding Timestamp}"
Classes.selected="{Binding IsSelected}">
<Ellipse.Styles>
<Style Selector="Ellipse">
<Setter Property="StrokeThickness" Value="0" />
</Style>
<Style Selector="Ellipse.selected">
<Setter Property="StrokeThickness" Value="1" />
</Style>
</Ellipse.Styles>
<Ellipse.Transitions>
<Transitions>
<DoubleTransition Property="StrokeThickness" Duration="0:0:0.25" />
</Transitions>
</Ellipse.Transitions>
<Ellipse.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Easing" Items="{Binding EasingViewModels}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Creation" />
</MenuItem.Icon>
<MenuItem.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
StrokeThickness="1"
Points="{Binding EasingPoints}"
Stretch="Uniform"
Width="20"
Height="20"
Margin="0 0 10 0" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<Separator />
<MenuItem Header="Duplicate" Command="{Binding DuplicateKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+D">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentDuplicate" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy" Command="{Binding CopyKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+C">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentCopy" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Paste" Command="{Binding PasteKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+V">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentPaste" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Delete" Command="{Binding DeleteKeyframes}" InputGesture="Delete">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Delete" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Ellipse.ContextFlyout>
</Ellipse>
</UserControl>

View File

@ -0,0 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewModel>
{
public TimelineKeyframeView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,167 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins;
using DynamicData;
using ReactiveUI;
using Disposable = System.Reactive.Disposables.Disposable;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineKeyframeViewModel
{
private bool _isSelected;
private string _timestamp;
private double _x;
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
{
IProfileEditorService profileEditorService1 = profileEditorService;
_timestamp = "0.000";
LayerPropertyKeyframe = layerPropertyKeyframe;
EasingViewModels = new ObservableCollection<TimelineEasingViewModel>();
this.WhenActivated(d =>
{
profileEditorService1.PixelsPerSecond.Subscribe(p =>
{
_pixelsPerSecond = p;
profileEditorService1.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
Disposable.Create(() =>
{
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
}).DisposeWith(d);
}).DisposeWith(d);
});
}
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
public ObservableCollection<TimelineEasingViewModel> EasingViewModels { get; }
public double X
{
get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value);
}
public string Timestamp
{
get => _timestamp;
set => this.RaiseAndSetIfChanged(ref _timestamp, value);
}
public void Update()
{
X = _pixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
}
public bool IsSelected
{
get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
}
public TimeSpan Position => LayerPropertyKeyframe.Position;
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
#region Context menu actions
public void Delete(bool save = true)
{
throw new NotImplementedException();
}
#endregion
#region Movement
private TimeSpan? _offset;
private double _pixelsPerSecond;
public void ReleaseMovement()
{
_offset = null;
}
public void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source)
{
if (source == this)
{
_offset = null;
return;
}
if (_offset != null)
return;
_offset = LayerPropertyKeyframe.Position - source.Position;
}
public void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source)
{
if (source == this || _offset == null)
return;
UpdatePosition(source.Position + _offset.Value);
}
public void UpdatePosition(TimeSpan position)
{
throw new NotImplementedException();
// if (position < TimeSpan.Zero)
// LayerPropertyKeyframe.Position = TimeSpan.Zero;
// else if (position > _profileEditorService.SelectedProfileElement.Timeline.Length)
// LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.Timeline.Length;
// else
// LayerPropertyKeyframe.Position = position;
Update();
}
#endregion
#region Easing
public void PopulateEasingViewModels()
{
if (EasingViewModels.Any())
return;
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
.Cast<Easings.Functions>()
.Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction)));
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected;
}
public void ClearEasingViewModels()
{
EasingViewModels.Clear();
}
private void TimelineEasingViewModelOnEasingModeSelected(object? sender, EventArgs e)
{
if (sender is TimelineEasingViewModel timelineEasingViewModel)
SelectEasingMode(timelineEasingViewModel);
}
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
{
throw new NotImplementedException();
LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
// Set every selection to false except on the VM that made the change
foreach (TimelineEasingViewModel propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
propertyTrackEasingViewModel.IsEasingModeSelected = false;
}
#endregion
}

View File

@ -0,0 +1,21 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelinePropertyView">
<Border Height="{DynamicResource RailsBorderHeight}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource ButtonBorderBrush}">
<ItemsControl Items="{Binding KeyframeViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Styles>
<Style Selector="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}" />
</Style>
</ItemsControl.Styles>
</ItemsControl>
</Border>
</UserControl>

View File

@ -0,0 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelinePropertyView : ReactiveUserControl<ITimelinePropertyViewModel>
{
public TimelinePropertyView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelinePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
public TimelinePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerProperty = layerProperty;
PropertyViewModel = propertyViewModel;
KeyframeViewModels = new ObservableCollection<TimelineKeyframeViewModel<T>>();
this.WhenActivated(d =>
{
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.KeyframeAdded += x, x => LayerProperty.KeyframeAdded -= x)
.Subscribe(_ => UpdateKeyframes())
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x)
.Subscribe(_ => UpdateKeyframes())
.DisposeWith(d);
UpdateKeyframes();
});
}
public LayerProperty<T> LayerProperty { get; }
public PropertyViewModel PropertyViewModel { get; }
public ObservableCollection<TimelineKeyframeViewModel<T>> KeyframeViewModels { get; }
private void UpdateKeyframes()
{
// Only show keyframes if they are enabled
if (LayerProperty.KeyframesEnabled)
{
List<LayerPropertyKeyframe<T>> keyframes = LayerProperty.Keyframes.ToList();
List<TimelineKeyframeViewModel<T>> toRemove = KeyframeViewModels.Where(t => !keyframes.Contains(t.LayerPropertyKeyframe)).ToList();
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in toRemove)
KeyframeViewModels.Remove(timelineKeyframeViewModel);
List<TimelineKeyframeViewModel<T>> toAdd = keyframes.Where(k => KeyframeViewModels.All(t => t.LayerPropertyKeyframe != k))
.Select(k => new TimelineKeyframeViewModel<T>(k, _profileEditorService)).ToList();
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in toAdd)
KeyframeViewModels.Add(timelineKeyframeViewModel);
}
else
{
KeyframeViewModels.Clear();
}
foreach (TimelineKeyframeViewModel<T> timelineKeyframeViewModel in KeyframeViewModels)
timelineKeyframeViewModel.Update();
}
#region Implementation of ITimelinePropertyViewModel
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels()
{
return KeyframeViewModels.Cast<ITimelineKeyframeViewModel>().ToList();
}
public void WipeKeyframes(TimeSpan? start, TimeSpan? end)
{
start ??= TimeSpan.Zero;
end ??= TimeSpan.MaxValue;
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList();
foreach (LayerPropertyKeyframe<T> keyframe in toShift)
LayerProperty.RemoveKeyframe(keyframe);
UpdateKeyframes();
}
public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
{
start ??= TimeSpan.Zero;
end ??= TimeSpan.MaxValue;
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position > start && k.Position < end).ToList();
foreach (LayerPropertyKeyframe<T> keyframe in toShift)
keyframe.Position += amount;
UpdateKeyframes();
}
#endregion
}

View File

@ -0,0 +1,25 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineView">
<UserControl.Resources>
<x:Double x:Key="RailsHeight">28</x:Double>
<x:Double x:Key="RailsBorderHeight">29</x:Double>
</UserControl.Resources>
<Grid Background="Transparent" PointerMoved="InputElement_OnPointerMoved" PointerReleased="InputElement_OnPointerReleased">
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
<ItemsControl.ItemTemplate>
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{Binding Children}">
<ContentControl Content="{Binding TimelineGroupViewModel}" />
</TreeDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<controls:SelectionRectangle InputElement="{Binding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished"></controls:SelectionRectangle>
</Grid>
</UserControl>

View File

@ -0,0 +1,68 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.UI.Shared.Events;
using Artemis.UI.Shared.Extensions;
using Avalonia;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineView : ReactiveUserControl<TimelineViewModel>
{
private bool _draggedCursor;
public TimelineView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
{
if (ViewModel == null)
return;
List<TimelineKeyframeView> keyframeViews = this.GetVisualChildrenOfType<TimelineKeyframeView>().Where(k =>
{
Rect hitTestRect = k.TransformedBounds != null ? k.TransformedBounds.Value.Bounds.TransformToAABB(k.TransformedBounds.Value.Transform) : Rect.Empty;
return e.AbsoluteRectangle.Intersects(hitTestRect);
}).ToList();
ViewModel.SelectKeyframes(keyframeViews.Where(kv => kv.ViewModel != null).Select(kv => kv.ViewModel!).ToList(), e.KeyModifiers.HasFlag(KeyModifiers.Shift));
}
private void InputElement_OnPointerMoved(object? sender, PointerEventArgs e)
{
if (_draggedCursor)
return;
_draggedCursor = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (ViewModel == null)
return;
if (_draggedCursor)
{
_draggedCursor = false;
return;
}
Point position = e.GetPosition(VisualRoot);
TimelineKeyframeView? keyframeView = this.GetVisualChildrenOfType<TimelineKeyframeView>().Where(k =>
{
Rect hitTestRect = k.TransformedBounds != null ? k.TransformedBounds.Value.Bounds.TransformToAABB(k.TransformedBounds.Value.Transform) : Rect.Empty;
return hitTestRect.Contains(position);
}).FirstOrDefault(kv => kv.ViewModel != null);
ViewModel.SelectKeyframe(keyframeView?.ViewModel, e.KeyModifiers.HasFlag(KeyModifiers.Shift), false);
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Controls.Mixins;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineViewModel : ActivatableViewModelBase
{
private readonly IProfileEditorService _profileEditorService;
private ObservableAsPropertyHelper<double>? _caretPosition;
public TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels, IProfileEditorService profileEditorService)
{
PropertyGroupViewModels = propertyGroupViewModels;
_profileEditorService = profileEditorService;
this.WhenActivated(d =>
{
_caretPosition = _profileEditorService.Time
.CombineLatest(_profileEditorService.PixelsPerSecond, (t, p) => t.TotalSeconds * p)
.ToProperty(this, vm => vm.CaretPosition)
.DisposeWith(d);
});
}
public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; }
public double CaretPosition => _caretPosition?.Value ?? 0.0;
public void ChangeTime(TimeSpan newTime)
{
_profileEditorService.ChangeTime(newTime);
}
public TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? snapTimes = null)
{
return _profileEditorService.SnapToTimeline(time, tolerance, snapToSegments, snapToCurrentTime, snapTimes);
}
public void SelectKeyframes(List<ITimelineKeyframeViewModel> keyframes, bool expand)
{
List<ITimelineKeyframeViewModel> expandedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).ToList();
List<ITimelineKeyframeViewModel> collapsedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(false)).Except(expandedKeyframes).ToList();
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in collapsedKeyframes)
timelineKeyframeViewModel.IsSelected = false;
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in expandedKeyframes)
{
if (timelineKeyframeViewModel.IsSelected && expand)
continue;
timelineKeyframeViewModel.IsSelected = keyframes.Contains(timelineKeyframeViewModel);
}
}
public void SelectKeyframe(ITimelineKeyframeViewModel? clicked, bool selectBetween, bool toggle)
{
List<ITimelineKeyframeViewModel> expandedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(true)).ToList();
List<ITimelineKeyframeViewModel> collapsedKeyframes = PropertyGroupViewModels.SelectMany(g => g.GetAllKeyframeViewModels(false)).Except(expandedKeyframes).ToList();
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in collapsedKeyframes)
timelineKeyframeViewModel.IsSelected = false;
if (clicked == null)
{
foreach (ITimelineKeyframeViewModel timelineKeyframeViewModel in expandedKeyframes)
timelineKeyframeViewModel.IsSelected = false;
return;
}
if (selectBetween)
{
int selectedIndex = expandedKeyframes.FindIndex(k => k.IsSelected);
// If nothing is selected, select only the clicked
if (selectedIndex == -1)
{
clicked.IsSelected = true;
return;
}
foreach (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes)
keyframeViewModel.IsSelected = false;
int clickedIndex = expandedKeyframes.IndexOf(clicked);
if (clickedIndex < selectedIndex)
foreach (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1))
keyframeViewModel.IsSelected = true;
else
foreach (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes.Skip(selectedIndex).Take(clickedIndex - selectedIndex + 1))
keyframeViewModel.IsSelected = true;
}
else if (toggle)
{
// Toggle only the clicked keyframe, leave others alone
clicked.IsSelected = !clicked.IsSelected;
}
else
{
// Only select the clicked keyframe
foreach (ITimelineKeyframeViewModel keyframeViewModel in expandedKeyframes)
keyframeViewModel.IsSelected = false;
clicked.IsSelected = true;
}
}
}

View File

@ -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; }
}

View File

@ -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
{

View File

@ -4,30 +4,30 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:viewModel="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree"
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:profileElementProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:viewModel="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree"
xmlns:properties="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreeGroupView">
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreeGroupView">
<UserControl.Resources>
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20"/>
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
</UserControl.Resources>
<UserControl.Styles>
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
<UserControl.Styles>
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
<Setter Property="RenderTransform" Value="rotate(-90deg)" />
</Style>
</UserControl.Styles>
</UserControl.Styles>
<StackPanel>
<Border Name="Bd"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0,0,0,1"
Height="29">
<Grid Margin="{Binding Converter={StaticResource PropertyTreeMarginConverter}}" ColumnDefinitions="19,*">
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding !ProfileElementPropertyGroupViewModel.IsExpanded}"
IsVisible="{Binding ProfileElementPropertyGroupViewModel.HasChildren}"
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding !PropertyGroupViewModel.IsExpanded}"
IsVisible="{Binding PropertyGroupViewModel.HasChildren}"
Kind="ChevronDown"
Grid.Column="0"
Margin="5 0"
@ -40,15 +40,14 @@
</avalonia:MaterialIcon.Transitions>
</avalonia:MaterialIcon>
<StackPanel Grid.Column="1">
<StackPanel Grid.Column="1">
<!-- Type: None -->
<TextBlock Text="{Binding LayerPropertyGroup.GroupDescription.Name}"
ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="3 5 0 5"
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.None}}">
</TextBlock>
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.None}}" />
<!-- Type: General -->
<StackPanel Orientation="Horizontal"
@ -178,18 +177,18 @@
</Border>
<!--
Do not bind directly to the ProfileElementPropertyGroupViewModel.Children collection
Do not bind directly to the PropertyGroupViewModel.Children collection
Instead use a reference provided by the VM that is null when collapsed, virtualization for noobs
-->
<ItemsControl Items="{Binding Children}"
IsVisible="{Binding ProfileElementPropertyGroupViewModel.IsExpanded}"
IsVisible="{Binding PropertyGroupViewModel.IsExpanded}"
HorizontalAlignment="Stretch">
<ItemsControl.DataTemplates>
<DataTemplate DataType="profileElementProperties:ProfileElementPropertyGroupViewModel">
<ContentControl Content="{Binding TreeGroupViewModel}" IsVisible="{Binding IsVisible}"></ContentControl>
<DataTemplate DataType="properties:PropertyGroupViewModel">
<ContentControl Content="{Binding TreeGroupViewModel}" IsVisible="{Binding IsVisible}" />
</DataTemplate>
<DataTemplate DataType="profileElementProperties:ProfileElementPropertyViewModel">
<ContentControl Content="{Binding TreePropertyViewModel}" IsVisible="{Binding IsVisible}"></ContentControl>
<DataTemplate DataType="properties:PropertyViewModel">
<ContentControl Content="{Binding TreePropertyViewModel}" IsVisible="{Binding IsVisible}" />
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>

View File

@ -0,0 +1,24 @@
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
public class TreeGroupView : ReactiveUserControl<TreeGroupViewModel>
{
public TreeGroupView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (ViewModel != null)
ViewModel.PropertyGroupViewModel.IsExpanded = !ViewModel.PropertyGroupViewModel.IsExpanded;
}
}

View File

@ -8,7 +8,7 @@ using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
using Artemis.UI.Screens.ProfileEditor.Properties.Windows;
using Artemis.UI.Shared;
using Artemis.UI.Shared.LayerBrushes;
using Artemis.UI.Shared.LayerEffects;
@ -18,7 +18,7 @@ using Ninject;
using Ninject.Parameters;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
public class TreeGroupViewModel : ActivatableViewModelBase
{
@ -27,16 +27,16 @@ public class TreeGroupViewModel : ActivatableViewModelBase
private BrushConfigurationWindowViewModel? _brushConfigurationWindowViewModel;
private EffectConfigurationWindowViewModel? _effectConfigurationWindowViewModel;
public TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel, IWindowService windowService, IProfileEditorService profileEditorService)
public TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel, IWindowService windowService, IProfileEditorService profileEditorService)
{
_windowService = windowService;
_profileEditorService = profileEditorService;
ProfileElementPropertyGroupViewModel = profileElementPropertyGroupViewModel;
PropertyGroupViewModel = propertyGroupViewModel;
DetermineGroupType();
this.WhenActivated(d =>
{
ProfileElementPropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
PropertyGroupViewModel.WhenAnyValue(vm => vm.IsExpanded).Subscribe(_ => this.RaisePropertyChanged(nameof(Children))).DisposeWith(d);
Disposable.Create(CloseViewModels).DisposeWith(d);
});
@ -44,12 +44,12 @@ public class TreeGroupViewModel : ActivatableViewModelBase
}
public ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel { get; }
public LayerPropertyGroup LayerPropertyGroup => ProfileElementPropertyGroupViewModel.LayerPropertyGroup;
public BaseLayerBrush? LayerBrush => ProfileElementPropertyGroupViewModel.LayerBrush;
public BaseLayerEffect? LayerEffect => ProfileElementPropertyGroupViewModel.LayerEffect;
public PropertyGroupViewModel PropertyGroupViewModel { get; }
public LayerPropertyGroup LayerPropertyGroup => PropertyGroupViewModel.LayerPropertyGroup;
public BaseLayerBrush? LayerBrush => PropertyGroupViewModel.LayerBrush;
public BaseLayerEffect? LayerEffect => PropertyGroupViewModel.LayerEffect;
public ObservableCollection<ViewModelBase>? Children => ProfileElementPropertyGroupViewModel.IsExpanded ? ProfileElementPropertyGroupViewModel.Children : null;
public ObservableCollection<ViewModelBase>? Children => PropertyGroupViewModel.IsExpanded ? PropertyGroupViewModel.Children : null;
public LayerPropertyGroupType GroupType { get; private set; }
@ -148,9 +148,9 @@ public class TreeGroupViewModel : ActivatableViewModelBase
GroupType = LayerPropertyGroupType.General;
else if (LayerPropertyGroup is LayerTransformProperties)
GroupType = LayerPropertyGroupType.Transform;
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerBrush != null)
else if (LayerPropertyGroup.Parent == null && PropertyGroupViewModel.LayerBrush != null)
GroupType = LayerPropertyGroupType.LayerBrushRoot;
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerEffect != null)
else if (LayerPropertyGroup.Parent == null && PropertyGroupViewModel.LayerEffect != null)
GroupType = LayerPropertyGroupType.LayerEffectRoot;
else
GroupType = LayerPropertyGroupType.None;

View File

@ -6,12 +6,11 @@
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreePropertyView">
<UserControl.Resources>
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
</UserControl.Resources>
<Border Name="Bd"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreePropertyView">
<UserControl.Resources>
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
</UserControl.Resources>
<Border Name="Bd"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0,0,0,1"
Height="29">
@ -24,10 +23,10 @@
IsChecked="{Binding KeyframesEnabled}"
IsEnabled="{Binding LayerProperty.KeyframesSupported}"
VerticalAlignment="Center" Padding="-25">
<avalonia:MaterialIcon Kind="Stopwatch" />
</ToggleButton>
<avalonia:MaterialIcon Kind="Stopwatch" />
</ToggleButton>
<TextBlock Grid.Column="1"
<TextBlock Grid.Column="1"
Margin="5,0,0,0"
Padding="0,0,5,0"
VerticalAlignment="Center"
@ -36,30 +35,30 @@
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"
HorizontalAlignment="Left" />
<ContentControl Grid.Column="2"
<ContentControl Grid.Column="2"
Margin="5 0"
Content="{Binding PropertyInputViewModel}"
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"/>
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}" />
<Button Grid.Column="3"
<Button Grid.Column="3"
Command="{Binding ResetToDefault}"
Classes="icon-button"
ToolTip.Tip="Reset the property to its default value."
Width="24"
Height="24">
<avalonia:MaterialIcon Kind="BackupRestore" />
</Button>
<avalonia:MaterialIcon Kind="BackupRestore" />
</Button>
<ToggleButton Grid.Column="4" Classes="icon-button"
ToolTip.Tip="Change the property's data binding"
Width="24"
Height="24"
VerticalAlignment="Center"
IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
IsChecked="{Binding HasDataBinding, Mode=OneWay}">
<avalonia:MaterialIcon Kind="VectorLink" />
</ToggleButton>
</Grid>
<ToggleButton Grid.Column="4" Classes="icon-button"
ToolTip.Tip="Change the property's data binding"
Width="24"
Height="24"
VerticalAlignment="Center"
IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
IsChecked="{Binding HasDataBinding, Mode=OneWay}">
<avalonia:MaterialIcon Kind="VectorLink" />
</ToggleButton>
</Grid>
</Border>
</UserControl>
</Border>
</UserControl>

View File

@ -0,0 +1,30 @@
using System;
using System.Reactive.Linq;
using Artemis.Core;
using Avalonia.Controls;
using Avalonia.Controls.Mixins;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
public class TreePropertyView : ReactiveUserControl<ITreePropertyViewModel>
{
public TreePropertyView()
{
this.WhenActivated(d =>
{
if (ViewModel != null)
Observable.FromEventPattern<LayerPropertyEventArgs>(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e)
.Subscribe(_ => this.BringIntoView())
.DisposeWith(d);
});
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,61 @@
using System;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Artemis.UI.Shared.Services.PropertyInput;
using Avalonia.Controls.Mixins;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
public TreePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService,
IPropertyInputService propertyInputService)
{
_profileEditorService = profileEditorService;
LayerProperty = layerProperty;
PropertyViewModel = propertyViewModel;
PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
this.WhenActivated(d => this.WhenAnyValue(vm => vm.LayerProperty.KeyframesEnabled).Subscribe(_ => this.RaisePropertyChanged(nameof(KeyframesEnabled))).DisposeWith(d));
}
public LayerProperty<T> LayerProperty { get; }
public PropertyViewModel PropertyViewModel { get; }
public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
public bool KeyframesEnabled
{
get => LayerProperty.KeyframesEnabled;
set => UpdateKeyframesEnabled(value);
}
private void UpdateKeyframesEnabled(bool value)
{
if (value == LayerProperty.KeyframesEnabled)
return;
_profileEditorService.ExecuteCommand(new ToggleLayerPropertyKeyframes<T>(LayerProperty, value));
}
public ILayerProperty BaseLayerProperty => LayerProperty;
public bool HasDataBinding => LayerProperty.HasDataBinding;
public double GetDepth()
{
int depth = 0;
LayerPropertyGroup? current = LayerProperty.LayerPropertyGroup;
while (current != null)
{
depth++;
current = current.Parent;
}
return depth;
}
}

View File

@ -0,0 +1,16 @@
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.BrushConfigurationWindowView"
Title="Artemis | Brush configuration"
Width="{Binding Configuration.DialogWidth}"
Height="{Binding Configuration.DialogHeight}">
<Panel>
<ContentControl Content="{Binding ConfigurationViewModel}" />
</Panel>
</controls:CoreWindow>

View File

@ -2,7 +2,7 @@ using System.ComponentModel;
using Avalonia;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
public class BrushConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
{

View File

@ -1,10 +1,9 @@
using System;
using Artemis.UI.Shared;
using Artemis.UI.Shared.LayerBrushes;
using Artemis.UI.Shared.LayerEffects;
using Avalonia.Threading;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
public class BrushConfigurationWindowViewModel : DialogViewModelBase<object?>
{
@ -12,7 +11,7 @@ public class BrushConfigurationWindowViewModel : DialogViewModelBase<object?>
{
ConfigurationViewModel = configurationViewModel;
Configuration = configuration;
ConfigurationViewModel.CloseRequested += ConfigurationViewModelOnCloseRequested;
}

View File

@ -0,0 +1,16 @@
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Windows.EffectConfigurationWindowView"
Title="Artemis | Effect configuration"
Width="{Binding Configuration.DialogWidth}"
Height="{Binding Configuration.DialogHeight}">
<Panel>
<ContentControl Content="{Binding ConfigurationViewModel}" />
</Panel>
</controls:CoreWindow>

View File

@ -2,7 +2,7 @@ using System.ComponentModel;
using Avalonia;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
{

View File

@ -3,7 +3,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.LayerEffects;
using Avalonia.Threading;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Windows;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
public class EffectConfigurationWindowViewModel : DialogViewModelBase<object?>
{
@ -22,7 +22,7 @@ public class EffectConfigurationWindowViewModel : DialogViewModelBase<object?>
{
return ConfigurationViewModel.CanClose() && Dispatcher.UIThread.InvokeAsync(async () => await ConfigurationViewModel.CanCloseAsync()).GetAwaiter().GetResult();
}
private void ConfigurationViewModelOnCloseRequested(object? sender, EventArgs e)
{
if (CanClose())

View File

@ -67,7 +67,7 @@
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
<Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True">
<ContentControl Content="{Binding ProfileElementPropertiesViewModel}"/>
<ContentControl Content="{Binding PropertiesViewModel}"/>
</Border>
</Grid>

View File

@ -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;