mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'bitmapbrush'
This commit is contained in:
commit
c6064986c5
@ -160,7 +160,7 @@
|
|||||||
<Compile Include="Extensions\DirectoryInfoExtensions.cs" />
|
<Compile Include="Extensions\DirectoryInfoExtensions.cs" />
|
||||||
<Compile Include="Extensions\DoubleExtensions.cs" />
|
<Compile Include="Extensions\DoubleExtensions.cs" />
|
||||||
<Compile Include="Extensions\FloatExtensions.cs" />
|
<Compile Include="Extensions\FloatExtensions.cs" />
|
||||||
<Compile Include="Extensions\RgbColorExtensions.cs" />
|
<Compile Include="Extensions\SKColorExtensions.cs" />
|
||||||
<Compile Include="Extensions\RgbDeviceExtensions.cs" />
|
<Compile Include="Extensions\RgbDeviceExtensions.cs" />
|
||||||
<Compile Include="Extensions\RgbRectangleExtensions.cs" />
|
<Compile Include="Extensions\RgbRectangleExtensions.cs" />
|
||||||
<Compile Include="Extensions\TypeExtensions.cs" />
|
<Compile Include="Extensions\TypeExtensions.cs" />
|
||||||
@ -194,6 +194,7 @@
|
|||||||
<Compile Include="Models\Profile\Profile.cs" />
|
<Compile Include="Models\Profile\Profile.cs" />
|
||||||
<Compile Include="Ninject\CoreModule.cs" />
|
<Compile Include="Ninject\CoreModule.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="RGB.NET\BitmapBrush.cs" />
|
||||||
<Compile Include="RGB.NET\GraphicsDecorator.cs" />
|
<Compile Include="RGB.NET\GraphicsDecorator.cs" />
|
||||||
<Compile Include="Services\DeviceService.cs" />
|
<Compile Include="Services\DeviceService.cs" />
|
||||||
<Compile Include="Services\Interfaces\ILayerService.cs" />
|
<Compile Include="Services\Interfaces\ILayerService.cs" />
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
|
||||||
using Artemis.Core.RGB.NET;
|
using Artemis.Core.RGB.NET;
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
|
|
||||||
@ -7,13 +6,13 @@ namespace Artemis.Core.Events
|
|||||||
{
|
{
|
||||||
public class FrameRenderedEventArgs : EventArgs
|
public class FrameRenderedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public FrameRenderedEventArgs(GraphicsDecorator graphicsDecorator, RGBSurface rgbSurface)
|
public FrameRenderedEventArgs(BitmapBrush bitmapBrush, RGBSurface rgbSurface)
|
||||||
{
|
{
|
||||||
GraphicsDecorator = graphicsDecorator;
|
BitmapBrush = bitmapBrush;
|
||||||
RgbSurface = rgbSurface;
|
RgbSurface = rgbSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GraphicsDecorator GraphicsDecorator { get; }
|
public BitmapBrush BitmapBrush { get; }
|
||||||
public RGBSurface RgbSurface { get; }
|
public RGBSurface RgbSurface { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
using RGB.NET.Core;
|
|
||||||
using Color = System.Windows.Media.Color;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Extensions
|
|
||||||
{
|
|
||||||
public static class RgbColorExtensions
|
|
||||||
{
|
|
||||||
public static Color ToMediaColor(this global::RGB.NET.Core.Color color)
|
|
||||||
{
|
|
||||||
var (_, r, g, b) = color.GetRGBBytes();
|
|
||||||
return Color.FromRgb(r, g, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14
src/Artemis.Core/Extensions/SKColorExtensions.cs
Normal file
14
src/Artemis.Core/Extensions/SKColorExtensions.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using RGB.NET.Core;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.Core.Extensions
|
||||||
|
{
|
||||||
|
// ReSharper disable once InconsistentNaming - I didn't come up with SKColor
|
||||||
|
public static class SKColorExtensions
|
||||||
|
{
|
||||||
|
public static Color ToRgbColor(this SKColor color)
|
||||||
|
{
|
||||||
|
return new Color(color.Alpha, color.Red, color.Green, color.Blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/Artemis.Core/RGB.NET/BitmapBrush.cs
Normal file
101
src/Artemis.Core/RGB.NET/BitmapBrush.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Artemis.Core.Extensions;
|
||||||
|
using RGB.NET.Core;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.Core.RGB.NET
|
||||||
|
{
|
||||||
|
public class BitmapBrush : AbstractDecoratable<IBrushDecorator>, IBrush, IDisposable
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public BitmapBrush(Scale scale)
|
||||||
|
{
|
||||||
|
Scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public BrushCalculationMode BrushCalculationMode { get; set; } = BrushCalculationMode.Absolute;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public double Brightness { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public double Opacity { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IList<IColorCorrection> ColorCorrections { get; } = new List<IColorCorrection>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Rectangle RenderedRectangle { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<BrushRenderTarget, Color> RenderedTargets { get; } = new Dictionary<BrushRenderTarget, Color>();
|
||||||
|
|
||||||
|
public Scale Scale { get; set; }
|
||||||
|
public SKBitmap Bitmap { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual void PerformRender(Rectangle rectangle, IEnumerable<BrushRenderTarget> renderTargets)
|
||||||
|
{
|
||||||
|
if (RenderedRectangle != rectangle || RenderedScale != Scale)
|
||||||
|
Bitmap = null;
|
||||||
|
|
||||||
|
RenderedRectangle = rectangle;
|
||||||
|
RenderedScale = Scale;
|
||||||
|
RenderedTargets.Clear();
|
||||||
|
|
||||||
|
if (Bitmap == null)
|
||||||
|
CreateBitmap(RenderedRectangle);
|
||||||
|
|
||||||
|
foreach (var renderTarget in renderTargets)
|
||||||
|
{
|
||||||
|
// TODO: Right now the sample size is 1, make this configurable to something higher and average the samples out
|
||||||
|
var scaledLocation = renderTarget.Point * Scale;
|
||||||
|
if (scaledLocation.X < Bitmap.Width && scaledLocation.Y < Bitmap.Height)
|
||||||
|
RenderedTargets[renderTarget] = Bitmap.GetPixel(RoundToInt(scaledLocation.X), RoundToInt(scaledLocation.Y)).ToRgbColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Scale RenderedScale { get; private set; }
|
||||||
|
|
||||||
|
private void CreateBitmap(Rectangle rectangle)
|
||||||
|
{
|
||||||
|
// TODO: Test this max size, it applied to System.Drawing.Bitmap but SKBitmap might scale better or worse
|
||||||
|
var width = Math.Min((rectangle.Location.X + rectangle.Size.Width) * Scale.Horizontal, 4096);
|
||||||
|
var height = Math.Min((rectangle.Location.Y + rectangle.Size.Height) * Scale.Vertical, 4096);
|
||||||
|
Bitmap = new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private int RoundToInt(double number)
|
||||||
|
{
|
||||||
|
return (int) Math.Round(number, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual void PerformFinalize()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Bitmap?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,7 +22,7 @@ namespace Artemis.Core.RGB.NET
|
|||||||
var height = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.Y + l.AbsoluteLedRectangle.Size.Height) * scale, 4096);
|
var height = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.Y + l.AbsoluteLedRectangle.Size.Height) * scale, 4096);
|
||||||
Bitmap = new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt()));
|
Bitmap = new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SKBitmap Bitmap { get; private set; }
|
public SKBitmap Bitmap { get; private set; }
|
||||||
|
|
||||||
public Color ManipulateColor(Rectangle rectangle, BrushRenderTarget renderTarget, Color color)
|
public Color ManipulateColor(Rectangle rectangle, BrushRenderTarget renderTarget, Color color)
|
||||||
|
|||||||
@ -89,14 +89,17 @@ namespace Artemis.Core.Services
|
|||||||
module.Update(args.DeltaTime);
|
module.Update(args.DeltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no ready graphics decorator, skip the frame
|
// If there is no ready bitmap brush, skip the frame
|
||||||
lock (_rgbService.GraphicsDecorator)
|
if (_rgbService.BitmapBrush == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (_rgbService.BitmapBrush)
|
||||||
{
|
{
|
||||||
if (_rgbService.GraphicsDecorator?.Bitmap == null)
|
if (_rgbService.BitmapBrush.Bitmap == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Render all active modules
|
// Render all active modules
|
||||||
using (var canvas = new SKCanvas(_rgbService.GraphicsDecorator.Bitmap))
|
using (var canvas = new SKCanvas(_rgbService.BitmapBrush.Bitmap))
|
||||||
{
|
{
|
||||||
canvas.Clear(new SKColor(0, 0, 0));
|
canvas.Clear(new SKColor(0, 0, 0));
|
||||||
lock (_modules)
|
lock (_modules)
|
||||||
@ -117,7 +120,7 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
private void SurfaceOnUpdated(UpdatedEventArgs args)
|
private void SurfaceOnUpdated(UpdatedEventArgs args)
|
||||||
{
|
{
|
||||||
OnFrameRendered(new FrameRenderedEventArgs(_rgbService.GraphicsDecorator, _rgbService.Surface));
|
OnFrameRendered(new FrameRenderedEventArgs(_rgbService.BitmapBrush, _rgbService.Surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
|
protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ namespace Artemis.Core.Services.Interfaces
|
|||||||
public interface IRgbService : IArtemisService
|
public interface IRgbService : IArtemisService
|
||||||
{
|
{
|
||||||
RGBSurface Surface { get; set; }
|
RGBSurface Surface { get; set; }
|
||||||
GraphicsDecorator GraphicsDecorator { get; }
|
BitmapBrush BitmapBrush { get; }
|
||||||
IReadOnlyCollection<IRGBDevice> LoadedDevices { get; }
|
IReadOnlyCollection<IRGBDevice> LoadedDevices { get; }
|
||||||
|
|
||||||
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
|
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
|
||||||
@ -25,6 +25,6 @@ namespace Artemis.Core.Services.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<DeviceEventArgs> DeviceReloaded;
|
event EventHandler<DeviceEventArgs> DeviceReloaded;
|
||||||
|
|
||||||
void UpdateGraphicsDecorator();
|
void UpdateSurfaceLedGroup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,7 +21,7 @@ namespace Artemis.Core.Services
|
|||||||
private readonly PluginSetting<double> _renderScaleSetting;
|
private readonly PluginSetting<double> _renderScaleSetting;
|
||||||
private readonly PluginSetting<int> _targetFrameRateSetting;
|
private readonly PluginSetting<int> _targetFrameRateSetting;
|
||||||
private readonly TimerUpdateTrigger _updateTrigger;
|
private readonly TimerUpdateTrigger _updateTrigger;
|
||||||
private ListLedGroup _background;
|
private ListLedGroup _surfaceLedGroup;
|
||||||
|
|
||||||
internal RgbService(ILogger logger, ISettingsService settingsService)
|
internal RgbService(ILogger logger, ISettingsService settingsService)
|
||||||
{
|
{
|
||||||
@ -30,7 +30,6 @@ namespace Artemis.Core.Services
|
|||||||
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25);
|
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25);
|
||||||
|
|
||||||
Surface = RGBSurface.Instance;
|
Surface = RGBSurface.Instance;
|
||||||
GraphicsDecorator = new GraphicsDecorator(new ListLedGroup(), 1);
|
|
||||||
|
|
||||||
// Let's throw these for now
|
// Let's throw these for now
|
||||||
Surface.Exception += SurfaceOnException;
|
Surface.Exception += SurfaceOnException;
|
||||||
@ -44,7 +43,7 @@ namespace Artemis.Core.Services
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RGBSurface Surface { get; set; }
|
public RGBSurface Surface { get; set; }
|
||||||
|
|
||||||
public GraphicsDecorator GraphicsDecorator { get; private set; }
|
public BitmapBrush BitmapBrush { get; private set; }
|
||||||
|
|
||||||
public IReadOnlyCollection<IRGBDevice> LoadedDevices => _loadedDevices.AsReadOnly();
|
public IReadOnlyCollection<IRGBDevice> LoadedDevices => _loadedDevices.AsReadOnly();
|
||||||
|
|
||||||
@ -57,7 +56,7 @@ namespace Artemis.Core.Services
|
|||||||
_logger.Warning("RgbDevice provider {deviceProvider} has no devices", deviceProvider.GetType().Name);
|
_logger.Warning("RgbDevice provider {deviceProvider} has no devices", deviceProvider.GetType().Name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var surfaceDevice in deviceProvider.Devices)
|
foreach (var surfaceDevice in deviceProvider.Devices)
|
||||||
{
|
{
|
||||||
if (!_loadedDevices.Contains(surfaceDevice))
|
if (!_loadedDevices.Contains(surfaceDevice))
|
||||||
@ -80,7 +79,7 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
private void RenderScaleSettingOnSettingChanged(object sender, EventArgs e)
|
private void RenderScaleSettingOnSettingChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
UpdateGraphicsDecorator();
|
UpdateSurfaceLedGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TargetFrameRateSettingOnSettingChanged(object sender, EventArgs e)
|
private void TargetFrameRateSettingOnSettingChanged(object sender, EventArgs e)
|
||||||
@ -99,23 +98,28 @@ namespace Artemis.Core.Services
|
|||||||
public event EventHandler<DeviceEventArgs> DeviceLoaded;
|
public event EventHandler<DeviceEventArgs> DeviceLoaded;
|
||||||
public event EventHandler<DeviceEventArgs> DeviceReloaded;
|
public event EventHandler<DeviceEventArgs> DeviceReloaded;
|
||||||
|
|
||||||
public void UpdateGraphicsDecorator()
|
public void UpdateSurfaceLedGroup()
|
||||||
{
|
{
|
||||||
lock (GraphicsDecorator)
|
if (_surfaceLedGroup == null)
|
||||||
{
|
{
|
||||||
// Clean up the old background if present
|
// Apply the application wide brush and decorator
|
||||||
if (_background != null)
|
BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value));
|
||||||
{
|
_surfaceLedGroup = new ListLedGroup(Surface.Leds) { Brush = BitmapBrush };
|
||||||
_background.Brush?.RemoveAllDecorators();
|
return;
|
||||||
_background.Detach();
|
}
|
||||||
}
|
|
||||||
|
lock (_surfaceLedGroup)
|
||||||
|
{
|
||||||
|
// Clean up the old background
|
||||||
|
_surfaceLedGroup.Detach();
|
||||||
|
|
||||||
// Apply the application wide brush and decorator
|
// Apply the application wide brush and decorator
|
||||||
_background = new ListLedGroup(Surface.Leds) {Brush = new SolidColorBrush(new Color(255, 255, 255, 255))};
|
BitmapBrush.Scale = new Scale(_renderScaleSetting.Value);
|
||||||
GraphicsDecorator = new GraphicsDecorator(_background, _renderScaleSetting.Value);
|
_surfaceLedGroup = new ListLedGroup(Surface.Leds) { Brush = BitmapBrush };
|
||||||
_background.Brush.RemoveAllDecorators();
|
}
|
||||||
|
lock (BitmapBrush)
|
||||||
_background.Brush.AddDecorator(GraphicsDecorator);
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ namespace Artemis.Core.Services.Storage
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the RGB service's graphics decorator to work with the new surface entity
|
// Update the RGB service's graphics decorator to work with the new surface entity
|
||||||
_rgbService.UpdateGraphicsDecorator();
|
_rgbService.UpdateSurfaceLedGroup();
|
||||||
OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(ActiveSurface));
|
OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(ActiveSurface));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ namespace Artemis.Core.Services.Storage
|
|||||||
}
|
}
|
||||||
|
|
||||||
_surfaceRepository.Save(surface.SurfaceEntity);
|
_surfaceRepository.Save(surface.SurfaceEntity);
|
||||||
_rgbService.UpdateGraphicsDecorator();
|
_rgbService.UpdateSurfaceLedGroup();
|
||||||
OnSurfaceConfigurationUpdated(new SurfaceConfigurationEventArgs(surface));
|
OnSurfaceConfigurationUpdated(new SurfaceConfigurationEventArgs(surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -268,6 +268,7 @@ namespace Artemis.Plugins.LayerElements.Noise
|
|||||||
return value * NORM_2D;
|
return value * NORM_2D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public double Evaluate(double x, double y, double z)
|
public double Evaluate(double x, double y, double z)
|
||||||
{
|
{
|
||||||
var stretchOffset = (x + y + z) * STRETCH_3D;
|
var stretchOffset = (x + y + z) * STRETCH_3D;
|
||||||
|
|||||||
@ -22,6 +22,9 @@
|
|||||||
<QuadraticEase EasingMode="EaseInOut" />
|
<QuadraticEase EasingMode="EaseInOut" />
|
||||||
</DoubleAnimation.EasingFunction>
|
</DoubleAnimation.EasingFunction>
|
||||||
</DoubleAnimation>
|
</DoubleAnimation>
|
||||||
|
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0.5" Storyboard.TargetProperty="Visibility">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}"/>
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
</Storyboard>
|
</Storyboard>
|
||||||
</BeginStoryboard>
|
</BeginStoryboard>
|
||||||
</DataTrigger.EnterActions>
|
</DataTrigger.EnterActions>
|
||||||
|
|||||||
@ -40,26 +40,33 @@ namespace Artemis.UI.Screens.Settings.Debug
|
|||||||
{
|
{
|
||||||
Execute.PostToUIThread(() =>
|
Execute.PostToUIThread(() =>
|
||||||
{
|
{
|
||||||
if (e.GraphicsDecorator.Bitmap == null)
|
if (e.BitmapBrush.Bitmap == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!(CurrentFrame is WriteableBitmap writeableBitmap))
|
if (!(CurrentFrame is WriteableBitmap writeableBitmap))
|
||||||
{
|
{
|
||||||
CurrentFrame = e.GraphicsDecorator.Bitmap.ToWriteableBitmap();
|
CurrentFrame = e.BitmapBrush.Bitmap.ToWriteableBitmap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var skiaImage = SKImage.FromPixels(e.GraphicsDecorator.Bitmap.PeekPixels()))
|
try
|
||||||
{
|
{
|
||||||
var info = new SKImageInfo(skiaImage.Width, skiaImage.Height);
|
using (var skiaImage = SKImage.FromPixels(e.BitmapBrush.Bitmap.PeekPixels()))
|
||||||
writeableBitmap.Lock();
|
|
||||||
using (var pixmap = new SKPixmap(info, writeableBitmap.BackBuffer, writeableBitmap.BackBufferStride))
|
|
||||||
{
|
{
|
||||||
skiaImage.ReadPixels(pixmap, 0, 0);
|
var info = new SKImageInfo(skiaImage.Width, skiaImage.Height);
|
||||||
}
|
writeableBitmap.Lock();
|
||||||
|
using (var pixmap = new SKPixmap(info, writeableBitmap.BackBuffer, writeableBitmap.BackBufferStride))
|
||||||
|
{
|
||||||
|
skiaImage.ReadPixels(pixmap, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight));
|
writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight));
|
||||||
writeableBitmap.Unlock();
|
writeableBitmap.Unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (AccessViolationException)
|
||||||
|
{
|
||||||
|
// oops
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,10 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:xaml="https://github.com/canton7/Stylet"
|
xmlns:xaml="https://github.com/canton7/Stylet"
|
||||||
|
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DataContext="{d:DesignInstance screens:SettingsViewModel}"
|
d:DataContext="{d:DesignInstance settings:SettingsViewModel}"
|
||||||
d:DesignHeight="600" d:DesignWidth="600">
|
d:DesignHeight="600" d:DesignWidth="600">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
@ -16,71 +18,206 @@
|
|||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<ScrollViewer>
|
<TabControl Style="{StaticResource MaterialDesignTabControl}">
|
||||||
<StackPanel Margin="16">
|
<TabItem Header="GENERAL" TextElement.Foreground="{DynamicResource MaterialDesignBody}">
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}">General</TextBlock>
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
<StackPanel>
|
<StackPanel Margin="15" MaxWidth="800">
|
||||||
<TextBlock>General settings like start up with Windows etc.</TextBlock>
|
<!-- General settings -->
|
||||||
<Grid>
|
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}" Margin="0 15">General</TextBlock>
|
||||||
<Grid.RowDefinitions>
|
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||||
<RowDefinition />
|
<StackPanel Margin="15">
|
||||||
<RowDefinition />
|
<Grid>
|
||||||
</Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<RowDefinition />
|
||||||
<ColumnDefinition />
|
<RowDefinition />
|
||||||
<ColumnDefinition />
|
</Grid.RowDefinitions>
|
||||||
</Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Start up with Windows</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||||
|
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" ToolTip="Default ToggleButton Style" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
|
||||||
|
|
||||||
<TextBlock VerticalAlignment="Bottom" Grid.Row="0" Grid.Column="0">Render scale</TextBlock>
|
<Grid>
|
||||||
<Slider Minimum="0.10"
|
<Grid.RowDefinitions>
|
||||||
Maximum="1"
|
<RowDefinition />
|
||||||
TickFrequency="0.05"
|
<RowDefinition />
|
||||||
Grid.Row="1"
|
</Grid.RowDefinitions>
|
||||||
Grid.Column="0"
|
<Grid.ColumnDefinitions>
|
||||||
Style="{StaticResource MaterialDesignDiscreteSlider}"
|
<ColumnDefinition Width="*" />
|
||||||
ToolTip="MaterialDesignDiscreteSlider"
|
<ColumnDefinition Width="Auto" />
|
||||||
Value="{Binding RenderScale}" />
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Start up with Windows minimized</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||||
|
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" ToolTip="Default ToggleButton Style" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
|
||||||
|
|
||||||
<TextBlock VerticalAlignment="Bottom" Grid.Row="0" Grid.Column="1">Target framerate</TextBlock>
|
<Grid>
|
||||||
<Slider Minimum="5"
|
<Grid.RowDefinitions>
|
||||||
Maximum="60"
|
<RowDefinition />
|
||||||
Grid.Row="1"
|
<RowDefinition />
|
||||||
Grid.Column="1"
|
</Grid.RowDefinitions>
|
||||||
Style="{StaticResource MaterialDesignDiscreteSlider}"
|
<Grid.ColumnDefinitions>
|
||||||
ToolTip="MaterialDesignDiscreteSlider"
|
<ColumnDefinition Width="*" />
|
||||||
Value="{Binding TargetFrameRate}" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid>
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0" VerticalAlignment="Center">
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Debugger</TextBlock>
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
|
||||||
|
Use the debugger to see the raw image Artemis is rendering on the surface.
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||||
|
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowDebugger}">
|
||||||
|
SHOW DEBUGGER
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</materialDesign:Card>
|
||||||
|
|
||||||
<Button Command="{s:Action ShowDebugger}" Style="{StaticResource MaterialDesignRaisedButton}"
|
<!-- Rendering settings -->
|
||||||
HorizontalAlignment="Left" Margin="0, 10, 0, 0">
|
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}" Margin="0 15">Rendering</TextBlock>
|
||||||
Show debugger
|
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||||
</Button>
|
<StackPanel Margin="15">
|
||||||
</StackPanel>
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Render scale</TextBlock>
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
|
||||||
|
Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces.
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||||
|
<ComboBox Width="80" SelectedItem="{Binding SelectedRenderScale}" ItemsSource="{Binding RenderScales}" DisplayMemberPath="Item1"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}">Devices</TextBlock>
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
<TextBlock>A list of devices and options to disable them</TextBlock>
|
<RowDefinition />
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0 12 0 0">
|
<RowDefinition />
|
||||||
<ItemsControl ItemsSource="{Binding DeviceSettingsViewModels}">
|
</Grid.RowDefinitions>
|
||||||
<ItemsControl.ItemsPanel>
|
<Grid.ColumnDefinitions>
|
||||||
<ItemsPanelTemplate>
|
<ColumnDefinition Width="*" />
|
||||||
<WrapPanel />
|
<ColumnDefinition Width="Auto" />
|
||||||
</ItemsPanelTemplate>
|
</Grid.ColumnDefinitions>
|
||||||
</ItemsControl.ItemsPanel>
|
<StackPanel Grid.Column="0">
|
||||||
<ItemsControl.ItemTemplate>
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Target framerate</TextBlock>
|
||||||
<DataTemplate>
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
|
||||||
<ContentControl xaml:View.Model="{Binding}" Margin="5" HorizontalAlignment="Left"
|
Sets the FPS Artemis tries to render at, higher FPS means more CPU-usage but smoother animations.
|
||||||
VerticalAlignment="Top" />
|
</TextBlock>
|
||||||
</DataTemplate>
|
</StackPanel>
|
||||||
</ItemsControl.ItemTemplate>
|
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||||
</ItemsControl>
|
<ComboBox Width="80" SelectedItem="{Binding SelectedTargetFrameRate}" ItemsSource="{Binding TargetFrameRates}" DisplayMemberPath="Item1"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</materialDesign:Card>
|
||||||
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
</TabItem>
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}">Plugins</TextBlock>
|
<TabItem Header="PLUGINS" TextElement.Foreground="{DynamicResource MaterialDesignBody}">
|
||||||
<Grid>
|
<DockPanel Margin="15">
|
||||||
<TextBlock>A list of plugins and options to disable them</TextBlock>
|
<TextBlock DockPanel.Dock="Top">Below you view and manage your plugins. To find and install new plugins use the workshop (TODO).</TextBlock>
|
||||||
</Grid>
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
</StackPanel>
|
<DataGrid Margin="0 8 0 0"
|
||||||
</ScrollViewer>
|
ItemsSource="{Binding Items3}"
|
||||||
|
CanUserSortColumns="True"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
materialDesign:DataGridAssist.CellPadding="13 8 8 8"
|
||||||
|
materialDesign:DataGridAssist.ColumnHeaderPadding="8">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridCheckBoxColumn Binding="{Binding IsSelected}"
|
||||||
|
ElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnStyle}"
|
||||||
|
EditingElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnEditingStyle}">
|
||||||
|
<DataGridCheckBoxColumn.Header>
|
||||||
|
<!--padding to allow hit test to pass thru for sorting -->
|
||||||
|
<Border Background="Transparent" Padding="6 0 6 0" HorizontalAlignment="Center">
|
||||||
|
<CheckBox HorizontalAlignment="Center"
|
||||||
|
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext}"
|
||||||
|
IsChecked="{Binding IsAllItems3Selected}" />
|
||||||
|
</Border>
|
||||||
|
</DataGridCheckBoxColumn.Header>
|
||||||
|
</DataGridCheckBoxColumn>
|
||||||
|
<DataGridTextColumn Binding="{Binding Code}" Header="Code" EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnEditingStyle}" />
|
||||||
|
<!-- if you want to use the pop up style (MaterialDesignDataGridTextColumnPopupEditingStyle), you must use MaterialDataGridTextColumn -->
|
||||||
|
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Name}"
|
||||||
|
Header="Name"
|
||||||
|
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}" />
|
||||||
|
<!-- set a max length to get an indicator in the editor -->
|
||||||
|
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Description}"
|
||||||
|
Header="Description"
|
||||||
|
MaxLength="255"
|
||||||
|
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}" />
|
||||||
|
<materialDesign:MaterialDataGridTextColumn Binding="{Binding Numeric}"
|
||||||
|
Header="Number with long header"
|
||||||
|
Width="120"
|
||||||
|
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}">
|
||||||
|
<DataGridTextColumn.HeaderStyle>
|
||||||
|
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource MaterialDesignDataGridColumnHeader}">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
|
<Setter Property="ContentTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock TextWrapping="Wrap" Text="{Binding}" TextAlignment="Right" />
|
||||||
|
</DataTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.HeaderStyle>
|
||||||
|
<DataGridTextColumn.ElementStyle>
|
||||||
|
<Style TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.ElementStyle>
|
||||||
|
</materialDesign:MaterialDataGridTextColumn>
|
||||||
|
<!-- use custom combo box column to get better combos. Use ItemsSourceBinding as your binding template to be applied to each combo -->
|
||||||
|
<materialDesign:MaterialDataGridComboBoxColumn Header="Food"
|
||||||
|
SelectedValueBinding="{Binding Food}"
|
||||||
|
ItemsSourceBinding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.Foods}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="DEVICES" TextElement.Foreground="{DynamicResource MaterialDesignBody}">
|
||||||
|
<DockPanel Margin="15">
|
||||||
|
<TextBlock DockPanel.Dock="Top">Below you view and manage the devices that were detected by Artemis</TextBlock>
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0 12 0 0">
|
||||||
|
<ItemsControl ItemsSource="{Binding DeviceSettingsViewModels}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ContentControl xaml:View.Model="{Binding}" Margin="5" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,4 +1,7 @@
|
|||||||
using Artemis.Core.Services;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core.Services;
|
||||||
using Artemis.Core.Services.Storage.Interfaces;
|
using Artemis.Core.Services.Storage.Interfaces;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.Settings.Debug;
|
using Artemis.UI.Screens.Settings.Debug;
|
||||||
@ -34,10 +37,32 @@ namespace Artemis.UI.Screens.Settings
|
|||||||
_deviceSettingsViewModelFactory = deviceSettingsViewModelFactory;
|
_deviceSettingsViewModelFactory = deviceSettingsViewModelFactory;
|
||||||
|
|
||||||
DeviceSettingsViewModels = new BindableCollection<DeviceSettingsViewModel>();
|
DeviceSettingsViewModels = new BindableCollection<DeviceSettingsViewModel>();
|
||||||
|
|
||||||
|
RenderScales = new List<Tuple<string, double>> {new Tuple<string, double>("10%", 0.1)};
|
||||||
|
for (var i = 25; i <= 100; i += 25)
|
||||||
|
RenderScales.Add(new Tuple<string, double>(i + "%", i / 100.0));
|
||||||
|
|
||||||
|
TargetFrameRates = new List<Tuple<string, int>>();
|
||||||
|
for (var i = 10; i <= 30; i += 5)
|
||||||
|
TargetFrameRates.Add(new Tuple<string, int>(i + " FPS", i));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BindableCollection<DeviceSettingsViewModel> DeviceSettingsViewModels { get; set; }
|
public BindableCollection<DeviceSettingsViewModel> DeviceSettingsViewModels { get; set; }
|
||||||
|
|
||||||
|
public List<Tuple<string, double>> RenderScales { get; set; }
|
||||||
|
|
||||||
|
public Tuple<string, double> SelectedRenderScale
|
||||||
|
{
|
||||||
|
get => RenderScales.FirstOrDefault(s => Math.Abs(s.Item2 - RenderScale) < 0.01);
|
||||||
|
set => RenderScale = value.Item2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tuple<string, int> SelectedTargetFrameRate
|
||||||
|
{
|
||||||
|
get => TargetFrameRates.FirstOrDefault(t => Math.Abs(t.Item2 - TargetFrameRate) < 0.01);
|
||||||
|
set => TargetFrameRate = value.Item2;
|
||||||
|
}
|
||||||
|
|
||||||
public double RenderScale
|
public double RenderScale
|
||||||
{
|
{
|
||||||
get => _settingsService.GetSetting("Core.RenderScale", 1.0).Value;
|
get => _settingsService.GetSetting("Core.RenderScale", 1.0).Value;
|
||||||
@ -48,6 +73,8 @@ namespace Artemis.UI.Screens.Settings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Tuple<string, int>> TargetFrameRates { get; set; }
|
||||||
|
|
||||||
public int TargetFrameRate
|
public int TargetFrameRate
|
||||||
{
|
{
|
||||||
get => _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
get => _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Image Source="{StaticResource BowIcon}" Stretch="Uniform" Margin="6,50,6,6" />
|
<Image Source="{StaticResource BowIcon}" Stretch="Uniform" Margin="50" />
|
||||||
<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Foreground="White" FontSize="16">
|
<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Foreground="White" FontSize="16">
|
||||||
Artemis is initializing...
|
Artemis is initializing...
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user