mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 13:28:33 +00:00
Compare commits
13 Commits
972f1c638c
...
4c6ca9b511
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c6ca9b511 | ||
|
|
25786c6951 | ||
|
|
d725234d56 | ||
|
|
37f973b093 | ||
|
|
7691af95b9 | ||
|
|
907c758b83 | ||
|
|
d2afc77bb8 | ||
|
|
a6e75d7a40 | ||
| bedf1f5f38 | |||
|
|
ec5fbba87c | ||
|
|
1e8c68bbeb | ||
| 5d82c159e1 | |||
| db84f1dc75 |
@ -39,8 +39,9 @@
|
||||
<PackageReference Include="DryIoc.dll" />
|
||||
<PackageReference Include="EmbedIO" />
|
||||
<PackageReference Include="HidSharp" />
|
||||
<PackageReference Include="HPPH.SkiaSharp" />
|
||||
<PackageReference Include="Humanizer.Core" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All"/>
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" />
|
||||
<PackageReference Include="RGB.NET.Core" />
|
||||
<PackageReference Include="RGB.NET.Layout" />
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
using SkiaSharp;
|
||||
using HPPH;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Artemis.Core.ColorScience;
|
||||
|
||||
@ -10,13 +12,27 @@ namespace Artemis.Core.ColorScience;
|
||||
/// </summary>
|
||||
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>
|
||||
/// Quantizes a span of colors into the desired amount of representative colors.
|
||||
/// </summary>
|
||||
/// <param name="colors">The colors to quantize</param>
|
||||
/// <param name="amount">How many colors to return. Must be a power of two.</param>
|
||||
/// <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))
|
||||
throw new ArgumentException("Must be power of two", nameof(amount));
|
||||
@ -24,38 +40,19 @@ public static class ColorQuantizer
|
||||
int splits = BitOperations.Log2((uint)amount);
|
||||
return QuantizeSplit(colors, splits);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Quantizes a span of colors, splitting the average <paramref name="splits"/> number of times.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <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.");
|
||||
|
||||
Span<ColorCube> cubes = new ColorCube[1 << splits];
|
||||
cubes[0] = new ColorCube(colors, 0, colors.Length, SortTarget.None);
|
||||
|
||||
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;
|
||||
// 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
|
||||
return MemoryMarshal.Cast<ColorBGRA, SKColor>(MemoryMarshal.Cast<SKColor, ColorBGRA>(colors).CreateSimpleColorPalette(1 << splits)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Artemis.Core.ColorScience;
|
||||
|
||||
internal enum SortTarget
|
||||
{
|
||||
None, Red, Green, Blue
|
||||
}
|
||||
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using SkiaSharp;
|
||||
|
||||
@ -14,15 +12,10 @@ namespace Artemis.Core;
|
||||
public sealed class Profile : ProfileElement
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly ObservableCollection<ScriptConfiguration> _scriptConfigurations;
|
||||
private readonly ObservableCollection<ProfileScript> _scripts;
|
||||
private bool _isFreshImport;
|
||||
|
||||
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
|
||||
{
|
||||
_scripts = new ObservableCollection<ProfileScript>();
|
||||
_scriptConfigurations = new ObservableCollection<ScriptConfiguration>();
|
||||
|
||||
Opacity = 0d;
|
||||
ShouldDisplay = true;
|
||||
Configuration = configuration;
|
||||
@ -31,8 +24,6 @@ public sealed class Profile : ProfileElement
|
||||
EntityId = profileEntity.Id;
|
||||
|
||||
Exceptions = new List<Exception>();
|
||||
Scripts = new ReadOnlyObservableCollection<ProfileScript>(_scripts);
|
||||
ScriptConfigurations = new ReadOnlyObservableCollection<ScriptConfiguration>(_scriptConfigurations);
|
||||
|
||||
Load();
|
||||
}
|
||||
@ -41,17 +32,7 @@ public sealed class Profile : ProfileElement
|
||||
/// Gets the profile configuration of this profile
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
|
||||
/// since import
|
||||
@ -85,15 +66,9 @@ public sealed class Profile : ProfileElement
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdating(deltaTime);
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Update(deltaTime);
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdated(deltaTime);
|
||||
|
||||
const double OPACITY_PER_SECOND = 1;
|
||||
|
||||
if (ShouldDisplay && Opacity < 1)
|
||||
@ -111,9 +86,6 @@ public sealed class Profile : ProfileElement
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds);
|
||||
|
||||
SKPaint? opacityPaint = null;
|
||||
bool applyOpacityLayer = Configuration.FadeInAndOut && Opacity < 1;
|
||||
|
||||
@ -133,9 +105,6 @@ public sealed class Profile : ProfileElement
|
||||
opacityPaint?.Dispose();
|
||||
}
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);
|
||||
|
||||
if (!Exceptions.Any())
|
||||
return;
|
||||
|
||||
@ -174,7 +143,7 @@ public sealed class Profile : ProfileElement
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return GetRootFolder().GetFeatureDependencies().Concat(Scripts.Select(c => c.ScriptingProvider));
|
||||
return GetRootFolder().GetFeatureDependencies();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -205,10 +174,7 @@ public sealed class Profile : ProfileElement
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
while (Scripts.Count > 0)
|
||||
RemoveScript(Scripts[0]);
|
||||
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
ChildrenList.Clear();
|
||||
@ -238,61 +204,11 @@ public sealed class Profile : ProfileElement
|
||||
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
|
||||
foreach (RenderProfileElement renderProfileElement in GetAllRenderElements())
|
||||
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()
|
||||
{
|
||||
if (Disposed)
|
||||
@ -310,12 +226,5 @@ public sealed class Profile : ProfileElement
|
||||
|
||||
ProfileEntity.Layers.Clear();
|
||||
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
|
||||
|
||||
ProfileEntity.ScriptConfigurations.Clear();
|
||||
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
|
||||
{
|
||||
scriptConfiguration.Save();
|
||||
ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Artemis.Core.SkiaSharp;
|
||||
using HPPH;
|
||||
using HPPH.SkiaSharp;
|
||||
using RGB.NET.Core;
|
||||
using RGB.NET.Presets.Textures.Sampler;
|
||||
using RGB.NET.Presets.Extensions;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core;
|
||||
@ -12,35 +12,29 @@ namespace Artemis.Core;
|
||||
/// <summary>
|
||||
/// Represents a SkiaSharp-based RGB.NET PixelTexture
|
||||
/// </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
|
||||
|
||||
internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection<ArtemisDevice> devices) : base(width, height, DATA_PER_PIXEL,
|
||||
new AverageByteSampler())
|
||||
internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection<ArtemisDevice> devices)
|
||||
{
|
||||
RenderScale = scale;
|
||||
Size = new Size(width, height);
|
||||
|
||||
ImageInfo = new SKImageInfo(width, height);
|
||||
Surface = graphicsContext == null
|
||||
? SKSurface.Create(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 (ArtemisLed artemisLed in artemisDevice.Leds)
|
||||
{
|
||||
_ledRects[artemisLed.RgbLed] = SKRectI.Create(
|
||||
(int) (artemisLed.AbsoluteRectangle.Left * RenderScale),
|
||||
(int) (artemisLed.AbsoluteRectangle.Top * RenderScale),
|
||||
(int) (artemisLed.AbsoluteRectangle.Width * RenderScale),
|
||||
(int) (artemisLed.AbsoluteRectangle.Height * RenderScale)
|
||||
(int)(artemisLed.AbsoluteRectangle.Left * RenderScale),
|
||||
(int)(artemisLed.AbsoluteRectangle.Top * RenderScale),
|
||||
(int)(artemisLed.AbsoluteRectangle.Width * RenderScale),
|
||||
(int)(artemisLed.AbsoluteRectangle.Height * RenderScale)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -50,47 +44,29 @@ public sealed class SKTexture : PixelTexture<byte>, IDisposable
|
||||
|
||||
internal Color GetColorAtRenderTarget(in RenderTarget renderTarget)
|
||||
{
|
||||
if (Data.Length == 0) return Color.Transparent;
|
||||
if (_image == null) return Color.Transparent;
|
||||
|
||||
SKRectI skRectI = _ledRects[renderTarget.Led];
|
||||
|
||||
if (skRectI.Width <= 0 || skRectI.Height <= 0)
|
||||
return Color.Transparent;
|
||||
|
||||
SamplerInfo<byte> samplerInfo = new(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, Stride, DataPerPixel, Data);
|
||||
|
||||
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
|
||||
Sampler.Sample(samplerInfo, pixelData);
|
||||
|
||||
return GetColor(pixelData);
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
Marshal.FreeHGlobal(_pixelDataPtr);
|
||||
return _image[skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height].Average().ToColor();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
~SKTexture()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Surface.Dispose();
|
||||
_pixelData.Dispose();
|
||||
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Constants
|
||||
|
||||
private const int DATA_PER_PIXEL = 4;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
@ -104,20 +80,23 @@ public sealed class SKTexture : PixelTexture<byte>, IDisposable
|
||||
internal void CopyPixelData()
|
||||
{
|
||||
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
|
||||
|
||||
#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>
|
||||
/// Gets the SKBitmap backing this texture
|
||||
/// </summary>
|
||||
@ -128,11 +107,6 @@ public sealed class SKTexture : PixelTexture<byte>, IDisposable
|
||||
/// </summary>
|
||||
public SKImageInfo ImageInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color data in RGB format
|
||||
/// </summary>
|
||||
protected override ReadOnlySpan<byte> Data => _pixelData.GetPixelSpan();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the render scale of the texture
|
||||
/// </summary>
|
||||
|
||||
@ -20,7 +20,7 @@ internal class SKTextureBrush : AbstractBrush
|
||||
#region Methods
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Core.Services.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
@ -7,22 +6,17 @@ namespace Artemis.Core.Services;
|
||||
internal class CoreRenderer : IRenderer
|
||||
{
|
||||
private readonly IModuleService _moduleService;
|
||||
private readonly IScriptingService _scriptingService;
|
||||
private readonly IProfileService _profileService;
|
||||
|
||||
public CoreRenderer(IModuleService moduleService, IScriptingService scriptingService, IProfileService profileService)
|
||||
public CoreRenderer(IModuleService moduleService, IProfileService profileService)
|
||||
{
|
||||
_moduleService = moduleService;
|
||||
_scriptingService = scriptingService;
|
||||
_profileService = profileService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Render(SKCanvas canvas, double delta)
|
||||
{
|
||||
foreach (GlobalScript scriptingServiceGlobalScript in _scriptingService.GlobalScripts)
|
||||
scriptingServiceGlobalScript.OnCoreUpdating(delta);
|
||||
|
||||
_moduleService.UpdateActiveModules(delta);
|
||||
|
||||
if (!_profileService.ProfileRenderingDisabled)
|
||||
@ -30,9 +24,6 @@ internal class CoreRenderer : IRenderer
|
||||
_profileService.UpdateProfiles(delta);
|
||||
_profileService.RenderProfiles(canvas);
|
||||
}
|
||||
|
||||
foreach (GlobalScript scriptingServiceGlobalScript in _scriptingService.GlobalScripts)
|
||||
scriptingServiceGlobalScript.OnCoreUpdated(delta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.DryIoc.Factories;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Storage;
|
||||
using DryIoc;
|
||||
using HidSharp;
|
||||
using RGB.NET.Core;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Services;
|
||||
|
||||
@ -33,7 +28,6 @@ internal class CoreService : ICoreService
|
||||
IPluginManagementService pluginManagementService,
|
||||
IProfileService profileService,
|
||||
IModuleService moduleService,
|
||||
IScriptingService scriptingService,
|
||||
IRenderService renderService)
|
||||
{
|
||||
Constants.CorePlugin.Container = container;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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.");
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -11,7 +11,6 @@ public class ProfileEntity
|
||||
{
|
||||
Folders = new List<FolderEntity>();
|
||||
Layers = new List<LayerEntity>();
|
||||
ScriptConfigurations = new List<ScriptConfigurationEntity>();
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
@ -21,7 +20,6 @@ public class ProfileEntity
|
||||
|
||||
public List<FolderEntity> Folders { get; set; }
|
||||
public List<LayerEntity> Layers { get; set; }
|
||||
public List<ScriptConfigurationEntity> ScriptConfigurations { get; set; }
|
||||
|
||||
public void UpdateGuid(Guid guid)
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -3,18 +3,17 @@
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
||||
<Design.PreviewWith>
|
||||
|
||||
<controls:HyperlinkButton Grid.Column="0" Classes="icon-button icon-button-small broken-state-button" Margin="50">
|
||||
<HyperlinkButton Grid.Column="0" Classes="icon-button icon-button-small broken-state-button" Margin="50">
|
||||
<avalonia:MaterialIcon Kind="AlertCircle" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<!-- 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" />
|
||||
</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" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@ -25,14 +25,14 @@
|
||||
</ToggleButton>
|
||||
|
||||
<TextBlock Margin="0 5 0 0">HyperlinkButton.icon-button</TextBlock>
|
||||
<controls:HyperlinkButton Classes="icon-button">
|
||||
<HyperlinkButton Classes="icon-button">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
|
||||
<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" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
|
||||
|
||||
<TextBlock Margin="0 5 0 0">Button.window-button</TextBlock>
|
||||
@ -93,7 +93,7 @@
|
||||
<Setter Property="Height" Value="20" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|HyperlinkButton.icon-button">
|
||||
<Style Selector="HyperlinkButton.icon-button">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
|
||||
</Style>
|
||||
|
||||
@ -113,24 +113,24 @@
|
||||
<Setter Property="Background" Value="#D64848"></Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.danger:pointerover">
|
||||
<Style Selector="^ /template/ controls|FABorder#Root">
|
||||
<Setter Property="Background" Value="#D65757"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.danger:pressed">
|
||||
<Style Selector="^ /template/ controls|FABorder#Root">
|
||||
<Setter Property="Background" Value="#D64848" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.danger:disabled">
|
||||
<Style Selector="^ /template/ controls|FABorder#Root">
|
||||
<Setter Property="Background" Value="#D79D9C" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
|
||||
</Style>
|
||||
</Style>
|
||||
<!-- <Style Selector="Button.danger:pointerover"> -->
|
||||
<!-- <Style Selector="^ /template/ controls|FABorder#Root"> -->
|
||||
<!-- <Setter Property="Background" Value="#D65757"/> -->
|
||||
<!-- <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" /> -->
|
||||
<!-- </Style> -->
|
||||
<!-- </Style> -->
|
||||
<!-- -->
|
||||
<!-- <Style Selector="Button.danger:pressed"> -->
|
||||
<!-- <Style Selector="^ /template/ controls|FABorder#Root"> -->
|
||||
<!-- <Setter Property="Background" Value="#D64848" /> -->
|
||||
<!-- <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" /> -->
|
||||
<!-- </Style> -->
|
||||
<!-- </Style> -->
|
||||
<!-- -->
|
||||
<!-- <Style Selector="Button.danger:disabled"> -->
|
||||
<!-- <Style Selector="^ /template/ controls|FABorder#Root"> -->
|
||||
<!-- <Setter Property="Background" Value="#D79D9C" /> -->
|
||||
<!-- <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> -->
|
||||
<!-- </Style> -->
|
||||
<!-- </Style> -->
|
||||
</Styles>
|
||||
@ -51,7 +51,7 @@
|
||||
RowDefinitions="*,*">
|
||||
<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>
|
||||
<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>
|
||||
</Panel>
|
||||
</Border>
|
||||
|
||||
@ -67,5 +67,8 @@
|
||||
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\PluginListView.axaml" />
|
||||
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\ProfileListView.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>
|
||||
</Project>
|
||||
@ -17,7 +17,7 @@
|
||||
<Label Grid.Column="0" Name="DescriptionEditorLabel" Target="DescriptionEditor" Margin="0 28 0 0" />
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<CheckBox Name="SynchronizedScrolling" IsChecked="True" VerticalAlignment="Bottom">Synchronized scrolling</CheckBox>
|
||||
<fa:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
Margin="0 0 0 -20"
|
||||
Content="Markdown supported"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/guides/user/markdown?mtm_campaign=artemis&mtm_kwd=markdown-editor"
|
||||
|
||||
@ -3,7 +3,6 @@ using System.Reactive;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.UI.Screens.Device;
|
||||
using Artemis.UI.Screens.Device.General;
|
||||
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.Tree;
|
||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
||||
using Artemis.UI.Screens.Scripting;
|
||||
using Artemis.UI.Screens.Settings;
|
||||
using Artemis.UI.Screens.Settings.Updating;
|
||||
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
|
||||
{
|
||||
ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release);
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10">
|
||||
These performance stats are rather basic, for advanced performance profiling check out the wiki.
|
||||
</TextBlock>
|
||||
<controls:HyperlinkButton Grid.Column="1" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins/profiling?mtm_campaign=artemis&mtm_kwd=debugger">
|
||||
<HyperlinkButton Grid.Column="1" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins/profiling?mtm_campaign=artemis&mtm_kwd=debugger">
|
||||
JetBrains Profiling Guide
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<controls:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
Grid.Row="1"
|
||||
Content="Learn more about layouts on the wiki"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts?mtm_campaign=artemis&mtm_kwd=device-properties"
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:home="clr-namespace:Artemis.UI.Screens.Home"
|
||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="900"
|
||||
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.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<controls:HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" HorizontalAlignment="Right" Command="{CompiledBinding GetMorePlugins}">
|
||||
<controls:HyperlinkButton.ContextMenu>
|
||||
<HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" HorizontalAlignment="Right" Command="{CompiledBinding GetMorePlugins}">
|
||||
<HyperlinkButton.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Test"></MenuItem>
|
||||
</ContextMenu>
|
||||
</controls:HyperlinkButton.ContextMenu>
|
||||
</HyperlinkButton.ContextMenu>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="OpenInBrowser" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Get more plugins</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@ -68,30 +67,30 @@
|
||||
|
||||
<DockPanel Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0">
|
||||
<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">
|
||||
<avalonia:MaterialIcon Kind="Github" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=home">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=home">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="1" NavigateUri="https://discordapp.com/invite/S3MVaC9">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Grid.Row="1" NavigateUri="https://discordapp.com/invite/S3MVaC9">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Chat" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="1" HorizontalAlignment="Right" NavigateUri="mailto:spoinky.nl@gmail.com">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Grid.Row="1" HorizontalAlignment="Right" NavigateUri="mailto:spoinky.nl@gmail.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Email" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">E-mail</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
@ -106,7 +105,7 @@
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
<HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&mtm_kwd=home">
|
||||
@ -114,7 +113,7 @@
|
||||
<avalonia:MaterialIcon Kind="Gift" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Classes="subtitle"
|
||||
|
||||
@ -9,7 +9,6 @@ using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
@ -99,8 +98,6 @@ public partial class PluginFeatureViewModel : ActivatableViewModelBase
|
||||
return MaterialIconKind.Brush;
|
||||
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider)))
|
||||
return MaterialIconKind.AutoAwesome;
|
||||
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(ScriptingProvider)))
|
||||
return MaterialIconKind.Code;
|
||||
return MaterialIconKind.Extension;
|
||||
}
|
||||
}
|
||||
@ -117,8 +114,6 @@ public partial class PluginFeatureViewModel : ActivatableViewModelBase
|
||||
return "Layer Brush";
|
||||
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(LayerEffectProvider)))
|
||||
return "Layer Effect";
|
||||
if (FeatureInfo.FeatureType.IsAssignableTo(typeof(ScriptingProvider)))
|
||||
return "Scripting Provider";
|
||||
return "Miscellaneous feature";
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,24 +28,24 @@
|
||||
<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">
|
||||
<controls:HyperlinkButton Classes="icon-button"
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.HelpPage}"
|
||||
ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}">
|
||||
<avalonia:MaterialIcon Kind="Quiz" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.Website}"
|
||||
ToolTip.Tip="{CompiledBinding Plugin.Info.Website}">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.Repository}"
|
||||
ToolTip.Tip="{CompiledBinding Plugin.Info.Repository}">
|
||||
<avalonia:MaterialIcon Kind="Git" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
@ -54,7 +54,7 @@
|
||||
Classes="subtitle"
|
||||
Text="{CompiledBinding Plugin.Info.Version}" />
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
<HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
IsVisible="{CompiledBinding Plugin.Info.License, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
VerticalAlignment="Top"
|
||||
|
||||
@ -87,30 +87,30 @@
|
||||
</DropDownButton.Flyout>
|
||||
</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}}"
|
||||
Command="{CompiledBinding OpenSettings}"
|
||||
ToolTip.Tip="Open settings">
|
||||
<avalonia:MaterialIcon Kind="Cog" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button icon-button-large"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button icon-button-large"
|
||||
IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.HelpPage}"
|
||||
ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}">
|
||||
<avalonia:MaterialIcon Kind="Quiz" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button icon-button-large"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button icon-button-large"
|
||||
IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.Website}"
|
||||
ToolTip.Tip="{CompiledBinding Plugin.Info.Website}">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button icon-button-large"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button icon-button-large"
|
||||
IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.Repository}"
|
||||
ToolTip.Tip="{CompiledBinding Plugin.Info.Repository}">
|
||||
<avalonia:MaterialIcon Kind="Git" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Name="EnabledToggle"
|
||||
|
||||
@ -31,11 +31,6 @@
|
||||
<avalonia:MaterialIcon Kind="Settings" />
|
||||
</MenuItem.Icon>
|
||||
</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.Icon>
|
||||
<avalonia:MaterialIcon Kind="Magic" />
|
||||
|
||||
@ -8,7 +8,6 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Screens.Scripting;
|
||||
using Artemis.UI.Screens.Sidebar;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
@ -64,7 +63,6 @@ public partial class MenuBarViewModel : ActivatableViewModelBase
|
||||
AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
|
||||
AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
|
||||
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));
|
||||
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));
|
||||
@ -81,7 +79,6 @@ public partial class MenuBarViewModel : ActivatableViewModelBase
|
||||
public ReactiveCommand<Unit, Unit> AddLayer { get; }
|
||||
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
|
||||
public ReactiveCommand<Unit, Unit> ViewProperties { get; }
|
||||
public ReactiveCommand<Unit, Unit> ViewScripts { get; }
|
||||
public ReactiveCommand<Unit, Unit> AdaptProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> ExportProfile { get; }
|
||||
@ -133,16 +130,7 @@ public partial class MenuBarViewModel : ActivatableViewModelBase
|
||||
|
||||
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()
|
||||
{
|
||||
if (ProfileConfiguration?.Profile == null)
|
||||
|
||||
@ -22,12 +22,12 @@
|
||||
<TextBlock Classes="h4" Text="{CompiledBinding Layer.Name}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="0"
|
||||
<HyperlinkButton Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints?mtm_campaign=artemis&mtm_kwd=profile-editor">
|
||||
Learn more about adaption hints
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
|
||||
<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.
|
||||
|
||||
@ -9,14 +9,14 @@
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.FolderTreeItemView"
|
||||
x:DataType="profileTree:FolderTreeItemViewModel">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<controls:HyperlinkButton Grid.Column="0"
|
||||
<HyperlinkButton Grid.Column="0"
|
||||
Classes="icon-button icon-button-small broken-state-button"
|
||||
Margin="0 0 5 0"
|
||||
Command="{CompiledBinding ShowBrokenStateExceptions}"
|
||||
IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}">
|
||||
<avalonia:MaterialIcon Kind="AlertCircle" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
<avalonia:MaterialIcon Grid.Column="1"
|
||||
Kind="Folder"
|
||||
Margin="0 0 5 0"
|
||||
|
||||
@ -9,14 +9,14 @@
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.LayerTreeItemView"
|
||||
x:DataType="profileTree:LayerTreeItemViewModel">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<controls:HyperlinkButton Grid.Column="0"
|
||||
<HyperlinkButton Grid.Column="0"
|
||||
Classes="icon-button icon-button-small broken-state-button"
|
||||
Margin="0 0 5 0"
|
||||
Command="{CompiledBinding ShowBrokenStateExceptions}"
|
||||
IsVisible="{CompiledBinding ProfileElement.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
ToolTip.Tip="{CompiledBinding ProfileElement.BrokenState, FallbackValue=''}">
|
||||
<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" />
|
||||
|
||||
<TextBlock Grid.Column="2" Text="{CompiledBinding Layer.Name}" VerticalAlignment="Center" />
|
||||
|
||||
@ -42,11 +42,11 @@
|
||||
<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.
|
||||
</TextBlock>
|
||||
<controls:HyperlinkButton HorizontalAlignment="Center"
|
||||
<HyperlinkButton HorizontalAlignment="Center"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings?mtm_campaign=artemis&mtm_kwd=profile-editor"
|
||||
Margin="0 10">
|
||||
Learn more
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -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>
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -25,15 +25,15 @@
|
||||
</TextBlock>
|
||||
|
||||
<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&mtm_kwd=about">
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=about">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
<avalonia:MaterialIcon Kind="Github" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=about">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=about">
|
||||
<avalonia:MaterialIcon Kind="BookOpenOutline" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<SelectableTextBlock Grid.Row="1"
|
||||
@ -42,12 +42,12 @@
|
||||
Classes="subtitle"
|
||||
Text="{CompiledBinding Version}" />
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
<HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Top"
|
||||
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
|
||||
PolyForm Noncommercial License 1.0.0
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</Grid>
|
||||
|
||||
<Border Classes="card" Margin="0 20 0 10">
|
||||
@ -65,9 +65,9 @@
|
||||
Project owner, main contributor
|
||||
</TextBlock>
|
||||
<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" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@ -86,9 +86,9 @@
|
||||
RGB.NET, main contributor
|
||||
</TextBlock>
|
||||
<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" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@ -107,9 +107,9 @@
|
||||
Main contributor
|
||||
</TextBlock>
|
||||
<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" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@ -128,9 +128,9 @@
|
||||
Graphics design
|
||||
</TextBlock>
|
||||
<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" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
@ -185,39 +185,39 @@
|
||||
<TextBlock Classes="library-name">SQLite</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1">
|
||||
<controls:HyperlinkButton NavigateUri="https://avaloniaui.net/">
|
||||
<HyperlinkButton NavigateUri="https://avaloniaui.net/">
|
||||
https://avaloniaui.net/
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/dadhi/DryIoc">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/dadhi/DryIoc">
|
||||
https://github.com/dadhi/DryIoc
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://learn.microsoft.com/en-us/ef/core/">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://learn.microsoft.com/en-us/ef/core/">
|
||||
https://learn.microsoft.com/en-us/ef/core/
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/amwx/FluentAvalonia">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/amwx/FluentAvalonia">
|
||||
https://github.com/amwx/FluentAvalonia
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://unosquare.github.io/embedio/">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://unosquare.github.io/embedio/">
|
||||
https://unosquare.github.io/embedio/
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/Humanizr/Humanizer">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/Humanizr/Humanizer">
|
||||
https://github.com/Humanizr/Humanizer
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/natemcmaster/DotNetCorePlugins">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/natemcmaster/DotNetCorePlugins">
|
||||
https://github.com/natemcmaster/DotNetCorePlugins
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/DarthAffe/RGB.NET">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/DarthAffe/RGB.NET">
|
||||
https://github.com/DarthAffe/RGB.NET
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://serilog.net/">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://serilog.net/">
|
||||
https://serilog.net/
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/mono/SkiaSharp">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/mono/SkiaSharp">
|
||||
https://github.com/mono/SkiaSharp
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://www.sqlite.org/">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://www.sqlite.org/">
|
||||
https://www.sqlite.org/
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
<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">
|
||||
<controls:HyperlinkButton VerticalAlignment="Top" Command="{CompiledBinding GetMorePlugins}">
|
||||
<HyperlinkButton VerticalAlignment="Top" Command="{CompiledBinding GetMorePlugins}">
|
||||
Get more plugins
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
<Button Classes="accent" Command="{CompiledBinding ImportPlugin}">Import plugin</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@ -29,10 +29,10 @@
|
||||
TextWrapping="Wrap"
|
||||
Text="{CompiledBinding Channel, StringFormat='Found no releases for the \'{0}\' channel.'}">
|
||||
</TextBlock>
|
||||
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/channels?mtm_campaign=artemis&mtm_kwd=releases"
|
||||
<HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/channels?mtm_campaign=artemis&mtm_kwd=releases"
|
||||
HorizontalAlignment="Center">
|
||||
Learn more about channels on the wiki
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<Grid ColumnDefinitions="300,*" Margin="10" IsVisible="{CompiledBinding ReleaseViewModels.Count}">
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
<!-- Bottom buttons -->
|
||||
<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">
|
||||
<ui:HyperlinkButton Classes="icon-button"
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
Width="44"
|
||||
Height="44"
|
||||
ToolTip.Tip="View website"
|
||||
@ -62,8 +62,8 @@
|
||||
ToolTip.VerticalOffset="-5"
|
||||
NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=sidebar">
|
||||
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
|
||||
</ui:HyperlinkButton>
|
||||
<ui:HyperlinkButton Classes="icon-button"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
Width="44"
|
||||
Height="44"
|
||||
ToolTip.Tip="View GitHub repository"
|
||||
@ -71,8 +71,8 @@
|
||||
ToolTip.VerticalOffset="-5"
|
||||
NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
|
||||
</ui:HyperlinkButton>
|
||||
<ui:HyperlinkButton Classes="icon-button"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
Width="44"
|
||||
Height="44"
|
||||
ToolTip.Tip="View Wiki"
|
||||
@ -80,8 +80,8 @@
|
||||
ToolTip.VerticalOffset="-5"
|
||||
NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=sidebar">
|
||||
<avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" />
|
||||
</ui:HyperlinkButton>
|
||||
<ui:HyperlinkButton Classes="icon-button"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
Width="44"
|
||||
Height="44"
|
||||
ToolTip.Tip="Join our Discord"
|
||||
@ -89,8 +89,8 @@
|
||||
ToolTip.VerticalOffset="-5"
|
||||
NavigateUri="https://discord.gg/S3MVaC9">
|
||||
<avalonia:MaterialIcon Kind="Chat" Width="20" Height="20" />
|
||||
</ui:HyperlinkButton>
|
||||
<ui:HyperlinkButton Classes="icon-button"
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button"
|
||||
Width="44"
|
||||
Height="44"
|
||||
ToolTip.Tip="View donation options"
|
||||
@ -98,7 +98,7 @@
|
||||
ToolTip.VerticalOffset="-5"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&mtm_kwd=sidebar">
|
||||
<avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" />
|
||||
</ui:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -33,18 +33,18 @@
|
||||
<TextBlock Classes="link-name">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1">
|
||||
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
<HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
https://wiki.artemis-rgb.com/
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
https://wiki.artemis-rgb.com/en/guides/user/introduction
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
https://github.com/Artemis-RGB/Artemis
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton NavigateUri="https://discord.gg/S3MVaC9">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://discord.gg/S3MVaC9">
|
||||
https://discord.gg/S3MVaC9
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
@ -16,15 +16,15 @@
|
||||
</TextBlock>
|
||||
|
||||
<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&mtm_kwd=wizard">
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
<avalonia:MaterialIcon Kind="Github" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
<avalonia:MaterialIcon Kind="BookOpenOutline" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
@ -33,12 +33,12 @@
|
||||
Classes="subtitle"
|
||||
Text="{CompiledBinding Version}" />
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
<HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Top"
|
||||
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
|
||||
PolyForm Noncommercial License 1.0.0
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</Grid>
|
||||
|
||||
<Border Classes="card">
|
||||
|
||||
@ -169,13 +169,13 @@
|
||||
<TextBlock Classes="h4" Text="{CompiledBinding NodeScript.Name}" />
|
||||
<TextBlock Classes="subtitle" Margin="10 0 0 13" Text="{CompiledBinding NodeScript.Description}" VerticalAlignment="Bottom" />
|
||||
</StackPanel>
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
<HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes?mtm_campaign=artemis&mtm_kwd=script-editor">
|
||||
Learn more about visual scripts
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
|
||||
<Border Classes="card-condensed" Grid.Row="2" Grid.ColumnSpan="2">
|
||||
<ContentControl Content="{CompiledBinding NodeScriptViewModel}" />
|
||||
|
||||
@ -39,18 +39,18 @@
|
||||
Background="{DynamicResource ContentDialogBackground}">
|
||||
<Border Background="{DynamicResource TaskDialogHeaderBackground}">
|
||||
<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"
|
||||
Margin="5 0 0 0"
|
||||
Command="{CompiledBinding ShowBrokenState}"
|
||||
IsVisible="{CompiledBinding Node.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
ToolTip.Tip="{CompiledBinding Node.BrokenState}">
|
||||
<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}" />
|
||||
|
||||
<controls:HyperlinkButton Grid.Column="2"
|
||||
<HyperlinkButton Grid.Column="2"
|
||||
IsVisible="{CompiledBinding Node.HelpUrl, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
VerticalAlignment="Center"
|
||||
Classes="icon-button icon-button-small"
|
||||
@ -58,7 +58,7 @@
|
||||
ToolTip.Tip="View node help"
|
||||
NavigateUri="{CompiledBinding Node.HelpUrl}">
|
||||
<avalonia:MaterialIcon Kind="Help" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
<Button Grid.Column="3" VerticalAlignment="Center" Classes="icon-button icon-button-small" Margin="5" Command="{CompiledBinding DeleteNode}">
|
||||
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
|
||||
</Button>
|
||||
|
||||
@ -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="1" Text="{CompiledBinding Email}"></TextBlock>
|
||||
|
||||
<controls:HyperlinkButton
|
||||
<HyperlinkButton
|
||||
IsVisible="{CompiledBinding AllowLogout}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="3"
|
||||
@ -51,8 +51,8 @@
|
||||
Padding="6 4"
|
||||
Click="Manage_OnClick">
|
||||
Manage account
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton
|
||||
IsVisible="{CompiledBinding AllowLogout}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
@ -60,7 +60,7 @@
|
||||
Padding="6 4"
|
||||
Click="Signout_OnClick">
|
||||
Sign out
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Ellipse.ContextFlyout>
|
||||
|
||||
@ -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>
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:entries1="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<Border Classes="router-container">
|
||||
<Grid RowDefinitions="200,*,*">
|
||||
<ProgressBar ZIndex="999" IsIndeterminate="True" IsVisible="{CompiledBinding !WorkshopReachable}" Grid.Row="0" VerticalAlignment="Top"></ProgressBar>
|
||||
|
||||
|
||||
<Image Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
VerticalAlignment="Top"
|
||||
@ -38,7 +38,7 @@
|
||||
<DropShadowEffect Color="Black" OffsetX="2" OffsetY="2" BlurRadius="5"></DropShadowEffect>
|
||||
</TextBlock.Effect>
|
||||
</TextBlock>
|
||||
|
||||
|
||||
<StackPanel Margin="30 -75 30 0" Grid.Row="1">
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" VerticalAlignment="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>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
|
||||
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/entries/plugins" VerticalContentAlignment="Top">
|
||||
<StackPanel>
|
||||
<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>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
|
||||
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/library" VerticalContentAlignment="Top">
|
||||
<StackPanel>
|
||||
<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>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
|
||||
<Button Width="150" Height="180" Command="{CompiledBinding AddSubmission}" VerticalContentAlignment="Top">
|
||||
<StackPanel>
|
||||
<avalonia:MaterialIcon Kind="CloudUpload" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
|
||||
@ -85,10 +85,26 @@
|
||||
</StackPanel>
|
||||
|
||||
<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>Not yet implemented, here we'll a few of the most recent uploads/updates to the workshop.</TextBlock>
|
||||
<TextBlock Classes="h4" Margin="0 15 0 5">Recently added</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>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@ -1,33 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using DynamicData;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Home;
|
||||
|
||||
public partial class WorkshopHomeViewModel : RoutableScreen
|
||||
{
|
||||
private readonly IWindowService _windowService;
|
||||
|
||||
[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;
|
||||
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));
|
||||
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<string, Unit> Navigate { get; }
|
||||
public ReadOnlyObservableCollection<EntryListItemVerticalViewModel> PopularEntries { get; }
|
||||
public ReadOnlyObservableCollection<EntryListItemVerticalViewModel> LatestEntries { get; }
|
||||
|
||||
private async Task ExecuteAddSubmission(CancellationToken arg)
|
||||
{
|
||||
|
||||
@ -70,9 +70,9 @@
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<controls:HyperlinkButton Command="{CompiledBinding ViewWorkshopPage}" HorizontalAlignment="Center">
|
||||
<HyperlinkButton Command="{CompiledBinding ViewWorkshopPage}" HorizontalAlignment="Center">
|
||||
View workshop page
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<controls:Frame Grid.Column="1" Grid.Row="0" Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
<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">
|
||||
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=workshop-wizard">
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=workshop-wizard">
|
||||
<avalonia:MaterialIcon Kind="BookOpenOutline" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
<shared:EnumComboBox Value="{CompiledBinding PhysicalLayout}"></shared:EnumComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
<HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{CompiledBinding IsKeyboardLayout}"
|
||||
Margin="0 10 0 0"
|
||||
@ -47,7 +47,7 @@
|
||||
HorizontalAlignment="Right"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts/keyboard-layouts?mtm_campaign=artemis&mtm_kwd=workshop-wizard">
|
||||
Learn about physical layouts
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
|
||||
<ScrollViewer Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
|
||||
@ -24,12 +24,12 @@
|
||||
</TextBlock>
|
||||
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="0"
|
||||
<HyperlinkButton Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints?mtm_campaign=artemis&mtm_kwd=workshop-wizard">
|
||||
Learn more about adaption hints
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
|
||||
<ScrollViewer Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
|
||||
@ -22,9 +22,9 @@
|
||||
<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">
|
||||
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=workshop-wizard">
|
||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=workshop-wizard">
|
||||
<avalonia:MaterialIcon Kind="BookOpenOutline" />
|
||||
</controls:HyperlinkButton>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
|
||||
@ -22,7 +22,8 @@ public class WorkshopUpdateService : IWorkshopUpdateService
|
||||
private readonly Lazy<IUpdateNotificationProvider> _updateNotificationProvider;
|
||||
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;
|
||||
_client = client;
|
||||
@ -56,20 +57,19 @@ public class WorkshopUpdateService : IWorkshopUpdateService
|
||||
|
||||
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
|
||||
{
|
||||
// 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);
|
||||
|
||||
// This happens during installation too but not on our reference of the entry
|
||||
@ -85,7 +85,7 @@ public class WorkshopUpdateService : IWorkshopUpdateService
|
||||
}
|
||||
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;
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
<Setter Property="SelectionForeground" Value="{DynamicResource TextOnAccentFillColorSelectedTextBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|ContentDialog.fullscreen controls|FABorder#BackgroundElement">
|
||||
<Style Selector="controls|ContentDialog.fullscreen Border#BackgroundElement">
|
||||
<Setter Property="MaxWidth" Value="99999"></Setter>
|
||||
<Setter Property="MaxHeight" Value="99999"></Setter>
|
||||
<Setter Property="Margin" Value="100"></Setter>
|
||||
|
||||
@ -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) {
|
||||
entriesV2(search: $search where: $filter order: $order first: $first after: $after) {
|
||||
totalCount
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
@ -21,4 +12,10 @@ query GetEntriesv2($search: String $filter: EntryFilterInput $order: [EntrySortI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetPopularEntries {
|
||||
popularEntries {
|
||||
...entrySummary
|
||||
}
|
||||
}
|
||||
@ -61,12 +61,14 @@ type Entry {
|
||||
iconId: UUID
|
||||
id: Long!
|
||||
images: [Image!]!
|
||||
isDefault: Boolean!
|
||||
isOfficial: Boolean!
|
||||
latestRelease: Release
|
||||
latestReleaseId: Long
|
||||
layoutInfo: [LayoutInfo!]!
|
||||
name: String!
|
||||
pluginInfo: PluginInfo
|
||||
popularityScore: Float!
|
||||
releases: [Release!]!
|
||||
summary: String!
|
||||
tags: [Tag!]!
|
||||
@ -146,7 +148,7 @@ type PluginInfosCollectionSegment {
|
||||
|
||||
type Query {
|
||||
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(
|
||||
"Returns the elements in the list that come after the specified cursor."
|
||||
after: String,
|
||||
@ -157,12 +159,14 @@ type Query {
|
||||
"Returns the last _n_ elements from the list."
|
||||
last: Int,
|
||||
order: [EntrySortInput!],
|
||||
popular: Boolean,
|
||||
search: String,
|
||||
where: EntryFilterInput
|
||||
): EntriesV2Connection
|
||||
entry(id: Long!): Entry
|
||||
pluginInfo(pluginGuid: UUID!): PluginInfo
|
||||
pluginInfos(order: [PluginInfoSortInput!], skip: Int, take: Int, where: PluginInfoFilterInput): PluginInfosCollectionSegment
|
||||
popularEntries(where: EntryFilterInput): [Entry!]!
|
||||
release(id: Long!): Release
|
||||
searchEntries(input: String!, order: [EntrySortInput!], type: EntryType, where: EntryFilterInput): [Entry!]!
|
||||
searchKeyboardLayout(deviceProvider: UUID!, logicalLayout: String, model: String!, physicalLayout: KeyboardLayoutType!, vendor: String!): LayoutInfo
|
||||
@ -268,6 +272,7 @@ input CreateEntryInput {
|
||||
categories: [Long!]!
|
||||
description: String!
|
||||
entryType: EntryType!
|
||||
isDefault: Boolean!
|
||||
name: String!
|
||||
summary: String!
|
||||
tags: [String!]!
|
||||
@ -312,6 +317,7 @@ input EntryFilterInput {
|
||||
iconId: UuidOperationFilterInput
|
||||
id: LongOperationFilterInput
|
||||
images: ListFilterInputTypeOfImageFilterInput
|
||||
isDefault: BooleanOperationFilterInput
|
||||
isOfficial: BooleanOperationFilterInput
|
||||
latestRelease: ReleaseFilterInput
|
||||
latestReleaseId: LongOperationFilterInput
|
||||
@ -319,6 +325,7 @@ input EntryFilterInput {
|
||||
name: StringOperationFilterInput
|
||||
or: [EntryFilterInput!]
|
||||
pluginInfo: PluginInfoFilterInput
|
||||
popularityScore: FloatOperationFilterInput
|
||||
releases: ListFilterInputTypeOfReleaseFilterInput
|
||||
summary: StringOperationFilterInput
|
||||
tags: ListFilterInputTypeOfTagFilterInput
|
||||
@ -334,11 +341,13 @@ input EntrySortInput {
|
||||
icon: ImageSortInput
|
||||
iconId: SortEnumType
|
||||
id: SortEnumType
|
||||
isDefault: SortEnumType
|
||||
isOfficial: SortEnumType
|
||||
latestRelease: ReleaseSortInput
|
||||
latestReleaseId: SortEnumType
|
||||
name: SortEnumType
|
||||
pluginInfo: PluginInfoSortInput
|
||||
popularityScore: SortEnumType
|
||||
summary: SortEnumType
|
||||
}
|
||||
|
||||
@ -349,6 +358,21 @@ input EntryTypeOperationFilterInput {
|
||||
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 {
|
||||
and: [ImageFilterInput!]
|
||||
description: StringOperationFilterInput
|
||||
@ -580,6 +604,7 @@ input UpdateEntryInput {
|
||||
categories: [Long!]!
|
||||
description: String!
|
||||
id: Long!
|
||||
isDefault: Boolean!
|
||||
name: String!
|
||||
summary: String!
|
||||
tags: [String!]!
|
||||
|
||||
@ -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_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_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_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=activatable/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@ -4,62 +4,63 @@
|
||||
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
|
||||
<PackageVersion Include="Avalonia" Version="11.0.11" />
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
|
||||
<PackageVersion Include="Avalonia.Controls.ItemsRepeater" Version="11.0.11" />
|
||||
<PackageVersion Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.3" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.11" />
|
||||
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.3.0" />
|
||||
<PackageVersion Include="Avalonia" Version="11.2.0" />
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
|
||||
<PackageVersion Include="Avalonia.Controls.ItemsRepeater" Version="11.1.4" />
|
||||
<PackageVersion Include="Avalonia.Controls.PanAndZoom" Version="11.2.0" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.2.0" />
|
||||
<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.Win32" Version="11.0.11" />
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="8.0.0" />
|
||||
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.0.10.9" />
|
||||
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.0.6" />
|
||||
<PackageVersion Include="Avalonia.Win32" Version="11.2.0" />
|
||||
<PackageVersion Include="HPPH.SkiaSharp" Version="1.0.0" />
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.0" />
|
||||
<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.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="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="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageVersion Include="IdentityModel" Version="7.0.0" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageVersion Include="LiteDB" Version="5.0.17" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
|
||||
<PackageVersion Include="LiteDB" Version="5.0.21" />
|
||||
<PackageVersion Include="Markdown.Avalonia.Tight" Version="11.0.2" />
|
||||
<PackageVersion Include="Material.Icons.Avalonia" Version="2.1.10" />
|
||||
<PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</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.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="Octopus.Octodiff" Version="2.0.546" />
|
||||
<PackageVersion Include="Octopus.Octodiff" Version="2.0.547" />
|
||||
<PackageVersion Include="PropertyChanged.SourceGenerator" Version="1.1.0" />
|
||||
<PackageVersion Include="RGB.NET.Core" Version="2.1.0" />
|
||||
<PackageVersion Include="RGB.NET.Layout" Version="2.1.0" />
|
||||
<PackageVersion Include="RGB.NET.Presets" Version="2.1.0" />
|
||||
<PackageVersion Include="RGB.NET.Core" Version="3.0.0-prerelease.3" />
|
||||
<PackageVersion Include="RGB.NET.Layout" Version="3.0.0-prerelease.3" />
|
||||
<PackageVersion Include="RGB.NET.Presets" Version="3.0.0-prerelease.3" />
|
||||
<PackageVersion Include="RawInput.Sharp" Version="0.1.3" />
|
||||
<PackageVersion Include="ReactiveUI" Version="20.1.1" />
|
||||
<PackageVersion Include="ReactiveUI.Validation" Version="4.0.9" />
|
||||
<PackageVersion Include="Serilog" Version="4.0.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="20.1.63" />
|
||||
<PackageVersion Include="ReactiveUI.Validation" Version="4.1.1" />
|
||||
<PackageVersion Include="Serilog" Version="4.1.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.8" />
|
||||
<PackageVersion Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.8" />
|
||||
<PackageVersion Include="Splat.DryIoc" Version="15.1.1" />
|
||||
<PackageVersion Include="StrawberryShake.Server" Version="13.9.6" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.6.2" />
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.3" />
|
||||
<PackageVersion Include="TextMateSharp.Grammars" Version="1.0.57" />
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
|
||||
<PackageVersion Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.9" />
|
||||
<PackageVersion Include="Splat.DryIoc" Version="15.2.22" />
|
||||
<PackageVersion Include="StrawberryShake.Server" Version="14.1.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.2.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
|
||||
<PackageVersion Include="TextMateSharp.Grammars" Version="1.0.64" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Loading…
x
Reference in New Issue
Block a user