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

Core - Refactored effects

Profile editor - Added effect creation
This commit is contained in:
Robert 2022-04-23 17:07:04 +02:00
parent e5ba48c7f4
commit 2bf36fbf20
61 changed files with 1005 additions and 763 deletions

View File

@ -14,6 +14,13 @@
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/Views/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" /> <entry key="Artemis.UI.Avalonia/Screens/Sidebar/Views/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" /> <entry key="Artemis.UI.Avalonia/Screens/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Views/MainWindow.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" /> <entry key="Artemis.UI.Avalonia/Views/MainWindow.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Windows/App.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Avalonia/Artemis.UI/Styles/Artemis.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" /> <entry key="Avalonia/Artemis.UI/Styles/Artemis.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
</map> </map>

8
src/.idea/.idea.Artemis/.idea/misc.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SwUserDefinedSpecifications">
<option name="specTypeByUrl">
<map />
</option>
</component>
</project>

View File

@ -230,7 +230,7 @@ namespace Artemis.Core
} }
if (genericType.IsInterface) if (genericType.IsInterface)
foreach (var i in typeToCheck.GetInterfaces()) foreach (Type i in typeToCheck.GetInterfaces())
if (i.IsOfGenericType(genericType, out concreteGenericType)) if (i.IsOfGenericType(genericType, out concreteGenericType))
return true; return true;

View File

@ -227,19 +227,29 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public override void Enable() public override void Enable()
{ {
if (Enabled) // No checks here, effects will do their own checks to ensure they never enable twice
return; // Also not enabling children, they'll enable themselves during their own Update
foreach (BaseLayerEffect baseLayerEffect in LayerEffects) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalEnable(); baseLayerEffect.InternalEnable();
foreach (ProfileElement profileElement in Children) Enabled = true;
{
if (profileElement is RenderProfileElement renderProfileElement && renderProfileElement.ShouldBeEnabled)
renderProfileElement.Enable();
} }
Enabled = true; /// <inheritdoc />
public override void Disable()
{
// No checks here, effects will do their own checks to ensure they never disable twice
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
// Disabling children since their Update won't get called with their parent disabled
foreach (ProfileElement profileElement in Children)
{
if (profileElement is RenderProfileElement renderProfileElement)
renderProfileElement.Disable();
}
Enabled = false;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -250,24 +260,6 @@ namespace Artemis.Core
baseLayerEffect.InternalUpdate(Timeline); ; baseLayerEffect.InternalUpdate(Timeline); ;
} }
/// <inheritdoc />
public override void Disable()
{
if (!Enabled)
return;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
foreach (ProfileElement profileElement in Children)
{
if (profileElement is RenderProfileElement renderProfileElement)
renderProfileElement.Disable();
}
Enabled = false;
}
/// <summary> /// <summary>
/// Occurs when a property affecting the rendering properties of this folder has been updated /// Occurs when a property affecting the rendering properties of this folder has been updated
/// </summary> /// </summary>
@ -308,7 +300,6 @@ namespace Artemis.Core
internal override void Load() internal override void Load()
{ {
ExpandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
Reset(); Reset();
// Load child folders // Load child folders
@ -340,8 +331,6 @@ namespace Artemis.Core
FolderEntity.Suspended = Suspended; FolderEntity.Suspended = Suspended;
FolderEntity.ProfileId = Profile.EntityId; FolderEntity.ProfileId = Profile.EntityId;
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups);
SaveRenderElement(); SaveRenderElement();
} }

View File

@ -296,7 +296,6 @@ namespace Artemis.Core
Suspended = LayerEntity.Suspended; Suspended = LayerEntity.Suspended;
Order = LayerEntity.Order; Order = LayerEntity.Order;
ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups);
LoadRenderElement(); LoadRenderElement();
Adapter.Load(); Adapter.Load();
} }
@ -313,8 +312,6 @@ namespace Artemis.Core
LayerEntity.Suspended = Suspended; LayerEntity.Suspended = Suspended;
LayerEntity.Name = Name; LayerEntity.Name = Name;
LayerEntity.ProfileId = Profile.EntityId; LayerEntity.ProfileId = Profile.EntityId;
LayerEntity.ExpandedPropertyGroups.Clear();
LayerEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups);
General.ApplyToEntity(); General.ApplyToEntity();
Transform.ApplyToEntity(); Transform.ApplyToEntity();
@ -505,9 +502,7 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public override void Enable() public override void Enable()
{ {
if (Enabled) // No checks here, the brush and effects will do their own checks to ensure they never enable twice
return;
bool tryOrBreak = TryOrBreak(() => LayerBrush?.InternalEnable(), "Failed to enable layer brush"); bool tryOrBreak = TryOrBreak(() => LayerBrush?.InternalEnable(), "Failed to enable layer brush");
if (!tryOrBreak) if (!tryOrBreak)
return; return;
@ -523,6 +518,17 @@ namespace Artemis.Core
Enabled = true; Enabled = true;
} }
/// <inheritdoc />
public override void Disable()
{
// No checks here, the brush and effects will do their own checks to ensure they never disable twice
LayerBrush?.InternalDisable();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
Enabled = false;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OverrideTimelineAndApply(TimeSpan position) public override void OverrideTimelineAndApply(TimeSpan position)
{ {
@ -536,19 +542,6 @@ namespace Artemis.Core
baseLayerEffect.InternalUpdate(Timeline); baseLayerEffect.InternalUpdate(Timeline);
} }
/// <inheritdoc />
public override void Disable()
{
if (!Enabled)
return;
LayerBrush?.InternalDisable();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
Enabled = false;
}
/// <inheritdoc /> /// <inheritdoc />
public override void Reset() public override void Reset()
{ {
@ -822,6 +815,7 @@ namespace Artemis.Core
General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
General.BlendMode.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; General.BlendMode.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
Transform.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; Transform.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
LayerBrush?.Update(0);
OnLayerBrushUpdated(); OnLayerBrushUpdated();
ClearBrokenState("Failed to initialize layer brush"); ClearBrokenState("Failed to initialize layer brush");

View File

@ -0,0 +1,25 @@
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 LayerEffectPropertyGroup : LayerPropertyGroup
{
/// <summary>
/// Whether or not this layer effect is enabled
/// </summary>
[PropertyDescription(Name = "Enabled", Description = "Whether or not this layer effect is enabled")]
public BoolLayerProperty IsEnabled { get; set; } = null!;
internal void InitializeIsEnabled()
{
IsEnabled.DefaultValue = true;
if (!IsEnabled.IsLoadedFromStorage)
IsEnabled.SetCurrentValue(true);
}
}
}

View File

@ -203,7 +203,7 @@ namespace Artemis.Core
/// or existing keyframe. /// or existing keyframe.
/// </param> /// </param>
/// <returns>The keyframe if one was created or updated.</returns> /// <returns>The keyframe if one was created or updated.</returns>
public LayerPropertyKeyframe<T>? SetCurrentValue(T value, TimeSpan? time) public LayerPropertyKeyframe<T>? SetCurrentValue(T value, TimeSpan? time = null)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");

View File

@ -249,6 +249,15 @@ namespace Artemis.Core
layerPropertyGroup.Update(timeline); 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() internal virtual void OnVisibilityChanged()
{ {
VisibilityChanged?.Invoke(this, EventArgs.Empty); VisibilityChanged?.Invoke(this, EventArgs.Empty);

View File

@ -4,14 +4,13 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.LayerEffects.Placeholder; using Artemis.Core.LayerEffects.Placeholder;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core;
{
/// <summary> /// <summary>
/// Represents an element of a <see cref="Profile" /> that has advanced rendering capabilities /// Represents an element of a <see cref="Profile" /> that has advanced rendering capabilities
/// </summary> /// </summary>
@ -22,12 +21,11 @@ namespace Artemis.Core
internal RenderProfileElement(ProfileElement parent, Profile profile) : base(profile) internal RenderProfileElement(ProfileElement parent, Profile profile) : base(profile)
{ {
Timeline = new Timeline(); _layerEffects = new List<BaseLayerEffect>();
ExpandedPropertyGroups = new List<string>();
LayerEffectsList = new List<BaseLayerEffect>();
LayerEffects = new ReadOnlyCollection<BaseLayerEffect>(LayerEffectsList);
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
_displayCondition = new AlwaysOnCondition(this); _displayCondition = new AlwaysOnCondition(this);
Timeline = new Timeline();
LayerEffects = new ReadOnlyCollection<BaseLayerEffect>(_layerEffects);
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
@ -54,6 +52,12 @@ namespace Artemis.Core
/// </summary> /// </summary>
public event EventHandler? LayerEffectsUpdated; public event EventHandler? LayerEffectsUpdated;
/// <summary>
/// Overrides the main timeline to the specified time and clears any extra time lines
/// </summary>
/// <param name="position">The position to set the timeline to</param>
public abstract void OverrideTimelineAndApply(TimeSpan position);
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
@ -84,7 +88,7 @@ namespace Artemis.Core
_ => DisplayCondition _ => DisplayCondition
}; };
ActivateEffects(); LoadLayerEffects();
} }
internal void SaveRenderElement() internal void SaveRenderElement()
@ -114,7 +118,7 @@ namespace Artemis.Core
layerProperty.BaseDataBinding.LoadNodeScript(); layerProperty.BaseDataBinding.LoadNodeScript();
} }
internal void OnLayerEffectsUpdated() private void OnLayerEffectsUpdated()
{ {
LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); LayerEffectsUpdated?.Invoke(this, EventArgs.Empty);
} }
@ -127,7 +131,8 @@ namespace Artemis.Core
public Timeline Timeline { get; private set; } public Timeline Timeline { get; private set; }
/// <summary> /// <summary>
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display condition /// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display
/// condition
/// </summary> /// </summary>
protected void UpdateTimeline(double deltaTime) protected void UpdateTimeline(double deltaTime)
{ {
@ -178,36 +183,6 @@ namespace Artemis.Core
private set => SetAndNotify(ref _bounds, value); private set => SetAndNotify(ref _bounds, value);
} }
#region Property group expansion
internal List<string> ExpandedPropertyGroups;
/// <summary>
/// Determines whether the provided property group is expanded
/// </summary>
/// <param name="layerPropertyGroup">The property group to check</param>
/// <returns>A boolean indicating whether the provided property group is expanded</returns>
public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup)
{
return ExpandedPropertyGroups.Contains(layerPropertyGroup.Path);
}
/// <summary>
/// Expands or collapses the provided property group
/// </summary>
/// <param name="layerPropertyGroup">The group to expand or collapse</param>
/// <param name="expanded">Whether to expand or collapse the property group</param>
public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded)
{
if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup))
ExpandedPropertyGroups.Remove(layerPropertyGroup.Path);
else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup))
ExpandedPropertyGroups.Add(layerPropertyGroup.Path);
}
#endregion
#endregion #endregion
#region State #region State
@ -226,7 +201,7 @@ namespace Artemis.Core
#region Effect management #region Effect management
internal readonly List<BaseLayerEffect> LayerEffectsList; private readonly List<BaseLayerEffect> _layerEffects;
/// <summary> /// <summary>
/// Gets a read-only collection of the layer effects on this entity /// Gets a read-only collection of the layer effects on this entity
@ -234,42 +209,106 @@ namespace Artemis.Core
public ReadOnlyCollection<BaseLayerEffect> LayerEffects { get; } public ReadOnlyCollection<BaseLayerEffect> LayerEffects { get; }
/// <summary> /// <summary>
/// Adds a the layer effect described inthe provided <paramref name="descriptor" /> /// Adds a the provided layer effect to the render profile element
/// </summary> /// </summary>
public void AddLayerEffect(LayerEffectDescriptor descriptor) /// <param name="layerEffect">The effect to add.</param>
public void AddLayerEffect(BaseLayerEffect layerEffect)
{ {
if (descriptor == null) if (layerEffect == null)
throw new ArgumentNullException(nameof(descriptor)); throw new ArgumentNullException(nameof(layerEffect));
LayerEffectEntity entity = new() // Ensure something needs to be done
{ if (_layerEffects.Contains(layerEffect))
Id = Guid.NewGuid(), return;
Suspended = false,
Order = LayerEffects.Count + 1
};
descriptor.CreateInstance(this, entity);
// Make sure the layer effect is tied to this element
layerEffect.ProfileElement = this;
_layerEffects.Add(layerEffect);
// Update the order on the effects
OrderEffects(); OrderEffects();
OnLayerEffectsUpdated(); OnLayerEffectsUpdated();
} }
/// <summary> /// <summary>
/// Removes the provided layer /// Removes the provided layer effect.
/// </summary> /// </summary>
/// <param name="effect"></param> /// <param name="layerEffect">The effect to remove.</param>
public void RemoveLayerEffect([NotNull] BaseLayerEffect effect) public void RemoveLayerEffect(BaseLayerEffect layerEffect)
{ {
if (effect == null) throw new ArgumentNullException(nameof(effect)); if (layerEffect == null)
throw new ArgumentNullException(nameof(layerEffect));
// Remove the effect from the layer and dispose it // Ensure something needs to be done
LayerEffectsList.Remove(effect); if (!_layerEffects.Contains(layerEffect))
effect.Dispose(); return;
// Remove the effect from the layer
_layerEffects.Remove(layerEffect);
// Update the order on the remaining effects // Update the order on the remaining effects
OrderEffects(); OrderEffects();
OnLayerEffectsUpdated(); OnLayerEffectsUpdated();
} }
private void LoadLayerEffects()
{
foreach (BaseLayerEffect baseLayerEffect in _layerEffects)
baseLayerEffect.Dispose();
_layerEffects.Clear();
foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects.OrderBy(e => e.Order))
LoadLayerEffect(layerEffectEntity);
}
private void LoadLayerEffect(LayerEffectEntity layerEffectEntity)
{
LayerEffectDescriptor? descriptor = LayerEffectStore.Get(layerEffectEntity.ProviderId, layerEffectEntity.EffectType)?.LayerEffectDescriptor;
BaseLayerEffect layerEffect;
// Create an instance with the descriptor
if (descriptor != null)
{
layerEffect = descriptor.CreateInstance(this, layerEffectEntity);
}
// If no descriptor was found and there was no existing placeholder, create a placeholder
else
{
descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.ProviderId);
layerEffect = descriptor.CreateInstance(this, layerEffectEntity);
}
_layerEffects.Add(layerEffect);
}
private void ReplaceLayerEffectWithPlaceholder(BaseLayerEffect layerEffect)
{
int index = _layerEffects.IndexOf(layerEffect);
if (index == -1)
return;
LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffect.ProviderId);
BaseLayerEffect placeholder = descriptor.CreateInstance(this, layerEffect.LayerEffectEntity);
_layerEffects[index] = placeholder;
layerEffect.Dispose();
OnLayerEffectsUpdated();
}
private void ReplacePlaceholderWithLayerEffect(PlaceholderLayerEffect placeholder)
{
int index = _layerEffects.IndexOf(placeholder);
if (index == -1)
return;
LayerEffectDescriptor? descriptor = LayerEffectStore.Get(placeholder.OriginalEntity.ProviderId, placeholder.PlaceholderFor)?.LayerEffectDescriptor;
if (descriptor == null)
throw new ArtemisCoreException("Can't replace a placeholder effect because the real effect isn't available.");
BaseLayerEffect layerEffect = descriptor.CreateInstance(this, placeholder.OriginalEntity);
_layerEffects[index] = layerEffect;
placeholder.Dispose();
OnLayerEffectsUpdated();
}
private void OrderEffects() private void OrderEffects()
{ {
int index = 0; int index = 0;
@ -279,68 +318,36 @@ namespace Artemis.Core
index++; index++;
} }
LayerEffectsList.Sort((a, b) => a.Order.CompareTo(b.Order)); _layerEffects.Sort((a, b) => a.Order.CompareTo(b.Order));
}
internal void ActivateEffects()
{
foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects)
{
// If there is a non-placeholder existing effect, skip this entity
BaseLayerEffect? existing = LayerEffectsList.FirstOrDefault(e => e.LayerEffectEntity.Id == layerEffectEntity.Id);
if (existing != null && existing.Descriptor.PlaceholderFor == null)
continue;
LayerEffectDescriptor? descriptor = LayerEffectStore.Get(layerEffectEntity.ProviderId, layerEffectEntity.EffectType)?.LayerEffectDescriptor;
if (descriptor != null)
{
// If a descriptor is found but there is an existing placeholder, remove the placeholder
if (existing != null)
{
LayerEffectsList.Remove(existing);
existing.Dispose();
}
// Create an instance with the descriptor
descriptor.CreateInstance(this, layerEffectEntity);
}
else if (existing == null)
{
// If no descriptor was found and there was no existing placeholder, create a placeholder
descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.ProviderId);
descriptor.CreateInstance(this, layerEffectEntity);
}
}
OrderEffects();
}
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
{
LayerEffectsList.Add(layerEffect);
OnLayerEffectsUpdated();
} }
private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e) private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e)
{ {
// If effects provided by the plugin are on the element, replace them with placeholders // Find effects that just got disabled and replace them with placeholders
List<BaseLayerEffect> pluginEffects = LayerEffectsList.Where(ef => ef.ProviderId == e.Registration.PluginFeature.Id).ToList(); List<BaseLayerEffect> affectedLayerEffects = _layerEffects.Where(ef => ef.ProviderId == e.Registration.PluginFeature.Id).ToList();
foreach (BaseLayerEffect pluginEffect in pluginEffects)
{
LayerEffectEntity entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.LayerEffectEntity.Id);
LayerEffectsList.Remove(pluginEffect);
pluginEffect.Dispose();
LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.ProviderId); if (!affectedLayerEffects.Any())
descriptor.CreateInstance(this, entity); return;
}
foreach (BaseLayerEffect baseLayerEffect in affectedLayerEffects)
ReplaceLayerEffectWithPlaceholder(baseLayerEffect);
OnLayerEffectsUpdated();
} }
private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e) private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e)
{ {
if (RenderElementEntity.LayerEffects.Any(ef => ef.ProviderId == e.Registration.PluginFeature.Id)) // Find placeholders that just got enabled and replace them with real effects
ActivateEffects(); List<PlaceholderLayerEffect> affectedPlaceholders = LayerEffects
.Where(l => l is PlaceholderLayerEffect ph && ph.OriginalEntity.ProviderId == e.Registration.PluginFeature.Id)
.Cast<PlaceholderLayerEffect>()
.ToList();
if (!affectedPlaceholders.Any())
return;
foreach (PlaceholderLayerEffect placeholderLayerEffect in affectedPlaceholders)
ReplacePlaceholderWithLayerEffect(placeholderLayerEffect);
OnLayerEffectsUpdated();
} }
#endregion #endregion
@ -354,7 +361,7 @@ namespace Artemis.Core
public bool DisplayConditionMet public bool DisplayConditionMet
{ {
get => _displayConditionMet; get => _displayConditionMet;
protected set => SetAndNotify(ref _displayConditionMet, value); private set => SetAndNotify(ref _displayConditionMet, value);
} }
private bool _displayConditionMet; private bool _displayConditionMet;
@ -386,11 +393,4 @@ namespace Artemis.Core
} }
#endregion #endregion
/// <summary>
/// Overrides the main timeline to the specified time and clears any extra time lines
/// </summary>
/// <param name="position">The position to set the timeline to</param>
public abstract void OverrideTimelineAndApply(TimeSpan position);
}
} }

View File

@ -451,7 +451,7 @@ namespace Artemis.Core
}); });
DeviceEntity.InputMappings.Clear(); DeviceEntity.InputMappings.Clear();
foreach (var (original, mapped) in InputMappings) foreach ((ArtemisLed? original, ArtemisLed? mapped) in InputMappings)
DeviceEntity.InputMappings.Add(new InputMappingEntity {OriginalLedId = (int) original.RgbLed.Id, MappedLedId = (int) mapped.RgbLed.Id}); DeviceEntity.InputMappings.Add(new InputMappingEntity {OriginalLedId = (int) original.RgbLed.Id, MappedLedId = (int) mapped.RgbLed.Id});
DeviceEntity.Categories.Clear(); DeviceEntity.Categories.Clear();

View File

@ -6,7 +6,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary> /// <summary>
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead /// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead
/// </summary> /// </summary>
public abstract class PropertiesLayerBrush<T> : BaseLayerBrush where T : LayerPropertyGroup public abstract class PropertiesLayerBrush<T> : BaseLayerBrush where T : LayerPropertyGroup, new()
{ {
private T _properties = null!; private T _properties = null!;
@ -35,7 +35,7 @@ namespace Artemis.Core.LayerBrushes
internal void InitializeProperties(PropertyGroupEntity? propertyGroupEntity) internal void InitializeProperties(PropertyGroupEntity? propertyGroupEntity)
{ {
Properties = Activator.CreateInstance<T>(); Properties = new T();
PropertyGroupDescriptionAttribute groupDescription = new() {Identifier = "Brush", Name = Descriptor.DisplayName, Description = Descriptor.Description}; PropertyGroupDescriptionAttribute groupDescription = new() {Identifier = "Brush", Name = Descriptor.DisplayName, Description = Descriptor.Description};
Properties.Initialize(Layer, null, groupDescription, propertyGroupEntity); Properties.Initialize(Layer, null, groupDescription, propertyGroupEntity);
PropertiesInitialized = true; PropertiesInitialized = true;

View File

@ -6,7 +6,7 @@ namespace Artemis.Core.LayerBrushes
/// Represents a brush that renders on a layer /// Represents a brush that renders on a layer
/// </summary> /// </summary>
/// <typeparam name="T">The type of brush properties</typeparam> /// <typeparam name="T">The type of brush properties</typeparam>
public abstract class LayerBrush<T> : PropertiesLayerBrush<T> where T : LayerPropertyGroup public abstract class LayerBrush<T> : PropertiesLayerBrush<T> where T : LayerPropertyGroup, new()
{ {
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="LayerBrush{T}" /> class /// Creates a new instance of the <see cref="LayerBrush{T}" /> class

View File

@ -69,8 +69,6 @@ namespace Artemis.Core.LayerBrushes
brush.LayerBrushEntity = entity ?? new LayerBrushEntity { ProviderId = Provider.Id, BrushType = LayerBrushType.FullName }; brush.LayerBrushEntity = entity ?? new LayerBrushEntity { ProviderId = Provider.Id, BrushType = LayerBrushType.FullName };
brush.Initialize(); brush.Initialize();
brush.Update(0);
return brush; return brush;
} }
} }

View File

@ -6,7 +6,7 @@ namespace Artemis.Core.LayerBrushes
/// Represents a brush that renders on a per-layer basis /// Represents a brush that renders on a per-layer basis
/// </summary> /// </summary>
/// <typeparam name="T">The type of brush properties</typeparam> /// <typeparam name="T">The type of brush properties</typeparam>
public abstract class PerLedLayerBrush<T> : PropertiesLayerBrush<T> where T : LayerPropertyGroup public abstract class PerLedLayerBrush<T> : PropertiesLayerBrush<T> where T : LayerPropertyGroup, new()
{ {
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="PerLedLayerBrush{T}" /> class /// Creates a new instance of the <see cref="PerLedLayerBrush{T}" /> class

View File

@ -7,12 +7,11 @@ namespace Artemis.Core.LayerEffects
/// <summary> /// <summary>
/// For internal use only, please use <see cref="LayerEffect{T}" /> instead /// For internal use only, please use <see cref="LayerEffect{T}" /> instead
/// </summary> /// </summary>
public abstract class BaseLayerEffect : BreakableModel, IDisposable public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel
{ {
private ILayerEffectConfigurationDialog? _configurationDialog; private ILayerEffectConfigurationDialog? _configurationDialog;
private LayerEffectDescriptor _descriptor; private LayerEffectDescriptor _descriptor;
private bool _suspended; private bool _suspended;
private Guid _entityId;
private bool _hasBeenRenamed; private bool _hasBeenRenamed;
private string _name; private string _name;
private int _order; private int _order;
@ -51,15 +50,6 @@ namespace Artemis.Core.LayerEffects
set => SetAndNotify(ref _name, value); set => SetAndNotify(ref _name, value);
} }
/// <summary>
/// Gets or sets the suspended state, if suspended the effect is skipped in render and update
/// </summary>
public bool Suspended
{
get => _suspended;
set => SetAndNotify(ref _suspended, value);
}
/// <summary> /// <summary>
/// Gets or sets whether the effect has been renamed by the user, if true consider refraining from changing the name /// Gets or sets whether the effect has been renamed by the user, if true consider refraining from changing the name
/// programatically /// programatically
@ -105,13 +95,18 @@ namespace Artemis.Core.LayerEffects
/// <summary> /// <summary>
/// Gets a reference to the layer property group without knowing it's type /// Gets a reference to the layer property group without knowing it's type
/// </summary> /// </summary>
public virtual LayerPropertyGroup? BaseProperties => null; public virtual LayerEffectPropertyGroup? BaseProperties => null;
/// <summary> /// <summary>
/// Gets a boolean indicating whether the layer effect is enabled or not /// Gets a boolean indicating whether the layer effect is enabled or not
/// </summary> /// </summary>
public bool Enabled { get; private set; } public bool Enabled { get; private set; }
/// <summary>
/// Gets a boolean indicating whether the layer effect is suspended or not
/// </summary>
public bool Suspended => BaseProperties is not {PropertiesInitialized: true} || !BaseProperties.IsEnabled;
#region IDisposable #region IDisposable
/// <summary> /// <summary>
@ -224,11 +219,20 @@ namespace Artemis.Core.LayerEffects
#endregion #endregion
/// <inheritdoc />
public void Load()
{
Name = LayerEffectEntity.Name;
HasBeenRenamed = LayerEffectEntity.HasBeenRenamed;
Order = LayerEffectEntity.Order;
}
/// <inheritdoc />
public void Save() public void Save()
{ {
// No need to update the ID, type and provider ID. They're set once by the LayerBrushDescriptors CreateInstance and can't change LayerEffectEntity.ProviderId = Descriptor.Provider.Id;
LayerEffectEntity.EffectType = GetType().FullName;
LayerEffectEntity.Name = Name; LayerEffectEntity.Name = Name;
LayerEffectEntity.Suspended = Suspended;
LayerEffectEntity.HasBeenRenamed = HasBeenRenamed; LayerEffectEntity.HasBeenRenamed = HasBeenRenamed;
LayerEffectEntity.Order = Order; LayerEffectEntity.Order = Order;

View File

@ -6,7 +6,7 @@ namespace Artemis.Core.LayerEffects
/// Represents an effect that applies preprocessing and/or postprocessing to a layer /// Represents an effect that applies preprocessing and/or postprocessing to a layer
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public abstract class LayerEffect<T> : BaseLayerEffect where T : LayerPropertyGroup public abstract class LayerEffect<T> : BaseLayerEffect where T : LayerEffectPropertyGroup, new()
{ {
private T _properties = null!; private T _properties = null!;
@ -16,7 +16,7 @@ namespace Artemis.Core.LayerEffects
public bool PropertiesInitialized { get; internal set; } public bool PropertiesInitialized { get; internal set; }
/// <inheritdoc /> /// <inheritdoc />
public override LayerPropertyGroup BaseProperties => Properties; public override LayerEffectPropertyGroup BaseProperties => Properties;
/// <summary> /// <summary>
/// Gets the properties of this effect. /// Gets the properties of this effect.
@ -33,18 +33,21 @@ namespace Artemis.Core.LayerEffects
internal set => _properties = value; internal set => _properties = value;
} }
internal void InitializeProperties()
{
Properties = Activator.CreateInstance<T>();
Properties.Initialize(ProfileElement, null, new PropertyGroupDescriptionAttribute(){Identifier = "LayerEffect"}, LayerEffectEntity.PropertyGroup);
PropertiesInitialized = true;
EnableLayerEffect();
}
internal override void Initialize() internal override void Initialize()
{ {
InitializeProperties(); InitializeProperties();
} }
private void InitializeProperties()
{
Properties = new T();
Properties.Initialize(ProfileElement, null, new PropertyGroupDescriptionAttribute {Identifier = "LayerEffect"}, LayerEffectEntity.PropertyGroup);
// Initialize will call PopulateDefaults but that is for plugin developers so can't rely on that to default IsEnabled to true
Properties.InitializeIsEnabled();
PropertiesInitialized = true;
EnableLayerEffect();
}
} }
} }

View File

@ -1,23 +1,31 @@
using System; using System;
using System.Linq;
using Artemis.Core.LayerEffects.Placeholder; using Artemis.Core.LayerEffects.Placeholder;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Ninject; using Ninject;
namespace Artemis.Core.LayerEffects namespace Artemis.Core.LayerEffects;
{
/// <summary> /// <summary>
/// A class that describes a layer effect /// A class that describes a layer effect
/// </summary> /// </summary>
public class LayerEffectDescriptor public class LayerEffectDescriptor
{ {
internal LayerEffectDescriptor(string displayName, string description, string icon, Type? layerEffectType, LayerEffectProvider provider) internal LayerEffectDescriptor(string displayName, string description, string icon, Type layerEffectType, LayerEffectProvider provider)
{ {
DisplayName = displayName; DisplayName = displayName;
Description = description; Description = description;
Icon = icon; Icon = icon;
LayerEffectType = layerEffectType; LayerEffectType = layerEffectType ?? throw new ArgumentNullException(nameof(layerEffectType));
Provider = provider; Provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
internal LayerEffectDescriptor(string placeholderFor, LayerEffectProvider provider)
{
PlaceholderFor = placeholderFor ?? throw new ArgumentNullException(nameof(placeholderFor));
Provider = provider ?? throw new ArgumentNullException(nameof(provider));
DisplayName = "Missing effect";
Description = "This effect could not be loaded";
Icon = "FileQuestion";
} }
/// <summary> /// <summary>
@ -49,68 +57,55 @@ namespace Artemis.Core.LayerEffects
/// <summary> /// <summary>
/// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder /// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder
/// </summary> /// </summary>
public string? PlaceholderFor { get; internal set; } public string? PlaceholderFor { get; }
/// <summary> /// <summary>
/// Creates an instance of the described effect and applies it to the render element /// Creates an instance of the described effect and applies it to the render element
/// </summary> /// </summary>
internal void CreateInstance(RenderProfileElement renderElement, LayerEffectEntity? entity) public BaseLayerEffect CreateInstance(RenderProfileElement renderElement, LayerEffectEntity? entity)
{ {
if (PlaceholderFor != null)
{
if (entity == null)
throw new ArtemisCoreException("Cannot create a placeholder for a layer effect that wasn't loaded from an entity");
return CreatePlaceHolderInstance(renderElement, entity);
}
if (LayerEffectType == null) if (LayerEffectType == null)
throw new ArtemisCoreException("Cannot create an instance of a layer effect because this descriptor is not a placeholder but is still missing its LayerEffectType"); throw new ArtemisCoreException("Cannot create an instance of a layer effect because this descriptor is not a placeholder but is still missing its LayerEffectType");
if (entity == null) BaseLayerEffect effect = (BaseLayerEffect) Provider.Plugin.Kernel!.Get(LayerEffectType);
effect.ProfileElement = renderElement;
effect.Descriptor = this;
if (entity != null)
{ {
entity = new LayerEffectEntity effect.LayerEffectEntity = entity;
{ effect.Load();
Id = Guid.NewGuid(), effect.Initialize();
Suspended = false,
Order = renderElement.LayerEffects.Count + 1,
ProviderId = Provider.Id,
EffectType = LayerEffectType.FullName
};
} }
else else
{ {
// Skip effects already on the element effect.LayerEffectEntity = new LayerEffectEntity();
if (renderElement.LayerEffects.Any(e => e.LayerEffectEntity.Id == entity.Id))
return;
}
if (PlaceholderFor != null)
{
CreatePlaceHolderInstance(renderElement, entity);
return;
}
BaseLayerEffect effect = (BaseLayerEffect) Provider.Plugin.Kernel!.Get(LayerEffectType);
effect.ProfileElement = renderElement;
effect.LayerEffectEntity = entity;
effect.Order = entity.Order;
effect.Name = entity.Name;
effect.Suspended = entity.Suspended;
effect.Descriptor = this;
effect.Initialize(); effect.Initialize();
effect.Update(0); effect.Save();
renderElement.ActivateLayerEffect(effect);
} }
private void CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity) return effect;
}
private BaseLayerEffect CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity)
{ {
if (PlaceholderFor == null) if (PlaceholderFor == null)
throw new ArtemisCoreException("Cannot create a placeholder instance using a layer effect descriptor that is not a placeholder for anything"); throw new ArtemisCoreException("Cannot create a placeholder instance using a layer effect descriptor that is not a placeholder for anything");
PlaceholderLayerEffect effect = new(entity, PlaceholderFor) PlaceholderLayerEffect effect = new(entity, PlaceholderFor)
{ {
ProfileElement = renderElement, ProfileElement = renderElement,
Descriptor = this Descriptor = this
}; };
effect.Initialize(); effect.Initialize();
renderElement.ActivateLayerEffect(effect);
if (renderElement.ShouldBeEnabled) return effect;
effect.InternalEnable();
}
} }
} }

View File

@ -16,7 +16,6 @@ namespace Artemis.Core.LayerEffects.Placeholder
LayerEffectEntity = originalEntity; LayerEffectEntity = originalEntity;
Order = OriginalEntity.Order; Order = OriginalEntity.Order;
Name = OriginalEntity.Name; Name = OriginalEntity.Name;
Suspended = OriginalEntity.Suspended;
HasBeenRenamed = OriginalEntity.HasBeenRenamed; HasBeenRenamed = OriginalEntity.HasBeenRenamed;
} }
@ -58,7 +57,7 @@ namespace Artemis.Core.LayerEffects.Placeholder
/// <summary> /// <summary>
/// This is in place so that the UI has something to show /// This is in place so that the UI has something to show
/// </summary> /// </summary>
internal class PlaceholderProperties : LayerPropertyGroup internal class PlaceholderProperties : LayerEffectPropertyGroup
{ {
protected override void PopulateDefaults() protected override void PopulateDefaults()
{ {

View File

@ -4,11 +4,7 @@
{ {
public static LayerEffectDescriptor Create(string missingProviderId) public static LayerEffectDescriptor Create(string missingProviderId)
{ {
LayerEffectDescriptor descriptor = new("Missing effect", "This effect could not be loaded", "FileQuestion", null, Constants.EffectPlaceholderPlugin) LayerEffectDescriptor descriptor = new(missingProviderId, Constants.EffectPlaceholderPlugin);
{
PlaceholderFor = missingProviderId
};
return descriptor; return descriptor;
} }
} }

View File

@ -12,7 +12,7 @@ namespace Artemis.Core.Modules
/// <summary> /// <summary>
/// Allows you to add new data to the Artemis data model /// Allows you to add new data to the Artemis data model
/// </summary> /// </summary>
public abstract class Module<T> : Module where T : DataModel public abstract class Module<T> : Module where T : DataModel, new()
{ {
/// <summary> /// <summary>
/// The data model driving this module /// The data model driving this module
@ -79,7 +79,7 @@ namespace Artemis.Core.Modules
internal override void InternalEnable() internal override void InternalEnable()
{ {
DataModel = Activator.CreateInstance<T>(); DataModel = new T();
DataModel.Module = this; DataModel.Module = this;
DataModel.DataModelDescription = GetDataModelDescription(); DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable(); base.InternalEnable();
@ -310,7 +310,7 @@ namespace Artemis.Core.Modules
/// <inheritdoc /> /// <inheritdoc />
internal override void InternalEnable() internal override void InternalEnable()
{ {
foreach ((DefaultCategoryName categoryName, var path) in _pendingDefaultProfilePaths) foreach ((DefaultCategoryName categoryName, string? path) in _pendingDefaultProfilePaths)
AddDefaultProfile(categoryName, path); AddDefaultProfile(categoryName, path);
_pendingDefaultProfilePaths.Clear(); _pendingDefaultProfilePaths.Clear();

View File

@ -71,7 +71,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public void SaveAllSettings() public void SaveAllSettings()
{ {
foreach (var (_, pluginSetting) in _settingEntities) foreach ((string _, IPluginSetting? pluginSetting) in _settingEntities)
pluginSetting.Save(); pluginSetting.Save();
} }

View File

@ -301,7 +301,7 @@ namespace Artemis.Core.Services
public void ReleaseAll() public void ReleaseAll()
{ {
foreach (var (device, keys) in _pressedKeys.ToList()) foreach ((ArtemisDevice? device, HashSet<KeyboardKey>? keys) in _pressedKeys.ToList())
{ {
foreach (KeyboardKey keyboardKey in keys) foreach (KeyboardKey keyboardKey in keys)
{ {

View File

@ -12,7 +12,7 @@ namespace Artemis.Core.Services
/// <see cref="object" /> or <see langword="null" />. /// <see cref="object" /> or <see langword="null" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para> /// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary> /// </summary>
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel, new()
{ {
private readonly Module<T> _module; private readonly Module<T> _module;

View File

@ -51,7 +51,7 @@ namespace Artemis.Core.Services
/// <param name="module">The module whose datamodel to apply the received JSON to</param> /// <param name="module">The module whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param> /// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns> /// <returns>The resulting end point</returns>
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel; DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new();
/// <summary> /// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />. /// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.

View File

@ -50,7 +50,7 @@ namespace Artemis.Core.Services
.WithModule(PluginsModule); .WithModule(PluginsModule);
// Add registered modules // Add registered modules
foreach (var webModule in _modules) foreach (WebModuleRegistration? webModule in _modules)
server = server.WithModule(webModule.CreateInstance()); server = server.WithModule(webModule.CreateInstance());
server = server server = server
@ -132,7 +132,7 @@ namespace Artemis.Core.Services
return endPoint; return endPoint;
} }
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new()
{ {
if (module == null) throw new ArgumentNullException(nameof(module)); if (module == null) throw new ArgumentNullException(nameof(module));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
@ -141,7 +141,7 @@ namespace Artemis.Core.Services
return endPoint; return endPoint;
} }
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel, new()
{ {
} }

View File

@ -24,7 +24,7 @@ namespace Artemis.Core.Internal
public override void Evaluate() public override void Evaluate()
{ {
foreach (var (property, inputPin) in _propertyPins) foreach ((IDataBindingProperty? property, InputPin? inputPin) in _propertyPins)
{ {
if (inputPin.ConnectedTo.Any()) if (inputPin.ConnectedTo.Any())
_propertyValues[property] = inputPin.Value!; _propertyValues[property] = inputPin.Value!;
@ -35,7 +35,7 @@ namespace Artemis.Core.Internal
public void ApplyToDataBinding() public void ApplyToDataBinding()
{ {
foreach (var (property, pendingValue) in _propertyValues) foreach ((IDataBindingProperty? property, object? pendingValue) in _propertyValues)
property.SetValue(pendingValue); property.SetValue(pendingValue);
} }

View File

@ -12,11 +12,8 @@ namespace Artemis.Storage.Entities.Profile.Abstract
public List<LayerEffectEntity> LayerEffects { get; set; } public List<LayerEffectEntity> LayerEffects { get; set; }
public List<PropertyEntity> PropertyEntities { get; set; } public List<PropertyEntity> PropertyEntities { get; set; }
public List<string> ExpandedPropertyGroups { get; set; }
public IConditionEntity DisplayCondition { get; set; } public IConditionEntity DisplayCondition { get; set; }
public TimelineEntity Timeline { get; set; } public TimelineEntity Timeline { get; set; }
public NodeScriptEntity NodeScript { get; set; }
} }
} }

View File

@ -11,7 +11,6 @@ namespace Artemis.Storage.Entities.Profile
{ {
PropertyEntities = new List<PropertyEntity>(); PropertyEntities = new List<PropertyEntity>();
LayerEffects = new List<LayerEffectEntity>(); LayerEffects = new List<LayerEffectEntity>();
ExpandedPropertyGroups = new List<string>();
} }
public int Order { get; set; } public int Order { get; set; }

View File

@ -5,11 +5,9 @@ namespace Artemis.Storage.Entities.Profile
{ {
public class LayerEffectEntity public class LayerEffectEntity
{ {
public Guid Id { get; set; }
public string ProviderId { get; set; } public string ProviderId { get; set; }
public string EffectType { get; set; } public string EffectType { get; set; }
public string Name { get; set; } public string Name { get; set; }
public bool Suspended { get; set; }
public bool HasBeenRenamed { get; set; } public bool HasBeenRenamed { get; set; }
public int Order { get; set; } public int Order { get; set; }

View File

@ -14,7 +14,6 @@ namespace Artemis.Storage.Entities.Profile
AdaptionHints = new List<IAdaptionHintEntity>(); AdaptionHints = new List<IAdaptionHintEntity>();
PropertyEntities = new List<PropertyEntity>(); PropertyEntities = new List<PropertyEntity>();
LayerEffects = new List<LayerEffectEntity>(); LayerEffects = new List<LayerEffectEntity>();
ExpandedPropertyGroups = new List<string>();
} }
public int Order { get; set; } public int Order { get; set; }

View File

@ -17,15 +17,17 @@ namespace Artemis.Storage.Migrations
foreach (FolderEntity profileEntityFolder in profileEntity.Folders) foreach (FolderEntity profileEntityFolder in profileEntity.Folders)
{ {
profileEntityFolder.Suspended = false; profileEntityFolder.Suspended = false;
foreach (LayerEffectEntity layerEffectEntity in profileEntityFolder.LayerEffects) // Commented out during Avalonia port when Suspended was moved into the LayerEffect's LayerProperties
layerEffectEntity.Suspended = false; // foreach (LayerEffectEntity layerEffectEntity in profileEntityFolder.LayerEffects)
// layerEffectEntity.Suspended = false;
} }
foreach (LayerEntity profileEntityLayer in profileEntity.Layers) foreach (LayerEntity profileEntityLayer in profileEntity.Layers)
{ {
profileEntityLayer.Suspended = false; profileEntityLayer.Suspended = false;
foreach (LayerEffectEntity layerEffectEntity in profileEntityLayer.LayerEffects) // Commented out during Avalonia port when Suspended was moved into the LayerEffect's LayerProperties
layerEffectEntity.Suspended = false; // foreach (LayerEffectEntity layerEffectEntity in profileEntityLayer.LayerEffects)
// layerEffectEntity.Suspended = false;
} }
repository.Upsert(profileEntity); repository.Upsert(profileEntity);

View File

@ -222,8 +222,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.3.0", "resolved": "1.3.4",
"contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.13", "Avalonia": "0.10.13",
"Avalonia.Desktop": "0.10.13", "Avalonia.Desktop": "0.10.13",
@ -1780,7 +1780,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Flurl.Http": "3.2.0", "Flurl.Http": "3.2.0",
"Live.Avalonia": "1.3.1", "Live.Avalonia": "1.3.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
@ -1801,7 +1801,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease7", "RGB.NET.Core": "1.0.0-prerelease7",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -222,8 +222,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.3.0", "resolved": "1.3.4",
"contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.13", "Avalonia": "0.10.13",
"Avalonia.Desktop": "0.10.13", "Avalonia.Desktop": "0.10.13",
@ -1780,7 +1780,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Flurl.Http": "3.2.0", "Flurl.Http": "3.2.0",
"Live.Avalonia": "1.3.1", "Live.Avalonia": "1.3.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
@ -1801,7 +1801,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease7", "RGB.NET.Core": "1.0.0-prerelease7",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -23,19 +23,13 @@
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" /> <PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
<PackageReference Include="DynamicData" Version="7.5.4" /> <PackageReference Include="DynamicData" Version="7.5.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" /> <PackageReference Include="FluentAvaloniaUI" Version="1.3.4" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" /> <PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
<PackageReference Include="ReactiveUI" Version="17.1.50" /> <PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" /> <PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease7" /> <PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease7" />
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" /> <PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Include="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -4,5 +4,4 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.ArtemisIcon"> x:Class="Artemis.UI.Shared.ArtemisIcon">
Welcome to Avalonia!
</UserControl> </UserControl>

View File

@ -53,7 +53,7 @@ namespace Artemis.UI.Shared
{ {
SvgSource source = new(); SvgSource source = new();
source.Load(iconString); source.Load(iconString);
Content = new SvgImage {Source = source}; Content = new Image {Source = new SvgImage {Source = source}};
} }
// An URI pointing to a different kind of image // An URI pointing to a different kind of image
else else
@ -79,10 +79,8 @@ namespace Artemis.UI.Shared
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e) private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
{ {
if (Content is SvgImage svgImage) if (Content is Image image && image.Source is IDisposable disposable)
svgImage.Source?.Dispose(); disposable.Dispose();
else if (Content is Image image)
((Bitmap) image.Source).Dispose();
} }
private void InitializeComponent() private void InitializeComponent()

View File

@ -1,47 +0,0 @@
<UserControl x:Class="Artemis.UI.Shared.DefaultTypes.DataModel.Display.DefaultDataModelDisplayView"
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.Shared.DefaultTypes.DataModel.Display"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DefaultDataModelDisplayViewModel}">
<UserControl.Resources>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Prefix -->
<TextBlock Grid.Column="0"
Text="{Binding PropertyDescription.Prefix}"
Visibility="{Binding PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"
TextAlignment="Right"
Margin="0 0 5 0" />
<!-- Value -->
<TextBlock Grid.Column="1"
Text="{Binding DisplayValue, Mode=OneWay}"
HorizontalAlignment="Right"
Visibility="{Binding ShowToString, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<TextBlock Grid.Column="1"
Text="null"
FontFamily="Consolas"
HorizontalAlignment="Right"
Foreground="{DynamicResource MaterialDesignCheckBoxDisabled}"
Visibility="{Binding ShowNull, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<!-- Affix -->
<TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}"
Visibility="{Binding PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}"
Margin="5 0 0 0" />
</Grid>
</UserControl>

View File

@ -0,0 +1,48 @@
using System;
using Artemis.Core;
using Artemis.Core.LayerEffects;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// <summary>
/// Represents a profile editor command that can be used to add a layer effect to a profile element.
/// </summary>
public class AddLayerEffect : IProfileEditorCommand, IDisposable
{
private readonly RenderProfileElement _renderProfileElement;
private readonly BaseLayerEffect _layerEffect;
private bool _executed;
/// <summary>
/// Creates a new instance of the <see cref="AddLayerEffect"/> class.
/// </summary>
public AddLayerEffect(RenderProfileElement renderProfileElement, BaseLayerEffect layerEffect)
{
_renderProfileElement = renderProfileElement;
_layerEffect = layerEffect;
}
/// <inheritdoc />
public string DisplayName => "Add layer effect";
/// <inheritdoc />
public void Execute()
{
_renderProfileElement.AddLayerEffect(_layerEffect);
_executed = true;
}
/// <inheritdoc />
public void Undo()
{
_renderProfileElement.RemoveLayerEffect(_layerEffect);
_executed = false;
}
/// <inheritdoc />
public void Dispose()
{
if (!_executed)
_layerEffect.Dispose();
}
}

View File

@ -62,9 +62,9 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Direct", "type": "Direct",
"requested": "[1.3.0, )", "requested": "[1.3.4, )",
"resolved": "1.3.0", "resolved": "1.3.4",
"contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.13", "Avalonia": "0.10.13",
"Avalonia.Desktop": "0.10.13", "Avalonia.Desktop": "0.10.13",

View File

@ -238,8 +238,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.3.0", "resolved": "1.3.4",
"contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.13", "Avalonia": "0.10.13",
"Avalonia.Desktop": "0.10.13", "Avalonia.Desktop": "0.10.13",
@ -1796,7 +1796,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Flurl.Http": "3.2.0", "Flurl.Http": "3.2.0",
"Live.Avalonia": "1.3.1", "Live.Avalonia": "1.3.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
@ -1817,7 +1817,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease7", "RGB.NET.Core": "1.0.0-prerelease7",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -24,7 +24,7 @@
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" /> <PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
<PackageReference Include="DynamicData" Version="7.5.4" /> <PackageReference Include="DynamicData" Version="7.5.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" /> <PackageReference Include="FluentAvaloniaUI" Version="1.3.4" />
<PackageReference Include="Flurl.Http" Version="3.2.0" /> <PackageReference Include="Flurl.Http" Version="3.2.0" />
<PackageReference Include="Live.Avalonia" Version="1.3.1" /> <PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" /> <PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />

View File

@ -42,8 +42,6 @@ public static class ArtemisBootstrapper
_kernel.Load(modules); _kernel.Load(modules);
_kernel.UseNinjectDependencyResolver(); _kernel.UseNinjectDependencyResolver();
DataModelPicker.DataModelUIService = _kernel.Get<IDataModelUIService>();
return _kernel; return _kernel;
} }
@ -62,6 +60,7 @@ public static class ArtemisBootstrapper
_application.DataContext = rootViewModel; _application.DataContext = rootViewModel;
RxApp.DefaultExceptionHandler = Observer.Create<Exception>(DisplayUnhandledException); RxApp.DefaultExceptionHandler = Observer.Create<Exception>(DisplayUnhandledException);
DataModelPicker.DataModelUIService = _kernel.Get<IDataModelUIService>();
} }
private static void DisplayUnhandledException(Exception exception) private static void DisplayUnhandledException(Exception exception)

View File

@ -1,11 +1,7 @@
using System; using Artemis.Core;
using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using Avalonia.Media;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
@ -60,14 +56,10 @@ public class ColorGradientPropertyInputViewModel : PropertyInputViewModel<ColorG
// Make sure something actually changed // Make sure something actually changed
if (Equals(ColorGradient, _originalGradient)) if (Equals(ColorGradient, _originalGradient))
{
LayerProperty.CurrentValue = _originalGradient; LayerProperty.CurrentValue = _originalGradient;
}
else else
{
// Update the gradient for realsies, giving the command a reference to the old gradient // Update the gradient for realsies, giving the command a reference to the old gradient
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<ColorGradient>(LayerProperty, ColorGradient, _originalGradient, Time)); ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<ColorGradient>(LayerProperty, ColorGradient, _originalGradient, Time));
}
_originalGradient = null; _originalGradient = null;
} }

View File

@ -1,4 +1,5 @@
using Artemis.Core; using System;
using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using ReactiveUI.Validation.Extensions; using ReactiveUI.Validation.Extensions;
@ -11,10 +12,10 @@ public class FloatPropertyInputViewModel : PropertyInputViewModel<float>
: base(layerProperty, profileEditorService, propertyInputService) : base(layerProperty, profileEditorService, propertyInputService)
{ {
if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MinInputValue.IsNumber())
this.ValidationRule(vm => vm.InputValue, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, this.ValidationRule(vm => vm.InputValue, i => i >= Convert.ToSingle(LayerProperty.PropertyDescription.MinInputValue),
$"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); $"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}.");
if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber())
this.ValidationRule(vm => vm.InputValue, i => i <= (float) LayerProperty.PropertyDescription.MaxInputValue, this.ValidationRule(vm => vm.InputValue, i => i <= Convert.ToSingle(LayerProperty.PropertyDescription.MaxInputValue),
$"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); $"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}.");
} }
} }

View File

@ -1,4 +1,5 @@
using Artemis.Core; using System;
using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using ReactiveUI; using ReactiveUI;
@ -16,17 +17,17 @@ public class FloatRangePropertyInputViewModel : PropertyInputViewModel<FloatRang
if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MinInputValue.IsNumber())
{ {
this.ValidationRule(vm => vm.Start, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, this.ValidationRule(vm => vm.Start, i => i >= Convert.ToSingle(LayerProperty.PropertyDescription.MinInputValue),
$"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); $"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}.");
this.ValidationRule(vm => vm.End, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, this.ValidationRule(vm => vm.End, i => i >= Convert.ToSingle(LayerProperty.PropertyDescription.MinInputValue),
$"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); $"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}.");
} }
if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber())
{ {
this.ValidationRule(vm => vm.Start, i => i < (float) LayerProperty.PropertyDescription.MaxInputValue, this.ValidationRule(vm => vm.Start, i => i < Convert.ToSingle(LayerProperty.PropertyDescription.MaxInputValue),
$"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); $"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}.");
this.ValidationRule(vm => vm.End, i => i < (float) LayerProperty.PropertyDescription.MaxInputValue, this.ValidationRule(vm => vm.End, i => i < Convert.ToSingle(LayerProperty.PropertyDescription.MaxInputValue),
$"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); $"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}.");
} }
} }

View File

@ -1,4 +1,5 @@
using Artemis.Core; using System;
using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using ReactiveUI.Validation.Extensions; using ReactiveUI.Validation.Extensions;
@ -11,10 +12,10 @@ public class IntPropertyInputViewModel : PropertyInputViewModel<int>
: base(layerProperty, profileEditorService, propertyInputService) : base(layerProperty, profileEditorService, propertyInputService)
{ {
if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MinInputValue.IsNumber())
this.ValidationRule(vm => vm.InputValue, i => i >= (int) LayerProperty.PropertyDescription.MinInputValue, this.ValidationRule(vm => vm.InputValue, i => i >= Convert.ToInt32(LayerProperty.PropertyDescription.MinInputValue),
$"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); $"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}.");
if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber())
this.ValidationRule(vm => vm.InputValue, i => i < (int) LayerProperty.PropertyDescription.MaxInputValue, this.ValidationRule(vm => vm.InputValue, i => i < Convert.ToInt32(LayerProperty.PropertyDescription.MaxInputValue),
$"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); $"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}.");
} }
} }

View File

@ -1,4 +1,5 @@
using Artemis.Core; using System;
using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using ReactiveUI; using ReactiveUI;
@ -16,17 +17,17 @@ public class IntRangePropertyInputViewModel : PropertyInputViewModel<IntRange>
if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MinInputValue.IsNumber())
{ {
this.ValidationRule(vm => vm.Start, i => i >= (int) LayerProperty.PropertyDescription.MinInputValue, this.ValidationRule(vm => vm.Start, i => i >= Convert.ToInt32(LayerProperty.PropertyDescription.MinInputValue),
$"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); $"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}.");
this.ValidationRule(vm => vm.End, i => i >= (int)LayerProperty.PropertyDescription.MinInputValue, this.ValidationRule(vm => vm.End, i => i >= Convert.ToInt32(LayerProperty.PropertyDescription.MinInputValue),
$"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); $"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}.");
} }
if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber())
{ {
this.ValidationRule(vm => vm.Start, i => i < (int)LayerProperty.PropertyDescription.MaxInputValue, this.ValidationRule(vm => vm.Start, i => i < Convert.ToInt32(LayerProperty.PropertyDescription.MaxInputValue),
$"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); $"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}.");
this.ValidationRule(vm => vm.End, i => i < (int) LayerProperty.PropertyDescription.MaxInputValue, this.ValidationRule(vm => vm.End, i => i < Convert.ToInt32(LayerProperty.PropertyDescription.MaxInputValue),
$"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); $"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}.");
} }
} }

View File

@ -77,7 +77,7 @@ namespace Artemis.UI.Screens.Device
await _windowService.CreateContentDialog() await _windowService.CreateContentDialog()
.WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input") .WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input")
.WithViewModel<DeviceDetectInputViewModel>(out var viewModel, ("device", Device)) .WithViewModel<DeviceDetectInputViewModel>(out DeviceDetectInputViewModel? viewModel, ("device", Device))
.WithCloseButtonText("Cancel") .WithCloseButtonText("Cancel")
.ShowAsync(); .ShowAsync();

View File

@ -0,0 +1,64 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:layerEffects="clr-namespace:Artemis.Core.LayerEffects;assembly=Artemis.Core"
xmlns:dialogs="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Dialogs"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Dialogs.AddEffectView"
x:DataType="dialogs:AddEffectViewModel"
Width="500">
<Grid RowDefinitions="Auto,*">
<TextBox Name="SearchBox" Text="{CompiledBinding SearchText}" Margin="0 0 0 15" Watermark="Search"></TextBox>
<ListBox Name="EffectDescriptorsList"
Grid.Row="1"
Items="{CompiledBinding LayerEffectDescriptors}"
IsVisible="{CompiledBinding LayerEffectDescriptors.Count}"
Height="300">
<ListBox.DataTemplates>
<DataTemplate DataType="{x:Type layerEffects:LayerEffectDescriptor}">
<Grid RowDefinitions="Auto,*"
ColumnDefinitions="Auto,Auto"
Background="Transparent"
PointerReleased="InputElement_OnPointerReleased"
Margin="0 4"
VerticalAlignment="Center">
<shared:ArtemisIcon Grid.Column="0"
Grid.RowSpan="2"
Icon="{CompiledBinding Icon}"
Width="24"
Height="24"
VerticalAlignment="Center"
Margin="0 0 15 0" />
<TextBlock Grid.Column="1"
Grid.Row="0"
Classes="BodyStrongTextBlockStyle"
Text="{CompiledBinding DisplayName}"
VerticalAlignment="Bottom"
Width="450"
TextWrapping="Wrap" />
<TextBlock Grid.Column="1"
Grid.Row="1"
Foreground="{DynamicResource TextFillColorSecondary}"
Text="{CompiledBinding Description}"
VerticalAlignment="Top"
Width="450"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
<Grid Grid.Row="1" Height="300">
<StackPanel VerticalAlignment="Center"
Spacing="20"
IsVisible="{CompiledBinding !LayerEffectDescriptors.Count}">
<avalonia:MaterialIcon Kind="CloseCircle" Width="32" Height="32"></avalonia:MaterialIcon>
<TextBlock Classes="h5" TextAlignment="Center">None of the effects match your search</TextBlock>
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@ -0,0 +1,28 @@
using Artemis.Core.LayerEffects;
using Avalonia;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Dialogs;
public class AddEffectView : ReactiveUserControl<AddEffectViewModel>
{
public AddEffectView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (sender is not IDataContextProvider {DataContext: LayerEffectDescriptor descriptor} || ViewModel == null)
return;
ViewModel?.AddLayerEffect(descriptor);
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.Core.LayerEffects;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Dialogs;
public class AddEffectViewModel : ContentDialogViewModelBase
{
private readonly RenderProfileElement _renderProfileElement;
private readonly IProfileEditorService _profileEditorService;
private string? _searchText;
public AddEffectViewModel(RenderProfileElement renderProfileElement, IProfileEditorService profileEditorService, ILayerEffectService layerEffectService)
{
_renderProfileElement = renderProfileElement;
_profileEditorService = profileEditorService;
SourceList<LayerEffectDescriptor> layerEffectSourceList = new();
layerEffectSourceList.AddRange(layerEffectService.GetLayerEffects());
IObservable<Func<LayerEffectDescriptor, bool>> layerEffectFilter = this.WhenAnyValue(vm => vm.SearchText).Select(CreatePredicate);
layerEffectSourceList.Connect()
.Filter(layerEffectFilter)
.Bind(out ReadOnlyObservableCollection<LayerEffectDescriptor> layerEffectDescriptors)
.Subscribe();
LayerEffectDescriptors = layerEffectDescriptors;
}
public ReadOnlyObservableCollection<LayerEffectDescriptor> LayerEffectDescriptors { get; }
public string? SearchText
{
get => _searchText;
set => this.RaiseAndSetIfChanged(ref _searchText, value);
}
public void AddLayerEffect(LayerEffectDescriptor descriptor)
{
BaseLayerEffect layerEffect = descriptor.CreateInstance(_renderProfileElement, null);
_profileEditorService.ExecuteCommand(new AddLayerEffect(_renderProfileElement, layerEffect));
ContentDialog?.Hide();
}
private Func<LayerEffectDescriptor, bool> CreatePredicate(string? search)
{
if (string.IsNullOrWhiteSpace(search))
return _ => true;
search = search.Trim();
return data => data.DisplayName.Contains(search, StringComparison.InvariantCultureIgnoreCase) ||
data.Description.Contains(search, StringComparison.InvariantCultureIgnoreCase);
}
}

View File

@ -29,6 +29,7 @@
Name="TreeScrollViewer" Name="TreeScrollViewer"
Offset="{CompiledBinding #TimelineScrollViewer.Offset, Mode=OneWay}" Offset="{CompiledBinding #TimelineScrollViewer.Offset, Mode=OneWay}"
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}"> Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
<Grid RowDefinitions="*,Auto">
<ItemsControl Items="{CompiledBinding PropertyGroupViewModels}" Padding="0 0 8 0"> <ItemsControl Items="{CompiledBinding PropertyGroupViewModels}" Padding="0 0 8 0">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{CompiledBinding Children}"> <TreeDataTemplate DataType="{x:Type local:PropertyGroupViewModel}" ItemsSource="{CompiledBinding Children}">
@ -36,6 +37,14 @@
</TreeDataTemplate> </TreeDataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<Button Grid.Row="1"
Command="{CompiledBinding AddEffect}"
Margin="4"
VerticalAlignment="Bottom"
HorizontalAlignment="Right">
Add new effect
</Button>
</Grid>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
@ -12,8 +13,12 @@ using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Playback; using Artemis.UI.Screens.ProfileEditor.Playback;
using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding; using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
using Artemis.UI.Screens.ProfileEditor.Properties.Dialogs;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using ReactiveUI; using ReactiveUI;
@ -23,6 +28,8 @@ public class PropertiesViewModel : ActivatableViewModelBase
{ {
private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedPropertyViewModels; private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedPropertyViewModels;
private readonly IDataBindingVmFactory _dataBindingVmFactory; private readonly IDataBindingVmFactory _dataBindingVmFactory;
private readonly IWindowService _windowService;
private readonly ILayerEffectService _layerEffectService;
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
@ -38,18 +45,22 @@ public class PropertiesViewModel : ActivatableViewModelBase
ISettingsService settingsService, ISettingsService settingsService,
ILayerPropertyVmFactory layerPropertyVmFactory, ILayerPropertyVmFactory layerPropertyVmFactory,
IDataBindingVmFactory dataBindingVmFactory, IDataBindingVmFactory dataBindingVmFactory,
IWindowService windowService,
ILayerEffectService layerEffectService,
PlaybackViewModel playbackViewModel) PlaybackViewModel playbackViewModel)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_settingsService = settingsService; _settingsService = settingsService;
_layerPropertyVmFactory = layerPropertyVmFactory; _layerPropertyVmFactory = layerPropertyVmFactory;
_dataBindingVmFactory = dataBindingVmFactory; _dataBindingVmFactory = dataBindingVmFactory;
_windowService = windowService;
_layerEffectService = layerEffectService;
_cachedPropertyViewModels = new Dictionary<LayerPropertyGroup, PropertyGroupViewModel>(); _cachedPropertyViewModels = new Dictionary<LayerPropertyGroup, PropertyGroupViewModel>();
PropertyGroupViewModels = new ObservableCollection<PropertyGroupViewModel>(); PropertyGroupViewModels = new ObservableCollection<PropertyGroupViewModel>();
PlaybackViewModel = playbackViewModel; PlaybackViewModel = playbackViewModel;
TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels); TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels);
AddEffect = ReactiveCommand.CreateFromTask(ExecuteAddEffect);
// React to service profile element changes as long as the VM is active // React to service profile element changes as long as the VM is active
this.WhenActivated(d => this.WhenActivated(d =>
{ {
@ -86,6 +97,7 @@ public class PropertiesViewModel : ActivatableViewModelBase
public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; } public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; }
public PlaybackViewModel PlaybackViewModel { get; } public PlaybackViewModel PlaybackViewModel { get; }
public TimelineViewModel TimelineViewModel { get; } public TimelineViewModel TimelineViewModel { get; }
public ReactiveCommand<Unit, Unit> AddEffect { get; }
public DataBindingViewModel? DataBindingViewModel public DataBindingViewModel? DataBindingViewModel
{ {
@ -102,6 +114,17 @@ public class PropertiesViewModel : ActivatableViewModelBase
public IObservable<bool> Playing => _profileEditorService.Playing; public IObservable<bool> Playing => _profileEditorService.Playing;
public PluginSetting<double> PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0); public PluginSetting<double> PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0);
private async Task ExecuteAddEffect()
{
if (ProfileElement == null)
return;
await _windowService.CreateContentDialog()
.WithTitle("Add layer effect")
.WithViewModel(out AddEffectViewModel _, ("renderProfileElement", ProfileElement))
.WithCloseButtonText("Cancel")
.ShowAsync();
}
private void UpdatePropertyGroups() private void UpdatePropertyGroups()
{ {

View File

@ -38,18 +38,36 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable
PopulateChildren(); PopulateChildren();
} }
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerBrush layerBrush)
BaseLayerBrush layerBrush)
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
{ {
_layerPropertyVmFactory = layerPropertyVmFactory;
_propertyInputService = propertyInputService;
LayerBrush = layerBrush; LayerBrush = layerBrush;
Children = new ObservableCollection<ViewModelBase>();
LayerPropertyGroup = layerPropertyGroup;
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren();
} }
public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerEffect layerEffect)
BaseLayerEffect layerEffect)
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
{ {
_layerPropertyVmFactory = layerPropertyVmFactory;
_propertyInputService = propertyInputService;
LayerEffect = layerEffect; LayerEffect = layerEffect;
Children = new ObservableCollection<ViewModelBase>();
LayerPropertyGroup = layerPropertyGroup;
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
_isVisible = !LayerPropertyGroup.IsHidden;
PopulateChildren();
} }
public ObservableCollection<ViewModelBase> Children { get; } public ObservableCollection<ViewModelBase> Children { get; }

View File

@ -53,7 +53,7 @@
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Margin="0 5" Margin="0 5"
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.General}}"> IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.General}}">
<avalonia:MaterialIcon Kind="HammerWrench" Margin="0 -1 5 0" /> <avalonia:MaterialIcon Kind="HammerWrench" Margin="0 0 5 0" />
<TextBlock ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}">General</TextBlock> <TextBlock ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}">General</TextBlock>
</StackPanel> </StackPanel>
@ -61,22 +61,23 @@
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Margin="0 5" Margin="0 5"
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.Transform}}"> IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.Transform}}">
<avalonia:MaterialIcon Kind="TransitConnectionVariant" Margin="0 -1 5 0" /> <avalonia:MaterialIcon Kind="TransitConnectionVariant" Margin="0 0 5 0" />
<TextBlock ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}">Transform</TextBlock> <TextBlock ToolTip.Tip="{Binding LayerPropertyGroup.GroupDescription.Description}">Transform</TextBlock>
</StackPanel> </StackPanel>
<!-- Type: LayerBrushRoot --> <!-- Type: LayerBrushRoot -->
<Grid IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerBrushRoot}}" <Grid IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerBrushRoot}}"
Height="29"
ColumnDefinitions="Auto,Auto,Auto,*"> ColumnDefinitions="Auto,Auto,Auto,*">
<shared:ArtemisIcon Grid.Column="0" <shared:ArtemisIcon Grid.Column="0"
Icon="{Binding LayerBrush.Descriptor.Icon}" Icon="{Binding LayerBrush.Descriptor.Icon}"
Width="16" Width="16"
Height="16" Height="16"
Margin="0 5 5 0" /> Margin="0 0 5 0" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
ToolTip.Tip="{Binding LayerBrush.Descriptor.Description}" ToolTip.Tip="{Binding LayerBrush.Descriptor.Description}"
Margin="0 5 0 0"> Margin="0 5 5 0">
Brush -&#160; Brush -
</TextBlock> </TextBlock>
<TextBlock Grid.Column="2" <TextBlock Grid.Column="2"
Text="{Binding LayerBrush.Descriptor.DisplayName}" Text="{Binding LayerBrush.Descriptor.DisplayName}"
@ -101,8 +102,8 @@
</Grid> </Grid>
<!-- Type: LayerEffectRoot --> <!-- Type: LayerEffectRoot -->
<Grid Height="24" <Grid IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerEffectRoot}}"
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerEffectRoot}}" Height="29"
ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto"> ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto">
<shared:ArtemisIcon <shared:ArtemisIcon
Grid.Column="0" Grid.Column="0"
@ -110,7 +111,7 @@
Icon="{Binding LayerEffect.Descriptor.Icon}" Icon="{Binding LayerEffect.Descriptor.Icon}"
Width="16" Width="16"
Height="16" Height="16"
Margin="0 5 5 0" Margin="0 0 5 0"
Background="Transparent" /> Background="Transparent" />
<TextBlock Grid.Column="1" ToolTip.Tip="{Binding LayerEffect.Descriptor.Description}" Margin="0 5 0 0"> <TextBlock Grid.Column="1" ToolTip.Tip="{Binding LayerEffect.Descriptor.Description}" Margin="0 5 0 0">
Effect Effect
@ -133,18 +134,23 @@
Margin="0 5" Margin="0 5"
IsVisible="{Binding LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> IsVisible="{Binding LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<StackPanel Grid.Column="5" Orientation="Horizontal"> <StackPanel Grid.Column="5" Orientation="Horizontal" Spacing="2">
<ToggleButton <Button Classes="icon-button"
Classes="icon-button"
ToolTip.Tip="Toggle suspended state" ToolTip.Tip="Toggle suspended state"
Width="18" Width="24"
Height="18" Height="24"
IsChecked="{Binding !LayerEffect.Suspended}" VerticalAlignment="Center"
VerticalAlignment="Center" Padding="-25"
Margin="5 0"
Command="{Binding SuspendedToggled}"> Command="{Binding SuspendedToggled}">
<avalonia:MaterialIcon Kind="Eye" Height="13" Width="13" /> <avalonia:MaterialIcon Kind="EyeOff" Height="16" Width="16" IsVisible="True" />
</ToggleButton> </Button>
<Button Classes="icon-button"
ToolTip.Tip="Toggle suspended state"
Width="24"
Height="24"
VerticalAlignment="Center"
Command="{Binding SuspendedToggled}">
<avalonia:MaterialIcon Kind="Eye" Height="16" Width="16" IsVisible="True" />
</Button>
<Button Classes="icon-button" <Button Classes="icon-button"
ToolTip.Tip="Rename" ToolTip.Tip="Rename"
Width="24" Width="24"

View File

@ -268,7 +268,7 @@
Items="{CompiledBinding RenderScales}"> Items="{CompiledBinding RenderScales}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding [0]}" /> <TextBlock Text="{CompiledBinding Display}" />
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
@ -294,7 +294,7 @@
Items="{CompiledBinding TargetFrameRates}"> Items="{CompiledBinding TargetFrameRates}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding [0]}" /> <TextBlock Text="{CompiledBinding Display}" />
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>

View File

@ -80,21 +80,21 @@ namespace Artemis.UI.Screens.Settings
"Vulkan" "Vulkan"
}; };
public ObservableCollection<(string, double)> RenderScales { get; } = new() public ObservableCollection<RenderSettingViewModel> RenderScales { get; } = new()
{ {
new ValueTuple<string, double>("25%", 0.25), new RenderSettingViewModel("25%", 0.25),
new ValueTuple<string, double>("50%", 0.5), new RenderSettingViewModel("50%", 0.5),
new ValueTuple<string, double>("100%", 1) new RenderSettingViewModel("100%", 1)
}; };
public ObservableCollection<(string, int)> TargetFrameRates { get; } = new() public ObservableCollection<RenderSettingViewModel> TargetFrameRates { get; } = new()
{ {
new ValueTuple<string, int>("10 FPS", 10), new RenderSettingViewModel("10 FPS", 10),
new ValueTuple<string, int>("20 FPS", 20), new RenderSettingViewModel("20 FPS", 20),
new ValueTuple<string, int>("30 FPS", 30), new RenderSettingViewModel("30 FPS", 30),
new ValueTuple<string, int>("45 FPS", 45), new RenderSettingViewModel("45 FPS", 45),
new ValueTuple<string, int>("60 FPS (lol)", 60), new RenderSettingViewModel("60 FPS (lol)", 60),
new ValueTuple<string, int>("144 FPS (omegalol)", 144) new RenderSettingViewModel("144 FPS (omegalol)", 144)
}; };
public LayerBrushDescriptor? SelectedLayerBrushDescriptor public LayerBrushDescriptor? SelectedLayerBrushDescriptor
@ -106,21 +106,23 @@ namespace Artemis.UI.Screens.Settings
} }
} }
public (string, double)? SelectedRenderScale public RenderSettingViewModel? SelectedRenderScale
{ {
get => RenderScales.FirstOrDefault(s => Math.Abs(s.Item2 - CoreRenderScale.Value) < 0.01); get => RenderScales.FirstOrDefault(s => Math.Abs(s.Value - CoreRenderScale.Value) < 0.01);
set set
{ {
if (value != null) CoreRenderScale.Value = value.Value.Item2; if (value != null)
CoreRenderScale.Value = value.Value;
} }
} }
public (string, int)? SelectedTargetFrameRate public RenderSettingViewModel? SelectedTargetFrameRate
{ {
get => TargetFrameRates.FirstOrDefault(s => s.Item2 == CoreTargetFrameRate.Value); get => TargetFrameRates.FirstOrDefault(s => Math.Abs(s.Value - CoreTargetFrameRate.Value) < 0.01);
set set
{ {
if (value != null) CoreTargetFrameRate.Value = value.Value.Item2; if (value != null)
CoreTargetFrameRate.Value = (int) value.Value;
} }
} }

View File

@ -0,0 +1,13 @@
namespace Artemis.UI.Screens.Settings;
public class RenderSettingViewModel
{
public RenderSettingViewModel(string display, double value)
{
Display = display;
Value = value;
}
public string Display { get; }
public double Value { get; }
}

View File

@ -96,9 +96,9 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Direct", "type": "Direct",
"requested": "[1.3.0, )", "requested": "[1.3.4, )",
"resolved": "1.3.0", "resolved": "1.3.4",
"contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.13", "Avalonia": "0.10.13",
"Avalonia.Desktop": "0.10.13", "Avalonia.Desktop": "0.10.13",
@ -1788,7 +1788,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease7", "RGB.NET.Core": "1.0.0-prerelease7",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -252,8 +252,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.3.0", "resolved": "1.3.4",
"contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.13", "Avalonia": "0.10.13",
"Avalonia.Desktop": "0.10.13", "Avalonia.Desktop": "0.10.13",
@ -1738,7 +1738,7 @@
"Avalonia.Svg.Skia": "0.10.12", "Avalonia.Svg.Skia": "0.10.12",
"Avalonia.Xaml.Behaviors": "0.10.13.2", "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4", "DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0", "FluentAvaloniaUI": "1.3.4",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease7", "RGB.NET.Core": "1.0.0-prerelease7",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -33,42 +33,50 @@ Global
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|Any CPU.ActiveCfg = Release|x64 {E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|Any CPU.ActiveCfg = Release|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|x64.ActiveCfg = Release|x64 {E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|x64.ActiveCfg = Release|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|x64.Build.0 = Release|x64 {E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|x64.Build.0 = Release|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Debug|Any CPU.Build.0 = Debug|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|Any CPU.ActiveCfg = Debug|x64 {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|Any CPU.ActiveCfg = Debug|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|x64.ActiveCfg = Debug|x64 {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|x64.ActiveCfg = Debug|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|x64.Build.0 = Debug|x64 {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|x64.Build.0 = Debug|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|Any CPU.ActiveCfg = Release|x64 {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|Any CPU.ActiveCfg = Release|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|x64.ActiveCfg = Release|x64 {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|x64.ActiveCfg = Release|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|x64.Build.0 = Release|x64 {9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|x64.Build.0 = Release|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|Any CPU.Build.0 = Debug|x64
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|Any CPU.ActiveCfg = Debug|x64 {035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|Any CPU.ActiveCfg = Debug|x64
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|x64.ActiveCfg = Debug|x64 {035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|x64.ActiveCfg = Debug|x64
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|x64.Build.0 = Debug|x64 {035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|x64.Build.0 = Debug|x64
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|Any CPU.ActiveCfg = Release|x64 {035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|Any CPU.ActiveCfg = Release|x64
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|x64.ActiveCfg = Release|x64 {035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|x64.ActiveCfg = Release|x64
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|Any CPU.Build.0 = Debug|x64
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|Any CPU.ActiveCfg = Debug|x64 {05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|Any CPU.ActiveCfg = Debug|x64
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|x64.ActiveCfg = Debug|x64 {05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|x64.ActiveCfg = Debug|x64
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|x64.Build.0 = Debug|x64 {05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|x64.Build.0 = Debug|x64
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|Any CPU.ActiveCfg = Release|x64 {05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|Any CPU.ActiveCfg = Release|x64
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|x64.ActiveCfg = Release|x64 {05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|x64.ActiveCfg = Release|x64
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|Any CPU.Build.0 = Debug|x64
{DE45A288-9320-461F-BE2A-26DFE3817216}.Debug|Any CPU.ActiveCfg = Debug|x64 {DE45A288-9320-461F-BE2A-26DFE3817216}.Debug|Any CPU.ActiveCfg = Debug|x64
{DE45A288-9320-461F-BE2A-26DFE3817216}.Debug|x64.ActiveCfg = Debug|x64 {DE45A288-9320-461F-BE2A-26DFE3817216}.Debug|x64.ActiveCfg = Debug|x64
{DE45A288-9320-461F-BE2A-26DFE3817216}.Debug|x64.Build.0 = Debug|x64 {DE45A288-9320-461F-BE2A-26DFE3817216}.Debug|x64.Build.0 = Debug|x64
{DE45A288-9320-461F-BE2A-26DFE3817216}.Release|Any CPU.ActiveCfg = Release|x64 {DE45A288-9320-461F-BE2A-26DFE3817216}.Release|Any CPU.ActiveCfg = Release|x64
{DE45A288-9320-461F-BE2A-26DFE3817216}.Release|x64.ActiveCfg = Release|x64 {DE45A288-9320-461F-BE2A-26DFE3817216}.Release|x64.ActiveCfg = Release|x64
{DE45A288-9320-461F-BE2A-26DFE3817216}.Debug|Any CPU.Build.0 = Debug|x64
{9012C8E2-3BEC-42F5-8270-7352A5922B04}.Debug|Any CPU.ActiveCfg = Debug|x64 {9012C8E2-3BEC-42F5-8270-7352A5922B04}.Debug|Any CPU.ActiveCfg = Debug|x64
{9012C8E2-3BEC-42F5-8270-7352A5922B04}.Debug|x64.ActiveCfg = Debug|x64 {9012C8E2-3BEC-42F5-8270-7352A5922B04}.Debug|x64.ActiveCfg = Debug|x64
{9012C8E2-3BEC-42F5-8270-7352A5922B04}.Debug|x64.Build.0 = Debug|x64 {9012C8E2-3BEC-42F5-8270-7352A5922B04}.Debug|x64.Build.0 = Debug|x64
{9012C8E2-3BEC-42F5-8270-7352A5922B04}.Release|Any CPU.ActiveCfg = Release|x64 {9012C8E2-3BEC-42F5-8270-7352A5922B04}.Release|Any CPU.ActiveCfg = Release|x64
{9012C8E2-3BEC-42F5-8270-7352A5922B04}.Release|x64.ActiveCfg = Release|x64 {9012C8E2-3BEC-42F5-8270-7352A5922B04}.Release|x64.ActiveCfg = Release|x64
{9012C8E2-3BEC-42F5-8270-7352A5922B04}.Debug|Any CPU.Build.0 = Debug|x64
{2F5F16DC-FACF-4559-9882-37C2949814C7}.Debug|Any CPU.ActiveCfg = Debug|x64 {2F5F16DC-FACF-4559-9882-37C2949814C7}.Debug|Any CPU.ActiveCfg = Debug|x64
{2F5F16DC-FACF-4559-9882-37C2949814C7}.Debug|x64.ActiveCfg = Debug|x64 {2F5F16DC-FACF-4559-9882-37C2949814C7}.Debug|x64.ActiveCfg = Debug|x64
{2F5F16DC-FACF-4559-9882-37C2949814C7}.Debug|x64.Build.0 = Debug|x64 {2F5F16DC-FACF-4559-9882-37C2949814C7}.Debug|x64.Build.0 = Debug|x64
{2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|Any CPU.ActiveCfg = Release|x64 {2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|Any CPU.ActiveCfg = Release|x64
{2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|x64.ActiveCfg = Release|x64 {2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|x64.ActiveCfg = Release|x64
{2F5F16DC-FACF-4559-9882-37C2949814C7}.Debug|Any CPU.Build.0 = Debug|x64
{412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|Any CPU.ActiveCfg = Debug|x64 {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|Any CPU.ActiveCfg = Debug|x64
{412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|x64.ActiveCfg = Debug|x64 {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|x64.ActiveCfg = Debug|x64
{412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|x64.Build.0 = Debug|x64 {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|x64.Build.0 = Debug|x64
{412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|Any CPU.ActiveCfg = Release|x64 {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|Any CPU.ActiveCfg = Release|x64
{412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|x64.ActiveCfg = Release|x64 {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|x64.ActiveCfg = Release|x64
{412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|Any CPU.Build.0 = Debug|x64
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE