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

UI - Further restructuring

This commit is contained in:
SpoinkyNL 2020-05-14 21:58:30 +02:00
parent 54081b591b
commit af21d83487
53 changed files with 459 additions and 1242 deletions

View File

@ -72,6 +72,33 @@ namespace Artemis.Core.Models.Profile.LayerProperties
public override IReadOnlyList<BaseLayerPropertyKeyframe> BaseKeyframes => _keyframes.Cast<BaseLayerPropertyKeyframe>().ToList().AsReadOnly();
/// <summary>
/// Sets the current value, using either keyframes if enabled or the base value.
/// </summary>
/// <param name="value">The value to set.</param>
/// <param name="time">
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
/// or existing keyframe.
/// </param>
public void SetCurrentValue(T value, TimeSpan? time)
{
if (time == null || !KeyframesEnabled || !KeyframesSupported)
BaseValue = value;
else
{
// If on a keyframe, update the keyframe
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
// Create a new keyframe if none found
if (currentKeyframe == null)
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear));
else
currentKeyframe.Value = value;
// Update the property so that the new keyframe is reflected on the current value
Update(0);
}
}
/// <summary>
/// Adds a keyframe to the layer property
/// </summary>
@ -220,9 +247,24 @@ namespace Artemis.Core.Models.Profile.LayerProperties
#region Events
/// <summary>
/// Occurs once every frame when the layer property is updated
/// </summary>
public event EventHandler Updated;
/// <summary>
/// Occurs when the base value of the layer property was updated
/// </summary>
public event EventHandler BaseValueChanged;
/// <summary>
/// Occurs when a new keyframe was added to the layer property
/// </summary>
public event EventHandler KeyframeAdded;
/// <summary>
/// Occurs when a keyframe was removed from the layer property
/// </summary>
public event EventHandler KeyframeRemoved;
protected virtual void OnUpdated()

View File

@ -33,6 +33,11 @@ namespace Artemis.Core.Models.Profile
/// </summary>
public bool IsCorePropertyGroup { get; internal set; }
/// <summary>
/// Gets or sets whether the property is hidden in the UI
/// </summary>
public bool IsHidden { get; set; }
/// <summary>
/// A list of all layer properties in this group
/// </summary>

View File

@ -47,29 +47,4 @@ namespace Artemis.UI.Ninject.Factories
{
ProfileLayerViewModel Create(Layer layer);
}
public interface ILayerPropertyVmFactory : IVmFactory
{
LayerPropertyViewModel Create(BaseLayerProperty layerProperty, LayerPropertyViewModel parent);
}
public interface IPropertyTreeVmFactory : IVmFactory
{
PropertyTreeViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
}
public interface IPropertyTimelineVmFactory : IVmFactory
{
PropertyTimelineViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
}
public interface IPropertyTrackVmFactory : IVmFactory
{
PropertyTrackViewModel Create(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel);
}
public interface IPropertyTrackKeyframeVmFactory : IVmFactory
{
PropertyTrackKeyframeViewModel<T> Create<T>(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe<T> keyframe);
}
}

View File

@ -1,11 +1,23 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Artemis.Core.Models.Profile.LayerProperties;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract
{
public abstract class LayerPropertyBaseViewModel : PropertyChangedBase
public abstract class LayerPropertyBaseViewModel : PropertyChangedBase, IDisposable
{
protected LayerPropertyBaseViewModel()
{
Children = new List<LayerPropertyBaseViewModel>();
}
public bool IsExpanded { get; set; }
public abstract bool IsVisible { get; }
public List<LayerPropertyBaseViewModel> Children { get; set; }
public abstract List<BaseLayerPropertyKeyframe> GetKeyframes(bool visibleOnly);
public abstract void Dispose();
}
}

View File

@ -11,9 +11,8 @@ using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.Core.Services;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Events;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
using Artemis.UI.Services.Interfaces;
using Stylet;
@ -22,22 +21,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
public class LayerPropertiesViewModel : ProfileEditorPanelViewModel
{
private readonly ICoreService _coreService;
private readonly IPropertyTreeVmFactory _propertyTreeVmFactory;
private readonly IPropertyTimelineVmFactory _propertyTimelineVmFactory;
private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService;
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
ICoreService coreService,
ISettingsService settingsService,
IPropertyTreeVmFactory propertyTreeVmFactory,
IPropertyTimelineVmFactory propertyTimelineVmFactory)
public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService)
{
_profileEditorService = profileEditorService;
_coreService = coreService;
_settingsService = settingsService;
_propertyTreeVmFactory = propertyTreeVmFactory;
_propertyTimelineVmFactory = propertyTimelineVmFactory;
PixelsPerSecond = 31;
}
@ -63,13 +54,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; set; }
public PropertyTreeViewModel PropertyTree { get; set; }
public PropertyTimelineViewModel PropertyTimeline { get; set; }
public TreeViewModel TreeViewModel { get; set; }
public TimelineViewModel TimelineViewModel { get; set; }
protected override void OnInitialActivate()
{
PropertyTree = _propertyTreeVmFactory.Create(this);
PropertyTimeline = _propertyTimelineVmFactory.Create(this);
TreeViewModel = new TreeViewModel(LayerPropertyGroups);
TimelineViewModel = new TimelineViewModel(LayerPropertyGroups);
PopulateProperties(_profileEditorService.SelectedProfileElement);
@ -84,10 +75,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
_profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
_profileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
PropertyTree?.Dispose();
PropertyTimeline?.Dispose();
PropertyTree = null;
PropertyTimeline = null;
base.OnClose();
}
@ -124,8 +111,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
layer.GetType().GetProperty(nameof(layer.Transform)),
typeof(PropertyGroupDescriptionAttribute)
);
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.General, (PropertyGroupDescriptionAttribute) generalAttribute));
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute));
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.General, (PropertyGroupDescriptionAttribute) generalAttribute));
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute));
// Add the rout group of the brush
// The root group of the brush has no attribute so let's pull one out of our sleeve
@ -134,7 +121,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
Name = layer.LayerBrush.Descriptor.DisplayName,
Description = layer.LayerBrush.Descriptor.Description
};
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.LayerBrush.BaseProperties, brushDescription));
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.LayerBrush.BaseProperties, brushDescription));
}
}

View File

@ -4,27 +4,37 @@ using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
using Artemis.UI.Services.Interfaces;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{
public class LayerPropertyGroupViewModel : LayerPropertyBaseViewModel
{
public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription)
public LayerPropertyGroupViewModel(IProfileEditorService profileEditorService, LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription)
{
ProfileEditorService = profileEditorService;
LayerPropertyGroup = layerPropertyGroup;
PropertyGroupDescription = propertyGroupDescription;
IsExpanded = PropertyGroupDescription.ExpandByDefault;
Children = new List<LayerPropertyBaseViewModel>();
TreePropertyGroupViewModel = new TreePropertyGroupViewModel(this);
TimelinePropertyGroupViewModel = new TimelinePropertyGroupViewModel(this);
PopulateChildren();
}
public override bool IsVisible => !LayerPropertyGroup.IsHidden;
public IProfileEditorService ProfileEditorService { get; }
public LayerPropertyGroup LayerPropertyGroup { get; }
public PropertyGroupDescriptionAttribute PropertyGroupDescription { get; }
public bool IsExpanded { get; set; }
public List<LayerPropertyBaseViewModel> Children { get; set; }
public TreePropertyGroupViewModel TreePropertyGroupViewModel { get; set; }
public TimelinePropertyGroupViewModel TimelinePropertyGroupViewModel { get; set; }
private void PopulateChildren()
{
@ -36,18 +46,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
var value = propertyInfo.GetValue(LayerPropertyGroup);
// Create VMs for properties on the group
if (propertyAttribute != null && value is BaseLayerProperty)
if (propertyAttribute != null && value is BaseLayerProperty baseLayerProperty)
{
// Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line
var genericType = propertyInfo.PropertyType.GetGenericArguments()[0];
var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType);
var instance = Activator.CreateInstance(genericViewModel, value, propertyAttribute);
Children.Add((LayerPropertyBaseViewModel) instance);
var viewModel = ProfileEditorService.CreateLayerPropertyViewModel(baseLayerProperty, propertyAttribute);
if (viewModel != null)
Children.Add(viewModel);
}
// Create VMs for child groups on this group, resulting in a nested structure
else if (groupAttribute != null && value is LayerPropertyGroup layerPropertyGroup)
{
Children.Add(new LayerPropertyGroupViewModel(layerPropertyGroup, groupAttribute));
Children.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layerPropertyGroup, groupAttribute));
}
}
}
@ -63,5 +71,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
return result;
}
public override void Dispose()
{
foreach (var layerPropertyBaseViewModel in Children)
layerPropertyBaseViewModel.Dispose();
}
}
}

View File

@ -0,0 +1,14 @@
using Artemis.Core.Models.Profile.LayerProperties;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{
public class LayerKeyframeViewModel<T>
{
public LayerKeyframeViewModel(LayerPropertyKeyframe<T> keyframe)
{
Keyframe = keyframe;
}
public LayerPropertyKeyframe<T> Keyframe { get; }
}
}

View File

@ -3,23 +3,68 @@ using System.Linq;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
using Artemis.UI.Services.Interfaces;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{
public class LayerPropertyViewModel<T> : LayerPropertyBaseViewModel
public class LayerPropertyViewModel<T> : LayerPropertyViewModel
{
public LayerPropertyViewModel(LayerProperty<T> layerProperty, PropertyDescriptionAttribute propertyDescription)
public LayerPropertyViewModel(IProfileEditorService profileEditorService, LayerProperty<T> layerProperty, PropertyDescriptionAttribute propertyDescription)
: base(profileEditorService, layerProperty)
{
LayerProperty = layerProperty;
PropertyDescription = propertyDescription;
TreePropertyViewModel = new TreePropertyViewModel<T>(this);
TimelinePropertyViewModel = new TimelinePropertyViewModel<T>(this);
TreePropertyBaseViewModel = TreePropertyViewModel;
TimelinePropertyBaseViewModel = TimelinePropertyViewModel;
}
public override bool IsVisible => !LayerProperty.IsHidden;
public LayerProperty<T> LayerProperty { get; }
public PropertyDescriptionAttribute PropertyDescription { get; }
public TreePropertyViewModel<T> TreePropertyViewModel { get; set; }
public TimelinePropertyViewModel<T> TimelinePropertyViewModel { get; set; }
public override List<BaseLayerPropertyKeyframe> GetKeyframes(bool visibleOnly)
{
return LayerProperty.BaseKeyframes.ToList();
}
public override void Dispose()
{
TreePropertyViewModel.Dispose();
TimelinePropertyViewModel.Dispose();
}
public void SetCurrentValue(T value, bool saveChanges)
{
LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime);
if (saveChanges)
ProfileEditorService.UpdateSelectedProfileElement();
else
ProfileEditorService.UpdateProfilePreview();
}
}
public abstract class LayerPropertyViewModel : LayerPropertyBaseViewModel
{
public IProfileEditorService ProfileEditorService { get; }
public BaseLayerProperty BaseLayerProperty { get; }
protected LayerPropertyViewModel(IProfileEditorService profileEditorService, BaseLayerProperty baseLayerProperty)
{
ProfileEditorService = profileEditorService;
BaseLayerProperty = baseLayerProperty;
}
public PropertyDescriptionAttribute PropertyDescription { get; protected set; }
public TreePropertyViewModel TreePropertyBaseViewModel { get; set; }
public TimelinePropertyViewModel TimelinePropertyBaseViewModel { get; set; }
}
}

View File

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using Artemis.UI.Exceptions;
using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
{
public abstract class PropertyInputViewModel : PropertyChangedBase, IDisposable
{
protected PropertyInputViewModel(IProfileEditorService profileEditorService)
{
ProfileEditorService = profileEditorService;
}
protected IProfileEditorService ProfileEditorService { get; }
public abstract List<Type> CompatibleTypes { get; }
public bool Initialized { get; private set; }
public bool InputDragging { get; private set; }
public LayerPropertyViewModel LayerPropertyViewModel { get; private set; }
protected object InputValue
{
get => LayerPropertyViewModel.LayerProperty.GetCurrentValue();
set => UpdateInputValue(value);
}
public void Initialize(LayerPropertyViewModel layerPropertyViewModel)
{
var type = layerPropertyViewModel.LayerProperty.Type;
if (type.IsEnum)
type = typeof(Enum);
if (Initialized)
throw new ArtemisUIException("Cannot initialize the same property input VM twice");
if (!CompatibleTypes.Contains(type))
throw new ArtemisUIException($"This input VM does not support the provided type {type.Name}");
LayerPropertyViewModel = layerPropertyViewModel;
LayerPropertyViewModel.LayerProperty.ValueChanged += LayerPropertyOnValueChanged;
Update();
Initialized = true;
OnInitialized();
}
public abstract void Update();
protected virtual void OnInitialized()
{
}
private void LayerPropertyOnValueChanged(object sender, EventArgs e)
{
Update();
}
private void UpdateInputValue(object value)
{
LayerPropertyViewModel.LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime);
// Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress
LayerPropertyViewModel.LayerProperty.KeyframeEngine?.Update(0);
if (!InputDragging)
ProfileEditorService.UpdateSelectedProfileElement();
else
ProfileEditorService.UpdateProfilePreview();
}
#region Event handlers
public void InputDragStarted(object sender, EventArgs e)
{
InputDragging = true;
}
public void InputDragEnded(object sender, EventArgs e)
{
InputDragging = false;
ProfileEditorService.UpdateSelectedProfileElement();
}
#endregion
public virtual void Dispose()
{
if (LayerPropertyViewModel != null)
LayerPropertyViewModel.LayerProperty.ValueChanged -= LayerPropertyOnValueChanged;
}
}
}

View File

@ -1,54 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyTreeChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="19"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PropertyTreeChildViewModel}">
<Grid Height="22">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0"
Style="{StaticResource MaterialDesignFlatToggleButton}"
ToolTip="Toggle key-framing"
Width="18"
Height="18"
IsChecked="{Binding LayerPropertyViewModel.KeyframesEnabled}"
IsEnabled="{Binding LayerPropertyViewModel.LayerProperty.CanUseKeyframes}"
VerticalAlignment="Center" Padding="-25">
<materialDesign:PackIcon Kind="Stopwatch" Height="13" Width="13" />
</ToggleButton>
<TextBlock Grid.Column="1"
Margin="5,0,0,0"
Padding="0,0,5,0"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
Text="{Binding LayerPropertyViewModel.LayerProperty.Name}"
ToolTip="{Binding LayerPropertyViewModel.LayerProperty.Description}"
HorizontalAlignment="Left" />
<ContentControl Grid.Column="2" Margin="20 0" s:View.Model="{Binding PropertyInputViewModel}" />
<Button Grid.Column="3"
Style="{StaticResource MaterialDesignOutlinedButton}"
Margin="0,1,0,1.2"
Padding="0"
Width="80"
Height="20"
ToolTip="Change the property's data binding"
VerticalAlignment="Center"
IsEnabled="False">
<TextBlock FontSize="10">DATA BINDING</TextBlock>
</Button>
</Grid>
</UserControl>

View File

@ -1,42 +0,0 @@
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
{
public class PropertyTreeChildViewModel : PropertyTreeItemViewModel
{
public PropertyTreeChildViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel)
{
PropertyInputViewModel = layerPropertyViewModel.GetPropertyInputViewModel();
}
public PropertyInputViewModel PropertyInputViewModel { get; set; }
public override void Update(bool forceUpdate)
{
if (forceUpdate)
PropertyInputViewModel?.Update();
else
{
// Only update if visible and if keyframes are enabled
if (LayerPropertyViewModel.Parent.IsExpanded && LayerPropertyViewModel.KeyframesEnabled)
PropertyInputViewModel?.Update();
}
}
public override void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
}
public override void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
}
public override void Dispose()
{
PropertyInputViewModel?.Dispose();
PropertyInputViewModel = null;
base.Dispose();
}
}
}

View File

@ -1,37 +0,0 @@
using System;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
{
public abstract class PropertyTreeItemViewModel : PropertyChangedBase, IDisposable
{
protected PropertyTreeItemViewModel(LayerPropertyViewModel layerPropertyViewModel)
{
LayerPropertyViewModel = layerPropertyViewModel;
}
public LayerPropertyViewModel LayerPropertyViewModel { get; }
/// <summary>
/// Updates the tree item's input if it is visible and has keyframes enabled
/// </summary>
/// <param name="forceUpdate">Force update regardless of visibility and keyframes</param>
public abstract void Update(bool forceUpdate);
/// <summary>
/// Removes the layer property recursively
/// </summary>
/// <param name="layerPropertyViewModel"></param>
public abstract void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel);
/// <summary>
/// Adds the layer property recursively
/// </summary>
/// <param name="layerPropertyViewModel"></param>
public abstract void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel);
public virtual void Dispose()
{
}
}
}

View File

@ -1,65 +0,0 @@
using System.Linq;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
{
public class PropertyTreeParentViewModel : PropertyTreeItemViewModel
{
public PropertyTreeParentViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel)
{
Children = new BindableCollection<PropertyTreeItemViewModel>();
}
public BindableCollection<PropertyTreeItemViewModel> Children { get; set; }
public override void Update(bool forceUpdate)
{
foreach (var child in Children)
child.Update(forceUpdate);
}
// TODO: Change this to not add one by one, this raises far too many events
public override void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
if (layerPropertyViewModel.Parent == LayerPropertyViewModel)
{
lock (Children)
{
var index = layerPropertyViewModel.LayerProperty.Parent.Children.IndexOf(layerPropertyViewModel.LayerProperty);
if (index > Children.Count)
index = Children.Count;
if (layerPropertyViewModel.Children.Any())
Children.Insert(index, new PropertyTreeParentViewModel(layerPropertyViewModel));
else
Children.Insert(index, new PropertyTreeChildViewModel(layerPropertyViewModel));
}
}
else
{
foreach (var propertyTreeItemViewModel in Children)
propertyTreeItemViewModel.AddLayerProperty(layerPropertyViewModel);
}
}
// TODO: Change this to not remove one by one, this raises far too many events
public override void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
foreach (var child in Children.ToList())
{
if (child.LayerPropertyViewModel == layerPropertyViewModel)
{
Children.Remove(child);
child.Dispose();
}
else
child.RemoveLayerProperty(layerPropertyViewModel);
}
}
public override void Dispose()
{
foreach (var child in Children.ToList())
child.Dispose();
}
}
}

View File

@ -1,103 +0,0 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Artemis.UI.Events;
using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
{
public class PropertyTreeViewModel : PropertyChangedBase, IDisposable
{
private readonly IProfileEditorService _profileEditorService;
public PropertyTreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerPropertiesViewModel = layerPropertiesViewModel;
PropertyTreeItemViewModels = new BindableCollection<PropertyTreeItemViewModel>();
_profileEditorService.CurrentTimeChanged += OnCurrentTimeChanged;
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
}
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
public BindableCollection<PropertyTreeItemViewModel> PropertyTreeItemViewModels { get; set; }
public void Dispose()
{
_profileEditorService.CurrentTimeChanged -= OnCurrentTimeChanged;
_profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated;
foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels)
propertyTreeItemViewModel.Dispose();
}
// TODO: Change this to not add one by one, this raises far too many events
public void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
// Add as a root VM
if (layerPropertyViewModel.Parent == null)
PropertyTreeItemViewModels.Add(new PropertyTreeParentViewModel(layerPropertyViewModel));
// Add recursively to one of the child VMs
else
{
foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels)
propertyTreeItemViewModel.AddLayerProperty(layerPropertyViewModel);
}
}
// TODO: Change this to not remove one by one, this raises far too many events
public void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
// Remove a root VM
var rootVm = PropertyTreeItemViewModels.FirstOrDefault(vm => vm.LayerPropertyViewModel == layerPropertyViewModel);
if (rootVm != null)
PropertyTreeItemViewModels.Remove(rootVm);
// Remove recursively from one of the child VMs
else
{
foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels)
propertyTreeItemViewModel.RemoveLayerProperty(layerPropertyViewModel);
}
}
/// <summary>
/// Updates the tree item's input if it is visible and has keyframes enabled
/// </summary>
/// <param name="forceUpdate">Force update regardless of visibility and keyframes</param>
public void Update(bool forceUpdate)
{
foreach (var viewModel in PropertyTreeItemViewModels)
viewModel.Update(forceUpdate);
}
public void PropertyTreePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Handled || !(sender is TreeView))
return;
e.Handled = true;
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = sender
};
var parent = ((Control) sender).Parent as UIElement;
parent?.RaiseEvent(eventArg);
}
private void OnCurrentTimeChanged(object sender, EventArgs e)
{
Update(false);
}
private void OnSelectedProfileElementUpdated(object sender, ProfileElementEventArgs e)
{
Update(true);
}
}
}

View File

@ -1,55 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.PropertyTimelineView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="25"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PropertyTimelineViewModel}">
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
MouseDown="{s:Action TimelineCanvasMouseDown}"
MouseUp="{s:Action TimelineCanvasMouseUp}"
MouseMove="{s:Action TimelineCanvasMouseMove}">
<Grid.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
<BeginStoryboard>
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
<BeginStoryboard>
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<ItemsControl ItemsSource="{Binding PropertyTrackViewModels}"
Width="{Binding Width}"
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}"
HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Multi-selection rectangle -->
<Path Data="{Binding SelectionRectangle}" Opacity="0"
Stroke="{DynamicResource PrimaryHueLightBrush}"
StrokeThickness="1"
x:Name="MultiSelectionPath"
IsHitTestVisible="False">
<Path.Fill>
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
</Path.Fill>
</Path>
</Grid>
</UserControl>

View File

@ -1,245 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Artemis.UI.Events;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Utilities;
using Artemis.UI.Utilities;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTimelineViewModel : PropertyChangedBase, IDisposable
{
private readonly IProfileEditorService _profileEditorService;
private readonly IPropertyTrackVmFactory _propertyTrackVmFactory;
public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel,
IProfileEditorService profileEditorService,
IPropertyTrackVmFactory propertyTrackVmFactory)
{
_profileEditorService = profileEditorService;
_propertyTrackVmFactory = propertyTrackVmFactory;
LayerPropertiesViewModel = layerPropertiesViewModel;
PropertyTrackViewModels = new BindableCollection<PropertyTrackViewModel>();
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
LayerPropertiesViewModel.PixelsPerSecondChanged += OnPixelsPerSecondChanged;
Execute.PostToUIThread(() => SelectionRectangle = new RectangleGeometry());
}
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
public double Width { get; set; }
public BindableCollection<PropertyTrackViewModel> PropertyTrackViewModels { get; set; }
public RectangleGeometry SelectionRectangle { get; set; }
public void Dispose()
{
_profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated;
LayerPropertiesViewModel.PixelsPerSecondChanged -= OnPixelsPerSecondChanged;
}
public void UpdateEndTime()
{
// End time is the last keyframe + 10 sec
var lastKeyFrame = PropertyTrackViewModels.SelectMany(r => r.KeyframeViewModels).OrderByDescending(t => t.Keyframe.Position).FirstOrDefault();
var endTime = lastKeyFrame?.Keyframe.Position.Add(new TimeSpan(0, 0, 0, 10)) ?? TimeSpan.FromSeconds(10);
Width = endTime.TotalSeconds * LayerPropertiesViewModel.PixelsPerSecond;
// Ensure the caret isn't outside the end time
if (_profileEditorService.CurrentTime > endTime)
_profileEditorService.CurrentTime = endTime;
}
public void PopulateProperties(List<LayerPropertyViewModel> properties)
{
var newViewModels = new List<PropertyTrackViewModel>();
foreach (var property in properties)
newViewModels.AddRange(CreateViewModels(property));
PropertyTrackViewModels.Clear();
PropertyTrackViewModels.AddRange(newViewModels);
UpdateEndTime();
}
public void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
// Determine the index by flattening all the layer's properties
var index = layerPropertyViewModel.LayerProperty.GetFlattenedIndex();
if (index > PropertyTrackViewModels.Count)
index = PropertyTrackViewModels.Count;
PropertyTrackViewModels.Insert(index, _propertyTrackVmFactory.Create(this, layerPropertyViewModel));
}
public void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel)
{
var vm = PropertyTrackViewModels.FirstOrDefault(v => v.LayerPropertyViewModel == layerPropertyViewModel);
if (vm != null)
PropertyTrackViewModels.Remove(vm);
}
public void UpdateKeyframePositions()
{
foreach (var viewModel in PropertyTrackViewModels)
viewModel.UpdateKeyframes(LayerPropertiesViewModel.PixelsPerSecond);
UpdateEndTime();
}
/// <summary>
/// Updates the time line's keyframes
/// </summary>
public void Update()
{
foreach (var viewModel in PropertyTrackViewModels)
viewModel.PopulateKeyframes();
UpdateEndTime();
}
private void OnSelectedProfileElementUpdated(object sender, ProfileElementEventArgs e)
{
Update();
}
private void OnPixelsPerSecondChanged(object sender, EventArgs e)
{
UpdateKeyframePositions();
}
private List<PropertyTrackViewModel> CreateViewModels(LayerPropertyViewModel property)
{
var result = new List<PropertyTrackViewModel> {_propertyTrackVmFactory.Create(this, property)};
foreach (var child in property.Children)
result.AddRange(CreateViewModels(child));
return result;
}
#region Keyframe movement
public void MoveSelectedKeyframes(TimeSpan cursorTime)
{
// Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging
SelectionRectangle.Rect = new Rect();
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
keyframeViewModel.ApplyMovement(cursorTime);
_profileEditorService.UpdateProfilePreview();
}
public void ReleaseSelectedKeyframes()
{
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
keyframeViewModel.ReleaseMovement();
}
#endregion
#region Keyframe selection
private Point _mouseDragStartPoint;
private bool _mouseDragging;
// ReSharper disable once UnusedMember.Global - Called from view
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Released)
return;
((IInputElement) sender).CaptureMouse();
SelectionRectangle.Rect = new Rect();
_mouseDragStartPoint = e.GetPosition((IInputElement) sender);
_mouseDragging = true;
e.Handled = true;
}
// ReSharper disable once UnusedMember.Global - Called from view
public void TimelineCanvasMouseUp(object sender, MouseEventArgs e)
{
if (!_mouseDragging)
return;
var position = e.GetPosition((IInputElement) sender);
var selectedRect = new Rect(_mouseDragStartPoint, position);
SelectionRectangle.Rect = selectedRect;
var selectedKeyframes = HitTestUtilities.GetHitViewModels<PropertyTrackKeyframeViewModel>((Visual) sender, SelectionRectangle);
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels)
keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel);
_mouseDragging = false;
e.Handled = true;
((IInputElement) sender).ReleaseMouseCapture();
}
public void TimelineCanvasMouseMove(object sender, MouseEventArgs e)
{
if (_mouseDragging && e.LeftButton == MouseButtonState.Pressed)
{
var position = e.GetPosition((IInputElement) sender);
var selectedRect = new Rect(_mouseDragStartPoint, position);
SelectionRectangle.Rect = selectedRect;
e.Handled = true;
}
}
public void SelectKeyframe(PropertyTrackKeyframeViewModel clicked, bool selectBetween, bool toggle)
{
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
if (selectBetween)
{
var selectedIndex = keyframeViewModels.FindIndex(k => k.IsSelected);
// If nothing is selected, select only the clicked
if (selectedIndex == -1)
{
clicked.IsSelected = true;
return;
}
foreach (var keyframeViewModel in keyframeViewModels)
keyframeViewModel.IsSelected = false;
var clickedIndex = keyframeViewModels.IndexOf(clicked);
if (clickedIndex < selectedIndex)
{
foreach (var keyframeViewModel in keyframeViewModels.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1))
keyframeViewModel.IsSelected = true;
}
else
{
foreach (var keyframeViewModel in keyframeViewModels.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 (var keyframeViewModel in keyframeViewModels)
keyframeViewModel.IsSelected = false;
clicked.IsSelected = true;
}
}
#endregion
}
}

View File

@ -1,51 +0,0 @@
using System.Windows;
using System.Windows.Media;
using Artemis.Core.Utilities;
using Humanizer;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTrackEasingViewModel : PropertyChangedBase
{
private readonly PropertyTrackKeyframeViewModel _keyframeViewModel;
private bool _isEasingModeSelected;
public PropertyTrackEasingViewModel(PropertyTrackKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction)
{
_keyframeViewModel = keyframeViewModel;
_isEasingModeSelected = keyframeViewModel.Keyframe.EasingFunction == easingFunction;
EasingFunction = easingFunction;
Description = easingFunction.Humanize();
CreateGeometry();
}
public Easings.Functions EasingFunction { get; }
public PointCollection EasingPoints { get; set; }
public string Description { get; set; }
public bool IsEasingModeSelected
{
get => _isEasingModeSelected;
set
{
_isEasingModeSelected = value;
if (_isEasingModeSelected)
_keyframeViewModel.SelectEasingMode(this);
}
}
private void CreateGeometry()
{
EasingPoints = new PointCollection();
for (var i = 1; i <= 10; i++)
{
var x = i;
var y = Easings.Interpolate(i / 10.0, EasingFunction) * 10;
EasingPoints.Add(new Point(x, y));
}
}
}
}

View File

@ -1,176 +0,0 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Utilities;
using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTrackKeyframeViewModel<T> : PropertyTrackKeyframeViewModel
{
private readonly IProfileEditorService _profileEditorService;
private int _pixelsPerSecond;
public PropertyTrackKeyframeViewModel(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe<T> keyframe, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
PropertyTrackViewModel = propertyTrackViewModel;
Keyframe = keyframe;
EasingViewModels = new BindableCollection<PropertyTrackEasingViewModel>();
CreateEasingViewModels();
}
public bool IsSelected { get; set; }
public PropertyTrackViewModel PropertyTrackViewModel { get; }
public LayerPropertyKeyframe<T> Keyframe { get; }
public BindableCollection<PropertyTrackEasingViewModel> EasingViewModels { get; set; }
public double X { get; set; }
public string Timestamp { get; set; }
public UIElement ParentView { get; set; }
public void Update(int pixelsPerSecond)
{
_pixelsPerSecond = pixelsPerSecond;
X = pixelsPerSecond * Keyframe.Position.TotalSeconds;
Timestamp = $"{Math.Floor(Keyframe.Position.TotalSeconds):00}.{Keyframe.Position.Milliseconds:000}";
}
#region Keyframe movement
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Released)
return;
((IInputElement) sender).CaptureMouse();
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !IsSelected)
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, true, false);
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, true);
else if (!IsSelected)
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, false);
e.Handled = true;
}
public void KeyframeMouseUp(object sender, MouseButtonEventArgs e)
{
_profileEditorService.UpdateSelectedProfileElement();
PropertyTrackViewModel.PropertyTimelineViewModel.ReleaseSelectedKeyframes();
((IInputElement) sender).ReleaseMouseCapture();
}
public void KeyframeMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
PropertyTrackViewModel.PropertyTimelineViewModel.MoveSelectedKeyframes(GetCursorTime(e.GetPosition(ParentView)));
e.Handled = true;
}
private TimeSpan GetCursorTime(Point position)
{
// Get the parent grid, need that for our position
var x = Math.Max(0, position.X);
var time = TimeSpan.FromSeconds(x / _pixelsPerSecond);
// Round the time to something that fits the current zoom level
if (_pixelsPerSecond < 200)
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0);
else if (_pixelsPerSecond < 500)
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0);
else
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds));
// If shift is held, snap to the current time
// Take a tolerance of 5 pixels (half a keyframe width)
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
var tolerance = 1000f / _pixelsPerSecond * 5;
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
time = _profileEditorService.CurrentTime;
}
return time;
}
#endregion
#region Context menu actions
public void Copy()
{
var keyframe = PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.CreateNewKeyframe(Keyframe.Position, Keyframe.BaseValue);
keyframe.EasingFunction = Keyframe.EasingFunction;
_profileEditorService.UpdateSelectedProfileElement();
}
public void Delete()
{
PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.RemoveKeyframe(Keyframe);
_profileEditorService.UpdateSelectedProfileElement();
}
#endregion
#region Easing
private void CreateEasingViewModels()
{
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new PropertyTrackEasingViewModel(this, v)));
}
public void SelectEasingMode(PropertyTrackEasingViewModel easingViewModel)
{
Keyframe.EasingFunction = easingViewModel.EasingFunction;
// Set every selection to false except on the VM that made the change
foreach (var propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
propertyTrackEasingViewModel.IsEasingModeSelected = false;
_profileEditorService.UpdateSelectedProfileElement();
}
#endregion
#region Movement
private bool _movementReleased = true;
private TimeSpan _startOffset;
public void ApplyMovement(TimeSpan cursorTime)
{
if (_movementReleased)
{
_movementReleased = false;
_startOffset = cursorTime - Keyframe.Position;
}
else
{
Keyframe.Position = cursorTime - _startOffset;
if (Keyframe.Position < TimeSpan.Zero)
Keyframe.Position = TimeSpan.Zero;
Update(_pixelsPerSecond);
}
}
public void ReleaseMovement()
{
_movementReleased = true;
}
#endregion
}
public abstract class PropertyTrackKeyframeViewModel : PropertyChangedBase
{
}
}

View File

@ -1,111 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.PropertyTrackView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="20" d:DesignWidth="600"
d:DataContext="{d:DesignInstance local:PropertyTrackViewModel}">
<Border Height="25"
BorderThickness="0,0,0,1"
BorderBrush="{DynamicResource MaterialDesignDivider}"
Visibility="{Binding MustDisplay, Converter={StaticResource BoolToVisibilityConverter}}">
<ItemsControl ItemsSource="{Binding KeyframeViewModels}"
Background="{DynamicResource MaterialDesignToolBarBackground}"
HorizontalAlignment="Left">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding X}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
Stroke="White"
StrokeThickness="0"
Width="10"
Height="10"
Margin="-5,6,0,0"
ToolTip="{Binding Timestamp}"
s:View.ActionTarget="{Binding}"
MouseDown="{s:Action KeyframeMouseDown}"
MouseUp="{s:Action KeyframeMouseUp}"
MouseMove="{s:Action KeyframeMouseMove}">
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="1" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="0" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
<Ellipse.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy" Command="{s:Action Copy}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="ContentCopy" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Delete" Command="{s:Action Delete}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Delete" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding Path=IsEasingModeSelected, Mode=TwoWay}" />
</Style>
</MenuItem.ItemContainerStyle>
<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>
<!-- <MenuItem Header="Easing mode" IsEnabled="{Binding CanSelectEasingMode}"> -->
<!-- <MenuItem Header="Ease in" Command="{s:Action SetEasingMode}" CommandParameter="EaseIn" /> -->
<!-- <MenuItem Header="Ease out" Command="{s:Action SetEasingMode}" CommandParameter="EaseOut" /> -->
<!-- <MenuItem Header="Ease in and out" Command="{s:Action SetEasingMode}" CommandParameter="EaseInOut" /> -->
<!-- </MenuItem> -->
</ContextMenu>
</Ellipse.ContextMenu>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</UserControl>

View File

@ -1,91 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.UI.Ninject.Factories;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTrackViewModel : Screen
{
private readonly IPropertyTrackKeyframeVmFactory _propertyTrackKeyframeVmFactory;
public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel,
IPropertyTrackKeyframeVmFactory propertyTrackKeyframeVmFactory)
{
_propertyTrackKeyframeVmFactory = propertyTrackKeyframeVmFactory;
PropertyTimelineViewModel = propertyTimelineViewModel;
LayerPropertyViewModel = layerPropertyViewModel;
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
PopulateKeyframes();
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
LayerPropertyViewModel.ExpandedStateChanged += (sender, args) => UpdateMustDisplay();
LayerPropertyViewModel.LayerProperty.VisibilityChanged += (sender, args) => UpdateMustDisplay();
UpdateMustDisplay();
}
public PropertyTimelineViewModel PropertyTimelineViewModel { get; }
public LayerPropertyViewModel LayerPropertyViewModel { get; }
public BindableCollection<PropertyTrackKeyframeViewModel> KeyframeViewModels { get; set; }
public bool MustDisplay { get; set; }
private void UpdateMustDisplay()
{
var expandedTest = LayerPropertyViewModel.Parent;
while (expandedTest != null)
{
if (!expandedTest.IsExpanded)
{
MustDisplay = false;
return;
}
expandedTest = expandedTest.Parent;
}
var visibilityTest = LayerPropertyViewModel.LayerProperty;
while (visibilityTest != null)
{
if (visibilityTest.IsHidden)
{
MustDisplay = false;
return;
}
visibilityTest = visibilityTest.Parent;
}
MustDisplay = true;
}
public void PopulateKeyframes()
{
// Remove old keyframes
KeyframeViewModels.RemoveRange(KeyframeViewModels.ToList().Where(vm => !LayerPropertyViewModel.LayerProperty.UntypedKeyframes.Contains(vm.Keyframe)));
// Add new keyframes
KeyframeViewModels.AddRange(
LayerPropertyViewModel.LayerProperty.UntypedKeyframes
.Where(k => KeyframeViewModels.All(vm => vm.Keyframe != k))
.Select(k => _propertyTrackKeyframeVmFactory.Create(this, k))
);
UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
}
public void UpdateKeyframes(int pixelsPerSecond)
{
foreach (var keyframeViewModel in KeyframeViewModels)
{
keyframeViewModel.ParentView = View;
keyframeViewModel.Update(pixelsPerSecond);
}
}
protected override void OnViewLoaded()
{
foreach (var keyframeViewModel in KeyframeViewModels)
keyframeViewModel.ParentView = View;
base.OnViewLoaded();
}
}
}

View File

@ -0,0 +1,14 @@
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class TimelinePropertyGroupViewModel
{
public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
{
LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel;
}
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
}
}

View File

@ -0,0 +1,30 @@
using System;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class TimelinePropertyViewModel<T> : TimelinePropertyViewModel
{
public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel)
{
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
}
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
public override void Dispose()
{
}
}
public abstract class TimelinePropertyViewModel : IDisposable
{
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
{
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
}
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
public abstract void Dispose();
}
}

View File

@ -0,0 +1,14 @@
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class TimelineViewModel
{
public TimelineViewModel(BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
{
LayerPropertyGroups = layerPropertyGroups;
}
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
}
}

View File

@ -5,18 +5,20 @@ using Artemis.Core.Events;
using Artemis.Core.Models.Profile;
using Artemis.Core.Plugins.LayerBrush;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Utilities;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
{
public class BrushPropertyInputViewModel : PropertyInputViewModel
public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushReference>
{
private readonly ILayerService _layerService;
private readonly IPluginService _pluginService;
public BrushPropertyInputViewModel(IProfileEditorService profileEditorService, ILayerService layerService, IPluginService pluginService) : base(profileEditorService)
public BrushPropertyInputViewModel(LayerPropertyViewModel<LayerBrushReference> layerPropertyViewModel, ILayerService layerService, IPluginService pluginService)
: base(layerPropertyViewModel)
{
_layerService = layerService;
_pluginService = pluginService;
@ -26,9 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
}
public BindableCollection<ValueDescription> EnumValues { get; }
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(LayerBrushReference)};
public LayerBrushReference BrushInputValue
{
get => (LayerBrushReference) InputValue;

View File

@ -18,7 +18,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
public object EnumInputValue
{
get => InputValue ?? Enum.GetValues(LayerPropertyViewModel.LayerProperty.Type).Cast<object>().First();
get => InputValue ?? Enum.GetValues(GetLayerPropertyEnumType()).Cast<object>().First();
set => InputValue = value;
}
@ -29,7 +29,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
protected override void OnInitialized()
{
EnumValues = EnumUtilities.GetAllValuesAndDescriptions(LayerPropertyViewModel.LayerProperty.Type);
EnumValues = EnumUtilities.GetAllValuesAndDescriptions(GetLayerPropertyEnumType());
}
private Type GetLayerPropertyEnumType()
{
return LayerPropertyViewModel.BaseLayerProperty.GetType().GetGenericTypeDefinition();
}
}
}

View File

@ -0,0 +1,51 @@
using System;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput
{
public abstract class PropertyInputViewModel<T> : PropertyChangedBase, IDisposable
{
protected PropertyInputViewModel(LayerPropertyViewModel<T> layerPropertyViewModel)
{
LayerPropertyViewModel = layerPropertyViewModel;
LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated;
}
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
public bool InputDragging { get; private set; }
protected T InputValue
{
get => LayerPropertyViewModel.LayerProperty.CurrentValue;
set => LayerPropertyViewModel.SetCurrentValue(value, !InputDragging);
}
public abstract void Update();
private void LayerPropertyOnUpdated(object? sender, EventArgs e)
{
Update();
}
#region Event handlers
public void InputDragStarted(object sender, EventArgs e)
{
InputDragging = true;
}
public void InputDragEnded(object sender, EventArgs e)
{
InputDragging = false;
LayerPropertyViewModel.ProfileEditorService.UpdateSelectedProfileElement();
}
#endregion
public void Dispose()
{
LayerPropertyViewModel.LayerProperty.Updated -= LayerPropertyOnUpdated;
}
}
}

View File

@ -0,0 +1,14 @@
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
{
public class TreePropertyGroupViewModel
{
public TreePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
{
LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel)layerPropertyBaseViewModel;
}
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Artemis.Core.Models.Profile;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
{
public class TreePropertyViewModel<T> : TreePropertyViewModel
{
public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel)
{
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
PropertyInputViewModel = CreatePropertyInputViewModel();
}
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
public PropertyInputViewModel<T> PropertyInputViewModel { get; set; }
public override void Dispose()
{
PropertyInputViewModel.Dispose();
}
private PropertyInputViewModel<T> CreatePropertyInputViewModel()
{
if (!IsPropertySupported(typeof(T)))
throw new ArtemisUIException($"Failed to create a property input view model, type {typeof(T).Name} is not supported.");
return (PropertyInputViewModel<T>) Activator.CreateInstance(SupportedTypes[typeof(T)], this);
}
}
public abstract class TreePropertyViewModel : IDisposable
{
public static ReadOnlyDictionary<Type, Type> SupportedTypes = new ReadOnlyDictionary<Type, Type>(new Dictionary<Type, Type>
{
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)},
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)},
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)},
{typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}
});
public static void RegisterPropertyInputViewModel()
{
}
public static bool IsPropertySupported(Type type)
{
return SupportedTypes.ContainsKey(type);
}
protected TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
{
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
}
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
public abstract void Dispose();
}
}

View File

@ -1,14 +1,16 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyTreeView"
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.TreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:abstract="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract"
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:TreeViewModel}}">
<UserControl.Resources>
<Style x:Key="PropertyTreeStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="Background" Value="Transparent" />
@ -33,7 +35,8 @@
BorderThickness="0,0,0,1"
Height="25"
Padding="{TemplateBinding Padding}">
<Grid Margin="{Binding Converter={StaticResource lengthConverter}, RelativeSource={RelativeSource TemplatedParent}}">
<Grid
Margin="{Binding Converter={StaticResource lengthConverter}, RelativeSource={RelativeSource TemplatedParent}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19" />
<ColumnDefinition />
@ -79,30 +82,30 @@
</Setter>
</Style>
</UserControl.Resources>
<TreeView ItemsSource="{Binding PropertyTreeItemViewModels}"
<TreeView ItemsSource="{Binding LayerPropertyGroups}"
HorizontalContentAlignment="Stretch"
Background="{DynamicResource MaterialDesignToolBarBackground}"
PreviewMouseWheel="{s:Action PropertyTreePreviewMouseWheel}"
Margin="0 -1">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource PropertyTreeStyle}">
<Setter Property="IsExpanded" Value="{Binding LayerPropertyViewModel.IsExpanded, Mode=TwoWay}" />
<Setter Property="Visibility" Value="{Binding LayerPropertyViewModel.LayerProperty.IsHidden, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="Visibility" Value="{Binding IsVisible}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<!-- Parents only show their name and children -->
<HierarchicalDataTemplate DataType="{x:Type local:PropertyTreeParentViewModel}" ItemsSource="{Binding Children}">
<HierarchicalDataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding LayerPropertyViewModel.LayerProperty.Name}"
ToolTip="{Binding LayerPropertyViewModel.LayerProperty.Description}"
Margin="5 0"
<TextBlock Text="{Binding PropertyGroupDescription.Name}"
ToolTip="{Binding PropertyGroupDescription.Description}" Margin="5 0"
VerticalAlignment="Center" />
</StackPanel>
</HierarchicalDataTemplate>
<!-- Children show their full view -->
<DataTemplate DataType="{x:Type local:PropertyTreeChildViewModel}">
<ContentControl s:View.Model="{Binding}" />
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}">
<!-- <ContentControl s:View.Model="{Binding TreePropertyBaseViewModel}" /> -->
<TextBlock Text="{Binding PropertyDescription.Name}"
ToolTip="{Binding PropertyDescription.Description}" Margin="5 0" VerticalAlignment="Center" />
</DataTemplate>
</TreeView.Resources>
</TreeView>

View File

@ -0,0 +1,14 @@
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
{
public class TreeViewModel
{
public TreeViewModel(BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
{
LayerPropertyGroups = layerPropertyGroups;
}
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
}
}

View File

@ -25,7 +25,7 @@
</TextBlock>
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
</StackPanel>
<TreeView Grid.Row="1"
ItemsSource="{Binding RootFolder.Children}"
HorizontalContentAlignment="Stretch"
@ -39,6 +39,7 @@
<HierarchicalDataTemplate DataType="{x:Type treeItem:FolderViewModel}" ItemsSource="{Binding Children}">
<ContentControl s:View.Model="{Binding}" />
</HierarchicalDataTemplate>
<!-- TODO: Ensure this item source is required -->
<HierarchicalDataTemplate DataType="{x:Type treeItem:LayerViewModel}" ItemsSource="{Binding Children}">
<ContentControl s:View.Model="{Binding}" />
</HierarchicalDataTemplate>

View File

@ -122,9 +122,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
if (!SupportsChildren)
throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name);
var layer = new Layer(ProfileElement.Profile, ProfileElement, "New layer");
foreach (var baseLayerProperty in layer.Properties)
_layerService.InstantiateKeyframeEngine(baseLayerProperty);
var layer = _layerService.CreateLayer(ProfileElement.Profile, ProfileElement, "New layer");
ProfileElement.AddChild(layer);
UpdateProfileElements();
_profileEditorService.UpdateSelectedProfile();
@ -174,7 +172,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
}
// Ensure every child element has an up-to-date VM
if (ProfileElement.Children == null)
if (ProfileElement.Children == null)
return;
var newChildren = new List<TreeItemViewModel>();
@ -192,7 +190,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
}
}
if (!newChildren.Any())
if (!newChildren.Any())
return;
// Add the new children in one call, prevent extra UI events

View File

@ -94,15 +94,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
difference += 360;
else if (difference > 350)
difference -= 360;
newRotation = layer.Properties.Rotation.CurrentValue + difference;
newRotation = layer.Transform.Rotation.CurrentValue + difference;
// Round the end-result to increments of 5 as well, to avoid staying on an offset
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
newRotation = (float) Math.Round(newRotation / 5f) * 5f;
else
newRotation = (float) Math.Round(newRotation, 2, MidpointRounding.AwayFromZero);
layer.Properties.Rotation.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime);
layer.Transform.Rotation.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
@ -121,7 +121,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
var dragStart = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint();
_dragOffset = _layerEditorService.GetDragOffset(layer, dragStart);
_dragStart = dragStart + _dragOffset;
_dragStartScale = layer.Properties.Scale.CurrentValue;
_dragStartScale = layer.Transform.Scale.CurrentValue;
_isResizing = true;
}
@ -159,19 +159,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
break;
case ShapeControlPoint.TopCenter:
height = VerticalResize(layer, position, ResizeOrigin.Top);
width = layer.Properties.Scale.CurrentValue.Width;
width = layer.Transform.Scale.CurrentValue.Width;
break;
case ShapeControlPoint.RightCenter:
width = HorizontalResize(layer, position, ResizeOrigin.Right);
height = layer.Properties.Scale.CurrentValue.Height;
height = layer.Transform.Scale.CurrentValue.Height;
break;
case ShapeControlPoint.BottomCenter:
width = layer.Properties.Scale.CurrentValue.Width;
width = layer.Transform.Scale.CurrentValue.Width;
height = VerticalResize(layer, position, ResizeOrigin.Bottom);
break;
case ShapeControlPoint.LeftCenter:
width = HorizontalResize(layer, position, ResizeOrigin.Left);
height = layer.Properties.Scale.CurrentValue.Height;
height = layer.Transform.Scale.CurrentValue.Height;
break;
default:
throw new ArgumentOutOfRangeException();
@ -186,7 +186,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
height = (float) Math.Round(1.0 / bounds.Height * smallestSide, 2, MidpointRounding.AwayFromZero);
}
layer.Properties.Scale.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime);
layer.Transform.Scale.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
@ -319,7 +319,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
// Scale down the resulting position and make it relative
var scaled = _layerEditorService.GetScaledPoint(layer, position, true);
// Round and update the position property
layer.Properties.Position.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
layer.Transform.Position.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
@ -338,13 +338,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
var scaled = _layerEditorService.GetScaledPoint(layer, countered[1], false);
// Update the anchor point, this causes the shape to move
layer.Properties.AnchorPoint.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
layer.Transform.AnchorPoint.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
// TopLeft is not updated yet and acts as a snapshot of the top-left before changing the anchor
var path = _layerEditorService.GetLayerPath(layer, true, true, true);
// Calculate the (scaled) difference between the old and now position
var difference = _layerEditorService.GetScaledPoint(layer, _topLeft - path.Points[0], false);
// Apply the difference so that the shape effectively stays in place
layer.Properties.Position.SetCurrentValue(RoundPoint(layer.Properties.Position.CurrentValue + difference, 3), ProfileEditorService.CurrentTime);
layer.Transform.Position.SetCurrentValue(RoundPoint(layer.Transform.Position.CurrentValue + difference, 3), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
@ -362,9 +362,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
{
var counterRotatePath = new SKPath();
counterRotatePath.AddPoly(skPoints, false);
counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Properties.Rotation.CurrentValue * -1, pivot.X, pivot.Y));
counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y));
if (includeScale)
counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Properties.Scale.CurrentValue.Width / 100f), 1f / (layer.Properties.Scale.CurrentValue.Height / 100f)));
counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Transform.Scale.CurrentValue.Width / 100f), 1f / (layer.Transform.Scale.CurrentValue.Height / 100f)));
return counterRotatePath.Points;
}

View File

@ -1,7 +1,10 @@
using System;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.Core.Plugins.Abstract;
using Artemis.UI.Events;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
namespace Artemis.UI.Services.Interfaces
{
@ -11,6 +14,7 @@ namespace Artemis.UI.Services.Interfaces
ProfileElement SelectedProfileElement { get; }
TimeSpan CurrentTime { get; set; }
LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription);
void ChangeSelectedProfile(Profile profile);
void UpdateSelectedProfile();
void ChangeSelectedProfileElement(ProfileElement profileElement);
@ -18,6 +22,8 @@ namespace Artemis.UI.Services.Interfaces
void UpdateProfilePreview();
void UndoUpdateProfile(ProfileModule module);
void RedoUpdateProfile(ProfileModule module);
void StopRegularRender();
void ResumeRegularRender();
/// <summary>
/// Occurs when a new profile is selected
@ -48,8 +54,5 @@ namespace Artemis.UI.Services.Interfaces
/// Occurs when the profile preview has been updated
/// </summary>
event EventHandler ProfilePreviewUpdated;
void StopRegularRender();
void ResumeRegularRender();
}
}

View File

@ -35,7 +35,7 @@ namespace Artemis.UI.Services
public Point GetLayerAnchorPosition(Layer layer, SKPoint? positionOverride = null)
{
var layerBounds = GetLayerBounds(layer).ToSKRect();
var positionProperty = layer.Properties.Position.CurrentValue;
var positionProperty = layer.Transform.Position.CurrentValue;
if (positionOverride != null)
positionProperty = positionOverride.Value;
@ -59,7 +59,7 @@ namespace Artemis.UI.Services
// the layer using the structure of the XAML while the Core has to deal with that by applying the layer
// position to the translation
var anchorPosition = GetLayerAnchorPosition(layer);
var anchorProperty = layer.Properties.AnchorPoint.CurrentValue;
var anchorProperty = layer.Transform.AnchorPoint.CurrentValue;
// Translation originates from the unscaled center of the shape and is tied to the anchor
var x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width;
@ -67,8 +67,8 @@ namespace Artemis.UI.Services
var transformGroup = new TransformGroup();
transformGroup.Children.Add(new TranslateTransform(x, y));
transformGroup.Children.Add(new ScaleTransform(layer.Properties.Scale.CurrentValue.Width / 100f, layer.Properties.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
transformGroup.Children.Add(new RotateTransform(layer.Properties.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
transformGroup.Children.Add(new ScaleTransform(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
transformGroup.Children.Add(new RotateTransform(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
return transformGroup;
}
@ -83,7 +83,7 @@ namespace Artemis.UI.Services
if (anchorOverride != null)
anchorPosition = anchorOverride.Value;
var anchorProperty = layer.Properties.AnchorPoint.CurrentValue;
var anchorProperty = layer.Transform.AnchorPoint.CurrentValue;
// Translation originates from the unscaled center of the shape and is tied to the anchor
var x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width;
@ -94,9 +94,9 @@ namespace Artemis.UI.Services
if (includeTranslation)
path.Transform(SKMatrix.MakeTranslation(x, y));
if (includeScale)
path.Transform(SKMatrix.MakeScale(layer.Properties.Scale.CurrentValue.Width / 100f, layer.Properties.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
path.Transform(SKMatrix.MakeScale(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
if (includeRotation)
path.Transform(SKMatrix.MakeRotationDegrees(layer.Properties.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
path.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
return path;
}

View File

@ -1,11 +1,20 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Services.Interfaces;
using Artemis.Core.Services.Storage.Interfaces;
using Artemis.UI.Events;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
using Artemis.UI.Services.Interfaces;
using Ninject;
using Ninject.Parameters;
namespace Artemis.UI.Services
{
@ -13,15 +22,21 @@ namespace Artemis.UI.Services
{
private readonly ICoreService _coreService;
private readonly IProfileService _profileService;
private readonly IKernel _kernel;
private readonly List<Type> _registeredProfileEditors;
private TimeSpan _currentTime;
private TimeSpan _lastUpdateTime;
public ProfileEditorService(ICoreService coreService, IProfileService profileService)
public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel)
{
_coreService = coreService;
_profileService = profileService;
_kernel = kernel;
_registeredProfileEditors = new List<Type>();
}
public ReadOnlyCollection<Type> RegisteredPropertyEditors => _registeredProfileEditors.AsReadOnly();
public Profile SelectedProfile { get; private set; }
public ProfileElement SelectedProfileElement { get; private set; }
@ -38,6 +53,23 @@ namespace Artemis.UI.Services
}
}
public LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription)
{
// Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line
var genericType = baseLayerProperty.GetType().GetGenericArguments()[0];
// Only create entries for types supported by a tree input VM
if (!TreePropertyViewModel.IsPropertySupported(genericType))
return null;
var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType);
var parameters = new IParameter[]
{
new ConstructorArgument("layerProperty", baseLayerProperty),
new ConstructorArgument("propertyDescription", propertyDescription)
};
return (LayerPropertyBaseViewModel) _kernel.Get(genericViewModel, parameters);
}
public void ChangeSelectedProfile(Profile profile)
{
ChangeSelectedProfileElement(null);

View File

@ -35,7 +35,7 @@ namespace Artemis.Plugins.LayerBrushes.Color
GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged;
}
private void GradientTypeOnBaseValueChanged(object? sender, EventArgs e)
private void GradientTypeOnBaseValueChanged(object sender, EventArgs e)
{
Color.IsHidden = GradientType.BaseValue != LayerBrushes.Color.GradientType.Solid;
Gradient.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid;

View File

@ -57,7 +57,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise
ColorType.BaseValueChanged += ColorTypeOnBaseValueChanged;
}
private void ColorTypeOnBaseValueChanged(object? sender, EventArgs e)
private void ColorTypeOnBaseValueChanged(object sender, EventArgs e)
{
GradientColor.IsHidden = ColorType.BaseValue != ColorMappingType.Gradient;
MainColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple;