Browse Source

Merge branch 'dev' of https://github.com/ionite34/StabilityMatrix into image-to-video

pull/438/head
JT 11 months ago
parent
commit
2f6deecf51
  1. 2
      .config/dotnet-tools.json
  2. 4
      .editorconfig
  3. 4
      .github/workflows/release.yml
  4. 16
      Avalonia.Gif/Avalonia.Gif.csproj
  5. 10
      Avalonia.Gif/BgWorkerCommand.cs
  6. 12
      Avalonia.Gif/BgWorkerState.cs
  7. 10
      Avalonia.Gif/Decoding/BlockTypes.cs
  8. 8
      Avalonia.Gif/Decoding/ExtensionType.cs
  9. 10
      Avalonia.Gif/Decoding/FrameDisposal.cs
  10. 36
      Avalonia.Gif/Decoding/GifColor.cs
  11. 653
      Avalonia.Gif/Decoding/GifDecoder.cs
  12. 20
      Avalonia.Gif/Decoding/GifFrame.cs
  13. 19
      Avalonia.Gif/Decoding/GifHeader.cs
  14. 43
      Avalonia.Gif/Decoding/GifRect.cs
  15. 8
      Avalonia.Gif/Decoding/GifRepeatBehavior.cs
  16. 23
      Avalonia.Gif/Decoding/InvalidGifStreamException.cs
  17. 23
      Avalonia.Gif/Decoding/LzwDecompressionException.cs
  18. 81
      Avalonia.Gif/Extensions/StreamExtensions.cs
  19. 296
      Avalonia.Gif/GifImage.cs
  20. 147
      Avalonia.Gif/GifInstance.cs
  21. 20
      Avalonia.Gif/InvalidGifStreamException.cs
  22. 145
      CHANGELOG.md
  23. 2
      README.md
  24. 1
      StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj
  25. 34
      StabilityMatrix.Avalonia/App.axaml
  26. 239
      StabilityMatrix.Avalonia/App.axaml.cs
  27. 49
      StabilityMatrix.Avalonia/Assets.cs
  28. 544
      StabilityMatrix.Avalonia/Assets/hf-packages.json
  29. BIN
      StabilityMatrix.Avalonia/Assets/santahat.png
  30. 34
      StabilityMatrix.Avalonia/Assets/sitecustomize.py
  31. 304
      StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml.cs
  32. 60
      StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml
  33. 61
      StabilityMatrix.Avalonia/Controls/BetterAdvancedImage.cs
  34. 9
      StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml
  35. 0
      StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml.cs
  36. 127
      StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml
  37. 0
      StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml.cs
  38. 0
      StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml
  39. 0
      StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml.cs
  40. 0
      StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml
  41. 0
      StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml.cs
  42. 0
      StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml
  43. 0
      StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml.cs
  44. 0
      StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml
  45. 0
      StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml.cs
  46. 0
      StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml
  47. 0
      StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml.cs
  48. 0
      StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml
  49. 0
      StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml.cs
  50. 0
      StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml
  51. 0
      StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml.cs
  52. 177
      StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml
  53. 39
      StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml.cs
  54. 0
      StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml
  55. 0
      StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml.cs
  56. 2
      StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml
  57. 8
      StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml.cs
  58. 20
      StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml
  59. 0
      StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml.cs
  60. 113
      StabilityMatrix.Avalonia/Controls/Inference/StackExpander.axaml
  61. 41
      StabilityMatrix.Avalonia/Controls/Inference/StackExpander.axaml.cs
  62. 0
      StabilityMatrix.Avalonia/Controls/Inference/UpscalerCard.axaml
  63. 0
      StabilityMatrix.Avalonia/Controls/Inference/UpscalerCard.axaml.cs
  64. 37
      StabilityMatrix.Avalonia/Controls/LineDashFrame.cs
  65. 127
      StabilityMatrix.Avalonia/Controls/ProgressRing.cs
  66. 124
      StabilityMatrix.Avalonia/Controls/PropertyGrid/BetterPropertyGrid.cs
  67. 35
      StabilityMatrix.Avalonia/Controls/PropertyGrid/PropertyGridCultureData.cs
  68. 36
      StabilityMatrix.Avalonia/Controls/PropertyGrid/PropertyGridLocalizationService.cs
  69. 60
      StabilityMatrix.Avalonia/Controls/PropertyGrid/ToggleSwitchCellEditFactory.cs
  70. 104
      StabilityMatrix.Avalonia/Controls/SelectImageCard.axaml
  71. 6
      StabilityMatrix.Avalonia/Controls/SelectImageCard.axaml.cs
  72. 96
      StabilityMatrix.Avalonia/Controls/StackExpander.axaml
  73. 20
      StabilityMatrix.Avalonia/Controls/StackExpander.axaml.cs
  74. 22
      StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs
  75. 31
      StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs
  76. 41
      StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs
  77. 84
      StabilityMatrix.Avalonia/Converters/NullableDefaultNumericConverter.cs
  78. 6
      StabilityMatrix.Avalonia/Converters/NullableDefaultNumericConverters.cs
  79. 8
      StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs
  80. 155
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  81. 30
      StabilityMatrix.Avalonia/DesignData/MockMetadataImportService.cs
  82. 54
      StabilityMatrix.Avalonia/DesignData/MockPropertyGridObject.cs
  83. 60
      StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs
  84. 19
      StabilityMatrix.Avalonia/Extensions/DataObjectExtensions.cs
  85. 2
      StabilityMatrix.Avalonia/Helpers/TextEditorConfigs.cs
  86. 20
      StabilityMatrix.Avalonia/Helpers/UriHandler.cs
  87. 11
      StabilityMatrix.Avalonia/Helpers/ViewModelSerializer.cs
  88. 21
      StabilityMatrix.Avalonia/Languages/Cultures.cs
  89. 218
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  90. 3
      StabilityMatrix.Avalonia/Languages/Resources.es.resx
  91. 3
      StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx
  92. 3
      StabilityMatrix.Avalonia/Languages/Resources.it-it.resx
  93. 3
      StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx
  94. 74
      StabilityMatrix.Avalonia/Languages/Resources.resx
  95. 3
      StabilityMatrix.Avalonia/Languages/Resources.ru-ru.resx
  96. 783
      StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx
  97. 3
      StabilityMatrix.Avalonia/Languages/Resources.zh-Hans.resx
  98. 3
      StabilityMatrix.Avalonia/Languages/Resources.zh-Hant.resx
  99. 113
      StabilityMatrix.Avalonia/MarkupExtensions/ShowDisabledTooltipExtension.cs
  100. 42
      StabilityMatrix.Avalonia/MarkupExtensions/TernaryExtension.cs
  101. Some files were not shown because too many files have changed in this diff Show More

2
.config/dotnet-tools.json

@ -15,7 +15,7 @@
] ]
}, },
"csharpier": { "csharpier": {
"version": "0.25.0", "version": "0.26.4",
"commands": [ "commands": [
"dotnet-csharpier" "dotnet-csharpier"
] ]

4
.editorconfig

@ -7,3 +7,7 @@ dotnet_sort_system_directives_first = true
# ReSharper properties # ReSharper properties
resharper_csharp_max_line_length = 120 resharper_csharp_max_line_length = 120
# dotnet code quality
# noinspection EditorConfigKeyCorrectness
dotnet_code_quality.CA1826.exclude_ordefault_methods = true

4
.github/workflows/release.yml

@ -209,8 +209,6 @@ jobs:
files: | files: |
StabilityMatrix-win-x64.zip StabilityMatrix-win-x64.zip
StabilityMatrix-linux-x64.zip StabilityMatrix-linux-x64.zip
StabilityMatrix-win-x64/StabilityMatrix.exe
StabilityMatrix-linux-x64/StabilityMatrix.AppImage
fail_on_unmatched_files: true fail_on_unmatched_files: true
tag_name: v${{ github.event.inputs.version }} tag_name: v${{ github.event.inputs.version }}
body: ${{ steps.release_notes.outputs.release_notes }} body: ${{ steps.release_notes.outputs.release_notes }}
@ -244,7 +242,7 @@ jobs:
SM_CF_CACHE_PURGE_TOKEN: ${{ secrets.SM_CF_CACHE_PURGE_TOKEN }} SM_CF_CACHE_PURGE_TOKEN: ${{ secrets.SM_CF_CACHE_PURGE_TOKEN }}
SM_CF_ZONE_ID: ${{ secrets.SM_CF_ZONE_ID }} SM_CF_ZONE_ID: ${{ secrets.SM_CF_ZONE_ID }}
SM_SIGNING_PRIVATE_KEY: ${{ secrets.SM_SIGNING_PRIVATE_KEY }} SM_SIGNING_PRIVATE_KEY: ${{ secrets.SM_SIGNING_PRIVATE_KEY }}
run: sm-tools updates publish-matrix-v3 -v $RELEASE_VERSION -y run: sm-tools updates publish-matrix-v3 -v ${{ github.event.inputs.version }} -y
publish-auto-update-b2: publish-auto-update-b2:
name: Publish Auto-Update Release (B2) name: Publish Auto-Update Release (B2)

16
Avalonia.Gif/Avalonia.Gif.csproj

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64;osx-arm64</RuntimeIdentifiers>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.5" />
</ItemGroup>
</Project>

10
Avalonia.Gif/BgWorkerCommand.cs

@ -0,0 +1,10 @@
namespace Avalonia.Gif
{
internal enum BgWorkerCommand
{
Null,
Play,
Pause,
Dispose
}
}

12
Avalonia.Gif/BgWorkerState.cs

@ -0,0 +1,12 @@
namespace Avalonia.Gif
{
internal enum BgWorkerState
{
Null,
Start,
Running,
Paused,
Complete,
Dispose
}
}

10
Avalonia.Gif/Decoding/BlockTypes.cs

@ -0,0 +1,10 @@
namespace Avalonia.Gif.Decoding
{
internal enum BlockTypes
{
Empty = 0,
Extension = 0x21,
ImageDescriptor = 0x2C,
Trailer = 0x3B,
}
}

8
Avalonia.Gif/Decoding/ExtensionType.cs

@ -0,0 +1,8 @@
namespace Avalonia.Gif.Decoding
{
internal enum ExtensionType
{
GraphicsControl = 0xF9,
Application = 0xFF
}
}

10
Avalonia.Gif/Decoding/FrameDisposal.cs

@ -0,0 +1,10 @@
namespace Avalonia.Gif.Decoding
{
public enum FrameDisposal
{
Unknown = 0,
Leave = 1,
Background = 2,
Restore = 3
}
}

36
Avalonia.Gif/Decoding/GifColor.cs

@ -0,0 +1,36 @@
using System.Runtime.InteropServices;
namespace Avalonia.Gif
{
[StructLayout(LayoutKind.Explicit)]
public readonly struct GifColor
{
[FieldOffset(3)]
public readonly byte A;
[FieldOffset(2)]
public readonly byte R;
[FieldOffset(1)]
public readonly byte G;
[FieldOffset(0)]
public readonly byte B;
/// <summary>
/// A struct that represents a ARGB color and is aligned as
/// a BGRA bytefield in memory.
/// </summary>
/// <param name="r">Red</param>
/// <param name="g">Green</param>
/// <param name="b">Blue</param>
/// <param name="a">Alpha</param>
public GifColor(byte r, byte g, byte b, byte a = byte.MaxValue)
{
A = a;
R = r;
G = g;
B = b;
}
}
}

653
Avalonia.Gif/Decoding/GifDecoder.cs

@ -0,0 +1,653 @@
// This source file's Lempel-Ziv-Welch algorithm is derived from Chromium's Android GifPlayer
// as seen here (https://github.com/chromium/chromium/blob/master/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer)
// Licensed under the Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
// Copyright (C) 2015 The Gifplayer Authors. All Rights Reserved.
// The rest of the source file is licensed under MIT License.
// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Avalonia;
using Avalonia.Media.Imaging;
using static Avalonia.Gif.Extensions.StreamExtensions;
namespace Avalonia.Gif.Decoding
{
public sealed class GifDecoder : IDisposable
{
private static readonly ReadOnlyMemory<byte> G87AMagic = "GIF87a"u8.ToArray().AsMemory();
private static readonly ReadOnlyMemory<byte> G89AMagic = "GIF89a"u8.ToArray().AsMemory();
private static readonly ReadOnlyMemory<byte> NetscapeMagic = "NETSCAPE2.0"u8.ToArray().AsMemory();
private static readonly TimeSpan FrameDelayThreshold = TimeSpan.FromMilliseconds(10);
private static readonly TimeSpan FrameDelayDefault = TimeSpan.FromMilliseconds(100);
private static readonly GifColor TransparentColor = new(0, 0, 0, 0);
private static readonly int MaxTempBuf = 768;
private static readonly int MaxStackSize = 4096;
private static readonly int MaxBits = 4097;
private readonly Stream _fileStream;
private readonly CancellationToken _currentCtsToken;
private readonly bool _hasFrameBackups;
private int _gctSize,
_bgIndex,
_prevFrame = -1,
_backupFrame = -1;
private bool _gctUsed;
private GifRect _gifDimensions;
// private ulong _globalColorTable;
private readonly int _backBufferBytes;
private GifColor[] _bitmapBackBuffer;
private short[] _prefixBuf;
private byte[] _suffixBuf;
private byte[] _pixelStack;
private byte[] _indexBuf;
private byte[] _backupFrameIndexBuf;
private volatile bool _hasNewFrame;
public GifHeader Header { get; private set; }
public readonly List<GifFrame> Frames = new();
public PixelSize Size => new PixelSize(Header.Dimensions.Width, Header.Dimensions.Height);
public GifDecoder(Stream fileStream, CancellationToken currentCtsToken)
{
_fileStream = fileStream;
_currentCtsToken = currentCtsToken;
ProcessHeaderData();
ProcessFrameData();
Header.IterationCount = Header.Iterations switch
{
-1 => new GifRepeatBehavior { Count = 1 },
0 => new GifRepeatBehavior { LoopForever = true },
> 0 => new GifRepeatBehavior { Count = Header.Iterations },
_ => Header.IterationCount
};
var pixelCount = _gifDimensions.TotalPixels;
_hasFrameBackups = Frames.Any(f => f.FrameDisposalMethod == FrameDisposal.Restore);
_bitmapBackBuffer = new GifColor[pixelCount];
_indexBuf = new byte[pixelCount];
if (_hasFrameBackups)
_backupFrameIndexBuf = new byte[pixelCount];
_prefixBuf = new short[MaxStackSize];
_suffixBuf = new byte[MaxStackSize];
_pixelStack = new byte[MaxStackSize + 1];
_backBufferBytes = pixelCount * Marshal.SizeOf(typeof(GifColor));
}
public void Dispose()
{
Frames.Clear();
_bitmapBackBuffer = null;
_prefixBuf = null;
_suffixBuf = null;
_pixelStack = null;
_indexBuf = null;
_backupFrameIndexBuf = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int PixCoord(int x, int y) => x + y * _gifDimensions.Width;
static readonly (int Start, int Step)[] Pass = { (0, 8), (4, 8), (2, 4), (1, 2) };
private void ClearImage()
{
Array.Fill(_bitmapBackBuffer, TransparentColor);
//ClearArea(_gifDimensions);
_prevFrame = -1;
_backupFrame = -1;
}
public void RenderFrame(int fIndex, WriteableBitmap writeableBitmap, bool forceClear = false)
{
if (_currentCtsToken.IsCancellationRequested)
return;
if (fIndex < 0 | fIndex >= Frames.Count)
return;
if (_prevFrame == fIndex)
return;
if (fIndex == 0 || forceClear || fIndex < _prevFrame)
ClearImage();
DisposePreviousFrame();
_prevFrame++;
// render intermediate frame
for (int idx = _prevFrame; idx < fIndex; ++idx)
{
var prevFrame = Frames[idx];
if (prevFrame.FrameDisposalMethod == FrameDisposal.Restore)
continue;
if (prevFrame.FrameDisposalMethod == FrameDisposal.Background)
{
ClearArea(prevFrame.Dimensions);
continue;
}
RenderFrameAt(idx, writeableBitmap);
}
RenderFrameAt(fIndex, writeableBitmap);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RenderFrameAt(int idx, WriteableBitmap writeableBitmap)
{
var tmpB = ArrayPool<byte>.Shared.Rent(MaxTempBuf);
var curFrame = Frames[idx];
DecompressFrameToIndexBuffer(curFrame, _indexBuf, tmpB);
if (_hasFrameBackups & curFrame.ShouldBackup)
{
Buffer.BlockCopy(_indexBuf, 0, _backupFrameIndexBuf, 0, curFrame.Dimensions.TotalPixels);
_backupFrame = idx;
}
DrawFrame(curFrame, _indexBuf);
_prevFrame = idx;
_hasNewFrame = true;
using var lockedBitmap = writeableBitmap.Lock();
WriteBackBufToFb(lockedBitmap.Address);
ArrayPool<byte>.Shared.Return(tmpB);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DrawFrame(GifFrame curFrame, Memory<byte> frameIndexSpan)
{
var activeColorTable = curFrame.IsLocalColorTableUsed ? curFrame.LocalColorTable : Header.GlobarColorTable;
var cX = curFrame.Dimensions.X;
var cY = curFrame.Dimensions.Y;
var cH = curFrame.Dimensions.Height;
var cW = curFrame.Dimensions.Width;
var tC = curFrame.TransparentColorIndex;
var hT = curFrame.HasTransparency;
if (curFrame.IsInterlaced)
{
for (var i = 0; i < 4; i++)
{
var curPass = Pass[i];
var y = curPass.Start;
while (y < cH)
{
DrawRow(y);
y += curPass.Step;
}
}
}
else
{
for (var i = 0; i < cH; i++)
DrawRow(i);
}
//for (var row = 0; row < cH; row++)
void DrawRow(int row)
{
// Get the starting point of the current row on frame's index stream.
var indexOffset = row * cW;
// Get the target backbuffer offset from the frames coords.
var targetOffset = PixCoord(cX, row + cY);
var len = _bitmapBackBuffer.Length;
for (var i = 0; i < cW; i++)
{
var indexColor = frameIndexSpan.Span[indexOffset + i];
if (activeColorTable == null || targetOffset >= len || indexColor > activeColorTable.Length)
return;
if (!(hT & indexColor == tC))
_bitmapBackBuffer[targetOffset] = activeColorTable[indexColor];
targetOffset++;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DisposePreviousFrame()
{
if (_prevFrame == -1)
return;
var prevFrame = Frames[_prevFrame];
switch (prevFrame.FrameDisposalMethod)
{
case FrameDisposal.Background:
ClearArea(prevFrame.Dimensions);
break;
case FrameDisposal.Restore:
if (_hasFrameBackups && _backupFrame != -1)
DrawFrame(Frames[_backupFrame], _backupFrameIndexBuf);
else
ClearArea(prevFrame.Dimensions);
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ClearArea(GifRect area)
{
for (var y = 0; y < area.Height; y++)
{
var targetOffset = PixCoord(area.X, y + area.Y);
for (var x = 0; x < area.Width; x++)
_bitmapBackBuffer[targetOffset + x] = TransparentColor;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecompressFrameToIndexBuffer(GifFrame curFrame, Span<byte> indexSpan, byte[] tempBuf)
{
_fileStream.Position = curFrame.LzwStreamPosition;
var totalPixels = curFrame.Dimensions.TotalPixels;
// Initialize GIF data stream decoder.
var dataSize = curFrame.LzwMinCodeSize;
var clear = 1 << dataSize;
var endOfInformation = clear + 1;
var available = clear + 2;
var oldCode = -1;
var codeSize = dataSize + 1;
var codeMask = (1 << codeSize) - 1;
for (var code = 0; code < clear; code++)
{
_prefixBuf[code] = 0;
_suffixBuf[code] = (byte)code;
}
// Decode GIF pixel stream.
int bits,
first,
top,
pixelIndex;
var datum = bits = first = top = pixelIndex = 0;
while (pixelIndex < totalPixels)
{
var blockSize = _fileStream.ReadBlock(tempBuf);
if (blockSize == 0)
break;
var blockPos = 0;
while (blockPos < blockSize)
{
datum += tempBuf[blockPos] << bits;
blockPos++;
bits += 8;
while (bits >= codeSize)
{
// Get the next code.
var code = datum & codeMask;
datum >>= codeSize;
bits -= codeSize;
// Interpret the code
if (code == clear)
{
// Reset decoder.
codeSize = dataSize + 1;
codeMask = (1 << codeSize) - 1;
available = clear + 2;
oldCode = -1;
continue;
}
// Check for explicit end-of-stream
if (code == endOfInformation)
return;
if (oldCode == -1)
{
indexSpan[pixelIndex++] = _suffixBuf[code];
oldCode = code;
first = code;
continue;
}
var inCode = code;
if (code >= available)
{
_pixelStack[top++] = (byte)first;
code = oldCode;
if (top == MaxBits)
ThrowException();
}
while (code >= clear)
{
if (code >= MaxBits || code == _prefixBuf[code])
ThrowException();
_pixelStack[top++] = _suffixBuf[code];
code = _prefixBuf[code];
if (top == MaxBits)
ThrowException();
}
first = _suffixBuf[code];
_pixelStack[top++] = (byte)first;
// Add new code to the dictionary
if (available < MaxStackSize)
{
_prefixBuf[available] = (short)oldCode;
_suffixBuf[available] = (byte)first;
available++;
if ((available & codeMask) == 0 && available < MaxStackSize)
{
codeSize++;
codeMask += available;
}
}
oldCode = inCode;
// Drain the pixel stack.
do
{
indexSpan[pixelIndex++] = _pixelStack[--top];
} while (top > 0);
}
}
}
while (pixelIndex < totalPixels)
indexSpan[pixelIndex++] = 0; // clear missing pixels
void ThrowException() => throw new LzwDecompressionException();
}
/// <summary>
/// Directly copies the <see cref="GifColor"/> struct array to a bitmap IntPtr.
/// </summary>
private void WriteBackBufToFb(IntPtr targetPointer)
{
if (_currentCtsToken.IsCancellationRequested)
return;
if (!(_hasNewFrame & _bitmapBackBuffer != null))
return;
unsafe
{
fixed (void* src = &_bitmapBackBuffer[0])
Buffer.MemoryCopy(src, targetPointer.ToPointer(), (uint)_backBufferBytes, (uint)_backBufferBytes);
_hasNewFrame = false;
}
}
/// <summary>
/// Processes GIF Header.
/// </summary>
private void ProcessHeaderData()
{
var str = _fileStream;
var tmpB = ArrayPool<byte>.Shared.Rent(MaxTempBuf);
var tempBuf = tmpB.AsSpan();
var _ = str.Read(tmpB, 0, 6);
if (!tempBuf[..3].SequenceEqual(G87AMagic[..3].Span))
throw new InvalidGifStreamException("Not a GIF stream.");
if (!(tempBuf[..6].SequenceEqual(G87AMagic.Span) | tempBuf[..6].SequenceEqual(G89AMagic.Span)))
throw new InvalidGifStreamException(
"Unsupported GIF Version: " + Encoding.ASCII.GetString(tempBuf[..6].ToArray())
);
ProcessScreenDescriptor(tmpB);
Header = new GifHeader
{
Dimensions = _gifDimensions,
HasGlobalColorTable = _gctUsed,
// GlobalColorTableCacheID = _globalColorTable,
GlobarColorTable = ProcessColorTable(ref str, tmpB, _gctSize),
GlobalColorTableSize = _gctSize,
BackgroundColorIndex = _bgIndex,
HeaderSize = _fileStream.Position
};
ArrayPool<byte>.Shared.Return(tmpB);
}
/// <summary>
/// Parses colors from file stream to target color table.
/// </summary>
private static GifColor[] ProcessColorTable(ref Stream stream, byte[] rawBufSpan, int nColors)
{
var nBytes = 3 * nColors;
var target = new GifColor[nColors];
var n = stream.Read(rawBufSpan, 0, nBytes);
if (n < nBytes)
throw new InvalidOperationException("Wrong color table bytes.");
int i = 0,
j = 0;
while (i < nColors)
{
var r = rawBufSpan[j++];
var g = rawBufSpan[j++];
var b = rawBufSpan[j++];
target[i++] = new GifColor(r, g, b);
}
return target;
}
/// <summary>
/// Parses screen and other GIF descriptors.
/// </summary>
private void ProcessScreenDescriptor(byte[] tempBuf)
{
var width = _fileStream.ReadUShortS(tempBuf);
var height = _fileStream.ReadUShortS(tempBuf);
var packed = _fileStream.ReadByteS(tempBuf);
_gctUsed = (packed & 0x80) != 0;
_gctSize = 2 << (packed & 7);
_bgIndex = _fileStream.ReadByteS(tempBuf);
_gifDimensions = new GifRect(0, 0, width, height);
_fileStream.Skip(1);
}
/// <summary>
/// Parses all frame data.
/// </summary>
private void ProcessFrameData()
{
_fileStream.Position = Header.HeaderSize;
var tempBuf = ArrayPool<byte>.Shared.Rent(MaxTempBuf);
var terminate = false;
var curFrame = 0;
Frames.Add(new GifFrame());
do
{
var blockType = (BlockTypes)_fileStream.ReadByteS(tempBuf);
switch (blockType)
{
case BlockTypes.Empty:
break;
case BlockTypes.Extension:
ProcessExtensions(ref curFrame, tempBuf);
break;
case BlockTypes.ImageDescriptor:
ProcessImageDescriptor(ref curFrame, tempBuf);
_fileStream.SkipBlocks(tempBuf);
break;
case BlockTypes.Trailer:
Frames.RemoveAt(Frames.Count - 1);
terminate = true;
break;
default:
_fileStream.SkipBlocks(tempBuf);
break;
}
// Break the loop when the stream is not valid anymore.
if (_fileStream.Position >= _fileStream.Length & terminate == false)
throw new InvalidProgramException("Reach the end of the filestream without trailer block.");
} while (!terminate);
ArrayPool<byte>.Shared.Return(tempBuf);
}
/// <summary>
/// Parses GIF Image Descriptor Block.
/// </summary>
private void ProcessImageDescriptor(ref int curFrame, byte[] tempBuf)
{
var str = _fileStream;
var currentFrame = Frames[curFrame];
// Parse frame dimensions.
var frameX = str.ReadUShortS(tempBuf);
var frameY = str.ReadUShortS(tempBuf);
var frameW = str.ReadUShortS(tempBuf);
var frameH = str.ReadUShortS(tempBuf);
frameW = (ushort)Math.Min(frameW, _gifDimensions.Width - frameX);
frameH = (ushort)Math.Min(frameH, _gifDimensions.Height - frameY);
currentFrame.Dimensions = new GifRect(frameX, frameY, frameW, frameH);
// Unpack interlace and lct info.
var packed = str.ReadByteS(tempBuf);
currentFrame.IsInterlaced = (packed & 0x40) != 0;
currentFrame.IsLocalColorTableUsed = (packed & 0x80) != 0;
currentFrame.LocalColorTableSize = (int)Math.Pow(2, (packed & 0x07) + 1);
if (currentFrame.IsLocalColorTableUsed)
currentFrame.LocalColorTable = ProcessColorTable(ref str, tempBuf, currentFrame.LocalColorTableSize);
currentFrame.LzwMinCodeSize = str.ReadByteS(tempBuf);
currentFrame.LzwStreamPosition = str.Position;
curFrame += 1;
Frames.Add(new GifFrame());
}
/// <summary>
/// Parses GIF Extension Blocks.
/// </summary>
private void ProcessExtensions(ref int curFrame, byte[] tempBuf)
{
var extType = (ExtensionType)_fileStream.ReadByteS(tempBuf);
switch (extType)
{
case ExtensionType.GraphicsControl:
_fileStream.ReadBlock(tempBuf);
var currentFrame = Frames[curFrame];
var packed = tempBuf[0];
currentFrame.FrameDisposalMethod = (FrameDisposal)((packed & 0x1c) >> 2);
if (
currentFrame.FrameDisposalMethod != FrameDisposal.Restore
&& currentFrame.FrameDisposalMethod != FrameDisposal.Background
)
currentFrame.ShouldBackup = true;
currentFrame.HasTransparency = (packed & 1) != 0;
currentFrame.FrameDelay = TimeSpan.FromMilliseconds(SpanToShort(tempBuf.AsSpan(1)) * 10);
if (currentFrame.FrameDelay <= FrameDelayThreshold)
currentFrame.FrameDelay = FrameDelayDefault;
currentFrame.TransparentColorIndex = tempBuf[3];
break;
case ExtensionType.Application:
var blockLen = _fileStream.ReadBlock(tempBuf);
var _ = tempBuf.AsSpan(0, blockLen);
var blockHeader = tempBuf.AsSpan(0, NetscapeMagic.Length);
if (blockHeader.SequenceEqual(NetscapeMagic.Span))
{
var count = 1;
while (count > 0)
count = _fileStream.ReadBlock(tempBuf);
var iterationCount = SpanToShort(tempBuf.AsSpan(1));
Header.Iterations = iterationCount;
}
else
_fileStream.SkipBlocks(tempBuf);
break;
default:
_fileStream.SkipBlocks(tempBuf);
break;
}
}
}
}

20
Avalonia.Gif/Decoding/GifFrame.cs

@ -0,0 +1,20 @@
using System;
namespace Avalonia.Gif.Decoding
{
public class GifFrame
{
public bool HasTransparency,
IsInterlaced,
IsLocalColorTableUsed;
public byte TransparentColorIndex;
public int LzwMinCodeSize,
LocalColorTableSize;
public long LzwStreamPosition;
public TimeSpan FrameDelay;
public FrameDisposal FrameDisposalMethod;
public bool ShouldBackup;
public GifRect Dimensions;
public GifColor[] LocalColorTable;
}
}

19
Avalonia.Gif/Decoding/GifHeader.cs

@ -0,0 +1,19 @@
// Licensed under the MIT License.
// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.
namespace Avalonia.Gif.Decoding
{
public class GifHeader
{
public bool HasGlobalColorTable;
public int GlobalColorTableSize;
public ulong GlobalColorTableCacheId;
public int BackgroundColorIndex;
public long HeaderSize;
internal int Iterations = -1;
public GifRepeatBehavior IterationCount;
public GifRect Dimensions;
private GifColor[] _globarColorTable;
public GifColor[] GlobarColorTable;
}
}

43
Avalonia.Gif/Decoding/GifRect.cs

@ -0,0 +1,43 @@
namespace Avalonia.Gif.Decoding
{
public readonly struct GifRect
{
public int X { get; }
public int Y { get; }
public int Width { get; }
public int Height { get; }
public int TotalPixels { get; }
public GifRect(int x, int y, int width, int height)
{
X = x;
Y = y;
Width = width;
Height = height;
TotalPixels = width * height;
}
public static bool operator ==(GifRect a, GifRect b)
{
return a.X == b.X && a.Y == b.Y && a.Width == b.Width && a.Height == b.Height;
}
public static bool operator !=(GifRect a, GifRect b)
{
return !(a == b);
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
return false;
return this == (GifRect)obj;
}
public override int GetHashCode()
{
return X.GetHashCode() ^ Y.GetHashCode() | Width.GetHashCode() ^ Height.GetHashCode();
}
}
}

8
Avalonia.Gif/Decoding/GifRepeatBehavior.cs

@ -0,0 +1,8 @@
namespace Avalonia.Gif.Decoding
{
public class GifRepeatBehavior
{
public bool LoopForever { get; set; }
public int? Count { get; set; }
}
}

23
Avalonia.Gif/Decoding/InvalidGifStreamException.cs

@ -0,0 +1,23 @@
// Licensed under the MIT License.
// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.
using System;
using System.Runtime.Serialization;
namespace Avalonia.Gif.Decoding
{
[Serializable]
public class InvalidGifStreamException : Exception
{
public InvalidGifStreamException() { }
public InvalidGifStreamException(string message)
: base(message) { }
public InvalidGifStreamException(string message, Exception innerException)
: base(message, innerException) { }
protected InvalidGifStreamException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
}

23
Avalonia.Gif/Decoding/LzwDecompressionException.cs

@ -0,0 +1,23 @@
// Licensed under the MIT License.
// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.
using System;
using System.Runtime.Serialization;
namespace Avalonia.Gif.Decoding
{
[Serializable]
public class LzwDecompressionException : Exception
{
public LzwDecompressionException() { }
public LzwDecompressionException(string message)
: base(message) { }
public LzwDecompressionException(string message, Exception innerException)
: base(message, innerException) { }
protected LzwDecompressionException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
}

81
Avalonia.Gif/Extensions/StreamExtensions.cs

@ -0,0 +1,81 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
namespace Avalonia.Gif.Extensions
{
[DebuggerStepThrough]
internal static class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort SpanToShort(Span<byte> b) => (ushort)(b[0] | (b[1] << 8));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Skip(this Stream stream, long count)
{
stream.Position += count;
}
/// <summary>
/// Read a Gif block from stream while advancing the position.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadBlock(this Stream stream, byte[] tempBuf)
{
stream.Read(tempBuf, 0, 1);
var blockLength = (int)tempBuf[0];
if (blockLength > 0)
stream.Read(tempBuf, 0, blockLength);
// Guard against infinite loop.
if (stream.Position >= stream.Length)
throw new InvalidGifStreamException("Reach the end of the filestream without trailer block.");
return blockLength;
}
/// <summary>
/// Skips GIF blocks until it encounters an empty block.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SkipBlocks(this Stream stream, byte[] tempBuf)
{
int blockLength;
do
{
stream.Read(tempBuf, 0, 1);
blockLength = tempBuf[0];
stream.Position += blockLength;
// Guard against infinite loop.
if (stream.Position >= stream.Length)
throw new InvalidGifStreamException("Reach the end of the filestream without trailer block.");
} while (blockLength > 0);
}
/// <summary>
/// Read a <see cref="ushort"/> from stream by providing a temporary buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadUShortS(this Stream stream, byte[] tempBuf)
{
stream.Read(tempBuf, 0, 2);
return SpanToShort(tempBuf);
}
/// <summary>
/// Read a <see cref="ushort"/> from stream by providing a temporary buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte ReadByteS(this Stream stream, byte[] tempBuf)
{
stream.Read(tempBuf, 0, 1);
var finalVal = tempBuf[0];
return finalVal;
}
}
}

296
Avalonia.Gif/GifImage.cs

@ -0,0 +1,296 @@
using System;
using System.IO;
using System.Numerics;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Rendering.Composition;
using Avalonia.VisualTree;
namespace Avalonia.Gif
{
public class GifImage : Control
{
public static readonly StyledProperty<string> SourceUriRawProperty = AvaloniaProperty.Register<
GifImage,
string
>("SourceUriRaw");
public static readonly StyledProperty<Uri> SourceUriProperty = AvaloniaProperty.Register<GifImage, Uri>(
"SourceUri"
);
public static readonly StyledProperty<Stream> SourceStreamProperty = AvaloniaProperty.Register<
GifImage,
Stream
>("SourceStream");
public static readonly StyledProperty<IterationCount> IterationCountProperty = AvaloniaProperty.Register<
GifImage,
IterationCount
>("IterationCount", IterationCount.Infinite);
private GifInstance? _gifInstance;
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty = AvaloniaProperty.Register<
GifImage,
StretchDirection
>("StretchDirection");
public static readonly StyledProperty<Stretch> StretchProperty = AvaloniaProperty.Register<GifImage, Stretch>(
"Stretch"
);
private CompositionCustomVisual? _customVisual;
private object? _initialSource = null;
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
switch (change.Property.Name)
{
case nameof(SourceUriRaw):
case nameof(SourceUri):
case nameof(SourceStream):
SourceChanged(change);
break;
case nameof(Stretch):
case nameof(StretchDirection):
InvalidateArrange();
InvalidateMeasure();
Update();
break;
case nameof(IterationCount):
IterationCountChanged(change);
break;
case nameof(Bounds):
Update();
break;
}
base.OnPropertyChanged(change);
}
public string SourceUriRaw
{
get => GetValue(SourceUriRawProperty);
set => SetValue(SourceUriRawProperty, value);
}
public Uri SourceUri
{
get => GetValue(SourceUriProperty);
set => SetValue(SourceUriProperty, value);
}
public Stream SourceStream
{
get => GetValue(SourceStreamProperty);
set => SetValue(SourceStreamProperty, value);
}
public IterationCount IterationCount
{
get => GetValue(IterationCountProperty);
set => SetValue(IterationCountProperty, value);
}
public StretchDirection StretchDirection
{
get => GetValue(StretchDirectionProperty);
set => SetValue(StretchDirectionProperty, value);
}
public Stretch Stretch
{
get => GetValue(StretchProperty);
set => SetValue(StretchProperty, value);
}
private static void IterationCountChanged(AvaloniaPropertyChangedEventArgs e)
{
var image = e.Sender as GifImage;
if (image is null || e.NewValue is not IterationCount iterationCount)
return;
image.IterationCount = iterationCount;
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
var compositor = ElementComposition.GetElementVisual(this)?.Compositor;
if (compositor == null || _customVisual?.Compositor == compositor)
return;
_customVisual = compositor.CreateCustomVisual(new CustomVisualHandler());
ElementComposition.SetElementChildVisual(this, _customVisual);
_customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage);
if (_initialSource is not null)
{
UpdateGifInstance(_initialSource);
_initialSource = null;
}
Update();
base.OnAttachedToVisualTree(e);
}
private void Update()
{
if (_customVisual is null || _gifInstance is null)
return;
var dpi = this.GetVisualRoot()?.RenderScaling ?? 1.0;
var sourceSize = _gifInstance.GifPixelSize.ToSize(dpi);
var viewPort = new Rect(Bounds.Size);
var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
var scaledSize = sourceSize * scale;
var destRect = viewPort.CenterRect(new Rect(scaledSize)).Intersect(viewPort);
if (Stretch == Stretch.None)
{
_customVisual.Size = new Vector2((float)sourceSize.Width, (float)sourceSize.Height);
}
else
{
_customVisual.Size = new Vector2((float)destRect.Size.Width, (float)destRect.Size.Height);
}
_customVisual.Offset = new Vector3((float)destRect.Position.X, (float)destRect.Position.Y, 0);
}
private class CustomVisualHandler : CompositionCustomVisualHandler
{
private TimeSpan _animationElapsed;
private TimeSpan? _lastServerTime;
private GifInstance? _currentInstance;
private bool _running;
public static readonly object StopMessage = new(),
StartMessage = new();
public override void OnMessage(object message)
{
if (message == StartMessage)
{
_running = true;
_lastServerTime = null;
RegisterForNextAnimationFrameUpdate();
}
else if (message == StopMessage)
{
_running = false;
}
else if (message is GifInstance instance)
{
_currentInstance?.Dispose();
_currentInstance = instance;
}
}
public override void OnAnimationFrameUpdate()
{
if (!_running)
return;
Invalidate();
RegisterForNextAnimationFrameUpdate();
}
public override void OnRender(ImmediateDrawingContext drawingContext)
{
if (_running)
{
if (_lastServerTime.HasValue)
_animationElapsed += (CompositionNow - _lastServerTime.Value);
_lastServerTime = CompositionNow;
}
try
{
if (_currentInstance is null || _currentInstance.IsDisposed)
return;
var bitmap = _currentInstance.ProcessFrameTime(_animationElapsed);
if (bitmap is not null)
{
drawingContext.DrawBitmap(
bitmap,
new Rect(_currentInstance.GifPixelSize.ToSize(1)),
GetRenderBounds()
);
}
}
catch (Exception e)
{
Logger.Sink?.Log(LogEventLevel.Error, "GifImage Renderer ", this, e.ToString());
}
}
}
/// <summary>
/// Measures the control.
/// </summary>
/// <param name="availableSize">The available size.</param>
/// <returns>The desired size of the control.</returns>
protected override Size MeasureOverride(Size availableSize)
{
var result = new Size();
var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0;
if (_gifInstance != null)
{
result = Stretch.CalculateSize(
availableSize,
_gifInstance.GifPixelSize.ToSize(scaling),
StretchDirection
);
}
return result;
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
if (_gifInstance is null)
return new Size();
var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0;
var sourceSize = _gifInstance.GifPixelSize.ToSize(scaling);
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}
private void SourceChanged(AvaloniaPropertyChangedEventArgs e)
{
if (
e.NewValue is null
|| (e.NewValue is string value && !Uri.IsWellFormedUriString(value, UriKind.Absolute))
)
{
return;
}
if (_customVisual is null)
{
_initialSource = e.NewValue;
return;
}
UpdateGifInstance(e.NewValue);
InvalidateArrange();
InvalidateMeasure();
Update();
}
private void UpdateGifInstance(object source)
{
_gifInstance?.Dispose();
_gifInstance = new GifInstance(source);
_gifInstance.IterationCount = IterationCount;
_customVisual?.SendHandlerMessage(_gifInstance);
}
}
}

147
Avalonia.Gif/GifInstance.cs

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Gif.Decoding;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
namespace Avalonia.Gif
{
public class GifInstance : IDisposable
{
public IterationCount IterationCount { get; set; }
public bool AutoStart { get; private set; } = true;
private readonly GifDecoder _gifDecoder;
private readonly WriteableBitmap? _targetBitmap;
private TimeSpan _totalTime;
private readonly List<TimeSpan> _frameTimes;
private uint _iterationCount;
private int _currentFrameIndex;
private readonly List<ulong> _colorTableIdList;
public CancellationTokenSource CurrentCts { get; }
internal GifInstance(object newValue)
: this(
newValue switch
{
Stream s => s,
Uri u => GetStreamFromUri(u),
string str => GetStreamFromString(str),
_ => throw new InvalidDataException("Unsupported source object")
}
) { }
public GifInstance(string uri)
: this(GetStreamFromString(uri)) { }
public GifInstance(Uri uri)
: this(GetStreamFromUri(uri)) { }
public GifInstance(Stream currentStream)
{
if (!currentStream.CanSeek)
throw new InvalidDataException("The provided stream is not seekable.");
if (!currentStream.CanRead)
throw new InvalidOperationException("Can't read the stream provided.");
currentStream.Seek(0, SeekOrigin.Begin);
CurrentCts = new CancellationTokenSource();
_gifDecoder = new GifDecoder(currentStream, CurrentCts.Token);
var pixSize = new PixelSize(_gifDecoder.Header.Dimensions.Width, _gifDecoder.Header.Dimensions.Height);
_targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque);
GifPixelSize = pixSize;
_totalTime = TimeSpan.Zero;
_frameTimes = _gifDecoder
.Frames
.Select(frame =>
{
_totalTime = _totalTime.Add(frame.FrameDelay);
return _totalTime;
})
.ToList();
_gifDecoder.RenderFrame(0, _targetBitmap);
}
private static Stream GetStreamFromString(string str)
{
if (!Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out var res))
{
throw new InvalidCastException("The string provided can't be converted to URI.");
}
return GetStreamFromUri(res);
}
private static Stream GetStreamFromUri(Uri uri)
{
var uriString = uri.OriginalString.Trim();
if (!uriString.StartsWith("resm") && !uriString.StartsWith("avares"))
{
return new FileStream(uriString, FileMode.Open, FileAccess.Read);
}
return AssetLoader.Open(uri);
}
public int GifFrameCount => _frameTimes.Count;
public PixelSize GifPixelSize { get; }
public void Dispose()
{
IsDisposed = true;
CurrentCts.Cancel();
_targetBitmap?.Dispose();
}
public bool IsDisposed { get; private set; }
public WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed)
{
if (!IterationCount.IsInfinite && _iterationCount > IterationCount.Value)
{
return null;
}
if (CurrentCts.IsCancellationRequested || _targetBitmap is null)
{
return null;
}
var elapsedTicks = stopwatchElapsed.Ticks;
var timeModulus = TimeSpan.FromTicks(elapsedTicks % _totalTime.Ticks);
var targetFrame = _frameTimes.FirstOrDefault(x => timeModulus < x);
var currentFrame = _frameTimes.IndexOf(targetFrame);
if (currentFrame == -1)
currentFrame = 0;
if (_currentFrameIndex == currentFrame)
return _targetBitmap;
_iterationCount = (uint)(elapsedTicks / _totalTime.Ticks);
return ProcessFrameIndex(currentFrame);
}
internal WriteableBitmap ProcessFrameIndex(int frameIndex)
{
_gifDecoder.RenderFrame(frameIndex, _targetBitmap);
_currentFrameIndex = frameIndex;
return _targetBitmap;
}
}
}

20
Avalonia.Gif/InvalidGifStreamException.cs

@ -0,0 +1,20 @@
using System;
using System.Runtime.Serialization;
namespace Avalonia.Gif
{
[Serializable]
internal class InvalidGifStreamException : Exception
{
public InvalidGifStreamException() { }
public InvalidGifStreamException(string message)
: base(message) { }
public InvalidGifStreamException(string message, Exception innerException)
: base(message, innerException) { }
protected InvalidGifStreamException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
}

145
CHANGELOG.md

@ -5,6 +5,142 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
## v2.7.2
### Changed
- Changed Symlink shared folder link targets for Automatic1111 and ComfyUI. From `ControlNet -> models/controlnet` to `ControlNet -> models/controlnet/ControlNet` and `T2IAdapter -> models/controlnet/T2IAdapter`.
- Changed FreeU defaults to match recommended SD1.5 defaults
- Changed default denoise strength from 1.0 to 0.7
### Fixed
- Fixed ControlNet / T2IAdapter shared folder links for Automatic1111 conflicting with each other
- Fixed URIScheme registration errors on Linux
- Fixed RuinedFooocus missing output folder on startup
- Fixed incorrect Fooocus VRAM launch arguments
## v2.7.1
### Added
- Added Turkish UI language option, thanks to Progresor for the translation
### Fixed
- Fixed Inference Image to Image projects missing denoise strength setting
## v2.7.0
### Added
#### General
- New package: [RuinedFooocus](https://github.com/runew0lf/RuinedFooocus)
- Added an X button to all search fields to instantly clear them (Esc key also works)
- Added System Information section to Settings
#### Inference
- Added Image to Image project type
- Added Modular custom steps
- Use the plus button to add new steps (Hires Fix, Upscaler, and Save Image are currently available), and the edit button to enable removing or dragging steps to reorder them. This enables multi-pass Hires Fix, mixing different upscalers, and saving intermediate images at any point in the pipeline.
- Added Sampler addons
- Addons usually affect guidance like ControlNet, T2I, FreeU, and other addons to come. They apply to the individual sampler, so you can mix and match different ControlNets for Base and Hires Fix, or use the current output from a previous sampler as ControlNet guidance image for HighRes passes.
- Added SD Turbo Scheduler
- Added display names for new samplers ("Heun++ 2", "DDPM", "LCM")
- Added Ctrl+Enter as a shortcut for the Generate Image button
#### Accounts Settings Subpage
- Lykos Account sign-up and login - currently for Patreon OAuth connections but GitHub requests caching and settings sync are planned
- Supporters can now connect your Patreon accounts, then head to the Updates page to choose to receive auto-updates from the Dev or Preview channels
- CivitAI Account login with API key - enables downloading models from the Browser page that require CivitAI logins, more integrations like liking and commenting are also planned
#### Updates Settings Subpage
- Toggle auto-update notifications and manually check for updates
- Choose between Stable, Preview, and Dev update channels
#### Inference Settings Subpage
- Moved Inference settings to subpage
- Updated with more localized labels
#### Outputs Page
- Added Refresh button to update gallery from file system changes
#### Checkpoints Page
- Added the ability to drag & drop checkpoints between different folders
- Added "Copy Trigger Words" option to the three-dots menu on the Checkpoints page (when data is available)
- Added trigger words on checkpoint card and tooltip
- Added "Find Connected Metadata" options for root-level and file-level scans
- Added "Update Existing Metadata" button
#### Model Browser
- Added Hugging Face tab to the Model Browser
- Added additional base model filter options for CivitAI ("SD 1.5 LCM", "SDXL 1.0 LCM", "SDXL Turbo", "Other")
- Added the ability to type in a specific page number in the CivitAI Model Browser
- Right clicking anywhere on the model card will open the same menu as the three-dots button
- New model downloads will save trigger words in metadata, if available
- Model author username and avatar display, with clickable link to their profile
### Changed
#### General
- Model Browser page has been redesigned, featuring more information like rating and download counts
- Model Browser navigation has improved animations on hover and compact number formatting
- Updated Outputs Page button and menu layout
- Rearranged Add Package dialog slightly to accommodate longer package list
- Folder-level "Find Connected Metadata" now scans the selected folder and its subfolders
- Model Browser now split into "CivitAI" and "Hugging Face" tabs
#### Inference
- Selected images (i.e. Image2Image, Upscale, ControlNet) will now save their source paths saved and restored on load. If the image is moved or deleted, the selection will show as missing and can be reselected
- Project files (.smproj) have been updated to v3, existing projects will be upgraded on load and will no longer be compatible with older versions of Stability Matrix
### Fixed
- Fixed Outputs page reverting back to Shared Output Folder every time the page is reloaded
- Potentially fixed updates sometimes clearing settings or launching in the wrong directory
- Improved startup time and window load time after exiting dialogs
- Fixed control character decoding that caused some progress bars to show as `\u2588`
- Fixed Python `rich` package's progress bars not showing in console
- Optimized ProgressRing animation bindings to reduce CPU usage
- Improved safety checks in custom control rendering to reduce potential graphical artifacts
- Improved console rendering safety with cursor line increment clamping, as potential fix for [#111](https://github.com/LykosAI/StabilityMatrix/issues/111)
- Fixed [#290](https://github.com/LykosAI/StabilityMatrix/issues/290) - Model browser crash due to text trimming certain unicode characters
- Fixed crash when loading an empty settings file
- Improve Settings save and load performance with .NET 8 Source Generating Serialization
- Fixed ApplicationException during database shutdown
- InvokeAI model links for T2I/IpAdapters now point to the correct folders
- Added extra checks to help prevent settings resetting in certain scenarios
- Fixed Refiner model enabled state not saving to Inference project files
- Fixed NullReference error labels when clearing the Inference batch size settings, now shows improved message with minimum and maximum value constraints
## v2.7.0-pre.4
### Added
#### Inference
- Added Image to Image project type
- Added Modular custom steps
- Use the plus button to add new steps (Hires Fix, Upscaler, and Save Image are currently available), and the edit button to enable removing or dragging steps to reorder them. This enables multi-pass Hires Fix, mixing different upscalers, and saving intermediate images at any point in the pipeline.
- Added Sampler addons
- Addons usually affect guidance like ControlNet, T2I, FreeU, and other addons to come. They apply to the individual sampler, so you can mix and match different ControlNets for Base and Hires Fix, or use the current output from a previous sampler as ControlNet guidance image for HighRes passes.
- Added SD Turbo Scheduler
- Added display names for new samplers ("Heun++ 2", "DDPM", "LCM")
#### Model Browser
- Added additional base model filter options ("SD 1.5 LCM", "SDXL 1.0 LCM", "SDXL Turbo", "Other")
### Changed
#### Inference
- Selected images (i.e. Image2Image, Upscale, ControlNet) will now save their source paths saved and restored on load. If the image is moved or deleted, the selection will show as missing and can be reselected
- Project files (.smproj) have been updated to v3, existing projects will be upgraded on load and will no longer be compatible with older versions of Stability Matrix
### Fixed
- Fixed Refiner model enabled state not saving to Inference project files
## v2.7.0-pre.3
### Added
- Added "Find Connected Metadata" options for root-level and file-level scans to the Checkpoints page
- Added "Update Existing Metadata" button to the Checkpoints page
- Added Hugging Face tab to the Model Browser
- Added the ability to type in a specific page number in the CivitAI Model Browser
### Changed
- Folder-level "Find Connected Metadata" now scans the selected folder and its subfolders
- Model Browser now split into "CivitAI" and "Hugging Face" tabs
### Fixed
- InvokeAI model links for T2I/IpAdapters now point to the correct folders
- Added extra checks to help prevent settings resetting in certain scenarios
## v2.7.0-pre.2
### Added
- Added System Information section to Settings
### Changed
- Moved Inference Settings to subpage
### Fixed
- Fixed crash when loading an empty settings file
- Improve Settings save and load performance with .NET 8 Source Generating Serialization
- Fixed ApplicationException during database shutdown
## v2.7.0-pre.1
### Fixed
- Fixed control character decoding that caused some progress bars to show as `\u2588`
- Fixed Python `rich` package's progress bars not showing in console
- Optimized ProgressRing animation bindings to reduce CPU usage
- Improved safety checks in custom control rendering to reduce potential graphical artifacts
- Improved console rendering safety with cursor line increment clamping, as potential fix for [#111](https://github.com/LykosAI/StabilityMatrix/issues/111)
## v2.7.0-dev.4 ## v2.7.0-dev.4
### Fixed ### Fixed
- Fixed [#290](https://github.com/LykosAI/StabilityMatrix/issues/290) - Model browser crash due to text trimming certain unicode characters - Fixed [#290](https://github.com/LykosAI/StabilityMatrix/issues/290) - Model browser crash due to text trimming certain unicode characters
@ -36,7 +172,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Added Refresh button to update gallery from file system changes - Added Refresh button to update gallery from file system changes
#### Checkpoints Page #### Checkpoints Page
- Added the ability to drag & drop checkpoints between different folders - Added the ability to drag & drop checkpoints between different folders
## Changed ### Changed
#### Outputs Page #### Outputs Page
- Updated button and menu layout - Updated button and menu layout
#### Packages Page #### Packages Page
@ -55,9 +191,14 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Updates Settings Subpage - Updates Settings Subpage
- Toggle auto-update notifications and manually check for updates - Toggle auto-update notifications and manually check for updates
- Choose between Stable, Preview, and Dev update channels - Choose between Stable, Preview, and Dev update channels
## Changed ### Changed
- Model Browser page has been redesigned, featuring more information like rating and download counts - Model Browser page has been redesigned, featuring more information like rating and download counts
## v2.6.7
### Fixed
- Fixed prerequisite install not unpacking due to improperly formatted 7z argument (Caused the "python310._pth FileNotFoundException")
- Fixed [#301](https://github.com/LykosAI/StabilityMatrix/issues/301) - Package updates failing silently because of a PortableGit error
## v2.6.6 ## v2.6.6
### Fixed ### Fixed
- Fixed [#297](https://github.com/LykosAI/StabilityMatrix/issues/297) - Model browser LiteAsyncException occuring when fetching entries with unrecognized values from enum name changes - Fixed [#297](https://github.com/LykosAI/StabilityMatrix/issues/297) - Model browser LiteAsyncException occuring when fetching entries with unrecognized values from enum name changes

2
README.md

@ -104,6 +104,8 @@ Stability Matrix is now available in the following languages, thanks to our comm
- Lautaroturina - Lautaroturina
- 🇷🇺 Русский - 🇷🇺 Русский
- aolko - aolko
- 🇹🇷 Türkçe
- Progresor
If you would like to contribute a translation, please create an issue or contact us on Discord. Include an email where we'll send an invite to our [POEditor](https://poeditor.com/) project. If you would like to contribute a translation, please create an issue or contact us on Discord. Include an email where we'll send an invite to our [POEditor](https://poeditor.com/) project.

1
StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj

@ -21,6 +21,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.5" /> <PackageReference Include="Avalonia" Version="11.0.5" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
<PackageReference Include="DotNet.Bundle" Version="0.9.13" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />

34
StabilityMatrix.Avalonia/App.axaml

@ -4,6 +4,7 @@
xmlns:local="using:StabilityMatrix.Avalonia" xmlns:local="using:StabilityMatrix.Avalonia"
xmlns:idcr="using:Dock.Avalonia.Controls.Recycling" xmlns:idcr="using:Dock.Avalonia.Controls.Recycling"
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia" xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
Name="Stability Matrix"
RequestedThemeVariant="Dark"> RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
@ -21,18 +22,16 @@
<ResourceInclude Source="Styles/ContextMenuStyles.axaml"/> <ResourceInclude Source="Styles/ContextMenuStyles.axaml"/>
<ResourceInclude Source="Controls/EditorFlyouts.axaml"/> <ResourceInclude Source="Controls/EditorFlyouts.axaml"/>
<ResourceInclude Source="Controls/Scroll/BetterScrollViewer.axaml"/> <ResourceInclude Source="Controls/Scroll/BetterScrollViewer.axaml"/>
<ResourceInclude Source="Controls/ImageFolderCard.axaml"/>
<ResourceInclude Source="Styles/ControlThemes/HyperlinkIconButtonStyles.axaml"/> <ResourceInclude Source="Styles/ControlThemes/HyperlinkIconButtonStyles.axaml"/>
<ResourceInclude Source="Styles/ControlThemes/ListBoxStyles.axaml"/> <ResourceInclude Source="Styles/ControlThemes/ListBoxStyles.axaml"/>
<ResourceInclude Source="Styles/ListBoxStyles.axaml"/> <ResourceInclude Source="Styles/ListBoxStyles.axaml"/>
<ResourceInclude Source="Styles/FAComboBoxStyles.axaml"/> <ResourceInclude Source="Styles/FAComboBoxStyles.axaml"/>
<ResourceInclude Source="Controls/Inference/ImageFolderCard.axaml"/>
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<idcr:ControlRecycling x:Key="ControlRecyclingKey" /> <idcr:ControlRecycling x:Key="ControlRecyclingKey" />
<x:Double x:Key="ContentDialogMaxWidth">700</x:Double> <x:Double x:Key="ContentDialogMaxWidth">700</x:Double>
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">32</x:Double>
<SolidColorBrush x:Key="ToolTipBackground" Color="#1E1F22"/> <SolidColorBrush x:Key="ToolTipBackground" Color="#1E1F22"/>
<SolidColorBrush x:Key="ToolTipForeground" Color="#9FBDC3"/> <SolidColorBrush x:Key="ToolTipForeground" Color="#9FBDC3"/>
<FontFamily x:Key="NotoSansJP">avares://StabilityMatrix.Avalonia/Assets/Fonts/NotoSansJP#Noto Sans JP</FontFamily> <FontFamily x:Key="NotoSansJP">avares://StabilityMatrix.Avalonia/Assets/Fonts/NotoSansJP#Noto Sans JP</FontFamily>
@ -53,29 +52,30 @@
<StyleInclude Source="Styles/ToggleButtonStyles.axaml"/> <StyleInclude Source="Styles/ToggleButtonStyles.axaml"/>
<StyleInclude Source="Styles/DockStyles.axaml"/> <StyleInclude Source="Styles/DockStyles.axaml"/>
<StyleInclude Source="Styles/BorderStyles.axaml"/> <StyleInclude Source="Styles/BorderStyles.axaml"/>
<StyleInclude Source="Controls/SeedCard.axaml"/>
<StyleInclude Source="Controls/SamplerCard.axaml"/>
<StyleInclude Source="Controls/ImageGalleryCard.axaml"/>
<StyleInclude Source="Controls/PromptCard.axaml"/>
<StyleInclude Source="Controls/StackCard.axaml"/>
<StyleInclude Source="Controls/StackEditableCard.axaml"/>
<StyleInclude Source="Controls/UpscalerCard.axaml"/>
<StyleInclude Source="Controls/StackExpander.axaml"/>
<StyleInclude Source="Controls/ModelCard.axaml"/>
<StyleInclude Source="Controls/BatchSizeCard.axaml"/>
<StyleInclude Source="Controls/AdvancedImageBox.axaml"/> <StyleInclude Source="Controls/AdvancedImageBox.axaml"/>
<StyleInclude Source="Controls/FrameCarousel.axaml"/> <StyleInclude Source="Controls/FrameCarousel.axaml"/>
<StyleInclude Source="Controls/CodeCompletion/CompletionWindow.axaml"/> <StyleInclude Source="Controls/CodeCompletion/CompletionWindow.axaml"/>
<StyleInclude Source="Controls/SelectImageCard.axaml"/>
<StyleInclude Source="Controls/SharpenCard.axaml"/>
<StyleInclude Source="Controls/FreeUCard.axaml"/>
<StyleInclude Source="Controls/ControlNetCard.axaml"/>
<StyleInclude Source="Controls/Paginator.axaml"/> <StyleInclude Source="Controls/Paginator.axaml"/>
<StyleInclude Source="Controls/SelectableImageCard/SelectableImageButton.axaml"/> <StyleInclude Source="Controls/SelectableImageCard/SelectableImageButton.axaml"/>
<StyleInclude Source="Controls/SettingsAccountLinkExpander.axaml"/> <StyleInclude Source="Controls/SettingsAccountLinkExpander.axaml"/>
<StyleInclude Source="Controls/StarsRating.axaml"/> <StyleInclude Source="Controls/StarsRating.axaml"/>
<StyleInclude Source="Controls/VideoGenerationSettingsCard.axaml"/> <StyleInclude Source="Controls/VideoGenerationSettingsCard.axaml"/>
<StyleInclude Source="Controls/VideoOutputSettingsCard.axaml"/> <StyleInclude Source="Controls/VideoOutputSettingsCard.axaml"/>
<StyleInclude Source="Controls/Inference/StackCard.axaml"/>
<StyleInclude Source="Controls/Inference/StackEditableCard.axaml"/>
<StyleInclude Source="Controls/Inference/StackExpander.axaml"/>
<StyleInclude Source="Controls/Inference/SeedCard.axaml"/>
<StyleInclude Source="Controls/Inference/SamplerCard.axaml"/>
<StyleInclude Source="Controls/Inference/ImageGalleryCard.axaml"/>
<StyleInclude Source="Controls/Inference/PromptCard.axaml"/>
<StyleInclude Source="Controls/Inference/UpscalerCard.axaml"/>
<StyleInclude Source="Controls/Inference/ModelCard.axaml"/>
<StyleInclude Source="Controls/Inference/BatchSizeCard.axaml"/>
<StyleInclude Source="Controls/Inference/SelectImageCard.axaml"/>
<StyleInclude Source="Controls/Inference/SharpenCard.axaml"/>
<StyleInclude Source="Controls/Inference/FreeUCard.axaml"/>
<StyleInclude Source="Controls/Inference/ControlNetCard.axaml"/>
<Style Selector="DockControl"> <Style Selector="DockControl">
<Setter Property="(DockProperties.ControlRecycling)" Value="{StaticResource ControlRecyclingKey}" /> <Setter Property="(DockProperties.ControlRecycling)" Value="{StaticResource ControlRecyclingKey}" />
</Style> </Style>

239
StabilityMatrix.Avalonia/App.axaml.cs

@ -1,7 +1,3 @@
#if DEBUG
using StabilityMatrix.Avalonia.Diagnostics.LogViewer;
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions;
#endif
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -14,6 +10,7 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
@ -64,11 +61,17 @@ using StabilityMatrix.Core.Services;
using Application = Avalonia.Application; using Application = Avalonia.Application;
using DrawingColor = System.Drawing.Color; using DrawingColor = System.Drawing.Color;
using LogLevel = Microsoft.Extensions.Logging.LogLevel; using LogLevel = Microsoft.Extensions.Logging.LogLevel;
#if DEBUG
using StabilityMatrix.Avalonia.Diagnostics.LogViewer;
using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions;
#endif
namespace StabilityMatrix.Avalonia; namespace StabilityMatrix.Avalonia;
public sealed class App : Application public sealed class App : Application
{ {
private static bool isAsyncDisposeComplete;
[NotNull] [NotNull]
public static IServiceProvider? Services { get; private set; } public static IServiceProvider? Services { get; private set; }
@ -77,8 +80,7 @@ public sealed class App : Application
public static TopLevel TopLevel => TopLevel.GetTopLevel(VisualRoot)!; public static TopLevel TopLevel => TopLevel.GetTopLevel(VisualRoot)!;
internal static bool IsHeadlessMode => internal static bool IsHeadlessMode => TopLevel.TryGetPlatformHandle()?.HandleDescriptor is null or "STUB";
TopLevel.TryGetPlatformHandle()?.HandleDescriptor is null or "STUB";
[NotNull] [NotNull]
public static IStorageProvider? StorageProvider { get; internal set; } public static IStorageProvider? StorageProvider { get; internal set; }
@ -114,7 +116,8 @@ public sealed class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
// Remove DataAnnotations validation plugin since we're using INotifyDataErrorInfo from MvvmToolkit // Remove DataAnnotations validation plugin since we're using INotifyDataErrorInfo from MvvmToolkit
var dataValidationPluginsToRemove = BindingPlugins.DataValidators var dataValidationPluginsToRemove = BindingPlugins
.DataValidators
.OfType<DataAnnotationsValidationPlugin>() .OfType<DataAnnotationsValidationPlugin>()
.ToArray(); .ToArray();
@ -158,7 +161,10 @@ public sealed class App : Application
DesktopLifetime.MainWindow = setupWindow; DesktopLifetime.MainWindow = setupWindow;
setupWindow.ShowAsyncCts.Token.Register(() => setupWindow
.ShowAsyncCts
.Token
.Register(() =>
{ {
if (setupWindow.Result == ContentDialogResult.Primary) if (setupWindow.Result == ContentDialogResult.Primary)
{ {
@ -217,25 +223,7 @@ public sealed class App : Application
mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen; mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
} }
mainWindow.Closing += (_, _) => mainWindow.Closing += OnMainWindowClosing;
{
var validWindowPosition = mainWindow.Screens.All.Any(
screen => screen.Bounds.Contains(mainWindow.Position)
);
settingsManager.Transaction(
s =>
{
s.WindowSettings = new WindowSettings(
mainWindow.Width,
mainWindow.Height,
validWindowPosition ? mainWindow.Position.X : 0,
validWindowPosition ? mainWindow.Position.Y : 0
);
},
ignoreMissingLibraryDir: true
);
};
mainWindow.Closed += (_, _) => Shutdown(); mainWindow.Closed += (_, _) => Shutdown();
mainWindow.SetDefaultFonts(); mainWindow.SetDefaultFonts();
@ -301,10 +289,7 @@ public sealed class App : Application
services.AddSingleton<IDisposable>(p => p.GetRequiredService<LaunchPageViewModel>()); services.AddSingleton<IDisposable>(p => p.GetRequiredService<LaunchPageViewModel>());
} }
internal static void ConfigureDialogViewModels( internal static void ConfigureDialogViewModels(IServiceCollection services, Type[] exportedTypes)
IServiceCollection services,
Type[] exportedTypes
)
{ {
// Dialog factory // Dialog factory
services.AddSingleton<ServiceManager<ViewModelBase>>(provider => services.AddSingleton<ServiceManager<ViewModelBase>>(provider =>
@ -312,17 +297,7 @@ public sealed class App : Application
var serviceManager = new ServiceManager<ViewModelBase>(); var serviceManager = new ServiceManager<ViewModelBase>();
var serviceManagedTypes = exportedTypes var serviceManagedTypes = exportedTypes
.Select( .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(ManagedServiceAttribute), true) })
t =>
new
{
t,
attributes = t.GetCustomAttributes(
typeof(ManagedServiceAttribute),
true
)
}
)
.Where(t1 => t1.attributes is { Length: > 0 }) .Where(t1 => t1.attributes is { Length: > 0 })
.Select(t1 => t1.t) .Select(t1 => t1.t)
.ToList(); .ToList();
@ -346,21 +321,18 @@ public sealed class App : Application
services.AddMessagePipe(); services.AddMessagePipe();
services.AddMessagePipeNamedPipeInterprocess("StabilityMatrix"); services.AddMessagePipeNamedPipeInterprocess("StabilityMatrix");
var exportedTypes = AppDomain.CurrentDomain var exportedTypes = AppDomain
.CurrentDomain
.GetAssemblies() .GetAssemblies()
.Where(a => a.FullName?.StartsWith("StabilityMatrix") == true) .Where(a => a.FullName?.StartsWith("StabilityMatrix") == true)
.SelectMany(a => a.GetExportedTypes()) .SelectMany(a => a.GetExportedTypes())
.ToArray(); .ToArray();
var transientTypes = exportedTypes var transientTypes = exportedTypes
.Select( .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(TransientAttribute), false) })
t =>
new { t, attributes = t.GetCustomAttributes(typeof(TransientAttribute), false) }
)
.Where( .Where(
t1 => t1 =>
t1.attributes is { Length: > 0 } t1.attributes is { Length: > 0 } && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
&& !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
) )
.Select(t1 => new { Type = t1.t, Attribute = (TransientAttribute)t1.attributes[0] }); .Select(t1 => new { Type = t1.t, Attribute = (TransientAttribute)t1.attributes[0] });
@ -377,23 +349,12 @@ public sealed class App : Application
} }
var singletonTypes = exportedTypes var singletonTypes = exportedTypes
.Select( .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(SingletonAttribute), false) })
t =>
new { t, attributes = t.GetCustomAttributes(typeof(SingletonAttribute), false) }
)
.Where( .Where(
t1 => t1 =>
t1.attributes is { Length: > 0 } t1.attributes is { Length: > 0 } && !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
&& !t1.t.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)
) )
.Select( .Select(t1 => new { Type = t1.t, Attributes = t1.attributes.Cast<SingletonAttribute>().ToArray() });
t1 =>
new
{
Type = t1.t,
Attributes = t1.attributes.Cast<SingletonAttribute>().ToArray()
}
);
foreach (var typePair in singletonTypes) foreach (var typePair in singletonTypes)
{ {
@ -425,9 +386,7 @@ public sealed class App : Application
// Rich presence // Rich presence
services.AddSingleton<IDiscordRichPresenceService, DiscordRichPresenceService>(); services.AddSingleton<IDiscordRichPresenceService, DiscordRichPresenceService>();
services.AddSingleton<IDisposable>( services.AddSingleton<IDisposable>(provider => provider.GetRequiredService<IDiscordRichPresenceService>());
provider => provider.GetRequiredService<IDiscordRichPresenceService>()
);
Config = new ConfigurationBuilder() Config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) .SetBasePath(Directory.GetCurrentDirectory())
@ -473,9 +432,7 @@ public sealed class App : Application
jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitFileType>()); jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitFileType>());
jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelType>()); jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelType>());
jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelFormat>()); jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelFormat>());
jsonSerializerOptions.Converters.Add( jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
);
jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
var defaultRefitSettings = new RefitSettings var defaultRefitSettings = new RefitSettings
@ -484,8 +441,7 @@ public sealed class App : Application
}; };
// Refit settings for IApiFactory // Refit settings for IApiFactory
var defaultSystemTextJsonSettings = var defaultSystemTextJsonSettings = SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions();
SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions();
defaultSystemTextJsonSettings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; defaultSystemTextJsonSettings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
var apiFactoryRefitSettings = new RefitSettings var apiFactoryRefitSettings = new RefitSettings
{ {
@ -559,27 +515,19 @@ public sealed class App : Application
c.BaseAddress = new Uri("https://stableauthentication.azurewebsites.net"); c.BaseAddress = new Uri("https://stableauthentication.azurewebsites.net");
c.Timeout = TimeSpan.FromSeconds(15); c.Timeout = TimeSpan.FromSeconds(15);
}) })
.ConfigurePrimaryHttpMessageHandler( .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })
() => new HttpClientHandler { AllowAutoRedirect = false }
)
.AddPolicyHandler(retryPolicy) .AddPolicyHandler(retryPolicy)
.AddHttpMessageHandler( .AddHttpMessageHandler(
serviceProvider => serviceProvider =>
new TokenAuthHeaderHandler( new TokenAuthHeaderHandler(serviceProvider.GetRequiredService<LykosAuthTokenProvider>())
serviceProvider.GetRequiredService<LykosAuthTokenProvider>()
)
); );
// Add Refit client managers // Add Refit client managers
services services.AddHttpClient("A3Client").AddPolicyHandler(localTimeout.WrapAsync(localRetryPolicy));
.AddHttpClient("A3Client")
.AddPolicyHandler(localTimeout.WrapAsync(localRetryPolicy));
services services
.AddHttpClient("DontFollowRedirects") .AddHttpClient("DontFollowRedirects")
.ConfigurePrimaryHttpMessageHandler( .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })
() => new HttpClientHandler { AllowAutoRedirect = false }
)
.AddPolicyHandler(retryPolicy); .AddPolicyHandler(retryPolicy);
/*services.AddHttpClient("IComfyApi") /*services.AddHttpClient("IComfyApi")
@ -609,11 +557,7 @@ public sealed class App : Application
#if DEBUG #if DEBUG
builder.AddNLog( builder.AddNLog(
ConfigureLogging(), ConfigureLogging(),
new NLogProviderOptions new NLogProviderOptions { IgnoreEmptyEventId = false, CaptureEventId = EventIdCaptureType.Legacy }
{
IgnoreEmptyEventId = false,
CaptureEventId = EventIdCaptureType.Legacy
}
); );
#else #else
builder.AddNLog(ConfigureLogging()); builder.AddNLog(ConfigureLogging());
@ -639,6 +583,87 @@ public sealed class App : Application
} }
} }
/// <summary>
/// Handle shutdown requests (happens before <see cref="OnExit"/>)
/// </summary>
private static void OnMainWindowClosing(object? sender, WindowClosingEventArgs e)
{
if (e.Cancel)
return;
var mainWindow = (MainWindow)sender!;
// Show confirmation if package running
var launchPageViewModel = Services.GetRequiredService<LaunchPageViewModel>();
launchPageViewModel.OnMainWindowClosing(e);
if (e.Cancel)
return;
// Check if we need to dispose IAsyncDisposables
if (
!isAsyncDisposeComplete
&& Services.GetServices<IAsyncDisposable>().ToList() is { Count: > 0 } asyncDisposables
)
{
// Cancel shutdown for now
e.Cancel = true;
isAsyncDisposeComplete = true;
Debug.WriteLine("OnShutdownRequested Canceled: Disposing IAsyncDisposables");
Task.Run(async () =>
{
foreach (var disposable in asyncDisposables)
{
Debug.WriteLine($"Disposing IAsyncDisposable ({disposable.GetType().Name})");
try
{
await disposable.DisposeAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Debug.Fail(ex.ToString());
}
}
})
.ContinueWith(_ =>
{
// Shutdown again
Dispatcher.UIThread.Invoke(() => Shutdown());
})
.SafeFireAndForget();
return;
}
OnMainWindowClosingTerminal(mainWindow);
}
/// <summary>
/// Called at the end of <see cref="OnMainWindowClosing"/> before the main window is closed.
/// </summary>
private static void OnMainWindowClosingTerminal(Window sender)
{
var settingsManager = Services.GetRequiredService<ISettingsManager>();
// Save window position
var validWindowPosition = sender.Screens.All.Any(screen => screen.Bounds.Contains(sender.Position));
settingsManager.Transaction(
s =>
{
s.WindowSettings = new WindowSettings(
sender.Width,
sender.Height,
validWindowPosition ? sender.Position.X : 0,
validWindowPosition ? sender.Position.Y : 0
);
},
ignoreMissingLibraryDir: true
);
}
private static void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs args) private static void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs args)
{ {
Debug.WriteLine("Start OnExit"); Debug.WriteLine("Start OnExit");
@ -646,16 +671,15 @@ public sealed class App : Application
var settingsManager = Services.GetRequiredService<ISettingsManager>(); var settingsManager = Services.GetRequiredService<ISettingsManager>();
// If RemoveFolderLinksOnShutdown is set, delete all package junctions // If RemoveFolderLinksOnShutdown is set, delete all package junctions
if ( if (settingsManager is { IsLibraryDirSet: true, Settings.RemoveFolderLinksOnShutdown: true })
settingsManager is { IsLibraryDirSet: true, Settings.RemoveFolderLinksOnShutdown: true }
)
{ {
var sharedFolders = Services.GetRequiredService<ISharedFolders>(); var sharedFolders = Services.GetRequiredService<ISharedFolders>();
sharedFolders.RemoveLinksForAllPackages(); sharedFolders.RemoveLinksForAllPackages();
} }
Debug.WriteLine("Start OnExit: Disposing services"); Debug.WriteLine("Start OnExit: Disposing services");
// Dispose all services
// Dispose IDisposable services
foreach (var disposable in Services.GetServices<IDisposable>()) foreach (var disposable in Services.GetServices<IDisposable>())
{ {
Debug.WriteLine($"Disposing {disposable.GetType().Name}"); Debug.WriteLine($"Disposing {disposable.GetType().Name}");
@ -690,13 +714,10 @@ public sealed class App : Application
.WriteTo( .WriteTo(
new FileTarget new FileTarget
{ {
Layout = Layout = "${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}",
"${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}",
ArchiveOldFileOnStartup = true, ArchiveOldFileOnStartup = true,
FileName = FileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.log",
"${specialfolder:folder=ApplicationData}/StabilityMatrix/app.log", ArchiveFileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/app.{#}.log",
ArchiveFileName =
"${specialfolder:folder=ApplicationData}/StabilityMatrix/app.{#}.log",
ArchiveNumbering = ArchiveNumberingMode.Rolling, ArchiveNumbering = ArchiveNumberingMode.Rolling,
MaxArchiveFiles = 2 MaxArchiveFiles = 2
} }
@ -709,8 +730,11 @@ public sealed class App : Application
builder.ForLogger("Microsoft.Extensions.Http.*").WriteToNil(NLog.LogLevel.Warn); builder.ForLogger("Microsoft.Extensions.Http.*").WriteToNil(NLog.LogLevel.Warn);
// Disable console trace logging by default // Disable console trace logging by default
builder.ForLogger("StabilityMatrix.Avalonia.ViewModels.ConsoleViewModel").WriteToNil(NLog.LogLevel.Debug);
// Disable LoadableViewModelBase trace logging by default
builder builder
.ForLogger("StabilityMatrix.Avalonia.ViewModels.ConsoleViewModel") .ForLogger("StabilityMatrix.Avalonia.ViewModels.Base.LoadableViewModelBase")
.WriteToNil(NLog.LogLevel.Debug); .WriteToNil(NLog.LogLevel.Debug);
builder.ForLogger().FilterMinLevel(NLog.LogLevel.Trace).WriteTo(debugTarget); builder.ForLogger().FilterMinLevel(NLog.LogLevel.Trace).WriteTo(debugTarget);
@ -727,7 +751,9 @@ public sealed class App : Application
// Sentry // Sentry
if (SentrySdk.IsEnabled) if (SentrySdk.IsEnabled)
{ {
LogManager.Configuration.AddSentry(o => LogManager
.Configuration
.AddSentry(o =>
{ {
o.InitializeSdk = false; o.InitializeSdk = false;
o.Layout = "${message}"; o.Layout = "${message}";
@ -777,14 +803,12 @@ public sealed class App : Application
results.Add(ms); results.Add(ms);
} }
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher
.UIThread
.InvokeAsync(async () =>
{ {
var dest = await StorageProvider.SaveFilePickerAsync( var dest = await StorageProvider.SaveFilePickerAsync(
new FilePickerSaveOptions() new FilePickerSaveOptions() { SuggestedFileName = "screenshot.png", ShowOverwritePrompt = true }
{
SuggestedFileName = "screenshot.png",
ShowOverwritePrompt = true
}
); );
if (dest?.TryGetLocalPath() is { } localPath) if (dest?.TryGetLocalPath() is { } localPath)
@ -822,8 +846,7 @@ public sealed class App : Application
{ {
#if DEBUG #if DEBUG
setupBuilder.SetupExtensions( setupBuilder.SetupExtensions(
extensionBuilder => extensionBuilder => extensionBuilder.RegisterTarget<DataStoreLoggerTarget>("DataStoreLogger")
extensionBuilder.RegisterTarget<DataStoreLoggerTarget>("DataStoreLogger")
); );
#endif #endif
} }

49
StabilityMatrix.Avalonia/Assets.cs

@ -11,20 +11,16 @@ namespace StabilityMatrix.Avalonia;
internal static class Assets internal static class Assets
{ {
public static AvaloniaResource AppIcon { get; } = public static AvaloniaResource AppIcon { get; } = new("avares://StabilityMatrix.Avalonia/Assets/Icon.ico");
new("avares://StabilityMatrix.Avalonia/Assets/Icon.ico");
public static AvaloniaResource AppIconPng { get; } = public static AvaloniaResource AppIconPng { get; } = new("avares://StabilityMatrix.Avalonia/Assets/Icon.png");
new("avares://StabilityMatrix.Avalonia/Assets/Icon.png");
/// <summary> /// <summary>
/// Fixed image for models with no images. /// Fixed image for models with no images.
/// </summary> /// </summary>
public static Uri NoImage { get; } = public static Uri NoImage { get; } = new("avares://StabilityMatrix.Avalonia/Assets/noimage.png");
new("avares://StabilityMatrix.Avalonia/Assets/noimage.png");
public static AvaloniaResource LicensesJson => public static AvaloniaResource LicensesJson => new("avares://StabilityMatrix.Avalonia/Assets/licenses.json");
new("avares://StabilityMatrix.Avalonia/Assets/licenses.json");
public static AvaloniaResource ImagePromptLanguageJson => public static AvaloniaResource ImagePromptLanguageJson =>
new("avares://StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json"); new("avares://StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json");
@ -32,6 +28,8 @@ internal static class Assets
public static AvaloniaResource ThemeMatrixDarkJson => public static AvaloniaResource ThemeMatrixDarkJson =>
new("avares://StabilityMatrix.Avalonia/Assets/ThemeMatrixDark.json"); new("avares://StabilityMatrix.Avalonia/Assets/ThemeMatrixDark.json");
public static AvaloniaResource HfPackagesJson => new("avares://StabilityMatrix.Avalonia/Assets/hf-packages.json");
private const UnixFileMode Unix755 = private const UnixFileMode Unix755 =
UnixFileMode.UserRead UnixFileMode.UserRead
| UnixFileMode.UserWrite | UnixFileMode.UserWrite
@ -46,23 +44,14 @@ internal static class Assets
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
public static AvaloniaResource SevenZipExecutable => public static AvaloniaResource SevenZipExecutable =>
Compat.Switch( Compat.Switch(
( (PlatformKind.Windows, new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/win-x64/7za.exe")),
PlatformKind.Windows,
new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/win-x64/7za.exe")
),
( (
PlatformKind.Linux | PlatformKind.X64, PlatformKind.Linux | PlatformKind.X64,
new AvaloniaResource( new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs", Unix755)
"avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs",
Unix755
)
), ),
( (
PlatformKind.MacOS | PlatformKind.Arm, PlatformKind.MacOS | PlatformKind.Arm,
new AvaloniaResource( new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/macos-arm64/7zz", Unix755)
"avares://StabilityMatrix.Avalonia/Assets/macos-arm64/7zz",
Unix755
)
) )
); );
@ -73,21 +62,15 @@ internal static class Assets
Compat.Switch( Compat.Switch(
( (
PlatformKind.Windows, PlatformKind.Windows,
new AvaloniaResource( new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/win-x64/7za - LICENSE.txt")
"avares://StabilityMatrix.Avalonia/Assets/win-x64/7za - LICENSE.txt"
)
), ),
( (
PlatformKind.Linux | PlatformKind.X64, PlatformKind.Linux | PlatformKind.X64,
new AvaloniaResource( new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs - LICENSE.txt")
"avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs - LICENSE.txt"
)
), ),
( (
PlatformKind.MacOS | PlatformKind.Arm, PlatformKind.MacOS | PlatformKind.Arm,
new AvaloniaResource( new AvaloniaResource("avares://StabilityMatrix.Avalonia/Assets/macos-arm64/7zz - LICENSE.txt")
"avares://StabilityMatrix.Avalonia/Assets/macos-arm64/7zz - LICENSE.txt"
)
) )
); );
@ -111,9 +94,7 @@ internal static class Assets
PlatformKind.Windows | PlatformKind.X64, PlatformKind.Windows | PlatformKind.X64,
new RemoteResource new RemoteResource
{ {
Url = new Uri( Url = new Uri("https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip"),
"https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip"
),
HashSha256 = "608619f8619075629c9c69f361352a0da6ed7e62f83a0e19c63e0ea32eb7629d" HashSha256 = "608619f8619075629c9c69f361352a0da6ed7e62f83a0e19c63e0ea32eb7629d"
} }
), ),
@ -167,9 +148,7 @@ internal static class Assets
/// <summary> /// <summary>
/// Yield AvaloniaResources given a relative directory path within the 'Assets' folder. /// Yield AvaloniaResources given a relative directory path within the 'Assets' folder.
/// </summary> /// </summary>
public static IEnumerable<(AvaloniaResource resource, string relativePath)> FindAssets( public static IEnumerable<(AvaloniaResource resource, string relativePath)> FindAssets(string relativeAssetPath)
string relativeAssetPath
)
{ {
var baseUri = new Uri("avares://StabilityMatrix.Avalonia/Assets/"); var baseUri = new Uri("avares://StabilityMatrix.Avalonia/Assets/");
var targetUri = new Uri(baseUri, relativeAssetPath); var targetUri = new Uri(baseUri, relativeAssetPath);

544
StabilityMatrix.Avalonia/Assets/hf-packages.json

@ -0,0 +1,544 @@
[
{
"ModelCategory": "BaseModel",
"ModelName": "Stable Diffusion 1.5",
"RepositoryPath": "runwayml/stable-diffusion-v1-5",
"Files": [
"v1-5-pruned-emaonly.safetensors"
],
"LicenseType": "CreativeML Open RAIL-M"
},
{
"ModelCategory": "BaseModel",
"ModelName": "Stable Diffusion 2.1",
"RepositoryPath": "stabilityai/stable-diffusion-2-1",
"Files": [
"v2-1_768-ema-pruned.safetensors"
],
"LicenseType": "Open RAIL++"
},
{
"ModelCategory": "BaseModel",
"ModelName": "Stable Diffusion XL (Base)",
"RepositoryPath": "stabilityai/stable-diffusion-xl-base-1.0",
"Files": [
"sd_xl_base_1.0_0.9vae.safetensors",
"sd_xl_offset_example-lora_1.0.safetensors"
],
"LicenseType": "Open RAIL++",
"LicensePath": "LICENSE.md"
},
{
"ModelCategory": "BaseModel",
"ModelName": "Stable Diffusion XL (Refiner)",
"RepositoryPath": "stabilityai/stable-diffusion-xl-refiner-1.0",
"Files": [
"sd_xl_refiner_1.0_0.9vae.safetensors"
],
"LicenseType": "Open RAIL++",
"LicensePath": "LICENSE.md"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Canny",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_canny.pth",
"control_v11p_sd15_canny.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Depth",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11f1p_sd15_depth.pth",
"control_v11f1p_sd15_depth.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "MLSD",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_mlsd.pth",
"control_v11p_sd15_mlsd.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Inpaint",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_inpaint.pth",
"control_v11p_sd15_inpaint.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "IP2P",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11e_sd15_ip2p.pth",
"control_v11e_sd15_ip2p.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Tile",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11f1e_sd15_tile.pth",
"control_v11f1e_sd15_tile.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "OpenPose",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_openpose.pth",
"control_v11p_sd15_openpose.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "LineArt",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_lineart.pth",
"control_v11p_sd15_lineart.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "LineArt Anime",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15s2_lineart_anime.pth",
"control_v11p_sd15s2_lineart_anime.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "NormalBae",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_normalbae.pth",
"control_v11p_sd15_normalbae.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Seg",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_seg.pth",
"control_v11p_sd15_seg.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Soft Edge",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_softedge.pth",
"control_v11p_sd15_softedge.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Scribble",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11p_sd15_scribble.pth",
"control_v11p_sd15_scribble.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "ControlNet",
"ModelName": "Shuffle",
"RepositoryPath": "lllyasviel/ControlNet-v1-1",
"Files": [
"control_v11e_sd15_shuffle.pth",
"control_v11e_sd15_shuffle.yaml"
],
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Canny",
"RepositoryPath": "lllyasviel/control_v11p_sd15_canny",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "canny",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Depth",
"RepositoryPath": "lllyasviel/control_v11f1p_sd15_depth",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "depth",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "MLSD",
"RepositoryPath": "lllyasviel/control_v11p_sd15_mlsd",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "MLSD",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Inpaint",
"RepositoryPath": "lllyasviel/control_v11p_sd15_inpaint",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "inpaint",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "IP2P",
"RepositoryPath": "lllyasviel/control_v11e_sd15_ip2p",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "ip2p",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Tile",
"RepositoryPath": "lllyasviel/control_v11f1e_sd15_tile",
"Files": [
"diffusion_pytorch_model.bin",
"config.json"
],
"Subfolder": "tile",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "OpenPose",
"RepositoryPath": "lllyasviel/control_v11p_sd15_openpose",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "openpose",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "LineArt",
"RepositoryPath": "lllyasviel/control_v11p_sd15_lineart",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "lineart",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "LineArt Anime",
"RepositoryPath": "lllyasviel/control_v11p_sd15s2_lineart_anime",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "lineart_anime",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "NormalBae",
"RepositoryPath": "lllyasviel/control_v11p_sd15_normalbae",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "normalbae",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Seg",
"RepositoryPath": "lllyasviel/control_v11p_sd15_seg",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "seg",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Soft Edge",
"RepositoryPath": "lllyasviel/control_v11p_sd15_softedge",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "softedge",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Scribble",
"RepositoryPath": "lllyasviel/control_v11p_sd15_scribble",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "scribble",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersControlNet",
"ModelName": "Shuffle",
"RepositoryPath": "lllyasviel/control_v11e_sd15_shuffle",
"Files": [
"diffusion_pytorch_model.safetensors",
"config.json"
],
"Subfolder": "shuffle",
"LicenseType": "Open RAIL"
},
{
"ModelCategory": "DiffusersClipVision",
"ModelName": "IP Adapter Encoder",
"RepositoryPath": "InvokeAI/ip_adapter_sd_image_encoder",
"Files": [
"config.json",
"model.safetensors"
],
"Subfolder": "ip_adapter_sd_image_encoder",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersClipVision",
"ModelName": "IP Adapter Encoder (SDXL)",
"RepositoryPath": "InvokeAI/ip_adapter_sdxl_image_encoder",
"Files": [
"config.json",
"model.safetensors"
],
"Subfolder": "ip_adapter_sd_image_encoder",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersIpAdapter",
"ModelName": "SD 1.5 Adapter",
"RepositoryPath": "InvokeAI/ip_adapter_sd15",
"Files": [
"image_encoder.txt",
"ip_adapter.bin"
],
"Subfolder": "ip_adapter_sd15",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersIpAdapter",
"ModelName": "SD 1.5 Light Adapter",
"RepositoryPath": "InvokeAI/ip_adapter_sd15_light",
"Files": [
"image_encoder.txt",
"ip_adapter.bin"
],
"Subfolder": "ip_adapter_sd15_light",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersIpAdapter",
"ModelName": "SD 1.5 Plus Adapter",
"RepositoryPath": "InvokeAI/ip_adapter_plus_sd15",
"Files": [
"image_encoder.txt",
"ip_adapter.bin"
],
"Subfolder": "ip_adapter_plus_sd15",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersIpAdapter",
"ModelName": "SD 1.5 Face Plus Adapter",
"RepositoryPath": "InvokeAI/ip_adapter_plus_face_sd15",
"Files": [
"image_encoder.txt",
"ip_adapter.bin"
],
"Subfolder": "ip_adapter_plus_face_sd15",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersIpAdapterXl",
"ModelName": "SDXL Adapter",
"RepositoryPath": "InvokeAI/ip_adapter_sdxl",
"Files": [
"image_encoder.txt",
"ip_adapter.bin"
],
"Subfolder": "ip_adapter_sdxl",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersIpAdapterXl",
"ModelName": "SDXL Plus Adapter",
"RepositoryPath": "InvokeAI/ip-adapter-plus_sdxl_vit-h",
"Files": [
"image_encoder.txt",
"ip_adapter.bin"
],
"Subfolder": "ip_adapter_plus_sdxl_vit-h",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersIpAdapterXl",
"ModelName": "SDXL Face Plus Adapter",
"RepositoryPath": "InvokeAI/ip-adapter-plus-face_sdxl_vit-h",
"Files": [
"image_encoder.txt",
"ip_adapter.bin"
],
"Subfolder": "ip_adapter_plus_face_sdxl_vit-h",
"LicenseType": "CreativeML Open RAIL-M"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Sketch",
"RepositoryPath": "TencentARC/t2iadapter_sketch_sd15v2",
"Files": [
"config.json",
"diffusion_pytorch_model.bin"
],
"Subfolder": "t2iadapter_sketch_sd15v2",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Depth",
"RepositoryPath": "TencentARC/t2iadapter_depth_sd15v2",
"Files": [
"config.json",
"diffusion_pytorch_model.bin"
],
"Subfolder": "t2iadapter_depth_sd15v2",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Canny",
"RepositoryPath": "TencentARC/t2iadapter_canny_sd15v2",
"Files": [
"config.json",
"diffusion_pytorch_model.bin"
],
"Subfolder": "t2iadapter_canny_sd15v2",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Depth-Zoe",
"RepositoryPath": "TencentARC/t2iadapter_zoedepth_sd15v1",
"Files": [
"config.json",
"diffusion_pytorch_model.bin"
],
"Subfolder": "t2iadapter_zoedepth_sd15v1",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Sketch (SDXL)",
"RepositoryPath": "TencentARC/t2i-adapter-sketch-sdxl-1.0",
"Files": [
"config.json",
"diffusion_pytorch_model.safetensors"
],
"Subfolder": "t2i-adapter-sketch-sdxl-1.0",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Depth-Zoe (SDXL)",
"RepositoryPath": "TencentARC/t2i-adapter-depth-zoe-sdxl-1.0",
"Files": [
"config.json",
"diffusion_pytorch_model.safetensors"
],
"Subfolder": "t2i-adapter-depth-zoe-sdxl-1.0",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "OpenPose (SDXL)",
"RepositoryPath": "TencentARC/t2i-adapter-openpose-sdxl-1.0",
"Files": [
"config.json",
"diffusion_pytorch_model.safetensors"
],
"Subfolder": "t2i-adapter-openpose-sdxl-1.0",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Depth-MiDaS (SDXL)",
"RepositoryPath": "TencentARC/t2i-adapter-depth-midas-sdxl-1.0",
"Files": [
"config.json",
"diffusion_pytorch_model.safetensors"
],
"Subfolder": "t2i-adapter-depth-midas-sdxl-1.0",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "LineArt (SDXL)",
"RepositoryPath": "TencentARC/t2i-adapter-lineart-sdxl-1.0",
"Files": [
"config.json",
"diffusion_pytorch_model.safetensors"
],
"Subfolder": "t2i-adapter-lineart-sdxl-1.0",
"LicenseType": "Apache 2.0"
},
{
"ModelCategory": "DiffusersT2IAdapter",
"ModelName": "Canny (SDXL)",
"RepositoryPath": "TencentARC/t2i-adapter-canny-sdxl-1.0",
"Files": [
"config.json",
"diffusion_pytorch_model.safetensors"
],
"Subfolder": "t2i-adapter-canny-sdxl-1.0",
"LicenseType": "Apache 2.0"
}
]

BIN
StabilityMatrix.Avalonia/Assets/santahat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

34
StabilityMatrix.Avalonia/Assets/sitecustomize.py

@ -46,7 +46,41 @@ def audit(event: str, *args):
# Reconfigure stdout to UTF-8 # Reconfigure stdout to UTF-8
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
sys.stdin.reconfigure(encoding="utf-8")
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
# Install the audit hook # Install the audit hook
sys.addaudithook(audit) sys.addaudithook(audit)
# Patch Rich terminal detection
def _patch_rich_console():
try:
from rich import console
class _Console(console.Console):
@property
def is_terminal(self) -> bool:
return True
console.Console = _Console
except ImportError:
pass
except Exception as e:
print("[sitecustomize error]:", e)
_patch_rich_console()
# Patch tqdm to use stdout instead of stderr
def _patch_tqdm():
try:
import sys
from tqdm import std
sys.stderr = sys.stdout
except ImportError:
pass
except Exception as e:
print("[sitecustomize error]:", e)
_patch_tqdm()

304
StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml.cs

@ -54,11 +54,7 @@ public class AdvancedImageBox : TemplatedControl
remove => _propertyChanged -= value; remove => _propertyChanged -= value;
} }
protected bool RaiseAndSetIfChanged<T>( protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
ref T field,
T value,
[CallerMemberName] string? propertyName = null
)
{ {
if (EqualityComparer<T>.Default.Equals(field, value)) if (EqualityComparer<T>.Default.Equals(field, value))
return false; return false;
@ -335,9 +331,7 @@ public class AdvancedImageBox : TemplatedControl
if (index < Count - 1) if (index < Count - 1)
index++; index++;
return constrainZoomLevel > 0 && this[index] >= constrainZoomLevel return constrainZoomLevel > 0 && this[index] >= constrainZoomLevel ? constrainZoomLevel : this[index];
? constrainZoomLevel
: this[index];
} }
/// <summary> /// <summary>
@ -352,9 +346,7 @@ public class AdvancedImageBox : TemplatedControl
if (index > 0) if (index > 0)
index--; index--;
return constrainZoomLevel > 0 && this[index] <= constrainZoomLevel return constrainZoomLevel > 0 && this[index] <= constrainZoomLevel ? constrainZoomLevel : this[index];
? constrainZoomLevel
: this[index];
} }
/// <summary> /// <summary>
@ -525,11 +517,10 @@ public class AdvancedImageBox : TemplatedControl
#endregion #endregion
#region Properties #region Properties
public static readonly DirectProperty<AdvancedImageBox, bool> CanRenderProperty = public static readonly DirectProperty<AdvancedImageBox, bool> CanRenderProperty = AvaloniaProperty.RegisterDirect<
AvaloniaProperty.RegisterDirect<AdvancedImageBox, bool>( AdvancedImageBox,
nameof(CanRender), bool
o => o.CanRender >(nameof(CanRender), o => o.CanRender);
);
/// <summary> /// <summary>
/// Gets or sets if control can render the image /// Gets or sets if control can render the image
@ -560,11 +551,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(GridCellSizeProperty, value); set => SetValue(GridCellSizeProperty, value);
} }
public static readonly StyledProperty<ISolidColorBrush> GridColorProperty = public static readonly StyledProperty<ISolidColorBrush> GridColorProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>( AdvancedImageBox,
nameof(GridColor), ISolidColorBrush
SolidColorBrush.Parse("#181818") >(nameof(GridColor), SolidColorBrush.Parse("#181818"));
);
/// <summary> /// <summary>
/// Gets or sets the color used to create the checkerboard style background /// Gets or sets the color used to create the checkerboard style background
@ -575,11 +565,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(GridColorProperty, value); set => SetValue(GridColorProperty, value);
} }
public static readonly StyledProperty<ISolidColorBrush> GridColorAlternateProperty = public static readonly StyledProperty<ISolidColorBrush> GridColorAlternateProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>( AdvancedImageBox,
nameof(GridColorAlternate), ISolidColorBrush
SolidColorBrush.Parse("#252525") >(nameof(GridColorAlternate), SolidColorBrush.Parse("#252525"));
);
/// <summary> /// <summary>
/// Gets or sets the color used to create the checkerboard style background /// Gets or sets the color used to create the checkerboard style background
@ -606,7 +595,8 @@ public class AdvancedImageBox : TemplatedControl
{ {
var loader = ImageLoader.AsyncImageLoader; var loader = ImageLoader.AsyncImageLoader;
Dispatcher.UIThread Dispatcher
.UIThread
.InvokeAsync(async () => .InvokeAsync(async () =>
{ {
Image = await loader.ProvideImageAsync(value); Image = await loader.ProvideImageAsync(value);
@ -616,10 +606,9 @@ public class AdvancedImageBox : TemplatedControl
} }
} }
public static readonly StyledProperty<Bitmap?> ImageProperty = AvaloniaProperty.Register< public static readonly StyledProperty<Bitmap?> ImageProperty = AvaloniaProperty.Register<AdvancedImageBox, Bitmap?>(
AdvancedImageBox, nameof(Image)
Bitmap? );
>(nameof(Image));
/// <summary> /// <summary>
/// Gets or sets the image to be displayed /// Gets or sets the image to be displayed
@ -656,8 +645,7 @@ public class AdvancedImageBox : TemplatedControl
{ {
offsetBackup = Offset; offsetBackup = Offset;
var zoomFactorScale = var zoomFactorScale = (float)GetZoomLevelToFit(newImage) / GetZoomLevelToFit(oldImage);
(float)GetZoomLevelToFit(newImage) / GetZoomLevelToFit(oldImage);
var imageScale = newImage.Size / oldImage.Size; var imageScale = newImage.Size / oldImage.Size;
Debug.WriteLine($"Image scale: {imageScale}"); Debug.WriteLine($"Image scale: {imageScale}");
@ -731,8 +719,10 @@ public class AdvancedImageBox : TemplatedControl
public bool HaveTrackerImage => _trackerImage is not null; public bool HaveTrackerImage => _trackerImage is not null;
public static readonly StyledProperty<bool> TrackerImageAutoZoomProperty = public static readonly StyledProperty<bool> TrackerImageAutoZoomProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, bool>(nameof(TrackerImageAutoZoom), true); AdvancedImageBox,
bool
>(nameof(TrackerImageAutoZoom), true);
/// <summary> /// <summary>
/// Gets or sets if the tracker image will be scaled to the current zoom /// Gets or sets if the tracker image will be scaled to the current zoom
@ -743,8 +733,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(TrackerImageAutoZoomProperty, value); set => SetValue(TrackerImageAutoZoomProperty, value);
} }
public static readonly StyledProperty<bool> IsTrackerImageEnabledProperty = public static readonly StyledProperty<bool> IsTrackerImageEnabledProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, bool>("IsTrackerImageEnabled"); AdvancedImageBox,
bool
>("IsTrackerImageEnabled");
public bool IsTrackerImageEnabled public bool IsTrackerImageEnabled
{ {
@ -776,10 +768,10 @@ public class AdvancedImageBox : TemplatedControl
} }
} }
public static readonly StyledProperty<bool> ShowGridProperty = AvaloniaProperty.Register< public static readonly StyledProperty<bool> ShowGridProperty = AvaloniaProperty.Register<AdvancedImageBox, bool>(
AdvancedImageBox, nameof(ShowGrid),
bool true
>(nameof(ShowGrid), true); );
/// <summary> /// <summary>
/// Gets or sets the grid visibility when reach high zoom levels /// Gets or sets the grid visibility when reach high zoom levels
@ -791,10 +783,7 @@ public class AdvancedImageBox : TemplatedControl
} }
public static readonly DirectProperty<AdvancedImageBox, Point> PointerPositionProperty = public static readonly DirectProperty<AdvancedImageBox, Point> PointerPositionProperty =
AvaloniaProperty.RegisterDirect<AdvancedImageBox, Point>( AvaloniaProperty.RegisterDirect<AdvancedImageBox, Point>(nameof(PointerPosition), o => o.PointerPosition);
nameof(PointerPosition),
o => o.PointerPosition
);
/// <summary> /// <summary>
/// Gets the current pointer position /// Gets the current pointer position
@ -805,11 +794,10 @@ public class AdvancedImageBox : TemplatedControl
private set => SetAndRaise(PointerPositionProperty, ref _pointerPosition, value); private set => SetAndRaise(PointerPositionProperty, ref _pointerPosition, value);
} }
public static readonly DirectProperty<AdvancedImageBox, bool> IsPanningProperty = public static readonly DirectProperty<AdvancedImageBox, bool> IsPanningProperty = AvaloniaProperty.RegisterDirect<
AvaloniaProperty.RegisterDirect<AdvancedImageBox, bool>( AdvancedImageBox,
nameof(IsPanning), bool
o => o.IsPanning >(nameof(IsPanning), o => o.IsPanning);
);
/// <summary> /// <summary>
/// Gets if control is currently panning /// Gets if control is currently panning
@ -836,11 +824,10 @@ public class AdvancedImageBox : TemplatedControl
} }
} }
public static readonly DirectProperty<AdvancedImageBox, bool> IsSelectingProperty = public static readonly DirectProperty<AdvancedImageBox, bool> IsSelectingProperty = AvaloniaProperty.RegisterDirect<
AvaloniaProperty.RegisterDirect<AdvancedImageBox, bool>( AdvancedImageBox,
nameof(IsSelecting), bool
o => o.IsSelecting >(nameof(IsSelecting), o => o.IsSelecting);
);
/// <summary> /// <summary>
/// Gets if control is currently selecting a ROI /// Gets if control is currently selecting a ROI
@ -863,10 +850,10 @@ public class AdvancedImageBox : TemplatedControl
} }
} }
public static readonly StyledProperty<bool> AutoPanProperty = AvaloniaProperty.Register< public static readonly StyledProperty<bool> AutoPanProperty = AvaloniaProperty.Register<AdvancedImageBox, bool>(
AdvancedImageBox, nameof(AutoPan),
bool true
>(nameof(AutoPan), true); );
/// <summary> /// <summary>
/// Gets or sets if the control can pan with the mouse /// Gets or sets if the control can pan with the mouse
@ -877,11 +864,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(AutoPanProperty, value); set => SetValue(AutoPanProperty, value);
} }
public static readonly StyledProperty<MouseButtons> PanWithMouseButtonsProperty = public static readonly StyledProperty<MouseButtons> PanWithMouseButtonsProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, MouseButtons>( AdvancedImageBox,
nameof(PanWithMouseButtons), MouseButtons
MouseButtons.LeftButton | MouseButtons.MiddleButton >(nameof(PanWithMouseButtons), MouseButtons.LeftButton | MouseButtons.MiddleButton);
);
/// <summary> /// <summary>
/// Gets or sets the mouse buttons to pan the image /// Gets or sets the mouse buttons to pan the image
@ -906,11 +892,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(PanWithArrowsProperty, value); set => SetValue(PanWithArrowsProperty, value);
} }
public static readonly StyledProperty<MouseButtons> SelectWithMouseButtonsProperty = public static readonly StyledProperty<MouseButtons> SelectWithMouseButtonsProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, MouseButtons>( AdvancedImageBox,
nameof(SelectWithMouseButtons), MouseButtons
MouseButtons.LeftButton >(nameof(SelectWithMouseButtons), MouseButtons.LeftButton);
);
/// <summary> /// <summary>
/// Gets or sets the mouse buttons to select a region on image /// Gets or sets the mouse buttons to select a region on image
@ -935,10 +920,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(InvertMousePanProperty, value); set => SetValue(InvertMousePanProperty, value);
} }
public static readonly StyledProperty<bool> AutoCenterProperty = AvaloniaProperty.Register< public static readonly StyledProperty<bool> AutoCenterProperty = AvaloniaProperty.Register<AdvancedImageBox, bool>(
AdvancedImageBox, nameof(AutoCenter),
bool true
>(nameof(AutoCenter), true); );
/// <summary> /// <summary>
/// Gets or sets if image is auto centered /// Gets or sets if image is auto centered
@ -993,10 +978,10 @@ public class AdvancedImageBox : TemplatedControl
} }
} }
public static readonly StyledProperty<bool> AllowZoomProperty = AvaloniaProperty.Register< public static readonly StyledProperty<bool> AllowZoomProperty = AvaloniaProperty.Register<AdvancedImageBox, bool>(
AdvancedImageBox, nameof(AllowZoom),
bool true
>(nameof(AllowZoom), true); );
/// <summary> /// <summary>
/// Gets or sets if zoom is allowed /// Gets or sets if zoom is allowed
@ -1007,10 +992,8 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(AllowZoomProperty, value); set => SetValue(AllowZoomProperty, value);
} }
public static readonly DirectProperty< public static readonly DirectProperty<AdvancedImageBox, ZoomLevelCollection> ZoomLevelsProperty =
AdvancedImageBox, AvaloniaProperty.RegisterDirect<AdvancedImageBox, ZoomLevelCollection>(
ZoomLevelCollection
> ZoomLevelsProperty = AvaloniaProperty.RegisterDirect<AdvancedImageBox, ZoomLevelCollection>(
nameof(ZoomLevels), nameof(ZoomLevels),
o => o.ZoomLevels, o => o.ZoomLevels,
(o, v) => o.ZoomLevels = v (o, v) => o.ZoomLevels = v
@ -1028,10 +1011,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetAndRaise(ZoomLevelsProperty, ref _zoomLevels, value); set => SetAndRaise(ZoomLevelsProperty, ref _zoomLevels, value);
} }
public static readonly StyledProperty<int> MinZoomProperty = AvaloniaProperty.Register< public static readonly StyledProperty<int> MinZoomProperty = AvaloniaProperty.Register<AdvancedImageBox, int>(
AdvancedImageBox, nameof(MinZoom),
int 10
>(nameof(MinZoom), 10); );
/// <summary> /// <summary>
/// Gets or sets the minimum possible zoom. /// Gets or sets the minimum possible zoom.
@ -1043,10 +1026,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(MinZoomProperty, value); set => SetValue(MinZoomProperty, value);
} }
public static readonly StyledProperty<int> MaxZoomProperty = AvaloniaProperty.Register< public static readonly StyledProperty<int> MaxZoomProperty = AvaloniaProperty.Register<AdvancedImageBox, int>(
AdvancedImageBox, nameof(MaxZoom),
int 6400
>(nameof(MaxZoom), 6400); );
/// <summary> /// <summary>
/// Gets or sets the maximum possible zoom. /// Gets or sets the maximum possible zoom.
@ -1058,8 +1041,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(MaxZoomProperty, value); set => SetValue(MaxZoomProperty, value);
} }
public static readonly StyledProperty<bool> ConstrainZoomOutToFitLevelProperty = public static readonly StyledProperty<bool> ConstrainZoomOutToFitLevelProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, bool>(nameof(ConstrainZoomOutToFitLevel), true); AdvancedImageBox,
bool
>(nameof(ConstrainZoomOutToFitLevel), true);
/// <summary> /// <summary>
/// Gets or sets if the zoom out should constrain to fit image as the lowest zoom level. /// Gets or sets if the zoom out should constrain to fit image as the lowest zoom level.
@ -1070,8 +1055,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(ConstrainZoomOutToFitLevelProperty, value); set => SetValue(ConstrainZoomOutToFitLevelProperty, value);
} }
public static readonly DirectProperty<AdvancedImageBox, int> OldZoomProperty = public static readonly DirectProperty<AdvancedImageBox, int> OldZoomProperty = AvaloniaProperty.RegisterDirect<
AvaloniaProperty.RegisterDirect<AdvancedImageBox, int>(nameof(OldZoom), o => o.OldZoom); AdvancedImageBox,
int
>(nameof(OldZoom), o => o.OldZoom);
private int _oldZoom = 100; private int _oldZoom = 100;
@ -1085,10 +1072,10 @@ public class AdvancedImageBox : TemplatedControl
private set => SetAndRaise(OldZoomProperty, ref _oldZoom, value); private set => SetAndRaise(OldZoomProperty, ref _oldZoom, value);
} }
public static readonly StyledProperty<int> ZoomProperty = AvaloniaProperty.Register< public static readonly StyledProperty<int> ZoomProperty = AvaloniaProperty.Register<AdvancedImageBox, int>(
AdvancedImageBox, nameof(Zoom),
int 100
>(nameof(Zoom), 100); );
/// <summary> /// <summary>
/// Gets or sets the zoom. /// Gets or sets the zoom.
@ -1178,11 +1165,10 @@ public class AdvancedImageBox : TemplatedControl
/// <value>The height of the scaled image.</value> /// <value>The height of the scaled image.</value>
public double ScaledImageHeight => Image?.Size.Height * ZoomFactor ?? 0; public double ScaledImageHeight => Image?.Size.Height * ZoomFactor ?? 0;
public static readonly StyledProperty<ISolidColorBrush> PixelGridColorProperty = public static readonly StyledProperty<ISolidColorBrush> PixelGridColorProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>( AdvancedImageBox,
nameof(PixelGridColor), ISolidColorBrush
Brushes.DimGray >(nameof(PixelGridColor), Brushes.DimGray);
);
/// <summary> /// <summary>
/// Gets or sets the color of the pixel grid. /// Gets or sets the color of the pixel grid.
@ -1194,8 +1180,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(PixelGridColorProperty, value); set => SetValue(PixelGridColorProperty, value);
} }
public static readonly StyledProperty<int> PixelGridZoomThresholdProperty = public static readonly StyledProperty<int> PixelGridZoomThresholdProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, int>(nameof(PixelGridZoomThreshold), 13); AdvancedImageBox,
int
>(nameof(PixelGridZoomThreshold), 13);
/// <summary> /// <summary>
/// Minimum size of zoomed pixel's before the pixel grid will be drawn /// Minimum size of zoomed pixel's before the pixel grid will be drawn
@ -1206,11 +1194,15 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(PixelGridZoomThresholdProperty, value); set => SetValue(PixelGridZoomThresholdProperty, value);
} }
public static readonly StyledProperty<SelectionModes> SelectionModeProperty = public static readonly StyledProperty<SelectionModes> SelectionModeProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, SelectionModes>(nameof(SelectionMode)); AdvancedImageBox,
SelectionModes
>(nameof(SelectionMode));
public static readonly StyledProperty<bool> IsPixelGridEnabledProperty = public static readonly StyledProperty<bool> IsPixelGridEnabledProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, bool>("IsPixelGridEnabled", true); AdvancedImageBox,
bool
>("IsPixelGridEnabled", true);
/// <summary> /// <summary>
/// Whether or not to draw the pixel grid at the <see cref="PixelGridZoomThreshold"/> /// Whether or not to draw the pixel grid at the <see cref="PixelGridZoomThreshold"/>
@ -1227,11 +1219,10 @@ public class AdvancedImageBox : TemplatedControl
set => SetValue(SelectionModeProperty, value); set => SetValue(SelectionModeProperty, value);
} }
public static readonly StyledProperty<ISolidColorBrush> SelectionColorProperty = public static readonly StyledProperty<ISolidColorBrush> SelectionColorProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>( AdvancedImageBox,
nameof(SelectionColor), ISolidColorBrush
new SolidColorBrush(new Color(127, 0, 128, 255)) >(nameof(SelectionColor), new SolidColorBrush(new Color(127, 0, 128, 255)));
);
public ISolidColorBrush SelectionColor public ISolidColorBrush SelectionColor
{ {
@ -1313,14 +1304,9 @@ public class AdvancedImageBox : TemplatedControl
AffectsRender<AdvancedImageBox>(ShowGridProperty); AffectsRender<AdvancedImageBox>(ShowGridProperty);
HorizontalScrollBar = HorizontalScrollBar =
e.NameScope.Find<ScrollBar>("PART_HorizontalScrollBar") e.NameScope.Find<ScrollBar>("PART_HorizontalScrollBar") ?? throw new NullReferenceException();
?? throw new NullReferenceException(); VerticalScrollBar = e.NameScope.Find<ScrollBar>("PART_VerticalScrollBar") ?? throw new NullReferenceException();
VerticalScrollBar = ViewPort = e.NameScope.Find<ContentPresenter>("PART_ViewPort") ?? throw new NullReferenceException();
e.NameScope.Find<ScrollBar>("PART_VerticalScrollBar")
?? throw new NullReferenceException();
ViewPort =
e.NameScope.Find<ContentPresenter>("PART_ViewPort")
?? throw new NullReferenceException();
SizeModeChanged(); SizeModeChanged();
@ -1347,13 +1333,12 @@ public class AdvancedImageBox : TemplatedControl
// If we're in high zoom, switch off bitmap interpolation mode // If we're in high zoom, switch off bitmap interpolation mode
// Otherwise use high quality // Otherwise use high quality
BitmapInterpolationMode = isHighZoom BitmapInterpolationMode = isHighZoom ? BitmapInterpolationMode.None : BitmapInterpolationMode.HighQuality;
? BitmapInterpolationMode.None
: BitmapInterpolationMode.HighQuality;
InvalidateVisual(); InvalidateVisual();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RenderBackgroundGrid(DrawingContext context) private void RenderBackgroundGrid(DrawingContext context)
{ {
var size = GridCellSize; var size = GridCellSize;
@ -1387,8 +1372,6 @@ public class AdvancedImageBox : TemplatedControl
public override void Render(DrawingContext context) public override void Render(DrawingContext context)
{ {
base.Render(context);
var gridCellSize = GridCellSize; var gridCellSize = GridCellSize;
if (ShowGrid & gridCellSize > 0 && (!IsHorizontalBarVisible || !IsVerticalBarVisible)) if (ShowGrid & gridCellSize > 0 && (!IsHorizontalBarVisible || !IsVerticalBarVisible))
@ -1399,9 +1382,7 @@ public class AdvancedImageBox : TemplatedControl
var zoomFactor = ZoomFactor; var zoomFactor = ZoomFactor;
var shouldDrawPixelGrid = var shouldDrawPixelGrid =
IsPixelGridEnabled IsPixelGridEnabled && SizeMode == SizeModes.Normal && zoomFactor > PixelGridZoomThreshold;
&& SizeMode == SizeModes.Normal
&& zoomFactor > PixelGridZoomThreshold;
// Draw Grid // Draw Grid
/*var viewPortSize = ViewPortSize; /*var viewPortSize = ViewPortSize;
@ -1441,16 +1422,10 @@ public class AdvancedImageBox : TemplatedControl
if (HaveTrackerImage && _pointerPosition is { X: >= 0, Y: >= 0 }) if (HaveTrackerImage && _pointerPosition is { X: >= 0, Y: >= 0 })
{ {
var destSize = TrackerImageAutoZoom var destSize = TrackerImageAutoZoom
? new Size( ? new Size(_trackerImage!.Size.Width * zoomFactor, _trackerImage.Size.Height * zoomFactor)
_trackerImage!.Size.Width * zoomFactor,
_trackerImage.Size.Height * zoomFactor
)
: image.Size; : image.Size;
var destPos = new Point( var destPos = new Point(_pointerPosition.X - destSize.Width / 2, _pointerPosition.Y - destSize.Height / 2);
_pointerPosition.X - destSize.Width / 2,
_pointerPosition.Y - destSize.Height / 2
);
context.DrawImage(_trackerImage!, new Rect(destPos, destSize)); context.DrawImage(_trackerImage!, new Rect(destPos, destSize));
} }
@ -1462,30 +1437,14 @@ public class AdvancedImageBox : TemplatedControl
var offsetY = Offset.Y % zoomFactor; var offsetY = Offset.Y % zoomFactor;
Pen pen = new(PixelGridColor); Pen pen = new(PixelGridColor);
for ( for (var x = imageViewPort.X + zoomFactor - offsetX; x < imageViewPort.Right; x += zoomFactor)
var x = imageViewPort.X + zoomFactor - offsetX;
x < imageViewPort.Right;
x += zoomFactor
)
{ {
context.DrawLine( context.DrawLine(pen, new Point(x, imageViewPort.X), new Point(x, imageViewPort.Bottom));
pen,
new Point(x, imageViewPort.X),
new Point(x, imageViewPort.Bottom)
);
} }
for ( for (var y = imageViewPort.Y + zoomFactor - offsetY; y < imageViewPort.Bottom; y += zoomFactor)
var y = imageViewPort.Y + zoomFactor - offsetY;
y < imageViewPort.Bottom;
y += zoomFactor
)
{ {
context.DrawLine( context.DrawLine(pen, new Point(imageViewPort.Y, y), new Point(imageViewPort.Right, y));
pen,
new Point(imageViewPort.Y, y),
new Point(imageViewPort.Right, y)
);
} }
context.DrawRectangle(pen, imageViewPort); context.DrawRectangle(pen, imageViewPort);
@ -1496,12 +1455,7 @@ public class AdvancedImageBox : TemplatedControl
var rect = GetOffsetRectangle(SelectionRegion); var rect = GetOffsetRectangle(SelectionRegion);
var selectionColor = SelectionColor; var selectionColor = SelectionColor;
context.FillRectangle(selectionColor, rect); context.FillRectangle(selectionColor, rect);
var color = Color.FromArgb( var color = Color.FromArgb(255, selectionColor.Color.R, selectionColor.Color.G, selectionColor.Color.B);
255,
selectionColor.Color.R,
selectionColor.Color.G,
selectionColor.Color.B
);
context.DrawRectangle(new Pen(color.ToUInt32()), rect); context.DrawRectangle(new Pen(color.ToUInt32()), rect);
} }
} }
@ -1609,8 +1563,7 @@ public class AdvancedImageBox : TemplatedControl
{ {
if ( if (
!( !(
pointer.Properties.IsLeftButtonPressed pointer.Properties.IsLeftButtonPressed && (SelectWithMouseButtons & MouseButtons.LeftButton) != 0
&& (SelectWithMouseButtons & MouseButtons.LeftButton) != 0
|| pointer.Properties.IsMiddleButtonPressed || pointer.Properties.IsMiddleButtonPressed
&& (SelectWithMouseButtons & MouseButtons.MiddleButton) != 0 && (SelectWithMouseButtons & MouseButtons.MiddleButton) != 0
|| pointer.Properties.IsRightButtonPressed || pointer.Properties.IsRightButtonPressed
@ -1624,12 +1577,10 @@ public class AdvancedImageBox : TemplatedControl
{ {
if ( if (
!( !(
pointer.Properties.IsLeftButtonPressed pointer.Properties.IsLeftButtonPressed && (PanWithMouseButtons & MouseButtons.LeftButton) != 0
&& (PanWithMouseButtons & MouseButtons.LeftButton) != 0
|| pointer.Properties.IsMiddleButtonPressed || pointer.Properties.IsMiddleButtonPressed
&& (PanWithMouseButtons & MouseButtons.MiddleButton) != 0 && (PanWithMouseButtons & MouseButtons.MiddleButton) != 0
|| pointer.Properties.IsRightButtonPressed || pointer.Properties.IsRightButtonPressed && (PanWithMouseButtons & MouseButtons.RightButton) != 0
&& (PanWithMouseButtons & MouseButtons.RightButton) != 0
) )
|| !AutoPan || !AutoPan
|| SizeMode != SizeModes.Normal || SizeMode != SizeModes.Normal
@ -2669,12 +2620,8 @@ public class AdvancedImageBox : TemplatedControl
case SizeModes.Normal: case SizeModes.Normal:
if (AutoCenter) if (AutoCenter)
{ {
xOffset = ( xOffset = (!IsHorizontalBarVisible ? (viewPortSize.Width - ScaledImageWidth) / 2 : 0);
!IsHorizontalBarVisible ? (viewPortSize.Width - ScaledImageWidth) / 2 : 0 yOffset = (!IsVerticalBarVisible ? (viewPortSize.Height - ScaledImageHeight) / 2 : 0);
);
yOffset = (
!IsVerticalBarVisible ? (viewPortSize.Height - ScaledImageHeight) / 2 : 0
);
} }
width = Math.Min(ScaledImageWidth - Math.Abs(Offset.X), viewPortSize.Width); width = Math.Min(ScaledImageWidth - Math.Abs(Offset.X), viewPortSize.Width);
@ -2729,12 +2676,7 @@ public class AdvancedImageBox : TemplatedControl
var pixelSize = SelectionPixelSize; var pixelSize = SelectionPixelSize;
using var frameBuffer = image.Lock(); using var frameBuffer = image.Lock();
var newBitmap = new WriteableBitmap( var newBitmap = new WriteableBitmap(pixelSize, image.Dpi, frameBuffer.Format, AlphaFormat.Unpremul);
pixelSize,
image.Dpi,
frameBuffer.Format,
AlphaFormat.Unpremul
);
using var newFrameBuffer = newBitmap.Lock(); using var newFrameBuffer = newBitmap.Lock();
var i = 0; var i = 0;

60
StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml

@ -1,30 +1,70 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
x:Class="StabilityMatrix.Avalonia.Controls.AdvancedImageBoxView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models" xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="StabilityMatrix.Avalonia.Controls.AdvancedImageBoxView"
d:DataContext="{x:Static mocks:DesignData.SampleImageSource}" d:DataContext="{x:Static mocks:DesignData.SampleImageSource}"
x:DataType="models:ImageSource"> d:DesignHeight="450"
d:DesignWidth="800"
x:DataType="models:ImageSource"
mc:Ignorable="d">
<Grid>
<controls:AdvancedImageBox <controls:AdvancedImageBox
Name="ImageBox" Name="ImageBox"
SizeMode="Fit"
CornerRadius="4" CornerRadius="4"
Image="{Binding BitmapAsync^}"> Image="{Binding BitmapAsync^}"
SizeMode="Fit">
<controls:AdvancedImageBox.ContextFlyout> <controls:AdvancedImageBox.ContextFlyout>
<ui:FAMenuFlyout> <ui:FAMenuFlyout>
<ui:MenuFlyoutItem <ui:MenuFlyoutItem
x:Name="CopyMenuItem" x:Name="CopyMenuItem"
IsEnabled="{OnPlatform Windows=True, Default=False}"
CommandParameter="{Binding #ImageBox.Image}" CommandParameter="{Binding #ImageBox.Image}"
Text="Copy"
HotKey="Ctrl+C" HotKey="Ctrl+C"
IconSource="Copy" /> IconSource="Copy"
IsEnabled="{OnPlatform Windows=True,
Default=False}"
Text="Copy" />
</ui:FAMenuFlyout> </ui:FAMenuFlyout>
</controls:AdvancedImageBox.ContextFlyout> </controls:AdvancedImageBox.ContextFlyout>
</controls:AdvancedImageBox> </controls:AdvancedImageBox>
<!-- Label pill card -->
<Border
IsVisible="{Binding Label, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Grid.Row="0"
Margin="4"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
BoxShadow="inset 1.2 0 10 1.8 #66000000"
CornerRadius="16">
<Border.Resources>
<DropShadowEffect
x:Key="TextDropShadowEffect"
BlurRadius="12"
Opacity="0.9"
Color="#FF000000" />
<DropShadowEffect
x:Key="ImageDropShadowEffect"
BlurRadius="12"
Opacity="0.2"
Color="#FF000000" />
</Border.Resources>
<Button
Padding="10,4"
Classes="transparent"
CornerRadius="16">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock
VerticalAlignment="Center"
Effect="{StaticResource TextDropShadowEffect}"
Text="{Binding Label}" />
</StackPanel>
</Button>
</Border>
</Grid>
</UserControl> </UserControl>

61
StabilityMatrix.Avalonia/Controls/BetterAdvancedImage.cs

@ -15,11 +15,15 @@ public class BetterAdvancedImage : AdvancedImage
#region Reflection Shenanigans to access private parent fields #region Reflection Shenanigans to access private parent fields
[NotNull] [NotNull]
private static readonly FieldInfo? IsCornerRadiusUsedField = typeof(AdvancedImage).GetField( private static readonly FieldInfo? IsCornerRadiusUsedField = typeof(AdvancedImage).GetField(
"_isCornerRadiusUsed",BindingFlags.Instance | BindingFlags.NonPublic); "_isCornerRadiusUsed",
BindingFlags.Instance | BindingFlags.NonPublic
);
[NotNull] [NotNull]
private static readonly FieldInfo? CornerRadiusClipField = typeof(AdvancedImage).GetField( private static readonly FieldInfo? CornerRadiusClipField = typeof(AdvancedImage).GetField(
"_cornerRadiusClip",BindingFlags.Instance | BindingFlags.NonPublic); "_cornerRadiusClip",
BindingFlags.Instance | BindingFlags.NonPublic
);
private bool IsCornerRadiusUsed private bool IsCornerRadiusUsed
{ {
@ -29,7 +33,7 @@ public class BetterAdvancedImage : AdvancedImage
private RoundedRect CornerRadiusClip private RoundedRect CornerRadiusClip
{ {
get => (RoundedRect) CornerRadiusClipField.GetValue(this)!; get => (RoundedRect)CornerRadiusClipField.GetValue(this)!;
set => CornerRadiusClipField.SetValue(this, value); set => CornerRadiusClipField.SetValue(this, value);
} }
@ -48,13 +52,11 @@ public class BetterAdvancedImage : AdvancedImage
protected override Type StyleKeyOverride { get; } = typeof(AdvancedImage); protected override Type StyleKeyOverride { get; } = typeof(AdvancedImage);
public BetterAdvancedImage(Uri? baseUri) : base(baseUri) public BetterAdvancedImage(Uri? baseUri)
{ : base(baseUri) { }
}
public BetterAdvancedImage(IServiceProvider serviceProvider) : base(serviceProvider) public BetterAdvancedImage(IServiceProvider serviceProvider)
{ : base(serviceProvider) { }
}
/// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <inheritdoc /> /// <inheritdoc />
@ -74,43 +76,41 @@ public class BetterAdvancedImage : AdvancedImage
var destX = HorizontalContentAlignment switch var destX = HorizontalContentAlignment switch
{ {
HorizontalAlignment.Left => 0, HorizontalAlignment.Left => 0,
HorizontalAlignment.Center => (int) (viewPort.Width - scaledSize.Width) / 2, HorizontalAlignment.Center => (int)(viewPort.Width - scaledSize.Width) / 2,
HorizontalAlignment.Right => (int) (viewPort.Width - scaledSize.Width), HorizontalAlignment.Right => (int)(viewPort.Width - scaledSize.Width),
// Stretch is default, use center // Stretch is default, use center
HorizontalAlignment.Stretch => (int) (viewPort.Width - scaledSize.Width) / 2, HorizontalAlignment.Stretch
=> (int)(viewPort.Width - scaledSize.Width) / 2,
_ => throw new ArgumentException(nameof(HorizontalContentAlignment)) _ => throw new ArgumentException(nameof(HorizontalContentAlignment))
}; };
var destY = VerticalContentAlignment switch var destY = VerticalContentAlignment switch
{ {
VerticalAlignment.Top => 0, VerticalAlignment.Top => 0,
VerticalAlignment.Center => (int) (viewPort.Height - scaledSize.Height) / 2, VerticalAlignment.Center => (int)(viewPort.Height - scaledSize.Height) / 2,
VerticalAlignment.Bottom => (int) (viewPort.Height - scaledSize.Height), VerticalAlignment.Bottom => (int)(viewPort.Height - scaledSize.Height),
VerticalAlignment.Stretch => 0, // Stretch is default, use top VerticalAlignment.Stretch => 0, // Stretch is default, use top
_ => throw new ArgumentException(nameof(VerticalContentAlignment)) _ => throw new ArgumentException(nameof(VerticalContentAlignment))
}; };
var destRect = viewPort var destRect = viewPort.CenterRect(new Rect(scaledSize)).WithX(destX).WithY(destY).Intersect(viewPort);
.CenterRect(new Rect(scaledSize))
.WithX(destX)
.WithY(destY)
.Intersect(viewPort);
var destRectUnscaledSize = destRect.Size / scale; var destRectUnscaledSize = destRect.Size / scale;
// Calculate starting points for source // Calculate starting points for source
var sourceX = HorizontalContentAlignment switch var sourceX = HorizontalContentAlignment switch
{ {
HorizontalAlignment.Left => 0, HorizontalAlignment.Left => 0,
HorizontalAlignment.Center => (int) (sourceSize - destRectUnscaledSize).Width / 2, HorizontalAlignment.Center => (int)(sourceSize - destRectUnscaledSize).Width / 2,
HorizontalAlignment.Right => (int) (sourceSize - destRectUnscaledSize).Width, HorizontalAlignment.Right => (int)(sourceSize - destRectUnscaledSize).Width,
// Stretch is default, use center // Stretch is default, use center
HorizontalAlignment.Stretch => (int) (sourceSize - destRectUnscaledSize).Width / 2, HorizontalAlignment.Stretch
=> (int)(sourceSize - destRectUnscaledSize).Width / 2,
_ => throw new ArgumentException(nameof(HorizontalContentAlignment)) _ => throw new ArgumentException(nameof(HorizontalContentAlignment))
}; };
var sourceY = VerticalContentAlignment switch var sourceY = VerticalContentAlignment switch
{ {
VerticalAlignment.Top => 0, VerticalAlignment.Top => 0,
VerticalAlignment.Center => (int) (sourceSize - destRectUnscaledSize).Height / 2, VerticalAlignment.Center => (int)(sourceSize - destRectUnscaledSize).Height / 2,
VerticalAlignment.Bottom => (int) (sourceSize - destRectUnscaledSize).Height, VerticalAlignment.Bottom => (int)(sourceSize - destRectUnscaledSize).Height,
VerticalAlignment.Stretch => 0, // Stretch is default, use top VerticalAlignment.Stretch => 0, // Stretch is default, use top
_ => throw new ArgumentException(nameof(VerticalContentAlignment)) _ => throw new ArgumentException(nameof(VerticalContentAlignment))
}; };
@ -120,10 +120,17 @@ public class BetterAdvancedImage : AdvancedImage
.WithX(sourceX) .WithX(sourceX)
.WithY(sourceY); .WithY(sourceY);
DrawingContext.PushedState? pushedState = if (IsCornerRadiusUsed)
IsCornerRadiusUsed ? context.PushClip(CornerRadiusClip) : null; {
using (context.PushClip(CornerRadiusClip))
{
context.DrawImage(source, sourceRect, destRect); context.DrawImage(source, sourceRect, destRect);
pushedState?.Dispose(); }
}
else
{
context.DrawImage(source, sourceRect, destRect);
}
} }
else else
{ {

9
StabilityMatrix.Avalonia/Controls/BatchSizeCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml

@ -6,6 +6,7 @@
xmlns:icons="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:icons="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
x:DataType="vmInference:BatchSizeCardViewModel"> x:DataType="vmInference:BatchSizeCardViewModel">
<Design.PreviewWith> <Design.PreviewWith>
@ -19,7 +20,7 @@
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="controls|BatchSizeCard"> <Style Selector="controls|BatchSizeCard">
<!-- Set Defaults --> <Setter Property="Focusable" Value="True"/>
<Setter Property="ContextFlyout"> <Setter Property="ContextFlyout">
<ui:FAMenuFlyout Placement="BottomEdgeAlignedLeft"> <ui:FAMenuFlyout Placement="BottomEdgeAlignedLeft">
<ui:ToggleMenuFlyoutItem <ui:ToggleMenuFlyoutItem
@ -48,7 +49,7 @@
Minimum="1" Minimum="1"
Increment="1" Increment="1"
ParsingNumberStyle="Integer" ParsingNumberStyle="Integer"
Value="{Binding BatchSize}" Value="{Binding BatchSize, Converter={x:Static converters:NullableDefaultNumericConverters.IntToDecimal}}"
ClipValueToMinMax="True"/> ClipValueToMinMax="True"/>
</StackPanel> </StackPanel>
@ -71,7 +72,7 @@
Minimum="1" Minimum="1"
Increment="1" Increment="1"
ParsingNumberStyle="Integer" ParsingNumberStyle="Integer"
Value="{Binding BatchCount}" Value="{Binding BatchCount, Converter={x:Static converters:NullableDefaultNumericConverters.IntToDecimal}}"
ClipValueToMinMax="True"/> ClipValueToMinMax="True"/>
</StackPanel> </StackPanel>
@ -99,7 +100,7 @@
Maximum="{Binding BatchSize}" Maximum="{Binding BatchSize}"
Increment="1" Increment="1"
ParsingNumberStyle="Integer" ParsingNumberStyle="Integer"
Value="{Binding BatchIndex}" Value="{Binding BatchIndex, Converter={x:Static converters:NullableDefaultNumericConverters.IntToDecimal}}"
ClipValueToMinMax="True"/> ClipValueToMinMax="True"/>
</StackPanel> </StackPanel>

0
StabilityMatrix.Avalonia/Controls/BatchSizeCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml.cs

127
StabilityMatrix.Avalonia/Controls/ControlNetCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml

@ -2,91 +2,89 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls" xmlns:controls="using:StabilityMatrix.Avalonia.Controls"
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core" xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference" xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia" xmlns:markupExtensions="clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions"
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
x:DataType="vmInference:ControlNetCardViewModel"> x:DataType="vmInference:ControlNetCardViewModel">
<Design.PreviewWith> <Design.PreviewWith>
<StackPanel Width="350" Height="400"> <StackPanel Width="400" Height="500">
<controls:ControlNetCard DataContext="{x:Static mocks:DesignData.ControlNetCardViewModel}" /> <controls:ControlNetCard DataContext="{x:Static mocks:DesignData.ControlNetCardViewModel}" />
</StackPanel> </StackPanel>
</Design.PreviewWith> </Design.PreviewWith>
<Styles.Resources>
<ToolTip x:Key="WipFeatureToolTip">
<StackPanel>
<TextBlock Text="{x:Static lang:Resources.Label_WipFeature}" Theme="{DynamicResource BodyStrongTextBlockStyle}" />
<TextBlock Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{x:Static lang:Resources.Label_WipFeatureDescription}" />
</StackPanel>
</ToolTip>
</Styles.Resources>
<Style Selector="controls|ControlNetCard"> <Style Selector="controls|ControlNetCard">
<!-- Set Defaults --> <!-- Set Defaults -->
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<controls:Card Padding="12"> <controls:Card Padding="12">
<StackPanel Spacing="6">
<sg:SpacedGrid <sg:SpacedGrid
ColumnDefinitions="0.8*,*" ColumnDefinitions="0.34*,0.65*"
ColumnSpacing="8" ColumnSpacing="8"
RowDefinitions="Auto,Auto,Auto,Auto,5,Auto" RowDefinitions="Auto,Auto,Auto"
RowSpacing="6"> RowSpacing="4">
<!-- Image Select --> <!-- Image Select -->
<controls:SelectImageCard <controls:SelectImageCard
Grid.RowSpan="4" Grid.RowSpan="2"
Padding="6" Padding="6"
VerticalAlignment="Stretch"
DataContext="{Binding SelectImageCardViewModel}" DataContext="{Binding SelectImageCardViewModel}"
FontSize="13" /> FontSize="13" />
<!-- TODO: Preprocessor Model --> <!-- TODO: Preprocessor Model -->
<TextBlock
Grid.Row="0"
Grid.Column="1"
MinWidth="60"
Foreground="{StaticResource ThemeGreyColor}"
VerticalAlignment="Bottom"
Text="{x:Static lang:Resources.Label_Preprocessor}"
TextAlignment="Left" />
<ui:FAComboBox <ui:FAComboBox
Grid.Row="1" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
IsEnabled="False"
Margin="0,0,0,4" Margin="0,0,0,4"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Theme="{StaticResource FAComboBoxHybridModelTheme}" /> markupExtensions:ShowDisabledTooltipExtension.ShowOnDisabled="True"
Header="{x:Static lang:Resources.Label_Preprocessor}"
IsEnabled="False"
Theme="{StaticResource FAComboBoxHybridModelTheme}"
ToolTip.ShowDelay="1200"
ToolTip.Tip="{StaticResource WipFeatureToolTip}" />
<!-- ControlNet Model --> <!-- ControlNet Model -->
<TextBlock
Grid.Row="2"
Grid.Column="1"
MinWidth="60"
VerticalAlignment="Bottom"
Text="{x:Static lang:Resources.Label_Model}"
TextAlignment="Left" />
<ui:FAComboBox <ui:FAComboBox
x:Name="PART_ModelComboBox" x:Name="PART_ModelComboBox"
Grid.Row="3" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Header="{x:Static lang:Resources.Label_Model}"
ItemContainerTheme="{StaticResource FAComboBoxItemHybridModelTheme}"
ItemsSource="{Binding ClientManager.ControlNetModels}" ItemsSource="{Binding ClientManager.ControlNetModels}"
SelectedItem="{Binding SelectedModel}" SelectedItem="{Binding SelectedModel}">
ItemContainerTheme="{StaticResource FAComboBoxItemHybridModelTheme}">
<ui:FAComboBox.Resources> <ui:FAComboBox.Resources>
<input:StandardUICommand <input:StandardUICommand x:Key="RemoteDownloadCommand" Command="{Binding RemoteDownloadCommand}" />
x:Key="RemoteDownloadCommand"
Command="{Binding RemoteDownloadCommand}"/>
</ui:FAComboBox.Resources> </ui:FAComboBox.Resources>
<ui:FAComboBox.DataTemplates> <ui:FAComboBox.DataTemplates>
<controls:HybridModelTemplateSelector> <controls:HybridModelTemplateSelector>
<DataTemplate DataType="models:HybridModelFile" x:Key="{x:Static models:HybridModelType.Downloadable}"> <DataTemplate x:Key="{x:Static models:HybridModelType.Downloadable}" DataType="models:HybridModelFile">
<Grid ColumnDefinitions="*,Auto"> <Grid ColumnDefinitions="*,Auto">
<TextBlock <TextBlock Foreground="{DynamicResource ThemeGreyColor}" Text="{Binding ShortDisplayName}" />
Text="{Binding ShortDisplayName}"
Foreground="{DynamicResource ThemeGreyColor}"/>
<Button <Button
Grid.Column="1" Grid.Column="1"
Classes="transparent-full"
Margin="8,0,0,0" Margin="8,0,0,0"
Padding="0"> Padding="0"
Classes="transparent-full">
<fluentIcons:SymbolIcon <fluentIcons:SymbolIcon
VerticalAlignment="Center" VerticalAlignment="Center"
FontSize="18" FontSize="18"
@ -96,46 +94,45 @@
</Button> </Button>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="models:HybridModelFile" x:Key="{x:Static models:HybridModelType.None}"> <DataTemplate x:Key="{x:Static models:HybridModelType.None}" DataType="models:HybridModelFile">
<TextBlock Text="{Binding ShortDisplayName}"/> <TextBlock Text="{Binding ShortDisplayName}" />
</DataTemplate> </DataTemplate>
</controls:HybridModelTemplateSelector> </controls:HybridModelTemplateSelector>
</ui:FAComboBox.DataTemplates> </ui:FAComboBox.DataTemplates>
</ui:FAComboBox> </ui:FAComboBox>
</sg:SpacedGrid>
<sg:SpacedGrid <sg:SpacedGrid
RowDefinitions="Auto,Auto,Auto,Auto"
ColumnDefinitions="*,Auto,Auto" ColumnDefinitions="*,Auto,Auto"
Grid.Row="5" RowDefinitions="Auto,Auto,Auto,Auto"
Grid.Column="0" RowSpacing="0">
Grid.ColumnSpan="2">
<!-- Strength --> <!-- Strength -->
<TextBlock <TextBlock
Text="{x:Static lang:Resources.Label_ControlWeight}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static lang:Resources.Label_ControlWeight}"
TextAlignment="Left" /> TextAlignment="Left" />
<ui:NumberBox <ui:NumberBox
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
SpinButtonPlacementMode="Inline"
Maximum="10" Maximum="10"
Minimum="0" Minimum="0"
SmallChange="0.01"
SimpleNumberFormat="F2" SimpleNumberFormat="F2"
SmallChange="0.01"
SpinButtonPlacementMode="Inline"
Value="{Binding Strength}" /> Value="{Binding Strength}" />
<Slider <Slider
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Margin="0,-8,0,0" Margin="0,-8,0,0"
IsSnapToTickEnabled="True"
Maximum="1" Maximum="1"
Minimum="0" Minimum="0"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
SmallChange="0.01" SmallChange="0.01"
TickFrequency="0.01"
Value="{Binding Strength}" /> Value="{Binding Strength}" />
<!-- Start end percent --> <!-- Start end percent -->
@ -150,63 +147,35 @@
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
SimpleNumberFormat="P0"
Maximum="10" Maximum="10"
Minimum="0" Minimum="0"
SimpleNumberFormat="P0"
SmallChange="0.01" SmallChange="0.01"
Value="{Binding StartPercent}" /> Value="{Binding StartPercent}" />
<ui:NumberBox <ui:NumberBox
Grid.Row="2" Grid.Row="2"
Grid.Column="2" Grid.Column="2"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
SimpleNumberFormat="P0"
Maximum="10"
Minimum="0"
SmallChange="0.01"
Value="{Binding EndPercent}" />
<!--<sg:SpacedGrid Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Right"
ColumnDefinitions="*,*">
<ui:NumberBox
HorizontalAlignment="Stretch"
Maximum="10"
Minimum="0"
SmallChange="0.01"
Value="{Binding StartPercent}" />
<ui:NumberBox
Grid.Column="1"
HorizontalAlignment="Stretch"
Maximum="10" Maximum="10"
Minimum="0" Minimum="0"
SimpleNumberFormat="P0"
SmallChange="0.01" SmallChange="0.01"
Value="{Binding EndPercent}" /> Value="{Binding EndPercent}" />
</sg:SpacedGrid>-->
<ui:RangeSlider <ui:RangeSlider
Grid.Row="3" Grid.Row="3"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Margin="0,4,0,0" Margin="0,4,0,0"
ShowValueToolTip="False"
Maximum="1" Maximum="1"
Minimum="0" Minimum="0"
RangeEnd="{Binding EndPercent}" RangeEnd="{Binding EndPercent}"
RangeStart="{Binding StartPercent}" RangeStart="{Binding StartPercent}"
ShowValueToolTip="False"
StepFrequency="0.01" StepFrequency="0.01"
ToolTipStringFormat="P" /> ToolTipStringFormat="P" />
</sg:SpacedGrid> </sg:SpacedGrid>
<StackPanel
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="2">
</StackPanel> </StackPanel>
</sg:SpacedGrid>
</controls:Card> </controls:Card>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

0
StabilityMatrix.Avalonia/Controls/ControlNetCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml.cs

0
StabilityMatrix.Avalonia/Controls/FreeUCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml

0
StabilityMatrix.Avalonia/Controls/FreeUCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml.cs

0
StabilityMatrix.Avalonia/Controls/ImageFolderCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml

0
StabilityMatrix.Avalonia/Controls/ImageFolderCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml.cs

0
StabilityMatrix.Avalonia/Controls/ImageGalleryCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml

0
StabilityMatrix.Avalonia/Controls/ImageGalleryCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml.cs

0
StabilityMatrix.Avalonia/Controls/ModelCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml

0
StabilityMatrix.Avalonia/Controls/ModelCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml.cs

0
StabilityMatrix.Avalonia/Controls/PromptCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml

0
StabilityMatrix.Avalonia/Controls/PromptCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml.cs

0
StabilityMatrix.Avalonia/Controls/SamplerCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml

0
StabilityMatrix.Avalonia/Controls/SamplerCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml.cs

0
StabilityMatrix.Avalonia/Controls/SeedCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml

0
StabilityMatrix.Avalonia/Controls/SeedCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml.cs

177
StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml

@ -0,0 +1,177 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mocks="using:StabilityMatrix.Avalonia.DesignData"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
x:DataType="vmInference:SelectImageCardViewModel">
<Design.PreviewWith>
<Panel Width="600" Height="500">
<Grid MaxHeight="400" ColumnDefinitions="*,*">
<controls:SelectImageCard Margin="4" DataContext="{x:Static mocks:DesignData.SelectImageCardViewModel}" />
<controls:SelectImageCard
Grid.Column="1"
Margin="4"
DataContext="{x:Static mocks:DesignData.SelectImageCardViewModel_WithImage}" />
</Grid>
</Panel>
</Design.PreviewWith>
<Style Selector="controls|SelectImageCard">
<!-- Set Defaults -->
<Setter Property="Padding" Value="12" />
<Setter Property="Template">
<ControlTemplate>
<controls:Card
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalAlignment}">
<!-- Background frame -->
<ExperimentalAcrylicBorder
VerticalAlignment="Stretch"
CornerRadius="4"
Material="{StaticResource OpaqueDarkAcrylicMaterial}">
<Panel>
<!-- Image -->
<controls:BetterAdvancedImage
x:Name="PART_BetterAdvancedImage"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
CornerRadius="4"
IsVisible="{Binding !IsSelectionAvailable}"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding ImageSource}"
Stretch="Uniform"
StretchDirection="Both" />
<!-- Missing image -->
<Border
BorderBrush="{StaticResource ThemeCoralRedColor}"
BorderThickness="3"
BoxShadow="inset 1.2 0 20 1.8 #66000000"
CornerRadius="4"
IsVisible="{Binding IsImageFileNotFound}">
<Grid
HorizontalAlignment="Center"
VerticalAlignment="Center"
RowDefinitions="Auto,Auto,Auto">
<fluentIcons:SymbolIcon
FontSize="28"
IsFilled="True"
Symbol="DocumentQuestionMark" />
<TextBlock
Grid.Row="1"
FontSize="{TemplateBinding FontSize}"
Text="{x:Static lang:Resources.Label_MissingImageFile}"
TextAlignment="Center"
TextWrapping="WrapWithOverflow" />
<SelectableTextBlock
Grid.Row="2"
Margin="0,4,0,0"
FontSize="10"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Text="{Binding NotFoundImagePath}"
TextAlignment="Center"
TextWrapping="WrapWithOverflow" />
</Grid>
</Border>
<!-- Active Selection Prompt -->
<Border
Margin="4"
HorizontalAlignment="Right"
VerticalAlignment="Top"
BoxShadow="inset 1.2 0 80 1.8 #66000000"
CornerRadius="10"
IsVisible="{Binding !IsSelectionAvailable}">
<Border.Resources>
<DropShadowEffect
x:Key="TextDropShadowEffect"
BlurRadius="12"
Opacity="0.9"
Color="#FF000000" />
<DropShadowEffect
x:Key="ImageDropShadowEffect"
BlurRadius="12"
Opacity="0.2"
Color="#FF000000" />
</Border.Resources>
<Button
Padding="2"
Classes="transparent"
Command="{Binding SelectImageFromFilePickerCommand}"
CornerRadius="10"
FontSize="{TemplateBinding FontSize}"
ToolTip.Tip="{x:Static lang:Resources.Action_ReplaceContents}">
<fluentIcons:SymbolIcon
Effect="{StaticResource ImageDropShadowEffect}"
FontSize="28"
IsFilled="True"
Symbol="ImageArrowCounterclockwise" />
</Button>
</Border>
<!-- No Image Selection Prompt -->
<controls:LineDashFrame
Padding="8,4"
CornerRadius="8"
IsVisible="{Binding IsSelectionAvailable}"
Stroke="DimGray"
StrokeDashLine="6"
StrokeDashSpace="6"
StrokeThickness="3">
<Grid
HorizontalAlignment="Center"
VerticalAlignment="Center"
RowDefinitions="*,Auto,Auto">
<fluentIcons:SymbolIcon
FontSize="28"
IsFilled="True"
Symbol="ImageCopy" />
<TextBlock
Grid.Row="1"
FontSize="{TemplateBinding FontSize}"
Foreground="DarkGray"
Text="Drag an image here"
TextAlignment="Center"
TextWrapping="WrapWithOverflow" />
<StackPanel
Grid.Row="2"
Margin="0,4,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Foreground="DarkGray"
Text="or" />
<Button
Margin="4,0"
Padding="4"
Command="{Binding SelectImageFromFilePickerCommand}"
Content="Browse"
FontSize="{TemplateBinding FontSize}" />
</StackPanel>
</Grid>
</controls:LineDashFrame>
</Panel>
</ExperimentalAcrylicBorder>
</controls:Card>
</ControlTemplate>
</Setter>
</Style>
</Styles>

39
StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml.cs

@ -0,0 +1,39 @@
using System;
using System.Drawing;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using DynamicData.Binding;
using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
[Transient]
public class SelectImageCard : DropTargetTemplatedControlBase
{
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
if (DataContext is not SelectImageCardViewModel vm)
return;
if (e.NameScope.Find<BetterAdvancedImage>("PART_BetterAdvancedImage") is not { } image)
return;
image
.WhenPropertyChanged(x => x.CurrentImage)
.Subscribe(propertyValue =>
{
if (propertyValue.Value?.Size is { } size)
{
vm.CurrentBitmapSize = new Size(Convert.ToInt32(size.Width), Convert.ToInt32(size.Height));
}
else
{
vm.CurrentBitmapSize = Size.Empty;
}
});
}
}

0
StabilityMatrix.Avalonia/Controls/SharpenCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml

0
StabilityMatrix.Avalonia/Controls/SharpenCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml.cs

2
StabilityMatrix.Avalonia/Controls/StackCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml

@ -20,7 +20,7 @@
<!-- Set Defaults --> <!-- Set Defaults -->
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<ScrollViewer Padding="8,0,8,0"> <ScrollViewer Padding="4,0,4,0">
<ItemsControl <ItemsControl
VerticalAlignment="Top" VerticalAlignment="Top"
ItemsSource="{Binding Cards}"> ItemsSource="{Binding Cards}">

8
StabilityMatrix.Avalonia/Controls/StackCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml.cs

@ -9,10 +9,10 @@ namespace StabilityMatrix.Avalonia.Controls;
[Transient] [Transient]
public class StackCard : TemplatedControl public class StackCard : TemplatedControl
{ {
public static readonly StyledProperty<int> SpacingProperty = AvaloniaProperty.Register< public static readonly StyledProperty<int> SpacingProperty = AvaloniaProperty.Register<StackCard, int>(
StackCard, "Spacing",
int 4
>("Spacing", 8); );
public int Spacing public int Spacing
{ {

20
StabilityMatrix.Avalonia/Controls/StackEditableCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml

@ -11,7 +11,7 @@
x:DataType="vmInference:StackEditableCardViewModel"> x:DataType="vmInference:StackEditableCardViewModel">
<Design.PreviewWith> <Design.PreviewWith>
<Grid Width="600" Height="800"> <Grid Width="500" Height="600">
<controls:StackEditableCard <controls:StackEditableCard
DataContext="{x:Static mocks:DesignData.StackEditableCardViewModel}"/> DataContext="{x:Static mocks:DesignData.StackEditableCardViewModel}"/>
</Grid> </Grid>
@ -58,23 +58,15 @@
Grid.Row="1" Grid.Row="1"
Theme="{StaticResource DraggableListBoxTheme}" Theme="{StaticResource DraggableListBoxTheme}"
ItemsSource="{Binding Cards}"> ItemsSource="{Binding Cards}">
<ListBox.DataTemplates> <ListBox.DataTemplates>
<local:ViewLocator/> <local:ViewLocator/>
</ListBox.DataTemplates> </ListBox.DataTemplates>
<!--<ListBox.Styles> <ListBox.ItemsPanel>
<Style Selector="ListBoxItem:not(:dragging)"> <ItemsPanelTemplate>
<Setter Property="Transitions"> <StackPanel Spacing="0" />
<Setter.Value> </ItemsPanelTemplate>
<Transitions> </ListBox.ItemsPanel>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.1" />
</Transitions>
</Setter.Value>
</Setter>
</Style>
</ListBox.Styles>-->
</ListBox> </ListBox>
</Grid> </Grid>

0
StabilityMatrix.Avalonia/Controls/StackEditableCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml.cs

113
StabilityMatrix.Avalonia/Controls/Inference/StackExpander.axaml

@ -0,0 +1,113 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls"
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
xmlns:local="clr-namespace:StabilityMatrix.Avalonia"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
x:DataType="vmInference:StackExpanderViewModel">
<Design.PreviewWith>
<Grid Width="500" Height="800">
<StackPanel>
<controls:StackExpander DataContext="{x:Static mocks:DesignData.StackExpanderViewModel}" />
<controls:StackExpander DataContext="{x:Static mocks:DesignData.StackExpanderViewModel2}" />
</StackPanel>
</Grid>
</Design.PreviewWith>
<Style Selector="controls|StackExpander">
<!-- Set Defaults -->
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Template">
<ControlTemplate>
<Expander
MinHeight="0"
CornerRadius="{TemplateBinding CornerRadius}"
ExpandDirection="{TemplateBinding ExpandDirection}"
IsExpanded="{TemplateBinding IsExpanded}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Top">
<Expander.Styles>
<Style Selector="Expander /template/ ToggleButton#PART_toggle">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</Expander.Styles>
<Expander.Header>
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto" Margin="0,-4">
<ToggleSwitch
Margin="2,0,0,4"
VerticalContentAlignment="Center"
IsChecked="{Binding IsEnabled}"
OffContent=""
OnContent="" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
IsVisible="{Binding TitleExtra, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Text="{Binding TitleExtra}" />
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding Title}" />
<!-- Settings button -->
<Button
Grid.Column="3"
Padding="12,6"
HorizontalAlignment="Right"
Classes="transparent-full"
Command="{Binding SettingsCommand}"
IsVisible="{Binding IsSettingsEnabled}">
<fluentIcons:SymbolIcon FontSize="17" Symbol="Settings" />
</Button>
<!-- Delete button for StackEditableCard -->
<Button
Grid.Column="4"
Padding="12,6"
HorizontalAlignment="Right"
Classes="transparent-red"
Command="{Binding RemoveFromParentListCommand}"
IsVisible="{Binding IsEditEnabled}">
<fluentIcons:SymbolIcon FontSize="16" Symbol="Delete" />
</Button>
</Grid>
</Expander.Header>
<Panel>
<ItemsControl VerticalAlignment="Top" ItemsSource="{Binding Cards}">
<ItemsControl.Styles>
<Style Selector="controls|Card">
<Setter Property="IsCardVisualsEnabled" Value="False" />
</Style>
</ItemsControl.Styles>
<ItemsControl.DataTemplates>
<local:ViewLocator />
</ItemsControl.DataTemplates>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
x:Name="PART_ItemsPanel"
VerticalAlignment="Top"
Spacing="{TemplateBinding Spacing}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Panel>
</Expander>
</ControlTemplate>
</Setter>
</Style>
</Styles>

41
StabilityMatrix.Avalonia/Controls/Inference/StackExpander.axaml.cs

@ -0,0 +1,41 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
[Transient]
public class StackExpander : TemplatedControl
{
public static readonly StyledProperty<bool> IsExpandedProperty = Expander
.IsExpandedProperty
.AddOwner<StackExpander>();
public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty = Expander
.ExpandDirectionProperty
.AddOwner<StackExpander>();
public static readonly StyledProperty<int> SpacingProperty = AvaloniaProperty.Register<StackCard, int>(
"Spacing",
8
);
public ExpandDirection ExpandDirection
{
get => GetValue(ExpandDirectionProperty);
set => SetValue(ExpandDirectionProperty, value);
}
public bool IsExpanded
{
get => GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}
public int Spacing
{
get => GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
}

0
StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml → StabilityMatrix.Avalonia/Controls/Inference/UpscalerCard.axaml

0
StabilityMatrix.Avalonia/Controls/UpscalerCard.axaml.cs → StabilityMatrix.Avalonia/Controls/Inference/UpscalerCard.axaml.cs

37
StabilityMatrix.Avalonia/Controls/LineDashFrame.cs

@ -12,8 +12,10 @@ public class LineDashFrame : Frame
{ {
protected override Type StyleKeyOverride { get; } = typeof(Frame); protected override Type StyleKeyOverride { get; } = typeof(Frame);
public static readonly StyledProperty<ISolidColorBrush> StrokeProperty = public static readonly StyledProperty<ISolidColorBrush> StrokeProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<LineDashFrame, ISolidColorBrush>("Stroke"); LineDashFrame,
ISolidColorBrush
>("Stroke");
public ISolidColorBrush Stroke public ISolidColorBrush Stroke
{ {
@ -21,8 +23,10 @@ public class LineDashFrame : Frame
set => SetValue(StrokeProperty, value); set => SetValue(StrokeProperty, value);
} }
public static readonly StyledProperty<double> StrokeThicknessProperty = public static readonly StyledProperty<double> StrokeThicknessProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<LineDashFrame, double>("StrokeThickness"); LineDashFrame,
double
>("StrokeThickness");
public double StrokeThickness public double StrokeThickness
{ {
@ -30,8 +34,10 @@ public class LineDashFrame : Frame
set => SetValue(StrokeThicknessProperty, value); set => SetValue(StrokeThicknessProperty, value);
} }
public static readonly StyledProperty<double> StrokeDashLineProperty = public static readonly StyledProperty<double> StrokeDashLineProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<LineDashFrame, double>("StrokeDashLine"); LineDashFrame,
double
>("StrokeDashLine");
public double StrokeDashLine public double StrokeDashLine
{ {
@ -39,8 +45,10 @@ public class LineDashFrame : Frame
set => SetValue(StrokeDashLineProperty, value); set => SetValue(StrokeDashLineProperty, value);
} }
public static readonly StyledProperty<double> StrokeDashSpaceProperty = public static readonly StyledProperty<double> StrokeDashSpaceProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<LineDashFrame, double>("StrokeDashSpace"); LineDashFrame,
double
>("StrokeDashSpace");
public double StrokeDashSpace public double StrokeDashSpace
{ {
@ -48,8 +56,10 @@ public class LineDashFrame : Frame
set => SetValue(StrokeDashSpaceProperty, value); set => SetValue(StrokeDashSpaceProperty, value);
} }
public static readonly StyledProperty<ISolidColorBrush> FillProperty = public static readonly StyledProperty<ISolidColorBrush> FillProperty = AvaloniaProperty.Register<
AvaloniaProperty.Register<LineDashFrame, ISolidColorBrush>("Fill"); LineDashFrame,
ISolidColorBrush
>("Fill");
public ISolidColorBrush Fill public ISolidColorBrush Fill
{ {
@ -82,17 +92,12 @@ public class LineDashFrame : Frame
/// <inheritdoc /> /// <inheritdoc />
public override void Render(DrawingContext context) public override void Render(DrawingContext context)
{ {
base.Render(context);
var width = Bounds.Width; var width = Bounds.Width;
var height = Bounds.Height; var height = Bounds.Height;
context.DrawRectangle(Fill, null, new Rect(0, 0, width, height)); context.DrawRectangle(Fill, null, new Rect(0, 0, width, height));
var dashPen = new Pen(Stroke, StrokeThickness) var dashPen = new Pen(Stroke, StrokeThickness) { DashStyle = new DashStyle(GetDashArray(width), 0) };
{
DashStyle = new DashStyle(GetDashArray(width), 0)
};
context.DrawLine(dashPen, new Point(0, 0), new Point(width, 0)); context.DrawLine(dashPen, new Point(0, 0), new Point(width, 0));
context.DrawLine(dashPen, new Point(0, height), new Point(width, height)); context.DrawLine(dashPen, new Point(0, height), new Point(width, height));

127
StabilityMatrix.Avalonia/Controls/ProgressRing.cs

@ -1,8 +1,10 @@
using System.Diagnostics.CodeAnalysis; using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
namespace StabilityMatrix.Avalonia.Controls; namespace StabilityMatrix.Avalonia.Controls;
@ -13,34 +15,11 @@ namespace StabilityMatrix.Avalonia.Controls;
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public class ProgressRing : RangeBase public class ProgressRing : RangeBase
{ {
public static readonly StyledProperty<bool> IsIndeterminateProperty = private Arc? fillArc;
ProgressBar.IsIndeterminateProperty.AddOwner<ProgressRing>();
public static readonly StyledProperty<bool> PreserveAspectProperty = public static readonly StyledProperty<bool> IsIndeterminateProperty = ProgressBar
AvaloniaProperty.Register<ProgressRing, bool>(nameof(PreserveAspect), true); .IsIndeterminateProperty
.AddOwner<ProgressRing>();
public static readonly StyledProperty<double> ValueAngleProperty =
AvaloniaProperty.Register<ProgressRing, double>(nameof(ValueAngle));
public static readonly StyledProperty<double> StartAngleProperty =
AvaloniaProperty.Register<ProgressRing, double>(nameof(StartAngle));
public static readonly StyledProperty<double> EndAngleProperty =
AvaloniaProperty.Register<ProgressRing, double>(nameof(EndAngle), 360);
static ProgressRing()
{
MinimumProperty.Changed.AddClassHandler<ProgressRing>(OnMinimumPropertyChanged);
MaximumProperty.Changed.AddClassHandler<ProgressRing>(OnMaximumPropertyChanged);
ValueProperty.Changed.AddClassHandler<ProgressRing>(OnValuePropertyChanged);
MaximumProperty.Changed.AddClassHandler<ProgressRing>(OnStartAnglePropertyChanged);
MaximumProperty.Changed.AddClassHandler<ProgressRing>(OnEndAnglePropertyChanged);
}
public ProgressRing()
{
UpdatePseudoClasses(IsIndeterminate, PreserveAspect);
}
public bool IsIndeterminate public bool IsIndeterminate
{ {
@ -48,35 +27,90 @@ public class ProgressRing : RangeBase
set => SetValue(IsIndeterminateProperty, value); set => SetValue(IsIndeterminateProperty, value);
} }
public static readonly StyledProperty<bool> PreserveAspectProperty = AvaloniaProperty.Register<ProgressRing, bool>(
nameof(PreserveAspect),
true
);
public bool PreserveAspect public bool PreserveAspect
{ {
get => GetValue(PreserveAspectProperty); get => GetValue(PreserveAspectProperty);
set => SetValue(PreserveAspectProperty, value); set => SetValue(PreserveAspectProperty, value);
} }
public double ValueAngle public static readonly StyledProperty<double> StrokeThicknessProperty = Shape
.StrokeThicknessProperty
.AddOwner<ProgressRing>();
public double StrokeThickness
{ {
get => GetValue(ValueAngleProperty); get => GetValue(StrokeThicknessProperty);
private set => SetValue(ValueAngleProperty, value); set => SetValue(StrokeThicknessProperty, value);
} }
public static readonly StyledProperty<double> StartAngleProperty = AvaloniaProperty.Register<ProgressRing, double>(
nameof(StartAngle)
);
public double StartAngle public double StartAngle
{ {
get => GetValue(StartAngleProperty); get => GetValue(StartAngleProperty);
set => SetValue(StartAngleProperty, value); set => SetValue(StartAngleProperty, value);
} }
public static readonly StyledProperty<double> SweepAngleProperty = AvaloniaProperty.Register<ProgressRing, double>(
nameof(SweepAngle)
);
public double SweepAngle
{
get => GetValue(SweepAngleProperty);
set => SetValue(SweepAngleProperty, value);
}
public static readonly StyledProperty<double> EndAngleProperty = AvaloniaProperty.Register<ProgressRing, double>(
nameof(EndAngle),
360
);
public double EndAngle public double EndAngle
{ {
get => GetValue(EndAngleProperty); get => GetValue(EndAngleProperty);
set => SetValue(EndAngleProperty, value); set => SetValue(EndAngleProperty, value);
} }
static ProgressRing()
{
AffectsRender<ProgressRing>(SweepAngleProperty, StartAngleProperty, EndAngleProperty);
ValueProperty.Changed.AddClassHandler<ProgressRing>(OnValuePropertyChanged);
SweepAngleProperty.Changed.AddClassHandler<ProgressRing>(OnSweepAnglePropertyChanged);
}
public ProgressRing()
{
UpdatePseudoClasses(IsIndeterminate, PreserveAspect);
}
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
fillArc = e.NameScope.Find<Arc>("PART_Fill");
if (fillArc is not null)
{
fillArc.StartAngle = StartAngle;
fillArc.SweepAngle = SweepAngle;
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
var e = change as AvaloniaPropertyChangedEventArgs<bool>; var e = change as AvaloniaPropertyChangedEventArgs<bool>;
if (e is null) return; if (e is null)
return;
if (e.Property == IsIndeterminateProperty) if (e.Property == IsIndeterminateProperty)
{ {
@ -88,9 +122,7 @@ public class ProgressRing : RangeBase
} }
} }
private void UpdatePseudoClasses( private void UpdatePseudoClasses(bool? isIndeterminate, bool? preserveAspect)
bool? isIndeterminate,
bool? preserveAspect)
{ {
if (isIndeterminate.HasValue) if (isIndeterminate.HasValue)
{ {
@ -103,28 +135,19 @@ public class ProgressRing : RangeBase
} }
} }
private static void OnMinimumPropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e)
{
sender.Minimum = (double) e.NewValue!;
}
private static void OnMaximumPropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e)
{
sender.Maximum = (double) e.NewValue!;
}
private static void OnValuePropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e) private static void OnValuePropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e)
{ {
sender.ValueAngle = ((double) e.NewValue! - sender.Minimum) * (sender.EndAngle - sender.StartAngle) / (sender.Maximum - sender.Minimum); sender.SweepAngle =
((double)e.NewValue! - sender.Minimum)
* (sender.EndAngle - sender.StartAngle)
/ (sender.Maximum - sender.Minimum);
} }
private static void OnStartAnglePropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e) private static void OnSweepAnglePropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e)
{ {
sender.StartAngle = (double) e.NewValue!; if (sender.fillArc is { } arc)
}
private static void OnEndAnglePropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e)
{ {
sender.EndAngle = (double) e.NewValue!; arc.SweepAngle = Math.Round(e.GetNewValue<double>());
}
} }
} }

124
StabilityMatrix.Avalonia/Controls/PropertyGrid/BetterPropertyGrid.cs

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.PropertyGrid.Services;
using JetBrains.Annotations;
using PropertyModels.ComponentModel;
using StabilityMatrix.Core.Extensions;
namespace StabilityMatrix.Avalonia.Controls;
/// <inheritdoc />
[PublicAPI]
public class BetterPropertyGrid : global::Avalonia.PropertyGrid.Controls.PropertyGrid
{
protected override Type StyleKeyOverride => typeof(global::Avalonia.PropertyGrid.Controls.PropertyGrid);
public static readonly StyledProperty<IEnumerable<string>> ExcludedCategoriesProperty = AvaloniaProperty.Register<
BetterPropertyGrid,
IEnumerable<string>
>("ExcludedCategories");
public IEnumerable<string> ExcludedCategories
{
get => GetValue(ExcludedCategoriesProperty);
set => SetValue(ExcludedCategoriesProperty, value);
}
public static readonly StyledProperty<IEnumerable<string>> IncludedCategoriesProperty = AvaloniaProperty.Register<
BetterPropertyGrid,
IEnumerable<string>
>("IncludedCategories");
public IEnumerable<string> IncludedCategories
{
get => GetValue(IncludedCategoriesProperty);
set => SetValue(IncludedCategoriesProperty, value);
}
static BetterPropertyGrid()
{
// Register factories
CellEditFactoryService.Default.AddFactory(new ToggleSwitchCellEditFactory());
// Initialize localization and name resolver
LocalizationService.Default.AddExtraService(new PropertyGridLocalizationService());
ExcludedCategoriesProperty
.Changed
.AddClassHandler<BetterPropertyGrid>(
(grid, args) =>
{
if (args.NewValue is IEnumerable<string> excludedCategories)
{
grid.FilterExcludeCategories(excludedCategories);
}
}
);
IncludedCategoriesProperty
.Changed
.AddClassHandler<BetterPropertyGrid>(
(grid, args) =>
{
if (args.NewValue is IEnumerable<string> includedCategories)
{
grid.FilterIncludeCategories(includedCategories);
}
}
);
}
public void FilterExcludeCategories(IEnumerable<string> excludedCategories)
{
// Get internal property `ViewModel` of internal type `PropertyGridViewModel`
var gridVm = this.GetProtectedProperty("ViewModel")!;
// Get public property `CategoryFilter`
var categoryFilter = gridVm.GetProtectedProperty<CheckedMaskModel>("CategoryFilter")!;
categoryFilter.BeginUpdate();
// Uncheck All, then check all except All
categoryFilter.UnCheck(categoryFilter.All);
foreach (var mask in categoryFilter.Masks.Where(m => m != categoryFilter.All))
{
categoryFilter.Check(mask);
}
// Uncheck excluded categories
foreach (var mask in excludedCategories)
{
categoryFilter.UnCheck(mask);
}
categoryFilter.EndUpdate();
}
public void FilterIncludeCategories(IEnumerable<string> includeCategories)
{
// Get internal property `ViewModel` of internal type `PropertyGridViewModel`
var gridVm = this.GetProtectedProperty("ViewModel")!;
// Get public property `CategoryFilter`
var categoryFilter = gridVm.GetProtectedProperty<CheckedMaskModel>("CategoryFilter")!;
categoryFilter.BeginUpdate();
// Uncheck non-included categories
foreach (var mask in categoryFilter.Masks.Where(m => !includeCategories.Contains(m)))
{
categoryFilter.UnCheck(mask);
}
categoryFilter.UnCheck(categoryFilter.All);
// Check included categories
foreach (var mask in includeCategories)
{
categoryFilter.Check(mask);
}
categoryFilter.EndUpdate();
}
}

35
StabilityMatrix.Avalonia/Controls/PropertyGrid/PropertyGridCultureData.cs

@ -0,0 +1,35 @@
using System;
using System.Globalization;
using PropertyModels.Localilzation;
using StabilityMatrix.Avalonia.Languages;
namespace StabilityMatrix.Avalonia.Controls;
internal class PropertyGridCultureData : ICultureData
{
/// <inheritdoc />
public bool Reload() => false;
/// <inheritdoc />
public CultureInfo Culture => Cultures.Current ?? Cultures.Default;
/// <inheritdoc />
public Uri Path => new("");
/// <inheritdoc />
public string this[string key]
{
get
{
if (Resources.ResourceManager.GetString(key) is { } result)
{
return result;
}
return key;
}
}
/// <inheritdoc />
public bool IsLoaded => true;
}

36
StabilityMatrix.Avalonia/Controls/PropertyGrid/PropertyGridLocalizationService.cs

@ -0,0 +1,36 @@
using System;
using PropertyModels.ComponentModel;
using PropertyModels.Localilzation;
using StabilityMatrix.Avalonia.Languages;
namespace StabilityMatrix.Avalonia.Controls;
/// <summary>
/// Implements <see cref="ILocalizationService"/> using static <see cref="Cultures"/>.
/// </summary>
internal class PropertyGridLocalizationService : MiniReactiveObject, ILocalizationService
{
/// <inheritdoc />
public ICultureData CultureData { get; } = new PropertyGridCultureData();
/// <inheritdoc />
public string this[string key] => CultureData[key];
/// <inheritdoc />
public event EventHandler? OnCultureChanged;
/// <inheritdoc />
public ILocalizationService[] GetExtraServices() => Array.Empty<ILocalizationService>();
/// <inheritdoc />
public void AddExtraService(ILocalizationService service) { }
/// <inheritdoc />
public void RemoveExtraService(ILocalizationService service) { }
/// <inheritdoc />
public ICultureData[] GetCultures() => new[] { CultureData };
/// <inheritdoc />
public void SelectCulture(string cultureName) { }
}

60
StabilityMatrix.Avalonia/Controls/PropertyGrid/ToggleSwitchCellEditFactory.cs

@ -0,0 +1,60 @@
using Avalonia.Controls;
using Avalonia.PropertyGrid.Controls;
using Avalonia.PropertyGrid.Controls.Factories;
using Avalonia.PropertyGrid.Localization;
namespace StabilityMatrix.Avalonia.Controls;
internal class ToggleSwitchCellEditFactory : AbstractCellEditFactory
{
// make this extend factor only effect on TestExtendPropertyGrid
public override bool Accept(object accessToken)
{
return accessToken is BetterPropertyGrid;
}
public override Control? HandleNewProperty(PropertyCellContext context)
{
var propertyDescriptor = context.Property;
var target = context.Target;
if (propertyDescriptor.PropertyType != typeof(bool))
{
return null;
}
var control = new ToggleSwitch();
control.SetLocalizeBinding(ToggleSwitch.OnContentProperty, "On");
control.SetLocalizeBinding(ToggleSwitch.OffContentProperty, "Off");
control.IsCheckedChanged += (s, e) =>
{
SetAndRaise(context, control, control.IsChecked);
};
return control;
}
public override bool HandlePropertyChanged(PropertyCellContext context)
{
var propertyDescriptor = context.Property;
var target = context.Target;
var control = context.CellEdit;
if (propertyDescriptor.PropertyType != typeof(bool))
{
return false;
}
ValidateProperty(control, propertyDescriptor, target);
if (control is ToggleSwitch ts)
{
ts.IsChecked = (bool)(propertyDescriptor.GetValue(target) ?? false);
return true;
}
return false;
}
}

104
StabilityMatrix.Avalonia/Controls/SelectImageCard.axaml

@ -1,104 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls"
xmlns:mocks="using:StabilityMatrix.Avalonia.DesignData"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
x:DataType="vmInference:SelectImageCardViewModel">
<Design.PreviewWith>
<Panel Width="600" Height="500">
<Grid ColumnDefinitions="*,*" MaxHeight="400">
<controls:SelectImageCard
Margin="4"
DataContext="{x:Static mocks:DesignData.SelectImageCardViewModel}" />
<controls:SelectImageCard
Grid.Column="1"
Margin="4"
DataContext="{x:Static mocks:DesignData.SelectImageCardViewModel_WithImage}" />
</Grid>
</Panel>
</Design.PreviewWith>
<Style Selector="controls|SelectImageCard">
<!-- Set Defaults -->
<Setter Property="Padding" Value="12"/>
<Setter Property="Template">
<ControlTemplate>
<controls:Card
IsCardVisualsEnabled="True"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}">
<!-- Background frame -->
<ExperimentalAcrylicBorder
VerticalAlignment="Stretch"
Material="{StaticResource OpaqueDarkAcrylicMaterial}"
CornerRadius="4">
<Panel>
<!-- Image -->
<controls:BetterAdvancedImage
CornerRadius="4"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
CurrentImage="{Binding CurrentBitmap, Mode=OneWayToSource}"
IsVisible="{Binding !IsSelectionAvailable}"
Source="{Binding ImageSource}"/>
<!-- Selection Prompt -->
<controls:LineDashFrame
IsVisible="{Binding IsSelectionAvailable}"
Stroke="DimGray"
StrokeThickness="3"
StrokeDashLine="6"
StrokeDashSpace="6">
<Grid
Cursor="Hand"
RowDefinitions="*,Auto,Auto"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<ui:SymbolIcon
FontSize="28"
Symbol="ImageCopyFilled"/>
<TextBlock
Grid.Row="1"
FontSize="{TemplateBinding FontSize}"
Text="Drag an image here"
TextWrapping="WrapWithOverflow"
Foreground="DarkGray">
</TextBlock>
<StackPanel
Grid.Row="2"
Margin="0,4,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<TextBlock
FontSize="{TemplateBinding FontSize}"
VerticalAlignment="Center"
Foreground="DarkGray"
Text="or"/>
<Button
FontSize="{TemplateBinding FontSize}"
Command="{Binding SelectImageFromFilePickerCommand}"
Content="Browse"
Margin="4,0"
Padding="4"/>
</StackPanel>
</Grid>
</controls:LineDashFrame>
</Panel>
</ExperimentalAcrylicBorder>
</controls:Card>
</ControlTemplate>
</Setter>
</Style>
</Styles>

6
StabilityMatrix.Avalonia/Controls/SelectImageCard.axaml.cs

@ -1,6 +0,0 @@
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
[Transient]
public class SelectImageCard : DropTargetTemplatedControlBase { }

96
StabilityMatrix.Avalonia/Controls/StackExpander.axaml

@ -1,96 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
xmlns:local="clr-namespace:StabilityMatrix.Avalonia"
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
x:DataType="vmInference:StackExpanderViewModel">
<Design.PreviewWith>
<Grid Width="500" Height="800">
<StackPanel>
<controls:StackExpander
DataContext="{x:Static mocks:DesignData.StackExpanderViewModel}"/>
</StackPanel>
</Grid>
</Design.PreviewWith>
<Style Selector="controls|StackExpander">
<!-- Set Defaults -->
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<ControlTemplate>
<Expander
VerticalContentAlignment="Top"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
HorizontalContentAlignment="Stretch"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Expander.Styles>
<Style Selector="Expander /template/ ToggleButton#PART_toggle">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</Expander.Styles>
<Expander.Header>
<Grid ColumnDefinitions="Auto,Auto,*,Auto">
<ToggleSwitch
Margin="2,0,0,2"
IsChecked="{Binding IsEnabled}"
VerticalContentAlignment="Center"
OnContent=""
OffContent=""/>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding TitleExtra}"
IsVisible="{Binding TitleExtra, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding Title}"/>
<Button
IsVisible="{Binding IsEditEnabled}"
Command="{Binding RemoveFromParentListCommand}"
Classes="transparent-red"
Grid.Column="3"
Padding="10,4"
HorizontalAlignment="Right">
<fluentIcons:SymbolIcon Symbol="Delete" FontSize="16" />
</Button>
</Grid>
</Expander.Header>
<Panel>
<ItemsControl
VerticalAlignment="Top"
ItemsSource="{Binding Cards}">
<ItemsControl.Styles>
<Style Selector="controls|Card">
<Setter Property="IsCardVisualsEnabled" Value="False"/>
</Style>
</ItemsControl.Styles>
<ItemsControl.DataTemplates>
<local:ViewLocator/>
</ItemsControl.DataTemplates>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
x:Name="PART_ItemsPanel"
VerticalAlignment="Top"
Spacing="{TemplateBinding Spacing}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Panel>
</Expander>
</ControlTemplate>
</Setter>
</Style>
</Styles>

20
StabilityMatrix.Avalonia/Controls/StackExpander.axaml.cs

@ -1,20 +0,0 @@
using Avalonia;
using Avalonia.Controls.Primitives;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Controls;
[Transient]
public class StackExpander : TemplatedControl
{
public static readonly StyledProperty<int> SpacingProperty = AvaloniaProperty.Register<
StackCard,
int
>("Spacing", 8);
public int Spacing
{
get => GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
}

22
StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs

@ -0,0 +1,22 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
public class CustomStringFormatConverter<T>([StringSyntax("CompositeFormat")] string format) : IValueConverter
where T : IFormatProvider, new()
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value is null ? null : string.Format(new T(), format, value);
}
/// <inheritdoc />
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value is null ? null : throw new NotImplementedException();
}
}

31
StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs

@ -0,0 +1,31 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
/// <summary>
/// Converts an index to index + 1
/// </summary>
public class IndexPlusOneConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is int i)
{
return i + 1;
}
return value;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is int i)
{
return i - 1;
}
return value;
}
}

41
StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs

@ -0,0 +1,41 @@
using System;
using Size = StabilityMatrix.Core.Helper.Size;
namespace StabilityMatrix.Avalonia.Converters;
public class MemoryBytesFormatter : ICustomFormatter, IFormatProvider
{
/// <inheritdoc />
public object? GetFormat(Type? formatType)
{
return formatType == typeof(ICustomFormatter) ? this : null;
}
/// <inheritdoc />
public string Format(string? format, object? arg, IFormatProvider? formatProvider)
{
if (format == null || !format.Trim().StartsWith('M'))
{
if (arg is IFormattable formatArg)
{
return formatArg.ToString(format, formatProvider);
}
return arg?.ToString() ?? string.Empty;
}
var value = Convert.ToUInt64(arg);
var result = format.Trim().EndsWith("10", StringComparison.OrdinalIgnoreCase)
? Size.FormatBase10Bytes(value)
: Size.FormatBytes(value);
// Strip i if not Mi
if (!format.Trim().Contains('I', StringComparison.OrdinalIgnoreCase))
{
result = result.Replace("i", string.Empty, StringComparison.OrdinalIgnoreCase);
}
return result;
}
}

84
StabilityMatrix.Avalonia/Converters/NullableDefaultNumericConverter.cs

@ -0,0 +1,84 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
/// <summary>
/// Converts a possibly boxed nullable value type to its default value
/// </summary>
public class NullableDefaultNumericConverter<TSource, TTarget> : IValueConverter
where TSource : unmanaged, INumber<TSource>
where TTarget : unmanaged, INumber<TTarget>
{
public ReturnBehavior NanHandling { get; set; } = ReturnBehavior.DefaultValue;
/// <summary>
/// Unboxes a nullable value type
/// </summary>
private TSource Unbox(TTarget? value)
{
if (!value.HasValue)
{
return default;
}
if (TTarget.IsNaN(value.Value))
{
return NanHandling switch
{
ReturnBehavior.DefaultValue => default,
ReturnBehavior.Throw => throw new InvalidCastException("Cannot convert NaN to a numeric type"),
_
=> throw new InvalidEnumArgumentException(
nameof(NanHandling),
(int)NanHandling,
typeof(ReturnBehavior)
)
};
}
return (TSource)System.Convert.ChangeType(value.Value, typeof(TSource));
}
/// <summary>
/// Convert a value type to a nullable value type
/// </summary>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (targetType != typeof(TTarget?) && !targetType.IsAssignableTo(typeof(TTarget)))
{
// ReSharper disable once LocalizableElement
throw new ArgumentException(
$"Convert Target type {targetType.Name} must be assignable to {typeof(TTarget).Name}"
);
}
return (TTarget?)System.Convert.ChangeType(value, typeof(TTarget));
}
/// <summary>
/// Convert a nullable value type to a value type
/// </summary>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (!targetType.IsAssignableTo(typeof(TSource)))
{
// ReSharper disable once LocalizableElement
throw new ArgumentException(
$"ConvertBack Target type {targetType.Name} must be assignable to {typeof(TSource).Name}"
);
}
return Unbox((TTarget?)value);
}
public enum ReturnBehavior
{
DefaultValue,
Throw
}
}

6
StabilityMatrix.Avalonia/Converters/NullableDefaultNumericConverters.cs

@ -0,0 +1,6 @@
namespace StabilityMatrix.Avalonia.Converters;
public static class NullableDefaultNumericConverters
{
public static readonly NullableDefaultNumericConverter<int, decimal> IntToDecimal = new();
}

8
StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs

@ -1,4 +1,5 @@
using Avalonia.Data.Converters; using System;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters; namespace StabilityMatrix.Avalonia.Converters;
@ -7,4 +8,9 @@ public static class StringFormatConverters
private static StringFormatValueConverter? _decimalConverter; private static StringFormatValueConverter? _decimalConverter;
public static StringFormatValueConverter Decimal => public static StringFormatValueConverter Decimal =>
_decimalConverter ??= new StringFormatValueConverter("{0:D}", null); _decimalConverter ??= new StringFormatValueConverter("{0:D}", null);
private static readonly Lazy<IValueConverter> MemoryBytesConverterLazy =
new(() => new CustomStringFormatConverter<MemoryBytesFormatter>("{0:M}"));
public static IValueConverter MemoryBytes => MemoryBytesConverterLazy.Value;
} }

155
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -1,11 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using AvaloniaEdit.Utils; using AvaloniaEdit.Utils;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -21,11 +23,11 @@ using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.Progress;
using StabilityMatrix.Avalonia.ViewModels.Inference; using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Avalonia.ViewModels.Inference.Modules; using StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
using StabilityMatrix.Avalonia.ViewModels.Inference.Video; using StabilityMatrix.Avalonia.ViewModels.Inference.Video;
using StabilityMatrix.Avalonia.ViewModels.OutputsPage; using StabilityMatrix.Avalonia.ViewModels.OutputsPage;
using StabilityMatrix.Avalonia.ViewModels.Progress;
using StabilityMatrix.Avalonia.ViewModels.Settings; using StabilityMatrix.Avalonia.ViewModels.Settings;
using StabilityMatrix.Core.Api; using StabilityMatrix.Core.Api;
using StabilityMatrix.Core.Database; using StabilityMatrix.Core.Database;
@ -43,6 +45,8 @@ using StabilityMatrix.Core.Models.Update;
using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Services;
using StabilityMatrix.Core.Updater; using StabilityMatrix.Core.Updater;
using CivitAiBrowserViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.CivitAiBrowserViewModel;
using HuggingFacePageViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.HuggingFacePageViewModel;
namespace StabilityMatrix.Avalonia.DesignData; namespace StabilityMatrix.Avalonia.DesignData;
@ -77,10 +81,7 @@ public static class DesignData
Id = activePackageId, Id = activePackageId,
DisplayName = "My Installed Package", DisplayName = "My Installed Package",
PackageName = "stable-diffusion-webui", PackageName = "stable-diffusion-webui",
Version = new InstalledPackageVersion Version = new InstalledPackageVersion { InstalledReleaseVersion = "v1.0.0" },
{
InstalledReleaseVersion = "v1.0.0"
},
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui", LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now LastUpdateCheck = DateTimeOffset.Now
}, },
@ -92,8 +93,7 @@ public static class DesignData
Version = new InstalledPackageVersion Version = new InstalledPackageVersion
{ {
InstalledBranch = "master", InstalledBranch = "master",
InstalledCommitSha = InstalledCommitSha = "abc12uwu345568972abaedf7g7e679a98879e879f87ga8"
"abc12uwu345568972abaedf7g7e679a98879e879f87ga8"
}, },
LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui", LibraryPath = $"Packages{Path.DirectorySeparatorChar}example-webui",
LastUpdateCheck = DateTimeOffset.Now LastUpdateCheck = DateTimeOffset.Now
@ -126,7 +126,8 @@ public static class DesignData
.AddSingleton<IInferenceClientManager, MockInferenceClientManager>() .AddSingleton<IInferenceClientManager, MockInferenceClientManager>()
.AddSingleton<ICompletionProvider, MockCompletionProvider>() .AddSingleton<ICompletionProvider, MockCompletionProvider>()
.AddSingleton<IModelIndexService, MockModelIndexService>() .AddSingleton<IModelIndexService, MockModelIndexService>()
.AddSingleton<IImageIndexService, MockImageIndexService>(); .AddSingleton<IImageIndexService, MockImageIndexService>()
.AddSingleton<IMetadataImportService, MetadataImportService>();
// Placeholder services that nobody should need during design time // Placeholder services that nobody should need during design time
services services
@ -149,6 +150,7 @@ public static class DesignData
var modelFinder = Services.GetRequiredService<ModelFinder>(); var modelFinder = Services.GetRequiredService<ModelFinder>();
var packageFactory = Services.GetRequiredService<IPackageFactory>(); var packageFactory = Services.GetRequiredService<IPackageFactory>();
var notificationService = Services.GetRequiredService<INotificationService>(); var notificationService = Services.GetRequiredService<INotificationService>();
var modelImportService = Services.GetRequiredService<IMetadataImportService>();
LaunchOptionsViewModel = Services.GetRequiredService<LaunchOptionsViewModel>(); LaunchOptionsViewModel = Services.GetRequiredService<LaunchOptionsViewModel>();
LaunchOptionsViewModel.Cards = new[] LaunchOptionsViewModel.Cards = new[]
@ -185,7 +187,7 @@ public static class DesignData
CheckpointsPageViewModel.CheckpointFoldersCache, CheckpointsPageViewModel.CheckpointFoldersCache,
new CheckpointFolder[] new CheckpointFolder[]
{ {
new(settingsManager, downloadService, modelFinder, notificationService) new(settingsManager, downloadService, modelFinder, notificationService, modelImportService)
{ {
DirectoryPath = "Models/StableDiffusion", DirectoryPath = "Models/StableDiffusion",
DisplayedCheckpointFiles = new ObservableCollectionExtended<CheckpointFile>() DisplayedCheckpointFiles = new ObservableCollectionExtended<CheckpointFile>()
@ -212,14 +214,10 @@ public static class DesignData
TrainedWords = ["aurora", "lightning"] TrainedWords = ["aurora", "lightning"]
} }
}, },
new() new() { FilePath = "~/Models/Lora/model.safetensors", Title = "Some model" },
{
FilePath = "~/Models/Lora/model.safetensors",
Title = "Some model"
},
}, },
}, },
new(settingsManager, downloadService, modelFinder, notificationService) new(settingsManager, downloadService, modelFinder, notificationService, modelImportService)
{ {
Title = "Lora", Title = "Lora",
DirectoryPath = "Packages/Lora", DirectoryPath = "Packages/Lora",
@ -300,8 +298,7 @@ public static class DesignData
); );
}*/ }*/
CheckpointBrowserViewModel.ModelCards = CivitAiBrowserViewModel.ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>
new ObservableCollection<CheckpointBrowserCardViewModel>
{ {
dialogFactory.Get<CheckpointBrowserCardViewModel>(vm => dialogFactory.Get<CheckpointBrowserCardViewModel>(vm =>
{ {
@ -310,9 +307,7 @@ public static class DesignData
Name = "BB95 Furry Mix", Name = "BB95 Furry Mix",
Description = "A furry mix of BB95", Description = "A furry mix of BB95",
Stats = new CivitModelStats { Rating = 3.5, RatingCount = 24 }, Stats = new CivitModelStats { Rating = 3.5, RatingCount = 24 },
ModelVersions = [ ModelVersions = [new() { Name = "v1.2.2-Inpainting" }],
new() { Name = "v1.2.2-Inpainting" }
],
Creator = new CivitCreator Creator = new CivitCreator
{ {
Image = "https://gravatar.com/avatar/fe74084ae8a081dc2283f5bde4736756ad?f=y&d=retro", Image = "https://gravatar.com/avatar/fe74084ae8a081dc2283f5bde4736756ad?f=y&d=retro",
@ -327,7 +322,8 @@ public static class DesignData
Name = "Another Model", Name = "Another Model",
Description = "A mix of example", Description = "A mix of example",
Stats = new CivitModelStats { Rating = 5, RatingCount = 3500 }, Stats = new CivitModelStats { Rating = 5, RatingCount = 3500 },
ModelVersions = [ ModelVersions =
[
new() new()
{ {
Name = "v1.2.2-Inpainting", Name = "v1.2.2-Inpainting",
@ -336,7 +332,8 @@ public static class DesignData
new() new()
{ {
Nsfw = "None", Nsfw = "None",
Url = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/" Url =
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/"
+ "78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg" + "78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg"
} }
} }
@ -377,15 +374,13 @@ public static class DesignData
new() { FilePath = "~/Models/Lora/model.safetensors", Title = "Some model" } new() { FilePath = "~/Models/Lora/model.safetensors", Title = "Some model" }
}; };
ProgressManagerViewModel.ProgressItems.AddRange( ProgressManagerViewModel
.ProgressItems
.AddRange(
new ProgressItemViewModelBase[] new ProgressItemViewModelBase[]
{ {
new ProgressItemViewModel( new ProgressItemViewModel(
new ProgressItem( new ProgressItem(Guid.NewGuid(), "Test File.exe", new ProgressReport(0.5f, "Downloading..."))
Guid.NewGuid(),
"Test File.exe",
new ProgressReport(0.5f, "Downloading...")
)
), ),
new MockDownloadProgressItemViewModel( new MockDownloadProgressItemViewModel(
"Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe" "Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe"
@ -402,8 +397,7 @@ public static class DesignData
UpdateViewModel = Services.GetRequiredService<UpdateViewModel>(); UpdateViewModel = Services.GetRequiredService<UpdateViewModel>();
UpdateViewModel.CurrentVersionText = "v2.0.0"; UpdateViewModel.CurrentVersionText = "v2.0.0";
UpdateViewModel.NewVersionText = "v2.0.1"; UpdateViewModel.NewVersionText = "v2.0.1";
UpdateViewModel.ReleaseNotes = UpdateViewModel.ReleaseNotes = "## v2.0.1\n- Fixed a bug\n- Added a feature\n- Removed a feature";
"## v2.0.1\n- Fixed a bug\n- Added a feature\n- Removed a feature";
isInitialized = true; isInitialized = true;
} }
@ -420,14 +414,15 @@ public static class DesignData
public static ServiceManager<ViewModelBase> DialogFactory => public static ServiceManager<ViewModelBase> DialogFactory =>
Services.GetRequiredService<ServiceManager<ViewModelBase>>(); Services.GetRequiredService<ServiceManager<ViewModelBase>>();
public static MainWindowViewModel MainWindowViewModel => public static MainWindowViewModel MainWindowViewModel => Services.GetRequiredService<MainWindowViewModel>();
Services.GetRequiredService<MainWindowViewModel>();
public static FirstLaunchSetupViewModel FirstLaunchSetupViewModel => public static FirstLaunchSetupViewModel FirstLaunchSetupViewModel =>
Services.GetRequiredService<FirstLaunchSetupViewModel>(); Services.GetRequiredService<FirstLaunchSetupViewModel>();
public static LaunchPageViewModel LaunchPageViewModel => public static LaunchPageViewModel LaunchPageViewModel => Services.GetRequiredService<LaunchPageViewModel>();
Services.GetRequiredService<LaunchPageViewModel>();
public static HuggingFacePageViewModel HuggingFacePageViewModel =>
Services.GetRequiredService<HuggingFacePageViewModel>();
public static OutputsPageViewModel OutputsPageViewModel public static OutputsPageViewModel OutputsPageViewModel
{ {
@ -458,10 +453,7 @@ public static class DesignData
vm.SetPackages(settings.Settings.InstalledPackages); vm.SetPackages(settings.Settings.InstalledPackages);
vm.SetUnknownPackages( vm.SetUnknownPackages(
new InstalledPackage[] new InstalledPackage[] { UnknownInstalledPackage.FromDirectoryName("sd-unknown-with-long-name"), }
{
UnknownInstalledPackage.FromDirectoryName("sd-unknown-with-long-name"),
}
); );
vm.PackageCards[0].IsUpdateAvailable = true; vm.PackageCards[0].IsUpdateAvailable = true;
@ -476,14 +468,12 @@ public static class DesignData
public static NewCheckpointsPageViewModel NewCheckpointsPageViewModel => public static NewCheckpointsPageViewModel NewCheckpointsPageViewModel =>
Services.GetRequiredService<NewCheckpointsPageViewModel>(); Services.GetRequiredService<NewCheckpointsPageViewModel>();
public static SettingsViewModel SettingsViewModel => public static SettingsViewModel SettingsViewModel => Services.GetRequiredService<SettingsViewModel>();
Services.GetRequiredService<SettingsViewModel>();
public static InferenceSettingsViewModel InferenceSettingsViewModel => public static InferenceSettingsViewModel InferenceSettingsViewModel =>
Services.GetRequiredService<InferenceSettingsViewModel>(); Services.GetRequiredService<InferenceSettingsViewModel>();
public static MainSettingsViewModel MainSettingsViewModel => public static MainSettingsViewModel MainSettingsViewModel => Services.GetRequiredService<MainSettingsViewModel>();
Services.GetRequiredService<MainSettingsViewModel>();
public static AccountSettingsViewModel AccountSettingsViewModel => public static AccountSettingsViewModel AccountSettingsViewModel =>
Services.GetRequiredService<AccountSettingsViewModel>(); Services.GetRequiredService<AccountSettingsViewModel>();
@ -519,6 +509,9 @@ public static class DesignData
} }
} }
public static CivitAiBrowserViewModel CivitAiBrowserViewModel =>
Services.GetRequiredService<CivitAiBrowserViewModel>();
public static CheckpointBrowserViewModel CheckpointBrowserViewModel => public static CheckpointBrowserViewModel CheckpointBrowserViewModel =>
Services.GetRequiredService<CheckpointBrowserViewModel>(); Services.GetRequiredService<CheckpointBrowserViewModel>();
@ -564,10 +557,7 @@ The gallery images are often inpainted, but you will get something very similar
} }
} }
}; };
var sampleViewModel = new ModelVersionViewModel( var sampleViewModel = new ModelVersionViewModel(new HashSet<string> { "ABCD" }, sampleCivitVersions[0]);
new HashSet<string> { "ABCD" },
sampleCivitVersions[0]
);
// Sample data for dialogs // Sample data for dialogs
vm.Versions = new List<ModelVersionViewModel> { sampleViewModel }; vm.Versions = new List<ModelVersionViewModel> { sampleViewModel };
@ -579,8 +569,7 @@ The gallery images are often inpainted, but you will get something very similar
public static OneClickInstallViewModel OneClickInstallViewModel => public static OneClickInstallViewModel OneClickInstallViewModel =>
Services.GetRequiredService<OneClickInstallViewModel>(); Services.GetRequiredService<OneClickInstallViewModel>();
public static InferenceViewModel InferenceViewModel => public static InferenceViewModel InferenceViewModel => Services.GetRequiredService<InferenceViewModel>();
Services.GetRequiredService<InferenceViewModel>();
public static SelectDataDirectoryViewModel SelectDataDirectoryViewModel => public static SelectDataDirectoryViewModel SelectDataDirectoryViewModel =>
Services.GetRequiredService<SelectDataDirectoryViewModel>(); Services.GetRequiredService<SelectDataDirectoryViewModel>();
@ -618,14 +607,10 @@ The gallery images are often inpainted, but you will get something very similar
public static PythonPackagesViewModel PythonPackagesViewModel => public static PythonPackagesViewModel PythonPackagesViewModel =>
DialogFactory.Get<PythonPackagesViewModel>(vm => DialogFactory.Get<PythonPackagesViewModel>(vm =>
{ {
vm.AddPackages( vm.AddPackages(new PipPackageInfo("pip", "1.0.0"), new PipPackageInfo("torch", "2.1.0+cu121"));
new PipPackageInfo("pip", "1.0.0"),
new PipPackageInfo("torch", "2.1.0+cu121")
);
}); });
public static LykosLoginViewModel LykosLoginViewModel => public static LykosLoginViewModel LykosLoginViewModel => DialogFactory.Get<LykosLoginViewModel>();
DialogFactory.Get<LykosLoginViewModel>();
public static OAuthConnectViewModel OAuthConnectViewModel => public static OAuthConnectViewModel OAuthConnectViewModel =>
DialogFactory.Get<OAuthConnectViewModel>(vm => DialogFactory.Get<OAuthConnectViewModel>(vm =>
@ -654,14 +639,26 @@ The gallery images are often inpainted, but you will get something very similar
vm.OutputProgress.Text = "Sampler 10/30"; vm.OutputProgress.Text = "Sampler 10/30";
}); });
public static InferenceImageToImageViewModel InferenceImageToImageViewModel =>
DialogFactory.Get<InferenceImageToImageViewModel>();
public static InferenceImageUpscaleViewModel InferenceImageUpscaleViewModel => public static InferenceImageUpscaleViewModel InferenceImageUpscaleViewModel =>
DialogFactory.Get<InferenceImageUpscaleViewModel>(); DialogFactory.Get<InferenceImageUpscaleViewModel>();
public static PackageImportViewModel PackageImportViewModel => public static PackageImportViewModel PackageImportViewModel => DialogFactory.Get<PackageImportViewModel>();
DialogFactory.Get<PackageImportViewModel>();
public static RefreshBadgeViewModel RefreshBadgeViewModel => new() { State = ProgressState.Success };
public static RefreshBadgeViewModel RefreshBadgeViewModel => public static PropertyGridViewModel PropertyGridViewModel =>
new() { State = ProgressState.Success }; DialogFactory.Get<PropertyGridViewModel>(vm =>
{
vm.SelectedObject = new INotifyPropertyChanged[]
{
new MockPropertyGridObject(),
new MockPropertyGridObjectAlt()
};
vm.ExcludeCategories = ["Excluded Category"];
});
public static SeedCardViewModel SeedCardViewModel => new(); public static SeedCardViewModel SeedCardViewModel => new();
public static SvdImgToVidConditioningViewModel SvdImgToVidConditioningViewModel => new(); public static SvdImgToVidConditioningViewModel SvdImgToVidConditioningViewModel => new();
@ -723,8 +720,7 @@ The gallery images are often inpainted, but you will get something very similar
); );
}); });
public static ImageFolderCardViewModel ImageFolderCardViewModel => public static ImageFolderCardViewModel ImageFolderCardViewModel => DialogFactory.Get<ImageFolderCardViewModel>();
DialogFactory.Get<ImageFolderCardViewModel>();
public static FreeUCardViewModel FreeUCardViewModel => DialogFactory.Get<FreeUCardViewModel>(); public static FreeUCardViewModel FreeUCardViewModel => DialogFactory.Get<FreeUCardViewModel>();
@ -755,7 +751,7 @@ The gallery images are often inpainted, but you will get something very similar
public static StackEditableCardViewModel StackEditableCardViewModel => public static StackEditableCardViewModel StackEditableCardViewModel =>
DialogFactory.Get<StackEditableCardViewModel>(vm => DialogFactory.Get<StackEditableCardViewModel>(vm =>
{ {
vm.AddCards(StackExpanderViewModel, StackExpanderViewModel); vm.AddCards(StackExpanderViewModel, StackExpanderViewModel2);
}); });
public static StackExpanderViewModel StackExpanderViewModel => public static StackExpanderViewModel StackExpanderViewModel =>
@ -766,11 +762,18 @@ The gallery images are often inpainted, but you will get something very similar
vm.OnContainerIndexChanged(0); vm.OnContainerIndexChanged(0);
}); });
public static UpscalerCardViewModel UpscalerCardViewModel => public static StackExpanderViewModel StackExpanderViewModel2 =>
DialogFactory.Get<UpscalerCardViewModel>(); DialogFactory.Get<StackExpanderViewModel>(vm =>
{
vm.Title = "Hires Fix";
vm.IsSettingsEnabled = true;
vm.AddCards(UpscalerCardViewModel, SamplerCardViewModel);
vm.OnContainerIndexChanged(1);
});
public static UpscalerCardViewModel UpscalerCardViewModel => DialogFactory.Get<UpscalerCardViewModel>();
public static BatchSizeCardViewModel BatchSizeCardViewModel => public static BatchSizeCardViewModel BatchSizeCardViewModel => DialogFactory.Get<BatchSizeCardViewModel>();
DialogFactory.Get<BatchSizeCardViewModel>();
public static BatchSizeCardViewModel BatchSizeCardViewModelWithIndexOption => public static BatchSizeCardViewModel BatchSizeCardViewModelWithIndexOption =>
DialogFactory.Get<BatchSizeCardViewModel>(vm => DialogFactory.Get<BatchSizeCardViewModel>(vm =>
@ -822,14 +825,12 @@ The gallery images are often inpainted, but you will get something very similar
vm.Resource = ComfyUpscaler.DefaultDownloadableModels[0].DownloadableResource!.Value; vm.Resource = ComfyUpscaler.DefaultDownloadableModels[0].DownloadableResource!.Value;
}); });
public static SharpenCardViewModel SharpenCardViewModel => public static SharpenCardViewModel SharpenCardViewModel => DialogFactory.Get<SharpenCardViewModel>();
DialogFactory.Get<SharpenCardViewModel>();
public static InferenceConnectionHelpViewModel InferenceConnectionHelpViewModel => public static InferenceConnectionHelpViewModel InferenceConnectionHelpViewModel =>
DialogFactory.Get<InferenceConnectionHelpViewModel>(); DialogFactory.Get<InferenceConnectionHelpViewModel>();
public static SelectImageCardViewModel SelectImageCardViewModel => public static SelectImageCardViewModel SelectImageCardViewModel => DialogFactory.Get<SelectImageCardViewModel>();
DialogFactory.Get<SelectImageCardViewModel>();
public static SelectImageCardViewModel SelectImageCardViewModel_WithImage => public static SelectImageCardViewModel SelectImageCardViewModel_WithImage =>
DialogFactory.Get<SelectImageCardViewModel>(vm => DialogFactory.Get<SelectImageCardViewModel>(vm =>
@ -842,14 +843,12 @@ The gallery images are often inpainted, but you will get something very similar
}); });
public static ImageSource SampleImageSource => public static ImageSource SampleImageSource =>
new( new(new Uri("https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500"))
new Uri( {
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500" Label = "Test Image"
) };
);
public static ControlNetCardViewModel ControlNetCardViewModel => public static ControlNetCardViewModel ControlNetCardViewModel => DialogFactory.Get<ControlNetCardViewModel>();
DialogFactory.Get<ControlNetCardViewModel>();
public static Indexer Types { get; } = new(); public static Indexer Types { get; } = new();
@ -859,9 +858,7 @@ The gallery images are often inpainted, but you will get something very similar
{ {
get get
{ {
var type = var type = Type.GetType(typeName) ?? throw new ArgumentException($"Type {typeName} not found");
Type.GetType(typeName)
?? throw new ArgumentException($"Type {typeName} not found");
try try
{ {
return Services.GetService(type); return Services.GetService(type);

30
StabilityMatrix.Avalonia/DesignData/MockMetadataImportService.cs

@ -0,0 +1,30 @@
using System;
using System.Threading.Tasks;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.DesignData;
public class MockMetadataImportService : IMetadataImportService
{
public Task ScanDirectoryForMissingInfo(DirectoryPath directory, IProgress<ProgressReport>? progress = null)
{
return Task.CompletedTask;
}
public Task<ConnectedModelInfo?> GetMetadataForFile(
FilePath filePath,
IProgress<ProgressReport>? progress = null,
bool forceReimport = false
)
{
return null;
}
public Task UpdateExistingMetadata(DirectoryPath directory, IProgress<ProgressReport>? progress = null)
{
return Task.CompletedTask;
}
}

54
StabilityMatrix.Avalonia/DesignData/MockPropertyGridObject.cs

@ -0,0 +1,54 @@
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using PropertyModels.ComponentModel;
using StabilityMatrix.Avalonia.Languages;
#pragma warning disable CS0657 // Not a valid attribute location for this declaration
namespace StabilityMatrix.Avalonia.DesignData;
public partial class MockPropertyGridObject : ObservableObject
{
[ObservableProperty]
private string? stringProperty;
[ObservableProperty]
private int intProperty;
[ObservableProperty]
[property: Trackable(0, 50, Increment = 1, FormatString = "{0:0}")]
private int intRange = 10;
[ObservableProperty]
[property: Trackable(0d, 1d, Increment = 0.01, FormatString = "{0:P0}")]
private double floatPercentRange = 0.25;
[ObservableProperty]
[property: DisplayName("Int Custom Name")]
private int intCustomNameProperty = 42;
[ObservableProperty]
[property: DisplayName(nameof(Resources.Label_Language))]
private int? intLocalizedNameProperty;
[ObservableProperty]
private bool boolProperty;
[ObservableProperty]
[property: Category("Included Category")]
private string? stringIncludedCategoryProperty;
[ObservableProperty]
[property: Category("Excluded Category")]
private string? stringExcludedCategoryProperty;
}
public partial class MockPropertyGridObjectAlt : ObservableObject
{
[ObservableProperty]
private int altIntProperty = 10;
[ObservableProperty]
[property: Category("Settings")]
private string? altStringProperty;
}

60
StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs

@ -1,6 +1,8 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Drawing; using System.Drawing;
using System.IO;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Models.Inference; using StabilityMatrix.Avalonia.Models.Inference;
using StabilityMatrix.Avalonia.ViewModels.Inference; using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Core.Models.Api.Comfy.Nodes; using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
@ -18,7 +20,9 @@ public static class ComfyNodeBuilderExtensions
int? batchIndex = null int? batchIndex = null
) )
{ {
var emptyLatent = builder.Nodes.AddTypedNode( var emptyLatent = builder
.Nodes
.AddTypedNode(
new ComfyNodeBuilder.EmptyLatentImage new ComfyNodeBuilder.EmptyLatentImage
{ {
Name = "EmptyLatentImage", Name = "EmptyLatentImage",
@ -34,7 +38,8 @@ public static class ComfyNodeBuilderExtensions
// If batch index is selected, add a LatentFromBatch // If batch index is selected, add a LatentFromBatch
if (batchIndex is not null) if (batchIndex is not null)
{ {
builder.Connections.Primary = builder.Nodes builder.Connections.Primary = builder
.Nodes
.AddNamedNode( .AddNamedNode(
ComfyNodeBuilder.LatentFromBatch( ComfyNodeBuilder.LatentFromBatch(
"LatentFromBatch", "LatentFromBatch",
@ -48,38 +53,38 @@ public static class ComfyNodeBuilderExtensions
} }
} }
public static void SetupImageLatentSource( /// <summary>
/// Setup an image as the <see cref="ComfyNodeBuilder.NodeBuilderConnections.Primary"/> connection
/// </summary>
public static void SetupImagePrimarySource(
this ComfyNodeBuilder builder, this ComfyNodeBuilder builder,
BatchSizeCardViewModel batchSizeCardViewModel, ImageSource image,
SamplerCardViewModel samplerCardViewModel Size imageSize,
int? batchIndex = null
) )
{ {
var emptyLatent = builder.Nodes.AddTypedNode( // Get source image
new ComfyNodeBuilder.EmptyLatentImage var sourceImageRelativePath = Path.Combine("Inference", image.GetHashGuidFileNameCached());
{
Name = "EmptyLatentImage",
BatchSize = batchSizeCardViewModel.BatchSize,
Height = samplerCardViewModel.Height,
Width = samplerCardViewModel.Width
}
);
builder.Connections.Primary = emptyLatent.Output; // Load source
builder.Connections.PrimarySize = new Size( var loadImage = builder
samplerCardViewModel.Width, .Nodes
samplerCardViewModel.Height .AddTypedNode(new ComfyNodeBuilder.LoadImage { Name = "LoadImage", Image = sourceImageRelativePath });
);
builder.Connections.Primary = loadImage.Output1;
builder.Connections.PrimarySize = imageSize;
// If batch index is selected, add a LatentFromBatch // If batch index is selected, add a LatentFromBatch
if (batchSizeCardViewModel.IsBatchIndexEnabled) if (batchIndex is not null)
{ {
builder.Connections.Primary = builder.Nodes builder.Connections.Primary = builder
.Nodes
.AddNamedNode( .AddNamedNode(
ComfyNodeBuilder.LatentFromBatch( ComfyNodeBuilder.LatentFromBatch(
"LatentFromBatch", "LatentFromBatch",
builder.GetPrimaryAsLatent(), builder.GetPrimaryAsLatent(),
// remote expects a 0-based index, vm is 1-based // remote expects a 0-based index, vm is 1-based
batchSizeCardViewModel.BatchIndex - 1, batchIndex.Value - 1,
1 1
) )
) )
@ -92,7 +97,10 @@ public static class ComfyNodeBuilderExtensions
if (builder.Connections.Primary is null) if (builder.Connections.Primary is null)
throw new ArgumentException("No Primary"); throw new ArgumentException("No Primary");
var image = builder.Connections.Primary.Match( var image = builder
.Connections
.Primary
.Match(
_ => _ =>
builder.GetPrimaryAsImage( builder.GetPrimaryAsImage(
builder.Connections.PrimaryVAE builder.Connections.PrimaryVAE
@ -103,8 +111,10 @@ public static class ComfyNodeBuilderExtensions
image => image image => image
); );
var previewImage = builder.Nodes.AddTypedNode( var previewImage = builder
new ComfyNodeBuilder.PreviewImage { Name = "SaveImage", Images = image } .Nodes
.AddTypedNode(
new ComfyNodeBuilder.PreviewImage { Name = builder.Nodes.GetUniqueName("SaveImage"), Images = image }
); );
builder.Connections.OutputNodes.Add(previewImage); builder.Connections.OutputNodes.Add(previewImage);

19
StabilityMatrix.Avalonia/Extensions/DataObjectExtensions.cs

@ -0,0 +1,19 @@
using Avalonia.Input;
namespace StabilityMatrix.Avalonia.Extensions;
public static class DataObjectExtensions
{
/// <summary>
/// Get Context from IDataObject, set by Xaml Behaviors
/// </summary>
public static T? GetContext<T>(this IDataObject dataObject)
{
if (dataObject.Get("Context") is T context)
{
return context;
}
return default;
}
}

2
StabilityMatrix.Avalonia/Helpers/TextEditorConfigs.cs

@ -84,6 +84,8 @@ public static class TextEditorConfigs
textMate.SetGrammar(scope); textMate.SetGrammar(scope);
textMate.SetTheme(registryOptions.LoadTheme(ThemeName.DarkPlus)); textMate.SetTheme(registryOptions.LoadTheme(ThemeName.DarkPlus));
editor.Options.ShowBoxForControlCharacters = false;
} }
private static IRawTheme GetThemeFromStream(Stream stream) private static IRawTheme GetThemeFromStream(Stream stream)

20
StabilityMatrix.Avalonia/Helpers/UriHandler.cs

@ -56,18 +56,26 @@ public class UriHandler
Environment.Exit(0); Environment.Exit(0);
} }
public void Callback() { }
public void RegisterUriScheme() public void RegisterUriScheme()
{ {
if (Compat.IsWindows) if (Compat.IsWindows)
{ {
RegisterUriSchemeWin(); RegisterUriSchemeWin();
} }
else else if (Compat.IsLinux)
{
// Try to register on unix but ignore errors
// Library does not support some distros
try
{ {
RegisterUriSchemeUnix(); RegisterUriSchemeUnix();
} }
catch (Exception e)
{
Debug.WriteLine(e);
Console.WriteLine(e);
}
}
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
@ -92,11 +100,7 @@ public class UriHandler
private void RegisterUriSchemeUnix() private void RegisterUriSchemeUnix()
{ {
var service = URISchemeServiceFactory.GetURISchemeSerivce( var service = URISchemeServiceFactory.GetURISchemeSerivce(Scheme, Description, Compat.AppCurrentPath.FullPath);
Scheme,
Description,
Compat.AppCurrentPath.FullPath
);
service.Set(); service.Set();
} }
} }

11
StabilityMatrix.Avalonia/Helpers/ViewModelSerializer.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -9,16 +8,12 @@ namespace StabilityMatrix.Avalonia.Helpers;
public static class ViewModelSerializer public static class ViewModelSerializer
{ {
public static IImmutableDictionary<string, Type> GetDerivedTypes(Type baseType) public static Dictionary<string, Type> GetDerivedTypes(Type baseType)
{ {
return GetJsonDerivedTypeAttributes(baseType) return GetJsonDerivedTypeAttributes(baseType).ToDictionary(x => x.typeDiscriminator, x => x.subType);
.ToImmutableDictionary(x => x.typeDiscriminator, x => x.subType);
} }
public static IEnumerable<( public static IEnumerable<(Type subType, string typeDiscriminator)> GetJsonDerivedTypeAttributes(Type type)
Type subType,
string typeDiscriminator
)> GetJsonDerivedTypeAttributes(Type type)
{ {
return type.GetCustomAttributes<JsonDerivedTypeAttribute>() return type.GetCustomAttributes<JsonDerivedTypeAttribute>()
.Select(x => (x.DerivedType, x.TypeDiscriminator as string ?? x.DerivedType.Name)); .Select(x => (x.DerivedType, x.TypeDiscriminator as string ?? x.DerivedType.Name));

21
StabilityMatrix.Avalonia/Languages/Cultures.cs

@ -13,10 +13,7 @@ public static class Cultures
public static CultureInfo? Current => Resources.Culture; public static CultureInfo? Current => Resources.Culture;
public static readonly Dictionary<string, CultureInfo> SupportedCulturesByCode = new Dictionary< public static readonly Dictionary<string, CultureInfo> SupportedCulturesByCode = new Dictionary<string, CultureInfo>
string,
CultureInfo
>
{ {
["en-US"] = Default, ["en-US"] = Default,
["ja-JP"] = new("ja-JP"), ["ja-JP"] = new("ja-JP"),
@ -25,18 +22,15 @@ public static class Cultures
["it-IT"] = new("it-IT"), ["it-IT"] = new("it-IT"),
["fr-FR"] = new("fr-FR"), ["fr-FR"] = new("fr-FR"),
["es"] = new("es"), ["es"] = new("es"),
["ru-RU"] = new("ru-RU") ["ru-RU"] = new("ru-RU"),
["tr-TR"] = new("tr-TR")
}; };
public static IReadOnlyList<CultureInfo> SupportedCultures => public static IReadOnlyList<CultureInfo> SupportedCultures => SupportedCulturesByCode.Values.ToImmutableList();
SupportedCulturesByCode.Values.ToImmutableList();
public static CultureInfo GetSupportedCultureOrDefault(string? cultureCode) public static CultureInfo GetSupportedCultureOrDefault(string? cultureCode)
{ {
if ( if (cultureCode is null || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture))
cultureCode is null
|| !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture)
)
{ {
return Default; return Default;
} }
@ -54,10 +48,7 @@ public static class Cultures
public static bool TrySetSupportedCulture(string? cultureCode) public static bool TrySetSupportedCulture(string? cultureCode)
{ {
if ( if (cultureCode is null || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture))
cultureCode is null
|| !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture)
)
{ {
return false; return false;
} }

218
StabilityMatrix.Avalonia/Languages/Resources.Designer.cs generated

@ -338,6 +338,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Open on Hugging Face.
/// </summary>
public static string Action_OpenOnHuggingFace {
get {
return ResourceManager.GetString("Action_OpenOnHuggingFace", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Open Project.... /// Looks up a localized string similar to Open Project....
/// </summary> /// </summary>
@ -419,6 +428,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Replace Contents.
/// </summary>
public static string Action_ReplaceContents {
get {
return ResourceManager.GetString("Action_ReplaceContents", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Restart. /// Looks up a localized string similar to Restart.
/// </summary> /// </summary>
@ -491,6 +509,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Select File.
/// </summary>
public static string Action_SelectFile {
get {
return ResourceManager.GetString("Action_SelectFile", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Send. /// Looks up a localized string similar to Send.
/// </summary> /// </summary>
@ -563,6 +590,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Update Existing Metadata.
/// </summary>
public static string Action_UpdateExistingMetadata {
get {
return ResourceManager.GetString("Action_UpdateExistingMetadata", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Upgrade. /// Looks up a localized string similar to Upgrade.
/// </summary> /// </summary>
@ -599,6 +635,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Addons.
/// </summary>
public static string Label_Addons {
get {
return ResourceManager.GetString("Label_Addons", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Add Stability Matrix to the Start Menu. /// Looks up a localized string similar to Add Stability Matrix to the Start Menu.
/// </summary> /// </summary>
@ -662,6 +707,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Auto Completion.
/// </summary>
public static string Label_AutoCompletion {
get {
return ResourceManager.GetString("Label_AutoCompletion", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Automatically scroll to end of console output. /// Looks up a localized string similar to Automatically scroll to end of console output.
/// </summary> /// </summary>
@ -761,6 +815,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to CivitAI.
/// </summary>
public static string Label_CivitAi {
get {
return ResourceManager.GetString("Label_CivitAi", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to You must be logged in to download this checkpoint. Please enter a CivitAI API Key in the settings.. /// Looks up a localized string similar to You must be logged in to download this checkpoint. Please enter a CivitAI API Key in the settings..
/// </summary> /// </summary>
@ -797,6 +860,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Replace underscores with spaces when inserting completions.
/// </summary>
public static string Label_CompletionReplaceUnderscoresWithSpaces {
get {
return ResourceManager.GetString("Label_CompletionReplaceUnderscoresWithSpaces", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Confirm Delete. /// Looks up a localized string similar to Confirm Delete.
/// </summary> /// </summary>
@ -1023,7 +1095,7 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Emebeddings / Textual Inversion. /// Looks up a localized string similar to Embeddings / Textual Inversion.
/// </summary> /// </summary>
public static string Label_EmbeddingsOrTextualInversion { public static string Label_EmbeddingsOrTextualInversion {
get { get {
@ -1112,6 +1184,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to General.
/// </summary>
public static string Label_General {
get {
return ResourceManager.GetString("Label_General", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Height. /// Looks up a localized string similar to Height.
/// </summary> /// </summary>
@ -1121,6 +1202,24 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Holiday Mode.
/// </summary>
public static string Label_HolidayMode {
get {
return ResourceManager.GetString("Label_HolidayMode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Hugging Face.
/// </summary>
public static string Label_HuggingFace {
get {
return ResourceManager.GetString("Label_HuggingFace", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Image to Image. /// Looks up a localized string similar to Image to Image.
/// </summary> /// </summary>
@ -1130,6 +1229,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Image Viewer.
/// </summary>
public static string Label_ImageViewer {
get {
return ResourceManager.GetString("Label_ImageViewer", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Import with Metadata. /// Looks up a localized string similar to Import with Metadata.
/// </summary> /// </summary>
@ -1166,6 +1274,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Inference.
/// </summary>
public static string Label_Inference {
get {
return ResourceManager.GetString("Label_Inference", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Inner exception. /// Looks up a localized string similar to Inner exception.
/// </summary> /// </summary>
@ -1310,6 +1427,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Missing Image File.
/// </summary>
public static string Label_MissingImageFile {
get {
return ResourceManager.GetString("Label_MissingImageFile", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Model. /// Looks up a localized string similar to Model.
/// </summary> /// </summary>
@ -1436,6 +1562,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Output Image Files.
/// </summary>
public static string Label_OutputImageFiles {
get {
return ResourceManager.GetString("Label_OutputImageFiles", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Output Browser. /// Looks up a localized string similar to Output Browser.
/// </summary> /// </summary>
@ -1562,6 +1697,42 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Prompt.
/// </summary>
public static string Label_Prompt {
get {
return ResourceManager.GetString("Label_Prompt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prompt Tags.
/// </summary>
public static string Label_PromptTags {
get {
return ResourceManager.GetString("Label_PromptTags", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format).
/// </summary>
public static string Label_PromptTagsDescription {
get {
return ResourceManager.GetString("Label_PromptTagsDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Import Prompt tags.
/// </summary>
public static string Label_PromptTagsImport {
get {
return ResourceManager.GetString("Label_PromptTagsImport", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Python Packages. /// Looks up a localized string similar to Python Packages.
/// </summary> /// </summary>
@ -1670,6 +1841,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Save Intermediate Image.
/// </summary>
public static string Label_SaveIntermediateImage {
get {
return ResourceManager.GetString("Label_SaveIntermediateImage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Search.... /// Looks up a localized string similar to Search....
/// </summary> /// </summary>
@ -1697,6 +1877,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Settings.
/// </summary>
public static string Label_Settings {
get {
return ResourceManager.GetString("Label_Settings", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Shared Model Folder Strategy. /// Looks up a localized string similar to Shared Model Folder Strategy.
/// </summary> /// </summary>
@ -1814,6 +2003,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to System Information.
/// </summary>
public static string Label_SystemInformation {
get {
return ResourceManager.GetString("Label_SystemInformation", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Text to Image. /// Looks up a localized string similar to Text to Image.
/// </summary> /// </summary>
@ -1976,6 +2174,24 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Not yet available.
/// </summary>
public static string Label_WipFeature {
get {
return ResourceManager.GetString("Label_WipFeature", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Feature will be available in a future update.
/// </summary>
public static string Label_WipFeatureDescription {
get {
return ResourceManager.GetString("Label_WipFeatureDescription", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to You&apos;re up to date. /// Looks up a localized string similar to You&apos;re up to date.
/// </summary> /// </summary>

3
StabilityMatrix.Avalonia/Languages/Resources.es.resx

@ -680,4 +680,7 @@
<data name="Action_RestoreDefaultLayout" xml:space="preserve"> <data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value>Restablecer Diseño Predeterminado</value> <value>Restablecer Diseño Predeterminado</value>
</data> </data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
</root> </root>

3
StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx

@ -678,4 +678,7 @@
<data name="Action_RestoreDefaultLayout" xml:space="preserve"> <data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value>Rétablir la présentation par défaut</value> <value>Rétablir la présentation par défaut</value>
</data> </data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
</root> </root>

3
StabilityMatrix.Avalonia/Languages/Resources.it-it.resx

@ -680,4 +680,7 @@
<data name="Action_RestoreDefaultLayout" xml:space="preserve"> <data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value></value> <value></value>
</data> </data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
</root> </root>

3
StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx

@ -680,4 +680,7 @@
<data name="Action_RestoreDefaultLayout" xml:space="preserve"> <data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value>レイアウトを初期状態に戻す</value> <value>レイアウトを初期状態に戻す</value>
</data> </data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
</root> </root>

74
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -175,7 +175,7 @@
<value>Deemphasis</value> <value>Deemphasis</value>
</data> </data>
<data name="Label_EmbeddingsOrTextualInversion" xml:space="preserve"> <data name="Label_EmbeddingsOrTextualInversion" xml:space="preserve">
<value>Emebeddings / Textual Inversion</value> <value>Embeddings / Textual Inversion</value>
</data> </data>
<data name="Label_NetworksLoraOrLycoris" xml:space="preserve"> <data name="Label_NetworksLoraOrLycoris" xml:space="preserve">
<value>Networks (Lora / LyCORIS)</value> <value>Networks (Lora / LyCORIS)</value>
@ -825,4 +825,76 @@
<data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve"> <data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve">
<value>Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here</value> <value>Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here</value>
</data> </data>
<data name="Action_OpenOnHuggingFace" xml:space="preserve">
<value>Open on Hugging Face</value>
</data>
<data name="Action_UpdateExistingMetadata" xml:space="preserve">
<value>Update Existing Metadata</value>
</data>
<data name="Label_General" xml:space="preserve">
<value>General</value><comment>A general settings category</comment>
</data>
<data name="Label_Inference" xml:space="preserve">
<value>Inference</value><comment>The Inference feature page</comment>
</data>
<data name="Label_Prompt" xml:space="preserve">
<value>Prompt</value><comment>A settings category for Inference generation prompts</comment>
</data>
<data name="Label_OutputImageFiles" xml:space="preserve">
<value>Output Image Files</value>
</data>
<data name="Label_ImageViewer" xml:space="preserve">
<value>Image Viewer</value>
</data>
<data name="Label_AutoCompletion" xml:space="preserve">
<value>Auto Completion</value>
</data>
<data name="Label_CompletionReplaceUnderscoresWithSpaces" xml:space="preserve">
<value>Replace underscores with spaces when inserting completions</value>
</data>
<data name="Label_PromptTags" xml:space="preserve">
<value>Prompt Tags</value><comment>Tags for image generation prompts</comment>
</data>
<data name="Label_PromptTagsImport" xml:space="preserve">
<value>Import Prompt tags</value>
</data>
<data name="Label_PromptTagsDescription" xml:space="preserve">
<value>Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format)</value>
</data>
<data name="Label_SystemInformation" xml:space="preserve">
<value>System Information</value>
</data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
<data name="Label_HuggingFace" xml:space="preserve">
<value>Hugging Face</value>
</data>
<data name="Label_Addons" xml:space="preserve">
<value>Addons</value><comment>Inference Sampler Addons</comment>
</data>
<data name="Label_SaveIntermediateImage" xml:space="preserve">
<value>Save Intermediate Image</value><comment>Inference module step to save an intermediate image</comment>
</data>
<data name="Label_Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Action_SelectFile" xml:space="preserve">
<value>Select File</value>
</data>
<data name="Action_ReplaceContents" xml:space="preserve">
<value>Replace Contents</value>
</data>
<data name="Label_WipFeature" xml:space="preserve">
<value>Not yet available</value>
</data>
<data name="Label_WipFeatureDescription" xml:space="preserve">
<value>Feature will be available in a future update</value>
</data>
<data name="Label_MissingImageFile" xml:space="preserve">
<value>Missing Image File</value>
</data>
<data name="Label_HolidayMode" xml:space="preserve">
<value>Holiday Mode</value>
</data>
</root> </root>

3
StabilityMatrix.Avalonia/Languages/Resources.ru-ru.resx

@ -678,4 +678,7 @@
<data name="Action_RestoreDefaultLayout" xml:space="preserve"> <data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value>Восстановить вид по умолчанию</value> <value>Восстановить вид по умолчанию</value>
</data> </data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
</root> </root>

783
StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx

@ -0,0 +1,783 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Action_Launch" xml:space="preserve">
<value>Başlat</value>
</data>
<data name="Action_Quit" xml:space="preserve">
<value>Çıkış</value>
</data>
<data name="Action_Save" xml:space="preserve">
<value>Kaydet</value>
</data>
<data name="Action_Cancel" xml:space="preserve">
<value>İptal</value>
</data>
<data name="Label_Language" xml:space="preserve">
<value>Dil</value>
</data>
<data name="Text_RelaunchRequiredToApplyLanguage" xml:space="preserve">
<value>Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekiyor</value>
</data>
<data name="Action_Relaunch" xml:space="preserve">
<value>Yeniden başlat</value>
</data>
<data name="Action_RelaunchLater" xml:space="preserve">
<value>Daha Sonra Yeniden Başlat</value>
</data>
<data name="Label_RelaunchRequired" xml:space="preserve">
<value>Yeniden Başlatma Gerekli</value>
</data>
<data name="Label_UnknownPackage" xml:space="preserve">
<value>Bilinmeyen Paket</value>
</data>
<data name="Action_Import" xml:space="preserve">
<value>İçe Aktar</value>
</data>
<data name="Label_PackageType" xml:space="preserve">
<value>Paket Türü</value>
</data>
<data name="Label_Version" xml:space="preserve">
<value>Sürüm</value>
</data>
<data name="Label_VersionType" xml:space="preserve">
<value>Sürüm Türü</value>
</data>
<data name="Label_Releases" xml:space="preserve">
<value>Sürümler</value>
</data>
<data name="Label_Branches" xml:space="preserve">
<value>Dallar</value>
</data>
<data name="Label_DragAndDropCheckpointsHereToImport" xml:space="preserve">
<value>İçe aktarmak için chekpoints&apos;leri buraya sürükleyip bırakın</value>
</data>
<data name="Label_Emphasis" xml:space="preserve">
<value>Vurgu</value>
</data>
<data name="Label_Deemphasis" xml:space="preserve">
<value>Vurguyu Kaldırma</value>
</data>
<data name="Label_EmbeddingsOrTextualInversion" xml:space="preserve">
<value>Emebeddings / Textual Inversion</value>
</data>
<data name="Label_NetworksLoraOrLycoris" xml:space="preserve">
<value>Networks (Lora / LyCORIS)</value>
</data>
<data name="Label_Comments" xml:space="preserve">
<value>Yorumlar</value>
</data>
<data name="Label_ShowPixelGridAtHighZoomLevels" xml:space="preserve">
<value>Yüksek yakınlaştırma seviyesinde piksel ızgarasını göster</value>
</data>
<data name="Label_Steps" xml:space="preserve">
<value>Adımlar</value>
</data>
<data name="Label_StepsBase" xml:space="preserve">
<value>Adımlar - Temel</value>
</data>
<data name="Label_StepsRefiner" xml:space="preserve">
<value>Adımlar - İyileştirici</value>
</data>
<data name="Label_CFGScale" xml:space="preserve">
<value>CFG Ölçeği</value>
</data>
<data name="Label_DenoisingStrength" xml:space="preserve">
<value>Gürültü Azaltma Gücü</value>
</data>
<data name="Label_Width" xml:space="preserve">
<value>Genişlik</value>
</data>
<data name="Label_Height" xml:space="preserve">
<value>Yükseklik</value>
</data>
<data name="Label_Refiner" xml:space="preserve">
<value>İyileştirici</value>
</data>
<data name="Label_VAE" xml:space="preserve">
<value>VAE</value>
</data>
<data name="Label_Model" xml:space="preserve">
<value>Model</value>
</data>
<data name="Action_Connect" xml:space="preserve">
<value>Bağlan</value>
</data>
<data name="Label_ConnectingEllipsis" xml:space="preserve">
<value>Bağlanıyor...</value>
</data>
<data name="Action_Close" xml:space="preserve">
<value>Kapat</value>
</data>
<data name="Label_WaitingToConnectEllipsis" xml:space="preserve">
<value>Bağlanmayı bekliyor...</value>
</data>
<data name="Label_UpdateAvailable" xml:space="preserve">
<value>Güncelleme Mevcut</value>
</data>
<data name="Label_BecomeAPatron" xml:space="preserve">
<value>Sponsor Ol</value>
</data>
<data name="Label_JoinDiscord" xml:space="preserve">
<value>Discord Sunucusuna Katıl</value>
</data>
<data name="Label_Downloads" xml:space="preserve">
<value>İndirmeler</value>
</data>
<data name="Action_Install" xml:space="preserve">
<value>Yükle</value>
</data>
<data name="Label_SkipSetup" xml:space="preserve">
<value>İlk kurulum işlemini atla</value>
</data>
<data name="Label_UnexpectedErrorOccurred" xml:space="preserve">
<value>Beklenmeyen bir hata oluştu</value>
</data>
<data name="Action_ExitApplication" xml:space="preserve">
<value>Uygulamadan Çık</value>
</data>
<data name="Label_DisplayName" xml:space="preserve">
<value>Görünen Ad</value>
</data>
<data name="Label_InstallationWithThisNameExists" xml:space="preserve">
<value>Bu ada sahip bir yükleme zaten mevcut.</value>
</data>
<data name="Label_PleaseChooseDifferentName" xml:space="preserve">
<value>Lütfen farklı bir ad seçin veya başka bir yükleme konumu seçin.</value>
</data>
<data name="Label_AdvancedOptions" xml:space="preserve">
<value>Gelişmiş Seçenekler</value>
</data>
<data name="Label_Commit" xml:space="preserve">
<value>İşlemek</value>
</data>
<data name="Label_SharedModelFolderStrategy" xml:space="preserve">
<value>Paylaşılan Model Klasör Stratejisi</value>
</data>
<data name="Label_PyTorchVersion" xml:space="preserve">
<value>PyTorch Sürümü</value>
</data>
<data name="Label_CloseDialogWhenFinished" xml:space="preserve">
<value>Bittiğinde iletişim kutusunu kapat</value>
</data>
<data name="Label_DataDirectory" xml:space="preserve">
<value>Veri Klasörü</value>
</data>
<data name="Label_DataDirectoryExplanation" xml:space="preserve">
<value>Model chekpoints, Lora&apos;ların, web UI&apos;lerin, ayarların vb. kurulacağı yer burasıdır.</value>
</data>
<data name="Label_FatWarning" xml:space="preserve">
<value>FAT32 veya exFAT sürücü kullanırken hatalarla karşılaşabilirsiniz. Daha sorunsuz bir deneyim için farklı bir sürücü seçin.</value>
</data>
<data name="Label_PortableMode" xml:space="preserve">
<value>Taşınabilir Mod</value>
</data>
<data name="Label_PortableModeExplanation" xml:space="preserve">
<value>Taşınabilir Modda tüm veriler ve ayarlar uygulamayla aynı dizinde saklanacaktır. Uygulamayı &apos;Veri&apos; klasörüyle birlikte farklı bir konuma veya bilgisayara taşıyabileceksiniz.</value>
</data>
<data name="Action_Continue" xml:space="preserve">
<value>Devam</value>
</data>
<data name="Label_PreviousImage" xml:space="preserve">
<value>Önceki Resim</value>
</data>
<data name="Label_NextImage" xml:space="preserve">
<value>Sonraki Resim</value>
</data>
<data name="Label_ModelDescription" xml:space="preserve">
<value>Model Açıklaması</value>
</data>
<data name="Label_NewVersionAvailable" xml:space="preserve">
<value>Stabilite Matrisi için yeni bir sürüm mevcut!</value>
</data>
<data name="Label_ImportLatest" xml:space="preserve">
<value>En Yeniyi İçe Aktar -</value>
</data>
<data name="Label_AllVersions" xml:space="preserve">
<value>Tüm Sürümler</value>
</data>
<data name="Label_ModelSearchWatermark" xml:space="preserve">
<value>Modelleri, #etiketleri veya @kullanıcıları ara</value>
</data>
<data name="Action_Search" xml:space="preserve">
<value>Ara</value>
</data>
<data name="Label_Sort" xml:space="preserve">
<value>Sırala</value>
</data>
<data name="Label_TimePeriod" xml:space="preserve">
<value>Süre</value>
</data>
<data name="Label_ModelType" xml:space="preserve">
<value>Model Türü</value>
</data>
<data name="Label_BaseModel" xml:space="preserve">
<value>Temel Model</value>
</data>
<data name="Label_ShowNsfwContent" xml:space="preserve">
<value>NSFW İçerik Göster</value>
</data>
<data name="Label_DataProvidedByCivitAi" xml:space="preserve">
<value>CivitAI tarafından sağlanan veriler</value>
</data>
<data name="Label_Page" xml:space="preserve">
<value>Sayfa</value>
</data>
<data name="Label_FirstPage" xml:space="preserve">
<value>İlk Sayfa</value>
</data>
<data name="Label_PreviousPage" xml:space="preserve">
<value>Önceki Sayfa</value>
</data>
<data name="Label_NextPage" xml:space="preserve">
<value>Sonraki Sayfa</value>
</data>
<data name="Label_LastPage" xml:space="preserve">
<value>Son Sayfa</value>
</data>
<data name="Action_Rename" xml:space="preserve">
<value>Yeniden Adlandır</value>
</data>
<data name="Action_Delete" xml:space="preserve">
<value>Sil</value>
</data>
<data name="Action_OpenOnCivitAi" xml:space="preserve">
<value>CivitAI&apos;de Aç</value>
</data>
<data name="Label_ConnectedModel" xml:space="preserve">
<value>Bağlı Model</value>
</data>
<data name="Label_LocalModel" xml:space="preserve">
<value>Yerel Model</value>
</data>
<data name="Action_ShowInExplorer" xml:space="preserve">
<value>Explorer&apos;da göster</value>
</data>
<data name="Action_New" xml:space="preserve">
<value>Yeni</value>
</data>
<data name="Label_Folder" xml:space="preserve">
<value>Klasör</value>
</data>
<data name="Label_DropFileToImport" xml:space="preserve">
<value>İçe aktarma için dosyayı buraya bırakın</value>
</data>
<data name="Label_ImportAsConnected" xml:space="preserve">
<value>Metadata ile içeri aktar</value>
</data>
<data name="Label_ImportAsConnectedExplanation" xml:space="preserve">
<value>Yeni yerel içe aktarmalar için bağlı meta veri arayın</value>
</data>
<data name="Label_Indexing" xml:space="preserve">
<value>İndeksleniyor...</value>
</data>
<data name="Label_ModelsFolder" xml:space="preserve">
<value>Model Klasörü</value>
</data>
<data name="Label_Categories" xml:space="preserve">
<value>Kategoriler</value>
</data>
<data name="Label_LetsGetStarted" xml:space="preserve">
<value>Başlayalım</value>
</data>
<data name="Label_ReadAndAgree" xml:space="preserve">
<value>Lisans Sözleşmesini Okudum ve Kabul Ediyorum.</value>
</data>
<data name="Label_LicenseAgreement" xml:space="preserve">
<value>Lisans Sözleşmesi.</value>
</data>
<data name="Label_FindConnectedMetadata" xml:space="preserve">
<value>Metadata&apos;larını bul</value>
</data>
<data name="Label_ShowModelImages" xml:space="preserve">
<value>Model Resimlerini Göster</value>
</data>
<data name="Label_Appearance" xml:space="preserve">
<value>Görünüm</value>
</data>
<data name="Label_Theme" xml:space="preserve">
<value>Tema</value>
</data>
<data name="Label_CheckpointManager" xml:space="preserve">
<value>Checkpoint Yöneticisi</value>
</data>
<data name="Label_RemoveSymlinksOnShutdown" xml:space="preserve">
<value>Kapatma sırasında paylaşılan Checkpoint dizini sembolik bağlantılarını kaldırın</value>
</data>
<data name="Label_RemoveSymlinksOnShutdown_Details" xml:space="preserve">
<value>Stabilite Matrisini başka bir sürücüye taşımada sorun yaşıyorsanız bu seçeneği seçin</value>
</data>
<data name="Label_ResetCheckpointsCache" xml:space="preserve">
<value>Checkpoints Önbelleğini Sıfırla</value>
</data>
<data name="Label_ResetCheckpointsCache_Details" xml:space="preserve">
<value>Kurulu checkpoints önbelleğini yeniden oluştur. Model tarayıcısında checkpointler yanlış etiketlenmişse kullanın</value>
</data>
<data name="Label_PackageEnvironment" xml:space="preserve">
<value>Paket Ortamı</value>
</data>
<data name="Action_Edit" xml:space="preserve">
<value>Düzenle</value>
</data>
<data name="Label_EnvironmentVariables" xml:space="preserve">
<value>Ortam Değişkenleri</value>
</data>
<data name="Label_EmbeddedPython" xml:space="preserve">
<value>Gömülü Python</value>
</data>
<data name="Action_CheckVersion" xml:space="preserve">
<value>Sürümü Kontrol Et</value>
</data>
<data name="Label_Integrations" xml:space="preserve">
<value>Entegrasyonlar</value>
</data>
<data name="Label_DiscordRichPresence" xml:space="preserve">
<value>Discord Zengin Varlık</value>
</data>
<data name="Label_System" xml:space="preserve">
<value>Sistem</value>
</data>
<data name="Label_AddToStartMenu" xml:space="preserve">
<value>Stability Matrix&apos;i başlat menüsüne ekle</value>
</data>
<data name="Label_AddToStartMenu_Details" xml:space="preserve">
<value>Mevcut uygulama konumu kullanılır, uygulamayı taşırsanız tekrar çalıştırabilirsiniz</value>
</data>
<data name="Label_OnlyAvailableOnWindows" xml:space="preserve">
<value>Yalnızca Windows&apos;ta kullanılabilir</value>
</data>
<data name="Action_AddForCurrentUser" xml:space="preserve">
<value>Geçerli Kullanıcı için Ekle</value>
</data>
<data name="Action_AddForAllUsers" xml:space="preserve">
<value>Tüm Kullanıcılar İçin Ekle</value>
</data>
<data name="Label_SelectNewDataDirectory" xml:space="preserve">
<value>Yeni Veri Dizini Seç</value>
</data>
<data name="Label_SelectNewDataDirectory_Details" xml:space="preserve">
<value>Mevcut veriyi taşımaz</value>
</data>
<data name="Action_SelectDirectory" xml:space="preserve">
<value>Dizin Seçin</value>
</data>
<data name="Label_About" xml:space="preserve">
<value>Hakkında</value>
</data>
<data name="Label_StabilityMatrix" xml:space="preserve">
<value>Stability Matrix</value>
</data>
<data name="Label_LicenseAndOpenSourceNotices" xml:space="preserve">
<value>Lisans ve Açık Kaynak Bildirimleri</value>
</data>
<data name="TeachingTip_ClickLaunchToGetStarted" xml:space="preserve">
<value>Başlamak için Başlat&apos;a tıklayın!</value>
</data>
<data name="Action_Stop" xml:space="preserve">
<value>Dur</value>
</data>
<data name="Action_SendInput" xml:space="preserve">
<value>Giriş Gönder</value>
</data>
<data name="Label_Input" xml:space="preserve">
<value>Giriş</value>
</data>
<data name="Action_Send" xml:space="preserve">
<value>Gönder</value>
</data>
<data name="Label_InputRequired" xml:space="preserve">
<value>Giriş gerekiyor</value>
</data>
<data name="Label_ConfirmQuestion" xml:space="preserve">
<value>Onaylamak?</value>
</data>
<data name="Action_Yes" xml:space="preserve">
<value>Evet</value>
</data>
<data name="Label_No" xml:space="preserve">
<value>Hayır</value>
</data>
<data name="Action_OpenWebUI" xml:space="preserve">
<value>Web Arayüzünü Aç</value>
</data>
<data name="Text_WelcomeToStabilityMatrix" xml:space="preserve">
<value>Stability Matrix&apos;e Hoş Geldiniz!</value>
</data>
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve">
<value>Tercih ettiğiniz arayüzü seçin ve başlamak için Yükle&apos;ye tıklayın</value>
</data>
<data name="Label_Installing" xml:space="preserve">
<value>Yükleniyor</value>
</data>
<data name="Text_ProceedingToLaunchPage" xml:space="preserve">
<value>Başlatma sayfasına geçiliyor</value>
</data>
<data name="Progress_DownloadingPackage" xml:space="preserve">
<value>Paket indiriliyor...</value>
</data>
<data name="Progress_DownloadComplete" xml:space="preserve">
<value>İndirme tamamlandı</value>
</data>
<data name="Progress_InstallationComplete" xml:space="preserve">
<value>Kurulum tamamlandı</value>
</data>
<data name="Progress_InstallingPrerequisites" xml:space="preserve">
<value>Önkoşullar kuruluyor...</value>
</data>
<data name="Progress_InstallingPackageRequirements" xml:space="preserve">
<value>Paket gereksinimleri kuruluyor...</value>
</data>
<data name="Action_OpenInExplorer" xml:space="preserve">
<value>Explorer&apos;da Aç</value>
</data>
<data name="Action_OpenInFinder" xml:space="preserve">
<value>Finder&apos;da Aç</value>
</data>
<data name="Action_Uninstall" xml:space="preserve">
<value>Kaldır</value>
</data>
<data name="Action_CheckForUpdates" xml:space="preserve">
<value>Güncellemeleri Kontrol Et</value>
</data>
<data name="Action_Update" xml:space="preserve">
<value>Güncelle</value>
</data>
<data name="Action_AddPackage" xml:space="preserve">
<value>Paket Ekle</value>
</data>
<data name="TeachingTip_AddPackageToGetStarted" xml:space="preserve">
<value>Başlamak için bir paket ekle!</value>
</data>
<data name="Label_EnvVarsTable_Name" xml:space="preserve">
<value>Ad</value>
</data>
<data name="Label_EnvVarsTable_Value" xml:space="preserve">
<value>Değer</value>
</data>
<data name="Action_Remove" xml:space="preserve">
<value>Kaldır</value>
</data>
<data name="Label_Details" xml:space="preserve">
<value>Ayrıntılar</value>
</data>
<data name="Label_Callstack" xml:space="preserve">
<value>Çağrı Yığını</value>
</data>
<data name="Label_InnerException" xml:space="preserve">
<value>İç istisna</value>
</data>
<data name="Label_SearchEllipsis" xml:space="preserve">
<value>Ara...</value>
</data>
<data name="Action_OK" xml:space="preserve">
<value>Tamam</value>
</data>
<data name="Action_Retry" xml:space="preserve">
<value>Tekrar Dene</value>
</data>
<data name="Label_PythonVersionInfo" xml:space="preserve">
<value>Python Sürüm Bilgisi</value>
</data>
<data name="Action_Restart" xml:space="preserve">
<value>Yeniden Başlat</value>
</data>
<data name="Label_ConfirmDelete" xml:space="preserve">
<value>Silmeyi Onayla</value>
</data>
<data name="Text_PackageUninstall_Details" xml:space="preserve">
<value>Bu, paket klasörünü ve içindeki tüm içeriği, eklediğiniz tüm oluşturulmuş resimleri ve dosyaları silecek.</value>
</data>
<data name="Progress_UninstallingPackage" xml:space="preserve">
<value>Paket Kaldırılıyor...</value>
</data>
<data name="Label_PackageUninstalled" xml:space="preserve">
<value>Paket Kaldırıldı</value>
</data>
<data name="Text_SomeFilesCouldNotBeDeleted" xml:space="preserve">
<value>Bazı dosyalar silinemedi. Lütfen paket klasöründeki açık dosyaları kapatın ve tekrar deneyin.</value>
</data>
<data name="Label_InvalidPackageType" xml:space="preserve">
<value>Geçersiz Paket Türü</value>
</data>
<data name="TextTemplate_UpdatingPackage" xml:space="preserve">
<value>{0} Güncelleniyor</value>
</data>
<data name="Progress_UpdateComplete" xml:space="preserve">
<value>Güncelleme tamamlandı</value>
</data>
<data name="TextTemplate_PackageUpdatedToLatest" xml:space="preserve">
<value>{0}, en son sürüme güncellendi</value>
</data>
<data name="TextTemplate_ErrorUpdatingPackage" xml:space="preserve">
<value>{0} güncelleme hatası</value>
</data>
<data name="Progress_UpdateFailed" xml:space="preserve">
<value>Güncelleme başarısız oldu</value>
</data>
<data name="Action_OpenInBrowser" xml:space="preserve">
<value>Tarayıcıda Aç</value>
</data>
<data name="Label_ErrorInstallingPackage" xml:space="preserve">
<value>Paket kurulum hatası</value>
</data>
<data name="Label_Branch" xml:space="preserve">
<value>Dal</value>
</data>
<data name="Label_AutoScrollToEnd" xml:space="preserve">
<value>Konsol çıktısının sonuna otomatik olarak kaydır</value>
</data>
<data name="Label_License" xml:space="preserve">
<value>Lisans</value>
</data>
<data name="Label_SharedModelStrategyShort" xml:space="preserve">
<value>Model Paylaşımı</value>
</data>
<data name="Label_PleaseSelectDataDirectory" xml:space="preserve">
<value>Lütfen Bir Veri Klasörü Seçin</value>
</data>
<data name="Label_DataFolderName" xml:space="preserve">
<value>Veri Klasörü Adı</value>
</data>
<data name="Label_CurrentDirectory" xml:space="preserve">
<value>Geçerli klasör:</value>
</data>
<data name="Text_AppWillRelaunchAfterUpdate" xml:space="preserve">
<value>Uygulama güncelleme sonrası yeniden başlatılacaktır</value>
</data>
<data name="Action_RemindMeLater" xml:space="preserve">
<value>Daha Sonra Hatırlat</value>
</data>
<data name="Action_InstallNow" xml:space="preserve">
<value>Şimdi Yükle</value>
</data>
<data name="Label_ReleaseNotes" xml:space="preserve">
<value>Sürüm Notları</value>
</data>
<data name="Action_OpenProjectEllipsis" xml:space="preserve">
<value>Projeyi Aç...</value>
</data>
<data name="Action_SaveAsEllipsis" xml:space="preserve">
<value>Farklı Kaydet...</value>
</data>
<data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value>Varsayılan Düzeni Geri Yükle</value>
</data>
<data name="Label_UseSharedOutputFolder" xml:space="preserve">
<value>Çıktı Paylaşımı</value>
</data>
<data name="Label_BatchIndex" xml:space="preserve">
<value>Toplu İndeks</value>
</data>
<data name="Action_Copy" xml:space="preserve">
<value>Kopyala</value>
</data>
<data name="Action_OpenInViewer" xml:space="preserve">
<value>Resim Görüntüleyici&apos;de aç</value>
</data>
<data name="Label_NumImagesSelected" xml:space="preserve">
<value>{0} resim seçildi</value>
</data>
<data name="Label_OutputFolder" xml:space="preserve">
<value>Çıkış Klasörü</value>
</data>
<data name="Label_OutputType" xml:space="preserve">
<value>Çıkış Türü</value>
</data>
<data name="Action_ClearSelection" xml:space="preserve">
<value>Seçimi Temizle</value>
</data>
<data name="Action_SelectAll" xml:space="preserve">
<value>Tümünü Seç</value>
</data>
<data name="Action_SendToInference" xml:space="preserve">
<value>Çıkarıma Gönder</value>
</data>
<data name="Label_TextToImage" xml:space="preserve">
<value>Metinden Resime</value>
</data>
<data name="Label_ImageToImage" xml:space="preserve">
<value>Resimden Resime</value>
</data>
<data name="Label_Inpainting" xml:space="preserve">
<value>Inpainting</value>
</data>
<data name="Label_Upscale" xml:space="preserve">
<value>Upscale</value>
</data>
<data name="Label_OutputsPageTitle" xml:space="preserve">
<value>Çıkış Tarayıcısı</value>
</data>
<data name="Label_OneImageSelected" xml:space="preserve">
<value>1 resim seçildi</value>
</data>
<data name="Label_PythonPackages" xml:space="preserve">
<value>Python Paketleri</value>
</data>
<data name="Action_Consolidate" xml:space="preserve">
<value>Birleştir</value>
</data>
<data name="Label_AreYouSure" xml:space="preserve">
<value>Emin misiniz?</value>
</data>
<data name="Label_ConsolidateExplanation" xml:space="preserve">
<value>Bu, seçilen paketlerden tüm oluşturulmuş resimleri, paylaşılan çıktılar klasörünün Konsolide dizinine taşıyacak. Bu işlem geri alınamaz.</value>
</data>
<data name="Action_Refresh" xml:space="preserve">
<value>Yenile</value>
</data>
<data name="Action_Upgrade" xml:space="preserve">
<value>Yükselt</value>
</data>
<data name="Action_Downgrade" xml:space="preserve">
<value>Düşür</value>
</data>
<data name="Action_OpenGithub" xml:space="preserve">
<value>GitHub&apos;da Aç</value>
</data>
<data name="Label_Connected" xml:space="preserve">
<value>Bağlandı</value>
</data>
<data name="Action_Disconnect" xml:space="preserve">
<value>Bağlantıyı Kes</value>
</data>
<data name="Label_Email" xml:space="preserve">
<value>E-posta</value>
</data>
<data name="Label_Username" xml:space="preserve">
<value>Kullanıcı Adı</value>
</data>
<data name="Label_Password" xml:space="preserve">
<value>Parola</value>
</data>
<data name="Action_Login" xml:space="preserve">
<value>Giriş</value>
</data>
<data name="Action_Signup" xml:space="preserve">
<value>Kaydol</value>
</data>
<data name="Label_ConfirmPassword" xml:space="preserve">
<value>Parolayı Onayla</value>
</data>
<data name="Label_ApiKey" xml:space="preserve">
<value>API Key</value>
</data>
<data name="Label_Accounts" xml:space="preserve">
<value>Hesaplar</value>
</data>
</root>

3
StabilityMatrix.Avalonia/Languages/Resources.zh-Hans.resx

@ -647,4 +647,7 @@
<data name="Label_Branch" xml:space="preserve"> <data name="Label_Branch" xml:space="preserve">
<value>分支</value> <value>分支</value>
</data> </data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
</root> </root>

3
StabilityMatrix.Avalonia/Languages/Resources.zh-Hant.resx

@ -639,4 +639,7 @@
<data name="Label_Branch" xml:space="preserve"> <data name="Label_Branch" xml:space="preserve">
<value>分支</value> <value>分支</value>
</data> </data>
<data name="Label_CivitAi" xml:space="preserve">
<value>CivitAI</value>
</data>
</root> </root>

113
StabilityMatrix.Avalonia/MarkupExtensions/ShowDisabledTooltipExtension.cs

@ -0,0 +1,113 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace StabilityMatrix.Avalonia.MarkupExtensions;
/// <summary>
/// Show tooltip on Controls with IsEffectivelyEnabled = false
/// https://github.com/AvaloniaUI/Avalonia/issues/3847#issuecomment-1618790059
/// </summary>
[PublicAPI]
public static class ShowDisabledTooltipExtension
{
static ShowDisabledTooltipExtension()
{
ShowOnDisabledProperty.Changed.AddClassHandler<Control>(HandleShowOnDisabledChanged);
}
public static bool GetShowOnDisabled(AvaloniaObject obj)
{
return obj.GetValue(ShowOnDisabledProperty);
}
public static void SetShowOnDisabled(AvaloniaObject obj, bool value)
{
obj.SetValue(ShowOnDisabledProperty, value);
}
public static readonly AttachedProperty<bool> ShowOnDisabledProperty = AvaloniaProperty.RegisterAttached<
object,
Control,
bool
>("ShowOnDisabled");
private static void HandleShowOnDisabledChanged(Control control, AvaloniaPropertyChangedEventArgs e)
{
if (e.GetNewValue<bool>())
{
control.DetachedFromVisualTree += AttachedControl_DetachedFromVisualOrExtension;
control.AttachedToVisualTree += AttachedControl_AttachedToVisualTree;
if (control.IsInitialized)
{
// enabled after visual attached
AttachedControl_AttachedToVisualTree(control, null!);
}
}
else
{
AttachedControl_DetachedFromVisualOrExtension(control, null!);
}
}
private static void AttachedControl_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is not Control control || TopLevel.GetTopLevel(control) is not { } tl)
{
return;
}
// NOTE pointermove needed to be tunneled for me but you may not need to...
tl.AddHandler(InputElement.PointerMovedEvent, TopLevel_PointerMoved, RoutingStrategies.Tunnel);
}
private static void AttachedControl_DetachedFromVisualOrExtension(object? s, VisualTreeAttachmentEventArgs e)
{
if (s is not Control control)
{
return;
}
control.DetachedFromVisualTree -= AttachedControl_DetachedFromVisualOrExtension;
control.AttachedToVisualTree -= AttachedControl_AttachedToVisualTree;
if (TopLevel.GetTopLevel(control) is not { } tl)
{
return;
}
tl.RemoveHandler(InputElement.PointerMovedEvent, TopLevel_PointerMoved);
}
private static void TopLevel_PointerMoved(object? sender, PointerEventArgs e)
{
if (sender is not Control tl)
{
return;
}
var attachedControls = tl.GetVisualDescendants().Where(GetShowOnDisabled).Cast<Control>().ToList();
// find disabled children under pointer w/ this extension enabled
var disabledChildUnderPointer = attachedControls.FirstOrDefault(
x =>
x.Bounds.Contains(e.GetPosition(x.Parent as Visual))
&& x is { IsEffectivelyVisible: true, IsEffectivelyEnabled: false }
);
if (disabledChildUnderPointer != null)
{
// manually show tooltip
ToolTip.SetIsOpen(disabledChildUnderPointer, true);
}
var disabledTooltipsToHide = attachedControls.Where(
x => ToolTip.GetIsOpen(x) && x != disabledChildUnderPointer && !x.IsEffectivelyEnabled
);
foreach (var control in disabledTooltipsToHide)
{
ToolTip.SetIsOpen(control, false);
}
}
}

42
StabilityMatrix.Avalonia/MarkupExtensions/TernaryExtension.cs

@ -0,0 +1,42 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
namespace StabilityMatrix.Avalonia.MarkupExtensions;
/// <summary>
/// https://github.com/AvaloniaUI/Avalonia/discussions/7408
/// </summary>
/// <example>
/// <code>{e:Ternary SomeProperty, True=1, False=0}</code>
/// </example>
public class TernaryExtension : MarkupExtension
{
public string Path { get; set; }
public Type Type { get; set; }
public object? True { get; set; }
public object? False { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var cultureInfo = CultureInfo.GetCultureInfo("en-US");
var binding = new ReflectionBindingExtension(Path)
{
Mode = BindingMode.OneWay,
Converter = new FuncValueConverter<bool, object?>(
isTrue =>
isTrue
? Convert.ChangeType(True, Type, cultureInfo.NumberFormat)
: Convert.ChangeType(False, Type, cultureInfo.NumberFormat)
)
};
return binding.ProvideValue(serviceProvider);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save