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

Merge branch 'development'

This commit is contained in:
Robert 2021-09-11 21:13:53 +02:00
commit fa0f8b5a1a
37 changed files with 424 additions and 262 deletions

View File

@ -13,6 +13,11 @@ namespace Artemis.Core
{ {
private readonly List<DataModelConditionPart> _children = new(); private readonly List<DataModelConditionPart> _children = new();
protected DataModelConditionPart()
{
Children = new(_children);
}
/// <summary> /// <summary>
/// Gets the parent of this part /// Gets the parent of this part
/// </summary> /// </summary>
@ -21,7 +26,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the children of this part /// Gets the children of this part
/// </summary> /// </summary>
public ReadOnlyCollection<DataModelConditionPart> Children => _children.AsReadOnly(); public ReadOnlyCollection<DataModelConditionPart> Children { get; }
/// <summary> /// <summary>
/// Adds a child to the display condition part's <see cref="Children" /> collection /// Adds a child to the display condition part's <see cref="Children" /> collection

View File

@ -18,13 +18,14 @@ namespace Artemis.Core
{ {
DataBinding = dataBinding; DataBinding = dataBinding;
Entity = entity; Entity = entity;
Conditions = new(_conditions);
Load(); Load();
} }
/// <summary> /// <summary>
/// Gets a list of conditions applied to this data binding /// Gets a list of conditions applied to this data binding
/// </summary> /// </summary>
public ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>> Conditions => _conditions.AsReadOnly(); public ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>> Conditions { get; }
internal ConditionalDataBindingEntity Entity { get; } internal ConditionalDataBindingEntity Entity { get; }

View File

@ -17,7 +17,7 @@ namespace Artemis.Core
{ {
DataBinding = dataBinding; DataBinding = dataBinding;
Entity = entity; Entity = entity;
Modifiers = new(_modifiers);
Load(); Load();
} }
@ -29,7 +29,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a list of modifiers applied to this data binding /// Gets a list of modifiers applied to this data binding
/// </summary> /// </summary>
public ReadOnlyCollection<DataBindingModifier<TLayerProperty, TProperty>> Modifiers => _modifiers.AsReadOnly(); public ReadOnlyCollection<DataBindingModifier<TLayerProperty, TProperty>> Modifiers { get; }
internal DirectDataBindingEntity Entity { get; } internal DirectDataBindingEntity Entity { get; }

View File

@ -306,7 +306,7 @@ namespace Artemis.Core
ChildrenList.Add(new Layer(Profile, this, childLayer)); ChildrenList.Add(new Layer(Profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me // Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList(); ChildrenList.Sort((a,b) => a.Order.CompareTo(b.Order));
for (int index = 0; index < ChildrenList.Count; index++) for (int index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1; ChildrenList[index].Order = index + 1;

View File

@ -43,6 +43,7 @@ namespace Artemis.Core
_transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
_leds = new List<ArtemisLed>(); _leds = new List<ArtemisLed>();
Leds = new ReadOnlyCollection<ArtemisLed>(_leds);
Adapter = new LayerAdapter(this); Adapter = new LayerAdapter(this);
Initialize(); Initialize();
@ -67,6 +68,7 @@ namespace Artemis.Core
_transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
_leds = new List<ArtemisLed>(); _leds = new List<ArtemisLed>();
Leds = new ReadOnlyCollection<ArtemisLed>(_leds);
Adapter = new LayerAdapter(this); Adapter = new LayerAdapter(this);
Load(); Load();
@ -76,7 +78,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// A collection of all the LEDs this layer is assigned to. /// A collection of all the LEDs this layer is assigned to.
/// </summary> /// </summary>
public ReadOnlyCollection<ArtemisLed> Leds => _leds.AsReadOnly(); public ReadOnlyCollection<ArtemisLed> Leds { get; private set; }
/// <summary> /// <summary>
/// Defines the shape that is rendered by the <see cref="LayerBrush" />. /// Defines the shape that is rendered by the <see cref="LayerBrush" />.
@ -679,6 +681,7 @@ namespace Artemis.Core
} }
_leds = leds; _leds = leds;
Leds = new ReadOnlyCollection<ArtemisLed>(_leds);
CalculateRenderProperties(); CalculateRenderProperties();
} }

View File

@ -44,6 +44,7 @@ namespace Artemis.Core
_baseValue = default!; _baseValue = default!;
_keyframes = new List<LayerPropertyKeyframe<T>>(); _keyframes = new List<LayerPropertyKeyframe<T>>();
Keyframes = new(_keyframes);
} }
/// <summary> /// <summary>
@ -337,7 +338,7 @@ namespace Artemis.Core
#region Keyframes #region Keyframes
private bool _keyframesEnabled; private bool _keyframesEnabled;
private List<LayerPropertyKeyframe<T>> _keyframes; private readonly List<LayerPropertyKeyframe<T>> _keyframes;
/// <summary> /// <summary>
/// Gets whether keyframes are supported on this type of property /// Gets whether keyframes are supported on this type of property
@ -363,7 +364,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a read-only list of all the keyframes on this layer property /// Gets a read-only list of all the keyframes on this layer property
/// </summary> /// </summary>
public ReadOnlyCollection<LayerPropertyKeyframe<T>> Keyframes => _keyframes.AsReadOnly(); public ReadOnlyCollection<LayerPropertyKeyframe<T>> Keyframes { get; }
/// <summary> /// <summary>
/// Gets the current keyframe in the timeline according to the current progress /// Gets the current keyframe in the timeline according to the current progress
@ -436,7 +437,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
internal void SortKeyframes() internal void SortKeyframes()
{ {
_keyframes = _keyframes.OrderBy(k => k.Position).ToList(); _keyframes.Sort((a, b) => a.Position.CompareTo(b.Position));
} }
private void UpdateKeyframes(Timeline timeline) private void UpdateKeyframes(Timeline timeline)

View File

@ -37,6 +37,9 @@ namespace Artemis.Core
_layerProperties = new List<ILayerProperty>(); _layerProperties = new List<ILayerProperty>();
_layerPropertyGroups = new List<LayerPropertyGroup>(); _layerPropertyGroups = new List<LayerPropertyGroup>();
LayerProperties = new(_layerProperties);
LayerPropertyGroups = new(_layerPropertyGroups);
} }
/// <summary> /// <summary>
@ -96,12 +99,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// A list of all layer properties in this group /// A list of all layer properties in this group
/// </summary> /// </summary>
public ReadOnlyCollection<ILayerProperty> LayerProperties => _layerProperties.AsReadOnly(); public ReadOnlyCollection<ILayerProperty> LayerProperties { get; }
/// <summary> /// <summary>
/// A list of al child groups in this group /// A list of al child groups in this group
/// </summary> /// </summary>
public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups => _layerPropertyGroups.AsReadOnly(); public ReadOnlyCollection<LayerPropertyGroup> LayerPropertyGroups { get; }
/// <summary> /// <summary>
/// Recursively gets all layer properties on this group and any subgroups /// Recursively gets all layer properties on this group and any subgroups

View File

@ -24,12 +24,15 @@ namespace Artemis.Core
{ {
_name = name; _name = name;
Entity = new ProfileCategoryEntity(); Entity = new ProfileCategoryEntity();
ProfileConfigurations = new(_profileConfigurations);
} }
internal ProfileCategory(ProfileCategoryEntity entity) internal ProfileCategory(ProfileCategoryEntity entity)
{ {
_name = null!; _name = null!;
Entity = entity; Entity = entity;
ProfileConfigurations = new(_profileConfigurations);
Load(); Load();
} }
@ -73,7 +76,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a read only collection of the profiles inside this category /// Gets a read only collection of the profiles inside this category
/// </summary> /// </summary>
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations => _profileConfigurations.AsReadOnly(); public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
/// <summary> /// <summary>
/// Gets the unique ID of this category /// Gets the unique ID of this category

View File

@ -18,12 +18,13 @@ namespace Artemis.Core
private Profile _profile; private Profile _profile;
private bool _suspended; private bool _suspended;
internal List<ProfileElement> ChildrenList; internal readonly List<ProfileElement> ChildrenList;
internal ProfileElement(Profile profile) internal ProfileElement(Profile profile)
{ {
_profile = profile; _profile = profile;
ChildrenList = new List<ProfileElement>(); ChildrenList = new List<ProfileElement>();
Children = new(ChildrenList);
} }
/// <summary> /// <summary>
@ -56,16 +57,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// The element's children /// The element's children
/// </summary> /// </summary>
public ReadOnlyCollection<ProfileElement> Children public ReadOnlyCollection<ProfileElement> Children { get; }
{
get
{
lock (ChildrenList)
{
return ChildrenList.AsReadOnly();
}
}
}
/// <summary> /// <summary>
/// The order in which this element appears in the update loop and editor /// The order in which this element appears in the update loop and editor

View File

@ -24,6 +24,7 @@ namespace Artemis.Core
Timeline = new Timeline(); Timeline = new Timeline();
ExpandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
LayerEffectsList = new List<BaseLayerEffect>(); LayerEffectsList = new List<BaseLayerEffect>();
LayerEffects = new(LayerEffectsList);
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
@ -223,12 +224,12 @@ namespace Artemis.Core
#region Effect management #region Effect management
internal List<BaseLayerEffect> LayerEffectsList; internal readonly List<BaseLayerEffect> LayerEffectsList;
/// <summary> /// <summary>
/// Gets a read-only collection of the layer effects on this entity /// Gets a read-only collection of the layer effects on this entity
/// </summary> /// </summary>
public ReadOnlyCollection<BaseLayerEffect> LayerEffects => LayerEffectsList.AsReadOnly(); public ReadOnlyCollection<BaseLayerEffect> LayerEffects { get; }
/// <summary> /// <summary>
/// Adds a the layer effect described inthe provided <paramref name="descriptor" /> /// Adds a the layer effect described inthe provided <paramref name="descriptor" />

View File

@ -24,6 +24,7 @@ namespace Artemis.Core
MainSegmentLength = TimeSpan.FromSeconds(5); MainSegmentLength = TimeSpan.FromSeconds(5);
_extraTimelines = new List<Timeline>(); _extraTimelines = new List<Timeline>();
ExtraTimelines = new(_extraTimelines);
Save(); Save();
} }
@ -32,6 +33,7 @@ namespace Artemis.Core
{ {
Entity = entity; Entity = entity;
_extraTimelines = new List<Timeline>(); _extraTimelines = new List<Timeline>();
ExtraTimelines = new(_extraTimelines);
Load(); Load();
} }
@ -45,6 +47,7 @@ namespace Artemis.Core
EndSegmentLength = Parent.EndSegmentLength; EndSegmentLength = Parent.EndSegmentLength;
_extraTimelines = new List<Timeline>(); _extraTimelines = new List<Timeline>();
ExtraTimelines = new(_extraTimelines);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -150,7 +153,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a list of extra copies of the timeline applied to this timeline /// Gets a list of extra copies of the timeline applied to this timeline
/// </summary> /// </summary>
public ReadOnlyCollection<Timeline> ExtraTimelines => _extraTimelines.AsReadOnly(); public ReadOnlyCollection<Timeline> ExtraTimelines { get; }
/// <summary> /// <summary>
/// Gets a boolean indicating whether the timeline has finished its run /// Gets a boolean indicating whether the timeline has finished its run

View File

@ -59,8 +59,18 @@ namespace Artemis.Core
internal void CalculateRectangles() internal void CalculateRectangles()
{ {
Rectangle = RgbLed.Boundary.ToSKRect(); Rectangle = Utilities.CreateScaleCompatibleRect(
AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRect(); RgbLed.Boundary.Location.X,
RgbLed.Boundary.Location.Y,
RgbLed.Boundary.Size.Width,
RgbLed.Boundary.Size.Height
);
AbsoluteRectangle = Utilities.CreateScaleCompatibleRect(
RgbLed.AbsoluteBoundary.Location.X,
RgbLed.AbsoluteBoundary.Location.Y,
RgbLed.AbsoluteBoundary.Size.Width,
RgbLed.AbsoluteBoundary.Size.Height
);
} }
} }
} }

View File

@ -17,13 +17,14 @@ namespace Artemis.Core.LayerBrushes
protected LayerBrushProvider() protected LayerBrushProvider()
{ {
_layerBrushDescriptors = new List<LayerBrushDescriptor>(); _layerBrushDescriptors = new List<LayerBrushDescriptor>();
LayerBrushDescriptors = new(_layerBrushDescriptors);
Disabled += OnDisabled; Disabled += OnDisabled;
} }
/// <summary> /// <summary>
/// A read-only collection of all layer brushes added with <see cref="RegisterLayerBrushDescriptor{T}" /> /// A read-only collection of all layer brushes added with <see cref="RegisterLayerBrushDescriptor{T}" />
/// </summary> /// </summary>
public ReadOnlyCollection<LayerBrushDescriptor> LayerBrushDescriptors => _layerBrushDescriptors.AsReadOnly(); public ReadOnlyCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; }
/// <summary> /// <summary>
/// Registers a layer brush descriptor for a given layer brush, so that it appears in the UI. /// Registers a layer brush descriptor for a given layer brush, so that it appears in the UI.

View File

@ -18,13 +18,14 @@ namespace Artemis.Core.LayerEffects
protected LayerEffectProvider() protected LayerEffectProvider()
{ {
_layerEffectDescriptors = new List<LayerEffectDescriptor>(); _layerEffectDescriptors = new List<LayerEffectDescriptor>();
LayerEffectDescriptors = new(_layerEffectDescriptors);
Disabled += OnDisabled; Disabled += OnDisabled;
} }
/// <summary> /// <summary>
/// A read-only collection of all layer effects added with <see cref="RegisterLayerEffectDescriptor{T}" /> /// A read-only collection of all layer effects added with <see cref="RegisterLayerEffectDescriptor{T}" />
/// </summary> /// </summary>
public ReadOnlyCollection<LayerEffectDescriptor> LayerEffectDescriptors => _layerEffectDescriptors.AsReadOnly(); public ReadOnlyCollection<LayerEffectDescriptor> LayerEffectDescriptors { get; }
/// <summary> /// <summary>
/// Adds a layer effect descriptor for a given layer effect, so that it appears in the UI. /// Adds a layer effect descriptor for a given layer effect, so that it appears in the UI.

View File

@ -26,6 +26,9 @@ namespace Artemis.Core.Modules
// These are both set right after construction to keep the constructor of inherited classes clean // These are both set right after construction to keep the constructor of inherited classes clean
Module = null!; Module = null!;
DataModelDescription = null!; DataModelDescription = null!;
ActivePaths = new(_activePaths);
DynamicChildren = new(_dynamicChildren);
} }
/// <summary> /// <summary>
@ -52,13 +55,13 @@ namespace Artemis.Core.Modules
/// Gets an read-only dictionary of all dynamic children /// Gets an read-only dictionary of all dynamic children
/// </summary> /// </summary>
[DataModelIgnore] [DataModelIgnore]
public ReadOnlyDictionary<string, DynamicChild> DynamicChildren => new(_dynamicChildren); public ReadOnlyDictionary<string, DynamicChild> DynamicChildren { get; }
/// <summary> /// <summary>
/// Gets a read-only list of <see cref="DataModelPath" />s targeting this data model /// Gets a read-only list of <see cref="DataModelPath" />s targeting this data model
/// </summary> /// </summary>
[DataModelIgnore] [DataModelIgnore]
public ReadOnlyCollection<DataModelPath> ActivePaths => _activePaths.AsReadOnly(); public ReadOnlyCollection<DataModelPath> ActivePaths { get; }
/// <summary> /// <summary>
/// Returns a read-only collection of all properties in this datamodel that are to be ignored /// Returns a read-only collection of all properties in this datamodel that are to be ignored

View File

@ -114,6 +114,12 @@ namespace Artemis.Core.Modules
private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new(); private readonly List<(DefaultCategoryName, string)> _defaultProfilePaths = new();
private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new(); private readonly List<(DefaultCategoryName, string)> _pendingDefaultProfilePaths = new();
protected Module()
{
DefaultProfilePaths = new ReadOnlyCollection<(DefaultCategoryName, string)>(_defaultProfilePaths);
HiddenProperties = new(HiddenPropertiesList);
}
/// <summary> /// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c> /// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
/// </summary> /// </summary>
@ -122,7 +128,7 @@ namespace Artemis.Core.Modules
/// <summary> /// <summary>
/// Gets a read only collection of default profile paths /// Gets a read only collection of default profile paths
/// </summary> /// </summary>
public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly(); public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths { get; }
/// <summary> /// <summary>
/// A list of activation requirements /// A list of activation requirements
@ -176,7 +182,7 @@ namespace Artemis.Core.Modules
/// <summary> /// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c> /// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
/// </summary> /// </summary>
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly(); public ReadOnlyCollection<PropertyInfo> HiddenProperties { get; }
internal DataModel? InternalDataModel { get; set; } internal DataModel? InternalDataModel { get; set; }

View File

@ -30,6 +30,9 @@ namespace Artemis.Core
_features = new List<PluginFeatureInfo>(); _features = new List<PluginFeatureInfo>();
_profilers = new List<Profiler>(); _profilers = new List<Profiler>();
Features = new(_features);
Profilers = new(_profilers);
} }
/// <summary> /// <summary>
@ -64,12 +67,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a read-only collection of all features this plugin provides /// Gets a read-only collection of all features this plugin provides
/// </summary> /// </summary>
public ReadOnlyCollection<PluginFeatureInfo> Features => _features.AsReadOnly(); public ReadOnlyCollection<PluginFeatureInfo> Features { get; }
/// <summary> /// <summary>
/// Gets a read-only collection of profiles running on the plugin /// Gets a read-only collection of profiles running on the plugin
/// </summary> /// </summary>
public ReadOnlyCollection<Profiler> Profilers => _profilers.AsReadOnly(); public ReadOnlyCollection<Profiler> Profilers { get; }
/// <summary> /// <summary>
/// The assembly the plugin code lives in /// The assembly the plugin code lives in

View File

@ -44,6 +44,11 @@ namespace Artemis.Core.ScriptingProviders
/// </summary> /// </summary>
public abstract class ScriptingProvider : PluginFeature public abstract class ScriptingProvider : PluginFeature
{ {
protected ScriptingProvider()
{
Scripts = new(InternalScripts);
}
/// <summary> /// <summary>
/// Gets the name of the scripting language this provider provides /// Gets the name of the scripting language this provider provides
/// </summary> /// </summary>
@ -52,7 +57,7 @@ namespace Artemis.Core.ScriptingProviders
/// <summary> /// <summary>
/// Gets a list of all active scripts belonging to this scripting provider /// Gets a list of all active scripts belonging to this scripting provider
/// </summary> /// </summary>
public ReadOnlyCollection<Script> Scripts => InternalScripts.AsReadOnly(); public ReadOnlyCollection<Script> Scripts { get; }
internal abstract Type GlobalScriptType { get; } internal abstract Type GlobalScriptType { get; }
internal abstract Type ProfileScriptType { get; } internal abstract Type ProfileScriptType { get; }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Artemis.Core.SkiaSharp; using Artemis.Core.SkiaSharp;
using RGB.NET.Core; using RGB.NET.Core;
@ -13,22 +14,36 @@ namespace Artemis.Core
/// </summary> /// </summary>
public sealed class SKTexture : PixelTexture<byte>, IDisposable public sealed class SKTexture : PixelTexture<byte>, IDisposable
{ {
private readonly bool _isScaledDown;
private readonly SKPixmap _pixelData; private readonly SKPixmap _pixelData;
private readonly IntPtr _pixelDataPtr; private readonly IntPtr _pixelDataPtr;
private readonly Dictionary<Led, SKRectI> _ledRects;
#region Constructors #region Constructors
internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, DATA_PER_PIXEL, new AverageByteSampler()) internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale, IReadOnlyCollection<ArtemisDevice> devices) : base(width, height, DATA_PER_PIXEL,
new AverageByteSampler())
{ {
ImageInfo = new SKImageInfo(width, height); ImageInfo = new SKImageInfo(width, height);
Surface = graphicsContext == null Surface = graphicsContext == null
? SKSurface.Create(ImageInfo) ? SKSurface.Create(ImageInfo)
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo); : SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
RenderScale = scale; RenderScale = scale;
_isScaledDown = Math.Abs(scale - 1) > 0.001;
_pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize); _pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize);
_pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes); _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)
);
}
}
} }
#endregion #endregion
@ -80,63 +95,11 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
protected override Color GetColor(in ReadOnlySpan<byte> pixel) protected override Color GetColor(in ReadOnlySpan<byte> pixel)
{ {
return new(pixel[2], pixel[1], pixel[0]); return new Color(pixel[2], pixel[1], pixel[0]);
} }
/// <inheritdoc /> /// <inheritdoc />
public override Color this[in Rectangle rectangle] public override Color this[in Rectangle rectangle] => Color.Transparent;
{
get
{
if (Data.Length == 0) return Color.Transparent;
SKRectI skRectI = CreatedFlooredRectI(
Size.Width * rectangle.Location.X.Clamp(0, 1),
Size.Height * rectangle.Location.Y.Clamp(0, 1),
Size.Width * rectangle.Size.Width.Clamp(0, 1),
Size.Height * rectangle.Size.Height.Clamp(0, 1)
);
if (skRectI.Width == 0 || skRectI.Height == 0) return Color.Transparent;
if (skRectI.Width == 1 && skRectI.Height == 1) return GetColor(GetPixelData(skRectI.Left, skRectI.Top));
int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<byte> buffer = stackalloc byte[bufferSize];
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
return GetColor(pixelData);
}
else
{
byte[] rent = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(rent).Slice(0, bufferSize);
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
ArrayPool<byte>.Shared.Return(rent);
return GetColor(pixelData);
}
}
}
private static SKRectI CreatedFlooredRectI(float x, float y, float width, float height)
{
return new(
width <= 0.0 ? checked((int) Math.Floor(x)) : checked((int) Math.Ceiling(x)),
height <= 0.0 ? checked((int) Math.Floor(y)) : checked((int) Math.Ceiling(y)),
width >= 0.0 ? checked((int) Math.Floor(x + width)) : checked((int) Math.Ceiling(x + width)),
height >= 0.0 ? checked((int) Math.Floor(y + height)) : checked((int) Math.Ceiling(y + height))
);
}
#endregion #endregion
@ -169,5 +132,40 @@ namespace Artemis.Core
public bool IsInvalid { get; private set; } public bool IsInvalid { get; private set; }
#endregion #endregion
internal Color GetColorAtRenderTarget(in RenderTarget renderTarget)
{
if (Data.Length == 0) return Color.Transparent;
SKRectI skRectI = _ledRects[renderTarget.Led];
if (skRectI.Width <= 0 || skRectI.Height <= 0)
return Color.Transparent;
int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<byte> buffer = stackalloc byte[bufferSize];
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
return GetColor(pixelData);
}
else
{
byte[] rent = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(rent).Slice(0, bufferSize);
GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
Span<byte> pixelData = stackalloc byte[DATA_PER_PIXEL];
Sampler.Sample(new SamplerInfo<byte>(skRectI.Width, skRectI.Height, buffer), pixelData);
ArrayPool<byte>.Shared.Return(rent);
return GetColor(pixelData);
}
}
} }
} }

View File

@ -0,0 +1,44 @@
using RGB.NET.Core;
namespace Artemis.Core
{
internal class SKTextureBrush : AbstractBrush
{
#region Properties & Fields
private SKTexture? _texture;
/// <summary>
/// Gets or sets the texture drawn by this brush.
/// </summary>
public SKTexture? Texture
{
get => _texture;
set => SetProperty(ref _texture, value);
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="TextureBrush" /> class.
/// </summary>
/// <param name="texture">The texture drawn by this brush.</param>
public SKTextureBrush(SKTexture? texture)
{
this.Texture = texture;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget)
{
return Texture?.GetColorAtRenderTarget(renderTarget) ?? Color.Transparent;
}
#endregion
}
}

View File

@ -25,7 +25,7 @@ namespace Artemis.Core.Services
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly PluginSetting<double> _renderScaleSetting; private readonly PluginSetting<double> _renderScaleSetting;
private readonly PluginSetting<int> _targetFrameRateSetting; private readonly PluginSetting<int> _targetFrameRateSetting;
private readonly TextureBrush _textureBrush = new(ITexture.Empty) {CalculationMode = RenderMode.Absolute}; private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
private Dictionary<Led, ArtemisLed> _ledMap; private Dictionary<Led, ArtemisLed> _ledMap;
private ListLedGroup? _surfaceLedGroup; private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture; private SKTexture? _texture;
@ -36,9 +36,10 @@ namespace Artemis.Core.Services
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25);
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5); _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25);
Surface = new RGBSurface(); Surface = new RGBSurface();
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
// Let's throw these for now // Let's throw these for now
Surface.Exception += SurfaceOnException; Surface.Exception += SurfaceOnException;
@ -49,10 +50,14 @@ namespace Artemis.Core.Services
_devices = new List<ArtemisDevice>(); _devices = new List<ArtemisDevice>();
_ledMap = new Dictionary<Led, ArtemisLed>(); _ledMap = new Dictionary<Led, ArtemisLed>();
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
LedMap = new ReadOnlyDictionary<Led, ArtemisLed>(_ledMap);
UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value};
SetRenderPaused(true); SetRenderPaused(true);
Surface.RegisterUpdateTrigger(UpdateTrigger); Surface.RegisterUpdateTrigger(UpdateTrigger);
Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
} }
@ -85,10 +90,11 @@ namespace Artemis.Core.Services
try try
{ {
_ledMap = new Dictionary<Led, ArtemisLed>(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed)); _ledMap = new Dictionary<Led, ArtemisLed>(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed));
LedMap = new ReadOnlyDictionary<Led, ArtemisLed>(_ledMap);
if (_surfaceLedGroup == null) if (_surfaceLedGroup == null)
{ {
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush }; _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
OnLedsChanged(); OnLedsChanged();
return; return;
} }
@ -99,7 +105,7 @@ namespace Artemis.Core.Services
_surfaceLedGroup.Detach(); _surfaceLedGroup.Detach();
// Apply the application wide brush and decorator // Apply the application wide brush and decorator
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush }; _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
OnLedsChanged(); OnLedsChanged();
} }
} }
@ -108,7 +114,6 @@ namespace Artemis.Core.Services
if (changedRenderPaused) if (changedRenderPaused)
SetRenderPaused(false); SetRenderPaused(false);
} }
} }
private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
@ -129,12 +134,17 @@ namespace Artemis.Core.Services
private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
{ {
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
_texture?.Invalidate(); _texture?.Invalidate();
foreach (ArtemisDevice artemisDevice in Devices)
artemisDevice.CalculateRenderProperties();
OnLedsChanged();
} }
public IReadOnlyCollection<ArtemisDevice> EnabledDevices => _enabledDevices.AsReadOnly(); public IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
public IReadOnlyCollection<ArtemisDevice> Devices => _devices.AsReadOnly(); public IReadOnlyCollection<ArtemisDevice> Devices { get; }
public IReadOnlyDictionary<Led, ArtemisLed> LedMap => new ReadOnlyDictionary<Led, ArtemisLed>(_ledMap); public IReadOnlyDictionary<Led, ArtemisLed> LedMap { get; private set; }
public RGBSurface Surface { get; set; } public RGBSurface Surface { get; set; }
public bool IsRenderPaused { get; set; } public bool IsRenderPaused { get; set; }
@ -310,7 +320,7 @@ namespace Artemis.Core.Services
int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt()); int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
_texture?.Dispose(); _texture?.Dispose();
_texture = new SKTexture(graphicsContext, width, height, renderScale); _texture = new SKTexture(graphicsContext, width, height, renderScale, Devices);
_textureBrush.Texture = _texture; _textureBrush.Texture = _texture;

View File

@ -24,6 +24,7 @@ namespace Artemis.Core.Services
_profileService = profileService; _profileService = profileService;
InternalGlobalScripts = new List<GlobalScript>(); InternalGlobalScripts = new List<GlobalScript>();
GlobalScripts = new(InternalGlobalScripts);
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
@ -80,7 +81,7 @@ namespace Artemis.Core.Services
CreateScriptInstance(profile, scriptConfiguration); CreateScriptInstance(profile, scriptConfiguration);
} }
public ReadOnlyCollection<GlobalScript> GlobalScripts => InternalGlobalScripts.AsReadOnly(); public ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
public GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration) public GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration)
{ {

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -42,8 +43,7 @@ namespace Artemis.Core.Services
_profileCategoryRepository = profileCategoryRepository; _profileCategoryRepository = profileCategoryRepository;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_profileRepository = profileRepository; _profileRepository = profileRepository;
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll() _profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
.Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
_rgbService.LedsChanged += RgbServiceOnLedsChanged; _rgbService.LedsChanged += RgbServiceOnLedsChanged;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -18,8 +19,8 @@ namespace Artemis.Core
public static void PrepareFirstLaunch() public static void PrepareFirstLaunch()
{ {
CreateAccessibleDirectory(Constants.DataFolder); CreateAccessibleDirectory(Constants.DataFolder);
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"plugins")); CreateAccessibleDirectory(Path.Combine(Constants.DataFolder, "plugins"));
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"user layouts")); CreateAccessibleDirectory(Path.Combine(Constants.DataFolder, "user layouts"));
} }
/// <summary> /// <summary>
@ -102,6 +103,30 @@ namespace Artemis.Core
RestartRequested?.Invoke(null, e); RestartRequested?.Invoke(null, e);
} }
#region Scaling
internal static int RenderScaleMultiplier { get; set; } = 2;
internal static SKRectI CreateScaleCompatibleRect(float x, float y, float width, float height)
{
int roundX = (int) MathF.Floor(x);
int roundY = (int) MathF.Floor(y);
int roundWidth = (int) MathF.Ceiling(width);
int roundHeight = (int) MathF.Ceiling(height);
if (RenderScaleMultiplier == 1)
return SKRectI.Create(roundX, roundY, roundWidth, roundHeight);
return SKRectI.Create(
roundX - (roundX % RenderScaleMultiplier),
roundY - (roundY % RenderScaleMultiplier),
roundWidth - (roundWidth % RenderScaleMultiplier),
roundHeight - (roundHeight % RenderScaleMultiplier)
);
}
#endregion
#region Events #region Events
/// <summary> /// <summary>

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -9,15 +11,15 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Artemis.Core; using Artemis.Core;
using Stylet;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared
{ {
/// <summary> /// <summary>
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors /// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
/// </summary> /// </summary>
public class DeviceVisualizer : FrameworkElement, IDisposable public class DeviceVisualizer : FrameworkElement
{ {
/// <summary> /// <summary>
/// The device to visualize /// The device to visualize
@ -34,14 +36,16 @@ namespace Artemis.UI.Shared
/// <summary> /// <summary>
/// A list of LEDs to highlight /// A list of LEDs to highlight
/// </summary> /// </summary>
public static readonly DependencyProperty HighlightedLedsProperty = DependencyProperty.Register(nameof(HighlightedLeds), typeof(IEnumerable<ArtemisLed>), typeof(DeviceVisualizer), public static readonly DependencyProperty HighlightedLedsProperty = DependencyProperty.Register(nameof(HighlightedLeds), typeof(ObservableCollection<ArtemisLed>), typeof(DeviceVisualizer),
new FrameworkPropertyMetadata(default(IEnumerable<ArtemisLed>))); new FrameworkPropertyMetadata(default(ObservableCollection<ArtemisLed>), HighlightedLedsPropertyChanged));
private readonly DrawingGroup _backingStore; private readonly DrawingGroup _backingStore;
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds; private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
private readonly Timer _timer; private readonly DispatcherTimer _timer;
private BitmapImage? _deviceImage; private BitmapImage? _deviceImage;
private ArtemisDevice? _oldDevice; private ArtemisDevice? _oldDevice;
private List<DeviceVisualizerLed> _highlightedLeds;
private List<DeviceVisualizerLed> _dimmedLeds;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="DeviceVisualizer" /> class /// Creates a new instance of the <see cref="DeviceVisualizer" /> class
@ -50,9 +54,10 @@ namespace Artemis.UI.Shared
{ {
_backingStore = new DrawingGroup(); _backingStore = new DrawingGroup();
_deviceVisualizerLeds = new List<DeviceVisualizerLed>(); _deviceVisualizerLeds = new List<DeviceVisualizerLed>();
_dimmedLeds = new List<DeviceVisualizerLed>();
// Run an update timer at 25 fps // Run an update timer at 25 fps
_timer = new Timer(40); _timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
MouseLeftButtonUp += OnMouseLeftButtonUp; MouseLeftButtonUp += OnMouseLeftButtonUp;
Loaded += OnLoaded; Loaded += OnLoaded;
@ -80,9 +85,9 @@ namespace Artemis.UI.Shared
/// <summary> /// <summary>
/// Gets or sets a list of LEDs to highlight /// Gets or sets a list of LEDs to highlight
/// </summary> /// </summary>
public IEnumerable<ArtemisLed>? HighlightedLeds public ObservableCollection<ArtemisLed>? HighlightedLeds
{ {
get => (IEnumerable<ArtemisLed>) GetValue(HighlightedLedsProperty); get => (ObservableCollection<ArtemisLed>) GetValue(HighlightedLedsProperty);
set => SetValue(HighlightedLedsProperty, value); set => SetValue(HighlightedLedsProperty, value);
} }
@ -158,10 +163,9 @@ namespace Artemis.UI.Shared
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
_timer.Dispose(); _timer.Stop();
} }
private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight) private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
{ {
double scale; double scale;
@ -191,8 +195,10 @@ namespace Artemis.UI.Shared
private void OnUnloaded(object? sender, RoutedEventArgs e) private void OnUnloaded(object? sender, RoutedEventArgs e)
{ {
_timer.Stop(); _timer.Stop();
_timer.Elapsed -= TimerOnTick; _timer.Tick -= TimerOnTick;
if (HighlightedLeds != null)
HighlightedLeds.CollectionChanged -= HighlightedLedsChanged;
if (_oldDevice != null) if (_oldDevice != null)
{ {
if (Device != null) if (Device != null)
@ -223,16 +229,34 @@ namespace Artemis.UI.Shared
private void OnLoaded(object? sender, RoutedEventArgs e) private void OnLoaded(object? sender, RoutedEventArgs e)
{ {
_timer.Start(); _timer.Start();
_timer.Elapsed += TimerOnTick; _timer.Tick += TimerOnTick;
} }
private void TimerOnTick(object? sender, EventArgs e) private void TimerOnTick(object? sender, EventArgs e)
{ {
Execute.PostToUIThread(() => if (ShowColors && Visibility == Visibility.Visible)
Render();
}
private void Render()
{
DrawingContext drawingContext = _backingStore.Append();
if (_highlightedLeds.Any())
{ {
if (ShowColors && Visibility == Visibility.Visible) foreach (DeviceVisualizerLed deviceVisualizerLed in _highlightedLeds)
Render(); deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
});
foreach (DeviceVisualizerLed deviceVisualizerLed in _dimmedLeds)
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, true);
}
else
{
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
}
drawingContext.Close();
} }
private void UpdateTransform() private void UpdateTransform()
@ -241,22 +265,12 @@ namespace Artemis.UI.Shared
InvalidateMeasure(); InvalidateMeasure();
} }
private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
}
private static void ShowColorsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
}
private void SetupForDevice() private void SetupForDevice()
{ {
_deviceImage = null; _deviceImage = null;
_deviceVisualizerLeds.Clear(); _deviceVisualizerLeds.Clear();
_highlightedLeds = new List<DeviceVisualizerLed>();
_dimmedLeds = new List<DeviceVisualizerLed>();
if (Device == null) if (Device == null)
return; return;
@ -319,40 +333,47 @@ namespace Artemis.UI.Shared
private void DeviceUpdated(object? sender, EventArgs e) private void DeviceUpdated(object? sender, EventArgs e)
{ {
Execute.PostToUIThread(SetupForDevice); Dispatcher.Invoke(SetupForDevice);
} }
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e) private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
Execute.PostToUIThread(SetupForDevice); Dispatcher.Invoke(SetupForDevice);
} }
private void Render() private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
DrawingContext drawingContext = _backingStore.Append(); DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
}
if (HighlightedLeds != null && HighlightedLeds.Any()) private static void ShowColorsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
}
private static void HighlightedLedsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
if (e.OldValue is ObservableCollection<ArtemisLed> oldCollection)
oldCollection.CollectionChanged -= deviceVisualizer.HighlightedLedsChanged;
if (e.NewValue is ObservableCollection<ArtemisLed> newCollection)
newCollection.CollectionChanged += deviceVisualizer.HighlightedLedsChanged;
}
private void HighlightedLedsChanged(object? sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
if (HighlightedLeds != null)
{ {
// Faster on large devices, maybe a bit slower on smaller ones but that's ok _highlightedLeds = _deviceVisualizerLeds.Where(l => HighlightedLeds.Contains(l.Led)).ToList();
ILookup<ArtemisLed, ArtemisLed> toHighlight = HighlightedLeds.ToLookup(l => l); _dimmedLeds = _deviceVisualizerLeds.Where(l => !HighlightedLeds.Contains(l.Led)).ToList();
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, !toHighlight.Contains(deviceVisualizerLed.Led));
} }
else else
{ {
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds) _highlightedLeds = new List<DeviceVisualizerLed>();
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false); _dimmedLeds = new List<DeviceVisualizerLed>();
} }
drawingContext.Close();
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
@ -26,10 +27,13 @@ namespace Artemis.UI.Shared.Services
_kernel = kernel; _kernel = kernel;
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>(); _registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>(); _registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
RegisteredDataModelEditors = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelEditors);
RegisteredDataModelDisplays = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelDisplays);
} }
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors => _registeredDataModelEditors.AsReadOnly(); public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays => _registeredDataModelDisplays.AsReadOnly(); public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
public DataModelPropertiesViewModel GetMainDataModelVisualization() public DataModelPropertiesViewModel GetMainDataModelVisualization()
{ {

View File

@ -12,7 +12,6 @@ using Ninject.Parameters;
using Serilog; using Serilog;
using SkiaSharp; using SkiaSharp;
using SkiaSharp.Views.WPF; using SkiaSharp.Views.WPF;
using Stylet;
namespace Artemis.UI.Shared.Services namespace Artemis.UI.Shared.Services
{ {
@ -39,15 +38,19 @@ namespace Artemis.UI.Shared.Services
_rgbService = rgbService; _rgbService = rgbService;
_moduleService = moduleService; _moduleService = moduleService;
_registeredPropertyEditors = new List<PropertyInputRegistration>(); _registeredPropertyEditors = new List<PropertyInputRegistration>();
RegisteredPropertyEditors = new(_registeredPropertyEditors);
coreService.FrameRendered += CoreServiceOnFrameRendered; coreService.FrameRendered += CoreServiceOnFrameRendered;
PixelsPerSecond = 100; PixelsPerSecond = 100;
} }
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{ {
if (!_doTick) return; if (!_doTick)
return;
_doTick = false; _doTick = false;
Execute.PostToUIThread(OnProfilePreviewUpdated);
OnProfilePreviewUpdated();
} }
private void ReloadProfile() private void ReloadProfile()
@ -103,7 +106,7 @@ namespace Artemis.UI.Shared.Services
} }
} }
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors { get; }
public bool Playing { get; set; } public bool Playing { get; set; }
@ -144,6 +147,7 @@ namespace Artemis.UI.Shared.Services
{ {
if (_currentTime.Equals(value)) return; if (_currentTime.Equals(value)) return;
_currentTime = value; _currentTime = value;
Tick(); Tick();
OnCurrentTimeChanged(); OnCurrentTimeChanged();
} }

View File

@ -25,7 +25,8 @@ namespace Artemis.UI.Shared
HitTestResultBehavior ResultCallback(HitTestResult r) => HitTestResultBehavior.Continue; HitTestResultBehavior ResultCallback(HitTestResult r) => HitTestResultBehavior.Continue;
HitTestFilterBehavior FilterCallback(DependencyObject e) HitTestFilterBehavior FilterCallback(DependencyObject e)
{ {
if (e is FrameworkElement fe && fe.DataContext is T context && !result.Contains(context)) result.Add(context); if (e is FrameworkElement fe && fe.DataContext is T context && !result.Contains(context))
result.Add(context);
return HitTestFilterBehavior.Continue; return HitTestFilterBehavior.Continue;
} }

View File

@ -583,30 +583,28 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
return; return;
} }
Execute.PostToUIThread(() => TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
if (SelectedProfileElement != null)
{ {
TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime)); if (Repeating && RepeatTimeline)
if (SelectedProfileElement != null)
{ {
if (Repeating && RepeatTimeline) if (newTime > SelectedProfileElement.Timeline.Length)
{ newTime = TimeSpan.Zero;
if (newTime > SelectedProfileElement.Timeline.Length)
newTime = TimeSpan.Zero;
}
else if (Repeating && RepeatSegment)
{
if (newTime > GetCurrentSegmentEnd())
newTime = GetCurrentSegmentStart();
}
else if (newTime > SelectedProfileElement.Timeline.Length)
{
newTime = SelectedProfileElement.Timeline.Length;
Pause();
}
} }
else if (Repeating && RepeatSegment)
{
if (newTime > GetCurrentSegmentEnd())
newTime = GetCurrentSegmentStart();
}
else if (newTime > SelectedProfileElement.Timeline.Length)
{
newTime = SelectedProfileElement.Timeline.Length;
Pause();
}
}
ProfileEditorService.CurrentTime = newTime; // Update current time on high priority to keep things buttery smooth as if you're using the mouse ༼ つ ◕_◕ ༽つ
}); Execute.OnUIThreadSync(() => ProfileEditorService.CurrentTime = newTime);
} }
#endregion #endregion

View File

@ -8,6 +8,8 @@ using Artemis.UI.Extensions;
using Artemis.UI.Screens.Shared; using Artemis.UI.Screens.Shared;
using Artemis.UI.Services; using Artemis.UI.Services;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using SkiaSharp;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Visualization namespace Artemis.UI.Screens.ProfileEditor.Visualization
{ {
@ -95,6 +97,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
CreateViewportRectangle(); CreateViewportRectangle();
} }
#region Updating
private LayerShape _lastShape;
private Rect _lastBounds;
private SKPoint _lastAnchor;
private SKPoint _lastPosition;
private double _lastRotation;
private SKSize _lastScale;
private void CreateShapeGeometry() private void CreateShapeGeometry()
{ {
if (Layer.LayerShape == null || !Layer.Leds.Any()) if (Layer.LayerShape == null || !Layer.Leds.Any())
@ -104,21 +116,22 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
} }
Rect bounds = _layerEditorService.GetLayerBounds(Layer); Rect bounds = _layerEditorService.GetLayerBounds(Layer);
Geometry shapeGeometry = Geometry.Empty;
switch (Layer.LayerShape) // Only bother the UI thread for both of these if we're sure
if (HasShapeChanged(bounds))
{ {
case EllipseShape _: Execute.OnUIThreadSync(() =>
shapeGeometry = new EllipseGeometry(bounds); {
break; if (Layer.LayerShape is RectangleShape)
case RectangleShape _: ShapeGeometry = new RectangleGeometry(bounds);
shapeGeometry = new RectangleGeometry(bounds); if (Layer.LayerShape is EllipseShape)
break; ShapeGeometry = new EllipseGeometry(bounds);
});
}
if ((Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation) && HasTransformationChanged())
{
Execute.OnUIThreadSync(() => ShapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer));
} }
if (Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation)
shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer);
ShapeGeometry = shapeGeometry;
} }
private void CreateViewportRectangle() private void CreateViewportRectangle()
@ -132,44 +145,30 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
ViewportRectangle = _layerEditorService.GetLayerBounds(Layer); ViewportRectangle = _layerEditorService.GetLayerBounds(Layer);
} }
private Geometry CreateRectangleGeometry(ArtemisLed led) private bool HasShapeChanged(Rect bounds)
{ {
Rect rect = led.RgbLed.AbsoluteBoundary.ToWindowsRect(1); bool result = !Equals(_lastBounds, bounds) || !Equals(_lastShape, Layer.LayerShape);
return new RectangleGeometry(rect); _lastShape = Layer.LayerShape;
_lastBounds = bounds;
return result;
} }
private Geometry CreateCircleGeometry(ArtemisLed led) private bool HasTransformationChanged()
{ {
Rect rect = led.RgbLed.AbsoluteBoundary.ToWindowsRect(1); bool result = _lastAnchor != Layer.Transform.AnchorPoint.CurrentValue ||
return new EllipseGeometry(rect); _lastPosition != Layer.Transform.Position.CurrentValue ||
_lastRotation != Layer.Transform.Rotation.CurrentValue ||
_lastScale != Layer.Transform.Scale.CurrentValue;
_lastAnchor = Layer.Transform.AnchorPoint.CurrentValue;
_lastPosition = Layer.Transform.Position.CurrentValue;
_lastRotation = Layer.Transform.Rotation.CurrentValue;
_lastScale = Layer.Transform.Scale.CurrentValue;
return result;
} }
private Geometry CreateCustomGeometry(ArtemisLed led, double deflateAmount) #endregion
{
Rect rect = led.RgbLed.AbsoluteBoundary.ToWindowsRect(1);
try
{
PathGeometry geometry = Geometry.Combine(
Geometry.Empty,
Geometry.Parse(led.RgbLed.ShapeData),
GeometryCombineMode.Union,
new TransformGroup
{
Children = new TransformCollection
{
new ScaleTransform(rect.Width, rect.Height),
new TranslateTransform(rect.X, rect.Y)
}
}
);
return geometry;
}
catch (Exception)
{
return CreateRectangleGeometry(led);
}
}
#region Overrides of Screen #region Overrides of Screen

View File

@ -51,7 +51,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
private void Update() private void Update()
{ {
if (!(ProfileEditorService.SelectedProfileElement is Layer layer)) if (ProfileEditorService.SelectedProfileElement is not Layer layer)
return; return;
ShapePath = _layerEditorService.GetLayerPath(layer, true, true, true); ShapePath = _layerEditorService.GetLayerPath(layer, true, true, true);

View File

@ -216,11 +216,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
public double RenderScale public double RenderScale
{ {
get => _settingsService.GetSetting("Core.RenderScale", 0.5).Value; get => _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
set set
{ {
_settingsService.GetSetting("Core.RenderScale", 0.5).Value = value; _settingsService.GetSetting("Core.RenderScale", 0.25).Value = value;
_settingsService.GetSetting("Core.RenderScale", 0.5).Save(); _settingsService.GetSetting("Core.RenderScale", 0.25).Save();
} }
} }

View File

@ -152,11 +152,6 @@ namespace Artemis.UI.Screens.Shared
PanY = rect.Top * -1 * Zoom; PanY = rect.Top * -1 * Zoom;
} }
public Rect TransformContainingRect(Rectangle rect)
{
return TransformContainingRect(rect.ToWindowsRect(1));
}
public Rect TransformContainingRect(Rect rect) public Rect TransformContainingRect(Rect rect)
{ {
// Create the same transform group the view is using // Create the same transform group the view is using
@ -168,6 +163,17 @@ namespace Artemis.UI.Screens.Shared
return transformGroup.TransformBounds(rect); return transformGroup.TransformBounds(rect);
} }
public Rect UnTransformContainingRect(Rect rect)
{
// Create the same transform group the view is using
TransformGroup transformGroup = new();
transformGroup.Children.Add(new TranslateTransform(PanX * -1, PanY * -1));
transformGroup.Children.Add(new ScaleTransform(1 / Zoom, 1 / Zoom));
// Apply it to the device rect
return transformGroup.TransformBounds(rect);
}
public Point GetRelativeMousePosition(object container, MouseEventArgs e) public Point GetRelativeMousePosition(object container, MouseEventArgs e)
{ {
// Get the mouse position relative to the panned / zoomed panel, not very MVVM but I can't find a better way // Get the mouse position relative to the panned / zoomed panel, not very MVVM but I can't find a better way

View File

@ -188,8 +188,7 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<Canvas> <Canvas>
<Rectangle ClipToBounds="False" <Rectangle Width="{Binding MaxTextureSize}"
Width="{Binding MaxTextureSize}"
Height="{Binding MaxTextureSize}" Height="{Binding MaxTextureSize}"
Stroke="{DynamicResource SecondaryHueMidBrush}" Stroke="{DynamicResource SecondaryHueMidBrush}"
StrokeThickness="{Binding MaxTextureSizeIndicatorThickness}" StrokeThickness="{Binding MaxTextureSizeIndicatorThickness}"

View File

@ -18,6 +18,7 @@ using Artemis.UI.Screens.SurfaceEditor.Visualization;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using SkiaSharp; using SkiaSharp;
using SkiaSharp.Views.WPF;
using Stylet; using Stylet;
using MouseButton = System.Windows.Input.MouseButton; using MouseButton = System.Windows.Input.MouseButton;
@ -107,7 +108,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
set => SetAndNotify(ref _colorFirstLedOnly, value); set => SetAndNotify(ref _colorFirstLedOnly, value);
} }
public double MaxTextureSize => 4096 / _settingsService.GetSetting("Core.RenderScale", 0.5).Value; public double MaxTextureSize => 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
public double MaxTextureSizeIndicatorThickness => 2 / PanZoomViewModel.Zoom; public double MaxTextureSizeIndicatorThickness => 2 / PanZoomViewModel.Zoom;
public void OpenHyperlink(object sender, RequestNavigateEventArgs e) public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
@ -305,7 +306,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
if (e.LeftButton == MouseButtonState.Pressed) if (e.LeftButton == MouseButtonState.Pressed)
StartMouseDrag(sender, position, relative); StartMouseDrag(sender, position, relative);
else else
StopMouseDrag(sender, position); StopMouseDrag(position);
} }
// ReSharper disable once UnusedMember.Global - Called from view // ReSharper disable once UnusedMember.Global - Called from view
@ -323,7 +324,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
if (_mouseDragStatus == MouseDragStatus.Dragging) if (_mouseDragStatus == MouseDragStatus.Dragging)
MoveSelected(relative); MoveSelected(relative);
else if (_mouseDragStatus == MouseDragStatus.Selecting) else if (_mouseDragStatus == MouseDragStatus.Selecting)
UpdateSelection(sender, position); UpdateSelection(position);
} }
private void StartMouseDrag(object sender, Point position, Point relative) private void StartMouseDrag(object sender, Point position, Point relative)
@ -360,17 +361,18 @@ namespace Artemis.UI.Screens.SurfaceEditor
ApplySurfaceSelection(); ApplySurfaceSelection();
} }
private void StopMouseDrag(object sender, Point position) private void StopMouseDrag(Point position)
{ {
if (_mouseDragStatus != MouseDragStatus.Dragging) if (_mouseDragStatus != MouseDragStatus.Dragging)
{ {
RectangleGeometry selectedRect = new(new Rect(_mouseDragStartPoint, position)); SKRect hitTestRect = PanZoomViewModel.UnTransformContainingRect(new Rect(_mouseDragStartPoint, position)).ToSKRect();
List<SurfaceDeviceViewModel> devices = HitTestUtilities.GetHitViewModels<SurfaceDeviceViewModel>((Visual) sender, selectedRect);
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels) foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
if (devices.Contains(device)) {
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
device.SelectionStatus = SelectionStatus.Selected; device.SelectionStatus = SelectionStatus.Selected;
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
device.SelectionStatus = SelectionStatus.None; device.SelectionStatus = SelectionStatus.None;
}
} }
else else
{ {
@ -382,7 +384,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
ApplySurfaceSelection(); ApplySurfaceSelection();
} }
private void UpdateSelection(object sender, Point position) private void UpdateSelection(Point position)
{ {
if (IsPanKeyDown()) if (IsPanKeyDown())
return; return;
@ -390,12 +392,14 @@ namespace Artemis.UI.Screens.SurfaceEditor
Rect selectedRect = new(_mouseDragStartPoint, position); Rect selectedRect = new(_mouseDragStartPoint, position);
SelectionRectangle.Rect = selectedRect; SelectionRectangle.Rect = selectedRect;
List<SurfaceDeviceViewModel> devices = HitTestUtilities.GetHitViewModels<SurfaceDeviceViewModel>((Visual) sender, SelectionRectangle); SKRect hitTestRect = PanZoomViewModel.UnTransformContainingRect(new Rect(_mouseDragStartPoint, position)).ToSKRect();
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels) foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
if (devices.Contains(device)) {
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
device.SelectionStatus = SelectionStatus.Selected; device.SelectionStatus = SelectionStatus.Selected;
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
device.SelectionStatus = SelectionStatus.None; device.SelectionStatus = SelectionStatus.None;
}
ApplySurfaceSelection(); ApplySurfaceSelection();
} }

View File

@ -103,7 +103,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Visualization
if (x < 0 || y < 0) if (x < 0 || y < 0)
return false; return false;
double maxTextureSize = 4096 / _settingsService.GetSetting("Core.RenderScale", 0.5).Value; double maxTextureSize = 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize) if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize)
return false; return false;

View File

@ -1,9 +1,12 @@
using System; using System;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using Artemis.Core; using Artemis.Core;
using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
using SkiaSharp.Views.WPF; using SkiaSharp.Views.WPF;
using Point = System.Windows.Point;
namespace Artemis.UI.Services namespace Artemis.UI.Services
{ {
@ -12,12 +15,15 @@ namespace Artemis.UI.Services
/// <inheritdoc /> /// <inheritdoc />
public Rect GetLayerBounds(Layer layer) public Rect GetLayerBounds(Layer layer)
{ {
// Adjust the render rectangle for the difference in render scale return new Rect(
return new( new Point(
layer.Bounds.Left, layer.Leds.Min(l => l.RgbLed.AbsoluteBoundary.Location.X),
layer.Bounds.Top, layer.Leds.Min(l => l.RgbLed.AbsoluteBoundary.Location.Y
Math.Max(0, layer.Bounds.Width), )),
Math.Max(0, layer.Bounds.Height) new Point(
layer.Leds.Max(l => l.RgbLed.AbsoluteBoundary.Location.X + l.RgbLed.AbsoluteBoundary.Size.Width),
layer.Leds.Max(l => l.RgbLed.AbsoluteBoundary.Location.Y + l.RgbLed.AbsoluteBoundary.Size.Height
))
); );
} }
@ -94,15 +100,16 @@ namespace Artemis.UI.Services
/// <inheritdoc /> /// <inheritdoc />
public SKPoint GetScaledPoint(Layer layer, SKPoint point, bool absolute) public SKPoint GetScaledPoint(Layer layer, SKPoint point, bool absolute)
{ {
SKRect bounds = GetLayerBounds(layer).ToSKRect();
if (absolute) if (absolute)
return new SKPoint( return new SKPoint(
100f / layer.Bounds.Width * (point.X - layer.Bounds.Left) / 100f, 100 / bounds.Width * (point.X - bounds.Left) / 100,
100f / layer.Bounds.Height * (point.Y - layer.Bounds.Top) / 100f 100 / bounds.Height * (point.Y - bounds.Top) / 100
); );
return new SKPoint( return new SKPoint(
100f / layer.Bounds.Width * point.X / 100f, 100 / bounds.Width * point.X / 100,
100f / layer.Bounds.Height * point.Y / 100f 100 / bounds.Height * point.Y / 100
); );
} }