JT
10 months ago
committed by
GitHub
307 changed files with 17177 additions and 3192 deletions
@ -0,0 +1,18 @@ |
|||||||
|
<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.7" /> |
||||||
|
<PackageReference Include="SkiaSharp" Version="2.88.7" /> |
||||||
|
<PackageReference Include="DotNet.Bundle" Version="0.9.13" /> |
||||||
|
</ItemGroup> |
||||||
|
|
||||||
|
</Project> |
@ -0,0 +1,10 @@ |
|||||||
|
namespace Avalonia.Gif |
||||||
|
{ |
||||||
|
internal enum BgWorkerCommand |
||||||
|
{ |
||||||
|
Null, |
||||||
|
Play, |
||||||
|
Pause, |
||||||
|
Dispose |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
namespace Avalonia.Gif |
||||||
|
{ |
||||||
|
internal enum BgWorkerState |
||||||
|
{ |
||||||
|
Null, |
||||||
|
Start, |
||||||
|
Running, |
||||||
|
Paused, |
||||||
|
Complete, |
||||||
|
Dispose |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
namespace Avalonia.Gif.Decoding |
||||||
|
{ |
||||||
|
internal enum BlockTypes |
||||||
|
{ |
||||||
|
Empty = 0, |
||||||
|
Extension = 0x21, |
||||||
|
ImageDescriptor = 0x2C, |
||||||
|
Trailer = 0x3B, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
namespace Avalonia.Gif.Decoding |
||||||
|
{ |
||||||
|
internal enum ExtensionType |
||||||
|
{ |
||||||
|
GraphicsControl = 0xF9, |
||||||
|
Application = 0xFF |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
namespace Avalonia.Gif.Decoding |
||||||
|
{ |
||||||
|
public enum FrameDisposal |
||||||
|
{ |
||||||
|
Unknown = 0, |
||||||
|
Leave = 1, |
||||||
|
Background = 2, |
||||||
|
Restore = 3 |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
namespace Avalonia.Gif.Decoding |
||||||
|
{ |
||||||
|
public class GifRepeatBehavior |
||||||
|
{ |
||||||
|
public bool LoopForever { get; set; } |
||||||
|
public int? Count { get; set; } |
||||||
|
} |
||||||
|
} |
@ -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) { } |
||||||
|
} |
||||||
|
} |
@ -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) { } |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,297 @@ |
|||||||
|
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 IGifInstance? _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 IGifInstance? _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 IGifInstance 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 WebpInstance(source); |
||||||
|
// _gifInstance = new GifInstance(source); |
||||||
|
_gifInstance.IterationCount = IterationCount; |
||||||
|
_customVisual?.SendHandlerMessage(_gifInstance); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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 : IGifInstance |
||||||
|
{ |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
using Avalonia.Animation; |
||||||
|
using Avalonia.Media.Imaging; |
||||||
|
|
||||||
|
namespace Avalonia.Gif; |
||||||
|
|
||||||
|
public interface IGifInstance : IDisposable |
||||||
|
{ |
||||||
|
IterationCount IterationCount { get; set; } |
||||||
|
bool AutoStart { get; } |
||||||
|
CancellationTokenSource CurrentCts { get; } |
||||||
|
int GifFrameCount { get; } |
||||||
|
PixelSize GifPixelSize { get; } |
||||||
|
bool IsDisposed { get; } |
||||||
|
WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed); |
||||||
|
} |
@ -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) { } |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,180 @@ |
|||||||
|
using Avalonia.Animation; |
||||||
|
using Avalonia.Media.Imaging; |
||||||
|
using Avalonia.Platform; |
||||||
|
using SkiaSharp; |
||||||
|
|
||||||
|
namespace Avalonia.Gif; |
||||||
|
|
||||||
|
public class WebpInstance : IGifInstance |
||||||
|
{ |
||||||
|
public IterationCount IterationCount { get; set; } |
||||||
|
public bool AutoStart { get; private set; } = true; |
||||||
|
|
||||||
|
private readonly WriteableBitmap? _targetBitmap; |
||||||
|
private TimeSpan _totalTime; |
||||||
|
private readonly List<TimeSpan> _frameTimes; |
||||||
|
private uint _iterationCount; |
||||||
|
private int _currentFrameIndex; |
||||||
|
|
||||||
|
private SKCodec? _codec; |
||||||
|
|
||||||
|
public CancellationTokenSource CurrentCts { get; } |
||||||
|
|
||||||
|
internal WebpInstance(object newValue) |
||||||
|
: this( |
||||||
|
newValue switch |
||||||
|
{ |
||||||
|
Stream s => s, |
||||||
|
Uri u => GetStreamFromUri(u), |
||||||
|
string str => GetStreamFromString(str), |
||||||
|
_ => throw new InvalidDataException("Unsupported source object") |
||||||
|
} |
||||||
|
) { } |
||||||
|
|
||||||
|
public WebpInstance(string uri) |
||||||
|
: this(GetStreamFromString(uri)) { } |
||||||
|
|
||||||
|
public WebpInstance(Uri uri) |
||||||
|
: this(GetStreamFromUri(uri)) { } |
||||||
|
|
||||||
|
public WebpInstance(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(); |
||||||
|
|
||||||
|
var managedStream = new SKManagedStream(currentStream); |
||||||
|
_codec = SKCodec.Create(managedStream); |
||||||
|
|
||||||
|
var pixSize = new PixelSize(_codec.Info.Width, _codec.Info.Height); |
||||||
|
|
||||||
|
_targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque); |
||||||
|
GifPixelSize = pixSize; |
||||||
|
|
||||||
|
_totalTime = TimeSpan.Zero; |
||||||
|
|
||||||
|
_frameTimes = _codec |
||||||
|
.FrameInfo |
||||||
|
.Select(frame => |
||||||
|
{ |
||||||
|
_totalTime = _totalTime.Add(TimeSpan.FromMilliseconds(frame.Duration)); |
||||||
|
return _totalTime; |
||||||
|
}) |
||||||
|
.ToList(); |
||||||
|
|
||||||
|
RenderFrame(_codec, _targetBitmap, 0); |
||||||
|
} |
||||||
|
|
||||||
|
private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index) |
||||||
|
{ |
||||||
|
codec.GetFrameInfo(index, out var frameInfo); |
||||||
|
|
||||||
|
var info = new SKImageInfo(codec.Info.Width, codec.Info.Height); |
||||||
|
var decodeInfo = info.WithAlphaType(frameInfo.AlphaType); |
||||||
|
|
||||||
|
using var frameBuffer = targetBitmap.Lock(); |
||||||
|
|
||||||
|
var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index)); |
||||||
|
|
||||||
|
if (result != SKCodecResult.Success) |
||||||
|
throw new InvalidDataException($"Could not decode frame {index} of {codec.FrameCount}."); |
||||||
|
} |
||||||
|
|
||||||
|
private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index, int priorIndex) |
||||||
|
{ |
||||||
|
codec.GetFrameInfo(index, out var frameInfo); |
||||||
|
|
||||||
|
var info = new SKImageInfo(codec.Info.Width, codec.Info.Height); |
||||||
|
var decodeInfo = info.WithAlphaType(frameInfo.AlphaType); |
||||||
|
|
||||||
|
using var frameBuffer = targetBitmap.Lock(); |
||||||
|
|
||||||
|
var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index, priorIndex)); |
||||||
|
|
||||||
|
if (result != SKCodecResult.Success) |
||||||
|
throw new InvalidDataException($"Could not decode frame {index} of {codec.FrameCount}."); |
||||||
|
} |
||||||
|
|
||||||
|
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(); |
||||||
|
_codec?.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) |
||||||
|
{ |
||||||
|
if (_codec is null) |
||||||
|
throw new InvalidOperationException("The codec is null."); |
||||||
|
|
||||||
|
if (_targetBitmap is null) |
||||||
|
throw new InvalidOperationException("The target bitmap is null."); |
||||||
|
|
||||||
|
RenderFrame(_codec, _targetBitmap, frameIndex, _currentFrameIndex); |
||||||
|
_currentFrameIndex = frameIndex; |
||||||
|
|
||||||
|
return _targetBitmap; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>com.apple.security.cs.allow-jit</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,12 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
|
<plist version="1.0"> |
||||||
|
<dict> |
||||||
|
<key>com.apple.security.cs.allow-jit</key> |
||||||
|
<true/> |
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key> |
||||||
|
<true/> |
||||||
|
<key>com.apple.security.cs.disable-library-validation</key> |
||||||
|
<true/> |
||||||
|
</dict> |
||||||
|
</plist> |
@ -0,0 +1,26 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
while getopts v: flag |
||||||
|
do |
||||||
|
case "${flag}" in |
||||||
|
v) version=${OPTARG};; |
||||||
|
*) echo "Invalid option";; |
||||||
|
esac |
||||||
|
done |
||||||
|
|
||||||
|
dotnet \ |
||||||
|
msbuild \ |
||||||
|
StabilityMatrix.Avalonia \ |
||||||
|
-t:BundleApp \ |
||||||
|
-p:RuntimeIdentifier=osx-arm64 \ |
||||||
|
-p:UseAppHost=true \ |
||||||
|
-p:Configuration=Release \ |
||||||
|
-p:CFBundleShortVersionString="$version" \ |
||||||
|
-p:SelfContained=true \ |
||||||
|
-p:CFBundleName="Stability Matrix" \ |
||||||
|
-p:CFBundleDisplayName="Stability Matrix" \ |
||||||
|
-p:CFBundleVersion="$version" \ |
||||||
|
-p:PublishDir="$(pwd)/out/osx-arm64/bin" \ |
||||||
|
|
||||||
|
# Copy the app out of bin |
||||||
|
cp -r ./out/osx-arm64/bin/Stability\ Matrix.app ./out/osx-arm64/Stability\ Matrix.app |
@ -0,0 +1,62 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
echo "Signing file: $1" |
||||||
|
|
||||||
|
# Setup keychain in CI |
||||||
|
if [ -n "$CI" ]; then |
||||||
|
# Turn our base64-encoded certificate back to a regular .p12 file |
||||||
|
|
||||||
|
echo "$MACOS_CERTIFICATE" | base64 --decode -o certificate.p12 |
||||||
|
|
||||||
|
# We need to create a new keychain, otherwise using the certificate will prompt |
||||||
|
# with a UI dialog asking for the certificate password, which we can't |
||||||
|
# use in a headless CI environment |
||||||
|
|
||||||
|
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain |
||||||
|
security default-keychain -s build.keychain |
||||||
|
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain |
||||||
|
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign |
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain |
||||||
|
fi |
||||||
|
|
||||||
|
# Sign all files |
||||||
|
PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || return ; pwd -P ) |
||||||
|
ENTITLEMENTS="$PARENT_PATH/EmbeddedEntitlements.entitlements" |
||||||
|
|
||||||
|
echo "Using entitlements file: $ENTITLEMENTS" |
||||||
|
|
||||||
|
# App |
||||||
|
if [ "$1" == "*.app" ]; then |
||||||
|
echo "[INFO] Signing app contents" |
||||||
|
|
||||||
|
find "$1/Contents/MacOS/"|while read fname; do |
||||||
|
if [[ -f $fname ]]; then |
||||||
|
echo "[INFO] Signing $fname" |
||||||
|
codesign --force --timestamp -s "$MACOS_CERTIFICATE_NAME" --options=runtime --entitlements "$ENTITLEMENTS" "$fname" |
||||||
|
fi |
||||||
|
done |
||||||
|
|
||||||
|
echo "[INFO] Signing app file" |
||||||
|
|
||||||
|
codesign --force --timestamp -s "$MACOS_CERTIFICATE_NAME" --options=runtime --entitlements "$ENTITLEMENTS" "$1" -v |
||||||
|
# Directory |
||||||
|
elif [ -d "$1" ]; then |
||||||
|
echo "[INFO] Signing directory contents" |
||||||
|
|
||||||
|
find "$1"|while read fname; do |
||||||
|
if [[ -f $fname ]] && [[ ! $fname =~ /(*.(py|msg|enc))/ ]]; then |
||||||
|
echo "[INFO] Signing $fname" |
||||||
|
|
||||||
|
codesign --force --timestamp -s "$MACOS_CERTIFICATE_NAME" --options=runtime --entitlements "$ENTITLEMENTS" "$fname" |
||||||
|
fi |
||||||
|
done |
||||||
|
# File |
||||||
|
elif [ -f "$1" ]; then |
||||||
|
echo "[INFO] Signing file" |
||||||
|
|
||||||
|
codesign --force --timestamp -s "$MACOS_CERTIFICATE_NAME" --options=runtime --entitlements "$ENTITLEMENTS" "$1" -v |
||||||
|
# Not matched |
||||||
|
else |
||||||
|
echo "[ERROR] Unknown file type" |
||||||
|
exit 1 |
||||||
|
fi |
@ -0,0 +1,37 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
echo "Signing file: $1" |
||||||
|
|
||||||
|
# Setup keychain in CI |
||||||
|
if [ -n "$CI" ]; then |
||||||
|
# Turn our base64-encoded certificate back to a regular .p12 file |
||||||
|
|
||||||
|
echo "$MACOS_CERTIFICATE" | base64 --decode -o certificate.p12 |
||||||
|
|
||||||
|
# We need to create a new keychain, otherwise using the certificate will prompt |
||||||
|
# with a UI dialog asking for the certificate password, which we can't |
||||||
|
# use in a headless CI environment |
||||||
|
|
||||||
|
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain |
||||||
|
security default-keychain -s build.keychain |
||||||
|
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain |
||||||
|
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign |
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain |
||||||
|
fi |
||||||
|
|
||||||
|
# Sign all files |
||||||
|
PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || return ; pwd -P ) |
||||||
|
ENTITLEMENTS="$PARENT_PATH/AppEntitlements.entitlements" |
||||||
|
|
||||||
|
echo "Using entitlements file: $ENTITLEMENTS" |
||||||
|
|
||||||
|
find "$1/Contents/MacOS/"|while read fname; do |
||||||
|
if [[ -f $fname ]]; then |
||||||
|
echo "[INFO] Signing $fname" |
||||||
|
codesign --force --timestamp -s "$MACOS_CERTIFICATE_NAME" --options=runtime --entitlements "$ENTITLEMENTS" "$fname" |
||||||
|
fi |
||||||
|
done |
||||||
|
|
||||||
|
echo "[INFO] Signing app file" |
||||||
|
|
||||||
|
codesign --force --timestamp -s "$MACOS_CERTIFICATE_NAME" --options=runtime --entitlements "$ENTITLEMENTS" "$1" -v |
@ -0,0 +1,32 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
echo "Notarizing file: $1" |
||||||
|
|
||||||
|
# Store the notarization credentials so that we can prevent a UI password dialog |
||||||
|
# from blocking the CI |
||||||
|
|
||||||
|
echo "Create keychain profile" |
||||||
|
xcrun notarytool store-credentials "notarytool-profile" \ |
||||||
|
--apple-id "$MACOS_NOTARIZATION_APPLE_ID" \ |
||||||
|
--team-id "$MACOS_NOTARIZATION_TEAM_ID" \ |
||||||
|
--password "$MACOS_NOTARIZATION_PWD" |
||||||
|
|
||||||
|
# We can't notarize an app bundle directly, but we need to compress it as an archive. |
||||||
|
# Therefore, we create a zip file containing our app bundle, so that we can send it to the |
||||||
|
# notarization service |
||||||
|
|
||||||
|
echo "Creating temp notarization archive" |
||||||
|
ditto -c -k --keepParent "$1" "notarization.zip" |
||||||
|
|
||||||
|
# Here we send the notarization request to the Apple's Notarization service, waiting for the result. |
||||||
|
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App |
||||||
|
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if |
||||||
|
# you're curious |
||||||
|
|
||||||
|
echo "Notarize app" |
||||||
|
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait |
||||||
|
|
||||||
|
# Finally, we need to "attach the staple" to our executable, which will allow our app to be |
||||||
|
# validated by macOS even when an internet connection is not available. |
||||||
|
echo "Attach staple" |
||||||
|
xcrun stapler staple "$1" |
Binary file not shown.
@ -0,0 +1,141 @@ |
|||||||
|
using System; |
||||||
|
using System.Linq; |
||||||
|
using System.Reactive; |
||||||
|
using System.Reactive.Disposables; |
||||||
|
using System.Reactive.Linq; |
||||||
|
using DynamicData; |
||||||
|
using DynamicData.Binding; |
||||||
|
using JetBrains.Annotations; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Collections; |
||||||
|
|
||||||
|
[PublicAPI] |
||||||
|
public class SearchCollection<TObject, TKey, TQuery> : AbstractNotifyPropertyChanged, IDisposable |
||||||
|
where TObject : notnull |
||||||
|
where TKey : notnull |
||||||
|
{ |
||||||
|
private readonly IDisposable cleanUp; |
||||||
|
|
||||||
|
private Func<TQuery?, Func<TObject, bool>>? PredicateSelector { get; } |
||||||
|
private Func<TQuery?, Func<TObject, (bool, int)>>? ScorerSelector { get; } |
||||||
|
private Func<TObject, (bool, int)>? Scorer { get; set; } |
||||||
|
|
||||||
|
private TQuery? _query; |
||||||
|
public TQuery? Query |
||||||
|
{ |
||||||
|
get => _query; |
||||||
|
set => SetAndRaise(ref _query, value); |
||||||
|
} |
||||||
|
|
||||||
|
private SortExpressionComparer<TObject> _sortComparer = []; |
||||||
|
public SortExpressionComparer<TObject> SortComparer |
||||||
|
{ |
||||||
|
get => _sortComparer; |
||||||
|
set => SetAndRaise(ref _sortComparer, value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Converts <see cref="SortComparer"/> to <see cref="SortExpressionComparer{SearchItem}"/>. |
||||||
|
/// </summary> |
||||||
|
private SortExpressionComparer<SearchItem> SearchItemSortComparer => |
||||||
|
[ |
||||||
|
..SortComparer |
||||||
|
.Select(sortExpression => new SortExpression<SearchItem>( |
||||||
|
item => sortExpression.Expression(item.Item), |
||||||
|
sortExpression.Direction |
||||||
|
)).Prepend(new SortExpression<SearchItem>(item => item.Score, SortDirection.Descending)) |
||||||
|
]; |
||||||
|
|
||||||
|
public IObservableCollection<TObject> Items { get; } = new ObservableCollectionExtended<TObject>(); |
||||||
|
|
||||||
|
public IObservableCollection<TObject> FilteredItems { get; } = |
||||||
|
new ObservableCollectionExtended<TObject>(); |
||||||
|
|
||||||
|
public SearchCollection( |
||||||
|
IObservable<IChangeSet<TObject, TKey>> source, |
||||||
|
Func<TQuery?, Func<TObject, bool>> predicateSelector, |
||||||
|
SortExpressionComparer<TObject>? sortComparer = null |
||||||
|
) |
||||||
|
{ |
||||||
|
PredicateSelector = predicateSelector; |
||||||
|
|
||||||
|
if (sortComparer is not null) |
||||||
|
{ |
||||||
|
SortComparer = sortComparer; |
||||||
|
} |
||||||
|
|
||||||
|
// Observable which creates a new predicate whenever Query property changes |
||||||
|
var dynamicPredicate = this.WhenValueChanged(@this => @this.Query).Select(predicateSelector); |
||||||
|
|
||||||
|
cleanUp = source |
||||||
|
.Bind(Items) |
||||||
|
.Filter(dynamicPredicate) |
||||||
|
.Sort(SortComparer) |
||||||
|
.Bind(FilteredItems) |
||||||
|
.Subscribe(); |
||||||
|
} |
||||||
|
|
||||||
|
public SearchCollection( |
||||||
|
IObservable<IChangeSet<TObject, TKey>> source, |
||||||
|
Func<TQuery?, Func<TObject, (bool, int)>> scorerSelector, |
||||||
|
SortExpressionComparer<TObject>? sortComparer = null |
||||||
|
) |
||||||
|
{ |
||||||
|
ScorerSelector = scorerSelector; |
||||||
|
|
||||||
|
if (sortComparer is not null) |
||||||
|
{ |
||||||
|
SortComparer = sortComparer; |
||||||
|
} |
||||||
|
|
||||||
|
// Monitor Query property for changes |
||||||
|
var queryChanged = this.WhenValueChanged(@this => @this.Query).Select(_ => Unit.Default); |
||||||
|
|
||||||
|
cleanUp = new CompositeDisposable( |
||||||
|
// Update Scorer property whenever Query property changes |
||||||
|
queryChanged.Subscribe(_ => Scorer = scorerSelector(Query)), |
||||||
|
// Transform source items into SearchItems |
||||||
|
source |
||||||
|
.Transform( |
||||||
|
obj => |
||||||
|
{ |
||||||
|
var (isMatch, score) = Scorer?.Invoke(obj) ?? (true, 0); |
||||||
|
|
||||||
|
return new SearchItem |
||||||
|
{ |
||||||
|
Item = obj, |
||||||
|
IsMatch = isMatch, |
||||||
|
Score = score |
||||||
|
}; |
||||||
|
}, |
||||||
|
forceTransform: queryChanged |
||||||
|
) |
||||||
|
.Filter(item => item.IsMatch) |
||||||
|
.Sort(SearchItemSortComparer, SortOptimisations.ComparesImmutableValuesOnly) |
||||||
|
.Transform(searchItem => searchItem.Item) |
||||||
|
.Bind(FilteredItems) |
||||||
|
.Subscribe() |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Clears <see cref="Query"/> property by setting it to default value. |
||||||
|
/// </summary> |
||||||
|
public void ClearQuery() |
||||||
|
{ |
||||||
|
Query = default; |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
cleanUp.Dispose(); |
||||||
|
GC.SuppressFinalize(this); |
||||||
|
} |
||||||
|
|
||||||
|
private readonly record struct SearchItem |
||||||
|
{ |
||||||
|
public TObject Item { get; init; } |
||||||
|
public int Score { get; init; } |
||||||
|
public bool IsMatch { get; init; } |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
using Avalonia; |
||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Controls.Primitives; |
||||||
|
using Avalonia.Controls.Templates; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Controls; |
||||||
|
|
||||||
|
public class BetterComboBox : ComboBox |
||||||
|
{ |
||||||
|
public static readonly DirectProperty<BetterComboBox, IDataTemplate?> SelectionBoxItemTemplateProperty = |
||||||
|
AvaloniaProperty.RegisterDirect<BetterComboBox, IDataTemplate?>( |
||||||
|
nameof(SelectionBoxItemTemplate), |
||||||
|
v => v.SelectionBoxItemTemplate, |
||||||
|
(x, v) => x.SelectionBoxItemTemplate = v |
||||||
|
); |
||||||
|
|
||||||
|
private IDataTemplate? _selectionBoxItemTemplate; |
||||||
|
|
||||||
|
public IDataTemplate? SelectionBoxItemTemplate |
||||||
|
{ |
||||||
|
get => _selectionBoxItemTemplate; |
||||||
|
set => SetAndRaise(SelectionBoxItemTemplateProperty, ref _selectionBoxItemTemplate, value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||||
|
{ |
||||||
|
base.OnApplyTemplate(e); |
||||||
|
|
||||||
|
if (e.NameScope.Find<ContentControl>("ContentPresenter") is { } contentPresenter) |
||||||
|
{ |
||||||
|
if (SelectionBoxItemTemplate is { } template) |
||||||
|
{ |
||||||
|
contentPresenter.ContentTemplate = template; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Controls.Templates; |
||||||
|
using Avalonia.Metadata; |
||||||
|
using JetBrains.Annotations; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Controls; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Selector for objects implementing <see cref="ITemplateKey{T}"/> |
||||||
|
/// </summary> |
||||||
|
[PublicAPI] |
||||||
|
public class DataTemplateSelector<TKey> : IDataTemplate |
||||||
|
where TKey : notnull |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Key that is used when no other key matches |
||||||
|
/// </summary> |
||||||
|
public TKey? DefaultKey { get; set; } |
||||||
|
|
||||||
|
[Content] |
||||||
|
public Dictionary<TKey, IDataTemplate> Templates { get; } = new(); |
||||||
|
|
||||||
|
public bool Match(object? data) => data is ITemplateKey<TKey>; |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public Control Build(object? data) |
||||||
|
{ |
||||||
|
if (data is not ITemplateKey<TKey> key) |
||||||
|
throw new ArgumentException(null, nameof(data)); |
||||||
|
|
||||||
|
if (Templates.TryGetValue(key.TemplateKey, out var template)) |
||||||
|
{ |
||||||
|
return template.Build(data)!; |
||||||
|
} |
||||||
|
|
||||||
|
if (DefaultKey is not null && Templates.TryGetValue(DefaultKey, out var defaultTemplate)) |
||||||
|
{ |
||||||
|
return defaultTemplate.Build(data)!; |
||||||
|
} |
||||||
|
|
||||||
|
throw new ArgumentException(null, nameof(data)); |
||||||
|
} |
||||||
|
} |
@ -1,13 +1,122 @@ |
|||||||
using System; |
using System; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.IO; |
||||||
|
using AsyncAwaitBestPractices; |
||||||
|
using Avalonia; |
||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Controls.Presenters; |
||||||
|
using Avalonia.Logging; |
||||||
using FluentAvalonia.UI.Controls; |
using FluentAvalonia.UI.Controls; |
||||||
|
using StabilityMatrix.Core.Processes; |
||||||
|
using Symbol = FluentIcons.Common.Symbol; |
||||||
|
|
||||||
namespace StabilityMatrix.Avalonia.Controls; |
namespace StabilityMatrix.Avalonia.Controls; |
||||||
|
|
||||||
/// <summary> |
/// <summary> |
||||||
/// Like <see cref="HyperlinkButton"/>, but with a link icon left of the text content. |
/// Like <see cref="HyperlinkButton"/>, but with a link icon left of the text content. |
||||||
/// </summary> |
/// </summary> |
||||||
public class HyperlinkIconButton : HyperlinkButton |
public class HyperlinkIconButton : Button |
||||||
{ |
{ |
||||||
/// <inheritdoc /> |
private Uri? _navigateUri; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Defines the <see cref="NavigateUri"/> property |
||||||
|
/// </summary> |
||||||
|
public static readonly DirectProperty<HyperlinkIconButton, Uri?> NavigateUriProperty = |
||||||
|
AvaloniaProperty.RegisterDirect<HyperlinkIconButton, Uri?>( |
||||||
|
nameof(NavigateUri), |
||||||
|
x => x.NavigateUri, |
||||||
|
(x, v) => x.NavigateUri = v |
||||||
|
); |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Gets or sets the Uri that the button should navigate to upon clicking. In assembly paths are not supported, (e.g., avares://...) |
||||||
|
/// </summary> |
||||||
|
public Uri? NavigateUri |
||||||
|
{ |
||||||
|
get => _navigateUri; |
||||||
|
set => SetAndRaise(NavigateUriProperty, ref _navigateUri, value); |
||||||
|
} |
||||||
|
|
||||||
|
public static readonly StyledProperty<Symbol> IconProperty = AvaloniaProperty.Register< |
||||||
|
HyperlinkIconButton, |
||||||
|
Symbol |
||||||
|
>("Icon", Symbol.Link); |
||||||
|
|
||||||
|
public Symbol Icon |
||||||
|
{ |
||||||
|
get => GetValue(IconProperty); |
||||||
|
set => SetValue(IconProperty, value); |
||||||
|
} |
||||||
|
|
||||||
protected override Type StyleKeyOverride => typeof(HyperlinkIconButton); |
protected override Type StyleKeyOverride => typeof(HyperlinkIconButton); |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
||||||
|
{ |
||||||
|
base.OnPropertyChanged(change); |
||||||
|
|
||||||
|
// Update icon |
||||||
|
if (change.Property == NavigateUriProperty) |
||||||
|
{ |
||||||
|
var uri = change.GetNewValue<Uri?>(); |
||||||
|
|
||||||
|
if (uri is not null && uri.IsFile && Icon == Symbol.Link) |
||||||
|
{ |
||||||
|
Icon = Symbol.Open; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected override void OnClick() |
||||||
|
{ |
||||||
|
base.OnClick(); |
||||||
|
|
||||||
|
if (NavigateUri is null) |
||||||
|
return; |
||||||
|
|
||||||
|
// File or Folder URIs |
||||||
|
if (NavigateUri.IsFile) |
||||||
|
{ |
||||||
|
var path = NavigateUri.LocalPath; |
||||||
|
|
||||||
|
if (Directory.Exists(path)) |
||||||
|
{ |
||||||
|
ProcessRunner |
||||||
|
.OpenFolderBrowser(path) |
||||||
|
.SafeFireAndForget(ex => |
||||||
|
{ |
||||||
|
Logger.TryGet(LogEventLevel.Error, $"Unable to open directory Uri {NavigateUri}"); |
||||||
|
}); |
||||||
|
} |
||||||
|
else if (File.Exists(path)) |
||||||
|
{ |
||||||
|
ProcessRunner |
||||||
|
.OpenFileBrowser(path) |
||||||
|
.SafeFireAndForget(ex => |
||||||
|
{ |
||||||
|
Logger.TryGet(LogEventLevel.Error, $"Unable to open file Uri {NavigateUri}"); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
// Web |
||||||
|
else |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
Process.Start( |
||||||
|
new ProcessStartInfo(NavigateUri.ToString()) { UseShellExecute = true, Verb = "open" } |
||||||
|
); |
||||||
|
} |
||||||
|
catch |
||||||
|
{ |
||||||
|
Logger.TryGet(LogEventLevel.Error, $"Unable to open Uri {NavigateUri}"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool RegisterContentPresenter(ContentPresenter presenter) |
||||||
|
{ |
||||||
|
return presenter.Name == "ContentPresenter"; |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,122 @@ |
|||||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:controls="using:StabilityMatrix.Avalonia.Controls" |
||||||
|
x:DataType="video:SvdImgToVidConditioningViewModel" |
||||||
|
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||||
|
xmlns:video="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference.Video" |
||||||
|
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"> |
||||||
|
<Design.PreviewWith> |
||||||
|
<Grid MinWidth="400"> |
||||||
|
<controls:VideoGenerationSettingsCard DataContext="{x:Static mocks:DesignData.SvdImgToVidConditioningViewModel}" /> |
||||||
|
</Grid> |
||||||
|
</Design.PreviewWith> |
||||||
|
|
||||||
|
<Style Selector="controls|VideoGenerationSettingsCard"> |
||||||
|
<!-- Set Defaults --> |
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
||||||
|
<Setter Property="Template"> |
||||||
|
<ControlTemplate> |
||||||
|
<controls:Card Padding="8" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"> |
||||||
|
<Grid Margin="4" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, *"> |
||||||
|
<TextBlock |
||||||
|
Grid.Row="0" |
||||||
|
Grid.Column="0" |
||||||
|
Margin="0,0,8,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_Frames}" /> |
||||||
|
<controls1:NumberBox |
||||||
|
Grid.Row="0" |
||||||
|
Grid.Column="1" |
||||||
|
SelectionHighlightColor="Transparent" |
||||||
|
Value="{Binding NumFrames}" |
||||||
|
Margin="8,0,0,0" |
||||||
|
SimpleNumberFormat="F0" |
||||||
|
SmallChange="1" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
SpinButtonPlacementMode="Inline"/> |
||||||
|
|
||||||
|
<TextBlock |
||||||
|
Grid.Row="1" |
||||||
|
Grid.Column="0" |
||||||
|
Margin="0,8,8,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_Fps}" /> |
||||||
|
<controls1:NumberBox |
||||||
|
Grid.Row="1" |
||||||
|
Grid.Column="1" |
||||||
|
SelectionHighlightColor="Transparent" |
||||||
|
Value="{Binding Fps}" |
||||||
|
Margin="8,8,0,0" |
||||||
|
SimpleNumberFormat="F0" |
||||||
|
SmallChange="1" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
SpinButtonPlacementMode="Inline"/> |
||||||
|
|
||||||
|
<TextBlock |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="0" |
||||||
|
Margin="0,8,8,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_MinCfg}" /> |
||||||
|
<controls1:NumberBox |
||||||
|
Margin="8,8,0,0" |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="1" |
||||||
|
SelectionHighlightColor="Transparent" |
||||||
|
Value="{Binding MinCfg}" |
||||||
|
SimpleNumberFormat="F0" |
||||||
|
SmallChange="1" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
SpinButtonPlacementMode="Inline"/> |
||||||
|
|
||||||
|
<TextBlock |
||||||
|
Margin="0,8,8,0" |
||||||
|
Grid.Row="3" |
||||||
|
Grid.Column="0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_MotionBucketId}" /> |
||||||
|
<controls1:NumberBox |
||||||
|
Margin="8,8,0,0" |
||||||
|
Grid.Row="3" |
||||||
|
Grid.Column="1" |
||||||
|
SelectionHighlightColor="Transparent" |
||||||
|
Value="{Binding MotionBucketId}" |
||||||
|
SimpleNumberFormat="F0" |
||||||
|
SmallChange="1" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
SpinButtonPlacementMode="Inline"/> |
||||||
|
|
||||||
|
<StackPanel Grid.Column="0" |
||||||
|
Grid.ColumnSpan="2" |
||||||
|
Grid.Row="4" |
||||||
|
Margin="0,16,0,0"> |
||||||
|
<Grid ColumnDefinitions="*,Auto"> |
||||||
|
<TextBlock |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_AugmentationLevel}" /> |
||||||
|
<controls1:NumberBox |
||||||
|
Grid.Column="1" |
||||||
|
Margin="4,0,0,0" |
||||||
|
ValidationMode="InvalidInputOverwritten" |
||||||
|
SmallChange="0.01" |
||||||
|
SimpleNumberFormat="F2" |
||||||
|
Value="{Binding AugmentationLevel}" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
MinWidth="100" |
||||||
|
SpinButtonPlacementMode="Compact"/> |
||||||
|
</Grid> |
||||||
|
<Slider |
||||||
|
Minimum="0" |
||||||
|
Maximum="10" |
||||||
|
Value="{Binding AugmentationLevel}" |
||||||
|
TickFrequency="1" |
||||||
|
Margin="0,0,0,-4" |
||||||
|
TickPlacement="BottomRight"/> |
||||||
|
</StackPanel> |
||||||
|
</Grid> |
||||||
|
</controls:Card> |
||||||
|
</ControlTemplate> |
||||||
|
</Setter> |
||||||
|
</Style> |
||||||
|
</Styles> |
@ -0,0 +1,7 @@ |
|||||||
|
using Avalonia.Controls.Primitives; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Controls; |
||||||
|
|
||||||
|
[Transient] |
||||||
|
public class VideoGenerationSettingsCard : TemplatedControl { } |
@ -0,0 +1,98 @@ |
|||||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:controls="using:StabilityMatrix.Avalonia.Controls" |
||||||
|
x:DataType="video:VideoOutputSettingsCardViewModel" |
||||||
|
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||||
|
xmlns:video="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference.Video" |
||||||
|
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"> |
||||||
|
<Design.PreviewWith> |
||||||
|
<Grid MinWidth="400"> |
||||||
|
<controls:VideoOutputSettingsCard |
||||||
|
DataContext="{x:Static mocks:DesignData.SvdImgToVidConditioningViewModel}" /> |
||||||
|
</Grid> |
||||||
|
</Design.PreviewWith> |
||||||
|
|
||||||
|
<Style Selector="controls|VideoOutputSettingsCard"> |
||||||
|
<!-- Set Defaults --> |
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
||||||
|
<Setter Property="Template"> |
||||||
|
<ControlTemplate> |
||||||
|
<controls:Card Padding="8" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"> |
||||||
|
<Grid Margin="4" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, *"> |
||||||
|
<TextBlock |
||||||
|
Grid.Row="0" |
||||||
|
Grid.Column="0" |
||||||
|
Grid.ColumnSpan="2" |
||||||
|
Text="Video Output Settings" |
||||||
|
FontSize="16" |
||||||
|
FontWeight="DemiBold" |
||||||
|
Margin="0,0,0,16" |
||||||
|
/> |
||||||
|
<TextBlock |
||||||
|
Grid.Row="1" |
||||||
|
Grid.Column="0" |
||||||
|
Margin="0,0,8,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_Fps}" /> |
||||||
|
<controls1:NumberBox |
||||||
|
Grid.Row="1" |
||||||
|
Grid.Column="1" |
||||||
|
SelectionHighlightColor="Transparent" |
||||||
|
Value="{Binding Fps}" |
||||||
|
Margin="8,0,0,0" |
||||||
|
SimpleNumberFormat="F0" |
||||||
|
SmallChange="1" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
SpinButtonPlacementMode="Inline" /> |
||||||
|
|
||||||
|
<TextBlock |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="0" |
||||||
|
Margin="0,8,8,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_Lossless}" /> |
||||||
|
<CheckBox |
||||||
|
Grid.Row="2" |
||||||
|
Grid.Column="1" |
||||||
|
IsChecked="{Binding Lossless}" |
||||||
|
Margin="8,8,0,0" |
||||||
|
HorizontalAlignment="Stretch" /> |
||||||
|
|
||||||
|
<TextBlock |
||||||
|
Grid.Row="3" |
||||||
|
Grid.Column="0" |
||||||
|
Margin="0,8,8,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_VideoQuality}" /> |
||||||
|
<controls1:NumberBox |
||||||
|
Margin="8,8,0,0" |
||||||
|
Grid.Row="3" |
||||||
|
Grid.Column="1" |
||||||
|
SelectionHighlightColor="Transparent" |
||||||
|
Value="{Binding Quality}" |
||||||
|
SimpleNumberFormat="F0" |
||||||
|
SmallChange="1" |
||||||
|
Maximum="100" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
SpinButtonPlacementMode="Inline" /> |
||||||
|
|
||||||
|
<TextBlock |
||||||
|
Margin="0,8,8,0" |
||||||
|
Grid.Row="4" |
||||||
|
Grid.Column="0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Label_VideoOutputMethod}" /> |
||||||
|
<ComboBox |
||||||
|
Grid.Row="4" |
||||||
|
Grid.Column="1" |
||||||
|
Margin="8,8,0,0" |
||||||
|
MinWidth="100" |
||||||
|
ItemsSource="{Binding AvailableMethods}" |
||||||
|
SelectedIndex="{Binding SelectedMethod}" /> |
||||||
|
</Grid> |
||||||
|
</controls:Card> |
||||||
|
</ControlTemplate> |
||||||
|
</Setter> |
||||||
|
</Style> |
||||||
|
</Styles> |
@ -0,0 +1,7 @@ |
|||||||
|
using Avalonia.Controls.Primitives; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Controls; |
||||||
|
|
||||||
|
[Transient] |
||||||
|
public class VideoOutputSettingsCard : TemplatedControl { } |
@ -0,0 +1,40 @@ |
|||||||
|
using System; |
||||||
|
using System.Globalization; |
||||||
|
using System.Linq; |
||||||
|
using System.Reflection; |
||||||
|
using Avalonia.Data.Converters; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Converters; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Converts an enum value to an attribute |
||||||
|
/// </summary> |
||||||
|
/// <typeparam name="TAttribute">Type of attribute</typeparam> |
||||||
|
public class EnumAttributeConverter<TAttribute>(Func<TAttribute, object?>? accessor = null) : IValueConverter |
||||||
|
where TAttribute : Attribute |
||||||
|
{ |
||||||
|
/// <inheritdoc /> |
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
if (value is null) |
||||||
|
return null; |
||||||
|
|
||||||
|
if (value is not Enum @enum) |
||||||
|
throw new ArgumentException("Value must be an enum"); |
||||||
|
|
||||||
|
var field = @enum.GetType().GetField(@enum.ToString()); |
||||||
|
if (field is null) |
||||||
|
throw new ArgumentException("Value must be an enum"); |
||||||
|
|
||||||
|
if (field.GetCustomAttributes<TAttribute>().FirstOrDefault() is not { } attribute) |
||||||
|
throw new ArgumentException($"Enum value {@enum} does not have attribute {typeof(TAttribute)}"); |
||||||
|
|
||||||
|
return accessor is not null ? accessor(attribute) : attribute; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
throw new NotSupportedException(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
using System.ComponentModel.DataAnnotations; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Converters; |
||||||
|
|
||||||
|
internal static class EnumAttributeConverters |
||||||
|
{ |
||||||
|
public static EnumAttributeConverter<DisplayAttribute> Display => new(); |
||||||
|
|
||||||
|
public static EnumAttributeConverter<DisplayAttribute> DisplayName => new(attribute => attribute.Name); |
||||||
|
|
||||||
|
public static EnumAttributeConverter<DisplayAttribute> DisplayDescription => |
||||||
|
new(attribute => attribute.Description); |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
using System; |
||||||
|
using System.Globalization; |
||||||
|
using Avalonia.Data.Converters; |
||||||
|
using StabilityMatrix.Core.Extensions; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Converters; |
||||||
|
|
||||||
|
public class FileUriConverter : IValueConverter |
||||||
|
{ |
||||||
|
/// <inheritdoc /> |
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
if (targetType != typeof(Uri)) |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return value switch |
||||||
|
{ |
||||||
|
string str => new Uri("file://" + str), |
||||||
|
IFormattable formattable => new Uri("file://" + formattable.ToString(null, culture)), |
||||||
|
_ => null |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
if (targetType == typeof(string) && value is Uri uri) |
||||||
|
{ |
||||||
|
return uri.ToString().StripStart("file://"); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
using System; |
||||||
|
using System.Globalization; |
||||||
|
using System.Windows.Input; |
||||||
|
using Avalonia.Data.Converters; |
||||||
|
using PropertyModels.ComponentModel; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Converters; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Converts an object's named <see cref="Func{TResult}"/> to a <see cref="ICommand"/>. |
||||||
|
/// </summary> |
||||||
|
public class FuncCommandConverter : IValueConverter |
||||||
|
{ |
||||||
|
/// <inheritdoc /> |
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
if (value is null || parameter is null) |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
// Parameter is the name of the Func<T> to convert. |
||||||
|
if (parameter is not string funcName) |
||||||
|
{ |
||||||
|
// ReSharper disable once LocalizableElement |
||||||
|
throw new ArgumentException("Parameter must be a string.", nameof(parameter)); |
||||||
|
} |
||||||
|
|
||||||
|
// Find the Func<T> on the object. |
||||||
|
if (value.GetType().GetMethod(funcName) is not { } methodInfo) |
||||||
|
{ |
||||||
|
// ReSharper disable once LocalizableElement |
||||||
|
throw new ArgumentException( |
||||||
|
$"Method {funcName} not found on {value.GetType().Name}.", |
||||||
|
nameof(parameter) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Create a delegate from the method info. |
||||||
|
var func = (Action)methodInfo.CreateDelegate(typeof(Action), value); |
||||||
|
|
||||||
|
// Create ICommand |
||||||
|
var command = ReactiveCommand.Create(func); |
||||||
|
|
||||||
|
return command; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||||
|
{ |
||||||
|
throw new NotSupportedException(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
using System; |
||||||
|
using Avalonia.Controls.Notifications; |
||||||
|
using StabilityMatrix.Core.Models.Settings; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Extensions; |
||||||
|
|
||||||
|
public static class NotificationLevelExtensions |
||||||
|
{ |
||||||
|
public static NotificationType ToNotificationType(this NotificationLevel level) |
||||||
|
{ |
||||||
|
return level switch |
||||||
|
{ |
||||||
|
NotificationLevel.Information => NotificationType.Information, |
||||||
|
NotificationLevel.Success => NotificationType.Success, |
||||||
|
NotificationLevel.Warning => NotificationType.Warning, |
||||||
|
NotificationLevel.Error => NotificationType.Error, |
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null) |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
using System.Threading.Tasks; |
||||||
|
using AsyncAwaitBestPractices; |
||||||
|
using DesktopNotifications; |
||||||
|
using NLog; |
||||||
|
using StabilityMatrix.Avalonia.Services; |
||||||
|
using StabilityMatrix.Core.Models.PackageModification; |
||||||
|
using StabilityMatrix.Core.Models.Settings; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Extensions; |
||||||
|
|
||||||
|
public static class NotificationServiceExtensions |
||||||
|
{ |
||||||
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
||||||
|
|
||||||
|
public static void OnPackageInstallCompleted( |
||||||
|
this INotificationService notificationService, |
||||||
|
IPackageModificationRunner runner |
||||||
|
) |
||||||
|
{ |
||||||
|
OnPackageInstallCompletedAsync(notificationService, runner) |
||||||
|
.SafeFireAndForget(ex => Logger.Error(ex, "Error Showing Notification")); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task OnPackageInstallCompletedAsync( |
||||||
|
this INotificationService notificationService, |
||||||
|
IPackageModificationRunner runner |
||||||
|
) |
||||||
|
{ |
||||||
|
if (runner.Failed) |
||||||
|
{ |
||||||
|
Logger.Error(runner.Exception, "Error Installing Package"); |
||||||
|
|
||||||
|
await notificationService.ShowAsync( |
||||||
|
NotificationKey.Package_Install_Failed, |
||||||
|
new Notification |
||||||
|
{ |
||||||
|
Title = runner.ModificationFailedTitle, |
||||||
|
Body = runner.ModificationFailedMessage |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
await notificationService.ShowAsync( |
||||||
|
NotificationKey.Package_Install_Completed, |
||||||
|
new Notification |
||||||
|
{ |
||||||
|
Title = runner.ModificationCompleteTitle, |
||||||
|
Body = runner.ModificationCompleteMessage |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,933 @@ |
|||||||
|
<?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>Starten</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Quit" xml:space="preserve"> |
||||||
|
<value>Beenden</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Save" xml:space="preserve"> |
||||||
|
<value>Speichern</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Cancel" xml:space="preserve"> |
||||||
|
<value>Abbrechen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Language" xml:space="preserve"> |
||||||
|
<value>Sprache</value> |
||||||
|
</data> |
||||||
|
<data name="Text_RelaunchRequiredToApplyLanguage" xml:space="preserve"> |
||||||
|
<value>Ein Neustart ist nötig, um Sprachänderungen vorzunehmen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Relaunch" xml:space="preserve"> |
||||||
|
<value>Neustarten</value> |
||||||
|
</data> |
||||||
|
<data name="Action_RelaunchLater" xml:space="preserve"> |
||||||
|
<value>Später Neustarten</value> |
||||||
|
</data> |
||||||
|
<data name="Label_RelaunchRequired" xml:space="preserve"> |
||||||
|
<value>Neustart benötigt</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UnknownPackage" xml:space="preserve"> |
||||||
|
<value>Unbekanntes Paket</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Import" xml:space="preserve"> |
||||||
|
<value>Importieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PackageType" xml:space="preserve"> |
||||||
|
<value>Pakettyp</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Version" xml:space="preserve"> |
||||||
|
<value>Version</value> |
||||||
|
</data> |
||||||
|
<data name="Label_VersionType" xml:space="preserve"> |
||||||
|
<value>Versionstyp</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Releases" xml:space="preserve"> |
||||||
|
<value>Veröffentlichungen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Branches" xml:space="preserve"> |
||||||
|
<value>Zweige</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DragAndDropCheckpointsHereToImport" xml:space="preserve"> |
||||||
|
<value>Checkpoints hierherziehen zum importieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Emphasis" xml:space="preserve"> |
||||||
|
<value>Betonung</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Deemphasis" xml:space="preserve"> |
||||||
|
<value>Negativ Betonung</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EmbeddingsOrTextualInversion" xml:space="preserve"> |
||||||
|
<value>Embeddings</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NetworksLoraOrLycoris" xml:space="preserve"> |
||||||
|
<value>Netzwerk (Lora / LyCORIS)</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Comments" xml:space="preserve"> |
||||||
|
<value>Kommentare</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ShowPixelGridAtHighZoomLevels" xml:space="preserve"> |
||||||
|
<value>Pixel-Gitter bei hohen Zoomstufen anzeigen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Steps" xml:space="preserve"> |
||||||
|
<value>Schritte</value> |
||||||
|
</data> |
||||||
|
<data name="Label_StepsBase" xml:space="preserve"> |
||||||
|
<value>Schritte - Base</value> |
||||||
|
</data> |
||||||
|
<data name="Label_StepsRefiner" xml:space="preserve"> |
||||||
|
<value>Schritte - Refiner</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CFGScale" xml:space="preserve"> |
||||||
|
<value>CFG Wert</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DenoisingStrength" xml:space="preserve"> |
||||||
|
<value>Entrauschungsstärke</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Width" xml:space="preserve"> |
||||||
|
<value>Breite</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Height" xml:space="preserve"> |
||||||
|
<value>Höhe</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Refiner" xml:space="preserve"> |
||||||
|
<value>Refiner</value> |
||||||
|
</data> |
||||||
|
<data name="Label_VAE" xml:space="preserve"> |
||||||
|
<value>VAE</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Model" xml:space="preserve"> |
||||||
|
<value>Modell</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Connect" xml:space="preserve"> |
||||||
|
<value>Verbinden</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConnectingEllipsis" xml:space="preserve"> |
||||||
|
<value>Verbindet...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Close" xml:space="preserve"> |
||||||
|
<value>Schließen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_WaitingToConnectEllipsis" xml:space="preserve"> |
||||||
|
<value>Warten auf die Verbindung...</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UpdateAvailable" xml:space="preserve"> |
||||||
|
<value>Updates verfügbar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_BecomeAPatron" xml:space="preserve"> |
||||||
|
<value>Werde ein Patreon</value> |
||||||
|
</data> |
||||||
|
<data name="Label_JoinDiscord" xml:space="preserve"> |
||||||
|
<value>Trete dem Discord Server bei</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Downloads" xml:space="preserve"> |
||||||
|
<value>Downloads</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Install" xml:space="preserve"> |
||||||
|
<value>Installieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SkipSetup" xml:space="preserve"> |
||||||
|
<value>Erstmalige Einstellung überspringen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UnexpectedErrorOccurred" xml:space="preserve"> |
||||||
|
<value>Ein unerwarteter Fehler ist aufgetreten</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ExitApplication" xml:space="preserve"> |
||||||
|
<value>Beende die Applikation</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DisplayName" xml:space="preserve"> |
||||||
|
<value>Anzeigename</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InstallationWithThisNameExists" xml:space="preserve"> |
||||||
|
<value>Eine Installation mit diesem namen existiert bereits.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PleaseChooseDifferentName" xml:space="preserve"> |
||||||
|
<value>Bitte wähle einen anderen namen oder ändere den Installationsordner.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AdvancedOptions" xml:space="preserve"> |
||||||
|
<value>Erweiterte Optionen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Commit" xml:space="preserve"> |
||||||
|
<value>Commit</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SharedModelFolderStrategy" xml:space="preserve"> |
||||||
|
<value>Geteilte Modell Ordnerstrategie</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PyTorchVersion" xml:space="preserve"> |
||||||
|
<value>PyTorch Version</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CloseDialogWhenFinished" xml:space="preserve"> |
||||||
|
<value>Dialog nach Beendigung schließen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataDirectory" xml:space="preserve"> |
||||||
|
<value>Daten Ordner</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataDirectoryExplanation" xml:space="preserve"> |
||||||
|
<value>Hier werden die Applikationsdaten (Modell Checkpoints, Web UIs, etc.) installiert.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_FatWarning" xml:space="preserve"> |
||||||
|
<value>Bei der Verwendung eines FAT32- oder exFAT-Laufwerks können Fehler auftreten. Wählen Sie ein anderes Laufwerk, um ein reibungsloseres Arbeiten zu ermöglichen.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PortableMode" xml:space="preserve"> |
||||||
|
<value>Tragbarer Modus</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PortableModeExplanation" xml:space="preserve"> |
||||||
|
<value>Im portablen Modus werden alle Daten und Einstellungen in demselben Verzeichnis wie die Anwendung gespeichert. Sie können die Anwendung mit ihrem 'Daten'-Ordner an einen anderen Ort oder auf einen anderen Computer verschieben.</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Continue" xml:space="preserve"> |
||||||
|
<value>Weiter</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PreviousImage" xml:space="preserve"> |
||||||
|
<value>Vorheriges Bild</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NextImage" xml:space="preserve"> |
||||||
|
<value>Nächstes Bild</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelDescription" xml:space="preserve"> |
||||||
|
<value>Modellbeschreibung</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NewVersionAvailable" xml:space="preserve"> |
||||||
|
<value>Eine neue Version von Stability Matrix ist verfügbar!</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImportLatest" xml:space="preserve"> |
||||||
|
<value>Neueste Importieren -</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AllVersions" xml:space="preserve"> |
||||||
|
<value>Alle Versionen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelSearchWatermark" xml:space="preserve"> |
||||||
|
<value>Suche modelle, #tags, or @nutzer</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Search" xml:space="preserve"> |
||||||
|
<value>Suchen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Sort" xml:space="preserve"> |
||||||
|
<value>Sortieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_TimePeriod" xml:space="preserve"> |
||||||
|
<value>Periode</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelType" xml:space="preserve"> |
||||||
|
<value>Modelltyp</value> |
||||||
|
</data> |
||||||
|
<data name="Label_BaseModel" xml:space="preserve"> |
||||||
|
<value>Basis Modell</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ShowNsfwContent" xml:space="preserve"> |
||||||
|
<value>Zeige NSFW Inhalte</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataProvidedByCivitAi" xml:space="preserve"> |
||||||
|
<value>Daten bereitgestellt von CivitAI</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Page" xml:space="preserve"> |
||||||
|
<value>Seite</value> |
||||||
|
</data> |
||||||
|
<data name="Label_FirstPage" xml:space="preserve"> |
||||||
|
<value>Erste Seite</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PreviousPage" xml:space="preserve"> |
||||||
|
<value>Vorherige Seite</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NextPage" xml:space="preserve"> |
||||||
|
<value>Nächste Seite</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LastPage" xml:space="preserve"> |
||||||
|
<value>Letzte Seite</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Rename" xml:space="preserve"> |
||||||
|
<value>Umbenennen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Delete" xml:space="preserve"> |
||||||
|
<value>Löschen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenOnCivitAi" xml:space="preserve"> |
||||||
|
<value>Öffnen in CivitAI</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConnectedModel" xml:space="preserve"> |
||||||
|
<value>Verbundenes Modell</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LocalModel" xml:space="preserve"> |
||||||
|
<value>Lokales Modell</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ShowInExplorer" xml:space="preserve"> |
||||||
|
<value>Im Explorer anzeigen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_New" xml:space="preserve"> |
||||||
|
<value>Neu</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Folder" xml:space="preserve"> |
||||||
|
<value>Ordner</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DropFileToImport" xml:space="preserve"> |
||||||
|
<value>Datei zum Importieren hier ablegen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImportAsConnected" xml:space="preserve"> |
||||||
|
<value>Importieren mit Metadaten</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImportAsConnectedExplanation" xml:space="preserve"> |
||||||
|
<value>Suche nach verbundenen Metadaten bei neuen lokalen Importen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Indexing" xml:space="preserve"> |
||||||
|
<value>Indizierung...</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelsFolder" xml:space="preserve"> |
||||||
|
<value>Modellordner</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Categories" xml:space="preserve"> |
||||||
|
<value>Kategorien</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LetsGetStarted" xml:space="preserve"> |
||||||
|
<value>Lass uns starten</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ReadAndAgree" xml:space="preserve"> |
||||||
|
<value>Ich habe gelesen und akzeptiere die</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LicenseAgreement" xml:space="preserve"> |
||||||
|
<value>Lizensbestimmungen.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_FindConnectedMetadata" xml:space="preserve"> |
||||||
|
<value>Finde verbundene Metadaten</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ShowModelImages" xml:space="preserve"> |
||||||
|
<value>Zeige Modell Bilder</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Appearance" xml:space="preserve"> |
||||||
|
<value>Aussehen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Theme" xml:space="preserve"> |
||||||
|
<value>Thema</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CheckpointManager" xml:space="preserve"> |
||||||
|
<value>Checkpoint Manager</value> |
||||||
|
</data> |
||||||
|
<data name="Label_RemoveSymlinksOnShutdown" xml:space="preserve"> |
||||||
|
<value>Symbolische Links auf gemeinsame Checkpoints beim Herunterfahren entfernen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_RemoveSymlinksOnShutdown_Details" xml:space="preserve"> |
||||||
|
<value>Wählen Sie diese Option, wenn Sie Probleme beim Verschieben von Stability Matrix auf ein anderes Laufwerk haben</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ResetCheckpointsCache" xml:space="preserve"> |
||||||
|
<value>Checkpoint-Cache zurücksetzen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ResetCheckpointsCache_Details" xml:space="preserve"> |
||||||
|
<value>Stellt den installierten Checkpoint-Cache wieder her. Wird verwendet, wenn Prüfpunkte im Modell-Browser falsch beschriftet sind</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PackageEnvironment" xml:space="preserve"> |
||||||
|
<value>Paket Umgebung</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Edit" xml:space="preserve"> |
||||||
|
<value>Bearbeiten</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EnvironmentVariables" xml:space="preserve"> |
||||||
|
<value>Umgebungsvariablen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EmbeddedPython" xml:space="preserve"> |
||||||
|
<value>Eingebettetes Python</value> |
||||||
|
</data> |
||||||
|
<data name="Action_CheckVersion" xml:space="preserve"> |
||||||
|
<value>Version überprüfen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Integrations" xml:space="preserve"> |
||||||
|
<value>Integrationen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DiscordRichPresence" xml:space="preserve"> |
||||||
|
<value>Discord Rich Presence</value> |
||||||
|
</data> |
||||||
|
<data name="Label_System" xml:space="preserve"> |
||||||
|
<value>System</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AddToStartMenu" xml:space="preserve"> |
||||||
|
<value>Füge Stability Matrix zum Startmenü hinzu</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AddToStartMenu_Details" xml:space="preserve"> |
||||||
|
<value>Verwendet den aktuellen Standort der Anwendung, Sie können dies erneut ausführen, wenn Sie die Anwendung verschieben</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OnlyAvailableOnWindows" xml:space="preserve"> |
||||||
|
<value>Nur auf Windows verfügbar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_AddForCurrentUser" xml:space="preserve"> |
||||||
|
<value>Hinzufügen für aktuellen nutzer</value> |
||||||
|
</data> |
||||||
|
<data name="Action_AddForAllUsers" xml:space="preserve"> |
||||||
|
<value>Hinzufügen für alle Nutzer</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SelectNewDataDirectory" xml:space="preserve"> |
||||||
|
<value>Wähle einen neuen Daten Ordner aus</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SelectNewDataDirectory_Details" xml:space="preserve"> |
||||||
|
<value>Verschiebt keine existierenden Daten</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SelectDirectory" xml:space="preserve"> |
||||||
|
<value>Ordner auswählen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_About" xml:space="preserve"> |
||||||
|
<value>Über</value> |
||||||
|
</data> |
||||||
|
<data name="Label_StabilityMatrix" xml:space="preserve"> |
||||||
|
<value>Stability Matrix</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LicenseAndOpenSourceNotices" xml:space="preserve"> |
||||||
|
<value>Lizenz und Open Source Notizen</value> |
||||||
|
</data> |
||||||
|
<data name="TeachingTip_ClickLaunchToGetStarted" xml:space="preserve"> |
||||||
|
<value>Klicken Sie auf Start, um loszulegen!</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Stop" xml:space="preserve"> |
||||||
|
<value>Stopp</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SendInput" xml:space="preserve"> |
||||||
|
<value>Eingaben senden</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Input" xml:space="preserve"> |
||||||
|
<value>Eingaben</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Send" xml:space="preserve"> |
||||||
|
<value>Senden</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InputRequired" xml:space="preserve"> |
||||||
|
<value>Eingaben benötigt</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConfirmQuestion" xml:space="preserve"> |
||||||
|
<value>Bestätigen?</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Yes" xml:space="preserve"> |
||||||
|
<value>Ja</value> |
||||||
|
</data> |
||||||
|
<data name="Label_No" xml:space="preserve"> |
||||||
|
<value>Nein</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenWebUI" xml:space="preserve"> |
||||||
|
<value>Web UI öffnen</value> |
||||||
|
</data> |
||||||
|
<data name="Text_WelcomeToStabilityMatrix" xml:space="preserve"> |
||||||
|
<value>Willkommen zu Stability Matrix!</value> |
||||||
|
</data> |
||||||
|
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve"> |
||||||
|
<value>Wählen Sie Ihre bevorzugte Schnittstelle und klicken Sie auf Installieren, um loszulegen.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Installing" xml:space="preserve"> |
||||||
|
<value>Installiert</value> |
||||||
|
</data> |
||||||
|
<data name="Text_ProceedingToLaunchPage" xml:space="preserve"> |
||||||
|
<value>Weiter zur Seite Start</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_DownloadingPackage" xml:space="preserve"> |
||||||
|
<value>Herunterladen des Pakets...</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_DownloadComplete" xml:space="preserve"> |
||||||
|
<value>Herunterladen abgeschlossen</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_InstallationComplete" xml:space="preserve"> |
||||||
|
<value>Installation abgeschlossen</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_InstallingPrerequisites" xml:space="preserve"> |
||||||
|
<value>Voraussetzungen installieren...</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_InstallingPackageRequirements" xml:space="preserve"> |
||||||
|
<value>Paket-Voraussetzungen installieren...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInExplorer" xml:space="preserve"> |
||||||
|
<value>Öffnen im Explorer</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInFinder" xml:space="preserve"> |
||||||
|
<value>Öffnen in Finder</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Uninstall" xml:space="preserve"> |
||||||
|
<value>Deinstallieren</value> |
||||||
|
</data> |
||||||
|
<data name="Action_CheckForUpdates" xml:space="preserve"> |
||||||
|
<value>Auf Updates überprüfen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Update" xml:space="preserve"> |
||||||
|
<value>Update</value> |
||||||
|
</data> |
||||||
|
<data name="Action_AddPackage" xml:space="preserve"> |
||||||
|
<value>Paket hinzufügen</value> |
||||||
|
</data> |
||||||
|
<data name="TeachingTip_AddPackageToGetStarted" xml:space="preserve"> |
||||||
|
<value>Füge ein Paket hinzu, um loszulegen!</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EnvVarsTable_Name" xml:space="preserve"> |
||||||
|
<value>Name</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EnvVarsTable_Value" xml:space="preserve"> |
||||||
|
<value>Wert</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Remove" xml:space="preserve"> |
||||||
|
<value>Entfernen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Details" xml:space="preserve"> |
||||||
|
<value>Details</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Callstack" xml:space="preserve"> |
||||||
|
<value>Callstack</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InnerException" xml:space="preserve"> |
||||||
|
<value>Inner Exception</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SearchEllipsis" xml:space="preserve"> |
||||||
|
<value>Suchen...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OK" xml:space="preserve"> |
||||||
|
<value>OK</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Retry" xml:space="preserve"> |
||||||
|
<value>Erneut versuchen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PythonVersionInfo" xml:space="preserve"> |
||||||
|
<value>Python Versionsinfo</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Restart" xml:space="preserve"> |
||||||
|
<value>Neustarten</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConfirmDelete" xml:space="preserve"> |
||||||
|
<value>Löschen bestätigen</value> |
||||||
|
</data> |
||||||
|
<data name="Text_PackageUninstall_Details" xml:space="preserve"> |
||||||
|
<value>Dadurch werden der Paketordner und sein gesamter Inhalt gelöscht, einschließlich aller generierten Bilder und Dateien, die Sie hinzugefügt haben.</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_UninstallingPackage" xml:space="preserve"> |
||||||
|
<value>Paket deinstallieren...</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PackageUninstalled" xml:space="preserve"> |
||||||
|
<value>Paket deinstalliert</value> |
||||||
|
</data> |
||||||
|
<data name="Text_SomeFilesCouldNotBeDeleted" xml:space="preserve"> |
||||||
|
<value>Einige Dateien konnten nicht gelöscht werden. Bitte schließen Sie alle offenen Dateien im Paketverzeichnis und versuchen Sie es erneut.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InvalidPackageType" xml:space="preserve"> |
||||||
|
<value>Ungültiger Pakettyp</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_UpdatingPackage" xml:space="preserve"> |
||||||
|
<value>Aktualisiert {0}</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_UpdateComplete" xml:space="preserve"> |
||||||
|
<value>Aktualisierung abgeschlossen</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_PackageUpdatedToLatest" xml:space="preserve"> |
||||||
|
<value>{0} wurde auf die neueste Version aktualisiert</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_ErrorUpdatingPackage" xml:space="preserve"> |
||||||
|
<value>Fehler beim Aktualisieren {0}</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_UpdateFailed" xml:space="preserve"> |
||||||
|
<value>Aktualisierung fehlgeschlagen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInBrowser" xml:space="preserve"> |
||||||
|
<value>Öffnen im Browser</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ErrorInstallingPackage" xml:space="preserve"> |
||||||
|
<value>Fehler bei der Installation des Pakets</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Branch" xml:space="preserve"> |
||||||
|
<value>Zweig</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AutoScrollToEnd" xml:space="preserve"> |
||||||
|
<value>Automatisch zum Ende der Konsolenausgabe blättern</value> |
||||||
|
</data> |
||||||
|
<data name="Label_License" xml:space="preserve"> |
||||||
|
<value>Lizenz</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SharedModelStrategyShort" xml:space="preserve"> |
||||||
|
<value>Modell-Sharing</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PleaseSelectDataDirectory" xml:space="preserve"> |
||||||
|
<value>Bitte wählen Sie ein Datenverzeichnis</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataFolderName" xml:space="preserve"> |
||||||
|
<value>Name des Datenordners</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CurrentDirectory" xml:space="preserve"> |
||||||
|
<value>Aktuelles Verzeichnis:</value> |
||||||
|
</data> |
||||||
|
<data name="Text_AppWillRelaunchAfterUpdate" xml:space="preserve"> |
||||||
|
<value>Die App wird nach der Aktualisierung neu gestartet</value> |
||||||
|
</data> |
||||||
|
<data name="Action_RemindMeLater" xml:space="preserve"> |
||||||
|
<value>Erinnern Sie mich später</value> |
||||||
|
</data> |
||||||
|
<data name="Action_InstallNow" xml:space="preserve"> |
||||||
|
<value>Jetzt installieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ReleaseNotes" xml:space="preserve"> |
||||||
|
<value>Anmerkungen zur Veröffentlichung</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenProjectEllipsis" xml:space="preserve"> |
||||||
|
<value>Projekt öffnen...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SaveAsEllipsis" xml:space="preserve"> |
||||||
|
<value>Speichern als...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_RestoreDefaultLayout" xml:space="preserve"> |
||||||
|
<value>Standard-Layout wiederherstellen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UseSharedOutputFolder" xml:space="preserve"> |
||||||
|
<value>Gemeinsame Nutzung des Outputs</value> |
||||||
|
</data> |
||||||
|
<data name="Label_BatchIndex" xml:space="preserve"> |
||||||
|
<value>Batch Index</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Copy" xml:space="preserve"> |
||||||
|
<value>Kopieren</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInViewer" xml:space="preserve"> |
||||||
|
<value>Öffnen im Bildbetrachter</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NumImagesSelected" xml:space="preserve"> |
||||||
|
<value>{0} Bilder ausgewählt</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OutputFolder" xml:space="preserve"> |
||||||
|
<value>Ausgabe-Ordner</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OutputType" xml:space="preserve"> |
||||||
|
<value>Ausgabetyp</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ClearSelection" xml:space="preserve"> |
||||||
|
<value>Auswahl löschen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SelectAll" xml:space="preserve"> |
||||||
|
<value>Alle auswählen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SendToInference" xml:space="preserve"> |
||||||
|
<value>An Inference senden</value> |
||||||
|
</data> |
||||||
|
<data name="Label_TextToImage" xml:space="preserve"> |
||||||
|
<value>Text zu Bild</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImageToImage" xml:space="preserve"> |
||||||
|
<value>Bild zu Bild</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Inpainting" xml:space="preserve"> |
||||||
|
<value>Inpainting</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Upscale" xml:space="preserve"> |
||||||
|
<value>Hochskalieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OutputsPageTitle" xml:space="preserve"> |
||||||
|
<value>Ausgabe-Browser</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OneImageSelected" xml:space="preserve"> |
||||||
|
<value>1 Bild ausgewählt</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PythonPackages" xml:space="preserve"> |
||||||
|
<value>Python Pakete</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Consolidate" xml:space="preserve"> |
||||||
|
<value>Konsolidieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AreYouSure" xml:space="preserve"> |
||||||
|
<value>Sind Sie sicher?</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConsolidateExplanation" xml:space="preserve"> |
||||||
|
<value>Dadurch werden alle generierten Bilder aus den ausgewählten Paketen in das konsolidierte Verzeichnis des gemeinsamen Ausgabeordners verschoben. Diese Aktion kann nicht rückgängig gemacht werden.</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Refresh" xml:space="preserve"> |
||||||
|
<value>Aktualisieren</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Upgrade" xml:space="preserve"> |
||||||
|
<value>Upgrade</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Downgrade" xml:space="preserve"> |
||||||
|
<value>Downgrade</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenGithub" xml:space="preserve"> |
||||||
|
<value>Öffnen in GitHub</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Connected" xml:space="preserve"> |
||||||
|
<value>Verbunden</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Disconnect" xml:space="preserve"> |
||||||
|
<value>Nicht verbunden</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Email" xml:space="preserve"> |
||||||
|
<value>Email</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Username" xml:space="preserve"> |
||||||
|
<value>Nutzername</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Password" xml:space="preserve"> |
||||||
|
<value>Password</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Login" xml:space="preserve"> |
||||||
|
<value>Einloggen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Signup" xml:space="preserve"> |
||||||
|
<value>Anmelden</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConfirmPassword" xml:space="preserve"> |
||||||
|
<value>Passwort bestätigen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ApiKey" xml:space="preserve"> |
||||||
|
<value>API Schlüssel</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Preprocessor" xml:space="preserve"> |
||||||
|
<value>Preprocessor</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Strength" xml:space="preserve"> |
||||||
|
<value>Stärke</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ControlWeight" xml:space="preserve"> |
||||||
|
<value>Kontrollgewicht</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ControlSteps" xml:space="preserve"> |
||||||
|
<value>Kontrollschritte</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CivitAiLoginRequired" xml:space="preserve"> |
||||||
|
<value>Sie müssen eingeloggt sein, um diesen Checkpoint herunterladen zu können. Bitte geben Sie einen CivitAI API Schlüssel in den Einstellungen ein.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DownloadFailed" xml:space="preserve"> |
||||||
|
<value>Download fehlgeschlagen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AutoUpdates" xml:space="preserve"> |
||||||
|
<value>Automatische Updates</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UpdatesPreviewChannelDescription" xml:space="preserve"> |
||||||
|
<value>Für Early Adopters. Preview-Builds sind zuverlässiger als die aus dem Dev-Channel und werden näher an den stabilen Versionen verfügbar sein. Ihr Feedback hilft uns sehr dabei, Probleme zu entdecken und Designelemente zu verbessern.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UpdatesDevChannelDescription" xml:space="preserve"> |
||||||
|
<value>Für technische Benutzer. Seien Sie der Erste, der auf unsere Entwicklungs-Builds aus den Funktionszweigen zugreift, sobald sie verfügbar sind. Da wir mit neuen Funktionen experimentieren, kann es noch einige Ecken und Kanten und Bugs geben.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Updates" xml:space="preserve"> |
||||||
|
<value>Updates</value> |
||||||
|
</data> |
||||||
|
<data name="Label_YouAreUpToDate" xml:space="preserve"> |
||||||
|
<value>Sie sind auf dem aktuellsten Stand</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_LastChecked" xml:space="preserve"> |
||||||
|
<value>Zuletzt überprüft: {0}</value> |
||||||
|
</data> |
||||||
|
<data name="Action_CopyTriggerWords" xml:space="preserve"> |
||||||
|
<value>Trigger Wörter kopieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_TriggerWords" xml:space="preserve"> |
||||||
|
<value>Trigger Wörter:</value> |
||||||
|
</data> |
||||||
|
<data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve"> |
||||||
|
<value>Zusätzliche Ordner wie IPAdapter und TextualInversions (Einbettungen) können hier aktiviert werden</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenOnHuggingFace" xml:space="preserve"> |
||||||
|
<value>Öffnen in Hugging Face</value> |
||||||
|
</data> |
||||||
|
<data name="Action_UpdateExistingMetadata" xml:space="preserve"> |
||||||
|
<value>Vorhandene Metadaten aktualisieren</value> |
||||||
|
</data> |
||||||
|
<data name="Label_General" xml:space="preserve"> |
||||||
|
<value>Generell</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>Ausgabe von Bilddateien</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImageViewer" xml:space="preserve"> |
||||||
|
<value>Bildbetrachter</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AutoCompletion" xml:space="preserve"> |
||||||
|
<value>Automatische Vervollständigung</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CompletionReplaceUnderscoresWithSpaces" xml:space="preserve"> |
||||||
|
<value>Ersetzen von Unterstrichen durch Leerzeichen beim Einfügen von Vervollständigungen</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>Importiere Prompt Tags</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PromptTagsDescription" xml:space="preserve"> |
||||||
|
<value>Tags-Datei, die zum Vorschlagen von Vervollständigungen verwendet wird (unterstützt das Format a1111-sd-webui-tagcomplete .csv)</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SystemInformation" xml:space="preserve"> |
||||||
|
<value>Systeminformationen</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>Zusätze</value> |
||||||
|
<comment>Inference Sampler Addons</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_SaveIntermediateImage" xml:space="preserve"> |
||||||
|
<value>Zwischenbild speichern</value> |
||||||
|
<comment>Inference module step to save an intermediate image</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_Settings" xml:space="preserve"> |
||||||
|
<value>Einstellungen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SelectFile" xml:space="preserve"> |
||||||
|
<value>Datei auswählen</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ReplaceContents" xml:space="preserve"> |
||||||
|
<value>Inhalt ersetzen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_WipFeature" xml:space="preserve"> |
||||||
|
<value>Noch nicht verfügbar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_WipFeatureDescription" xml:space="preserve"> |
||||||
|
<value>Die Funktion wird in einem zukünftigen Update verfügbar sein</value> |
||||||
|
</data> |
||||||
|
<data name="Label_MissingImageFile" xml:space="preserve"> |
||||||
|
<value>Fehlende Bilddatei</value> |
||||||
|
</data> |
||||||
|
<data name="Label_HolidayMode" xml:space="preserve"> |
||||||
|
<value>Urlaubsmodus</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CLIPSkip" xml:space="preserve"> |
||||||
|
<value>CLIP überspringen</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImageToVideo" xml:space="preserve"> |
||||||
|
<value>Bild zu Video</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Fps" xml:space="preserve"> |
||||||
|
<value>Bilder pro Sekunde </value> |
||||||
|
</data> |
||||||
|
<data name="Label_MinCfg" xml:space="preserve"> |
||||||
|
<value>Min CFG</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Lossless" xml:space="preserve"> |
||||||
|
<value>Fehlerfrei</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Frames" xml:space="preserve"> |
||||||
|
<value>Bilder</value> |
||||||
|
</data> |
||||||
|
<data name="Label_MotionBucketId" xml:space="preserve"> |
||||||
|
<value>Motion Bucket ID</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AugmentationLevel" xml:space="preserve"> |
||||||
|
<value>Augmentierungslevel</value> |
||||||
|
</data> |
||||||
|
<data name="Label_VideoOutputMethod" xml:space="preserve"> |
||||||
|
<value>Methode</value> |
||||||
|
</data> |
||||||
|
<data name="Label_VideoQuality" xml:space="preserve"> |
||||||
|
<value>Qualität</value> |
||||||
|
</data> |
||||||
|
</root> |
@ -0,0 +1,933 @@ |
|||||||
|
<?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>Executar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Quit" xml:space="preserve"> |
||||||
|
<value>Encerrar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Save" xml:space="preserve"> |
||||||
|
<value>Salvar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Cancel" xml:space="preserve"> |
||||||
|
<value>Cancelar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Language" xml:space="preserve"> |
||||||
|
<value>Idioma</value> |
||||||
|
</data> |
||||||
|
<data name="Text_RelaunchRequiredToApplyLanguage" xml:space="preserve"> |
||||||
|
<value>Reiniciar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Relaunch" xml:space="preserve"> |
||||||
|
<value>Reiniciar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_RelaunchLater" xml:space="preserve"> |
||||||
|
<value>Reiniciar mais tarde</value> |
||||||
|
</data> |
||||||
|
<data name="Label_RelaunchRequired" xml:space="preserve"> |
||||||
|
<value>Necessário Reiniciar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UnknownPackage" xml:space="preserve"> |
||||||
|
<value>Pacote Desconhecido</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Import" xml:space="preserve"> |
||||||
|
<value>Importar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PackageType" xml:space="preserve"> |
||||||
|
<value>Tipo do Pacote</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Version" xml:space="preserve"> |
||||||
|
<value>Versão</value> |
||||||
|
</data> |
||||||
|
<data name="Label_VersionType" xml:space="preserve"> |
||||||
|
<value>Tipo da Versão</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Releases" xml:space="preserve"> |
||||||
|
<value>Versões</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Branches" xml:space="preserve"> |
||||||
|
<value>Ramificações</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DragAndDropCheckpointsHereToImport" xml:space="preserve"> |
||||||
|
<value>Arraste e Solte os Checkpoints aqui para Importar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Emphasis" xml:space="preserve"> |
||||||
|
<value>Enfatizar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Deemphasis" xml:space="preserve"> |
||||||
|
<value>Desenfatizar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EmbeddingsOrTextualInversion" xml:space="preserve"> |
||||||
|
<value>Emebeddings / Inversão Textual</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NetworksLoraOrLycoris" xml:space="preserve"> |
||||||
|
<value>Networks (Lora / LyCORIS)</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Comments" xml:space="preserve"> |
||||||
|
<value>Comentários</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ShowPixelGridAtHighZoomLevels" xml:space="preserve"> |
||||||
|
<value>Exibir Pixels Grid com níveis de Zoom</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Steps" xml:space="preserve"> |
||||||
|
<value>Passos</value> |
||||||
|
</data> |
||||||
|
<data name="Label_StepsBase" xml:space="preserve"> |
||||||
|
<value>Passos - Base</value> |
||||||
|
</data> |
||||||
|
<data name="Label_StepsRefiner" xml:space="preserve"> |
||||||
|
<value>Passos - Refinamento</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CFGScale" xml:space="preserve"> |
||||||
|
<value>Escale de CFG</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DenoisingStrength" xml:space="preserve"> |
||||||
|
<value>Nível de Denoising</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Width" xml:space="preserve"> |
||||||
|
<value>Largura</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Height" xml:space="preserve"> |
||||||
|
<value>Altura</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Refiner" xml:space="preserve"> |
||||||
|
<value>Refinamento</value> |
||||||
|
</data> |
||||||
|
<data name="Label_VAE" xml:space="preserve"> |
||||||
|
<value>VAE</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Model" xml:space="preserve"> |
||||||
|
<value>Modelo</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Connect" xml:space="preserve"> |
||||||
|
<value>Conectar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConnectingEllipsis" xml:space="preserve"> |
||||||
|
<value>Conectando...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Close" xml:space="preserve"> |
||||||
|
<value>Fechar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_WaitingToConnectEllipsis" xml:space="preserve"> |
||||||
|
<value>Aguardando pela conecção</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UpdateAvailable" xml:space="preserve"> |
||||||
|
<value>Novo Update disponível</value> |
||||||
|
</data> |
||||||
|
<data name="Label_BecomeAPatron" xml:space="preserve"> |
||||||
|
<value>Torne-se um colaborador no Patreon</value> |
||||||
|
</data> |
||||||
|
<data name="Label_JoinDiscord" xml:space="preserve"> |
||||||
|
<value>Junte-se ao nosso canal no Discord</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Downloads" xml:space="preserve"> |
||||||
|
<value>Downloads</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Install" xml:space="preserve"> |
||||||
|
<value>Instalação</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SkipSetup" xml:space="preserve"> |
||||||
|
<value>Pular as configurações iniciais</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UnexpectedErrorOccurred" xml:space="preserve"> |
||||||
|
<value>Um erro inesperado ocorreu</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ExitApplication" xml:space="preserve"> |
||||||
|
<value>Sair da Aplicação</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DisplayName" xml:space="preserve"> |
||||||
|
<value>Nome de Exibição</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InstallationWithThisNameExists" xml:space="preserve"> |
||||||
|
<value>Uma instalação com este nome já existe</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PleaseChooseDifferentName" xml:space="preserve"> |
||||||
|
<value>Por favor escolha um novo nome ou escolha um novo local para a instalação</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AdvancedOptions" xml:space="preserve"> |
||||||
|
<value>Opções Avançadas</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Commit" xml:space="preserve"> |
||||||
|
<value>Salvar alterações</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SharedModelFolderStrategy" xml:space="preserve"> |
||||||
|
<value>Estratégia de Modelo de Pasta compartilhado</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PyTorchVersion" xml:space="preserve"> |
||||||
|
<value>Versão to PyTorch</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CloseDialogWhenFinished" xml:space="preserve"> |
||||||
|
<value>Fechar diálogo quando finalizar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataDirectory" xml:space="preserve"> |
||||||
|
<value>DIretório de Dados</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataDirectoryExplanation" xml:space="preserve"> |
||||||
|
<value>Este local é onde os dados do aplicativo (modelos de Checkpoints, Interfaces Web, etc.) serão instaldos</value> |
||||||
|
</data> |
||||||
|
<data name="Label_FatWarning" xml:space="preserve"> |
||||||
|
<value>Você poderá encontrar erros quando usar discos rígidos com formatação FAT32 ou exFAT. Escolha outro tipo de HD para uma experiência melhor.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PortableMode" xml:space="preserve"> |
||||||
|
<value>Modo Portátil</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PortableModeExplanation" xml:space="preserve"> |
||||||
|
<value>Com o Modo Portátil, todos os dados e configurações serão salvos no mesmo diretório da aplicação. Você poderá mover a aplicação com suas pastas de dados para um local diferente em seu computador.</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Continue" xml:space="preserve"> |
||||||
|
<value>Continuar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PreviousImage" xml:space="preserve"> |
||||||
|
<value>Imagem Anterior</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NextImage" xml:space="preserve"> |
||||||
|
<value>Próxima Imagem</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelDescription" xml:space="preserve"> |
||||||
|
<value>Descrição do Modelo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NewVersionAvailable" xml:space="preserve"> |
||||||
|
<value>Uma nova versão do Stability Matrix está disponível</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImportLatest" xml:space="preserve"> |
||||||
|
<value>Importar última versão</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AllVersions" xml:space="preserve"> |
||||||
|
<value>Todas as Versões</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelSearchWatermark" xml:space="preserve"> |
||||||
|
<value>Pesquisar Modelos, #tags, ou @users</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Search" xml:space="preserve"> |
||||||
|
<value>Pesquisar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Sort" xml:space="preserve"> |
||||||
|
<value>Filtrar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_TimePeriod" xml:space="preserve"> |
||||||
|
<value>Período</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelType" xml:space="preserve"> |
||||||
|
<value>Tipo do Modelo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_BaseModel" xml:space="preserve"> |
||||||
|
<value>Modelo Base</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ShowNsfwContent" xml:space="preserve"> |
||||||
|
<value>Mostar Conteúdo NSFW (adulto)</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataProvidedByCivitAi" xml:space="preserve"> |
||||||
|
<value>Dados fornecidos pela CivitAI</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Page" xml:space="preserve"> |
||||||
|
<value>Página</value> |
||||||
|
</data> |
||||||
|
<data name="Label_FirstPage" xml:space="preserve"> |
||||||
|
<value>Primeira Página</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PreviousPage" xml:space="preserve"> |
||||||
|
<value>Página Anterior</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NextPage" xml:space="preserve"> |
||||||
|
<value>Próxima Página</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LastPage" xml:space="preserve"> |
||||||
|
<value>Última Página</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Rename" xml:space="preserve"> |
||||||
|
<value>Renomear</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Delete" xml:space="preserve"> |
||||||
|
<value>Apagar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenOnCivitAi" xml:space="preserve"> |
||||||
|
<value>Abrir em CivitAI</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConnectedModel" xml:space="preserve"> |
||||||
|
<value>Modelo Conectado</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LocalModel" xml:space="preserve"> |
||||||
|
<value>Modelo Local</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ShowInExplorer" xml:space="preserve"> |
||||||
|
<value>Exibir no Explorer</value> |
||||||
|
</data> |
||||||
|
<data name="Action_New" xml:space="preserve"> |
||||||
|
<value>Novo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Folder" xml:space="preserve"> |
||||||
|
<value>Pasta</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DropFileToImport" xml:space="preserve"> |
||||||
|
<value>Coloque o arquivo aqui para Importar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImportAsConnected" xml:space="preserve"> |
||||||
|
<value>Importar com Metadados</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImportAsConnectedExplanation" xml:space="preserve"> |
||||||
|
<value>Procurar por Metadados conectados em novos locais de importação</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Indexing" xml:space="preserve"> |
||||||
|
<value>Indexando...</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ModelsFolder" xml:space="preserve"> |
||||||
|
<value>Pasta de Modelos</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Categories" xml:space="preserve"> |
||||||
|
<value>Catagorias</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LetsGetStarted" xml:space="preserve"> |
||||||
|
<value>Vamos começar!</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ReadAndAgree" xml:space="preserve"> |
||||||
|
<value>Ei lí e concordei com o</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LicenseAgreement" xml:space="preserve"> |
||||||
|
<value>Acordo de Licenciamento</value> |
||||||
|
</data> |
||||||
|
<data name="Label_FindConnectedMetadata" xml:space="preserve"> |
||||||
|
<value>Procurar Metadados Conectados</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ShowModelImages" xml:space="preserve"> |
||||||
|
<value>Exibir imagens dos Modelos</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Appearance" xml:space="preserve"> |
||||||
|
<value>Aparencia</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Theme" xml:space="preserve"> |
||||||
|
<value>Tema</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CheckpointManager" xml:space="preserve"> |
||||||
|
<value>Gerenciador de Checkpoint</value> |
||||||
|
</data> |
||||||
|
<data name="Label_RemoveSymlinksOnShutdown" xml:space="preserve"> |
||||||
|
<value>Remove os links simbólico compartilhados do diretorio de Checkpoints ao encerrar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_RemoveSymlinksOnShutdown_Details" xml:space="preserve"> |
||||||
|
<value>Selecione esta opção caso você esteja encontrando problemas ao mover o Stability Matrix para outro HD</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ResetCheckpointsCache" xml:space="preserve"> |
||||||
|
<value>Resetar o cache de Checkpoints</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ResetCheckpointsCache_Details" xml:space="preserve"> |
||||||
|
<value>Recriar o cache de Checkpoints instalados. Use caso os Checkpoints estiverem com a descrição incorreta no Selecionador de Modelos</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PackageEnvironment" xml:space="preserve"> |
||||||
|
<value>Ambiente do Pacote de Instalação</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Edit" xml:space="preserve"> |
||||||
|
<value>Editar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EnvironmentVariables" xml:space="preserve"> |
||||||
|
<value>Variáveis de Ambiente</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EmbeddedPython" xml:space="preserve"> |
||||||
|
<value>Python Embutido</value> |
||||||
|
</data> |
||||||
|
<data name="Action_CheckVersion" xml:space="preserve"> |
||||||
|
<value>Verificar Versão</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Integrations" xml:space="preserve"> |
||||||
|
<value>Integrações</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DiscordRichPresence" xml:space="preserve"> |
||||||
|
<value>Presença no Discord</value> |
||||||
|
</data> |
||||||
|
<data name="Label_System" xml:space="preserve"> |
||||||
|
<value>Sistema</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AddToStartMenu" xml:space="preserve"> |
||||||
|
<value>Adicionar o Stability Matrix ao Menu Iniciar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AddToStartMenu_Details" xml:space="preserve"> |
||||||
|
<value>Utilizar o local atual do aplicativo. Você poderá executar esta opção novamente caso você mover o aplicativo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OnlyAvailableOnWindows" xml:space="preserve"> |
||||||
|
<value>Apenas disponível no Windows</value> |
||||||
|
</data> |
||||||
|
<data name="Action_AddForCurrentUser" xml:space="preserve"> |
||||||
|
<value>Adicionar para o Usuário Atual</value> |
||||||
|
</data> |
||||||
|
<data name="Action_AddForAllUsers" xml:space="preserve"> |
||||||
|
<value>Adicionar para todos os Usuários</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SelectNewDataDirectory" xml:space="preserve"> |
||||||
|
<value>Selecionar novo Diretório de Dados</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SelectNewDataDirectory_Details" xml:space="preserve"> |
||||||
|
<value>Não mover os dados atuais</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SelectDirectory" xml:space="preserve"> |
||||||
|
<value>Selecionar o Diretório</value> |
||||||
|
</data> |
||||||
|
<data name="Label_About" xml:space="preserve"> |
||||||
|
<value>Sobre</value> |
||||||
|
</data> |
||||||
|
<data name="Label_StabilityMatrix" xml:space="preserve"> |
||||||
|
<value>Stability Matrix</value> |
||||||
|
</data> |
||||||
|
<data name="Label_LicenseAndOpenSourceNotices" xml:space="preserve"> |
||||||
|
<value>Avisos de licença e código aberto</value> |
||||||
|
</data> |
||||||
|
<data name="TeachingTip_ClickLaunchToGetStarted" xml:space="preserve"> |
||||||
|
<value>Clique em Iniciar para começar!</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Stop" xml:space="preserve"> |
||||||
|
<value>Parar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SendInput" xml:space="preserve"> |
||||||
|
<value>Enviar comando</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Input" xml:space="preserve"> |
||||||
|
<value>Input</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Send" xml:space="preserve"> |
||||||
|
<value>Enviar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InputRequired" xml:space="preserve"> |
||||||
|
<value>Input necessário</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConfirmQuestion" xml:space="preserve"> |
||||||
|
<value>Confirma?</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Yes" xml:space="preserve"> |
||||||
|
<value>Sim</value> |
||||||
|
</data> |
||||||
|
<data name="Label_No" xml:space="preserve"> |
||||||
|
<value>Não</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenWebUI" xml:space="preserve"> |
||||||
|
<value>Abrir a Interface Web</value> |
||||||
|
</data> |
||||||
|
<data name="Text_WelcomeToStabilityMatrix" xml:space="preserve"> |
||||||
|
<value>Bem-vindo ao Stability Matrix!</value> |
||||||
|
</data> |
||||||
|
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve"> |
||||||
|
<value>Escolha sua interface preferida e clique em Instalar para começar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Installing" xml:space="preserve"> |
||||||
|
<value>Instalando</value> |
||||||
|
</data> |
||||||
|
<data name="Text_ProceedingToLaunchPage" xml:space="preserve"> |
||||||
|
<value>Prosseguindo para a Página de Inicial</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_DownloadingPackage" xml:space="preserve"> |
||||||
|
<value>Baixando pacote...</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_DownloadComplete" xml:space="preserve"> |
||||||
|
<value>Download completo</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_InstallationComplete" xml:space="preserve"> |
||||||
|
<value>Instalação completa</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_InstallingPrerequisites" xml:space="preserve"> |
||||||
|
<value>Instalando pré-requisitos...</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_InstallingPackageRequirements" xml:space="preserve"> |
||||||
|
<value>Instalando requisitos de pacote...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInExplorer" xml:space="preserve"> |
||||||
|
<value>Abrir no Explorer</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInFinder" xml:space="preserve"> |
||||||
|
<value>Abrir no Finder</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Uninstall" xml:space="preserve"> |
||||||
|
<value>Desinstalar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_CheckForUpdates" xml:space="preserve"> |
||||||
|
<value>Verificar se existem atualizações</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Update" xml:space="preserve"> |
||||||
|
<value>Atualizar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_AddPackage" xml:space="preserve"> |
||||||
|
<value>Adicionar um pacote</value> |
||||||
|
</data> |
||||||
|
<data name="TeachingTip_AddPackageToGetStarted" xml:space="preserve"> |
||||||
|
<value>Adicione um pacote para iniciar!</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EnvVarsTable_Name" xml:space="preserve"> |
||||||
|
<value>Nome</value> |
||||||
|
</data> |
||||||
|
<data name="Label_EnvVarsTable_Value" xml:space="preserve"> |
||||||
|
<value>Valor</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Remove" xml:space="preserve"> |
||||||
|
<value>Remover</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Details" xml:space="preserve"> |
||||||
|
<value>Detalhes</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Callstack" xml:space="preserve"> |
||||||
|
<value>Pilha de chamadas</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InnerException" xml:space="preserve"> |
||||||
|
<value>Exceção interna</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SearchEllipsis" xml:space="preserve"> |
||||||
|
<value>Procurar...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OK" xml:space="preserve"> |
||||||
|
<value>OK</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Retry" xml:space="preserve"> |
||||||
|
<value>Tentar novamente</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PythonVersionInfo" xml:space="preserve"> |
||||||
|
<value>Informações da versão do Python</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Restart" xml:space="preserve"> |
||||||
|
<value>Reiniciar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConfirmDelete" xml:space="preserve"> |
||||||
|
<value>Confirmar exclusão</value> |
||||||
|
</data> |
||||||
|
<data name="Text_PackageUninstall_Details" xml:space="preserve"> |
||||||
|
<value>Isso excluirá a pasta do pacote e todo o seu conteúdo, incluindo quaisquer imagens e arquivos gerados que você possa ter adicionado.</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_UninstallingPackage" xml:space="preserve"> |
||||||
|
<value>Desinstalando pacote...</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PackageUninstalled" xml:space="preserve"> |
||||||
|
<value>Pacote desinstalado</value> |
||||||
|
</data> |
||||||
|
<data name="Text_SomeFilesCouldNotBeDeleted" xml:space="preserve"> |
||||||
|
<value>Alguns arquivos não puderam ser excluídos. Feche todos os arquivos abertos no diretório do pacote e tente novamente.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_InvalidPackageType" xml:space="preserve"> |
||||||
|
<value>Tipo de pacote inválido</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_UpdatingPackage" xml:space="preserve"> |
||||||
|
<value>Atualizando {0}</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_UpdateComplete" xml:space="preserve"> |
||||||
|
<value>Atualização completa</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_PackageUpdatedToLatest" xml:space="preserve"> |
||||||
|
<value>{0} foi atualizado para a versão mais recente</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_ErrorUpdatingPackage" xml:space="preserve"> |
||||||
|
<value>Erro ao atualizar {0}</value> |
||||||
|
</data> |
||||||
|
<data name="Progress_UpdateFailed" xml:space="preserve"> |
||||||
|
<value>A Atualização falhou</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInBrowser" xml:space="preserve"> |
||||||
|
<value>Abrir no navegador</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ErrorInstallingPackage" xml:space="preserve"> |
||||||
|
<value>Erro ao instalar o pacote</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Branch" xml:space="preserve"> |
||||||
|
<value>Branch</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AutoScrollToEnd" xml:space="preserve"> |
||||||
|
<value>Rolar automaticamente até o final da saída do console</value> |
||||||
|
</data> |
||||||
|
<data name="Label_License" xml:space="preserve"> |
||||||
|
<value>Licença</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SharedModelStrategyShort" xml:space="preserve"> |
||||||
|
<value>Compartilhamento de modelo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PleaseSelectDataDirectory" xml:space="preserve"> |
||||||
|
<value>Selecione um diretório de dados</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DataFolderName" xml:space="preserve"> |
||||||
|
<value>Nome da pasta de dados</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CurrentDirectory" xml:space="preserve"> |
||||||
|
<value>Diretório atual:</value> |
||||||
|
</data> |
||||||
|
<data name="Text_AppWillRelaunchAfterUpdate" xml:space="preserve"> |
||||||
|
<value>O aplicativo será reiniciado após a atualização</value> |
||||||
|
</data> |
||||||
|
<data name="Action_RemindMeLater" xml:space="preserve"> |
||||||
|
<value>Lembre-me mais tarde</value> |
||||||
|
</data> |
||||||
|
<data name="Action_InstallNow" xml:space="preserve"> |
||||||
|
<value>instale agora</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ReleaseNotes" xml:space="preserve"> |
||||||
|
<value>Notas de versão</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenProjectEllipsis" xml:space="preserve"> |
||||||
|
<value>Abrir Projeto...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SaveAsEllipsis" xml:space="preserve"> |
||||||
|
<value>Salvar como...</value> |
||||||
|
</data> |
||||||
|
<data name="Action_RestoreDefaultLayout" xml:space="preserve"> |
||||||
|
<value>Restaurar layout padrão</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UseSharedOutputFolder" xml:space="preserve"> |
||||||
|
<value>Usar Pasta de saída Compartilhada </value> |
||||||
|
</data> |
||||||
|
<data name="Label_BatchIndex" xml:space="preserve"> |
||||||
|
<value>Índice de lote</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Copy" xml:space="preserve"> |
||||||
|
<value>Copiar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenInViewer" xml:space="preserve"> |
||||||
|
<value>Abrir no Visualizador de Imagens</value> |
||||||
|
</data> |
||||||
|
<data name="Label_NumImagesSelected" xml:space="preserve"> |
||||||
|
<value>{0} imagens selecionadas</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OutputFolder" xml:space="preserve"> |
||||||
|
<value>Pasta de saída</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OutputType" xml:space="preserve"> |
||||||
|
<value>Tipo de saída</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ClearSelection" xml:space="preserve"> |
||||||
|
<value>Limpar Seleção</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SelectAll" xml:space="preserve"> |
||||||
|
<value>Selecionar tudo</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SendToInference" xml:space="preserve"> |
||||||
|
<value>Enviar para inferência</value> |
||||||
|
</data> |
||||||
|
<data name="Label_TextToImage" xml:space="preserve"> |
||||||
|
<value>Texto para imagem</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImageToImage" xml:space="preserve"> |
||||||
|
<value>Imagem para imagem</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Inpainting" xml:space="preserve"> |
||||||
|
<value>Inpainting (Pintar sobre a imagem)</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Upscale" xml:space="preserve"> |
||||||
|
<value>Aumentar o tamanho da Imagem</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OutputsPageTitle" xml:space="preserve"> |
||||||
|
<value>Navegador de Exibição</value> |
||||||
|
</data> |
||||||
|
<data name="Label_OneImageSelected" xml:space="preserve"> |
||||||
|
<value>1 imagem selecionada</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PythonPackages" xml:space="preserve"> |
||||||
|
<value>Pacotes Python</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Consolidate" xml:space="preserve"> |
||||||
|
<value>Consolidar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AreYouSure" xml:space="preserve"> |
||||||
|
<value>Tem certeza?</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConsolidateExplanation" xml:space="preserve"> |
||||||
|
<value>Isso moverá todas as imagens geradas dos pacotes selecionados para o diretório Consolidado da pasta de saídas compartilhadas. Essa ação não pode ser desfeita.</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Refresh" xml:space="preserve"> |
||||||
|
<value>Atualizar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Upgrade" xml:space="preserve"> |
||||||
|
<value>Upgrade</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Downgrade" xml:space="preserve"> |
||||||
|
<value>Downgrade</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenGithub" xml:space="preserve"> |
||||||
|
<value>Abrir no GitHub</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Connected" xml:space="preserve"> |
||||||
|
<value>Conectado</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Disconnect" xml:space="preserve"> |
||||||
|
<value>Desconectar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Email" xml:space="preserve"> |
||||||
|
<value>E-mail</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Username" xml:space="preserve"> |
||||||
|
<value>Nome de usuário</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Password" xml:space="preserve"> |
||||||
|
<value>Senha</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Login" xml:space="preserve"> |
||||||
|
<value>Conectar</value> |
||||||
|
</data> |
||||||
|
<data name="Action_Signup" xml:space="preserve"> |
||||||
|
<value>Cadastrar Conta</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ConfirmPassword" xml:space="preserve"> |
||||||
|
<value>Confirme sua senha</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ApiKey" xml:space="preserve"> |
||||||
|
<value>Chave API</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Accounts" xml:space="preserve"> |
||||||
|
<value>Contas</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Preprocessor" xml:space="preserve"> |
||||||
|
<value>Pré-processador</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Strength" xml:space="preserve"> |
||||||
|
<value>Força a aplicar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ControlWeight" xml:space="preserve"> |
||||||
|
<value>Controle de Potência a aplicar</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ControlSteps" xml:space="preserve"> |
||||||
|
<value>Número de Etapas de controle</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CivitAiLoginRequired" xml:space="preserve"> |
||||||
|
<value>Você deve estar logado para baixar este ponto de verificação. Insira uma chave de API CivitAI nas configurações.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_DownloadFailed" xml:space="preserve"> |
||||||
|
<value>O Download falhou</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AutoUpdates" xml:space="preserve"> |
||||||
|
<value>Atualizações automáticas</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UpdatesPreviewChannelDescription" xml:space="preserve"> |
||||||
|
<value>Para os usuários Beta. Os Builds (compilações) de pré-visualização serão mais confiáveis do que as do canal Dev e estarão disponíveis mais próximas de versões estáveis. Seu feedback nos ajudará muito a descobrir problemas e aprimorar os elementos do design.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_UpdatesDevChannelDescription" xml:space="preserve"> |
||||||
|
<value>Para usuários técnicos. Seja o primeiro a acessar nossos Builds (compilações) de desenvolvimento a partir das versões com novos recursos assim que estes estiverem disponíveis. Podem haver alguns Bugs e pequenos problemas à medida que experimentamos novos recursos.</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Updates" xml:space="preserve"> |
||||||
|
<value>Atualizações</value> |
||||||
|
</data> |
||||||
|
<data name="Label_YouAreUpToDate" xml:space="preserve"> |
||||||
|
<value>Você está atualizado</value> |
||||||
|
</data> |
||||||
|
<data name="TextTemplate_LastChecked" xml:space="preserve"> |
||||||
|
<value>Última verificação: {0}</value> |
||||||
|
</data> |
||||||
|
<data name="Action_CopyTriggerWords" xml:space="preserve"> |
||||||
|
<value>Copiar palavras que executam ações</value> |
||||||
|
</data> |
||||||
|
<data name="Label_TriggerWords" xml:space="preserve"> |
||||||
|
<value>Palavras que executam ações:</value> |
||||||
|
</data> |
||||||
|
<data name="TeachingTip_MoreCheckpointCategories" xml:space="preserve"> |
||||||
|
<value>Pastas adicionais como IPAdapters e TextualInversions (embeddings) podem ser habilitadas aqui</value> |
||||||
|
</data> |
||||||
|
<data name="Action_OpenOnHuggingFace" xml:space="preserve"> |
||||||
|
<value>Abrir no Hugging Face</value> |
||||||
|
</data> |
||||||
|
<data name="Action_UpdateExistingMetadata" xml:space="preserve"> |
||||||
|
<value>Atualizar metadados existentes</value> |
||||||
|
</data> |
||||||
|
<data name="Label_General" xml:space="preserve"> |
||||||
|
<value>Em geral</value> |
||||||
|
<comment>A general settings category</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_Inference" xml:space="preserve"> |
||||||
|
<value>Inferência</value> |
||||||
|
<comment>The Inference feature page</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_Prompt" xml:space="preserve"> |
||||||
|
<value>Incitar</value> |
||||||
|
<comment>A settings category for Inference generation prompts</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_OutputImageFiles" xml:space="preserve"> |
||||||
|
<value>Arquivos de imagem de saída</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImageViewer" xml:space="preserve"> |
||||||
|
<value>Visualizador de imagens</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AutoCompletion" xml:space="preserve"> |
||||||
|
<value>Preenchimento automático</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CompletionReplaceUnderscoresWithSpaces" xml:space="preserve"> |
||||||
|
<value>Substitua sublinhados por espaços ao inserir conclusões</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PromptTags" xml:space="preserve"> |
||||||
|
<value>Etiquetas de prompt</value> |
||||||
|
<comment>Tags for image generation prompts</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_PromptTagsImport" xml:space="preserve"> |
||||||
|
<value>Tags de prompt de importação</value> |
||||||
|
</data> |
||||||
|
<data name="Label_PromptTagsDescription" xml:space="preserve"> |
||||||
|
<value>Arquivo de tags a ser usado para sugerir conclusões (suporta o formato .csv a1111-sd-webui-tagcomplete)</value> |
||||||
|
</data> |
||||||
|
<data name="Label_SystemInformation" xml:space="preserve"> |
||||||
|
<value>Informação do sistema</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CivitAi" xml:space="preserve"> |
||||||
|
<value>CivitAI</value> |
||||||
|
</data> |
||||||
|
<data name="Label_HuggingFace" xml:space="preserve"> |
||||||
|
<value>Abraçando o rosto</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Addons" xml:space="preserve"> |
||||||
|
<value>Complementos</value> |
||||||
|
<comment>Inference Sampler Addons</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_SaveIntermediateImage" xml:space="preserve"> |
||||||
|
<value>Salvar imagem intermediária</value> |
||||||
|
<comment>Inference module step to save an intermediate image</comment> |
||||||
|
</data> |
||||||
|
<data name="Label_Settings" xml:space="preserve"> |
||||||
|
<value>Configurações</value> |
||||||
|
</data> |
||||||
|
<data name="Action_SelectFile" xml:space="preserve"> |
||||||
|
<value>Selecione o arquivo</value> |
||||||
|
</data> |
||||||
|
<data name="Action_ReplaceContents" xml:space="preserve"> |
||||||
|
<value>Substituir conteúdo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_WipFeature" xml:space="preserve"> |
||||||
|
<value>Não disponível ainda</value> |
||||||
|
</data> |
||||||
|
<data name="Label_WipFeatureDescription" xml:space="preserve"> |
||||||
|
<value>Este recurso estará disponível em uma atualização futura</value> |
||||||
|
</data> |
||||||
|
<data name="Label_MissingImageFile" xml:space="preserve"> |
||||||
|
<value>Arquivo de imagem não encontrado</value> |
||||||
|
</data> |
||||||
|
<data name="Label_HolidayMode" xml:space="preserve"> |
||||||
|
<value>Modo Férias</value> |
||||||
|
</data> |
||||||
|
<data name="Label_CLIPSkip" xml:space="preserve"> |
||||||
|
<value>Pular CLIPE</value> |
||||||
|
</data> |
||||||
|
<data name="Label_ImageToVideo" xml:space="preserve"> |
||||||
|
<value>Imagem para vídeo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Fps" xml:space="preserve"> |
||||||
|
<value>Quadros por segundo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_MinCfg" xml:space="preserve"> |
||||||
|
<value>CFG mínimo</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Lossless" xml:space="preserve"> |
||||||
|
<value>Sem perdas</value> |
||||||
|
</data> |
||||||
|
<data name="Label_Frames" xml:space="preserve"> |
||||||
|
<value>Frames</value> |
||||||
|
</data> |
||||||
|
<data name="Label_MotionBucketId" xml:space="preserve"> |
||||||
|
<value>ID do Motion Bucket</value> |
||||||
|
</data> |
||||||
|
<data name="Label_AugmentationLevel" xml:space="preserve"> |
||||||
|
<value>Nível de aumento</value> |
||||||
|
</data> |
||||||
|
<data name="Label_VideoOutputMethod" xml:space="preserve"> |
||||||
|
<value>Método</value> |
||||||
|
</data> |
||||||
|
</root> |
@ -0,0 +1,33 @@ |
|||||||
|
using System.Diagnostics.Contracts; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
using System.Text.RegularExpressions; |
||||||
|
using System.Windows.Input; |
||||||
|
using StabilityMatrix.Core.Extensions; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
public partial record CommandItem |
||||||
|
{ |
||||||
|
public ICommand Command { get; init; } |
||||||
|
|
||||||
|
public string DisplayName { get; init; } |
||||||
|
|
||||||
|
public CommandItem(ICommand command, [CallerArgumentExpression("command")] string? commandName = null) |
||||||
|
{ |
||||||
|
Command = command; |
||||||
|
DisplayName = commandName == null ? "" : ProcessName(commandName); |
||||||
|
} |
||||||
|
|
||||||
|
[Pure] |
||||||
|
private static string ProcessName(string name) |
||||||
|
{ |
||||||
|
name = name.StripEnd("Command"); |
||||||
|
|
||||||
|
name = SpaceTitleCaseRegex().Replace(name, "$1 $2"); |
||||||
|
|
||||||
|
return name; |
||||||
|
} |
||||||
|
|
||||||
|
[GeneratedRegex("([a-z])_?([A-Z])")]
|
||||||
|
private static partial Regex SpaceTitleCaseRegex(); |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Implements a template key for <see cref="DataTemplateSelector"/> |
||||||
|
/// </summary> |
||||||
|
public interface ITemplateKey<out T> |
||||||
|
{ |
||||||
|
T TemplateKey { get; } |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
public enum ImageSourceTemplateType |
||||||
|
{ |
||||||
|
Default, |
||||||
|
Image, |
||||||
|
WebpAnimation |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models.Inference; |
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))] |
||||||
|
public enum VideoOutputMethod |
||||||
|
{ |
||||||
|
Fastest, |
||||||
|
Default, |
||||||
|
Slowest, |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
using StabilityMatrix.Core.Models.Packages; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
public record PackageManagerNavigationOptions |
||||||
|
{ |
||||||
|
public bool OpenInstallerDialog { get; init; } |
||||||
|
|
||||||
|
public BasePackage? InstallerSelectedPackage { get; init; } |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Windows.Input; |
||||||
|
using CommunityToolkit.Mvvm.Input; |
||||||
|
using DynamicData.Binding; |
||||||
|
using JetBrains.Annotations; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
[PublicAPI] |
||||||
|
public class SelectableItem<T>(T item) : AbstractNotifyPropertyChanged, IEquatable<SelectableItem<T>> |
||||||
|
{ |
||||||
|
public T Item { get; } = item; |
||||||
|
|
||||||
|
private bool _isSelected; |
||||||
|
|
||||||
|
public bool IsSelected |
||||||
|
{ |
||||||
|
get => _isSelected; |
||||||
|
set => SetAndRaise(ref _isSelected, value); |
||||||
|
} |
||||||
|
|
||||||
|
public ICommand ToggleSelectedCommand => new RelayCommand(() => IsSelected = !IsSelected); |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public bool Equals(SelectableItem<T>? other) |
||||||
|
{ |
||||||
|
if (ReferenceEquals(null, other)) |
||||||
|
return false; |
||||||
|
if (ReferenceEquals(this, other)) |
||||||
|
return true; |
||||||
|
return EqualityComparer<T>.Default.Equals(Item, other.Item); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override bool Equals(object? obj) |
||||||
|
{ |
||||||
|
if (ReferenceEquals(null, obj)) |
||||||
|
return false; |
||||||
|
if (ReferenceEquals(this, obj)) |
||||||
|
return true; |
||||||
|
if (obj.GetType() != GetType()) |
||||||
|
return false; |
||||||
|
return Equals((SelectableItem<T>)obj); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override int GetHashCode() |
||||||
|
{ |
||||||
|
return HashCode.Combine(GetType().GetHashCode(), Item?.GetHashCode()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,254 @@ |
|||||||
|
<ResourceDictionary |
||||||
|
xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||||
|
xmlns:fluentIcons="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent" |
||||||
|
xmlns:mocks="using:StabilityMatrix.Avalonia.DesignData" |
||||||
|
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core" |
||||||
|
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" |
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> |
||||||
|
|
||||||
|
<Design.PreviewWith> |
||||||
|
<Panel Width="450" Height="600"> |
||||||
|
<StackPanel Margin="8" Spacing="4" Width="250"> |
||||||
|
<controls:BetterComboBox |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
ItemsSource="{x:Static mocks:DesignData.SampleHybridModels}" |
||||||
|
SelectedIndex="0" /> |
||||||
|
|
||||||
|
<controls:BetterComboBox |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
ItemsSource="{x:Static mocks:DesignData.SampleHybridModels}" |
||||||
|
SelectedIndex="0" |
||||||
|
Theme="{DynamicResource BetterComboBoxHybridModelTheme}" /> |
||||||
|
</StackPanel> |
||||||
|
</Panel> |
||||||
|
</Design.PreviewWith> |
||||||
|
|
||||||
|
<!-- ReSharper disable once Xaml.StaticResourceNotResolved --> |
||||||
|
<ControlTheme |
||||||
|
x:Key="BetterComboBoxItemHybridModelTheme" |
||||||
|
BasedOn="{StaticResource {x:Type ComboBoxItem}}" |
||||||
|
TargetType="ComboBoxItem"> |
||||||
|
<Setter Property="ToolTip.Placement" Value="RightEdgeAlignedTop" /> |
||||||
|
<Setter Property="ToolTip.Tip"> |
||||||
|
<Template> |
||||||
|
<sg:SpacedGrid |
||||||
|
x:DataType="models:HybridModelFile" |
||||||
|
ColumnDefinitions="Auto,*" |
||||||
|
ColumnSpacing="6" |
||||||
|
RowSpacing="0"> |
||||||
|
<!-- Image --> |
||||||
|
<controls:BetterAdvancedImage |
||||||
|
Width="64" |
||||||
|
Height="96" |
||||||
|
CornerRadius="6" |
||||||
|
IsVisible="{Binding Local.PreviewImageFullPathGlobal, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=''}" |
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality" |
||||||
|
Source="{Binding Local.PreviewImageFullPathGlobal, FallbackValue=''}" |
||||||
|
Stretch="UniformToFill" |
||||||
|
StretchDirection="Both" /> |
||||||
|
<StackPanel |
||||||
|
Grid.Column="1" |
||||||
|
MaxWidth="300" |
||||||
|
VerticalAlignment="Stretch"> |
||||||
|
<!-- Title --> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,4" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
FontSize="14" |
||||||
|
FontWeight="Medium" |
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" |
||||||
|
IsVisible="{Binding Local.ConnectedModelInfo, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}" |
||||||
|
Text="{Binding Local.ConnectedModelInfo.ModelName, FallbackValue=''}" |
||||||
|
TextWrapping="WrapWithOverflow" /> |
||||||
|
<!-- Version --> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
FontSize="13" |
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}" |
||||||
|
IsVisible="{Binding Local.ConnectedModelInfo, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}" |
||||||
|
Text="{Binding Local.ConnectedModelInfo.VersionName, FallbackValue=''}" |
||||||
|
TextWrapping="WrapWithOverflow" /> |
||||||
|
<!-- Path --> |
||||||
|
<TextBlock |
||||||
|
HorizontalAlignment="Left" |
||||||
|
FontSize="13" |
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}" |
||||||
|
Text="{Binding FileName}" |
||||||
|
TextWrapping="Wrap" /> |
||||||
|
</StackPanel> |
||||||
|
</sg:SpacedGrid> |
||||||
|
</Template> |
||||||
|
</Setter> |
||||||
|
</ControlTheme> |
||||||
|
|
||||||
|
<!-- ReSharper disable once Xaml.StaticResourceNotResolved --> |
||||||
|
<ControlTheme |
||||||
|
x:Key="{x:Type controls:BetterComboBox}" |
||||||
|
BasedOn="{StaticResource {x:Type ComboBox}}" |
||||||
|
TargetType="controls:BetterComboBox" /> |
||||||
|
|
||||||
|
<ControlTheme |
||||||
|
x:Key="BetterComboBoxHybridModelTheme" |
||||||
|
BasedOn="{StaticResource {x:Type controls:BetterComboBox}}" |
||||||
|
TargetType="controls:BetterComboBox"> |
||||||
|
|
||||||
|
<ControlTheme.Resources> |
||||||
|
<controls:HybridModelTemplateSelector x:Key="HybridModelTemplateSelector"> |
||||||
|
<DataTemplate x:Key="{x:Static models:HybridModelType.Downloadable}" DataType="models:HybridModelFile"> |
||||||
|
<Grid ColumnDefinitions="*,Auto"> |
||||||
|
<TextBlock Foreground="{DynamicResource ThemeGreyColor}" Text="{Binding ShortDisplayName}" /> |
||||||
|
<Button |
||||||
|
Grid.Column="1" |
||||||
|
Margin="8,0,0,0" |
||||||
|
Padding="0" |
||||||
|
Classes="transparent-full"> |
||||||
|
<fluentIcons:SymbolIcon |
||||||
|
VerticalAlignment="Center" |
||||||
|
FontSize="18" |
||||||
|
Foreground="{DynamicResource ThemeGreyColor}" |
||||||
|
IsFilled="True" |
||||||
|
Symbol="CloudArrowDown" /> |
||||||
|
</Button> |
||||||
|
</Grid> |
||||||
|
</DataTemplate> |
||||||
|
|
||||||
|
<DataTemplate x:Key="{x:Static models:HybridModelType.Local}" DataType="models:HybridModelFile"> |
||||||
|
<sg:SpacedGrid |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
ColumnDefinitions="Auto,*" |
||||||
|
ColumnSpacing="8" |
||||||
|
TextBlock.TextTrimming="CharacterEllipsis" |
||||||
|
TextBlock.TextWrapping="NoWrap"> |
||||||
|
<controls:BetterAdvancedImage |
||||||
|
Grid.RowSpan="2" |
||||||
|
Width="42" |
||||||
|
Height="42" |
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality" |
||||||
|
Source="{Binding Local.PreviewImageFullPathGlobal}" |
||||||
|
Stretch="UniformToFill" |
||||||
|
StretchDirection="Both"> |
||||||
|
<controls:BetterAdvancedImage.Clip> |
||||||
|
<EllipseGeometry Rect="0,0,42,42" /> |
||||||
|
</controls:BetterAdvancedImage.Clip> |
||||||
|
</controls:BetterAdvancedImage> |
||||||
|
|
||||||
|
<!-- Text --> |
||||||
|
<sg:SpacedGrid |
||||||
|
Grid.Row="1" |
||||||
|
Grid.Column="1" |
||||||
|
RowDefinitions="Auto,Auto,Auto" |
||||||
|
RowSpacing="1"> |
||||||
|
<TextBlock Text="{Binding Local.DisplayModelName}" TextTrimming="CharacterEllipsis" /> |
||||||
|
<TextBlock |
||||||
|
Grid.Row="1" |
||||||
|
FontSize="12" |
||||||
|
FontWeight="Regular" |
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" |
||||||
|
Text="{Binding Local.DisplayModelVersion}" |
||||||
|
TextTrimming="CharacterEllipsis" /> |
||||||
|
<TextBlock |
||||||
|
Grid.Row="2" |
||||||
|
FontSize="11" |
||||||
|
FontWeight="Normal" |
||||||
|
Foreground="{DynamicResource TextFillColorTertiaryBrush}" |
||||||
|
Text="{Binding Local.DisplayModelFileName}" /> |
||||||
|
</sg:SpacedGrid> |
||||||
|
</sg:SpacedGrid> |
||||||
|
</DataTemplate> |
||||||
|
|
||||||
|
<DataTemplate x:Key="{x:Static models:HybridModelType.None}" DataType="models:HybridModelFile"> |
||||||
|
<StackPanel> |
||||||
|
<TextBlock Text="{Binding ShortDisplayName}" TextTrimming="CharacterEllipsis" /> |
||||||
|
</StackPanel> |
||||||
|
</DataTemplate> |
||||||
|
</controls:HybridModelTemplateSelector> |
||||||
|
|
||||||
|
<controls:HybridModelTemplateSelector x:Key="HybridModelSelectionBoxTemplateSelector"> |
||||||
|
<DataTemplate x:Key="{x:Static models:HybridModelType.Downloadable}" DataType="models:HybridModelFile"> |
||||||
|
<Grid ColumnDefinitions="*,Auto"> |
||||||
|
<TextBlock Foreground="{DynamicResource ThemeGreyColor}" Text="{Binding ShortDisplayName}" /> |
||||||
|
<Button |
||||||
|
Grid.Column="1" |
||||||
|
Margin="8,0,0,0" |
||||||
|
Padding="0" |
||||||
|
Classes="transparent-full"> |
||||||
|
<fluentIcons:SymbolIcon |
||||||
|
VerticalAlignment="Center" |
||||||
|
FontSize="18" |
||||||
|
Foreground="{DynamicResource ThemeGreyColor}" |
||||||
|
IsFilled="True" |
||||||
|
Symbol="CloudArrowDown" /> |
||||||
|
</Button> |
||||||
|
</Grid> |
||||||
|
</DataTemplate> |
||||||
|
|
||||||
|
<DataTemplate x:Key="{x:Static models:HybridModelType.Local}" DataType="models:HybridModelFile"> |
||||||
|
<sg:SpacedGrid |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
ColumnDefinitions="Auto,*" |
||||||
|
ColumnSpacing="8" |
||||||
|
TextBlock.TextTrimming="CharacterEllipsis" |
||||||
|
TextBlock.TextWrapping="NoWrap"> |
||||||
|
<controls:BetterAdvancedImage |
||||||
|
Grid.RowSpan="2" |
||||||
|
Width="36" |
||||||
|
Height="36" |
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality" |
||||||
|
Source="{Binding Local.PreviewImageFullPathGlobal}" |
||||||
|
Stretch="UniformToFill" |
||||||
|
StretchDirection="Both"> |
||||||
|
<controls:BetterAdvancedImage.Clip> |
||||||
|
<EllipseGeometry Rect="0,0,36,36" /> |
||||||
|
</controls:BetterAdvancedImage.Clip> |
||||||
|
</controls:BetterAdvancedImage> |
||||||
|
|
||||||
|
<!-- Text --> |
||||||
|
<sg:SpacedGrid |
||||||
|
Grid.Row="1" |
||||||
|
Grid.Column="1" |
||||||
|
RowDefinitions="Auto,Auto" |
||||||
|
RowSpacing="1"> |
||||||
|
|
||||||
|
<TextBlock Text="{Binding Local.DisplayModelName}" TextTrimming="CharacterEllipsis" /> |
||||||
|
<TextBlock |
||||||
|
Grid.Row="1" |
||||||
|
FontSize="12" |
||||||
|
FontWeight="Regular" |
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}" |
||||||
|
Text="{Binding Local.DisplayModelVersion}" |
||||||
|
TextTrimming="CharacterEllipsis" /> |
||||||
|
</sg:SpacedGrid> |
||||||
|
</sg:SpacedGrid> |
||||||
|
</DataTemplate> |
||||||
|
|
||||||
|
<DataTemplate x:Key="{x:Static models:HybridModelType.None}" DataType="models:HybridModelFile"> |
||||||
|
<StackPanel> |
||||||
|
<TextBlock Text="{Binding ShortDisplayName}" TextTrimming="CharacterEllipsis" /> |
||||||
|
</StackPanel> |
||||||
|
</DataTemplate> |
||||||
|
</controls:HybridModelTemplateSelector> |
||||||
|
</ControlTheme.Resources> |
||||||
|
|
||||||
|
<Setter Property="TextBlock.TextWrapping" Value="NoWrap" /> |
||||||
|
<Setter Property="SelectionBoxItemTemplate" Value="{StaticResource HybridModelSelectionBoxTemplateSelector}" /> |
||||||
|
<Setter Property="ItemTemplate" Value="{StaticResource HybridModelTemplateSelector}" /> |
||||||
|
<Setter Property="ItemContainerTheme" Value="{StaticResource BetterComboBoxItemHybridModelTheme}" /> |
||||||
|
|
||||||
|
<Style Selector="^ /template/ Popup#PART_Popup"> |
||||||
|
<Setter Property="Width" Value="400" /> |
||||||
|
<Setter Property="Placement" Value="Bottom" /> |
||||||
|
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" /> |
||||||
|
<Setter Property="Effect"> |
||||||
|
<DropShadowEffect |
||||||
|
BlurRadius="32" |
||||||
|
Opacity="0.6" |
||||||
|
Color="#FF000000" /> |
||||||
|
</Setter> |
||||||
|
</Style> |
||||||
|
|
||||||
|
</ControlTheme> |
||||||
|
|
||||||
|
</ResourceDictionary> |
@ -0,0 +1,37 @@ |
|||||||
|
<Styles xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:fluentIcons="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||||
|
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"> |
||||||
|
<Design.PreviewWith> |
||||||
|
<Panel MinWidth="300"> |
||||||
|
<StackPanel Margin="16" Spacing="6"> |
||||||
|
<TextBox Classes="search" /> |
||||||
|
<TextBox Classes="search" Text="Some Text" /> |
||||||
|
</StackPanel> |
||||||
|
</Panel> |
||||||
|
</Design.PreviewWith> |
||||||
|
|
||||||
|
<Styles.Resources> |
||||||
|
<converters:FuncCommandConverter x:Key="FuncCommandConverter"/> |
||||||
|
</Styles.Resources> |
||||||
|
|
||||||
|
<!-- Success --> |
||||||
|
<Style Selector="TextBox.search"> |
||||||
|
<Setter Property="InnerRightContent"> |
||||||
|
<Template> |
||||||
|
<Grid> |
||||||
|
<Button Classes="transparent-full" |
||||||
|
IsVisible="{Binding $parent[TextBox].Text, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" |
||||||
|
Command="{Binding $parent[TextBox], Converter={StaticResource FuncCommandConverter}, ConverterParameter=Clear}"> |
||||||
|
<fluentIcons:SymbolIcon Symbol="Cancel" /> |
||||||
|
</Button> |
||||||
|
<fluentIcons:SymbolIcon |
||||||
|
IsVisible="{Binding $parent[TextBox].Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" |
||||||
|
Margin="0,0,10,0" |
||||||
|
FontSize="16" |
||||||
|
Symbol="Find" /> |
||||||
|
</Grid> |
||||||
|
</Template> |
||||||
|
</Setter> |
||||||
|
</Style> |
||||||
|
</Styles> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue