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

Folders - When display mode set to finish, finish all child timelines

Data bindings - Fixed an exception on profile editor undo
Timeline - Added segment options dialog where you can enter a time
This commit is contained in:
SpoinkyNL 2020-09-18 22:19:12 +02:00
parent 9f951ca33f
commit bfc93778a6
18 changed files with 369 additions and 102 deletions

View File

@ -103,6 +103,7 @@ namespace Artemis.Core
{ {
_disposed = true; _disposed = true;
Registration.DataBinding = null;
DataBindingMode?.Dispose(); DataBindingMode?.Dispose();
} }

View File

@ -57,6 +57,20 @@ namespace Artemis.Core
} }
internal FolderEntity FolderEntity { get; set; } internal FolderEntity FolderEntity { get; set; }
/// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties()
{
var result = new List<ILayerProperty>();
foreach (var layerEffect in LayerEffects)
{
if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
}
return result;
}
internal override RenderElementEntity RenderElementEntity => FolderEntity; internal override RenderElementEntity RenderElementEntity => FolderEntity;
public bool IsRootFolder => Parent == Profile; public bool IsRootFolder => Parent == Profile;
@ -88,6 +102,16 @@ namespace Artemis.Core
} }
} }
protected internal override void UpdateTimelineLength()
{
TimelineLength = !Children.Any() ? TimeSpan.Zero : Children.OfType<RenderProfileElement>().Max(c => c.TimelineLength);
if (StartSegmentLength + MainSegmentLength + EndSegmentLength > TimelineLength)
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength;
if (Parent is RenderProfileElement parent)
parent.UpdateTimelineLength();
}
public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment) public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment)
{ {
if (_disposed) if (_disposed)
@ -201,6 +225,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
base.AddChild(child, order); base.AddChild(child, order);
UpdateTimelineLength();
CalculateRenderProperties(); CalculateRenderProperties();
} }
@ -211,6 +236,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
base.RemoveChild(child); base.RemoveChild(child);
UpdateTimelineLength();
CalculateRenderProperties(); CalculateRenderProperties();
} }

View File

@ -69,6 +69,24 @@ namespace Artemis.Core
} }
internal LayerEntity LayerEntity { get; set; } internal LayerEntity LayerEntity { get; set; }
/// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties()
{
var result = new List<ILayerProperty>();
result.AddRange(General.GetAllLayerProperties());
result.AddRange(Transform.GetAllLayerProperties());
if (LayerBrush?.BaseProperties != null)
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
foreach (var layerEffect in LayerEffects)
{
if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
}
return result;
}
internal override RenderElementEntity RenderElementEntity => LayerEntity; internal override RenderElementEntity RenderElementEntity => LayerEntity;
/// <summary> /// <summary>
@ -269,6 +287,11 @@ namespace Artemis.Core
} }
} }
protected internal override void UpdateTimelineLength()
{
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength;
}
public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment) public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment)
{ {
if (_disposed) if (_disposed)

View File

@ -18,6 +18,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
public PropertyDescriptionAttribute PropertyDescription { get; } public PropertyDescriptionAttribute PropertyDescription { get; }
/// <summary>
/// Gets the unique path of the property on the layer
/// </summary>
public string Path { get; }
/// <summary> /// <summary>
/// Initializes the layer property /// Initializes the layer property
/// <para> /// <para>
@ -25,7 +30,7 @@ namespace Artemis.Core
/// <see cref="LayerProperty{T}" /> /// <see cref="LayerProperty{T}" />
/// </para> /// </para>
/// </summary> /// </summary>
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description); void Initialize(RenderProfileElement profileElement, LayerPropertyGroup @group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path);
/// <summary> /// <summary>
/// Returns a list off all data binding registrations /// Returns a list off all data binding registrations

View File

@ -30,6 +30,9 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public PropertyDescriptionAttribute PropertyDescription { get; internal set; } public PropertyDescriptionAttribute PropertyDescription { get; internal set; }
/// <inheritdoc />
public string Path { get; private set; }
/// <summary> /// <summary>
/// Updates the property, applying keyframes and data bindings to the current value /// Updates the property, applying keyframes and data bindings to the current value
/// </summary> /// </summary>
@ -384,6 +387,8 @@ namespace Artemis.Core
if (dataBindingRegistration.LayerProperty != this) if (dataBindingRegistration.LayerProperty != this)
throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property"); throw new ArtemisCoreException("Cannot enable a data binding using a data binding registration of a different layer property");
if (dataBindingRegistration.DataBinding != null)
throw new ArtemisCoreException("Provided data binding registration already has an enabled data binding");
var dataBinding = new DataBinding<T, TProperty>(dataBindingRegistration); var dataBinding = new DataBinding<T, TProperty>(dataBindingRegistration);
_dataBindings.Add(dataBinding); _dataBindings.Add(dataBinding);
@ -431,7 +436,7 @@ namespace Artemis.Core
internal PropertyEntity Entity { get; set; } internal PropertyEntity Entity { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description) public void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
@ -440,6 +445,7 @@ namespace Artemis.Core
ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement)); ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement));
LayerPropertyGroup = group ?? throw new ArgumentNullException(nameof(group)); LayerPropertyGroup = group ?? throw new ArgumentNullException(nameof(group));
Path = path;
Entity = entity ?? throw new ArgumentNullException(nameof(entity)); Entity = entity ?? throw new ArgumentNullException(nameof(entity));
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description)); PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
IsLoadedFromStorage = fromStorage; IsLoadedFromStorage = fromStorage;

View File

@ -207,21 +207,21 @@ namespace Artemis.Core
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription) private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
{ {
var path = Path + "."; var path = $"{Path}.{propertyInfo.Name}";
if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType)) if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path + propertyInfo.Name}"); throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path}");
var instance = (ILayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true); var instance = (ILayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true);
if (instance == null) if (instance == null)
throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}"); throw new ArtemisPluginException($"Failed to create instance of layer property at {path}");
// Ensure the description has a name, if not this is a good point to set it based on the property info // Ensure the description has a name, if not this is a good point to set it based on the property info
if (string.IsNullOrWhiteSpace(propertyDescription.Name)) if (string.IsNullOrWhiteSpace(propertyDescription.Name))
propertyDescription.Name = propertyInfo.Name.Humanize(); propertyDescription.Name = propertyInfo.Name.Humanize();
var entity = GetPropertyEntity(ProfileElement, path + propertyInfo.Name, out var fromStorage); var entity = GetPropertyEntity(ProfileElement, path, out var fromStorage);
instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription); instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription, path);
propertyInfo.SetValue(this, instance); propertyInfo.SetValue(this, instance);
_layerProperties.Add(instance); _layerProperties.Add(instance);
} }

View File

@ -19,6 +19,23 @@ namespace Artemis.Core
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
} }
public abstract List<ILayerProperty> GetAllLayerProperties();
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (var baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
internal void ApplyRenderElementDefaults() internal void ApplyRenderElementDefaults()
{ {
MainSegmentLength = TimeSpan.FromSeconds(5); MainSegmentLength = TimeSpan.FromSeconds(5);
@ -135,7 +152,13 @@ namespace Artemis.Core
public TimeSpan StartSegmentLength public TimeSpan StartSegmentLength
{ {
get => _startSegmentLength; get => _startSegmentLength;
set => SetAndNotify(ref _startSegmentLength, value); set
{
if (!SetAndNotify(ref _startSegmentLength, value)) return;
UpdateTimelineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimelineLength();
}
} }
/// <summary> /// <summary>
@ -144,7 +167,13 @@ namespace Artemis.Core
public TimeSpan MainSegmentLength public TimeSpan MainSegmentLength
{ {
get => _mainSegmentLength; get => _mainSegmentLength;
set => SetAndNotify(ref _mainSegmentLength, value); set
{
if (!SetAndNotify(ref _mainSegmentLength, value)) return;
UpdateTimelineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimelineLength();
}
} }
/// <summary> /// <summary>
@ -153,7 +182,13 @@ namespace Artemis.Core
public TimeSpan EndSegmentLength public TimeSpan EndSegmentLength
{ {
get => _endSegmentLength; get => _endSegmentLength;
set => SetAndNotify(ref _endSegmentLength, value); set
{
if (!SetAndNotify(ref _endSegmentLength, value)) return;
UpdateTimelineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimelineLength();
}
} }
/// <summary> /// <summary>
@ -165,11 +200,6 @@ namespace Artemis.Core
protected set => SetAndNotify(ref _timelinePosition, value); protected set => SetAndNotify(ref _timelinePosition, value);
} }
/// <summary>
/// Gets the total combined length of all three segments
/// </summary>
public TimeSpan TimelineLength => StartSegmentLength + MainSegmentLength + EndSegmentLength;
/// <summary> /// <summary>
/// Gets or sets whether main timeline should repeat itself as long as display conditions are met /// Gets or sets whether main timeline should repeat itself as long as display conditions are met
/// </summary> /// </summary>
@ -188,6 +218,11 @@ namespace Artemis.Core
set => SetAndNotify(ref _alwaysFinishTimeline, value); set => SetAndNotify(ref _alwaysFinishTimeline, value);
} }
/// <summary>
/// Gets the max length of this element and any of its children
/// </summary>
public TimeSpan TimelineLength { get; protected set; }
protected double UpdateTimeline(double deltaTime) protected double UpdateTimeline(double deltaTime)
{ {
var oldPosition = _timelinePosition; var oldPosition = _timelinePosition;
@ -195,7 +230,6 @@ namespace Artemis.Core
var mainSegmentEnd = StartSegmentLength + MainSegmentLength; var mainSegmentEnd = StartSegmentLength + MainSegmentLength;
TimelinePosition += deltaTimeSpan; TimelinePosition += deltaTimeSpan;
// Manage segments while the condition is met // Manage segments while the condition is met
if (DisplayConditionMet) if (DisplayConditionMet)
{ {
@ -213,6 +247,8 @@ namespace Artemis.Core
return (TimelinePosition - oldPosition).TotalSeconds; return (TimelinePosition - oldPosition).TotalSeconds;
} }
protected internal abstract void UpdateTimelineLength();
/// <summary> /// <summary>
/// Overrides the progress of the element /// Overrides the progress of the element
/// </summary> /// </summary>
@ -380,21 +416,6 @@ namespace Artemis.Core
#endregion #endregion
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (var baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
#region Events #region Events
public event EventHandler LayerEffectsUpdated; public event EventHandler LayerEffectsUpdated;

View File

@ -35,7 +35,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AvalonEdit" Version="6.0.1" /> <PackageReference Include="AvalonEdit" Version="6.0.1" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" /> <PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="MaterialDesignExtensions" Version="3.1.0" /> <PackageReference Include="MaterialDesignExtensions" Version="3.2.0" />
<PackageReference Include="MaterialDesignThemes" Version="3.1.3" /> <PackageReference Include="MaterialDesignThemes" Version="3.1.3" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" />
<PackageReference Include="Ninject" Version="3.3.4" /> <PackageReference Include="Ninject" Version="3.3.4" />

View File

@ -40,8 +40,7 @@
</Border.Background> </Border.Background>
</Border> </Border>
</Border> </Border>
<Track <Track x:Name="PART_Track"
x:Name="PART_Track"
Grid.Row="1" Grid.Row="1"
OpacityMask="{x:Null}"> OpacityMask="{x:Null}">
<Track.DecreaseRepeatButton> <Track.DecreaseRepeatButton>
@ -101,13 +100,19 @@
<RowDefinition Height="160" /> <RowDefinition Height="160" />
<RowDefinition /> <RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<materialDesign:ColorPicker Grid.Row="0" Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" MouseUp="ColorGradient_OnMouseUp" /> <materialDesign:ColorPicker Grid.Row="0"
Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
MouseUp="ColorGradient_OnMouseUp"
PreviewMouseDown="Slider_OnMouseDown"
PreviewMouseUp="Slider_OnMouseUp"/>
<Slider Grid.Row="1" Margin="8" <Slider Grid.Row="1" Margin="8"
IsMoveToPointEnabled="True" IsMoveToPointEnabled="True"
Orientation="Horizontal" Orientation="Horizontal"
Style="{DynamicResource MaterialDesignColorSlider}" Style="{DynamicResource MaterialDesignColorSlider}"
Template="{StaticResource MaterialDesignOpacitySlider}" Template="{StaticResource MaterialDesignOpacitySlider}"
Value="{Binding ColorOpacity, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Value="{Binding ColorOpacity, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
PreviewMouseDown="Slider_OnMouseDown"
PreviewMouseUp="Slider_OnMouseUp"
Maximum="255" /> Maximum="255" />
</Grid> </Grid>
</materialDesign:Card> </materialDesign:Card>

View File

@ -1,4 +1,5 @@
using System.ComponentModel; using System;
using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@ -139,5 +140,33 @@ namespace Artemis.UI.Shared
{ {
e.Handled = true; e.Handled = true;
} }
private void Slider_OnMouseDown(object sender, MouseButtonEventArgs e)
{
OnDragStarted();
}
private void Slider_OnMouseUp(object sender, MouseButtonEventArgs e)
{
OnDragEnded();
}
#region Events
public event EventHandler DragStarted;
public event EventHandler DragEnded;
protected virtual void OnDragStarted()
{
DragStarted?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnDragEnded()
{
DragEnded?.Invoke(this, EventArgs.Empty);
}
#endregion
} }
} }

View File

@ -154,23 +154,7 @@ namespace Artemis.UI.Shared.Services
if (!undid) if (!undid)
return false; return false;
if (SelectedProfileElement is Folder folder) ReloadProfile();
SelectedProfileElement = SelectedProfile.GetAllFolders().FirstOrDefault(f => f.EntityId == folder.EntityId);
else if (SelectedProfileElement is Layer layer)
SelectedProfileElement = SelectedProfile.GetAllLayers().FirstOrDefault(l => l.EntityId == layer.EntityId);
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile));
OnSelectedProfileElementChanged(new RenderProfileElementEventArgs(SelectedProfileElement));
if (SelectedProfileElement != null)
{
var elements = SelectedProfile.GetAllLayers().Cast<RenderProfileElement>().ToList();
elements.AddRange(SelectedProfile.GetAllFolders());
var element = elements.FirstOrDefault(l => l.EntityId == SelectedProfileElement.EntityId);
ChangeSelectedProfileElement(element);
}
UpdateProfilePreview();
return true; return true;
} }
@ -180,17 +164,7 @@ namespace Artemis.UI.Shared.Services
if (!redid) if (!redid)
return false; return false;
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile)); ReloadProfile();
if (SelectedProfileElement != null)
{
var elements = SelectedProfile.GetAllLayers().Cast<RenderProfileElement>().ToList();
elements.AddRange(SelectedProfile.GetAllFolders());
var element = elements.FirstOrDefault(l => l.EntityId == SelectedProfileElement.EntityId);
ChangeSelectedProfileElement(element);
}
UpdateProfilePreview();
return true; return true;
} }
@ -307,6 +281,27 @@ namespace Artemis.UI.Shared.Services
return SelectedProfile?.Module; return SelectedProfile?.Module;
} }
private void ReloadProfile()
{
// Trigger a profile change
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile));
// Trigger a selected element change
var previousSelectedProfileElement = SelectedProfileElement;
if (SelectedProfileElement is Folder folder)
SelectedProfileElement = SelectedProfile.GetAllFolders().FirstOrDefault(f => f.EntityId == folder.EntityId);
else if (SelectedProfileElement is Layer layer)
SelectedProfileElement = SelectedProfile.GetAllLayers().FirstOrDefault(l => l.EntityId == layer.EntityId);
OnSelectedProfileElementChanged(new RenderProfileElementEventArgs(SelectedProfileElement, previousSelectedProfileElement));
// Trigger selected data binding change
if (SelectedDataBinding != null)
{
SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties()?.FirstOrDefault(p => p.Path == SelectedDataBinding.Path);
OnSelectedDataBindingChanged();
}
UpdateProfilePreview();
}
public event EventHandler<ProfileEventArgs> ProfileSelected; public event EventHandler<ProfileEventArgs> ProfileSelected;
public event EventHandler<ProfileEventArgs> SelectedProfileUpdated; public event EventHandler<ProfileEventArgs> SelectedProfileUpdated;
public event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected; public event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected;

View File

@ -132,11 +132,11 @@
<Resource Include="Resources\Images\Sidebar\sidebar-header.png" /> <Resource Include="Resources\Images\Sidebar\sidebar-header.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation" Version="8.6.2" /> <PackageReference Include="FluentValidation" Version="9.2.0" />
<PackageReference Include="gong-wpf-dragdrop" Version="2.2.0" /> <PackageReference Include="gong-wpf-dragdrop" Version="2.2.0" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf.NetCore" Version="1.0.11" /> <PackageReference Include="Hardcodet.NotifyIcon.Wpf.NetCore" Version="1.0.11" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" /> <PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="MaterialDesignExtensions" Version="3.1.0" /> <PackageReference Include="MaterialDesignExtensions" Version="3.2.0" />
<PackageReference Include="MaterialDesignThemes" Version="3.1.3" /> <PackageReference Include="MaterialDesignThemes" Version="3.1.3" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" />

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput" xmlns:propertyInput="clr-namespace:Artemis.UI.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800" d:DesignHeight="25" d:DesignWidth="800"
d:DataContext="{d:DesignInstance propertyInput:SKColorPropertyInputViewModel}"> d:DataContext="{d:DesignInstance propertyInput:SKColorPropertyInputViewModel}">
@ -17,7 +18,9 @@
Margin="0 -2 0 3" Margin="0 -2 0 3"
Padding="0 -1" Padding="0 -1"
Color="{Binding InputValue, Converter={StaticResource SKColorToColorConverter}}" Color="{Binding InputValue, Converter={StaticResource SKColorToColorConverter}}"
IsEnabled="{Binding IsEnabled}"/> IsEnabled="{Binding IsEnabled}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}"/>
<TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" /> <TextBlock Width="10" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@ -20,8 +20,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
private DataBindingModeType _selectedDataBindingMode; private DataBindingModeType _selectedDataBindingMode;
private TimelineEasingViewModel _selectedEasingViewModel; private TimelineEasingViewModel _selectedEasingViewModel;
private TProperty _testInputValue;
private TProperty _testResultValue;
private bool _updating; private bool _updating;
private bool _isDataBindingEnabled; private bool _isDataBindingEnabled;
@ -45,8 +43,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
TestInputValue = _dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), true); TestInputValue = _dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), true);
TestResultValue = _dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), true); TestResultValue = _dataModelUIService.GetDataModelDisplayViewModel(typeof(TProperty), true);
DataBinding = Registration.DataBinding;
Initialize(); Initialize();
} }
@ -103,12 +99,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
} }
} }
public DataBinding<TLayerProperty, TProperty> DataBinding
{
get => _dataBinding;
set => SetAndNotify(ref _dataBinding, value);
}
public void Dispose() public void Dispose()
{ {
_profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated; _profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated;
@ -125,13 +115,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
private void CreateDataBindingModeModeViewModel() private void CreateDataBindingModeModeViewModel()
{ {
if (DataBinding?.DataBindingMode == null) if (Registration.DataBinding?.DataBindingMode == null)
{ {
ActiveItem = null; ActiveItem = null;
return; return;
} }
switch (DataBinding.DataBindingMode) switch (Registration.DataBinding.DataBindingMode)
{ {
case DirectDataBinding<TLayerProperty, TProperty> directDataBinding: case DirectDataBinding<TLayerProperty, TProperty> directDataBinding:
ActiveItem = _dataBindingsVmFactory.DirectDataBindingModeViewModel(directDataBinding); ActiveItem = _dataBindingsVmFactory.DirectDataBindingModeViewModel(directDataBinding);
@ -147,7 +137,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
if (_updating) if (_updating)
return; return;
if (DataBinding == null) if (Registration.DataBinding == null)
{ {
IsEasingTimeEnabled = false; IsEasingTimeEnabled = false;
return; return;
@ -156,10 +146,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
_updating = true; _updating = true;
IsDataBindingEnabled = ActiveItem != null; IsDataBindingEnabled = ActiveItem != null;
EasingTime = (int) DataBinding.EasingTime.TotalMilliseconds; EasingTime = (int) Registration.DataBinding.EasingTime.TotalMilliseconds;
SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == DataBinding.EasingFunction); SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == Registration.DataBinding.EasingFunction);
IsEasingTimeEnabled = EasingTime > 0; IsEasingTimeEnabled = EasingTime > 0;
switch (DataBinding.DataBindingMode) switch (Registration.DataBinding.DataBindingMode)
{ {
case DirectDataBinding<TLayerProperty, TProperty> _: case DirectDataBinding<TLayerProperty, TProperty> _:
SelectedDataBindingMode = DataBindingModeType.Direct; SelectedDataBindingMode = DataBindingModeType.Direct;
@ -182,10 +172,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
if (_updating) if (_updating)
return; return;
if (DataBinding != null) if (Registration.DataBinding != null)
{ {
DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime); Registration.DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime);
DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear; Registration.DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear;
} }
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
@ -197,17 +187,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
if (_updating) if (_updating)
return; return;
if (DataBinding != null && SelectedDataBindingMode == DataBindingModeType.None) if (Registration.DataBinding != null && SelectedDataBindingMode == DataBindingModeType.None)
{ {
RemoveDataBinding(); RemoveDataBinding();
CreateDataBindingModeModeViewModel(); CreateDataBindingModeModeViewModel();
return; return;
} }
if (DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None) if (Registration.DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None)
EnableDataBinding(); EnableDataBinding();
DataBinding.ChangeDataBindingMode(SelectedDataBindingMode); Registration.DataBinding.ChangeDataBindingMode(SelectedDataBindingMode);
CreateDataBindingModeModeViewModel(); CreateDataBindingModeModeViewModel();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
@ -215,7 +205,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
private void UpdateTestResult() private void UpdateTestResult()
{ {
if (DataBinding == null) if (Registration.DataBinding == null)
{ {
TestInputValue.UpdateValue(default); TestInputValue.UpdateValue(default);
TestResultValue.UpdateValue(default); TestResultValue.UpdateValue(default);
@ -225,26 +215,24 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
var currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty)); var currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty));
TestInputValue.UpdateValue(currentValue); TestInputValue.UpdateValue(currentValue);
TestResultValue.UpdateValue(DataBinding != null ? DataBinding.GetValue(currentValue) : default); TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default);
} }
private void EnableDataBinding() private void EnableDataBinding()
{ {
if (DataBinding != null) if (Registration.DataBinding != null)
return; return;
DataBinding = Registration.LayerProperty.EnableDataBinding(Registration); Registration.LayerProperty.EnableDataBinding(Registration);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
private void RemoveDataBinding() private void RemoveDataBinding()
{ {
if (DataBinding == null) if (Registration.DataBinding == null)
return; return;
var toDisable = DataBinding; Registration.LayerProperty.DisableDataBinding(Registration.DataBinding);
DataBinding = null;
Registration.LayerProperty.DisableDataBinding(toDisable);
Update(); Update();
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();

View File

@ -0,0 +1,41 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Dialogs.TimelineSegmentDialogView"
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.ProfileEditor.LayerProperties.Timeline.Dialogs"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:TimelineSegmentDialogViewModel}">
<StackPanel Margin="16" Width="240">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
<Run Text="Update " /><Run Text="{Binding Segment.Segment, Mode=OneWay}" /><Run Text=" segment" />
</TextBlock>
<TextBox materialDesign:HintAssist.Hint="Segment length (seconds)"
Margin="0 8 0 16"
Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 8 8 0" Command="{s:Action Cancel}">
<Button.CommandParameter>
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
False
</system:Boolean>
</Button.CommandParameter>
CANCEL
</Button>
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 8 0" Command="{s:Action Accept}">
<Button.CommandParameter>
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
True
</system:Boolean>
</Button.CommandParameter>
ACCEPT
</Button>
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,98 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Artemis.UI.Shared.Services;
using Castle.Core.Internal;
using FluentValidation;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Dialogs
{
public class TimelineSegmentDialogViewModel : DialogViewModelBase
{
private string _inputValue;
public TimelineSegmentDialogViewModel(IModelValidator<TimelineSegmentDialogViewModel> validator, TimelineSegmentViewModel segment)
: base(validator)
{
Segment = segment;
InputValue = $"{Math.Floor(Segment.SegmentLength.TotalSeconds):00}.{Segment.SegmentLength.Milliseconds:000}";
}
public TimelineSegmentViewModel Segment { get; }
public string InputValue
{
get => _inputValue;
set => SetAndNotify(ref _inputValue, value);
}
public async Task Accept()
{
await ValidateAsync();
if (HasErrors)
return;
Segment.UpdateLength(TimelineSegmentDialogViewModelValidator.CreateTime(InputValue));
Session.Close();
}
public void Cancel()
{
Session.Close();
}
}
public class TimelineSegmentDialogViewModelValidator : AbstractValidator<TimelineSegmentDialogViewModel>
{
private readonly Regex _inputRegex = new Regex("^[.][-|0-9]+$|^-?[0-9]*[.]{0,1}[0-9]*$");
public TimelineSegmentDialogViewModelValidator()
{
CascadeMode = CascadeMode.Stop;
RuleFor(m => m.InputValue)
.NotNull()
.WithMessage("A timeline length is required");
RuleFor(m => m.InputValue)
.Must(ValidateTime)
.WithMessage("Input cannot be converted to a time");
RuleFor(m => m.InputValue)
.Transform(CreateTime)
.GreaterThanOrEqualTo(TimeSpan.FromMilliseconds(100))
.WithMessage("Minimum timeline length is 100ms");
RuleFor(m => m.InputValue)
.Transform(CreateTime)
.LessThanOrEqualTo(TimeSpan.FromHours(24))
.WithMessage("Maximum timeline length is 24 hours");
}
public static TimeSpan CreateTime(string s)
{
var parts = s.Split(".");
// Only seconds provided
if (parts.Length == 1)
return TimeSpan.FromSeconds(double.Parse(parts[0]));
// Only milliseconds provided with a leading .
if (parts[0].IsNullOrEmpty())
{
// Add trailing zeros so 2.5 becomes 2.500, can't seem to make double.Parse do that
while (parts[0].Length < 3) parts[0] += "0";
return TimeSpan.FromMilliseconds(double.Parse(parts[1], CultureInfo.InvariantCulture));
}
// Seconds and milliseconds provided
// Add trailing zeros so 2.5 becomes 2.500, can't seem to make double.Parse do that
while (parts[1].Length < 3) parts[1] += "0";
return TimeSpan.FromSeconds(double.Parse(parts[0])).Add(TimeSpan.FromMilliseconds(double.Parse(parts[1])));
}
private bool ValidateTime(string arg)
{
return _inputRegex.IsMatch(arg);
}
}
}

View File

@ -16,11 +16,21 @@
<StackPanel Orientation="Horizontal" Visibility="{Binding SegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"> <StackPanel Orientation="Horizontal" Visibility="{Binding SegmentEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
<StackPanel.ContextMenu> <StackPanel.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="Open segment settings" Command="{s:Action OpenSettingsDialog}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Cog" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Repeat main segment" <MenuItem Header="Repeat main segment"
IsCheckable="True" IsCheckable="True"
IsChecked="{Binding Data.RepeatSegment, Source={StaticResource DataContextProxy}}" IsChecked="{Binding Data.RepeatSegment, Source={StaticResource DataContextProxy}}"
Visibility="{Binding Data.IsMainSegment, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Source={StaticResource DataContextProxy}}" /> Visibility="{Binding Data.IsMainSegment, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Source={StaticResource DataContextProxy}}" />
<MenuItem Header="Disable segment" Command="{s:Action DisableSegment}" /> <MenuItem Header="Disable segment" Command="{s:Action DisableSegment}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Close" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu> </ContextMenu>
</StackPanel.ContextMenu> </StackPanel.ContextMenu>

View File

@ -1,10 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Screens.ProfileEditor.Dialogs;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Dialogs;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;
@ -13,6 +17,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{ {
public class TimelineSegmentViewModel : Screen, IDisposable public class TimelineSegmentViewModel : Screen, IDisposable
{ {
private readonly IDialogService _dialogService;
private bool _draggingSegment; private bool _draggingSegment;
private bool _showDisableButton; private bool _showDisableButton;
private bool _showRepeatButton; private bool _showRepeatButton;
@ -25,8 +30,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
private double _segmentStartPosition; private double _segmentStartPosition;
public TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups, public TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups,
IProfileEditorService profileEditorService) IProfileEditorService profileEditorService, IDialogService dialogService)
{ {
_dialogService = dialogService;
ProfileEditorService = profileEditorService; ProfileEditorService = profileEditorService;
Segment = segment; Segment = segment;
LayerPropertyGroups = layerPropertyGroups; LayerPropertyGroups = layerPropertyGroups;
@ -130,6 +136,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
set => SetAndNotify(ref _showDisableButton, value); set => SetAndNotify(ref _showDisableButton, value);
} }
public async Task OpenSettingsDialog()
{
await _dialogService.ShowDialog<TimelineSegmentDialogViewModel>(new Dictionary<string, object> {{"segment", this}});
}
#region Updating #region Updating
private void UpdateHeader() private void UpdateHeader()
@ -276,6 +287,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0); newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0);
UpdateLength(newTime);
}
public void UpdateLength(TimeSpan newTime)
{
var oldSegmentLength = SegmentLength; var oldSegmentLength = SegmentLength;
if (Segment == SegmentViewModelType.Start) if (Segment == SegmentViewModelType.Start)
{ {