From 10c839f8c9b5846d1e5d8e535d5d9e0de2395926 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 3 Aug 2020 22:41:13 +0200 Subject: [PATCH] Color brush - Added linear gradient rotation Profile editor - Updated conditions UI --- src/Artemis.Core/Models/Profile/Folder.cs | 25 +- src/Artemis.Core/Models/Profile/Layer.cs | 10 +- .../LayerProperties/BaseLayerProperty.cs | 25 +- .../Models/Profile/LayerPropertyGroup.cs | 21 ++ .../Models/Profile/RenderProfileElement.cs | 14 +- .../Profile/Abstract/RenderElementEntity.cs | 2 +- .../Migrations/M4ProfileSegments.cs | 6 +- .../Converters/NullToBooleanConverter.cs | 40 ++++ .../Input/DoubleDataModelInputView.xaml | 2 +- .../Input/IntDataModelInputView.xaml | 2 +- .../Input/StringDataModelInputView.xaml | 2 +- .../DisplayConditionGroupView.xaml | 3 +- .../DisplayConditionGroupViewModel.cs | 7 + .../DisplayConditionPredicateView.xaml | 3 +- .../DisplayConditionsView.xaml | 217 +++++++++++++----- .../DisplayConditionsViewModel.cs | 15 +- .../LayerEffects/EffectsViewModel.cs | 9 + .../LayerProperties/LayerPropertiesView.xaml | 6 +- .../Timeline/TimelineSegmentViewModel.cs | 8 +- .../ColorBrush.cs | 130 +++++++---- .../ColorBrushProperties.cs | 3 + 21 files changed, 400 insertions(+), 150 deletions(-) create mode 100644 src/Artemis.UI/Converters/NullToBooleanConverter.cs diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index c95d37598..6513c3d67 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -21,6 +21,7 @@ namespace Artemis.Core.Models.Profile Parent = parent; Name = name; Enabled = true; + DisplayContinuously = true; _layerEffects = new List(); _expandedPropertyGroups = new List(); @@ -95,7 +96,7 @@ namespace Artemis.Core.Models.Profile if (stickToMainSegment) { - if (!RepeatMainSegment) + if (!DisplayContinuously) { var position = timeOverride + StartSegmentLength; if (position > StartSegmentLength + EndSegmentLength) @@ -146,6 +147,16 @@ namespace Artemis.Core.Models.Profile folderPath.Transform(SKMatrix.MakeTranslation(folderPath.Bounds.Left * -1, folderPath.Bounds.Top * -1)); + var targetLocation = Path.Bounds.Location; + if (Parent is Folder parentFolder) + targetLocation -= parentFolder.Path.Bounds.Location; + + canvas.Save(); + + using var clipPath = new SKPath(folderPath); + clipPath.Transform(SKMatrix.MakeTranslation(targetLocation.X, targetLocation.Y)); + canvas.ClipPath(clipPath); + foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) baseLayerEffect.PreProcess(folderCanvas, _folderBitmap.Info, folderPath, folderPaint); @@ -161,17 +172,7 @@ namespace Artemis.Core.Models.Profile profileElement.Render(deltaTime, folderCanvas, _folderBitmap.Info); folderCanvas.Restore(); } - - var targetLocation = Path.Bounds.Location; - if (Parent is Folder parentFolder) - targetLocation -= parentFolder.Path.Bounds.Location; - - canvas.Save(); - - using var clipPath = new SKPath(folderPath); - clipPath.Transform(SKMatrix.MakeTranslation(targetLocation.X, targetLocation.Y)); - canvas.ClipPath(clipPath); - + foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) baseLayerEffect.PostProcess(canvas, canvasInfo, folderPath, folderPaint); canvas.DrawBitmap(_folderBitmap, targetLocation, folderPaint); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 224a08896..f269b5ab3 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -39,6 +39,7 @@ namespace Artemis.Core.Models.Profile Parent = parent; Name = name; Enabled = true; + DisplayContinuously = true; General = new LayerGeneralProperties {IsCorePropertyGroup = true}; Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; @@ -131,8 +132,11 @@ namespace Artemis.Core.Models.Profile keyframes.AddRange(baseLayerProperty.BaseKeyframes); foreach (var baseLayerProperty in Transform.GetAllLayerProperties()) keyframes.AddRange(baseLayerProperty.BaseKeyframes); - foreach (var baseLayerProperty in LayerBrush.BaseProperties.GetAllLayerProperties()) - keyframes.AddRange(baseLayerProperty.BaseKeyframes); + if (LayerBrush?.BaseProperties != null) + { + foreach (var baseLayerProperty in LayerBrush.BaseProperties.GetAllLayerProperties()) + keyframes.AddRange(baseLayerProperty.BaseKeyframes); + } return keyframes; } @@ -246,7 +250,7 @@ namespace Artemis.Core.Models.Profile if (stickToMainSegment) { - if (!RepeatMainSegment) + if (!DisplayContinuously) { var position = timeOverride + StartSegmentLength; if (position > StartSegmentLength + EndSegmentLength) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 54692aff2..f865fae4e 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Artemis.Core.Events; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Storage.Entities.Profile; @@ -102,61 +103,61 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// Occurs once every frame when the layer property is updated /// - public event EventHandler Updated; + public event EventHandler Updated; /// /// Occurs when the base value of the layer property was updated /// - public event EventHandler BaseValueChanged; + public event EventHandler BaseValueChanged; /// /// Occurs when the value of the layer property was updated /// - public event EventHandler VisibilityChanged; + public event EventHandler VisibilityChanged; /// /// Occurs when keyframes are enabled/disabled /// - public event EventHandler KeyframesToggled; + public event EventHandler KeyframesToggled; /// /// Occurs when a new keyframe was added to the layer property /// - public event EventHandler KeyframeAdded; + public event EventHandler KeyframeAdded; /// /// Occurs when a keyframe was removed from the layer property /// - public event EventHandler KeyframeRemoved; + public event EventHandler KeyframeRemoved; protected virtual void OnUpdated() { - Updated?.Invoke(this, EventArgs.Empty); + Updated?.Invoke(this, new LayerPropertyEventArgs(this)); } protected virtual void OnBaseValueChanged() { - BaseValueChanged?.Invoke(this, EventArgs.Empty); + BaseValueChanged?.Invoke(this, new LayerPropertyEventArgs(this)); } protected virtual void OnVisibilityChanged() { - VisibilityChanged?.Invoke(this, EventArgs.Empty); + VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this)); } protected virtual void OnKeyframesToggled() { - KeyframesToggled?.Invoke(this, EventArgs.Empty); + KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this)); } protected virtual void OnKeyframeAdded() { - KeyframeAdded?.Invoke(this, EventArgs.Empty); + KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); } protected virtual void OnKeyframeRemoved() { - KeyframeRemoved?.Invoke(this, EventArgs.Empty); + KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); } #endregion diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index d8996de33..04a1d56b6 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -253,13 +253,29 @@ namespace Artemis.Core.Models.Profile } instance.ApplyToLayerProperty(entity, this, fromStorage); + instance.BaseValueChanged += InstanceOnBaseValueChanged; + } + + private void InstanceOnBaseValueChanged(object sender, EventArgs e) + { + OnLayerPropertyBaseValueChanged(new LayerPropertyEventArgs((BaseLayerProperty) sender)); } #region Events internal event EventHandler PropertyGroupUpdating; + + /// + /// Occurs when the property group has initialized all its children + /// public event EventHandler PropertyGroupInitialized; + /// + /// Occurs when one of the base value of one of the layer properties in this group changes + /// Note: Will not trigger on properties in child groups + /// + public event EventHandler LayerPropertyBaseValueChanged; + /// /// Occurs when the value of the layer property was updated /// @@ -275,6 +291,11 @@ namespace Artemis.Core.Models.Profile VisibilityChanged?.Invoke(this, EventArgs.Empty); } + protected virtual void OnLayerPropertyBaseValueChanged(LayerPropertyEventArgs e) + { + LayerPropertyBaseValueChanged?.Invoke(this, e); + } + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index dd8d7ba26..77d2b356d 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -25,7 +25,7 @@ namespace Artemis.Core.Models.Profile StartSegmentLength = RenderElementEntity.StartSegmentLength; MainSegmentLength = RenderElementEntity.MainSegmentLength; EndSegmentLength = RenderElementEntity.EndSegmentLength; - RepeatMainSegment = RenderElementEntity.RepeatMainSegment; + DisplayContinuously = RenderElementEntity.DisplayContinuously; AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline; } @@ -34,7 +34,7 @@ namespace Artemis.Core.Models.Profile RenderElementEntity.StartSegmentLength = StartSegmentLength; RenderElementEntity.MainSegmentLength = MainSegmentLength; RenderElementEntity.EndSegmentLength = EndSegmentLength; - RenderElementEntity.RepeatMainSegment = RepeatMainSegment; + RenderElementEntity.DisplayContinuously = DisplayContinuously; RenderElementEntity.AlwaysFinishTimeline = AlwaysFinishTimeline; RenderElementEntity.LayerEffects.Clear(); @@ -127,7 +127,7 @@ namespace Artemis.Core.Models.Profile private TimeSpan _startSegmentLength; private TimeSpan _mainSegmentLength; private TimeSpan _endSegmentLength; - private bool _repeatMainSegment; + private bool _displayContinuously; private bool _alwaysFinishTimeline; /// @@ -174,10 +174,10 @@ namespace Artemis.Core.Models.Profile /// /// Gets or sets whether main timeline should repeat itself as long as display conditions are met /// - public bool RepeatMainSegment + public bool DisplayContinuously { - get => _repeatMainSegment; - set => SetAndNotify(ref _repeatMainSegment, value); + get => _displayContinuously; + set => SetAndNotify(ref _displayContinuously, value); } /// @@ -201,7 +201,7 @@ namespace Artemis.Core.Models.Profile if (DisplayConditionMet) { // If we are at the end of the main timeline, wrap around back to the beginning - if (RepeatMainSegment && TimelinePosition >= mainSegmentEnd) + if (DisplayContinuously && TimelinePosition >= mainSegmentEnd) TimelinePosition = StartSegmentLength + (mainSegmentEnd - TimelinePosition); else if (TimelinePosition >= TimelineLength) TimelinePosition = TimelineLength; diff --git a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs index b8efbc81e..f76182c77 100644 --- a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs @@ -8,7 +8,7 @@ namespace Artemis.Storage.Entities.Profile.Abstract public TimeSpan StartSegmentLength { get; set; } public TimeSpan MainSegmentLength { get; set; } public TimeSpan EndSegmentLength { get; set; } - public bool RepeatMainSegment { get; set; } + public bool DisplayContinuously { get; set; } public bool AlwaysFinishTimeline { get; set; } public List LayerEffects { get; set; } diff --git a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs index e06761e8d..9a99933da 100644 --- a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs +++ b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs @@ -20,8 +20,10 @@ namespace Artemis.Storage.Migrations { if (folder.PropertyEntities.Any(p => p.KeyframeEntities.Any())) folder.MainSegmentLength = folder.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position)); - if (folder.MainSegmentLength == TimeSpan.Zero) + if (folder.MainSegmentLength == TimeSpan.Zero) folder.MainSegmentLength = TimeSpan.FromSeconds(5); + + folder.DisplayContinuously = true; } foreach (var layer in profileEntity.Layers.Where(l => l.MainSegmentLength == TimeSpan.Zero)) @@ -30,6 +32,8 @@ namespace Artemis.Storage.Migrations layer.MainSegmentLength = layer.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position)); if (layer.MainSegmentLength == TimeSpan.Zero) layer.MainSegmentLength = TimeSpan.FromSeconds(5); + + layer.DisplayContinuously = true; } repository.Update(profileEntity); diff --git a/src/Artemis.UI/Converters/NullToBooleanConverter.cs b/src/Artemis.UI/Converters/NullToBooleanConverter.cs new file mode 100644 index 000000000..fcdfd180c --- /dev/null +++ b/src/Artemis.UI/Converters/NullToBooleanConverter.cs @@ -0,0 +1,40 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Artemis.UI.Converters +{ + public class NullToBooleanConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + Parameters direction; + if (parameter == null) + direction = Parameters.Normal; + else + direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter); + + if (direction == Parameters.Normal) + { + if (value == null) + return false; + return true; + } + + if (value == null) + return true; + return false; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + private enum Parameters + { + Normal, + Inverted + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputView.xaml b/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputView.xaml index 7b55d7071..04e01fcdb 100644 --- a/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputView.xaml +++ b/src/Artemis.UI/DataModelVisualization/Input/DoubleDataModelInputView.xaml @@ -8,7 +8,7 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + diff --git a/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputView.xaml b/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputView.xaml index 856747483..fff568593 100644 --- a/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputView.xaml +++ b/src/Artemis.UI/DataModelVisualization/Input/IntDataModelInputView.xaml @@ -8,7 +8,7 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + diff --git a/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml b/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml index df27adb53..14d0bde05 100644 --- a/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml +++ b/src/Artemis.UI/DataModelVisualization/Input/StringDataModelInputView.xaml @@ -8,7 +8,7 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml index 5b8312291..6ebb2a67a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml @@ -38,7 +38,8 @@ Command="{s:Action Delete}"> - + + + + + + + + + + + + + Display conditions + + + + + + + + + + + + + + + + + + + + + + + + + + + Configure how the layer should act while the conditions above are met + + + + + + + + + + + + + + + + + Continue repeating the main segment of the timeline while the condition is met + + + + + + REPEAT + + + + + + + Only play the timeline once when the condition is met + + + + + + ONCE + + + + + + + + + + + + + Configure how the layer should act when the conditions above are no longer met + + + + + + + + + + + + + + + + When conditions are no longer met, finish the the current run of the main timeline + + + + + + FINISH + + + + + + + When conditions are no longer met, skip to the end segment of the timeline + + + + + + SKIP TO END + + + + + - - On end: - - - - - - - When conditions are no longer met, finish the timelines and then stop displaying. - - - - - - - - WAIT FOR FINISH - - - - - - - - - When conditions are no longer met, stop displaying immediately. - - - - - - - - SKIP - - - - - - + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 1ff24cd0a..b6cce8570 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.Conditions; using Artemis.Storage.Entities.Profile.Abstract; @@ -15,12 +16,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private DisplayConditionGroupViewModel _rootGroup; private RenderProfileElement _renderProfileElement; + private int _transitionerIndex; public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory) { _profileEditorService = profileEditorService; _displayConditionsVmFactory = displayConditionsVmFactory; - + } + + public int TransitionerIndex + { + get => _transitionerIndex; + set => SetAndNotify(ref _transitionerIndex, value); } public DisplayConditionGroupViewModel RootGroup @@ -69,6 +76,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions RootGroup = _displayConditionsVmFactory.DisplayConditionGroupViewModel(e.RenderProfileElement.DisplayConditionGroup, null); RootGroup.IsRootGroup = true; RootGroup.Update(); + + // Only show the intro to conditions once, and only if the layer has no conditions + if (TransitionerIndex != 1) + TransitionerIndex = RootGroup.Children.Any() ? 1 : 0; } protected override void OnActivate() diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs index dfaf3a250..cd6c58420 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs @@ -50,6 +50,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.LayerEffects LayerEffectDescriptors.AddRange(descriptors.Except(LayerEffectDescriptors)); LayerEffectDescriptors.RemoveRange(LayerEffectDescriptors.Except(descriptors)); + // Sort by display name + var index = 0; + foreach (var layerEffectDescriptor in LayerEffectDescriptors.OrderBy(d => d.DisplayName).ToList()) + { + if (LayerEffectDescriptors.IndexOf(layerEffectDescriptor) != index) + LayerEffectDescriptors.Move(LayerEffectDescriptors.IndexOf(layerEffectDescriptor), index); + index++; + } + SelectedLayerEffectDescriptor = null; NotifyOfPropertyChange(nameof(HasLayerEffectDescriptors)); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index f4b699c5d..4cc9827d4 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -18,6 +18,7 @@ behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True"> +