1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2024-11-14 20:35:46 +01:00
commit 4c6ca9b511
75 changed files with 470 additions and 2021 deletions

View File

@ -39,8 +39,9 @@
<PackageReference Include="DryIoc.dll" /> <PackageReference Include="DryIoc.dll" />
<PackageReference Include="EmbedIO" /> <PackageReference Include="EmbedIO" />
<PackageReference Include="HidSharp" /> <PackageReference Include="HidSharp" />
<PackageReference Include="HPPH.SkiaSharp" />
<PackageReference Include="Humanizer.Core" /> <PackageReference Include="Humanizer.Core" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All"/> <PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
<PackageReference Include="McMaster.NETCore.Plugins" /> <PackageReference Include="McMaster.NETCore.Plugins" />
<PackageReference Include="RGB.NET.Core" /> <PackageReference Include="RGB.NET.Core" />
<PackageReference Include="RGB.NET.Layout" /> <PackageReference Include="RGB.NET.Layout" />

View File

@ -1,167 +0,0 @@
using SkiaSharp;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Artemis.Core.ColorScience;
internal readonly struct ColorRanges
{
public readonly byte RedRange;
public readonly byte GreenRange;
public readonly byte BlueRange;
public ColorRanges(byte redRange, byte greenRange, byte blueRange)
{
this.RedRange = redRange;
this.GreenRange = greenRange;
this.BlueRange = blueRange;
}
}
internal readonly struct ColorCube
{
private const int BYTES_PER_COLOR = 4;
private static readonly int ELEMENTS_PER_VECTOR = Vector<byte>.Count / BYTES_PER_COLOR;
private static readonly int BYTES_PER_VECTOR = ELEMENTS_PER_VECTOR * BYTES_PER_COLOR;
private readonly int _from;
private readonly int _length;
private readonly SortTarget _currentOrder = SortTarget.None;
public ColorCube(in Span<SKColor> fullColorList, int from, int length, SortTarget preOrdered)
{
this._from = from;
this._length = length;
if (length < 2) return;
Span<SKColor> colors = fullColorList.Slice(from, length);
ColorRanges colorRanges = GetColorRanges(colors);
if ((colorRanges.RedRange > colorRanges.GreenRange) && (colorRanges.RedRange > colorRanges.BlueRange))
{
if (preOrdered != SortTarget.Red)
QuantizerSort.SortRed(colors);
_currentOrder = SortTarget.Red;
}
else if (colorRanges.GreenRange > colorRanges.BlueRange)
{
if (preOrdered != SortTarget.Green)
QuantizerSort.SortGreen(colors);
_currentOrder = SortTarget.Green;
}
else
{
if (preOrdered != SortTarget.Blue)
QuantizerSort.SortBlue(colors);
_currentOrder = SortTarget.Blue;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ColorRanges GetColorRanges(in ReadOnlySpan<SKColor> colors)
{
if (Vector.IsHardwareAccelerated && (colors.Length >= Vector<byte>.Count))
{
int chunks = colors.Length / ELEMENTS_PER_VECTOR;
int vectorElements = (chunks * ELEMENTS_PER_VECTOR);
int missingElements = colors.Length - vectorElements;
Vector<byte> max = Vector<byte>.Zero;
Vector<byte> min = new(byte.MaxValue);
foreach (Vector<byte> currentVector in MemoryMarshal.Cast<SKColor, Vector<byte>>(colors[..vectorElements]))
{
max = Vector.Max(max, currentVector);
min = Vector.Min(min, currentVector);
}
byte redMin = byte.MaxValue;
byte redMax = byte.MinValue;
byte greenMin = byte.MaxValue;
byte greenMax = byte.MinValue;
byte blueMin = byte.MaxValue;
byte blueMax = byte.MinValue;
for (int i = 0; i < BYTES_PER_VECTOR; i += BYTES_PER_COLOR)
{
if (min[i + 2] < redMin) redMin = min[i + 2];
if (max[i + 2] > redMax) redMax = max[i + 2];
if (min[i + 1] < greenMin) greenMin = min[i + 1];
if (max[i + 1] > greenMax) greenMax = max[i + 1];
if (min[i] < blueMin) blueMin = min[i];
if (max[i] > blueMax) blueMax = max[i];
}
for (int i = 0; i < missingElements; i++)
{
SKColor color = colors[^(i + 1)];
if (color.Red < redMin) redMin = color.Red;
if (color.Red > redMax) redMax = color.Red;
if (color.Green < greenMin) greenMin = color.Green;
if (color.Green > greenMax) greenMax = color.Green;
if (color.Blue < blueMin) blueMin = color.Blue;
if (color.Blue > blueMax) blueMax = color.Blue;
}
return new ColorRanges((byte)(redMax - redMin), (byte)(greenMax - greenMin), (byte)(blueMax - blueMin));
}
else
{
byte redMin = byte.MaxValue;
byte redMax = byte.MinValue;
byte greenMin = byte.MaxValue;
byte greenMax = byte.MinValue;
byte blueMin = byte.MaxValue;
byte blueMax = byte.MinValue;
foreach (SKColor color in colors)
{
if (color.Red < redMin) redMin = color.Red;
if (color.Red > redMax) redMax = color.Red;
if (color.Green < greenMin) greenMin = color.Green;
if (color.Green > greenMax) greenMax = color.Green;
if (color.Blue < blueMin) blueMin = color.Blue;
if (color.Blue > blueMax) blueMax = color.Blue;
}
return new ColorRanges((byte)(redMax - redMin), (byte)(greenMax - greenMin), (byte)(blueMax - blueMin));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Split(in Span<SKColor> fullColorList, out ColorCube a, out ColorCube b)
{
Span<SKColor> colors = fullColorList.Slice(_from, _length);
int median = colors.Length / 2;
a = new ColorCube(fullColorList, _from, median, _currentOrder);
b = new ColorCube(fullColorList, _from + median, colors.Length - median, _currentOrder);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal SKColor GetAverageColor(in ReadOnlySpan<SKColor> fullColorList)
{
ReadOnlySpan<SKColor> colors = fullColorList.Slice(_from, _length);
int r = 0, g = 0, b = 0;
foreach (SKColor color in colors)
{
r += color.Red;
g += color.Green;
b += color.Blue;
}
return new SKColor(
(byte)(r / colors.Length),
(byte)(g / colors.Length),
(byte)(b / colors.Length)
);
}
}

View File

@ -1,7 +1,9 @@
using SkiaSharp; using HPPH;
using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
namespace Artemis.Core.ColorScience; namespace Artemis.Core.ColorScience;
@ -10,13 +12,27 @@ namespace Artemis.Core.ColorScience;
/// </summary> /// </summary>
public static class ColorQuantizer public static class ColorQuantizer
{ {
/// <inheritdoc cref="Quantize(Span{SKColor}, int)"/>
[Obsolete("Use Quantize(Span<SKColor> colors, int amount) in-parameter instead")]
public static SKColor[] Quantize(in Span<SKColor> colors, int amount)
{
return Quantize(colors, amount);
}
/// <inheritdoc cref="QuantizeSplit(Span{SKColor}, int)"/>
[Obsolete("Use QuantizeSplit(Span<SKColor> colors, int splits) without the in-parameter instead")]
public static SKColor[] QuantizeSplit(in Span<SKColor> colors, int splits)
{
return QuantizeSplit(colors, splits);
}
/// <summary> /// <summary>
/// Quantizes a span of colors into the desired amount of representative colors. /// Quantizes a span of colors into the desired amount of representative colors.
/// </summary> /// </summary>
/// <param name="colors">The colors to quantize</param> /// <param name="colors">The colors to quantize</param>
/// <param name="amount">How many colors to return. Must be a power of two.</param> /// <param name="amount">How many colors to return. Must be a power of two.</param>
/// <returns><paramref name="amount"/> colors.</returns> /// <returns><paramref name="amount"/> colors.</returns>
public static SKColor[] Quantize(in Span<SKColor> colors, int amount) public static SKColor[] Quantize(Span<SKColor> colors, int amount)
{ {
if (!BitOperations.IsPow2(amount)) if (!BitOperations.IsPow2(amount))
throw new ArgumentException("Must be power of two", nameof(amount)); throw new ArgumentException("Must be power of two", nameof(amount));
@ -24,38 +40,19 @@ public static class ColorQuantizer
int splits = BitOperations.Log2((uint)amount); int splits = BitOperations.Log2((uint)amount);
return QuantizeSplit(colors, splits); return QuantizeSplit(colors, splits);
} }
/// <summary> /// <summary>
/// Quantizes a span of colors, splitting the average <paramref name="splits"/> number of times. /// Quantizes a span of colors, splitting the average <paramref name="splits"/> number of times.
/// </summary> /// </summary>
/// <param name="colors">The colors to quantize</param> /// <param name="colors">The colors to quantize</param>
/// <param name="splits">How many splits to execute. Each split doubles the number of colors returned.</param> /// <param name="splits">How many splits to execute. Each split doubles the number of colors returned.</param>
/// <returns>Up to (2 ^ <paramref name="splits"/>) number of colors.</returns> /// <returns>Up to (2 ^ <paramref name="splits"/>) number of colors.</returns>
public static SKColor[] QuantizeSplit(in Span<SKColor> colors, int splits) public static SKColor[] QuantizeSplit(Span<SKColor> colors, int splits)
{ {
if (colors.Length < (1 << splits)) throw new ArgumentException($"The color array must at least contain ({(1 << splits)}) to perform {splits} splits."); if (colors.Length < (1 << splits)) throw new ArgumentException($"The color array must at least contain ({(1 << splits)}) to perform {splits} splits.");
Span<ColorCube> cubes = new ColorCube[1 << splits]; // DarthAffe 22.07.2024: This is not ideal as it allocates an additional array, but i don't see a way to get SKColors out here
cubes[0] = new ColorCube(colors, 0, colors.Length, SortTarget.None); return MemoryMarshal.Cast<ColorBGRA, SKColor>(MemoryMarshal.Cast<SKColor, ColorBGRA>(colors).CreateSimpleColorPalette(1 << splits)).ToArray();
int currentIndex = 0;
for (int i = 0; i < splits; i++)
{
int currentCubeCount = 1 << i;
Span<ColorCube> currentCubes = cubes.Slice(0, currentCubeCount);
for (int j = 0; j < currentCubes.Length; j++)
{
currentCubes[j].Split(colors, out ColorCube a, out ColorCube b);
currentCubes[j] = a;
cubes[++currentIndex] = b;
}
}
SKColor[] result = new SKColor[cubes.Length];
for (int i = 0; i < cubes.Length; i++)
result[i] = cubes[i].GetAverageColor(colors);
return result;
} }
/// <summary> /// <summary>

View File

@ -1,121 +0,0 @@
using SkiaSharp;
using System;
using System.Buffers;
namespace Artemis.Core.ColorScience;
//HACK DarthAffe 17.11.2022: Sorting is a really hot path in the quantizer, therefore abstracting this into cleaner code (one method with parameter or something like that) sadly has a well measurable performance impact.
internal static class QuantizerSort
{
#region Methods
public static void SortRed(in Span<SKColor> colors)
{
Span<int> counts = stackalloc int[256];
foreach (SKColor t in colors)
counts[t.Red]++;
SKColor[] bucketsArray = ArrayPool<SKColor>.Shared.Rent(colors.Length);
try
{
Span<SKColor> buckets = bucketsArray.AsSpan().Slice(0, colors.Length);
Span<int> currentBucketIndex = stackalloc int[256];
int offset = 0;
for (int i = 0; i < counts.Length; i++)
{
currentBucketIndex[i] = offset;
offset += counts[i];
}
foreach (SKColor color in colors)
{
int index = color.Red;
int bucketIndex = currentBucketIndex[index];
currentBucketIndex[index]++;
buckets[bucketIndex] = color;
}
buckets.CopyTo(colors);
}
finally
{
ArrayPool<SKColor>.Shared.Return(bucketsArray);
}
}
public static void SortGreen(in Span<SKColor> colors)
{
Span<int> counts = stackalloc int[256];
foreach (SKColor t in colors)
counts[t.Green]++;
SKColor[] bucketsArray = ArrayPool<SKColor>.Shared.Rent(colors.Length);
try
{
Span<SKColor> buckets = bucketsArray.AsSpan().Slice(0, colors.Length);
Span<int> currentBucketIndex = stackalloc int[256];
int offset = 0;
for (int i = 0; i < counts.Length; i++)
{
currentBucketIndex[i] = offset;
offset += counts[i];
}
foreach (SKColor color in colors)
{
int index = color.Green;
int bucketIndex = currentBucketIndex[index];
currentBucketIndex[index]++;
buckets[bucketIndex] = color;
}
buckets.CopyTo(colors);
}
finally
{
ArrayPool<SKColor>.Shared.Return(bucketsArray);
}
}
public static void SortBlue(in Span<SKColor> colors)
{
Span<int> counts = stackalloc int[256];
foreach (SKColor t in colors)
counts[t.Blue]++;
SKColor[] bucketsArray = ArrayPool<SKColor>.Shared.Rent(colors.Length);
try
{
Span<SKColor> buckets = bucketsArray.AsSpan().Slice(0, colors.Length);
Span<int> currentBucketIndex = stackalloc int[256];
int offset = 0;
for (int i = 0; i < counts.Length; i++)
{
currentBucketIndex[i] = offset;
offset += counts[i];
}
foreach (SKColor color in colors)
{
int index = color.Blue;
int bucketIndex = currentBucketIndex[index];
currentBucketIndex[index]++;
buckets[bucketIndex] = color;
}
buckets.CopyTo(colors);
}
finally
{
ArrayPool<SKColor>.Shared.Return(bucketsArray);
}
}
#endregion
}

View File

@ -1,6 +0,0 @@
namespace Artemis.Core.ColorScience;
internal enum SortTarget
{
None, Red, Green, Blue
}

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using SkiaSharp; using SkiaSharp;
@ -14,15 +12,10 @@ namespace Artemis.Core;
public sealed class Profile : ProfileElement public sealed class Profile : ProfileElement
{ {
private readonly object _lock = new(); private readonly object _lock = new();
private readonly ObservableCollection<ScriptConfiguration> _scriptConfigurations;
private readonly ObservableCollection<ProfileScript> _scripts;
private bool _isFreshImport; private bool _isFreshImport;
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!) internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
{ {
_scripts = new ObservableCollection<ProfileScript>();
_scriptConfigurations = new ObservableCollection<ScriptConfiguration>();
Opacity = 0d; Opacity = 0d;
ShouldDisplay = true; ShouldDisplay = true;
Configuration = configuration; Configuration = configuration;
@ -31,8 +24,6 @@ public sealed class Profile : ProfileElement
EntityId = profileEntity.Id; EntityId = profileEntity.Id;
Exceptions = new List<Exception>(); Exceptions = new List<Exception>();
Scripts = new ReadOnlyObservableCollection<ProfileScript>(_scripts);
ScriptConfigurations = new ReadOnlyObservableCollection<ScriptConfiguration>(_scriptConfigurations);
Load(); Load();
} }
@ -41,17 +32,7 @@ public sealed class Profile : ProfileElement
/// Gets the profile configuration of this profile /// Gets the profile configuration of this profile
/// </summary> /// </summary>
public ProfileConfiguration Configuration { get; } public ProfileConfiguration Configuration { get; }
/// <summary>
/// Gets a collection of all active scripts assigned to this profile
/// </summary>
public ReadOnlyObservableCollection<ProfileScript> Scripts { get; }
/// <summary>
/// Gets a collection of all script configurations assigned to this profile
/// </summary>
public ReadOnlyObservableCollection<ScriptConfiguration> ScriptConfigurations { get; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it /// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
/// since import /// since import
@ -85,15 +66,9 @@ public sealed class Profile : ProfileElement
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileUpdating(deltaTime);
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Update(deltaTime); profileElement.Update(deltaTime);
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileUpdated(deltaTime);
const double OPACITY_PER_SECOND = 1; const double OPACITY_PER_SECOND = 1;
if (ShouldDisplay && Opacity < 1) if (ShouldDisplay && Opacity < 1)
@ -111,9 +86,6 @@ public sealed class Profile : ProfileElement
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds);
SKPaint? opacityPaint = null; SKPaint? opacityPaint = null;
bool applyOpacityLayer = Configuration.FadeInAndOut && Opacity < 1; bool applyOpacityLayer = Configuration.FadeInAndOut && Opacity < 1;
@ -133,9 +105,6 @@ public sealed class Profile : ProfileElement
opacityPaint?.Dispose(); opacityPaint?.Dispose();
} }
foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);
if (!Exceptions.Any()) if (!Exceptions.Any())
return; return;
@ -174,7 +143,7 @@ public sealed class Profile : ProfileElement
/// <inheritdoc /> /// <inheritdoc />
public override IEnumerable<PluginFeature> GetFeatureDependencies() public override IEnumerable<PluginFeature> GetFeatureDependencies()
{ {
return GetRootFolder().GetFeatureDependencies().Concat(Scripts.Select(c => c.ScriptingProvider)); return GetRootFolder().GetFeatureDependencies();
} }
/// <summary> /// <summary>
@ -205,10 +174,7 @@ public sealed class Profile : ProfileElement
{ {
if (!disposing) if (!disposing)
return; return;
while (Scripts.Count > 0)
RemoveScript(Scripts[0]);
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Dispose(); profileElement.Dispose();
ChildrenList.Clear(); ChildrenList.Clear();
@ -238,61 +204,11 @@ public sealed class Profile : ProfileElement
AddChild(new Folder(this, this, rootFolder)); AddChild(new Folder(this, this, rootFolder));
} }
while (_scriptConfigurations.Any())
RemoveScriptConfiguration(_scriptConfigurations[0]);
foreach (ScriptConfiguration scriptConfiguration in ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)))
AddScriptConfiguration(scriptConfiguration);
// Load node scripts last since they may rely on the profile structure being in place // Load node scripts last since they may rely on the profile structure being in place
foreach (RenderProfileElement renderProfileElement in GetAllRenderElements()) foreach (RenderProfileElement renderProfileElement in GetAllRenderElements())
renderProfileElement.LoadNodeScript(); renderProfileElement.LoadNodeScript();
} }
/// <summary>
/// Removes a script configuration from the profile, if the configuration has an active script it is also removed.
/// </summary>
internal void RemoveScriptConfiguration(ScriptConfiguration scriptConfiguration)
{
if (!_scriptConfigurations.Contains(scriptConfiguration))
return;
Script? script = scriptConfiguration.Script;
if (script != null)
RemoveScript((ProfileScript) script);
_scriptConfigurations.Remove(scriptConfiguration);
}
/// <summary>
/// Adds a script configuration to the profile but does not instantiate it's script.
/// </summary>
internal void AddScriptConfiguration(ScriptConfiguration scriptConfiguration)
{
if (!_scriptConfigurations.Contains(scriptConfiguration))
_scriptConfigurations.Add(scriptConfiguration);
}
/// <summary>
/// Adds a script that has a script configuration belonging to this profile.
/// </summary>
internal void AddScript(ProfileScript script)
{
if (!_scriptConfigurations.Contains(script.ScriptConfiguration))
throw new ArtemisCoreException("Cannot add a script to a profile whose script configuration doesn't belong to the same profile.");
if (!_scripts.Contains(script))
_scripts.Add(script);
}
/// <summary>
/// Removes a script from the profile and disposes it.
/// </summary>
internal void RemoveScript(ProfileScript script)
{
_scripts.Remove(script);
script.Dispose();
}
internal override void Save() internal override void Save()
{ {
if (Disposed) if (Disposed)
@ -310,12 +226,5 @@ public sealed class Profile : ProfileElement
ProfileEntity.Layers.Clear(); ProfileEntity.Layers.Clear();
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity)); ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
ProfileEntity.ScriptConfigurations.Clear();
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
{
scriptConfiguration.Save();
ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
}
} }
} }

View File

@ -1,23 +0,0 @@
namespace Artemis.Core.ScriptingProviders;
/// <summary>
/// Represents a view model containing a script editor
/// </summary>
public interface IScriptEditorViewModel
{
/// <summary>
/// Gets the script type this view model was created for
/// </summary>
ScriptType ScriptType { get; }
/// <summary>
/// Gets the script this editor is editing
/// </summary>
Script? Script { get; }
/// <summary>
/// Called whenever the view model must display a different script
/// </summary>
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
void ChangeScript(Script? script);
}

View File

@ -1,163 +0,0 @@
using System;
using Artemis.Storage.Entities.General;
namespace Artemis.Core.ScriptingProviders;
/// <summary>
/// Represents the configuration of a script
/// </summary>
public class ScriptConfiguration : CorePropertyChanged, IStorageModel
{
private bool _hasChanges;
private bool _isSuspended;
private string _name;
private string? _pendingScriptContent;
private string? _scriptContent;
private string _scriptingProviderId;
/// <summary>
/// Creates a new instance of the <see cref="ScriptConfiguration" /> class
/// </summary>
public ScriptConfiguration(ScriptingProvider provider, string name, ScriptType scriptType)
{
_scriptingProviderId = provider.Id;
_name = name;
Entity = new ScriptConfigurationEntity();
PendingScriptContent = provider.GetDefaultScriptContent(scriptType);
ScriptContent = PendingScriptContent;
}
internal ScriptConfiguration(ScriptConfigurationEntity entity)
{
_scriptingProviderId = null!;
_name = null!;
Entity = entity;
Load();
}
/// <summary>
/// Gets or sets the ID of the scripting provider
/// </summary>
public string ScriptingProviderId
{
get => _scriptingProviderId;
set => SetAndNotify(ref _scriptingProviderId, value);
}
/// <summary>
/// Gets or sets the name of the script
/// </summary>
public string Name
{
get => _name;
set => SetAndNotify(ref _name, value);
}
/// <summary>
/// Gets or sets the script's content
/// </summary>
public string? ScriptContent
{
get => _scriptContent;
private set
{
if (!SetAndNotify(ref _scriptContent, value)) return;
OnScriptContentChanged();
}
}
/// <summary>
/// Gets or sets the pending changes to the script's content
/// </summary>
public string? PendingScriptContent
{
get => _pendingScriptContent;
set
{
if (string.IsNullOrWhiteSpace(value))
value = null;
if (!SetAndNotify(ref _pendingScriptContent, value)) return;
HasChanges = ScriptContent != PendingScriptContent;
}
}
// TODO: Implement suspension
/// <summary>
/// [NYI] Gets or sets a boolean indicating whether this configuration is suspended
/// </summary>
public bool IsSuspended
{
get => _isSuspended;
set => SetAndNotify(ref _isSuspended, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether this configuration has pending changes to it's
/// <see cref="ScriptContent" />
/// </summary>
public bool HasChanges
{
get => _hasChanges;
set => SetAndNotify(ref _hasChanges, value);
}
/// <summary>
/// If active, gets the script
/// </summary>
public Script? Script { get; internal set; }
internal ScriptConfigurationEntity Entity { get; }
/// <summary>
/// Applies the <see cref="PendingScriptContent" /> to the <see cref="ScriptContent" />
/// </summary>
public void ApplyPendingChanges()
{
ScriptContent = PendingScriptContent;
HasChanges = false;
}
/// <summary>
/// Discards the <see cref="PendingScriptContent" />
/// </summary>
public void DiscardPendingChanges()
{
PendingScriptContent = ScriptContent;
HasChanges = false;
}
/// <summary>
/// Occurs whenever the contents of the script have changed
/// </summary>
public event EventHandler? ScriptContentChanged;
/// <summary>
/// Invokes the <see cref="ScriptContentChanged" /> event
/// </summary>
protected virtual void OnScriptContentChanged()
{
ScriptContentChanged?.Invoke(this, EventArgs.Empty);
}
#region Implementation of IStorageModel
/// <inheritdoc />
public void Load()
{
ScriptingProviderId = Entity.ScriptingProviderId;
ScriptContent = Entity.ScriptContent;
PendingScriptContent = Entity.ScriptContent;
Name = Entity.Name;
}
/// <inheritdoc />
public void Save()
{
Entity.ScriptingProviderId = ScriptingProviderId;
Entity.ScriptContent = ScriptContent;
Entity.Name = Name;
}
#endregion
}

View File

@ -1,80 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Artemis.Core.ScriptingProviders;
/// <summary>
/// Allows you to implement and register your own scripting provider.
/// </summary>
public abstract class ScriptingProvider<TGlobalScript, TProfileScript> : ScriptingProvider
where TGlobalScript : GlobalScript
where TProfileScript : ProfileScript
{
#region Overrides of PluginFeature
/// <inheritdoc />
internal override void InternalDisable()
{
base.InternalDisable();
while (Scripts.Count > 0)
Scripts[0].Dispose();
}
#endregion
#region Overrides of ScriptingProvider
/// <inheritdoc />
internal override Type ProfileScriptType => typeof(TProfileScript);
/// <inheritdoc />
internal override Type GlobalScriptType => typeof(TGlobalScript);
#endregion
}
/// <summary>
/// Allows you to implement and register your own scripting provider.
/// <para>
/// Note: You can't implement this, implement
/// <see cref="ScriptingProvider{TGlobalScript, TProfileScript}" /> instead.
/// </para>
/// </summary>
public abstract class ScriptingProvider : PluginFeature
{
/// <summary>
/// The base constructor of the <see cref="ScriptingProvider" /> class
/// </summary>
protected ScriptingProvider()
{
Scripts = new ReadOnlyCollection<Script>(InternalScripts);
}
/// <summary>
/// Gets the name of the scripting language this provider provides
/// </summary>
public abstract string LanguageName { get; }
/// <summary>
/// Gets a list of all active scripts belonging to this scripting provider
/// </summary>
public ReadOnlyCollection<Script> Scripts { get; }
internal abstract Type GlobalScriptType { get; }
internal abstract Type ProfileScriptType { get; }
internal List<Script> InternalScripts { get; } = new();
/// <summary>
/// Called when the UI needs a script editor for the specified <paramref name="scriptType" />
/// </summary>
/// <param name="scriptType">The type of script the editor will host</param>
public abstract IScriptEditorViewModel CreateScriptEditor(ScriptType scriptType);
/// <summary>
/// Called when a script for a certain type needs default content.
/// </summary>
/// <param name="scriptType">The type of script the default content is for.</param>
public abstract string GetDefaultScriptContent(ScriptType scriptType);
}

View File

@ -1,45 +0,0 @@
using Artemis.Core.Services;
namespace Artemis.Core.ScriptingProviders;
/// <summary>
/// Represents a script running globally
/// </summary>
public abstract class GlobalScript : Script
{
/// <inheritdoc />
protected GlobalScript(ScriptConfiguration configuration) : base(configuration)
{
}
internal ScriptingService? ScriptingService { get; set; }
/// <summary>
/// Called whenever the Artemis Core is about to update
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnCoreUpdating(double deltaTime)
{
}
/// <summary>
/// Called whenever the Artemis Core has been updated
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnCoreUpdated(double deltaTime)
{
}
#region Overrides of Script
/// <inheritdoc />
public override ScriptType ScriptType => ScriptType.Global;
/// <inheritdoc />
internal override void InternalCleanup()
{
ScriptingService?.RemoveScript(ScriptConfiguration);
}
#endregion
}

View File

@ -1,67 +0,0 @@
using SkiaSharp;
namespace Artemis.Core.ScriptingProviders;
/// <summary>
/// Represents a script bound to a specific <see cref="Profile" /> processed by a <see cref="ScriptingProvider" />.
/// </summary>
public abstract class ProfileScript : Script
{
/// <inheritdoc />
protected ProfileScript(Profile profile, ScriptConfiguration configuration) : base(configuration)
{
Profile = profile;
}
/// <summary>
/// Gets the profile this script is bound to
/// </summary>
public Profile Profile { get; }
/// <summary>
/// Called whenever the profile is about to update
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnProfileUpdating(double deltaTime)
{
}
/// <summary>
/// Called whenever the profile has been updated
/// </summary>
/// <param name="deltaTime">Seconds passed since last update</param>
public virtual void OnProfileUpdated(double deltaTime)
{
}
/// <summary>
/// Called whenever the profile is about to render
/// </summary>
/// <param name="canvas">The profile canvas</param>
/// <param name="bounds">The area to be filled, covers the entire canvas</param>
public virtual void OnProfileRendering(SKCanvas canvas, SKRect bounds)
{
}
/// <summary>
/// Called whenever the profile has been rendered
/// </summary>
/// <param name="canvas">The profile canvas</param>
/// <param name="bounds">The area to be filled, covers the entire canvas</param>
public virtual void OnProfileRendered(SKCanvas canvas, SKRect bounds)
{
}
#region Overrides of Script
/// <inheritdoc />
public override ScriptType ScriptType => ScriptType.Profile;
/// <inheritdoc />
internal override void InternalCleanup()
{
Profile.RemoveScript(this);
}
#endregion
}

View File

@ -1,114 +0,0 @@
using System;
using System.ComponentModel;
namespace Artemis.Core.ScriptingProviders;
/// <summary>
/// Represents a script processed by a <see cref="ScriptingProviders.ScriptingProvider" />.
/// </summary>
public abstract class Script : CorePropertyChanged, IDisposable
{
private bool _disposed;
private ScriptingProvider _scriptingProvider = null!;
/// <summary>
/// The base constructor of any script
/// </summary>
/// <param name="configuration">The script configuration this script belongs to</param>
protected Script(ScriptConfiguration configuration)
{
if (configuration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptConfiguration = configuration;
ScriptConfiguration.PropertyChanged += ScriptConfigurationOnPropertyChanged;
}
/// <summary>
/// Gets the scripting provider this script belongs to
/// </summary>
public ScriptingProvider ScriptingProvider
{
get => _scriptingProvider;
internal set => SetAndNotify(ref _scriptingProvider, value);
}
/// <summary>
/// Gets the script configuration this script belongs to
/// </summary>
public ScriptConfiguration ScriptConfiguration { get; }
/// <summary>
/// Gets the script type of this script
/// </summary>
public abstract ScriptType ScriptType { get; }
/// <summary>
/// Occurs when the contents of the script have changed
/// </summary>
public event EventHandler? ScriptContentChanged;
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <summary>
/// Invokes the <see cref="ScriptContentChanged" /> event
/// </summary>
protected virtual void OnScriptContentChanged()
{
ScriptContentChanged?.Invoke(this, EventArgs.Empty);
}
internal abstract void InternalCleanup();
private void ScriptConfigurationOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ScriptConfiguration.ScriptContent))
OnScriptContentChanged();
}
/// <inheritdoc />
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
ScriptConfiguration.PropertyChanged -= ScriptConfigurationOnPropertyChanged;
ScriptConfiguration.Script = null;
ScriptingProvider.InternalScripts.Remove(this);
// Can't trust those pesky plugin devs!
InternalCleanup();
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Represents a type of script
/// </summary>
public enum ScriptType
{
/// <summary>
/// A global script that's always active
/// </summary>
Global,
/// <summary>
/// A script tied to a <see cref="Profile" />
/// </summary>
Profile
}

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Artemis.Core.SkiaSharp; using Artemis.Core.SkiaSharp;
using HPPH;
using HPPH.SkiaSharp;
using RGB.NET.Core; using RGB.NET.Core;
using RGB.NET.Presets.Textures.Sampler; using RGB.NET.Presets.Extensions;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core; namespace Artemis.Core;
@ -12,35 +12,29 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a SkiaSharp-based RGB.NET PixelTexture /// Represents a SkiaSharp-based RGB.NET PixelTexture
/// </summary> /// </summary>
public sealed class SKTexture : PixelTexture<byte>, IDisposable public sealed class SKTexture : ITexture, IDisposable
{ {
private readonly Dictionary<Led, SKRectI> _ledRects;
private readonly SKPixmap _pixelData;
private readonly IntPtr _pixelDataPtr;
#region Constructors #region Constructors
internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection<ArtemisDevice> devices) : base(width, height, DATA_PER_PIXEL, internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection<ArtemisDevice> devices)
new AverageByteSampler())
{ {
RenderScale = scale;
Size = new Size(width, height);
ImageInfo = new SKImageInfo(width, height); ImageInfo = new SKImageInfo(width, height);
Surface = graphicsContext == null Surface = graphicsContext == null
? SKSurface.Create(ImageInfo) ? SKSurface.Create(ImageInfo)
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo); : SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
RenderScale = scale;
_pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize);
_pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes);
_ledRects = new Dictionary<Led, SKRectI>();
foreach (ArtemisDevice artemisDevice in devices) foreach (ArtemisDevice artemisDevice in devices)
{ {
foreach (ArtemisLed artemisLed in artemisDevice.Leds) foreach (ArtemisLed artemisLed in artemisDevice.Leds)
{ {
_ledRects[artemisLed.RgbLed] = SKRectI.Create( _ledRects[artemisLed.RgbLed] = SKRectI.Create(
(int) (artemisLed.AbsoluteRectangle.Left * RenderScale), (int)(artemisLed.AbsoluteRectangle.Left * RenderScale),
(int) (artemisLed.AbsoluteRectangle.Top * RenderScale), (int)(artemisLed.AbsoluteRectangle.Top * RenderScale),
(int) (artemisLed.AbsoluteRectangle.Width * RenderScale), (int)(artemisLed.AbsoluteRectangle.Width * RenderScale),
(int) (artemisLed.AbsoluteRectangle.Height * RenderScale) (int)(artemisLed.AbsoluteRectangle.Height * RenderScale)
); );
} }
} }
@ -50,47 +44,29 @@ public sealed class SKTexture : PixelTexture<byte>, IDisposable
internal Color GetColorAtRenderTarget(in RenderTarget renderTarget) internal Color GetColorAtRenderTarget(in RenderTarget renderTarget)
{ {
if (Data.Length == 0) return Color.Transparent; if (_image == null) return Color.Transparent;
SKRectI skRectI = _ledRects[renderTarget.Led]; SKRectI skRectI = _ledRects[renderTarget.Led];
if (skRectI.Width <= 0 || skRectI.Height <= 0) if (skRectI.Width <= 0 || skRectI.Height <= 0)
return Color.Transparent; return Color.Transparent;
SamplerInfo<byte> samplerInfo = new(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, Stride, DataPerPixel, Data); return _image[skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height].Average().ToColor();
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(samplerInfo, pixelData);
return GetColor(pixelData);
}
private void ReleaseUnmanagedResources()
{
Marshal.FreeHGlobal(_pixelDataPtr);
} }
/// <inheritdoc /> /// <inheritdoc />
~SKTexture() ~SKTexture()
{ {
ReleaseUnmanagedResources(); Dispose();
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
Surface.Dispose(); Surface.Dispose();
_pixelData.Dispose();
ReleaseUnmanagedResources();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#region Constants
private const int DATA_PER_PIXEL = 4;
#endregion
#region Methods #region Methods
/// <summary> /// <summary>
@ -104,20 +80,23 @@ public sealed class SKTexture : PixelTexture<byte>, IDisposable
internal void CopyPixelData() internal void CopyPixelData()
{ {
using SKImage skImage = Surface.Snapshot(); using SKImage skImage = Surface.Snapshot();
skImage.ReadPixels(_pixelData); _image = skImage.ToImage();
} }
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override Color GetColor(in ReadOnlySpan<byte> pixel) => new(pixel[2], pixel[1], pixel[0]);
/// <inheritdoc />
public override Color this[in Rectangle rectangle] => Color.Transparent;
#endregion #endregion
#region Properties & Fields #region Properties & Fields
private IImage? _image;
private readonly Dictionary<Led, SKRectI> _ledRects = [];
/// <inheritdoc />
public Size Size { get; }
/// <inheritdoc />
public Color this[Point point] => Color.Transparent;
/// <inheritdoc />
public Color this[Rectangle rectangle] => Color.Transparent;
/// <summary> /// <summary>
/// Gets the SKBitmap backing this texture /// Gets the SKBitmap backing this texture
/// </summary> /// </summary>
@ -128,11 +107,6 @@ public sealed class SKTexture : PixelTexture<byte>, IDisposable
/// </summary> /// </summary>
public SKImageInfo ImageInfo { get; } public SKImageInfo ImageInfo { get; }
/// <summary>
/// Gets the color data in RGB format
/// </summary>
protected override ReadOnlySpan<byte> Data => _pixelData.GetPixelSpan();
/// <summary> /// <summary>
/// Gets the render scale of the texture /// Gets the render scale of the texture
/// </summary> /// </summary>

View File

@ -20,7 +20,7 @@ internal class SKTextureBrush : AbstractBrush
#region Methods #region Methods
/// <inheritdoc /> /// <inheritdoc />
protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget) protected override Color GetColorAtPoint(Rectangle rectangle, RenderTarget renderTarget)
{ {
return Texture?.GetColorAtRenderTarget(renderTarget) ?? Color.Transparent; return Texture?.GetColorAtRenderTarget(renderTarget) ?? Color.Transparent;
} }

View File

@ -1,4 +1,3 @@
using Artemis.Core.ScriptingProviders;
using Artemis.Core.Services.Core; using Artemis.Core.Services.Core;
using SkiaSharp; using SkiaSharp;
@ -7,22 +6,17 @@ namespace Artemis.Core.Services;
internal class CoreRenderer : IRenderer internal class CoreRenderer : IRenderer
{ {
private readonly IModuleService _moduleService; private readonly IModuleService _moduleService;
private readonly IScriptingService _scriptingService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
public CoreRenderer(IModuleService moduleService, IScriptingService scriptingService, IProfileService profileService) public CoreRenderer(IModuleService moduleService, IProfileService profileService)
{ {
_moduleService = moduleService; _moduleService = moduleService;
_scriptingService = scriptingService;
_profileService = profileService; _profileService = profileService;
} }
/// <inheritdoc /> /// <inheritdoc />
public void Render(SKCanvas canvas, double delta) public void Render(SKCanvas canvas, double delta)
{ {
foreach (GlobalScript scriptingServiceGlobalScript in _scriptingService.GlobalScripts)
scriptingServiceGlobalScript.OnCoreUpdating(delta);
_moduleService.UpdateActiveModules(delta); _moduleService.UpdateActiveModules(delta);
if (!_profileService.ProfileRenderingDisabled) if (!_profileService.ProfileRenderingDisabled)
@ -30,9 +24,6 @@ internal class CoreRenderer : IRenderer
_profileService.UpdateProfiles(delta); _profileService.UpdateProfiles(delta);
_profileService.RenderProfiles(canvas); _profileService.RenderProfiles(canvas);
} }
foreach (GlobalScript scriptingServiceGlobalScript in _scriptingService.GlobalScripts)
scriptingServiceGlobalScript.OnCoreUpdated(delta);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,17 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Artemis.Core.DryIoc.Factories; using Artemis.Core.DryIoc.Factories;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage;
using DryIoc; using DryIoc;
using HidSharp; using HidSharp;
using RGB.NET.Core;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using SkiaSharp;
namespace Artemis.Core.Services; namespace Artemis.Core.Services;
@ -33,7 +28,6 @@ internal class CoreService : ICoreService
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IProfileService profileService, IProfileService profileService,
IModuleService moduleService, IModuleService moduleService,
IScriptingService scriptingService,
IRenderService renderService) IRenderService renderService)
{ {
Constants.CorePlugin.Container = container; Constants.CorePlugin.Container = container;

View File

@ -1,46 +0,0 @@
using System.Collections.ObjectModel;
using Artemis.Core.ScriptingProviders;
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to manage various types of <see cref="NodeScript" /> instances
/// </summary>
public interface IScriptingService : IArtemisService
{
/// <summary>
/// Gets a list of all available scripting providers
/// </summary>
ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
/// <summary>
/// Gets a list of all currently active global scripts
/// </summary>
ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <summary>
/// Adds a script by the provided script configuration to the provided profile and instantiates it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to add.</param>
/// <param name="profile">The profile to add the script to.</param>
ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// <summary>
/// Removes a script by the provided script configuration from the provided profile and disposes it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
/// <param name="profile">The profile to remove the script from.</param>
void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// <summary>
/// Adds a script by the provided script configuration to the global collection and instantiates it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to add.</param>
GlobalScript AddScript(ScriptConfiguration scriptConfiguration);
/// <summary>
/// Removes a script by the provided script configuration from the global collection and disposes it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
void RemoveScript(ScriptConfiguration scriptConfiguration);
}

View File

@ -1,165 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.ScriptingProviders;
namespace Artemis.Core.Services;
internal class ScriptingService : IScriptingService
{
private readonly List<GlobalScript> _globalScripts;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly List<ScriptingProvider> _scriptingProviders;
public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService)
{
_pluginManagementService = pluginManagementService;
_profileService = profileService;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
_globalScripts = new List<GlobalScript>();
ScriptingProviders = new ReadOnlyCollection<ScriptingProvider>(_scriptingProviders);
GlobalScripts = new ReadOnlyCollection<GlobalScript>(_globalScripts);
// No need to sub to Deactivated, scripts will deactivate themselves
profileService.ProfileActivated += ProfileServiceOnProfileActivated;
foreach (ProfileCategory profileCategory in _profileService.ProfileCategories)
{
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
{
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
}
}
private GlobalScript CreateScriptInstance(ScriptConfiguration scriptConfiguration)
{
GlobalScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
script = (GlobalScript) provider.Plugin.Resolve(provider.GlobalScriptType, scriptConfiguration);
script.ScriptingProvider = provider;
script.ScriptingService = this;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
return script;
}
catch (Exception e)
{
script?.Dispose();
throw new ArtemisCoreException("Failed to initialize global script", e);
}
}
private ProfileScript CreateScriptInstance(ScriptConfiguration scriptConfiguration, Profile profile)
{
ProfileScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
script = (ProfileScript) provider.Plugin.Resolve(provider.ProfileScriptType, profile, scriptConfiguration);
script.ScriptingProvider = provider;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
lock (profile)
{
profile.AddScript(script);
}
return script;
}
catch (Exception e)
{
// If something went wrong but the script was created, clean up as best we can
if (script != null)
{
if (profile.Scripts.Contains(script))
profile.RemoveScript(script);
else
script.Dispose();
}
throw new ArtemisCoreException("Failed to initialize profile script", e);
}
}
private void InitializeProfileScripts(Profile profile)
{
// Initialize the scripts on the profile
foreach (ScriptConfiguration scriptConfiguration in profile.ScriptConfigurations.Where(c => c.Script == null && _scriptingProviders.Any(p => p.Id == c.ScriptingProviderId)))
CreateScriptInstance(scriptConfiguration, profile);
}
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
{
_scriptingProviders.Clear();
_scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType<ScriptingProvider>());
foreach (ProfileCategory profileCategory in _profileService.ProfileCategories)
{
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
{
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
}
}
private void ProfileServiceOnProfileActivated(object? sender, ProfileConfigurationEventArgs e)
{
if (e.ProfileConfiguration.Profile != null)
InitializeProfileScripts(e.ProfileConfiguration.Profile);
}
/// <inheritdoc />
public ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
/// <inheritdoc />
public ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <inheritdoc />
public ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile)
{
profile.AddScriptConfiguration(scriptConfiguration);
return CreateScriptInstance(scriptConfiguration, profile);
}
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile)
{
profile.RemoveScriptConfiguration(scriptConfiguration);
}
/// <inheritdoc />
public GlobalScript AddScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
}

View File

@ -1,12 +0,0 @@
using System;
namespace Artemis.Storage.Entities.General;
public class ScriptConfigurationEntity
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string ScriptingProviderId { get; set; } = string.Empty;
public string? ScriptContent { get; set; }
}

View File

@ -11,7 +11,6 @@ public class ProfileEntity
{ {
Folders = new List<FolderEntity>(); Folders = new List<FolderEntity>();
Layers = new List<LayerEntity>(); Layers = new List<LayerEntity>();
ScriptConfigurations = new List<ScriptConfigurationEntity>();
} }
public Guid Id { get; set; } public Guid Id { get; set; }
@ -21,7 +20,6 @@ public class ProfileEntity
public List<FolderEntity> Folders { get; set; } public List<FolderEntity> Folders { get; set; }
public List<LayerEntity> Layers { get; set; } public List<LayerEntity> Layers { get; set; }
public List<ScriptConfigurationEntity> ScriptConfigurations { get; set; }
public void UpdateGuid(Guid guid) public void UpdateGuid(Guid guid)
{ {

View File

@ -1,54 +0,0 @@
using Artemis.Core.ScriptingProviders;
namespace Artemis.UI.Shared.ScriptingProviders;
/// <summary>
/// Represents a Stylet view model containing a script editor
/// </summary>
public class ScriptEditorViewModel : ActivatableViewModelBase, IScriptEditorViewModel
{
private Script? _script;
/// <summary>
/// Creates a new instance of <see cref="ScriptEditorViewModel" />
/// </summary>
/// <param name="scriptType">The script type this view model was created for</param>
public ScriptEditorViewModel(ScriptType scriptType)
{
ScriptType = scriptType;
}
/// <summary>
/// Called just before the script is changed to a different one
/// </summary>
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
protected virtual void OnScriptChanging(Script? script)
{
}
/// <summary>
/// Called after the script was changed to a different one
/// </summary>
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
protected virtual void OnScriptChanged(Script? script)
{
}
/// <inheritdoc />
public ScriptType ScriptType { get; }
/// <inheritdoc />
public Script? Script
{
get => _script;
internal set => RaiseAndSetIfChanged(ref _script, value);
}
/// <inheritdoc />
public void ChangeScript(Script? script)
{
OnScriptChanging(script);
Script = script;
OnScriptChanged(script);
}
}

View File

@ -3,18 +3,17 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"> xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
<Design.PreviewWith> <Design.PreviewWith>
<HyperlinkButton Grid.Column="0" Classes="icon-button icon-button-small broken-state-button" Margin="50">
<controls:HyperlinkButton Grid.Column="0" Classes="icon-button icon-button-small broken-state-button" Margin="50">
<avalonia:MaterialIcon Kind="AlertCircle" /> <avalonia:MaterialIcon Kind="AlertCircle" />
</controls:HyperlinkButton> </HyperlinkButton>
</Design.PreviewWith> </Design.PreviewWith>
<!-- Add Styles Here --> <!-- Add Styles Here -->
<Style Selector="controls|HyperlinkButton.broken-state-button avalonia|MaterialIcon"> <Style Selector="HyperlinkButton.broken-state-button avalonia|MaterialIcon">
<Setter Property="Foreground" Value="#E74C4C" /> <Setter Property="Foreground" Value="#E74C4C" />
</Style> </Style>
<Style Selector="controls|HyperlinkButton.broken-state-button:pointerover avalonia|MaterialIcon"> <Style Selector="HyperlinkButton.broken-state-button:pointerover avalonia|MaterialIcon">
<Setter Property="Foreground" Value="#B93F3F" /> <Setter Property="Foreground" Value="#B93F3F" />
</Style> </Style>
</Styles> </Styles>

View File

@ -25,14 +25,14 @@
</ToggleButton> </ToggleButton>
<TextBlock Margin="0 5 0 0">HyperlinkButton.icon-button</TextBlock> <TextBlock Margin="0 5 0 0">HyperlinkButton.icon-button</TextBlock>
<controls:HyperlinkButton Classes="icon-button"> <HyperlinkButton Classes="icon-button">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </HyperlinkButton>
<TextBlock Margin="0 5 0 0">HyperlinkButton.icon-button icon-button-small</TextBlock> <TextBlock Margin="0 5 0 0">HyperlinkButton.icon-button icon-button-small</TextBlock>
<controls:HyperlinkButton Classes="icon-button icon-button-small"> <HyperlinkButton Classes="icon-button icon-button-small">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </HyperlinkButton>
<TextBlock Margin="0 5 0 0">Button.window-button</TextBlock> <TextBlock Margin="0 5 0 0">Button.window-button</TextBlock>
@ -93,7 +93,7 @@
<Setter Property="Height" Value="20" /> <Setter Property="Height" Value="20" />
</Style> </Style>
<Style Selector="controls|HyperlinkButton.icon-button"> <Style Selector="HyperlinkButton.icon-button">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> <Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style> </Style>
@ -113,24 +113,24 @@
<Setter Property="Background" Value="#D64848"></Setter> <Setter Property="Background" Value="#D64848"></Setter>
</Style> </Style>
<Style Selector="Button.danger:pointerover"> <!-- <Style Selector="Button.danger:pointerover"> -->
<Style Selector="^ /template/ controls|FABorder#Root"> <!-- <Style Selector="^ /template/ controls|FABorder#Root"> -->
<Setter Property="Background" Value="#D65757"/> <!-- <Setter Property="Background" Value="#D65757"/> -->
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" /> <!-- <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" /> -->
</Style> <!-- </Style> -->
</Style> <!-- </Style> -->
<!-- -->
<Style Selector="Button.danger:pressed"> <!-- <Style Selector="Button.danger:pressed"> -->
<Style Selector="^ /template/ controls|FABorder#Root"> <!-- <Style Selector="^ /template/ controls|FABorder#Root"> -->
<Setter Property="Background" Value="#D64848" /> <!-- <Setter Property="Background" Value="#D64848" /> -->
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" /> <!-- <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" /> -->
</Style> <!-- </Style> -->
</Style> <!-- </Style> -->
<!-- -->
<Style Selector="Button.danger:disabled"> <!-- <Style Selector="Button.danger:disabled"> -->
<Style Selector="^ /template/ controls|FABorder#Root"> <!-- <Style Selector="^ /template/ controls|FABorder#Root"> -->
<Setter Property="Background" Value="#D79D9C" /> <!-- <Setter Property="Background" Value="#D79D9C" /> -->
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> <!-- <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> -->
</Style> <!-- </Style> -->
</Style> <!-- </Style> -->
</Styles> </Styles>

View File

@ -51,7 +51,7 @@
RowDefinitions="*,*"> RowDefinitions="*,*">
<TextBlock Grid.Column="0" Grid.Row="0" Classes="BodyStrongTextBlockStyle">Welcome to the data model picker</TextBlock> <TextBlock Grid.Column="0" Grid.Row="0" Classes="BodyStrongTextBlockStyle">Welcome to the data model picker</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Foreground="{DynamicResource TextFillColorSecondary}">Select a value from the data model below</TextBlock> <TextBlock Grid.Column="0" Grid.Row="1" Foreground="{DynamicResource TextFillColorSecondary}">Select a value from the data model below</TextBlock>
<controls:HyperlinkButton Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">Learn more</controls:HyperlinkButton> <HyperlinkButton Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">Learn more</HyperlinkButton>
</Grid> </Grid>
</Panel> </Panel>
</Border> </Border>

View File

@ -67,5 +67,8 @@
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\PluginListView.axaml" /> <UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\PluginListView.axaml" />
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\ProfileListView.axaml" /> <UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\ProfileListView.axaml" />
<UpToDateCheckInput Remove="Screens\Workshop\Plugins\Dialogs\PluginDialogView.axaml" /> <UpToDateCheckInput Remove="Screens\Workshop\Plugins\Dialogs\PluginDialogView.axaml" />
<UpToDateCheckInput Remove="Screens\Scripting\Dialogs\ScriptConfigurationCreateView.axaml" />
<UpToDateCheckInput Remove="Screens\Scripting\Dialogs\ScriptConfigurationEditView.axaml" />
<UpToDateCheckInput Remove="Screens\Scripting\ScriptsDialogView.axaml" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -17,7 +17,7 @@
<Label Grid.Column="0" Name="DescriptionEditorLabel" Target="DescriptionEditor" Margin="0 28 0 0" /> <Label Grid.Column="0" Name="DescriptionEditorLabel" Target="DescriptionEditor" Margin="0 28 0 0" />
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<CheckBox Name="SynchronizedScrolling" IsChecked="True" VerticalAlignment="Bottom">Synchronized scrolling</CheckBox> <CheckBox Name="SynchronizedScrolling" IsChecked="True" VerticalAlignment="Bottom">Synchronized scrolling</CheckBox>
<fa:HyperlinkButton <HyperlinkButton
Margin="0 0 0 -20" Margin="0 0 0 -20"
Content="Markdown supported" Content="Markdown supported"
NavigateUri="https://wiki.artemis-rgb.com/guides/user/markdown?mtm_campaign=artemis&amp;mtm_kwd=markdown-editor" NavigateUri="https://wiki.artemis-rgb.com/guides/user/markdown?mtm_campaign=artemis&amp;mtm_kwd=markdown-editor"

View File

@ -3,7 +3,6 @@ using System.Reactive;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.ScriptingProviders;
using Artemis.UI.Screens.Device; using Artemis.UI.Screens.Device;
using Artemis.UI.Screens.Device.General; using Artemis.UI.Screens.Device.General;
using Artemis.UI.Screens.Device.InputMappings; using Artemis.UI.Screens.Device.InputMappings;
@ -21,7 +20,6 @@ using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree; using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers; using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
using Artemis.UI.Screens.Scripting;
using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.Settings;
using Artemis.UI.Screens.Settings.Updating; using Artemis.UI.Screens.Settings.Updating;
using Artemis.UI.Screens.Sidebar; using Artemis.UI.Screens.Sidebar;
@ -470,32 +468,6 @@ public class LayerHintVmFactory : ILayerHintVmFactory
} }
} }
public interface IScriptVmFactory : IVmFactory
{
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
}
public class ScriptVmFactory : IScriptVmFactory
{
private readonly IContainer _container;
public ScriptVmFactory(IContainer container)
{
_container = container;
}
public ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration)
{
return _container.Resolve<ScriptConfigurationViewModel>(new object[] {scriptConfiguration});
}
public ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration)
{
return _container.Resolve<ScriptConfigurationViewModel>(new object[] {profile, scriptConfiguration});
}
}
public interface IReleaseVmFactory : IVmFactory public interface IReleaseVmFactory : IVmFactory
{ {
ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release); ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release);

View File

@ -17,9 +17,9 @@
<TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10"> <TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10">
These performance stats are rather basic, for advanced performance profiling check out the wiki. These performance stats are rather basic, for advanced performance profiling check out the wiki.
</TextBlock> </TextBlock>
<controls:HyperlinkButton Grid.Column="1" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins/profiling?mtm_campaign=artemis&amp;mtm_kwd=debugger"> <HyperlinkButton Grid.Column="1" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins/profiling?mtm_campaign=artemis&amp;mtm_kwd=debugger">
JetBrains Profiling Guide JetBrains Profiling Guide
</controls:HyperlinkButton> </HyperlinkButton>
</Grid> </Grid>
</StackPanel> </StackPanel>

View File

@ -104,7 +104,7 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
<controls:HyperlinkButton <HyperlinkButton
Grid.Row="1" Grid.Row="1"
Content="Learn more about layouts on the wiki" Content="Learn more about layouts on the wiki"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts?mtm_campaign=artemis&amp;mtm_kwd=device-properties" NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts?mtm_campaign=artemis&amp;mtm_kwd=device-properties"

View File

@ -3,7 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:home="clr-namespace:Artemis.UI.Screens.Home" xmlns:home="clr-namespace:Artemis.UI.Screens.Home"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="900" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="900"
x:Class="Artemis.UI.Screens.Home.HomeView" x:Class="Artemis.UI.Screens.Home.HomeView"
@ -42,17 +41,17 @@
Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers. We're also keeping track of a list of third-party plugins on our wiki. Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers. We're also keeping track of a list of third-party plugins on our wiki.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<controls:HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" HorizontalAlignment="Right" Command="{CompiledBinding GetMorePlugins}"> <HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" HorizontalAlignment="Right" Command="{CompiledBinding GetMorePlugins}">
<controls:HyperlinkButton.ContextMenu> <HyperlinkButton.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="Test"></MenuItem> <MenuItem Header="Test"></MenuItem>
</ContextMenu> </ContextMenu>
</controls:HyperlinkButton.ContextMenu> </HyperlinkButton.ContextMenu>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="OpenInBrowser" /> <avalonia:MaterialIcon Kind="OpenInBrowser" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Get more plugins</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Get more plugins</TextBlock>
</StackPanel> </StackPanel>
</controls:HyperlinkButton> </HyperlinkButton>
</Grid> </Grid>
</Border> </Border>
@ -68,30 +67,30 @@
<DockPanel Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0"> <DockPanel Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0">
<Grid Margin="8" RowDefinitions="*,*"> <Grid Margin="8" RowDefinitions="*,*">
<controls:HyperlinkButton Grid.Row="0" NavigateUri="https://github.com/Artemis-RGB/Artemis"> <HyperlinkButton Grid.Row="0" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="Github" /> <avalonia:MaterialIcon Kind="Github" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock>
</StackPanel> </StackPanel>
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=home"> <HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=home">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock>
</StackPanel> </StackPanel>
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Grid.Row="1" NavigateUri="https://discordapp.com/invite/S3MVaC9"> <HyperlinkButton Grid.Row="1" NavigateUri="https://discordapp.com/invite/S3MVaC9">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="Chat" /> <avalonia:MaterialIcon Kind="Chat" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Discord</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Discord</TextBlock>
</StackPanel> </StackPanel>
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Grid.Row="1" HorizontalAlignment="Right" NavigateUri="mailto:spoinky.nl@gmail.com"> <HyperlinkButton Grid.Row="1" HorizontalAlignment="Right" NavigateUri="mailto:spoinky.nl@gmail.com">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="Email" /> <avalonia:MaterialIcon Kind="Email" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">E-mail</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">E-mail</TextBlock>
</StackPanel> </StackPanel>
</controls:HyperlinkButton> </HyperlinkButton>
</Grid> </Grid>
</DockPanel> </DockPanel>
</Grid> </Grid>
@ -106,7 +105,7 @@
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<controls:HyperlinkButton Grid.Row="1" <HyperlinkButton Grid.Row="1"
Grid.Column="0" Grid.Column="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&amp;mtm_kwd=home"> NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&amp;mtm_kwd=home">
@ -114,7 +113,7 @@
<avalonia:MaterialIcon Kind="Gift" /> <avalonia:MaterialIcon Kind="Gift" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock>
</StackPanel> </StackPanel>
</controls:HyperlinkButton> </HyperlinkButton>
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Classes="subtitle" Classes="subtitle"

View File

@ -9,7 +9,6 @@ using Artemis.Core.DeviceProviders;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Core.ScriptingProviders;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -99,8 +98,6 @@ public partial class PluginFeatureViewModel : ActivatableViewModelBase
return MaterialIconKind.Brush; return MaterialIconKind.Brush;
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider))) if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider)))
return MaterialIconKind.AutoAwesome; return MaterialIconKind.AutoAwesome;
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(ScriptingProvider)))
return MaterialIconKind.Code;
return MaterialIconKind.Extension; return MaterialIconKind.Extension;
} }
} }
@ -117,8 +114,6 @@ public partial class PluginFeatureViewModel : ActivatableViewModelBase
return "Layer Brush"; return "Layer Brush";
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider))) if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider)))
return "Layer Effect"; return "Layer Effect";
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(ScriptingProvider)))
return "Scripting Provider";
return "Miscellaneous feature"; return "Miscellaneous feature";
} }
} }

View File

@ -28,24 +28,24 @@
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="28" VerticalAlignment="Bottom" Text="{CompiledBinding Plugin.Info.Name}" /> <TextBlock Grid.Row="0" Grid.Column="1" FontSize="28" VerticalAlignment="Bottom" Text="{CompiledBinding Plugin.Info.Name}" />
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.HelpPage}" NavigateUri="{CompiledBinding Plugin.Info.HelpPage}"
ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}"> ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}">
<avalonia:MaterialIcon Kind="Quiz" /> <avalonia:MaterialIcon Kind="Quiz" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Website}" NavigateUri="{CompiledBinding Plugin.Info.Website}"
ToolTip.Tip="{CompiledBinding Plugin.Info.Website}"> ToolTip.Tip="{CompiledBinding Plugin.Info.Website}">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Repository}" NavigateUri="{CompiledBinding Plugin.Info.Repository}"
ToolTip.Tip="{CompiledBinding Plugin.Info.Repository}"> ToolTip.Tip="{CompiledBinding Plugin.Info.Repository}">
<avalonia:MaterialIcon Kind="Git" /> <avalonia:MaterialIcon Kind="Git" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"
@ -54,7 +54,7 @@
Classes="subtitle" Classes="subtitle"
Text="{CompiledBinding Plugin.Info.Version}" /> Text="{CompiledBinding Plugin.Info.Version}" />
<controls:HyperlinkButton Grid.Row="1" <HyperlinkButton Grid.Row="1"
Grid.Column="2" Grid.Column="2"
IsVisible="{CompiledBinding Plugin.Info.License, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{CompiledBinding Plugin.Info.License, Converter={x:Static ObjectConverters.IsNotNull}}"
VerticalAlignment="Top" VerticalAlignment="Top"

View File

@ -87,30 +87,30 @@
</DropDownButton.Flyout> </DropDownButton.Flyout>
</DropDownButton> </DropDownButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large" <HyperlinkButton Classes="icon-button icon-button-large"
IsVisible="{CompiledBinding Plugin.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{CompiledBinding Plugin.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}"
Command="{CompiledBinding OpenSettings}" Command="{CompiledBinding OpenSettings}"
ToolTip.Tip="Open settings"> ToolTip.Tip="Open settings">
<avalonia:MaterialIcon Kind="Cog" /> <avalonia:MaterialIcon Kind="Cog" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large" <HyperlinkButton Classes="icon-button icon-button-large"
IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.HelpPage}" NavigateUri="{CompiledBinding Plugin.Info.HelpPage}"
ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}"> ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}">
<avalonia:MaterialIcon Kind="Quiz" /> <avalonia:MaterialIcon Kind="Quiz" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large" <HyperlinkButton Classes="icon-button icon-button-large"
IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Website}" NavigateUri="{CompiledBinding Plugin.Info.Website}"
ToolTip.Tip="{CompiledBinding Plugin.Info.Website}"> ToolTip.Tip="{CompiledBinding Plugin.Info.Website}">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large" <HyperlinkButton Classes="icon-button icon-button-large"
IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding Plugin.Info.Repository}" NavigateUri="{CompiledBinding Plugin.Info.Repository}"
ToolTip.Tip="{CompiledBinding Plugin.Info.Repository}"> ToolTip.Tip="{CompiledBinding Plugin.Info.Repository}">
<avalonia:MaterialIcon Kind="Git" /> <avalonia:MaterialIcon Kind="Git" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<CheckBox Name="EnabledToggle" <CheckBox Name="EnabledToggle"

View File

@ -31,11 +31,6 @@
<avalonia:MaterialIcon Kind="Settings" /> <avalonia:MaterialIcon Kind="Settings" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="_View Scripts" Command="{CompiledBinding ViewScripts}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="BookEdit" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Adapt Profile" Command="{CompiledBinding AdaptProfile}"> <MenuItem Header="Adapt Profile" Command="{CompiledBinding AdaptProfile}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Magic" /> <avalonia:MaterialIcon Kind="Magic" />

View File

@ -8,7 +8,6 @@ using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.Scripting;
using Artemis.UI.Screens.Sidebar; using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
@ -64,7 +63,6 @@ public partial class MenuBarViewModel : ActivatableViewModelBase
AddFolder = ReactiveCommand.Create(ExecuteAddFolder); AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
AddLayer = ReactiveCommand.Create(ExecuteAddLayer); AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
ViewScripts = ReactiveCommand.CreateFromTask(ExecuteViewScripts, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
AdaptProfile = ReactiveCommand.CreateFromTask(ExecuteAdaptProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); AdaptProfile = ReactiveCommand.CreateFromTask(ExecuteAdaptProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
DeleteProfile = ReactiveCommand.CreateFromTask(ExecuteDeleteProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); DeleteProfile = ReactiveCommand.CreateFromTask(ExecuteDeleteProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
@ -81,7 +79,6 @@ public partial class MenuBarViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit> AddLayer { get; } public ReactiveCommand<Unit, Unit> AddLayer { get; }
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; } public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
public ReactiveCommand<Unit, Unit> ViewProperties { get; } public ReactiveCommand<Unit, Unit> ViewProperties { get; }
public ReactiveCommand<Unit, Unit> ViewScripts { get; }
public ReactiveCommand<Unit, Unit> AdaptProfile { get; } public ReactiveCommand<Unit, Unit> AdaptProfile { get; }
public ReactiveCommand<Unit, Unit> DeleteProfile { get; } public ReactiveCommand<Unit, Unit> DeleteProfile { get; }
public ReactiveCommand<Unit, Unit> ExportProfile { get; } public ReactiveCommand<Unit, Unit> ExportProfile { get; }
@ -133,16 +130,7 @@ public partial class MenuBarViewModel : ActivatableViewModelBase
await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(ProfileConfiguration.Category, ProfileConfiguration); await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(ProfileConfiguration.Category, ProfileConfiguration);
} }
private async Task ExecuteViewScripts()
{
if (ProfileConfiguration?.Profile == null)
return;
await _windowService.ShowDialogAsync<ScriptsDialogViewModel, object?>(ProfileConfiguration.Profile);
await _profileEditorService.SaveProfileAsync();
}
private async Task ExecuteAdaptProfile() private async Task ExecuteAdaptProfile()
{ {
if (ProfileConfiguration?.Profile == null) if (ProfileConfiguration?.Profile == null)

View File

@ -22,12 +22,12 @@
<TextBlock Classes="h4" Text="{CompiledBinding Layer.Name}" TextWrapping="Wrap" /> <TextBlock Classes="h4" Text="{CompiledBinding Layer.Name}" TextWrapping="Wrap" />
</StackPanel> </StackPanel>
<controls:HyperlinkButton Grid.Row="0" <HyperlinkButton Grid.Row="0"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Top" VerticalAlignment="Top"
NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints?mtm_campaign=artemis&amp;mtm_kwd=profile-editor"> NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints?mtm_campaign=artemis&amp;mtm_kwd=profile-editor">
Learn more about adaption hints Learn more about adaption hints
</controls:HyperlinkButton> </HyperlinkButton>
<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Classes="subtitle" TextWrapping="Wrap"> <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Classes="subtitle" TextWrapping="Wrap">
Add hints below to help decide where to place this layer when the profile is imported. Add hints below to help decide where to place this layer when the profile is imported.

View File

@ -9,14 +9,14 @@
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.FolderTreeItemView" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.FolderTreeItemView"
x:DataType="profileTree:FolderTreeItemViewModel"> x:DataType="profileTree:FolderTreeItemViewModel">
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto">
<controls:HyperlinkButton Grid.Column="0" <HyperlinkButton Grid.Column="0"
Classes="icon-button icon-button-small broken-state-button" Classes="icon-button icon-button-small broken-state-button"
Margin="0 0 5 0" Margin="0 0 5 0"
Command="{CompiledBinding ShowBrokenStateExceptions}" Command="{CompiledBinding ShowBrokenStateExceptions}"
IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}"> ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}">
<avalonia:MaterialIcon Kind="AlertCircle" /> <avalonia:MaterialIcon Kind="AlertCircle" />
</controls:HyperlinkButton> </HyperlinkButton>
<avalonia:MaterialIcon Grid.Column="1" <avalonia:MaterialIcon Grid.Column="1"
Kind="Folder" Kind="Folder"
Margin="0 0 5 0" Margin="0 0 5 0"

View File

@ -9,14 +9,14 @@
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.LayerTreeItemView" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.LayerTreeItemView"
x:DataType="profileTree:LayerTreeItemViewModel"> x:DataType="profileTree:LayerTreeItemViewModel">
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto">
<controls:HyperlinkButton Grid.Column="0" <HyperlinkButton Grid.Column="0"
Classes="icon-button icon-button-small broken-state-button" Classes="icon-button icon-button-small broken-state-button"
Margin="0 0 5 0" Margin="0 0 5 0"
Command="{CompiledBinding ShowBrokenStateExceptions}" Command="{CompiledBinding ShowBrokenStateExceptions}"
IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}"> ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}">
<avalonia:MaterialIcon Kind="AlertCircle" /> <avalonia:MaterialIcon Kind="AlertCircle" />
</controls:HyperlinkButton> </HyperlinkButton>
<avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" /> <avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" />
<TextBlock Grid.Column="2" Text="{CompiledBinding Layer.Name}" VerticalAlignment="Center" /> <TextBlock Grid.Column="2" Text="{CompiledBinding Layer.Name}" VerticalAlignment="Center" />

View File

@ -42,11 +42,11 @@
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" Foreground="{DynamicResource TextFillColorSecondary}"> <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" Foreground="{DynamicResource TextFillColorSecondary}">
When you enable data bindings you can no longer use keyframes or normal values for this property. When you enable data bindings you can no longer use keyframes or normal values for this property.
</TextBlock> </TextBlock>
<controls:HyperlinkButton HorizontalAlignment="Center" <HyperlinkButton HorizontalAlignment="Center"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings?mtm_campaign=artemis&amp;mtm_kwd=profile-editor" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings?mtm_campaign=artemis&amp;mtm_kwd=profile-editor"
Margin="0 10"> Margin="0 10">
Learn more Learn more
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,36 +0,0 @@
<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:dialogs="clr-namespace:Artemis.UI.Screens.Scripting.Dialogs"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:scriptingProviders="clr-namespace:Artemis.Core.ScriptingProviders;assembly=Artemis.Core"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.Dialogs.ScriptConfigurationCreateView"
x:DataType="dialogs:ScriptConfigurationCreateViewModel">
<Panel>
<StackPanel IsVisible="{CompiledBinding ScriptingProviders.Count}">
<TextBlock Classes="label" Margin="0 5">Script name</TextBlock>
<TextBox Watermark="Name" Text="{CompiledBinding ScriptName}" />
<TextBlock Classes="label" Margin="0 5">Script type</TextBlock>
<ComboBox SelectedItem="{CompiledBinding SelectedScriptingProvider}" ItemsSource="{CompiledBinding ScriptingProviders}" HorizontalAlignment="Stretch">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="scriptingProviders:ScriptingProvider">
<StackPanel Orientation="Horizontal">
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}" Width="16" Height="16" Margin="0 0 5 0" />
<TextBlock Text="{CompiledBinding LanguageName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<TextBlock IsVisible="{CompiledBinding !ScriptingProviders.Count}">
You don't have any scripting providers installed or enabled, therefore you cannot use scripts.
</TextBlock>
</Panel>
</UserControl>

View File

@ -1,13 +0,0 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationCreateView : ReactiveUserControl<ScriptConfigurationCreateViewModel>
{
public ScriptConfigurationCreateView()
{
InitializeComponent();
}
}

View File

@ -1,40 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using Artemis.Core.ScriptingProviders;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationCreateViewModel : ContentDialogViewModelBase
{
[Notify] private string? _scriptName;
[Notify] private ScriptingProvider _selectedScriptingProvider;
public ScriptConfigurationCreateViewModel(IScriptingService scriptingService)
{
ScriptingProviders = new List<ScriptingProvider>(scriptingService.ScriptingProviders);
Submit = ReactiveCommand.Create(ExecuteSubmit, ValidationContext.Valid);
_selectedScriptingProvider = ScriptingProviders.First();
this.ValidationRule(vm => vm.ScriptName, s => !string.IsNullOrWhiteSpace(s), "Script name cannot be empty.");
}
public ScriptConfiguration? ScriptConfiguration { get; private set; }
public List<ScriptingProvider> ScriptingProviders { get; }
public ReactiveCommand<Unit, Unit> Submit { get; }
private void ExecuteSubmit()
{
if (ScriptName == null)
return;
ScriptConfiguration = new ScriptConfiguration(SelectedScriptingProvider, ScriptName, ScriptType.Profile);
ContentDialog?.Hide(ContentDialogResult.Primary);
}
}

View File

@ -1,10 +0,0 @@
<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:dialogs="clr-namespace:Artemis.UI.Screens.Scripting.Dialogs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.Dialogs.ScriptConfigurationEditView"
x:DataType="dialogs:ScriptConfigurationEditViewModel">
<TextBox Watermark="Name" Name="Input" Text="{CompiledBinding ScriptName}" />
</UserControl>

View File

@ -1,19 +0,0 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationEditView : ReactiveUserControl<ScriptConfigurationEditViewModel>
{
public ScriptConfigurationEditView()
{
InitializeComponent();
this.WhenActivated(_ =>
{
Input.Focus();
Input.SelectAll();
});
}
}

View File

@ -1,35 +0,0 @@
using System.Reactive;
using Artemis.Core.ScriptingProviders;
using Artemis.UI.Shared;
using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationEditViewModel : ContentDialogViewModelBase
{
[Notify] private string? _scriptName;
public ScriptConfigurationEditViewModel(ScriptConfiguration scriptConfiguration)
{
ScriptConfiguration = scriptConfiguration;
Submit = ReactiveCommand.Create(ExecuteSubmit, ValidationContext.Valid);
ScriptName = ScriptConfiguration.Name;
this.ValidationRule(vm => vm.ScriptName, s => !string.IsNullOrWhiteSpace(s), "Script name cannot be empty.");
}
public ScriptConfiguration ScriptConfiguration { get; }
public ReactiveCommand<Unit, Unit> Submit { get; }
private void ExecuteSubmit()
{
if (ScriptName == null)
return;
ScriptConfiguration.Name = ScriptName;
ContentDialog?.Hide(ContentDialogResult.Primary);
}
}

View File

@ -1,63 +0,0 @@
using System.Reactive;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.ScriptingProviders;
using Artemis.Core.Services;
using Artemis.UI.Screens.Scripting.Dialogs;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Scripting;
public class ScriptConfigurationViewModel : ActivatableViewModelBase
{
private readonly IScriptingService _scriptingService;
private readonly IWindowService _windowService;
public ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration, IScriptingService scriptingService, IWindowService windowService)
{
_scriptingService = scriptingService;
_windowService = windowService;
ScriptConfiguration = scriptConfiguration;
Script = ScriptConfiguration.Script;
EditScriptConfiguration = ReactiveCommand.CreateFromTask<ScriptConfiguration>(ExecuteEditScriptConfiguration);
ToggleSuspended = ReactiveCommand.Create(() => ScriptConfiguration.IsSuspended = !ScriptConfiguration.IsSuspended);
}
public ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration, IScriptingService scriptingService, IWindowService windowService)
: this(scriptConfiguration, scriptingService, windowService)
{
Profile = profile;
}
public Profile? Profile { get; }
public ScriptConfiguration ScriptConfiguration { get; }
public Script? Script { get; }
public ReactiveCommand<ScriptConfiguration, Unit> EditScriptConfiguration { get; }
public ReactiveCommand<Unit, bool> ToggleSuspended { get; }
private async Task ExecuteEditScriptConfiguration(ScriptConfiguration scriptConfiguration)
{
ContentDialogResult contentDialogResult = await _windowService.CreateContentDialog()
.WithTitle("Edit script")
.WithViewModel(out ScriptConfigurationEditViewModel vm, scriptConfiguration)
.WithCloseButtonText("Cancel")
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Submit))
.HavingSecondaryButton(b => b.WithText("Delete"))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
// Remove the script if the delete button was pressed
if (contentDialogResult == ContentDialogResult.Secondary)
{
if (Profile != null)
_scriptingService.RemoveScript(scriptConfiguration, Profile);
else
_scriptingService.RemoveScript(scriptConfiguration);
}
}
}

View File

@ -1,90 +0,0 @@
<windowing:AppWindow 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:scripting="clr-namespace:Artemis.UI.Screens.Scripting"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.ScriptsDialogView"
x:DataType="scripting:ScriptsDialogViewModel"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Scripts"
Width="1200"
Height="750">
<DockPanel>
<ScrollViewer DockPanel.Dock="Left" VerticalScrollBarVisibility="Auto" Width="300" Margin="10">
<StackPanel>
<ListBox ItemsSource="{CompiledBinding ScriptConfigurations}" SelectedItem="{CompiledBinding SelectedScript}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type scripting:ScriptConfigurationViewModel}">
<Grid ColumnDefinitions="Auto,*,Auto,Auto" RowDefinitions="*,*" Margin="4">
<shared:ArtemisIcon Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="2"
Icon="{CompiledBinding ScriptConfiguration.Script.ScriptingProvider.Plugin.Info.ResolvedIcon, FallbackValue=QuestionMark}"
Width="32 "
Height="32"
Margin="0 0 10 0"
VerticalAlignment="Center" />
<TextBlock Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Center"
Text="{CompiledBinding ScriptConfiguration.Name}"
IsVisible="{CompiledBinding !ScriptConfiguration.HasChanges}"
TextTrimming="CharacterEllipsis" />
<StackPanel Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal"
IsVisible="{CompiledBinding ScriptConfiguration.HasChanges}">
<TextBlock Text="{CompiledBinding ScriptConfiguration.Name}" FontWeight="Bold"></TextBlock>
<TextBlock Text="*"></TextBlock>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{CompiledBinding ScriptConfiguration.Script.ScriptingProvider.LanguageName, FallbackValue='Unknown scripting provider'}"
Classes="subtitle"
FontSize="11"
VerticalAlignment="Center" />
<Button Classes="icon-button icon-button-small"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
ToolTip.Tip="Edit script"
HorizontalAlignment="Right"
Command="{CompiledBinding EditScriptConfiguration}"
CommandParameter="{CompiledBinding ScriptConfiguration}"
Margin="0 0 2 0">
<avalonia:MaterialIcon Kind="Cog" />
</Button>
<Button Classes="icon-button icon-button-small"
Command="{CompiledBinding ToggleSuspended}"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="3"
ToolTip.Tip="Suspend/resume script">
<Panel>
<avalonia:MaterialIcon Kind="EyeOff" IsVisible="{CompiledBinding ScriptConfiguration.IsSuspended}" />
<avalonia:MaterialIcon Kind="Eye" IsVisible="{CompiledBinding !ScriptConfiguration.IsSuspended}" />
</Panel>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add new script"
Margin="10"
HorizontalAlignment="Stretch"
Command="{CompiledBinding AddScriptConfiguration}" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Top" Classes="router-container" Margin="0 10 0 0">
<ContentControl Content="{CompiledBinding ScriptEditorViewModel}" />
</Border>
</DockPanel>
</windowing:AppWindow>

View File

@ -1,34 +0,0 @@
using System.ComponentModel;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Scripting;
public partial class ScriptsDialogView : ReactiveAppWindow<ScriptsDialogViewModel>
{
private bool _canClose;
public ScriptsDialogView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Closing += OnClosing;
}
private async void OnClosing(object? sender, CancelEventArgs e)
{
if (_canClose)
return;
e.Cancel = true;
if (ViewModel == null || await ViewModel.CanClose())
{
_canClose = true;
Close();
}
}
}

View File

@ -1,142 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.ScriptingProviders;
using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Scripting.Dialogs;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using DynamicData;
using DynamicData.Binding;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.Scripting;
public partial class ScriptsDialogViewModel : DialogViewModelBase<object?>
{
private readonly Dictionary<ScriptingProvider, IScriptEditorViewModel> _providerViewModels = new();
private readonly IScriptingService _scriptingService;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _hasScripts;
[Notify] private ReadOnlyObservableCollection<ScriptConfigurationViewModel> _scriptConfigurations;
[Notify] private IScriptEditorViewModel? _scriptEditorViewModel;
[Notify] private ScriptConfigurationViewModel? _selectedScript;
public ScriptsDialogViewModel(IScriptingService scriptingService, IWindowService windowService, IProfileService profileService, IScriptVmFactory scriptVmFactory)
{
_scriptingService = scriptingService;
_windowService = windowService;
ScriptType = ScriptType.Global;
ScriptingProviders = new List<ScriptingProvider>(scriptingService.ScriptingProviders);
AddScriptConfiguration = ReactiveCommand.CreateFromTask(ExecuteAddScriptConfiguration, Observable.Return(ScriptingProviders.Any()));
this.WhenAnyValue(vm => vm.SelectedScript).Subscribe(s => SetupScriptEditor(s?.ScriptConfiguration));
_scriptConfigurations = new ReadOnlyObservableCollection<ScriptConfigurationViewModel>(new ObservableCollection<ScriptConfigurationViewModel>());
// TODO: When not bound to a profile, base the contents of the UI on the ScriptingService
}
public ScriptsDialogViewModel(Profile profile, IScriptingService scriptingService, IWindowService windowService, IProfileService profileService, IScriptVmFactory scriptVmFactory)
: this(scriptingService, windowService, profileService, scriptVmFactory)
{
ScriptType = ScriptType.Profile;
Profile = profile;
this.WhenActivated(d =>
{
_hasScripts = Profile.ScriptConfigurations.ToObservableChangeSet()
.Count()
.Select(c => c > 0)
.ToProperty(this, vm => vm.HasScripts)
.DisposeWith(d);
Profile.ScriptConfigurations.ToObservableChangeSet()
.Transform(c => scriptVmFactory.ScriptConfigurationViewModel(Profile, c))
.Bind(out ReadOnlyObservableCollection<ScriptConfigurationViewModel> scriptConfigurationViewModels)
.Subscribe()
.DisposeWith(d);
ScriptConfigurations = scriptConfigurationViewModels;
SelectedScript = ScriptConfigurations.FirstOrDefault();
Disposable.Create(() => profileService.SaveProfile(Profile, false)).DisposeWith(d);
});
}
public ScriptType ScriptType { get; }
public List<ScriptingProvider> ScriptingProviders { get; }
public Profile? Profile { get; }
public bool HasScripts => _hasScripts?.Value ?? false;
public ReactiveCommand<Unit, Unit> AddScriptConfiguration { get; }
public async Task<bool> CanClose()
{
if (!ScriptConfigurations.Any(s => s.ScriptConfiguration.HasChanges))
return true;
bool result = await _windowService.ShowConfirmContentDialog("Discard changes", "One or more scripts still have pending changes, do you want to discard them?");
if (!result)
return false;
foreach (ScriptConfigurationViewModel scriptConfigurationViewModel in ScriptConfigurations)
scriptConfigurationViewModel.ScriptConfiguration.DiscardPendingChanges();
return true;
}
private void SetupScriptEditor(ScriptConfiguration? scriptConfiguration)
{
if (scriptConfiguration == null)
{
ScriptEditorViewModel = null;
return;
}
// The script is null if the provider is missing
if (scriptConfiguration.Script == null)
{
ScriptEditorViewModel = null;
return;
}
if (!_providerViewModels.TryGetValue(scriptConfiguration.Script.ScriptingProvider, out IScriptEditorViewModel? viewModel))
{
viewModel = scriptConfiguration.Script.ScriptingProvider.CreateScriptEditor(ScriptType);
_providerViewModels.Add(scriptConfiguration.Script.ScriptingProvider, viewModel);
}
ScriptEditorViewModel = viewModel;
ScriptEditorViewModel.ChangeScript(scriptConfiguration.Script);
}
private async Task ExecuteAddScriptConfiguration()
{
await _windowService.CreateContentDialog()
.WithTitle("Add script")
.WithViewModel(out ScriptConfigurationCreateViewModel vm)
.WithCloseButtonText("Cancel")
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Submit))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
if (vm.ScriptConfiguration == null)
return;
// Add the script to the profile and instantiate it
if (Profile != null)
_scriptingService.AddScript(vm.ScriptConfiguration, Profile);
else
_scriptingService.AddScript(vm.ScriptConfiguration);
// Select the new script
SelectedScript = ScriptConfigurations.LastOrDefault();
}
}

View File

@ -25,15 +25,15 @@
</TextBlock> </TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=about"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=about">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" /> <avalonia:MaterialIcon Kind="Github" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=about"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=about">
<avalonia:MaterialIcon Kind="BookOpenOutline" /> <avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<SelectableTextBlock Grid.Row="1" <SelectableTextBlock Grid.Row="1"
@ -42,12 +42,12 @@
Classes="subtitle" Classes="subtitle"
Text="{CompiledBinding Version}" /> Text="{CompiledBinding Version}" />
<controls:HyperlinkButton Grid.Row="1" <HyperlinkButton Grid.Row="1"
Grid.Column="2" Grid.Column="2"
VerticalAlignment="Top" VerticalAlignment="Top"
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE"> NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
PolyForm Noncommercial License 1.0.0 PolyForm Noncommercial License 1.0.0
</controls:HyperlinkButton> </HyperlinkButton>
</Grid> </Grid>
<Border Classes="card" Margin="0 20 0 10"> <Border Classes="card" Margin="0 20 0 10">
@ -65,9 +65,9 @@
Project owner, main contributor Project owner, main contributor
</TextBlock> </TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6"> <StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/RobertBeekman/"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/RobertBeekman/">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -86,9 +86,9 @@
RGB.NET, main contributor RGB.NET, main contributor
</TextBlock> </TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6"> <StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/DarthAffe/"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/DarthAffe/">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -107,9 +107,9 @@
Main contributor Main contributor
</TextBlock> </TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6"> <StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/diogotr7/"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/diogotr7/">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -128,9 +128,9 @@
Graphics design Graphics design
</TextBlock> </TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6"> <StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://kwer.online/"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://kwer.online/">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
@ -185,39 +185,39 @@
<TextBlock Classes="library-name">SQLite</TextBlock> <TextBlock Classes="library-name">SQLite</TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1"> <StackPanel Grid.Column="1">
<controls:HyperlinkButton NavigateUri="https://avaloniaui.net/"> <HyperlinkButton NavigateUri="https://avaloniaui.net/">
https://avaloniaui.net/ https://avaloniaui.net/
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/dadhi/DryIoc"> <HyperlinkButton NavigateUri="https://github.com/dadhi/DryIoc">
https://github.com/dadhi/DryIoc https://github.com/dadhi/DryIoc
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://learn.microsoft.com/en-us/ef/core/"> <HyperlinkButton NavigateUri="https://learn.microsoft.com/en-us/ef/core/">
https://learn.microsoft.com/en-us/ef/core/ https://learn.microsoft.com/en-us/ef/core/
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/amwx/FluentAvalonia"> <HyperlinkButton NavigateUri="https://github.com/amwx/FluentAvalonia">
https://github.com/amwx/FluentAvalonia https://github.com/amwx/FluentAvalonia
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://unosquare.github.io/embedio/"> <HyperlinkButton NavigateUri="https://unosquare.github.io/embedio/">
https://unosquare.github.io/embedio/ https://unosquare.github.io/embedio/
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/Humanizr/Humanizer"> <HyperlinkButton NavigateUri="https://github.com/Humanizr/Humanizer">
https://github.com/Humanizr/Humanizer https://github.com/Humanizr/Humanizer
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/natemcmaster/DotNetCorePlugins"> <HyperlinkButton NavigateUri="https://github.com/natemcmaster/DotNetCorePlugins">
https://github.com/natemcmaster/DotNetCorePlugins https://github.com/natemcmaster/DotNetCorePlugins
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/DarthAffe/RGB.NET"> <HyperlinkButton NavigateUri="https://github.com/DarthAffe/RGB.NET">
https://github.com/DarthAffe/RGB.NET https://github.com/DarthAffe/RGB.NET
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://serilog.net/"> <HyperlinkButton NavigateUri="https://serilog.net/">
https://serilog.net/ https://serilog.net/
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/mono/SkiaSharp"> <HyperlinkButton NavigateUri="https://github.com/mono/SkiaSharp">
https://github.com/mono/SkiaSharp https://github.com/mono/SkiaSharp
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://www.sqlite.org/"> <HyperlinkButton NavigateUri="https://www.sqlite.org/">
https://www.sqlite.org/ https://www.sqlite.org/
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>

View File

@ -17,9 +17,9 @@
<TextBox Classes="clearButton" Text="{CompiledBinding SearchPluginInput}" Watermark="Search plugins" Margin="0 0 10 0" /> <TextBox Classes="clearButton" Text="{CompiledBinding SearchPluginInput}" Watermark="Search plugins" Margin="0 0 10 0" />
<StackPanel Spacing="5" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal"> <StackPanel Spacing="5" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal">
<controls:HyperlinkButton VerticalAlignment="Top" Command="{CompiledBinding GetMorePlugins}"> <HyperlinkButton VerticalAlignment="Top" Command="{CompiledBinding GetMorePlugins}">
Get more plugins Get more plugins
</controls:HyperlinkButton> </HyperlinkButton>
<Button Classes="accent" Command="{CompiledBinding ImportPlugin}">Import plugin</Button> <Button Classes="accent" Command="{CompiledBinding ImportPlugin}">Import plugin</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -29,10 +29,10 @@
TextWrapping="Wrap" TextWrapping="Wrap"
Text="{CompiledBinding Channel, StringFormat='Found no releases for the \'{0}\' channel.'}"> Text="{CompiledBinding Channel, StringFormat='Found no releases for the \'{0}\' channel.'}">
</TextBlock> </TextBlock>
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/channels?mtm_campaign=artemis&amp;mtm_kwd=releases" <HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/channels?mtm_campaign=artemis&amp;mtm_kwd=releases"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
Learn more about channels on the wiki Learn more about channels on the wiki
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<Grid ColumnDefinitions="300,*" Margin="10" IsVisible="{CompiledBinding ReleaseViewModels.Count}"> <Grid ColumnDefinitions="300,*" Margin="10" IsVisible="{CompiledBinding ReleaseViewModels.Count}">

View File

@ -54,7 +54,7 @@
<!-- Bottom buttons --> <!-- Bottom buttons -->
<Border Grid.Row="4" Margin="8" Height="1" Background="{DynamicResource ButtonBorderBrush}"></Border> <Border Grid.Row="4" Margin="8" Height="1" Background="{DynamicResource ButtonBorderBrush}"></Border>
<WrapPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Left" Margin="5 0 5 5"> <WrapPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Left" Margin="5 0 5 5">
<ui:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View website" ToolTip.Tip="View website"
@ -62,8 +62,8 @@
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar"> NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</ui:HyperlinkButton> </HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View GitHub repository" ToolTip.Tip="View GitHub repository"
@ -71,8 +71,8 @@
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://github.com/Artemis-RGB/Artemis"> NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</ui:HyperlinkButton> </HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View Wiki" ToolTip.Tip="View Wiki"
@ -80,8 +80,8 @@
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar"> NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" />
</ui:HyperlinkButton> </HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="Join our Discord" ToolTip.Tip="Join our Discord"
@ -89,8 +89,8 @@
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://discord.gg/S3MVaC9"> NavigateUri="https://discord.gg/S3MVaC9">
<avalonia:MaterialIcon Kind="Chat" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Chat" Width="20" Height="20" />
</ui:HyperlinkButton> </HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button" <HyperlinkButton Classes="icon-button"
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View donation options" ToolTip.Tip="View donation options"
@ -98,7 +98,7 @@
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&amp;mtm_kwd=sidebar"> NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" />
</ui:HyperlinkButton> </HyperlinkButton>
</WrapPanel> </WrapPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -33,18 +33,18 @@
<TextBlock Classes="link-name">Discord</TextBlock> <TextBlock Classes="link-name">Discord</TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1"> <StackPanel Grid.Column="1">
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&amp;mtm_kwd=wizard"> <HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&amp;mtm_kwd=wizard">
https://wiki.artemis-rgb.com/ https://wiki.artemis-rgb.com/
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction?mtm_campaign=artemis&amp;mtm_kwd=wizard"> <HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction?mtm_campaign=artemis&amp;mtm_kwd=wizard">
https://wiki.artemis-rgb.com/en/guides/user/introduction https://wiki.artemis-rgb.com/en/guides/user/introduction
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis"> <HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis">
https://github.com/Artemis-RGB/Artemis https://github.com/Artemis-RGB/Artemis
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://discord.gg/S3MVaC9"> <HyperlinkButton NavigateUri="https://discord.gg/S3MVaC9">
https://discord.gg/S3MVaC9 https://discord.gg/S3MVaC9
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>

View File

@ -16,15 +16,15 @@
</TextBlock> </TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" /> <avalonia:MaterialIcon Kind="Github" />
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="BookOpenOutline" /> <avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"
@ -33,12 +33,12 @@
Classes="subtitle" Classes="subtitle"
Text="{CompiledBinding Version}" /> Text="{CompiledBinding Version}" />
<controls:HyperlinkButton Grid.Row="1" <HyperlinkButton Grid.Row="1"
Grid.Column="2" Grid.Column="2"
VerticalAlignment="Top" VerticalAlignment="Top"
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE"> NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
PolyForm Noncommercial License 1.0.0 PolyForm Noncommercial License 1.0.0
</controls:HyperlinkButton> </HyperlinkButton>
</Grid> </Grid>
<Border Classes="card"> <Border Classes="card">

View File

@ -169,13 +169,13 @@
<TextBlock Classes="h4" Text="{CompiledBinding NodeScript.Name}" /> <TextBlock Classes="h4" Text="{CompiledBinding NodeScript.Name}" />
<TextBlock Classes="subtitle" Margin="10 0 0 13" Text="{CompiledBinding NodeScript.Description}" VerticalAlignment="Bottom" /> <TextBlock Classes="subtitle" Margin="10 0 0 13" Text="{CompiledBinding NodeScript.Description}" VerticalAlignment="Bottom" />
</StackPanel> </StackPanel>
<controls:HyperlinkButton Grid.Row="1" <HyperlinkButton Grid.Row="1"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Right" HorizontalAlignment="Right"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes?mtm_campaign=artemis&amp;mtm_kwd=script-editor"> NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes?mtm_campaign=artemis&amp;mtm_kwd=script-editor">
Learn more about visual scripts Learn more about visual scripts
</controls:HyperlinkButton> </HyperlinkButton>
<Border Classes="card-condensed" Grid.Row="2" Grid.ColumnSpan="2"> <Border Classes="card-condensed" Grid.Row="2" Grid.ColumnSpan="2">
<ContentControl Content="{CompiledBinding NodeScriptViewModel}" /> <ContentControl Content="{CompiledBinding NodeScriptViewModel}" />

View File

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

View File

@ -43,7 +43,7 @@
<TextBlock Grid.Column="1" Grid.Row="0" Text="{CompiledBinding Name}" Margin="0 4 0 0"></TextBlock> <TextBlock Grid.Column="1" Grid.Row="0" Text="{CompiledBinding Name}" Margin="0 4 0 0"></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="1" Text="{CompiledBinding Email}"></TextBlock> <TextBlock Grid.Column="1" Grid.Row="1" Text="{CompiledBinding Email}"></TextBlock>
<controls:HyperlinkButton <HyperlinkButton
IsVisible="{CompiledBinding AllowLogout}" IsVisible="{CompiledBinding AllowLogout}"
Grid.Column="1" Grid.Column="1"
Grid.Row="3" Grid.Row="3"
@ -51,8 +51,8 @@
Padding="6 4" Padding="6 4"
Click="Manage_OnClick"> Click="Manage_OnClick">
Manage account Manage account
</controls:HyperlinkButton> </HyperlinkButton>
<controls:HyperlinkButton <HyperlinkButton
IsVisible="{CompiledBinding AllowLogout}" IsVisible="{CompiledBinding AllowLogout}"
Grid.Column="1" Grid.Column="1"
Grid.Row="2" Grid.Row="2"
@ -60,7 +60,7 @@
Padding="6 4" Padding="6 4"
Click="Signout_OnClick"> Click="Signout_OnClick">
Sign out Sign out
</controls:HyperlinkButton> </HyperlinkButton>
</Grid> </Grid>
</Flyout> </Flyout>
</Ellipse.ContextFlyout> </Ellipse.ContextFlyout>

View File

@ -0,0 +1,76 @@
<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:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
mc:Ignorable="d"
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListItemVerticalView"
x:DataType="list:EntryListItemViewModel">
<UserControl.Styles>
<StyleInclude Source="/Screens/Workshop/Search/SearchViewStyles.axaml" />
</UserControl.Styles>
<UserControl.Resources>
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
</UserControl.Resources>
<Button Command="{CompiledBinding NavigateToEntry}" Padding="12">
<Grid RowDefinitions="Auto,43,Auto,Auto,*" ColumnDefinitions="Auto,*" Width="150" Height="200">
<Border Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
CornerRadius="6"
Width="60"
Height="60"
Margin="0 5"
ClipToBounds="True"
HorizontalAlignment="Left">
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
</Border>
<TextBlock Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
FontSize="16"
VerticalAlignment="Center"
Text="{CompiledBinding Entry.Name, FallbackValue=Title that is super long and should be wrapped}" />
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="0 1">
<TextBlock Classes="subtitle" MaxLines="1" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}"/>
<avalonia:MaterialIcon IsVisible="{CompiledBinding Entry.IsOfficial}"
Kind="ShieldStar"
Foreground="{DynamicResource SystemAccentColorLight1}"
Margin="2 0 0 0"
Width="18"
Height="18"
HorizontalAlignment="Left"
ToolTip.Tip="Official entry by the Artemis team" />
</StackPanel>
<TextBlock Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
FontSize="12"
Opacity="0.8"
Text="{CompiledBinding Entry.Summary, FallbackValue=Summary that is super long and should be wrapped}" />
<Border Grid.Row="4" Grid.Column="0" Classes="badge" VerticalAlignment="Bottom" HorizontalAlignment="Left">
<TextBlock>
<Run Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
<Run>downloads</Run>
</TextBlock>
</Border>
<Border Grid.Row="4" Grid.Column="1" Classes="badge" VerticalAlignment="Bottom" HorizontalAlignment="Right">
<TextBlock Text="{CompiledBinding Entry.EntryType, FallbackValue=Type}"></TextBlock>
</Border>
</Grid>
</Button>
</UserControl>

View File

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.List;
public partial class EntryListItemVerticalView : ReactiveUserControl<EntryListItemVerticalViewModel>
{
public EntryListItemVerticalView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,13 @@
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Services;
namespace Artemis.UI.Screens.Workshop.Entries.List;
public class EntryListItemVerticalViewModel : EntryListItemViewModel
{
/// <inheritdoc />
public EntryListItemVerticalViewModel(IEntrySummary entry, IRouter router, IWorkshopService workshopService) : base(entry, router, workshopService)
{
}
}

View File

@ -3,7 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:entries1="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List" xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"

View File

@ -10,7 +10,7 @@
<Border Classes="router-container"> <Border Classes="router-container">
<Grid RowDefinitions="200,*,*"> <Grid RowDefinitions="200,*,*">
<ProgressBar ZIndex="999" IsIndeterminate="True" IsVisible="{CompiledBinding !WorkshopReachable}" Grid.Row="0" VerticalAlignment="Top"></ProgressBar> <ProgressBar ZIndex="999" IsIndeterminate="True" IsVisible="{CompiledBinding !WorkshopReachable}" Grid.Row="0" VerticalAlignment="Top"></ProgressBar>
<Image Grid.Row="0" <Image Grid.Row="0"
Grid.RowSpan="2" Grid.RowSpan="2"
VerticalAlignment="Top" VerticalAlignment="Top"
@ -38,7 +38,7 @@
<DropShadowEffect Color="Black" OffsetX="2" OffsetY="2" BlurRadius="5"></DropShadowEffect> <DropShadowEffect Color="Black" OffsetX="2" OffsetY="2" BlurRadius="5"></DropShadowEffect>
</TextBlock.Effect> </TextBlock.Effect>
</TextBlock> </TextBlock>
<StackPanel Margin="30 -75 30 0" Grid.Row="1"> <StackPanel Margin="30 -75 30 0" Grid.Row="1">
<StackPanel Spacing="10" Orientation="Horizontal" VerticalAlignment="Top"> <StackPanel Spacing="10" Orientation="Horizontal" VerticalAlignment="Top">
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/entries/profiles" VerticalContentAlignment="Top"> <Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/entries/profiles" VerticalContentAlignment="Top">
@ -56,7 +56,7 @@
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Layouts make your devices look great in the editor.</TextBlock> <TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Layouts make your devices look great in the editor.</TextBlock>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/entries/plugins" VerticalContentAlignment="Top"> <Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/entries/plugins" VerticalContentAlignment="Top">
<StackPanel> <StackPanel>
<avalonia:MaterialIcon Kind="Connection" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" /> <avalonia:MaterialIcon Kind="Connection" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
@ -64,7 +64,7 @@
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Plugins add new functionality to Artemis.</TextBlock> <TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Plugins add new functionality to Artemis.</TextBlock>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/library" VerticalContentAlignment="Top"> <Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/library" VerticalContentAlignment="Top">
<StackPanel> <StackPanel>
<avalonia:MaterialIcon Kind="Bookshelf" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" /> <avalonia:MaterialIcon Kind="Bookshelf" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
@ -72,7 +72,7 @@
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Manage your submissions and downloaded content.</TextBlock> <TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Manage your submissions and downloaded content.</TextBlock>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Width="150" Height="180" Command="{CompiledBinding AddSubmission}" VerticalContentAlignment="Top"> <Button Width="150" Height="180" Command="{CompiledBinding AddSubmission}" VerticalContentAlignment="Top">
<StackPanel> <StackPanel>
<avalonia:MaterialIcon Kind="CloudUpload" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" /> <avalonia:MaterialIcon Kind="CloudUpload" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
@ -85,10 +85,26 @@
</StackPanel> </StackPanel>
<TextBlock Classes="h4" Margin="0 15 0 5">Featured submissions</TextBlock> <TextBlock Classes="h4" Margin="0 15 0 5">Featured submissions</TextBlock>
<TextBlock>Not yet implemented, here we'll show submissions we think are worth some extra attention.</TextBlock> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Padding="0 0 0 20">
<ItemsControl ItemsSource="{CompiledBinding PopularEntries}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<TextBlock Classes="h4" Margin="0 15 0 5">Recently updated</TextBlock> <TextBlock Classes="h4" Margin="0 15 0 5">Recently added</TextBlock>
<TextBlock>Not yet implemented, here we'll a few of the most recent uploads/updates to the workshop.</TextBlock> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Padding="0 0 0 20">
<ItemsControl ItemsSource="{CompiledBinding LatestEntries}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>

View File

@ -1,33 +1,70 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive; using System.Reactive;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.UI.Extensions; using Artemis.UI.Extensions;
using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Screens.Workshop.SubmissionWizard; using Artemis.UI.Screens.Workshop.SubmissionWizard;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Services; using Artemis.WebClient.Workshop.Services;
using DynamicData;
using PropertyChanged.SourceGenerator; using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Home; namespace Artemis.UI.Screens.Workshop.Home;
public partial class WorkshopHomeViewModel : RoutableScreen public partial class WorkshopHomeViewModel : RoutableScreen
{ {
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
[Notify(Setter.Private)] private bool _workshopReachable; [Notify(Setter.Private)] private bool _workshopReachable;
public WorkshopHomeViewModel(IRouter router, IWindowService windowService, IWorkshopService workshopService) public WorkshopHomeViewModel(IRouter router, IWindowService windowService, IWorkshopService workshopService, IWorkshopClient client,
Func<IEntrySummary, EntryListItemVerticalViewModel> getEntryListItemViewModel)
{ {
_windowService = windowService; _windowService = windowService;
SourceList<IEntrySummary> latest = new();
SourceList<IEntrySummary> popular = new();
latest.Connect().Transform(getEntryListItemViewModel).Bind(out ReadOnlyObservableCollection<EntryListItemVerticalViewModel> latestEntries).Subscribe();
popular.Connect().Transform(getEntryListItemViewModel).Bind(out ReadOnlyObservableCollection<EntryListItemVerticalViewModel> popularEntries).Subscribe();
AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission, this.WhenAnyValue(vm => vm.WorkshopReachable)); AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission, this.WhenAnyValue(vm => vm.WorkshopReachable));
Navigate = ReactiveCommand.CreateFromTask<string>(async r => await router.Navigate(r), this.WhenAnyValue(vm => vm.WorkshopReachable)); Navigate = ReactiveCommand.CreateFromTask<string>(async r => await router.Navigate(r), this.WhenAnyValue(vm => vm.WorkshopReachable));
PopularEntries = popularEntries;
LatestEntries = latestEntries;
this.WhenActivatedAsync(async d => WorkshopReachable = await workshopService.ValidateWorkshopStatus(d.AsCancellationToken())); this.WhenActivatedAsync(async d =>
{
WorkshopReachable = await workshopService.ValidateWorkshopStatus(d.AsCancellationToken());
IOperationResult<IGetPopularEntriesResult> popularResult = await client.GetPopularEntries.ExecuteAsync();
popular.Edit(p =>
{
p.Clear();
if (popularResult.Data?.PopularEntries != null)
p.AddRange(popularResult.Data.PopularEntries.Take(8));
});
IOperationResult<IGetEntriesv2Result> latestResult = await client.GetEntriesv2.ExecuteAsync(null, null, [new EntrySortInput {CreatedAt = SortEnumType.Desc}], 8, null);
latest.Edit(l =>
{
l.Clear();
if (latestResult.Data?.EntriesV2?.Edges != null)
l.AddRange(latestResult.Data.EntriesV2.Edges.Select(e => e.Node));
});
});
} }
public ReactiveCommand<Unit, Unit> AddSubmission { get; } public ReactiveCommand<Unit, Unit> AddSubmission { get; }
public ReactiveCommand<string, Unit> Navigate { get; } public ReactiveCommand<string, Unit> Navigate { get; }
public ReadOnlyObservableCollection<EntryListItemVerticalViewModel> PopularEntries { get; }
public ReadOnlyObservableCollection<EntryListItemVerticalViewModel> LatestEntries { get; }
private async Task ExecuteAddSubmission(CancellationToken arg) private async Task ExecuteAddSubmission(CancellationToken arg)
{ {

View File

@ -70,9 +70,9 @@
</ListBox> </ListBox>
</StackPanel> </StackPanel>
</Border> </Border>
<controls:HyperlinkButton Command="{CompiledBinding ViewWorkshopPage}" HorizontalAlignment="Center"> <HyperlinkButton Command="{CompiledBinding ViewWorkshopPage}" HorizontalAlignment="Center">
View workshop page View workshop page
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<controls:Frame Grid.Column="1" Grid.Row="0" Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0"> <controls:Frame Grid.Column="1" Grid.Row="0" Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">

View File

@ -21,9 +21,9 @@
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}"/> <TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}"/>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard">
<avalonia:MaterialIcon Kind="BookOpenOutline" /> <avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"

View File

@ -39,7 +39,7 @@
<shared:EnumComboBox Value="{CompiledBinding PhysicalLayout}"></shared:EnumComboBox> <shared:EnumComboBox Value="{CompiledBinding PhysicalLayout}"></shared:EnumComboBox>
</StackPanel> </StackPanel>
<controls:HyperlinkButton Grid.Row="1" <HyperlinkButton Grid.Row="1"
Grid.Column="0" Grid.Column="0"
IsVisible="{CompiledBinding IsKeyboardLayout}" IsVisible="{CompiledBinding IsKeyboardLayout}"
Margin="0 10 0 0" Margin="0 10 0 0"
@ -47,7 +47,7 @@
HorizontalAlignment="Right" HorizontalAlignment="Right"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts/keyboard-layouts?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard"> NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts/keyboard-layouts?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard">
Learn about physical layouts Learn about physical layouts
</controls:HyperlinkButton> </HyperlinkButton>
<ScrollViewer Grid.Row="2" <ScrollViewer Grid.Row="2"
Grid.Column="0" Grid.Column="0"

View File

@ -24,12 +24,12 @@
</TextBlock> </TextBlock>
<controls:HyperlinkButton Grid.Row="0" <HyperlinkButton Grid.Row="0"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Top" VerticalAlignment="Top"
NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard"> NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard">
Learn more about adaption hints Learn more about adaption hints
</controls:HyperlinkButton> </HyperlinkButton>
<ScrollViewer Grid.Row="2" <ScrollViewer Grid.Row="2"
Grid.Column="0" Grid.Column="0"

View File

@ -22,9 +22,9 @@
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}" /> <TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}" />
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard"> <HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard">
<avalonia:MaterialIcon Kind="BookOpenOutline" /> <avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton> </HyperlinkButton>
</StackPanel> </StackPanel>
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"

View File

@ -22,7 +22,8 @@ public class WorkshopUpdateService : IWorkshopUpdateService
private readonly Lazy<IUpdateNotificationProvider> _updateNotificationProvider; private readonly Lazy<IUpdateNotificationProvider> _updateNotificationProvider;
private readonly PluginSetting<bool> _showNotifications; private readonly PluginSetting<bool> _showNotifications;
public WorkshopUpdateService(ILogger logger, IWorkshopClient client, IWorkshopService workshopService, ISettingsService settingsService, Lazy<IUpdateNotificationProvider> updateNotificationProvider) public WorkshopUpdateService(ILogger logger, IWorkshopClient client, IWorkshopService workshopService, ISettingsService settingsService,
Lazy<IUpdateNotificationProvider> updateNotificationProvider)
{ {
_logger = logger; _logger = logger;
_client = client; _client = client;
@ -56,20 +57,19 @@ public class WorkshopUpdateService : IWorkshopUpdateService
public async Task<bool> AutoUpdateEntry(InstalledEntry installedEntry) public async Task<bool> AutoUpdateEntry(InstalledEntry installedEntry)
{ {
// Query the latest version
IOperationResult<IGetEntryLatestReleaseByIdResult> latestReleaseResult = await _client.GetEntryLatestReleaseById.ExecuteAsync(installedEntry.Id);
IEntrySummary? entry = latestReleaseResult.Data?.Entry?.LatestRelease?.Entry;
if (entry == null)
return false;
if (latestReleaseResult.Data?.Entry?.LatestRelease is not IRelease latestRelease)
return false;
if (latestRelease.Id == installedEntry.ReleaseId)
return false;
_logger.Information("Auto-updating entry {Entry} to version {Version}", entry, latestRelease.Version);
try try
{ {
// Query the latest version
IOperationResult<IGetEntryLatestReleaseByIdResult> latestReleaseResult = await _client.GetEntryLatestReleaseById.ExecuteAsync(installedEntry.Id);
IEntrySummary? entry = latestReleaseResult.Data?.Entry?.LatestRelease?.Entry;
if (entry == null)
return false;
if (latestReleaseResult.Data?.Entry?.LatestRelease is not IRelease latestRelease)
return false;
if (latestRelease.Id == installedEntry.ReleaseId)
return false;
_logger.Information("Auto-updating entry {Entry} to version {Version}", entry, latestRelease.Version);
EntryInstallResult updateResult = await _workshopService.InstallEntry(entry, latestRelease, new Progress<StreamProgress>(), CancellationToken.None); EntryInstallResult updateResult = await _workshopService.InstallEntry(entry, latestRelease, new Progress<StreamProgress>(), CancellationToken.None);
// This happens during installation too but not on our reference of the entry // This happens during installation too but not on our reference of the entry
@ -85,7 +85,7 @@ public class WorkshopUpdateService : IWorkshopUpdateService
} }
catch (Exception e) catch (Exception e)
{ {
_logger.Warning(e, "Auto-update failed for entry {Entry}", entry); _logger.Warning(e, "Auto-update failed for entry {Entry}", installedEntry);
} }
return false; return false;

View File

@ -27,7 +27,7 @@
<Setter Property="SelectionForeground" Value="{DynamicResource TextOnAccentFillColorSelectedTextBrush}" /> <Setter Property="SelectionForeground" Value="{DynamicResource TextOnAccentFillColorSelectedTextBrush}" />
</Style> </Style>
<Style Selector="controls|ContentDialog.fullscreen controls|FABorder#BackgroundElement"> <Style Selector="controls|ContentDialog.fullscreen Border#BackgroundElement">
<Setter Property="MaxWidth" Value="99999"></Setter> <Setter Property="MaxWidth" Value="99999"></Setter>
<Setter Property="MaxHeight" Value="99999"></Setter> <Setter Property="MaxHeight" Value="99999"></Setter>
<Setter Property="Margin" Value="100"></Setter> <Setter Property="Margin" Value="100"></Setter>

View File

@ -1,18 +1,9 @@
query GetEntries($search: String $filter: EntryFilterInput $skip: Int $take: Int $order: [EntrySortInput!]) {
entries(search: $search where: $filter skip: $skip take: $take, order: $order) {
totalCount
items {
...entrySummary
}
}
}
query GetEntriesv2($search: String $filter: EntryFilterInput $order: [EntrySortInput!] $first: Int $after: String) { query GetEntriesv2($search: String $filter: EntryFilterInput $order: [EntrySortInput!] $first: Int $after: String) {
entriesV2(search: $search where: $filter order: $order first: $first after: $after) { entriesV2(search: $search where: $filter order: $order first: $first after: $after) {
totalCount totalCount
pageInfo { pageInfo {
hasNextPage hasNextPage
endCursor endCursor
} }
edges { edges {
cursor cursor
@ -21,4 +12,10 @@ query GetEntriesv2($search: String $filter: EntryFilterInput $order: [EntrySortI
} }
} }
} }
}
query GetPopularEntries {
popularEntries {
...entrySummary
}
} }

View File

@ -61,12 +61,14 @@ type Entry {
iconId: UUID iconId: UUID
id: Long! id: Long!
images: [Image!]! images: [Image!]!
isDefault: Boolean!
isOfficial: Boolean! isOfficial: Boolean!
latestRelease: Release latestRelease: Release
latestReleaseId: Long latestReleaseId: Long
layoutInfo: [LayoutInfo!]! layoutInfo: [LayoutInfo!]!
name: String! name: String!
pluginInfo: PluginInfo pluginInfo: PluginInfo
popularityScore: Float!
releases: [Release!]! releases: [Release!]!
summary: String! summary: String!
tags: [Tag!]! tags: [Tag!]!
@ -146,7 +148,7 @@ type PluginInfosCollectionSegment {
type Query { type Query {
categories(order: [CategorySortInput!], where: CategoryFilterInput): [Category!]! categories(order: [CategorySortInput!], where: CategoryFilterInput): [Category!]!
entries(order: [EntrySortInput!], search: String, skip: Int, take: Int, where: EntryFilterInput): EntriesCollectionSegment entries(order: [EntrySortInput!], popular: Boolean, search: String, skip: Int, take: Int, where: EntryFilterInput): EntriesCollectionSegment
entriesV2( entriesV2(
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
after: String, after: String,
@ -157,12 +159,14 @@ type Query {
"Returns the last _n_ elements from the list." "Returns the last _n_ elements from the list."
last: Int, last: Int,
order: [EntrySortInput!], order: [EntrySortInput!],
popular: Boolean,
search: String, search: String,
where: EntryFilterInput where: EntryFilterInput
): EntriesV2Connection ): EntriesV2Connection
entry(id: Long!): Entry entry(id: Long!): Entry
pluginInfo(pluginGuid: UUID!): PluginInfo pluginInfo(pluginGuid: UUID!): PluginInfo
pluginInfos(order: [PluginInfoSortInput!], skip: Int, take: Int, where: PluginInfoFilterInput): PluginInfosCollectionSegment pluginInfos(order: [PluginInfoSortInput!], skip: Int, take: Int, where: PluginInfoFilterInput): PluginInfosCollectionSegment
popularEntries(where: EntryFilterInput): [Entry!]!
release(id: Long!): Release release(id: Long!): Release
searchEntries(input: String!, order: [EntrySortInput!], type: EntryType, where: EntryFilterInput): [Entry!]! searchEntries(input: String!, order: [EntrySortInput!], type: EntryType, where: EntryFilterInput): [Entry!]!
searchKeyboardLayout(deviceProvider: UUID!, logicalLayout: String, model: String!, physicalLayout: KeyboardLayoutType!, vendor: String!): LayoutInfo searchKeyboardLayout(deviceProvider: UUID!, logicalLayout: String, model: String!, physicalLayout: KeyboardLayoutType!, vendor: String!): LayoutInfo
@ -268,6 +272,7 @@ input CreateEntryInput {
categories: [Long!]! categories: [Long!]!
description: String! description: String!
entryType: EntryType! entryType: EntryType!
isDefault: Boolean!
name: String! name: String!
summary: String! summary: String!
tags: [String!]! tags: [String!]!
@ -312,6 +317,7 @@ input EntryFilterInput {
iconId: UuidOperationFilterInput iconId: UuidOperationFilterInput
id: LongOperationFilterInput id: LongOperationFilterInput
images: ListFilterInputTypeOfImageFilterInput images: ListFilterInputTypeOfImageFilterInput
isDefault: BooleanOperationFilterInput
isOfficial: BooleanOperationFilterInput isOfficial: BooleanOperationFilterInput
latestRelease: ReleaseFilterInput latestRelease: ReleaseFilterInput
latestReleaseId: LongOperationFilterInput latestReleaseId: LongOperationFilterInput
@ -319,6 +325,7 @@ input EntryFilterInput {
name: StringOperationFilterInput name: StringOperationFilterInput
or: [EntryFilterInput!] or: [EntryFilterInput!]
pluginInfo: PluginInfoFilterInput pluginInfo: PluginInfoFilterInput
popularityScore: FloatOperationFilterInput
releases: ListFilterInputTypeOfReleaseFilterInput releases: ListFilterInputTypeOfReleaseFilterInput
summary: StringOperationFilterInput summary: StringOperationFilterInput
tags: ListFilterInputTypeOfTagFilterInput tags: ListFilterInputTypeOfTagFilterInput
@ -334,11 +341,13 @@ input EntrySortInput {
icon: ImageSortInput icon: ImageSortInput
iconId: SortEnumType iconId: SortEnumType
id: SortEnumType id: SortEnumType
isDefault: SortEnumType
isOfficial: SortEnumType isOfficial: SortEnumType
latestRelease: ReleaseSortInput latestRelease: ReleaseSortInput
latestReleaseId: SortEnumType latestReleaseId: SortEnumType
name: SortEnumType name: SortEnumType
pluginInfo: PluginInfoSortInput pluginInfo: PluginInfoSortInput
popularityScore: SortEnumType
summary: SortEnumType summary: SortEnumType
} }
@ -349,6 +358,21 @@ input EntryTypeOperationFilterInput {
nin: [EntryType!] nin: [EntryType!]
} }
input FloatOperationFilterInput {
eq: Float
gt: Float
gte: Float
in: [Float]
lt: Float
lte: Float
neq: Float
ngt: Float
ngte: Float
nin: [Float]
nlt: Float
nlte: Float
}
input ImageFilterInput { input ImageFilterInput {
and: [ImageFilterInput!] and: [ImageFilterInput!]
description: StringOperationFilterInput description: StringOperationFilterInput
@ -580,6 +604,7 @@ input UpdateEntryInput {
categories: [Long!]! categories: [Long!]!
description: String! description: String!
id: Long! id: Long!
isDefault: Boolean!
name: String! name: String!
summary: String! summary: String!
tags: [String!]! tags: [String!]!

View File

@ -240,6 +240,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=activatable/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=activatable/@EntryIndexedValue">True</s:Boolean>

View File

@ -4,62 +4,63 @@
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled> <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.2.1" /> <PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
<PackageVersion Include="Avalonia" Version="11.0.11" /> <PackageVersion Include="Avalonia" Version="11.2.0" />
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.0.6" /> <PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
<PackageVersion Include="Avalonia.Controls.ItemsRepeater" Version="11.0.11" /> <PackageVersion Include="Avalonia.Controls.ItemsRepeater" Version="11.1.4" />
<PackageVersion Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.3" /> <PackageVersion Include="Avalonia.Controls.PanAndZoom" Version="11.2.0" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.11" /> <PackageVersion Include="Avalonia.Desktop" Version="11.2.0" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.9" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.0.9" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.0.11" /> <PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.0" />
<PackageVersion Include="Avalonia.Skia.Lottie" Version="11.0.0" /> <PackageVersion Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageVersion Include="Avalonia.Win32" Version="11.0.11" /> <PackageVersion Include="Avalonia.Win32" Version="11.2.0" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="8.0.0" /> <PackageVersion Include="HPPH.SkiaSharp" Version="1.0.0" />
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.0.10.9" /> <PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.0" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.0.6" /> <PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.2.0" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.1.0" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" /> <PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageVersion Include="DryIoc.dll" Version="5.4.3" /> <PackageVersion Include="DryIoc.dll" Version="5.4.3" />
<PackageVersion Include="DynamicData" Version="8.4.1" /> <PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="EmbedIO" Version="3.5.2" /> <PackageVersion Include="EmbedIO" Version="3.5.2" />
<PackageVersion Include="FluentAvalonia.ProgressRing" Version="1.69.2" /> <PackageVersion Include="FluentAvalonia.ProgressRing" Version="1.69.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.1.0" />
<PackageVersion Include="HidSharp" Version="2.1.0" /> <PackageVersion Include="HidSharp" Version="2.1.0" />
<PackageVersion Include="Humanizer.Core" Version="2.14.1" /> <PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="IdentityModel" Version="7.0.0" /> <PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" /> <PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="LiteDB" Version="5.0.17" /> <PackageVersion Include="LiteDB" Version="5.0.21" />
<PackageVersion Include="Markdown.Avalonia.Tight" Version="11.0.2" /> <PackageVersion Include="Markdown.Avalonia.Tight" Version="11.0.2" />
<PackageVersion Include="Material.Icons.Avalonia" Version="2.1.10" /> <PackageVersion Include="Material.Icons.Avalonia" Version="2.1.10" />
<PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.2" /> <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6"> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion> </PackageVersion>
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageVersion Include="Microsoft.Win32" Version="2.0.1" /> <PackageVersion Include="Microsoft.Win32" Version="2.0.1" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="8.0.6" /> <PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.0" />
<PackageVersion Include="NoStringEvaluating" Version="2.5.2" /> <PackageVersion Include="NoStringEvaluating" Version="2.5.2" />
<PackageVersion Include="Octopus.Octodiff" Version="2.0.546" /> <PackageVersion Include="Octopus.Octodiff" Version="2.0.547" />
<PackageVersion Include="PropertyChanged.SourceGenerator" Version="1.1.0" /> <PackageVersion Include="PropertyChanged.SourceGenerator" Version="1.1.0" />
<PackageVersion Include="RGB.NET.Core" Version="2.1.0" /> <PackageVersion Include="RGB.NET.Core" Version="3.0.0-prerelease.3" />
<PackageVersion Include="RGB.NET.Layout" Version="2.1.0" /> <PackageVersion Include="RGB.NET.Layout" Version="3.0.0-prerelease.3" />
<PackageVersion Include="RGB.NET.Presets" Version="2.1.0" /> <PackageVersion Include="RGB.NET.Presets" Version="3.0.0-prerelease.3" />
<PackageVersion Include="RawInput.Sharp" Version="0.1.3" /> <PackageVersion Include="RawInput.Sharp" Version="0.1.3" />
<PackageVersion Include="ReactiveUI" Version="20.1.1" /> <PackageVersion Include="ReactiveUI" Version="20.1.63" />
<PackageVersion Include="ReactiveUI.Validation" Version="4.0.9" /> <PackageVersion Include="ReactiveUI.Validation" Version="4.1.1" />
<PackageVersion Include="Serilog" Version="4.0.0" /> <PackageVersion Include="Serilog" Version="4.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" /> <PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.8" /> <PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.8" /> <PackageVersion Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.9" />
<PackageVersion Include="Splat.DryIoc" Version="15.1.1" /> <PackageVersion Include="Splat.DryIoc" Version="15.2.22" />
<PackageVersion Include="StrawberryShake.Server" Version="13.9.6" /> <PackageVersion Include="StrawberryShake.Server" Version="14.1.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.6.2" /> <PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.2.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.3" /> <PackageVersion Include="System.Text.Json" Version="9.0.0" />
<PackageVersion Include="TextMateSharp.Grammars" Version="1.0.57" /> <PackageVersion Include="TextMateSharp.Grammars" Version="1.0.64" />
</ItemGroup> </ItemGroup>
</Project> </Project>