-
-
Notifications
You must be signed in to change notification settings - Fork 66
/
LayerPropertyGroup.cs
356 lines (295 loc) · 14 KB
/
LayerPropertyGroup.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Artemis.Storage.Entities.Profile;
using Humanizer;
namespace Artemis.Core;
/// <summary>
/// Represents a property group on a layer
/// <para>
/// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will
/// initialize these for you.
/// </para>
/// </summary>
public abstract class LayerPropertyGroup : IDisposable, IPluginFeatureDependent
{
private readonly List<ILayerProperty> _layerProperties;
private readonly List<LayerPropertyGroup> _layerPropertyGroups;
private bool _disposed;
private bool _isHidden;
/// <summary>
/// A base constructor for a <see cref="LayerPropertyGroup" />
/// </summary>
protected LayerPropertyGroup()
{
// These are set right after construction to keep the constructor (and inherited constructs) clean
ProfileElement = null!;
GroupDescription = null!;
Path = "";
_layerProperties = new List<ILayerProperty>();
_layerPropertyGroups = new List<LayerPropertyGroup>();
LayerProperties = new ReadOnlyCollection<ILayerProperty>(_layerProperties);
LayerPropertyGroups = new ReadOnlyCollection<LayerPropertyGroup>(_layerPropertyGroups);
}
/// <summary>
/// Gets the profile element (such as layer or folder) this group is associated with
/// </summary>
public RenderProfileElement ProfileElement { get; private set; }
/// <summary>
/// Gets the description of this group
/// </summary>
public PropertyGroupDescriptionAttribute GroupDescription { get; private set; }
/// <summary>
/// The parent group of this group
/// </summary>
[LayerPropertyIgnore] // Ignore the parent when selecting child groups
public LayerPropertyGroup? Parent { get; internal set; }
/// <summary>
/// Gets the unique path of the property on the render element
/// </summary>
public string Path { get; private set; }
/// <summary>
/// Gets whether this property groups properties are all initialized
/// </summary>
public bool PropertiesInitialized { get; private set; }
/// <summary>
/// Gets or sets whether the property is hidden in the UI
/// </summary>
public bool IsHidden
{
get => _isHidden;
set
{
_isHidden = value;
OnVisibilityChanged();
}
}
/// <summary>
/// Gets the entity this property group uses for persistent storage
/// </summary>
public PropertyGroupEntity? PropertyGroupEntity { get; internal set; }
/// <summary>
/// A list of all layer properties in this group
/// </summary>
public ReadOnlyCollection<ILayerProperty> LayerProperties { get; }
/// <summary>
/// A list of al child groups in this group
/// </summary>
public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups { get; }
/// <summary>
/// Recursively gets all layer properties on this group and any subgroups
/// </summary>
public IReadOnlyCollection<ILayerProperty> GetAllLayerProperties()
{
if (_disposed)
throw new ObjectDisposedException("LayerPropertyGroup");
if (!PropertiesInitialized)
return new List<ILayerProperty>();
List<ILayerProperty> result = new(LayerProperties);
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
result.AddRange(layerPropertyGroup.GetAllLayerProperties());
return result.AsReadOnly();
}
/// <summary>
/// Applies the default value to all layer properties
/// </summary>
public void ResetAllLayerProperties()
{
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
layerProperty.ApplyDefaultValue();
}
/// <summary>
/// Occurs when the property group has initialized all its children
/// </summary>
public event EventHandler? PropertyGroupInitialized;
/// <summary>
/// Occurs when one of the current value of one of the layer properties in this group changes by some form of input
/// <para>Note: Will not trigger on properties in child groups</para>
/// </summary>
public event EventHandler<LayerPropertyEventArgs>? LayerPropertyOnCurrentValueSet;
/// <summary>
/// Occurs when the <see cref="IsHidden" /> value of the layer property was updated
/// </summary>
public event EventHandler? VisibilityChanged;
/// <summary>
/// Called before property group is activated to allow you to populate <see cref="LayerProperty{T}.DefaultValue" /> on
/// the properties you want
/// </summary>
protected abstract void PopulateDefaults();
/// <summary>
/// Called when the property group is activated
/// </summary>
protected abstract void EnableProperties();
/// <summary>
/// Called when the property group is deactivated (either the profile unloaded or the related brush/effect was removed)
/// </summary>
protected abstract void DisableProperties();
/// <summary>
/// Called when the property group and all its layer properties have been initialized
/// </summary>
protected virtual void OnPropertyGroupInitialized()
{
PropertyGroupInitialized?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
DisableProperties();
foreach (ILayerProperty layerProperty in _layerProperties)
layerProperty.Dispose();
foreach (LayerPropertyGroup layerPropertyGroup in _layerPropertyGroups)
layerPropertyGroup.Dispose();
}
}
internal void Initialize(RenderProfileElement profileElement, LayerPropertyGroup? parent, PropertyGroupDescriptionAttribute groupDescription, PropertyGroupEntity? propertyGroupEntity)
{
if (groupDescription.Identifier == null)
throw new ArtemisCoreException("Can't initialize a property group without an identifier");
// Doubt this will happen but let's make sure
if (PropertiesInitialized)
throw new ArtemisCoreException("Layer property group already initialized, wut");
ProfileElement = profileElement;
Parent = parent;
GroupDescription = groupDescription;
PropertyGroupEntity = propertyGroupEntity ?? new PropertyGroupEntity {Identifier = groupDescription.Identifier};
Path = parent != null ? parent.Path + "." + groupDescription.Identifier : groupDescription.Identifier;
// Get all properties implementing ILayerProperty or LayerPropertyGroup
foreach (PropertyInfo propertyInfo in GetType().GetProperties())
{
if (Attribute.IsDefined(propertyInfo, typeof(LayerPropertyIgnoreAttribute)))
continue;
if (typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
{
PropertyDescriptionAttribute? propertyDescription =
(PropertyDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
InitializeProperty(propertyInfo, propertyDescription ?? new PropertyDescriptionAttribute());
}
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
{
PropertyGroupDescriptionAttribute? propertyGroupDescription =
(PropertyGroupDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
InitializeChildGroup(propertyInfo, propertyGroupDescription ?? new PropertyGroupDescriptionAttribute());
}
}
// Request the property group to populate defaults
PopulateDefaults();
// Load the layer properties after defaults have been applied
foreach (ILayerProperty layerProperty in _layerProperties)
layerProperty.Load();
EnableProperties();
PropertiesInitialized = true;
OnPropertyGroupInitialized();
}
internal void ApplyToEntity()
{
if (!PropertiesInitialized || PropertyGroupEntity == null)
return;
foreach (ILayerProperty layerProperty in LayerProperties)
layerProperty.Save();
PropertyGroupEntity.PropertyGroups.Clear();
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
{
layerPropertyGroup.ApplyToEntity();
if (layerPropertyGroup.PropertyGroupEntity != null)
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
}
}
internal void Update(Timeline timeline)
{
foreach (ILayerProperty layerProperty in LayerProperties)
layerProperty.Update(timeline);
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
layerPropertyGroup.Update(timeline);
}
internal void MoveLayerProperty(ILayerProperty layerProperty, int index)
{
if (!_layerProperties.Contains(layerProperty))
return;
_layerProperties.Remove(layerProperty);
_layerProperties.Insert(index, layerProperty);
}
internal virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, EventArgs.Empty);
}
internal virtual void OnLayerPropertyOnCurrentValueSet(LayerPropertyEventArgs e)
{
Parent?.OnLayerPropertyOnCurrentValueSet(e);
LayerPropertyOnCurrentValueSet?.Invoke(this, e);
}
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
{
// Ensure the description has an identifier and name, if not this is a good point to set it based on the property info
if (string.IsNullOrWhiteSpace(propertyDescription.Identifier))
propertyDescription.Identifier = propertyInfo.Name;
if (string.IsNullOrWhiteSpace(propertyDescription.Name))
propertyDescription.Name = propertyInfo.Name.Humanize();
if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArtemisPluginException($"Property with PropertyDescription attribute must be of type ILayerProperty: {propertyDescription.Identifier}");
if (Activator.CreateInstance(propertyInfo.PropertyType, true) is not ILayerProperty instance)
throw new ArtemisPluginException($"Failed to create instance of layer property: {propertyDescription.Identifier}");
PropertyEntity entity = GetPropertyEntity(propertyDescription.Identifier, out bool fromStorage);
instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription);
propertyInfo.SetValue(this, instance);
_layerProperties.Add(instance);
}
private void InitializeChildGroup(PropertyInfo propertyInfo, PropertyGroupDescriptionAttribute propertyGroupDescription)
{
// Ensure the description has an identifier and name name, if not this is a good point to set it based on the property info
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Identifier))
propertyGroupDescription.Identifier = propertyInfo.Name;
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Name))
propertyGroupDescription.Name = propertyInfo.Name.Humanize();
if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArtemisPluginException($"Property with PropertyGroupDescription attribute must be of type LayerPropertyGroup: {propertyGroupDescription.Identifier}");
if (!(Activator.CreateInstance(propertyInfo.PropertyType) is LayerPropertyGroup instance))
throw new ArtemisPluginException($"Failed to create instance of layer property group: {propertyGroupDescription.Identifier}");
PropertyGroupEntity entity = GetPropertyGroupEntity(propertyGroupDescription.Identifier);
instance.Initialize(ProfileElement, this, propertyGroupDescription, entity);
propertyInfo.SetValue(this, instance);
_layerPropertyGroups.Add(instance);
}
private PropertyEntity GetPropertyEntity(string identifier, out bool fromStorage)
{
if (PropertyGroupEntity == null)
throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyEntity)} without {nameof(PropertyGroupEntity)} being setup");
PropertyEntity? entity = PropertyGroupEntity.Properties.FirstOrDefault(p => p.Identifier == identifier);
fromStorage = entity != null;
if (entity == null)
{
entity = new PropertyEntity {Identifier = identifier};
PropertyGroupEntity.Properties.Add(entity);
}
return entity;
}
private PropertyGroupEntity GetPropertyGroupEntity(string identifier)
{
if (PropertyGroupEntity == null)
throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyGroupEntity)} without {nameof(PropertyGroupEntity)} being setup");
return PropertyGroupEntity.PropertyGroups.FirstOrDefault(g => g.Identifier == identifier) ?? new PropertyGroupEntity {Identifier = identifier};
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return LayerProperties.SelectMany(p => p.GetFeatureDependencies()).Concat(LayerPropertyGroups.SelectMany(g => g.GetFeatureDependencies()));
}
#endregion
}