committed by
GitHub
121 changed files with 1944 additions and 2468 deletions
@ -1,35 +0,0 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|
||||
|
|
||||
<!-- Ensure that code generator is actually built --> |
|
||||
<ItemGroup> |
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\tools\MicroComGenerator\MicroComGenerator.csproj"> |
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly> |
|
||||
<ExcludeAssets>all</ExcludeAssets> |
|
||||
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> |
|
||||
<SetTargetFramework>TargetFramework=net6.0</SetTargetFramework> |
|
||||
</ProjectReference> |
|
||||
</ItemGroup> |
|
||||
|
|
||||
<Target Name="GenerateAvaloniaNativeComInterop" |
|
||||
BeforeTargets="CoreCompile" |
|
||||
DependsOnTargets="ResolveReferences" |
|
||||
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs" |
|
||||
Outputs="%(AvnComIdl.OutputFile)"> |
|
||||
<Message Importance="high" Text="Generating file %(AvnComIdl.OutputFile) from @(AvnComIdl)" /> |
|
||||
<Exec Command="dotnet "$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll" -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)" |
|
||||
LogStandardErrorAsError="true" /> |
|
||||
<ItemGroup> |
|
||||
<!-- Remove and re-add generated file, this is needed for the clean build --> |
|
||||
<Compile Remove="%(AvnComIdl.OutputFile)"/> |
|
||||
<Compile Include="%(AvnComIdl.OutputFile)"/> |
|
||||
</ItemGroup> |
|
||||
</Target> |
|
||||
<ItemGroup> |
|
||||
<UpToDateCheckInput Include="@(AvnComIdl)"/> |
|
||||
<UpToDateCheckInput Include="$(MSBuildThisFileDirectory)/../src/tools/MicroComGenerator/**/*.cs"/> |
|
||||
</ItemGroup> |
|
||||
<PropertyGroup> |
|
||||
<_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop> |
|
||||
</PropertyGroup> |
|
||||
<Import Project="$(MSBuildThisFileDirectory)/BuildTargets.targets" /> |
|
||||
</Project> |
|
||||
@ -1,14 +1,14 @@ |
|||||
using System.IO; |
using System.IO; |
||||
using MicroComGenerator; |
using MicroCom.CodeGenerator; |
||||
using Nuke.Common; |
using Nuke.Common; |
||||
|
|
||||
partial class Build : NukeBuild |
partial class Build : NukeBuild |
||||
{ |
{ |
||||
Target GenerateCppHeaders => _ => _.Executes(() => |
Target GenerateCppHeaders => _ => _.Executes(() => |
||||
{ |
{ |
||||
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"); |
var file = MicroComCodeGenerator.Parse( |
||||
var ast = AstParser.Parse(text); |
File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl")); |
||||
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", |
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", |
||||
CppGen.GenerateCpp(ast)); |
file.GenerateCppHeader()); |
||||
}); |
}); |
||||
} |
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
Compat issues with assembly Avalonia.Base: |
||||
|
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post<T>(System.Action<T>, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract. |
||||
|
Total Issues: 1 |
||||
@ -0,0 +1,47 @@ |
|||||
|
namespace Avalonia.Diagnostics.Behaviors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// See discussion https://github.com/AvaloniaUI/Avalonia/discussions/6773
|
||||
|
/// </summary>
|
||||
|
static class ColumnDefinition |
||||
|
{ |
||||
|
private readonly static Avalonia.Controls.GridLength ZeroWidth = |
||||
|
new Avalonia.Controls.GridLength(0, Avalonia.Controls.GridUnitType.Pixel); |
||||
|
|
||||
|
private readonly static AttachedProperty<Avalonia.Controls.GridLength?> LastWidthProperty = |
||||
|
AvaloniaProperty.RegisterAttached<Avalonia.Controls.ColumnDefinition, Avalonia.Controls.GridLength?>("LastWidth" |
||||
|
, typeof(ColumnDefinition) |
||||
|
, default); |
||||
|
|
||||
|
public readonly static AttachedProperty<bool> IsVisibleProperty = |
||||
|
AvaloniaProperty.RegisterAttached<Avalonia.Controls.ColumnDefinition, bool>("IsVisible" |
||||
|
, typeof(ColumnDefinition) |
||||
|
, true |
||||
|
, coerce: (element, visibility) => |
||||
|
{ |
||||
|
|
||||
|
var lastWidth = element.GetValue(LastWidthProperty); |
||||
|
if (visibility == true && lastWidth is { }) |
||||
|
{ |
||||
|
element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, lastWidth); |
||||
|
} |
||||
|
else if (visibility == false) |
||||
|
{ |
||||
|
element.SetValue(LastWidthProperty, element.GetValue(Avalonia.Controls.ColumnDefinition.WidthProperty)); |
||||
|
element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, ZeroWidth); |
||||
|
} |
||||
|
return visibility; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
public static bool GetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition) |
||||
|
{ |
||||
|
return columnDefinition.GetValue(IsVisibleProperty); |
||||
|
} |
||||
|
|
||||
|
public static void SetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition, bool visibility) |
||||
|
{ |
||||
|
columnDefinition.SetValue(IsVisibleProperty, visibility); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,119 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
using Lifetimes = Avalonia.Controls.ApplicationLifetimes; |
||||
|
using App = Avalonia.Application; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Controls |
||||
|
{ |
||||
|
class Application : AvaloniaObject |
||||
|
, Input.ICloseable |
||||
|
|
||||
|
{ |
||||
|
private readonly App _application; |
||||
|
private static readonly Version s_version = typeof(IAvaloniaObject).Assembly?.GetName()?.Version |
||||
|
?? Version.Parse("0.0.00"); |
||||
|
public event EventHandler? Closed; |
||||
|
|
||||
|
public Application(App application) |
||||
|
{ |
||||
|
_application = application; |
||||
|
|
||||
|
if (_application.ApplicationLifetime is Lifetimes.IControlledApplicationLifetime controller) |
||||
|
{ |
||||
|
EventHandler<Lifetimes.ControlledApplicationLifetimeExitEventArgs> eh = default!; |
||||
|
eh = (s, e) => |
||||
|
{ |
||||
|
controller.Exit -= eh; |
||||
|
Closed?.Invoke(s, e); |
||||
|
}; |
||||
|
controller.Exit += eh; |
||||
|
} |
||||
|
RendererRoot = application.ApplicationLifetime switch |
||||
|
{ |
||||
|
Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow.Renderer, |
||||
|
Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as VisualTree.IVisual)?.VisualRoot?.Renderer, |
||||
|
_ => null |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
internal App Instance => _application; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="DataContext"/> property.
|
||||
|
/// </summary>
|
||||
|
public object? DataContext => |
||||
|
_application.DataContext; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the application's global data templates.
|
||||
|
/// </summary>
|
||||
|
/// <value>
|
||||
|
/// The application's global data templates.
|
||||
|
/// </value>
|
||||
|
public Avalonia.Controls.Templates.DataTemplates DataTemplates => |
||||
|
_application.DataTemplates; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the application's focus manager.
|
||||
|
/// </summary>
|
||||
|
/// <value>
|
||||
|
/// The application's focus manager.
|
||||
|
/// </value>
|
||||
|
public Input.IFocusManager? FocusManager => |
||||
|
_application.FocusManager; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the application's input manager.
|
||||
|
/// </summary>
|
||||
|
/// <value>
|
||||
|
/// The application's input manager.
|
||||
|
/// </value>
|
||||
|
public Input.InputManager? InputManager => |
||||
|
_application.InputManager; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the application clipboard.
|
||||
|
/// </summary>
|
||||
|
public Input.Platform.IClipboard? Clipboard => |
||||
|
_application.Clipboard; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the application's global resource dictionary.
|
||||
|
/// </summary>
|
||||
|
public IResourceDictionary Resources => |
||||
|
_application.Resources; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the application's global styles.
|
||||
|
/// </summary>
|
||||
|
/// <value>
|
||||
|
/// The application's global styles.
|
||||
|
/// </value>
|
||||
|
/// <remarks>
|
||||
|
/// Global styles apply to all windows in the application.
|
||||
|
/// </remarks>
|
||||
|
public Styling.Styles Styles => |
||||
|
_application.Styles; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Application lifetime, use it for things like setting the main window and exiting the app from code
|
||||
|
/// Currently supported lifetimes are:
|
||||
|
/// - <see cref="Lifetimes.IClassicDesktopStyleApplicationLifetime"/>
|
||||
|
/// - <see cref="Lifetimes.ISingleViewApplicationLifetime"/>
|
||||
|
/// - <see cref="Lifetimes.IControlledApplicationLifetime"/>
|
||||
|
/// </summary>
|
||||
|
public Lifetimes.IApplicationLifetime? ApplicationLifetime => |
||||
|
_application.ApplicationLifetime; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Application name to be used for various platform-specific purposes
|
||||
|
/// </summary>
|
||||
|
public string? Name => |
||||
|
_application.Name; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the root of the visual tree, if the control is attached to a visual tree.
|
||||
|
/// </summary>
|
||||
|
internal Rendering.IRenderer? RendererRoot { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Data.Converters; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Converters |
||||
|
{ |
||||
|
internal class GetTypeNameConverter : IValueConverter |
||||
|
{ |
||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
|
{ |
||||
|
if (value is Type type) |
||||
|
{ |
||||
|
return type.GetTypeName(); |
||||
|
} |
||||
|
return BindingOperations.DoNothing; |
||||
|
} |
||||
|
|
||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) |
||||
|
{ |
||||
|
return BindingOperations.DoNothing; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics |
||||
|
{ |
||||
|
static class Convetions |
||||
|
{ |
||||
|
public static string DefaultScreenshotsRoot => |
||||
|
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), |
||||
|
"Screenshots"); |
||||
|
|
||||
|
public static IScreenshotHandler DefaultScreenshotHandler { get; } = |
||||
|
new Screenshots.FilePickerHandler(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Allowed to define custom handler for Shreeshot
|
||||
|
/// </summary>
|
||||
|
public interface IScreenshotHandler |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Handle the Screenshot
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task Take(IControl control); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics |
||||
|
{ |
||||
|
static class KeyGestureExtesions |
||||
|
{ |
||||
|
public static bool Matches(this KeyGesture gesture, RawKeyEventArgs keyEvent) => |
||||
|
keyEvent != null && |
||||
|
(KeyModifiers)(keyEvent.Modifiers & RawInputModifiers.KeyboardMask) == gesture.KeyModifiers && |
||||
|
ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(gesture.Key); |
||||
|
|
||||
|
private static Key ResolveNumPadOperationKey(Key key) |
||||
|
{ |
||||
|
switch (key) |
||||
|
{ |
||||
|
case Key.Add: |
||||
|
return Key.OemPlus; |
||||
|
case Key.Subtract: |
||||
|
return Key.OemMinus; |
||||
|
case Key.Decimal: |
||||
|
return Key.OemPeriod; |
||||
|
default: |
||||
|
return key; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Screenshots |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Base class for render Screenshto to stream
|
||||
|
/// </summary>
|
||||
|
public abstract class BaseRenderToStreamHandler : IScreenshotHandler |
||||
|
{ |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get stream
|
||||
|
/// </summary>
|
||||
|
/// <param name="control"></param>
|
||||
|
/// <returns>stream to render the control</returns>
|
||||
|
protected abstract Task<System.IO.Stream?> GetStream(IControl control); |
||||
|
|
||||
|
public async Task Take(IControl control) |
||||
|
{ |
||||
|
using var output = await GetStream(control); |
||||
|
if (output is { }) |
||||
|
{ |
||||
|
control.RenderTo(output); |
||||
|
await output.FlushAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
using Lifetimes = Avalonia.Controls.ApplicationLifetimes; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.Screenshots |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Show a FileSavePicker to select where save screenshot
|
||||
|
/// </summary>
|
||||
|
public sealed class FilePickerHandler : BaseRenderToStreamHandler |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Instance FilePickerHandler
|
||||
|
/// </summary>
|
||||
|
public FilePickerHandler() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Instance FilePickerHandler with specificated parameter
|
||||
|
/// </summary>
|
||||
|
/// <param name="title">SaveFilePicker Title</param>
|
||||
|
/// <param name="screenshotRoot"></param>
|
||||
|
public FilePickerHandler(string? title |
||||
|
, string? screenshotRoot = default |
||||
|
) |
||||
|
{ |
||||
|
if (title is { }) |
||||
|
Title = title; |
||||
|
if (screenshotRoot is { }) |
||||
|
ScreenshotsRoot = screenshotRoot; |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Get the root folder where screeshots well be stored.
|
||||
|
/// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
|
||||
|
/// </summary>
|
||||
|
public string ScreenshotsRoot { get; } |
||||
|
= Convetions.DefaultScreenshotsRoot; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SaveFilePicker Title
|
||||
|
/// </summary>
|
||||
|
public string Title { get; } = "Save Screenshot to ..."; |
||||
|
|
||||
|
Window GetWindow(IControl control) |
||||
|
{ |
||||
|
var window = control.VisualRoot as Window; |
||||
|
var app = Application.Current; |
||||
|
if (app?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop) |
||||
|
{ |
||||
|
window = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow); |
||||
|
} |
||||
|
return window!; |
||||
|
} |
||||
|
|
||||
|
protected async override Task<Stream?> GetStream(IControl control) |
||||
|
{ |
||||
|
Stream? output = default; |
||||
|
var result = await new SaveFileDialog() |
||||
|
{ |
||||
|
Title = Title, |
||||
|
Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } }, |
||||
|
Directory = ScreenshotsRoot, |
||||
|
}.ShowAsync(GetWindow(control)); |
||||
|
if (!string.IsNullOrWhiteSpace(result)) |
||||
|
{ |
||||
|
var foldler = Path.GetDirectoryName(result); |
||||
|
// Directory information for path, or null if path denotes a root directory or is
|
||||
|
// null. Returns System.String.Empty if path does not contain directory information.
|
||||
|
if (!string.IsNullOrWhiteSpace(foldler)) |
||||
|
{ |
||||
|
if (!Directory.Exists(foldler)) |
||||
|
{ |
||||
|
Directory.CreateDirectory(foldler); |
||||
|
} |
||||
|
output = new FileStream(result, FileMode.Create); |
||||
|
} |
||||
|
} |
||||
|
return output; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics |
||||
|
{ |
||||
|
internal static class TypeExtesnions |
||||
|
{ |
||||
|
private static readonly ConditionalWeakTable<Type, string> s_getTypeNameCache = |
||||
|
new ConditionalWeakTable<Type, string>(); |
||||
|
|
||||
|
public static string GetTypeName(this Type type) |
||||
|
{ |
||||
|
if (!s_getTypeNameCache.TryGetValue(type, out var name)) |
||||
|
{ |
||||
|
name = type.Name; |
||||
|
if (Nullable.GetUnderlyingType(type) is Type nullable) |
||||
|
{ |
||||
|
name = nullable.Name + "?"; |
||||
|
} |
||||
|
else if (type.IsGenericType) |
||||
|
{ |
||||
|
var definition = type.GetGenericTypeDefinition(); |
||||
|
var arguments = type.GetGenericArguments(); |
||||
|
name = definition.Name.Substring(0, definition.Name.IndexOf('`')); |
||||
|
name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>"; |
||||
|
} |
||||
|
s_getTypeNameCache.Add(type, name); |
||||
|
} |
||||
|
return name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics |
||||
|
{ |
||||
|
internal static class VisualExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Render control to the destination stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">Control to be rendered.</param>
|
||||
|
/// <param name="destination">Destination stream.</param>
|
||||
|
/// <param name="dpi">Dpi quality.</param>
|
||||
|
public static void RenderTo(this IControl source, Stream destination, double dpi = 96) |
||||
|
{ |
||||
|
if (source.TransformedBounds == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
var rect = source.TransformedBounds.Value.Clip; |
||||
|
var top = rect.TopLeft; |
||||
|
var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height); |
||||
|
var dpiVector = new Vector(dpi, dpi); |
||||
|
|
||||
|
// get Visual root
|
||||
|
var root = (source.VisualRoot |
||||
|
?? source.GetVisualRoot()) |
||||
|
as IControl ?? source; |
||||
|
|
||||
|
IDisposable? clipSetter = default; |
||||
|
IDisposable? clipToBoundsSetter = default; |
||||
|
IDisposable? renderTransformOriginSetter = default; |
||||
|
IDisposable? renderTransformSetter = default; |
||||
|
try |
||||
|
{ |
||||
|
// Set clip region
|
||||
|
var clipRegion = new Media.RectangleGeometry(rect); |
||||
|
clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation); |
||||
|
clipSetter = root.SetValue(Visual.ClipProperty, clipRegion, BindingPriority.Animation); |
||||
|
|
||||
|
// Translate origin
|
||||
|
renderTransformOriginSetter = root.SetValue(Visual.RenderTransformOriginProperty, |
||||
|
new RelativePoint(top, RelativeUnit.Absolute), |
||||
|
BindingPriority.Animation); |
||||
|
|
||||
|
renderTransformSetter = root.SetValue(Visual.RenderTransformProperty, |
||||
|
new Media.TranslateTransform(-top.X, -top.Y), |
||||
|
BindingPriority.Animation); |
||||
|
|
||||
|
using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) |
||||
|
{ |
||||
|
bitmap.Render(root); |
||||
|
bitmap.Save(destination); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
// Restore values before trasformation
|
||||
|
renderTransformSetter?.Dispose(); |
||||
|
renderTransformOriginSetter?.Dispose(); |
||||
|
clipSetter?.Dispose(); |
||||
|
clipToBoundsSetter?.Dispose(); |
||||
|
source?.InvalidateVisual(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace System.Runtime.CompilerServices |
||||
|
{ |
||||
|
#if !NET5_0_OR_GREATER
|
||||
|
internal class ModuleInitializerAttribute : Attribute |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue