diff --git a/src/Artemis.Core/Models/BreakableModel.cs b/src/Artemis.Core/Models/BreakableModel.cs
new file mode 100644
index 000000000..97bd761cb
--- /dev/null
+++ b/src/Artemis.Core/Models/BreakableModel.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+
+namespace Artemis.Core
+{
+ ///
+ /// Provides a default implementation for models that can have a broken state
+ ///
+ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
+ {
+ private string? _brokenState;
+ private Exception? _brokenStateException;
+
+ ///
+ /// Invokes the event
+ ///
+ protected virtual void OnBrokenStateChanged()
+ {
+ BrokenStateChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ public abstract string BrokenDisplayName { get; }
+
+ ///
+ /// Gets or sets the broken state of this breakable model, if this model is not broken.
+ /// Note: If setting this manually you are also responsible for invoking
+ ///
+ public string? BrokenState
+ {
+ get => _brokenState;
+ set => SetAndNotify(ref _brokenState, value);
+ }
+
+ ///
+ /// Gets or sets the exception that caused the broken state
+ /// Note: If setting this manually you are also responsible for invoking
+ ///
+ public Exception? BrokenStateException
+ {
+ get => _brokenStateException;
+ set => SetAndNotify(ref _brokenStateException, value);
+ }
+
+ ///
+ public bool TryOrBreak(Action action, string breakMessage)
+ {
+ try
+ {
+ action();
+ ClearBrokenState(breakMessage);
+ return true;
+ }
+ catch (Exception e)
+ {
+ SetBrokenState(breakMessage, e);
+ return false;
+ }
+ }
+
+ ///
+ public void SetBrokenState(string state, Exception? exception)
+ {
+ BrokenState = state ?? throw new ArgumentNullException(nameof(state));
+ BrokenStateException = exception;
+ OnBrokenStateChanged();
+ }
+
+ ///
+ public void ClearBrokenState(string state)
+ {
+ if (state == null) throw new ArgumentNullException(nameof(state));
+ if (BrokenState == null)
+ return;
+
+ if (BrokenState != state) return;
+ BrokenState = null;
+ BrokenStateException = null;
+ OnBrokenStateChanged();
+ }
+
+ ///
+ public virtual IEnumerable GetBrokenHierarchy()
+ {
+ if (BrokenState != null)
+ yield return this;
+ }
+
+ ///
+ public event EventHandler? BrokenStateChanged;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/IBreakableModel.cs b/src/Artemis.Core/Models/IBreakableModel.cs
new file mode 100644
index 000000000..88a5762d6
--- /dev/null
+++ b/src/Artemis.Core/Models/IBreakableModel.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a model that can have a broken state
+ ///
+ public interface IBreakableModel
+ {
+ ///
+ /// Gets the display name of this breakable model
+ ///
+ string BrokenDisplayName { get; }
+
+ ///
+ /// Gets or sets the broken state of this breakable model, if this model is not broken.
+ ///
+ string? BrokenState { get; set; }
+
+ ///
+ /// Gets or sets the exception that caused the broken state
+ ///
+ Exception? BrokenStateException { get; set; }
+
+ ///
+ /// Try to execute the provided action. If the action succeeded the broken state is cleared if it matches
+ /// , if the action throws an exception and
+ /// are set accordingly.
+ ///
+ /// The action to attempt to execute
+ /// The message to clear on succeed or set on failure (exception)
+ /// if the action succeeded; otherwise .
+ bool TryOrBreak(Action action, string breakMessage);
+
+ ///
+ /// Sets the broken state to the provided state and optional exception.
+ ///
+ /// The state to set the broken state to
+ /// The exception that caused the broken state
+ public void SetBrokenState(string state, Exception? exception);
+
+ ///
+ /// Clears the broken state and exception if equals .
+ ///
+ ///
+ public void ClearBrokenState(string state);
+
+ ///
+ /// Returns a list containing all broken models, including self and any children
+ ///
+ IEnumerable GetBrokenHierarchy();
+
+ ///
+ /// Occurs when the broken state of this model changes
+ ///
+ event EventHandler BrokenStateChanged;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index 81ec38993..c28e9ea00 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -183,18 +183,15 @@ namespace Artemis.Core
lock (Timeline)
{
- foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
- {
- baseLayerEffect.BaseProperties?.Update(Timeline);
- baseLayerEffect.Update(Timeline.Delta.TotalSeconds);
- }
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
+ baseLayerEffect.InternalUpdate(Timeline);
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try
{
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
- baseLayerEffect.PreProcess(canvas, rendererBounds, layerPaint);
+ baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
// No point rendering if the alpha was set to zero by one of the effects
if (layerPaint.Color.Alpha == 0)
@@ -208,7 +205,7 @@ namespace Artemis.Core
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top));
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
- baseLayerEffect.PostProcess(canvas, rendererBounds, layerPaint);
+ baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
}
finally
{
@@ -228,8 +225,6 @@ namespace Artemis.Core
if (Enabled)
return;
- Debug.WriteLine($"Enabling {this}");
-
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalEnable();
@@ -248,8 +243,6 @@ namespace Artemis.Core
if (!Enabled)
return;
- Debug.WriteLine($"Disabled {this}");
-
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
@@ -344,5 +337,15 @@ namespace Artemis.Core
{
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
}
+
+ #region Overrides of BreakableModel
+
+ ///
+ public override IEnumerable GetBrokenHierarchy()
+ {
+ return LayerEffects.Where(e => e.BrokenState != null);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index f73fb5879..8b6cde56e 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -206,6 +206,7 @@ namespace Artemis.Core
GetType().GetProperty(nameof(Transform))!,
typeof(PropertyGroupDescriptionAttribute)
)!;
+
General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute;
General.Initialize(this, "General.", Constants.CorePluginFeature);
Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute;
@@ -288,7 +289,7 @@ namespace Artemis.Core
// Adaption hints
Adapter.Save();
-
+
SaveRenderElement();
}
@@ -320,7 +321,7 @@ namespace Artemis.Core
{
if (Disposed)
throw new ObjectDisposedException("Layer");
-
+
UpdateDisplayCondition();
UpdateTimeline(deltaTime);
@@ -366,9 +367,17 @@ namespace Artemis.Core
if (Enabled)
return;
- LayerBrush?.InternalEnable();
- foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
- baseLayerEffect.InternalEnable();
+ bool tryOrBreak = TryOrBreak(() => LayerBrush?.InternalEnable(), "Failed to enable layer brush");
+ if (!tryOrBreak)
+ return;
+
+ tryOrBreak = TryOrBreak(() =>
+ {
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
+ baseLayerEffect.InternalEnable();
+ }, "Failed to enable one or more effects");
+ if (!tryOrBreak)
+ return;
Enabled = true;
}
@@ -393,17 +402,10 @@ namespace Artemis.Core
General.Update(timeline);
Transform.Update(timeline);
- if (LayerBrush != null)
- {
- LayerBrush.BaseProperties?.Update(timeline);
- LayerBrush.Update(timeline.Delta.TotalSeconds);
- }
+ LayerBrush?.InternalUpdate(timeline);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
- {
- baseLayerEffect.BaseProperties?.Update(timeline);
- baseLayerEffect.Update(timeline.Delta.TotalSeconds);
- }
+ baseLayerEffect.InternalUpdate(timeline);
}
private void RenderTimeline(Timeline timeline, SKCanvas canvas, SKPointI basePosition)
@@ -477,9 +479,9 @@ namespace Artemis.Core
{
if (LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering");
-
+
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
- baseLayerEffect.PreProcess(canvas, bounds, layerPaint);
+ baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint);
try
{
@@ -492,7 +494,7 @@ namespace Artemis.Core
LayerBrush.InternalRender(canvas, bounds, layerPaint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
- baseLayerEffect.PostProcess(canvas, bounds, layerPaint);
+ baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint);
}
finally
@@ -722,16 +724,24 @@ namespace Artemis.Core
internal void ActivateLayerBrush()
{
- LayerBrushReference? current = General.BrushReference.CurrentValue;
- if (current == null)
- return;
+ try
+ {
+ LayerBrushReference? current = General.BrushReference.CurrentValue;
+ if (current == null)
+ return;
- LayerBrushDescriptor? descriptor = current.LayerBrushProviderId != null && current.BrushType != null
- ? LayerBrushStore.Get(current.LayerBrushProviderId, current.BrushType)?.LayerBrushDescriptor
- : null;
- descriptor?.CreateInstance(this);
+ LayerBrushDescriptor? descriptor = current.LayerBrushProviderId != null && current.BrushType != null
+ ? LayerBrushStore.Get(current.LayerBrushProviderId, current.BrushType)?.LayerBrushDescriptor
+ : null;
+ descriptor?.CreateInstance(this);
- OnLayerBrushUpdated();
+ OnLayerBrushUpdated();
+ ClearBrokenState("Failed to initialize layer brush");
+ }
+ catch (Exception e)
+ {
+ SetBrokenState("Failed to initialize layer brush", e);
+ }
}
internal void DeactivateLayerBrush()
@@ -747,6 +757,19 @@ namespace Artemis.Core
}
#endregion
+
+ #region Overrides of BreakableModel
+
+ ///
+ public override IEnumerable GetBrokenHierarchy()
+ {
+ if (LayerBrush?.BrokenState != null)
+ yield return LayerBrush;
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.BrokenState != null))
+ yield return baseLayerEffect;
+ }
+
+ #endregion
}
///
diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs
index a018b947a..c0c3a2528 100644
--- a/src/Artemis.Core/Models/Profile/Profile.cs
+++ b/src/Artemis.Core/Models/Profile/Profile.cs
@@ -27,6 +27,7 @@ namespace Artemis.Core
UndoStack = new MaxStack(20);
RedoStack = new MaxStack(20);
+ Exceptions = new List();
Load();
}
@@ -77,6 +78,7 @@ namespace Artemis.Core
internal MaxStack UndoStack { get; set; }
internal MaxStack RedoStack { get; set; }
+ internal List Exceptions { get; }
///
public override void Update(double deltaTime)
@@ -113,6 +115,13 @@ namespace Artemis.Core
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);
+
+ if (!Exceptions.Any())
+ return;
+
+ List exceptions = new(Exceptions);
+ Exceptions.Clear();
+ throw new AggregateException($"One or more exceptions while rendering profile {Name}", exceptions);
}
}
@@ -238,5 +247,18 @@ namespace Artemis.Core
ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
}
}
+
+ #region Overrides of BreakableModel
+
+ ///
+ public override IEnumerable GetBrokenHierarchy()
+ {
+ foreach (IBreakableModel breakableModel in GetAllFolders().SelectMany(folders => folders.GetBrokenHierarchy()))
+ yield return breakableModel;
+ foreach (IBreakableModel breakableModel in GetAllLayers().SelectMany(layer => layer.GetBrokenHierarchy()))
+ yield return breakableModel;
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs
index 58f005f94..81537b0ba 100644
--- a/src/Artemis.Core/Models/Profile/ProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs
@@ -9,7 +9,7 @@ namespace Artemis.Core
///
/// Represents an element of a
///
- public abstract class ProfileElement : CorePropertyChanged, IDisposable
+ public abstract class ProfileElement : BreakableModel, IDisposable
{
private Guid _entityId;
private string? _name;
@@ -25,7 +25,7 @@ namespace Artemis.Core
_profile = profile;
ChildrenList = new List();
}
-
+
///
/// Gets the unique ID of this profile element
///
@@ -121,6 +121,13 @@ namespace Artemis.Core
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
}
+ #region Overrides of BreakableModel
+
+ ///
+ public override string BrokenDisplayName => Name ?? GetType().Name;
+
+ #endregion
+
#region Hierarchy
///
@@ -234,7 +241,7 @@ namespace Artemis.Core
internal abstract void Save();
#endregion
-
+
#region IDisposable
///
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
index 0154529f6..40a37b09d 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
@@ -8,7 +8,7 @@ namespace Artemis.Core.LayerBrushes
///
/// For internal use only, please use or or instead
///
- public abstract class BaseLayerBrush : CorePropertyChanged, IDisposable
+ public abstract class BaseLayerBrush : BreakableModel, IDisposable
{
private LayerBrushType _brushType;
private ILayerBrushConfigurationDialog? _configurationDialog;
@@ -134,6 +134,12 @@ namespace Artemis.Core.LayerBrushes
}
}
+ internal void InternalUpdate(Timeline timeline)
+ {
+ BaseProperties?.Update(timeline);
+ TryOrBreak(() => Update(timeline.Delta.TotalSeconds), "Failed to update");
+ }
+
///
/// Enables the layer brush if it isn't already enabled
///
@@ -142,7 +148,9 @@ namespace Artemis.Core.LayerBrushes
if (Enabled)
return;
- EnableLayerBrush();
+ if (!TryOrBreak(EnableLayerBrush, "Failed to enable"))
+ return;
+
Enabled = true;
}
@@ -170,6 +178,13 @@ namespace Artemis.Core.LayerBrushes
Dispose(true);
GC.SuppressFinalize(this);
}
+
+ #region Overrides of BreakableModel
+
+ ///
+ public override string BrokenDisplayName => Descriptor.DisplayName;
+
+ #endregion
}
///
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs
index 94b8623a4..9e69380d0 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs
@@ -28,12 +28,12 @@ namespace Artemis.Core.LayerBrushes
internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint)
{
- Render(canvas, bounds, paint);
+ TryOrBreak(() => Render(canvas, bounds, paint), "Failed to render");
}
internal override void Initialize()
{
- InitializeProperties();
+ TryOrBreak(InitializeProperties, "Failed to initialize");
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs
index bb48793e6..1f1948808 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs
@@ -31,7 +31,7 @@ namespace Artemis.Core.LayerBrushes
internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint)
{
// We don't want rotation on this canvas because that'll displace the LEDs, translations are applied to the points of each LED instead
- if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation)
+ if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation)
canvas.SetMatrix(canvas.TotalMatrix.PreConcat(Layer.GetTransformMatrix(true, false, false, true).Invert()));
using SKPath pointsPath = new();
@@ -46,34 +46,38 @@ namespace Artemis.Core.LayerBrushes
}
// Apply the translation to the points of each LED instead
- if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation)
+ if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation)
pointsPath.Transform(Layer.GetTransformMatrix(true, true, true, true).Invert());
SKPoint[] points = pointsPath.Points;
- for (int index = 0; index < Layer.Leds.Count; index++)
+
+ TryOrBreak(() =>
{
- ArtemisLed artemisLed = Layer.Leds[index];
- SKPoint renderPoint = points[index * 2 + 1];
- if (!float.IsFinite(renderPoint.X) || !float.IsFinite(renderPoint.Y))
- continue;
+ for (int index = 0; index < Layer.Leds.Count; index++)
+ {
+ ArtemisLed artemisLed = Layer.Leds[index];
+ SKPoint renderPoint = points[index * 2 + 1];
+ if (!float.IsFinite(renderPoint.X) || !float.IsFinite(renderPoint.Y))
+ continue;
- // Let the brush determine the color
- ledPaint.Color = GetColor(artemisLed, renderPoint);
+ // Let the brush determine the color
+ ledPaint.Color = GetColor(artemisLed, renderPoint);
- SKRect ledRectangle = SKRect.Create(
- artemisLed.AbsoluteRectangle.Left - Layer.Bounds.Left,
- artemisLed.AbsoluteRectangle.Top - Layer.Bounds.Top,
- artemisLed.AbsoluteRectangle.Width,
- artemisLed.AbsoluteRectangle.Height
- );
+ SKRect ledRectangle = SKRect.Create(
+ artemisLed.AbsoluteRectangle.Left - Layer.Bounds.Left,
+ artemisLed.AbsoluteRectangle.Top - Layer.Bounds.Top,
+ artemisLed.AbsoluteRectangle.Width,
+ artemisLed.AbsoluteRectangle.Height
+ );
- canvas.DrawRect(ledRectangle, ledPaint);
- }
+ canvas.DrawRect(ledRectangle, ledPaint);
+ }
+ }, "Failed to render");
}
internal override void Initialize()
{
- InitializeProperties();
+ TryOrBreak(InitializeProperties, "Failed to initialize");
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
index ac9f5d27f..ce32edfd0 100644
--- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
+++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
@@ -1,5 +1,4 @@
using System;
-using Artemis.Core.Services;
using SkiaSharp;
namespace Artemis.Core.LayerEffects
@@ -7,7 +6,7 @@ namespace Artemis.Core.LayerEffects
///
/// For internal use only, please use instead
///
- public abstract class BaseLayerEffect : CorePropertyChanged, IDisposable
+ public abstract class BaseLayerEffect : BreakableModel, IDisposable
{
private ILayerEffectConfigurationDialog? _configurationDialog;
private LayerEffectDescriptor _descriptor;
@@ -182,6 +181,22 @@ namespace Artemis.Core.LayerEffects
internal virtual string GetEffectTypeName() => GetType().Name;
+ internal void InternalUpdate(Timeline timeline)
+ {
+ BaseProperties?.Update(timeline);
+ TryOrBreak(() => Update(timeline.Delta.TotalSeconds), "Failed to update");
+ }
+
+ internal void InternalPreProcess(SKCanvas canvas, SKRect renderBounds, SKPaint paint)
+ {
+ TryOrBreak(() => PreProcess(canvas, renderBounds, paint), "Failed to pre-process");
+ }
+
+ internal void InternalPostProcess(SKCanvas canvas, SKRect renderBounds, SKPaint paint)
+ {
+ TryOrBreak(() => PostProcess(canvas, renderBounds, paint), "Failed to post-process");
+ }
+
///
/// Enables the layer effect if it isn't already enabled
///
@@ -205,5 +220,12 @@ namespace Artemis.Core.LayerEffects
DisableLayerEffect();
Enabled = false;
}
+
+ #region Overrides of BreakableModel
+
+ ///
+ public override string BrokenDisplayName => Name;
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionView.xaml b/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionView.xaml
index 3019c944b..e729a5959 100644
--- a/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionView.xaml
+++ b/src/Artemis.UI.Shared/Screens/Exceptions/ExceptionView.xaml
@@ -10,7 +10,7 @@
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
- Title="Unhandled exception"
+ Title="Exception | Artemis"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
index 6fc38bf39..2b748fc01 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
@@ -501,11 +501,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
public void GoToEnd()
{
+ if (SelectedProfileElement == null)
+ return;
+
ProfileEditorService.CurrentTime = SelectedProfileElement.Timeline.EndSegmentEndPosition;
}
public void GoToPreviousFrame()
{
+ if (SelectedProfileElement == null)
+ return;
+
double frameTime = 1000.0 / SettingsService.GetSetting("Core.TargetFrameRate", 25).Value;
double newTime = Math.Max(0, Math.Round((ProfileEditorService.CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
ProfileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
@@ -513,6 +519,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
public void GoToNextFrame()
{
+ if (SelectedProfileElement == null)
+ return;
+
double frameTime = 1000.0 / SettingsService.GetSetting("Core.TargetFrameRate", 25).Value;
double newTime = Math.Round((ProfileEditorService.CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
newTime = Math.Min(newTime, SelectedProfileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds);
diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs
index d0659951f..7a4a0d773 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using System.Timers;
using System.Windows;
using System.Windows.Input;
using Artemis.Core;
@@ -20,11 +21,13 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
private bool _draggingTreeView;
private TreeItemViewModel _selectedTreeItem;
private bool _updatingTree;
+ private readonly Timer _timer;
public ProfileTreeViewModel(IProfileEditorService profileEditorService, IProfileTreeVmFactory profileTreeVmFactory)
{
_profileEditorService = profileEditorService;
_profileTreeVmFactory = profileTreeVmFactory;
+ _timer = new Timer(500);
}
public TreeItemViewModel SelectedTreeItem
@@ -54,12 +57,15 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{
Subscribe();
CreateRootFolderViewModel();
+ _timer.Start();
base.OnInitialActivate();
}
protected override void OnClose()
{
Unsubscribe();
+ _timer.Stop();
+ _timer.Dispose();
base.OnClose();
}
@@ -203,12 +209,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{
_profileEditorService.SelectedProfileChanged += OnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged += OnSelectedProfileElementChanged;
+ _timer.Elapsed += TimerOnElapsed;
}
private void Unsubscribe()
{
_profileEditorService.SelectedProfileChanged -= OnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged -= OnSelectedProfileElementChanged;
+ _timer.Elapsed -= TimerOnElapsed;
}
private void OnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e)
@@ -242,6 +250,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
CreateRootFolderViewModel();
}
+ private void TimerOnElapsed(object sender, ElapsedEventArgs e)
+ {
+ ActiveItem?.UpdateBrokenState();
+ }
+
#endregion
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml
index b1a9f2d85..eaba09b42 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml
@@ -1,17 +1,20 @@
+ 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:s="https://github.com/canton7/Stylet"
+ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+ xmlns:treeItem1="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem"
+ xmlns:converters="clr-namespace:Artemis.UI.Converters"
+ xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
+ x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem.FolderView"
+ mc:Ignorable="d"
+ d:DesignHeight="450" d:DesignWidth="800"
+ d:DataContext="{d:DesignInstance {x:Type treeItem1:FolderViewModel}}">
-
+
+
@@ -58,18 +61,32 @@
+
-
+
+
+
-
-
-
+ ((Folder) ProfileElement).IsExpanded = value;
}
+ public override void UpdateBrokenState()
+ {
+ List brokenModels = ProfileElement.GetBrokenHierarchy().ToList();
+ if (!brokenModels.Any())
+ BrokenState = null;
+ else
+ {
+ BrokenState = "Folder is in a broken state, click to view exception(s).\r\n" +
+ $"{string.Join("\r\n", brokenModels.Select(e => $" • {e.BrokenDisplayName} - {e.BrokenState}"))}";
+ }
+
+ foreach (TreeItemViewModel treeItemViewModel in GetChildren())
+ treeItemViewModel.UpdateBrokenState();
+ }
+
private void ProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Folder.IsExpanded))
NotifyOfPropertyChange(nameof(IsExpanded));
}
-
+
+
#region Overrides of Screen
///
diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml
index aa89223e3..0e1c22bfb 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml
@@ -6,14 +6,15 @@
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:treeItem1="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem"
- xmlns:Converters="clr-namespace:Artemis.UI.Converters"
+ xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem.LayerView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type treeItem1:LayerViewModel}}">
-
+
+
@@ -55,13 +56,27 @@
+
-
-
+
+
+
+
-
-
+ ProfileElement as Layer;
public bool ShowIcons => Layer?.LayerBrush != null;
public override bool SupportsChildren => false;
public override bool IsExpanded { get; set; }
+
+ public void OpenAdaptionHints()
+ {
+ _windowManager.ShowDialog(_vmFactory.LayerHintsDialogViewModel(Layer));
+ }
+
+ public override void UpdateBrokenState()
+ {
+ List brokenModels = ProfileElement.GetBrokenHierarchy().ToList();
+ if (!brokenModels.Any())
+ BrokenState = null;
+ else
+ {
+ BrokenState = "Layer is in a broken state, click to view exception(s).\r\n" +
+ $"{string.Join("\r\n", brokenModels.Select(e => $" • {e.BrokenDisplayName} - {e.BrokenState}"))}";
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs
index 4c1cba42b..58d6560fd 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs
@@ -23,6 +23,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
private readonly IProfileTreeVmFactory _profileTreeVmFactory;
private readonly IRgbService _rgbService;
private ProfileElement _profileElement;
+ private string _brokenState;
protected TreeItemViewModel(ProfileElement profileElement,
IRgbService rgbService,
@@ -49,6 +50,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
set => SetAndNotify(ref _profileElement, value);
}
+ public string BrokenState
+ {
+ get => _brokenState;
+ set => SetAndNotify(ref _brokenState, value);
+ }
+
public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement();
public abstract bool SupportsChildren { get; }
@@ -286,6 +293,23 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
NotifyOfPropertyChange(nameof(CanPasteElement));
}
+ public abstract void UpdateBrokenState();
+
+ public async Task ShowBrokenStateExceptions()
+ {
+ List broken = ProfileElement.GetBrokenHierarchy().Where(b => b.BrokenStateException != null).ToList();
+
+ foreach (IBreakableModel current in broken)
+ {
+ _dialogService.ShowExceptionDialog($"{current.BrokenDisplayName} - {current.BrokenState}", current.BrokenStateException!);
+ if (broken.Last() != current)
+ {
+ if (!await _dialogService.ShowConfirmDialog("Broken state", "Do you want to view the next exception?"))
+ return;
+ }
+ }
+ }
+
private void Subscribe()
{
ProfileElement.ChildAdded += ProfileElementOnChildAdded;