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

Merge pull request #779 from Artemis-RGB/bugfix/layer-brush-null

Layers - Prevent brushes from going null when provider is missing
This commit is contained in:
RobertBeekman 2023-04-14 13:52:35 +02:00 committed by GitHub
commit bee96166dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 49 deletions

View File

@ -59,7 +59,7 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
} }
/// <inheritdoc /> /// <inheritdoc />
public void SetBrokenState(string state, Exception? exception) public void SetBrokenState(string state, Exception? exception = null)
{ {
BrokenState = state ?? throw new ArgumentNullException(nameof(state)); BrokenState = state ?? throw new ArgumentNullException(nameof(state));
BrokenStateException = exception; BrokenStateException = exception;

View File

@ -16,6 +16,9 @@ namespace Artemis.Core;
/// </summary> /// </summary>
public sealed class Layer : RenderProfileElement public sealed class Layer : RenderProfileElement
{ {
private const string BROKEN_STATE_BRUSH_NOT_FOUND = "Failed to load layer brush, ensure the plugin is enabled";
private const string BROKEN_STATE_INIT_FAILED = "Failed to initialize layer brush";
private readonly List<Layer> _renderCopies = new(); private readonly List<Layer> _renderCopies = new();
private LayerGeneralProperties _general = new(); private LayerGeneralProperties _general = new();
private LayerTransformProperties _transform = new(); private LayerTransformProperties _transform = new();
@ -261,7 +264,10 @@ public sealed class Layer : RenderProfileElement
private void LayerBrushStoreOnLayerBrushRemoved(object? sender, LayerBrushStoreEvent e) private void LayerBrushStoreOnLayerBrushRemoved(object? sender, LayerBrushStoreEvent e)
{ {
if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor) if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor)
{
DeactivateLayerBrush(); DeactivateLayerBrush();
SetBrokenState(BROKEN_STATE_BRUSH_NOT_FOUND);
}
} }
private void LayerBrushStoreOnLayerBrushAdded(object? sender, LayerBrushStoreEvent e) private void LayerBrushStoreOnLayerBrushAdded(object? sender, LayerBrushStoreEvent e)
@ -807,33 +813,46 @@ public sealed class Layer : RenderProfileElement
/// <summary> /// <summary>
/// Changes the current layer brush to the provided layer brush and activates it /// Changes the current layer brush to the provided layer brush and activates it
/// </summary> /// </summary>
public void ChangeLayerBrush(BaseLayerBrush? layerBrush) public void ChangeLayerBrush(BaseLayerBrush layerBrush)
{ {
BaseLayerBrush? oldLayerBrush = LayerBrush; if (layerBrush == null)
throw new ArgumentNullException(nameof(layerBrush));
General.BrushReference.SetCurrentValue(layerBrush != null ? new LayerBrushReference(layerBrush.Descriptor) : null); BaseLayerBrush? oldLayerBrush = LayerBrush;
General.BrushReference.SetCurrentValue(new LayerBrushReference(layerBrush.Descriptor));
LayerBrush = layerBrush; LayerBrush = layerBrush;
// Don't dispose the brush, only disable it
// That way an undo-action does not need to worry about disposed brushes
oldLayerBrush?.InternalDisable(); oldLayerBrush?.InternalDisable();
ActivateLayerBrush();
if (LayerBrush != null)
ActivateLayerBrush();
else
OnLayerBrushUpdated();
} }
internal void ActivateLayerBrush() private void ActivateLayerBrush()
{ {
try try
{ {
// If the brush is null, try to instantiate it
if (LayerBrush == null) if (LayerBrush == null)
{ {
// If the brush is null, try to instantiate it // Ensure the provider is loaded and still provides the expected brush
LayerBrushReference? brushReference = General.BrushReference.CurrentValue; LayerBrushReference? brushReference = General.BrushReference.CurrentValue;
if (brushReference?.LayerBrushProviderId != null && brushReference.BrushType != null) if (brushReference?.LayerBrushProviderId != null && brushReference.BrushType != null)
ChangeLayerBrush(LayerBrushStore.Get(brushReference.LayerBrushProviderId, brushReference.BrushType)?.LayerBrushDescriptor.CreateInstance(this, LayerEntity.LayerBrush)); {
// If that's not possible there's nothing to do // Only apply the brush if it is successfully retrieved from the store and instantiated
return; BaseLayerBrush? layerBrush = LayerBrushStore.Get(brushReference.LayerBrushProviderId, brushReference.BrushType)?.LayerBrushDescriptor.CreateInstance(this, LayerEntity.LayerBrush);
if (layerBrush != null)
{
ClearBrokenState(BROKEN_STATE_BRUSH_NOT_FOUND);
ChangeLayerBrush(layerBrush);
}
// If that's not possible there's nothing to do
else
{
SetBrokenState(BROKEN_STATE_BRUSH_NOT_FOUND);
return;
}
}
} }
General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
@ -846,15 +865,15 @@ public sealed class Layer : RenderProfileElement
} }
OnLayerBrushUpdated(); OnLayerBrushUpdated();
ClearBrokenState("Failed to initialize layer brush"); ClearBrokenState(BROKEN_STATE_INIT_FAILED);
} }
catch (Exception e) catch (Exception e)
{ {
SetBrokenState("Failed to initialize layer brush", e); SetBrokenState(BROKEN_STATE_INIT_FAILED, e);
} }
} }
internal void DeactivateLayerBrush() private void DeactivateLayerBrush()
{ {
if (LayerBrush == null) if (LayerBrush == null)
return; return;

View File

@ -54,7 +54,8 @@ public class ChangeLayerBrush : IProfileEditorCommand, IDisposable
/// <inheritdoc /> /// <inheritdoc />
public void Undo() public void Undo()
{ {
_layer.ChangeLayerBrush(_previousBrush); if (_previousBrush != null)
_layer.ChangeLayerBrush(_previousBrush);
_executed = false; _executed = false;
} }

View File

@ -21,6 +21,7 @@
<!-- Custom styles --> <!-- Custom styles -->
<StyleInclude Source="/Styles/Border.axaml" /> <StyleInclude Source="/Styles/Border.axaml" />
<StyleInclude Source="/Styles/BrokenState.axaml" />
<StyleInclude Source="/Styles/Skeleton.axaml" /> <StyleInclude Source="/Styles/Skeleton.axaml" />
<StyleInclude Source="/Styles/Button.axaml" /> <StyleInclude Source="/Styles/Button.axaml" />
<StyleInclude Source="/Styles/Condensed.axaml" /> <StyleInclude Source="/Styles/Condensed.axaml" />

View File

@ -0,0 +1,20 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
<Design.PreviewWith>
<controls:HyperlinkButton Grid.Column="0" Classes="icon-button icon-button-small broken-state-button" Margin="50">
<avalonia:MaterialIcon Kind="AlertCircle" />
</controls:HyperlinkButton>
</Design.PreviewWith>
<!-- Add Styles Here -->
<Style Selector="controls|HyperlinkButton.broken-state-button avalonia|MaterialIcon">
<Setter Property="Foreground" Value="#E74C4C" />
</Style>
<Style Selector="controls|HyperlinkButton.broken-state-button:pointerover avalonia|MaterialIcon">
<Setter Property="Foreground" Value="#B93F3F" />
</Style>
</Styles>

View File

@ -4,21 +4,19 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:profileTree="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree" xmlns:profileTree="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.FolderTreeItemView" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.FolderTreeItemView"
x:DataType="profileTree:FolderTreeItemViewModel"> x:DataType="profileTree:FolderTreeItemViewModel">
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto">
<Button Grid.Column="0" <controls:HyperlinkButton Grid.Column="0"
ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState}" Classes="icon-button icon-button-small broken-state-button"
IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}" Margin="0 0 5 0"
Classes="icon-button icon-button-small" Command="{CompiledBinding ShowBrokenStateExceptions}"
Foreground="White" IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
Background="#E74C4C" ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}">
BorderBrush="#E74C4C"
Margin="0 0 5 0"
Command="{Binding ShowBrokenStateExceptions}">
<avalonia:MaterialIcon Kind="AlertCircle" /> <avalonia:MaterialIcon Kind="AlertCircle" />
</Button> </controls:HyperlinkButton>
<avalonia:MaterialIcon Grid.Column="1" <avalonia:MaterialIcon Grid.Column="1"
Kind="Folder" Kind="Folder"
Margin="0 0 5 0" Margin="0 0 5 0"

View File

@ -4,19 +4,19 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:profileTree="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree" xmlns:profileTree="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.LayerTreeItemView" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.LayerTreeItemView"
x:DataType="profileTree:LayerTreeItemViewModel"> x:DataType="profileTree:LayerTreeItemViewModel">
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto">
<Button Grid.Column="0" <controls:HyperlinkButton Grid.Column="0"
ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState}" Classes="icon-button icon-button-small broken-state-button"
IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}" Margin="0 0 5 0"
Classes="icon-button icon-button-small" Command="{CompiledBinding ShowBrokenStateExceptions}"
Foreground="#E74C4C" IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="0 0 5 0" ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}">
Command="{Binding ShowBrokenStateExceptions}">
<avalonia:MaterialIcon Kind="AlertCircle" /> <avalonia:MaterialIcon Kind="AlertCircle" />
</Button> </controls:HyperlinkButton>
<avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" /> <avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" />
<TextBox Grid.Column="2" <TextBox Grid.Column="2"
Margin="-5 0 0 0" Margin="-5 0 0 0"

View File

@ -39,21 +39,16 @@
Background="{DynamicResource ContentDialogBackground}"> Background="{DynamicResource ContentDialogBackground}">
<Border Background="{DynamicResource TaskDialogHeaderBackground}"> <Border Background="{DynamicResource TaskDialogHeaderBackground}">
<Grid Classes="node-header" VerticalAlignment="Top" ColumnDefinitions="Auto,*,Auto,Auto"> <Grid Classes="node-header" VerticalAlignment="Top" ColumnDefinitions="Auto,*,Auto,Auto">
<Button Grid.Column="0" <controls:HyperlinkButton Grid.Column="0"
VerticalAlignment="Center" Classes="icon-button icon-button-small broken-state-button"
IsVisible="{CompiledBinding Node.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}" Margin="5 0 0 0"
Classes="icon-button icon-button-small" Command="{CompiledBinding ShowBrokenState}"
Foreground="White" IsVisible="{CompiledBinding Node.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
Background="#E74C4C" ToolTip.Tip="{CompiledBinding Node.BrokenState}">
BorderBrush="#E74C4C" <avalonia:MaterialIcon Kind="AlertCircle" />
Margin="5 0 0 0" </controls:HyperlinkButton>
ToolTip.Tip="{CompiledBinding Node.BrokenState}"
Command="{CompiledBinding ShowBrokenState}">
<avalonia:MaterialIcon Kind="AlertCircle"></avalonia:MaterialIcon>
</Button>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}" /> <TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}" />
<controls:HyperlinkButton Grid.Column="2" <controls:HyperlinkButton Grid.Column="2"
IsVisible="{CompiledBinding Node.HelpUrl, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Node.HelpUrl, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"