mirror of
https://github.com/AlexMacocian/WpfExtended.git
synced 2026-07-15 14:59:30 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8c783aaed | |||
| fc6cb11880 | |||
| 4b3002b164 | |||
| 8dd9d93e2b | |||
| ca9e682d04 | |||
| 103ee09b0e | |||
| ce5e5e780a | |||
| 0c85e6a8d9 | |||
| 8121d3191e | |||
| 786728d632 | |||
| a2afb3b1ee | |||
| f02af67c7f | |||
| ffe46f1e37 | |||
| 187cd6609d | |||
| c22ec7d874 | |||
| 32560f7326 | |||
| dc6907bc79 | |||
| c0f4b69d80 | |||
| 8ac8b78758 | |||
| 9d7ce59e14 | |||
| 5d63d73f93 | |||
| e6df17358b | |||
| a4dbcd9d53 | |||
| 16f60dbc57 | |||
| 3206512cfb | |||
| b666bd7968 | |||
| 02f83fc78e | |||
| da2263c30f | |||
| faf7e4248b | |||
| daca000745 | |||
| 409b6cdf16 | |||
| 13d5a0f7f6 | |||
| 64da5aa909 | |||
| 544ec77667 | |||
| d349041cbe | |||
| 40e85ffe96 | |||
| dd99b05ee8 | |||
| 1325a6821e | |||
| cd68132463 | |||
| 7e9c3c5f64 | |||
| f818f4152d |
@@ -0,0 +1 @@
|
||||
* amacocian@yahoo.com
|
||||
+15
-14
@@ -17,9 +17,7 @@ jobs:
|
||||
|
||||
env:
|
||||
Configuration: Release
|
||||
Solution_Path: WpfExtended.sln
|
||||
Test_Project_Path: Slim.Tests\Slim.Tests.csproj
|
||||
Source_Project_Path: Slim\Slim.csproj
|
||||
Solution_Path: WpfExtended.slnx
|
||||
Actions_Allow_Unsecure_Commands: true
|
||||
|
||||
steps:
|
||||
@@ -31,7 +29,7 @@ jobs:
|
||||
- name: Install .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.202'
|
||||
dotnet-version: '10.x'
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.0.1
|
||||
@@ -44,17 +42,20 @@ jobs:
|
||||
- name: Build WpfExtended project
|
||||
run: dotnet build WpfExtended -c $env:Configuration -p:SolutionDir=$env:GITHUB_WORKSPACE
|
||||
|
||||
- name: Push WpfExtended Nuget package
|
||||
uses: brandedoutcast/publish-nuget@v2.5.5
|
||||
with:
|
||||
PROJECT_FILE_PATH: WpfExtended\WpfExtended.csproj
|
||||
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
||||
- name: Package WpfExtended
|
||||
run: dotnet pack -c Release -o . WpfExtended\WpfExtended.csproj
|
||||
|
||||
- name: Build WpfExtended.SourceGeneration project
|
||||
run: dotnet build WpfExtended.SourceGeneration -c $env:Configuration
|
||||
|
||||
- name: Push WpfExtended.SourceGeneration Nuget package
|
||||
uses: brandedoutcast/publish-nuget@v2.5.5
|
||||
with:
|
||||
PROJECT_FILE_PATH: WpfExtended.SourceGeneration\WpfExtended.SourceGeneration.csproj
|
||||
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
|
||||
- name: Package WpfExtended.SourceGeneration
|
||||
run: dotnet pack -c Release -o . WpfExtended.SourceGeneration\WpfExtended.SourceGeneration.csproj
|
||||
|
||||
- name: Build WpfExtended.Blazor project
|
||||
run: dotnet build WpfExtended.Blazor -c $env:Configuration
|
||||
|
||||
- name: Package WpfExtended.Blazor
|
||||
run: dotnet pack -c Release -o . WpfExtended.Blazor\WpfExtended.Blazor.csproj
|
||||
|
||||
- name: Publish
|
||||
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
|
||||
@@ -19,8 +19,9 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
Solution_Path: WpfExtended.sln
|
||||
Solution_Path: WpfExtended.slnx
|
||||
Actions_Allow_Unsecure_Commands: true
|
||||
Test_Project_Path: WpfExtended.Test\WpfExtended.Tests.csproj
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -31,7 +32,7 @@ jobs:
|
||||
- name: Install .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.202'
|
||||
dotnet-version: '10.x'
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.0.1
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
|
||||
namespace WpfExtended.Blazor.Exceptions;
|
||||
|
||||
public sealed class CoreWebView2Exception(CoreWebView2ProcessFailedEventArgs args, string message, Exception? innerException = default) : Exception(message, innerException)
|
||||
{
|
||||
public CoreWebView2ProcessFailedEventArgs Args { get; } = args ?? throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Window
|
||||
x:Class="WpfExtended.Blazor.Launch.BlazorHostWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WpfExtended.Blazor.Launch"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
|
||||
x:Name="_this"
|
||||
Title="BlazorHostWindow"
|
||||
Width="800"
|
||||
Height="450"
|
||||
mc:Ignorable="d">
|
||||
<shell:WindowChrome.WindowChrome>
|
||||
<WindowChrome x:Name="WindowChrome" />
|
||||
</shell:WindowChrome.WindowChrome>
|
||||
<blazor:BlazorWebView
|
||||
x:Name="BlazorWebView"
|
||||
HostPage="{Binding ElementName=_this, Path=HostPage, Mode=OneWay}"
|
||||
Services="{Binding ElementName=_this, Path=Services, Mode=OneWay}">
|
||||
<blazor:BlazorWebView.RootComponents>
|
||||
<blazor:RootComponent x:Name="RootComponent" Selector="#app" />
|
||||
</blazor:BlazorWebView.RootComponents>
|
||||
</blazor:BlazorWebView>
|
||||
</Window>
|
||||
@@ -0,0 +1,121 @@
|
||||
using Microsoft.AspNetCore.Components.WebView;
|
||||
using Microsoft.AspNetCore.Components.WebView.Wpf;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace WpfExtended.Blazor.Launch;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for BlazorHostWindow.xaml
|
||||
/// </summary>
|
||||
public partial class BlazorHostWindow : Window
|
||||
{
|
||||
public static readonly DependencyProperty ServicesProperty = DependencyProperty.Register(
|
||||
nameof(Services), typeof(IServiceProvider), typeof(BlazorHostWindow));
|
||||
|
||||
public static readonly DependencyProperty ShowTitleBarProperty = DependencyProperty.Register(
|
||||
nameof(ShowTitleBar), typeof(bool), typeof(BlazorHostWindow));
|
||||
|
||||
public static readonly DependencyProperty HostPageProperty = DependencyProperty.Register(
|
||||
nameof(HostPage), typeof(string), typeof(BlazorHostWindow));
|
||||
|
||||
public static readonly DependencyProperty AppTypeProperty = DependencyProperty.Register(
|
||||
nameof(AppType), typeof(Type), typeof(BlazorHostWindow));
|
||||
|
||||
public event EventHandler<BlazorWebViewInitializedEventArgs>? BlazorWebViewInitialized;
|
||||
|
||||
public event EventHandler<BlazorWebViewInitializingEventArgs>? BlazorWebViewInitializing;
|
||||
|
||||
public event EventHandler<CoreWebView2InitializationCompletedEventArgs>? CoreWebView2InitializationCompleted;
|
||||
|
||||
public event EventHandler<CoreWebView2>? CoreWebView2Initialized;
|
||||
|
||||
public IServiceProvider Services
|
||||
{
|
||||
get => (IServiceProvider)this.GetValue(ServicesProperty);
|
||||
set => this.SetValue(ServicesProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowTitleBar
|
||||
{
|
||||
get => (bool)this.GetValue(ShowTitleBarProperty);
|
||||
set
|
||||
{
|
||||
this.SetValue(ShowTitleBarProperty, value);
|
||||
this.WindowChrome.CaptionHeight = value ? SystemParameters.CaptionHeight : 0;
|
||||
this.BlazorWebView.Margin = value ? new Thickness(0, SystemParameters.CaptionHeight, 0, 0) : new Thickness(0);
|
||||
}
|
||||
}
|
||||
|
||||
public string HostPage
|
||||
{
|
||||
get => (string)this.GetValue(HostPageProperty);
|
||||
set => this.SetValue(HostPageProperty, value);
|
||||
}
|
||||
|
||||
public Type AppType
|
||||
{
|
||||
get => (Type)this.GetValue(AppTypeProperty);
|
||||
set
|
||||
{
|
||||
this.SetValue(AppTypeProperty, value);
|
||||
this.RootComponent.ComponentType = value;
|
||||
}
|
||||
}
|
||||
|
||||
public BlazorHostWindow(
|
||||
BlazorLaunchProperties blazorLaunchProperties,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Services = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
this.ShowTitleBar = blazorLaunchProperties.ShowTitleBar;
|
||||
this.HostPage = blazorLaunchProperties.HostPage;
|
||||
this.AppType = blazorLaunchProperties.AppType;
|
||||
this.BlazorWebView.BlazorWebViewInitialized += this.BlazorWebView_BlazorWebViewInitialized;
|
||||
this.BlazorWebView.BlazorWebViewInitializing += this.BlazorWebView_BlazorWebViewInitializing;
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(EventArgs e)
|
||||
{
|
||||
base.OnStateChanged(e);
|
||||
var cornerRadius = Math.Max(
|
||||
Math.Max(SystemParameters.WindowCornerRadius.TopLeft, SystemParameters.WindowCornerRadius.TopRight),
|
||||
Math.Max(SystemParameters.WindowCornerRadius.BottomLeft, SystemParameters.WindowCornerRadius.BottomRight));
|
||||
this.WindowChrome.ResizeBorderThickness = this.WindowState is WindowState.Maximized ? new Thickness(0) : new Thickness(cornerRadius);
|
||||
this.WindowChrome.CornerRadius = this.WindowState is WindowState.Maximized ? new CornerRadius(0) : SystemParameters.WindowCornerRadius;
|
||||
this.BlazorWebView.Margin = this.WindowState is WindowState.Maximized
|
||||
? this.ShowTitleBar
|
||||
? new Thickness(cornerRadius, SystemParameters.CaptionHeight + cornerRadius, cornerRadius, cornerRadius)
|
||||
: new Thickness(cornerRadius)
|
||||
: new Thickness(0, this.ShowTitleBar ? SystemParameters.CaptionHeight : 0, 0, 0);
|
||||
}
|
||||
|
||||
private void BlazorWebView_BlazorWebViewInitialized(object? sender, BlazorWebViewInitializedEventArgs args)
|
||||
{
|
||||
args.WebView.CoreWebView2InitializationCompleted += this.CoreWebView2_InitializationCompleted;
|
||||
this.BlazorWebViewInitialized?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void BlazorWebView_BlazorWebViewInitializing(object? sender, BlazorWebViewInitializingEventArgs args)
|
||||
{
|
||||
this.BlazorWebViewInitializing?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void CoreWebView2_InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e)
|
||||
{
|
||||
this.CoreWebView2InitializationCompleted?.Invoke(this, e);
|
||||
|
||||
if (!e.IsSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender is Microsoft.Web.WebView2.Wpf.WebView2 webView2)
|
||||
{
|
||||
this.CoreWebView2Initialized?.Invoke(this, webView2.CoreWebView2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.WebView;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Extensions;
|
||||
using WpfExtended.Blazor.Exceptions;
|
||||
|
||||
namespace WpfExtended.Blazor.Launch;
|
||||
|
||||
public abstract class BlazorHybridApplication<TApp> : ExtendedApplication<BlazorHostWindow>
|
||||
where TApp : ComponentBase
|
||||
{
|
||||
public abstract bool DevToolsEnabled { get; }
|
||||
public abstract string HostPage { get; }
|
||||
public abstract bool ShowTitleBar { get; }
|
||||
|
||||
protected override void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(new BlazorLaunchProperties(typeof(TApp), this.HostPage, this.ShowTitleBar));
|
||||
services.AddWpfBlazorWebView();
|
||||
if (this.DevToolsEnabled)
|
||||
{
|
||||
services.AddBlazorWebViewDeveloperTools();
|
||||
}
|
||||
}
|
||||
|
||||
protected override ValueTask ApplicationStarting()
|
||||
{
|
||||
var host = this.ServiceProvider.GetRequiredService<BlazorHostWindow>();
|
||||
host.BlazorWebViewInitializing += (_, e) => this.Host_BlazorWebViewInitializing(e);
|
||||
host.BlazorWebViewInitialized += (_, e) => this.Host_BlazorWebViewInitialized(e);
|
||||
host.CoreWebView2InitializationCompleted += (_, e) => this.Host_CoreWebView2InitializationCompleted(e);
|
||||
host.CoreWebView2Initialized += (_, e) => this.Host_CoreWebView2Initialized(e);
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual void Host_CoreWebView2Initialized(CoreWebView2 e)
|
||||
{
|
||||
e.ProcessFailed += (_, ex) => this.CoreWebView2_ProcessFailed(ex);
|
||||
}
|
||||
|
||||
protected virtual void Host_CoreWebView2InitializationCompleted(CoreWebView2InitializationCompletedEventArgs e)
|
||||
{
|
||||
if (e.InitializationException is not null)
|
||||
{
|
||||
this.HandleException(e.InitializationException);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.IsSuccess is false)
|
||||
{
|
||||
this.HandleException(new InvalidOperationException("CoreWebView2 initialization failed."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Host_BlazorWebViewInitialized(BlazorWebViewInitializedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void Host_BlazorWebViewInitializing(BlazorWebViewInitializingEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void CoreWebView2_ProcessFailed(CoreWebView2ProcessFailedEventArgs e)
|
||||
{
|
||||
var exception = new CoreWebView2Exception(e, "Encountered exception in CoreWebView2");
|
||||
if (!this.HandleException(exception))
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace WpfExtended.Blazor.Launch;
|
||||
|
||||
public sealed record BlazorLaunchProperties(Type AppType, string HostPage, bool ShowTitleBar)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0-windows10.0.17763.0</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Version>0.9.0</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Description>
|
||||
Extension library for Blazor Hybrid running on Windows Presentation Platform.
|
||||
</Description>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<RepositoryUrl>https://github.com/AlexMacocian/WpfExtended</RepositoryUrl>
|
||||
|
||||
<!-- Disable static web asset props file generation for consumers -->
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
<GenerateStaticWebAssetsManifest>false</GenerateStaticWebAssetsManifest>
|
||||
<GenerateStaticWebAssetEndpointsManifest>false</GenerateStaticWebAssetEndpointsManifest>
|
||||
<GenerateStaticWebAssetEndpointsPropsFile>false</GenerateStaticWebAssetEndpointsPropsFile>
|
||||
<DisableStaticWebAssetsBuildPropsFileGeneration>true</DisableStaticWebAssetsBuildPropsFileGeneration>
|
||||
<IncludeStaticWebAssetsPackItems>false</IncludeStaticWebAssetsPackItems>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Remove auto-generated build props/targets from the NuGet package -->
|
||||
<Target Name="RemoveStaticWebAssetsPropsFromPackage" BeforeTargets="GenerateNuspec">
|
||||
<ItemGroup>
|
||||
<_PackageFiles Remove="@(_PackageFiles)"
|
||||
Condition="'%(Identity)' != '' And $([System.String]::new('%(Identity)').Contains('StaticWebAssets'))" />
|
||||
<_PackageFiles Remove="@(_PackageFiles)"
|
||||
Condition="'%(Identity)' != '' And $([System.String]::new('%(Identity)').EndsWith('.props'))" />
|
||||
<_PackageFiles Remove="@(_PackageFiles)"
|
||||
Condition="'%(Identity)' != '' And $([System.String]::new('%(Identity)').EndsWith('.targets'))" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Wpf" Version="10.0.20" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WpfExtended\WpfExtended.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,24 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Media.Effects;
|
||||
|
||||
namespace WpfExtended.SourceGeneration.Tests
|
||||
{
|
||||
internal partial class Class1 : UserControl
|
||||
{
|
||||
[GenerateDependencyProperty]
|
||||
public int someF;
|
||||
[GenerateDependencyProperty(InitialValue = "This has a value")]
|
||||
public string someValue;
|
||||
[GenerateDependencyProperty]
|
||||
public Effect effect;
|
||||
|
||||
public void TestValues()
|
||||
{
|
||||
this.SomeF.Should().Be(0);
|
||||
this.SomeValue.Should().Be("This has a value");
|
||||
this.Effect.Should().BeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace WpfExtended.SourceGeneration.Tests
|
||||
{
|
||||
public class Launcher
|
||||
{
|
||||
[STAThread]
|
||||
public static int Main()
|
||||
{
|
||||
var cls = new Class1();
|
||||
cls.TestValues();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>obj</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.0.0-alpha0002" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WpfExtended.SourceGeneration\WpfExtended.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,143 +0,0 @@
|
||||
using System.Extensions.Templates;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Extensions
|
||||
{
|
||||
internal class CodeWriter
|
||||
{
|
||||
private StringBuilder StringBuilder { get; } = new StringBuilder();
|
||||
private CodeBlock CurrentBlock { get; set; } = new CodeBlock();
|
||||
|
||||
public CodeWriter Append(string value)
|
||||
{
|
||||
this.CurrentBlock.Append(this.StringBuilder, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter AppendLine(string value)
|
||||
{
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter AppendLine()
|
||||
{
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter Append(char c)
|
||||
{
|
||||
this.CurrentBlock.Append(this.StringBuilder, c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter AppendLine(char c)
|
||||
{
|
||||
this.AppendLine(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter Append(AbstractTemplate abstractTemplate)
|
||||
{
|
||||
abstractTemplate.Generate(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter BeginCodeBlock()
|
||||
{
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder).AppendLine(this.StringBuilder, '{');
|
||||
this.CurrentBlock = CodeBlock.GenerateCodeBlock(this.CurrentBlock);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter EndCodeBlock()
|
||||
{
|
||||
this.CurrentBlock = this.CurrentBlock.End(this.StringBuilder).Parent;
|
||||
this.CurrentBlock.AppendLine(this.StringBuilder, '}');
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.StringBuilder.ToString();
|
||||
}
|
||||
|
||||
private class CodeBlock
|
||||
{
|
||||
private const char PrefixSymbol = '\t';
|
||||
private bool NewLine { get; set; } = true;
|
||||
private string Prefix { get; }
|
||||
public CodeBlock Parent { get; }
|
||||
public int Level { get; }
|
||||
public CodeBlock()
|
||||
{
|
||||
this.Level = 0;
|
||||
this.Prefix = string.Empty;
|
||||
}
|
||||
private CodeBlock(int level, CodeBlock parent)
|
||||
{
|
||||
this.Level = level;
|
||||
this.Prefix = new string(Enumerable.Repeat(PrefixSymbol, this.Level).ToArray());
|
||||
this.Parent = parent;
|
||||
}
|
||||
|
||||
public CodeBlock AppendLine(StringBuilder stringBuilder)
|
||||
{
|
||||
stringBuilder.AppendLine();
|
||||
this.NewLine = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock AppendLine(StringBuilder stringBuilder, string value)
|
||||
{
|
||||
value = this.NewLine is true ? Prefix + value : value;
|
||||
stringBuilder.AppendLine(value);
|
||||
this.NewLine = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock Append(StringBuilder stringBuilder, string value)
|
||||
{
|
||||
value = this.NewLine is true ? Prefix + value : value;
|
||||
stringBuilder.Append(value);
|
||||
this.NewLine = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock Append(StringBuilder stringBuilder, char c)
|
||||
{
|
||||
var value = this.NewLine is true ? Prefix + c : c.ToString();
|
||||
stringBuilder.Append(value);
|
||||
this.NewLine = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock AppendLine(StringBuilder stringBuilder, char c)
|
||||
{
|
||||
var value = this.NewLine is true ? Prefix + c : c.ToString();
|
||||
stringBuilder.AppendLine(value);
|
||||
this.NewLine = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlock End(StringBuilder stringBuilder)
|
||||
{
|
||||
if (this.NewLine is false)
|
||||
{
|
||||
stringBuilder.AppendLine();
|
||||
this.NewLine = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static CodeBlock GenerateCodeBlock(CodeBlock codeBlock)
|
||||
{
|
||||
var child = new CodeBlock(codeBlock.Level + 1, codeBlock);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,196 +1,189 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Sybil;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Extensions.Templates;
|
||||
|
||||
namespace System.Extensions
|
||||
namespace System.Extensions;
|
||||
#nullable enable
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public class DependencyPropertyGenerator : IIncrementalGenerator
|
||||
{
|
||||
[Generator]
|
||||
public class DependencyPropertyGenerator : ISourceGenerator
|
||||
private const string DependencyPropertyNamespace = "System.Windows";
|
||||
private const string AttributeNamespace = "System.Windows.Extensions";
|
||||
private const string AttributeName = "GenerateDependencyPropertyAttribute";
|
||||
private const string AttributeShortName = "GenerateDependencyProperty";
|
||||
private const string DependencyPropertySuffix = "Property";
|
||||
private const string DependencyProperty = "DependencyProperty";
|
||||
private const string Register = "Register";
|
||||
private const string Partial = "partial";
|
||||
private const string Public = "public";
|
||||
private const string Static = "static";
|
||||
private const string Readonly = "readonly";
|
||||
private const string InitialValue = "InitialValue";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
private const string AttributeNamespace = "System.Windows.Extensions";
|
||||
private const string AttributeName = "GenerateDependencyPropertyAttribute";
|
||||
private const string DependencyPropertySuffix = "Property";
|
||||
private static readonly ClassTemplate GenerateDependencyPropertyAttribute =
|
||||
new ClassTemplate()
|
||||
.WithUsings(
|
||||
new UsingsTemplate()
|
||||
.WithNamespace("System"))
|
||||
.WithNamespace(
|
||||
new NamespaceTemplate()
|
||||
.WithNamespace(AttributeNamespace))
|
||||
.WithConstructor(
|
||||
new ConstructorTemplate()
|
||||
.WithType(AttributeName)
|
||||
.WithModifiers(Modifier.Public))
|
||||
.WithAttributes(
|
||||
new AttributeTemplate()
|
||||
.WithAttribute("AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)"))
|
||||
.WithModifiers(Modifier.Public, Modifier.Sealed)
|
||||
.WithName(AttributeName)
|
||||
.WithBase("Attribute")
|
||||
.WithProperty(
|
||||
new PropertyTemplate()
|
||||
.WithModifiers(Modifier.Public)
|
||||
.WithName("InitialValue")
|
||||
.WithType("object"));
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
context.RegisterPostInitializationOutput(context =>
|
||||
{
|
||||
// Register a syntax receiver that will be created for each generation pass
|
||||
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
|
||||
var compilationUnitBuilder =
|
||||
SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithUsing("System.ComponentModel")
|
||||
.WithNamespace(
|
||||
SyntaxBuilder.CreateNamespace(AttributeNamespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
|
||||
.WithModifier(Public)
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
|
||||
.WithModifier(Public))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Field)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithBaseClass(nameof(Attribute))
|
||||
.WithProperty(SyntaxBuilder.CreateProperty("object", InitialValue)
|
||||
.WithModifier(Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()))));
|
||||
var compilationUnitSyntax = compilationUnitBuilder.Build();
|
||||
var source = compilationUnitSyntax.ToFullString();
|
||||
context.AddSource($"{AttributeName}.g", source);
|
||||
});
|
||||
|
||||
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
predicate: static (s, _) => s is FieldDeclarationSyntax,
|
||||
transform: static (ctx, _) => GetFilteredFieldDeclarationSyntax(ctx)).Where(static c => c is not null);
|
||||
|
||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax? GetFilteredFieldDeclarationSyntax(GeneratorSyntaxContext context)
|
||||
{
|
||||
var fieldDeclarationSyntax = (FieldDeclarationSyntax)context.Node;
|
||||
if (fieldDeclarationSyntax.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() == AttributeName || s.Name.ToString() == AttributeShortName))
|
||||
{
|
||||
return fieldDeclarationSyntax.Parent as ClassDeclarationSyntax;
|
||||
}
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
return default;
|
||||
}
|
||||
|
||||
private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax?> classes, SourceProductionContext sourceProductionContext)
|
||||
{
|
||||
if (classes.IsDefaultOrEmpty)
|
||||
{
|
||||
var attributeText = GenerateDependencyPropertyAttribute.GenerateString();
|
||||
context.AddSource("GenerateDependencyPropertyAttribute.g", attributeText);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
|
||||
return;
|
||||
var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion;
|
||||
if (!maybeLanguageVersion.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions;
|
||||
var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options));
|
||||
|
||||
var attributeSymbol = compilation.GetTypeByMetadataName($"{AttributeNamespace}.{AttributeName}");
|
||||
if (attributeSymbol is null)
|
||||
var languageVersion = maybeLanguageVersion.Value;
|
||||
var distinctClasses = classes.Distinct();
|
||||
foreach (var c in distinctClasses.OfType<ClassDeclarationSyntax>())
|
||||
{
|
||||
var className = c.Identifier.ToFullString().Trim();
|
||||
if (!c.Modifiers.Any(m => m.ToString() == Partial))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostics.MissingAttributeDiagnostic(AttributeName, AttributeNamespace));
|
||||
return;
|
||||
Diagnostics.ClassNotPartial(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var fieldSymbols = new List<IFieldSymbol>();
|
||||
foreach (var field in receiver.CandidateFields)
|
||||
var originalNamespaceSyntax = GetParentOfType<BaseNamespaceDeclarationSyntax>(c);
|
||||
if (originalNamespaceSyntax is null)
|
||||
{
|
||||
var model = compilation.GetSemanticModel(field.SyntaxTree);
|
||||
foreach (var variable in field.Declaration.Variables)
|
||||
Diagnostics.ClassNotInNamespace(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var generatedClassBuilder = SyntaxBuilder.CreateClass(className)
|
||||
.WithModifiers(string.Join(" ", c.Modifiers.Select(m => m.ToFullString())));
|
||||
var generatedCompilationUnit = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(
|
||||
(languageVersion >= LanguageVersion.CSharp10 ?
|
||||
SyntaxBuilder.CreateFileScopedNamespace(originalNamespaceSyntax.Name.ToFullString()) :
|
||||
SyntaxBuilder.CreateNamespace(originalNamespaceSyntax.Name.ToFullString()))
|
||||
.WithClass(generatedClassBuilder));
|
||||
|
||||
if (originalNamespaceSyntax.Usings.OfType<UsingDirectiveSyntax>().Any(u => u.Name?.ToString() == DependencyPropertyNamespace) is false &&
|
||||
(originalNamespaceSyntax.Parent as CompilationUnitSyntax)?.Usings.Any(u => u.Name?.ToString() == DependencyPropertyNamespace) is false)
|
||||
{
|
||||
generatedCompilationUnit.WithUsing(DependencyPropertyNamespace);
|
||||
}
|
||||
|
||||
foreach (var field in c.DescendantNodes().OfType<FieldDeclarationSyntax>())
|
||||
{
|
||||
var attributeSyntax = field.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.FirstOrDefault(s => s.Name.ToString() == AttributeName ||
|
||||
s.Name.ToString() == AttributeShortName);
|
||||
if (attributeSyntax is null)
|
||||
{
|
||||
var fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol;
|
||||
if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)))
|
||||
{
|
||||
fieldSymbols.Add(fieldSymbol);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var initialValueArgument = attributeSyntax.ArgumentList?.Arguments
|
||||
.FirstOrDefault(arg => arg.NameEquals != null && arg.NameEquals.Name.Identifier.ValueText == InitialValue);
|
||||
|
||||
var fieldName = field.Declaration.Variables.First().Identifier.ToFullString().Trim();
|
||||
var fieldType = field.Declaration.Type.ToFullString().Trim();
|
||||
var propertyName = fieldName.TrimStart('_');
|
||||
propertyName = $"{char.ToUpper(propertyName[0])}{propertyName.Substring(1)}".Trim();
|
||||
var dependencyPropertyName = $"{propertyName.Trim()}{DependencyPropertySuffix}".Trim();
|
||||
var propertyBuilder = SyntaxBuilder.CreateProperty(fieldType, propertyName)
|
||||
.WithModifier(Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()
|
||||
.WithBody($"this.{fieldName} = ({fieldType})this.GetValue({dependencyPropertyName});\r\nreturn this.{fieldName};"))
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()
|
||||
.WithBody($"this.{fieldName} = value;\r\nthis.SetValue({dependencyPropertyName}, value);"));
|
||||
var dependencyPropertyFieldBuilder = SyntaxBuilder.CreateField(DependencyProperty, dependencyPropertyName)
|
||||
.WithModifiers($"{Public} {Static} {Readonly}")
|
||||
.WithInitializer($"{DependencyProperty}.Register(nameof({propertyName}), typeof({fieldType}), typeof({className}), new PropertyMetadata({initialValueArgument?.Expression.ToString()}))");
|
||||
|
||||
generatedClassBuilder
|
||||
.WithProperty(propertyBuilder)
|
||||
.WithField(dependencyPropertyFieldBuilder);
|
||||
}
|
||||
|
||||
foreach (var group in fieldSymbols.GroupBy(f => f.ContainingType, SymbolEqualityComparer.IncludeNullability))
|
||||
var generatedSyntax = generatedCompilationUnit.Build();
|
||||
if (originalNamespaceSyntax.Usings.Count != 0)
|
||||
{
|
||||
var classSource = ProcessClass((INamedTypeSymbol)group.Key, attributeSymbol, group.ToList(), context);
|
||||
context.ReportDiagnostic(Diagnostics.GeneratedSymbolsForClassDiagnostic(group.Key.Name));
|
||||
context.AddSource($"{group.Key.Name}.DependencyPropertyGenerator.g.cs", classSource);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ProcessClass(INamedTypeSymbol classSymbol, INamedTypeSymbol attributeSymbol, List<IFieldSymbol> fields, GeneratorExecutionContext generatorExecutionContext)
|
||||
{
|
||||
if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default))
|
||||
{
|
||||
var syntaxTree = classSymbol.DeclaringSyntaxReferences.First().SyntaxTree;
|
||||
var textSpan = classSymbol.DeclaringSyntaxReferences.First().Span;
|
||||
generatorExecutionContext.ReportDiagnostic(
|
||||
Diagnostics.ClassNotTopLevelDiagnostic(classSymbol.Name, classSymbol.ContainingSymbol.Name, syntaxTree, textSpan));
|
||||
return null;
|
||||
}
|
||||
|
||||
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
|
||||
var source = new ClassTemplate()
|
||||
.WithUsings(
|
||||
new UsingsTemplate()
|
||||
.WithNamespace("System"),
|
||||
new UsingsTemplate()
|
||||
.WithNamespace("System.Windows"))
|
||||
.WithNamespace(
|
||||
new NamespaceTemplate()
|
||||
.WithNamespace(namespaceName))
|
||||
.WithName(classSymbol.Name);
|
||||
AssignModifiers(source, classSymbol);
|
||||
foreach (var fieldSymbol in fields)
|
||||
{
|
||||
ProcessField(source, classSymbol, attributeSymbol, fieldSymbol);
|
||||
}
|
||||
|
||||
return source.GenerateString();
|
||||
}
|
||||
|
||||
private static void AssignModifiers(ClassTemplate source, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
//Find class declaration syntax and parse modifiers.
|
||||
if (classSymbol.DeclaringSyntaxReferences.Select(sr => sr.GetSyntax()).OfType<ClassDeclarationSyntax>().First() is ClassDeclarationSyntax classDeclarationSyntax)
|
||||
{
|
||||
source.WithModifiers(classDeclarationSyntax.Modifiers.Select(m => Modifier.Parse(m.ValueText)).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessField(ClassTemplate source, INamedTypeSymbol classSymbol, INamedTypeSymbol attributeSymbol, IFieldSymbol fieldSymbol)
|
||||
{
|
||||
var fieldName = fieldSymbol.Name;
|
||||
var fieldType = fieldSymbol.Type;
|
||||
|
||||
string propertyName = GenerateName(fieldName);
|
||||
|
||||
var attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
|
||||
var initialValueOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "InitialValue").Value;
|
||||
|
||||
var dependencyPropertyValue = initialValueOpt.Value is null ?
|
||||
@$"DependencyProperty.Register(""{propertyName}"", typeof({fieldType}), typeof({classSymbol.Name}))" :
|
||||
@$"DependencyProperty.Register(""{propertyName}"", typeof({fieldType}), typeof({classSymbol.Name}), new PropertyMetadata({initialValueOpt.ToCSharpString()}))";
|
||||
|
||||
source
|
||||
.WithField(
|
||||
new FieldTemplate()
|
||||
.WithModifiers(Modifier.Public, Modifier.Static, Modifier.Readonly)
|
||||
.WithType("DependencyProperty")
|
||||
.WithName(propertyName + DependencyPropertySuffix)
|
||||
.WithValue(dependencyPropertyValue))
|
||||
.WithProperty(
|
||||
new PropertyTemplate()
|
||||
.WithType(fieldType.ToString())
|
||||
.WithName(propertyName)
|
||||
.WithGetter(
|
||||
new GetterTemplate()
|
||||
.WithCode(
|
||||
new CodeBlockTemplate()
|
||||
.WithLine($"this.{fieldName} = ({fieldType})this.GetValue({propertyName + DependencyPropertySuffix});")
|
||||
.WithLine($"return ({fieldType})this.GetValue({propertyName + DependencyPropertySuffix});")))
|
||||
.WithSetter(
|
||||
new SetterTemplate()
|
||||
.WithCode(
|
||||
new CodeBlockTemplate()
|
||||
.WithLine($"this.{fieldName} = value;")
|
||||
.WithLine($"this.SetValue({propertyName + DependencyPropertySuffix}, value);"))));
|
||||
}
|
||||
|
||||
private static string GenerateName(string fieldName)
|
||||
{
|
||||
fieldName = fieldName.TrimStart('_');
|
||||
if (char.IsUpper(fieldName.First()))
|
||||
{
|
||||
return char.ToLower(fieldName.First()) + fieldName.Substring(1);
|
||||
generatedSyntax = generatedSyntax.AddUsings([.. originalNamespaceSyntax.Usings]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return char.ToUpper(fieldName.First()) + fieldName.Substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<FieldDeclarationSyntax> CandidateFields { get; } = new List<FieldDeclarationSyntax>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax &&
|
||||
fieldDeclarationSyntax.AttributeLists.Count > 0)
|
||||
if (originalNamespaceSyntax.Parent is CompilationUnitSyntax compilationUnit)
|
||||
{
|
||||
this.CandidateFields.Add(fieldDeclarationSyntax);
|
||||
generatedSyntax = generatedSyntax.AddUsings([.. compilationUnit.Usings]);
|
||||
}
|
||||
}
|
||||
|
||||
var fileSource = generatedSyntax.ToFullString();
|
||||
sourceProductionContext.AddSource($"{className}.g", fileSource);
|
||||
}
|
||||
}
|
||||
|
||||
private static T? GetParentOfType<T>(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode.Parent is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (syntaxNode.Parent is T parentNode)
|
||||
{
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
return GetParentOfType<T>(syntaxNode.Parent);
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
@@ -7,7 +7,9 @@ namespace System.Extensions
|
||||
{
|
||||
public const string DependencyPropertyGenerator_NoAttributeFound = "DGD 0001";
|
||||
public const string DependencyPropertyGenerator_ClassMustBeTopLevel = "DGD 1001";
|
||||
public const string DependencyPropertyGenerator_Success = "DGD 3001";
|
||||
public const string DependencyPropertyGenerator_ClassMustBePartial = "DGD 1002";
|
||||
public const string DependencyPropertyGenerator_ClassMustBeInNamespace = "DGD 1003";
|
||||
public const string DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged = "DGD 1004";
|
||||
|
||||
public static Diagnostic MissingAttributeDiagnostic(string attributeName, string attributeNamespace) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
@@ -35,17 +37,43 @@ namespace System.Extensions
|
||||
Location.Create(syntaxTree, textSpan),
|
||||
className, containingSymbol);
|
||||
|
||||
public static Diagnostic GeneratedSymbolsForClassDiagnostic(string className) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
DependencyPropertyGenerator_Success,
|
||||
$"{className} created dependency properties",
|
||||
"DependencyPropertyGenerator successfully created implementations for properties in {0}",
|
||||
"WpfExtended.SourceGeneration",
|
||||
DiagnosticSeverity.Info,
|
||||
true,
|
||||
null,
|
||||
null),
|
||||
Location.None,
|
||||
className);
|
||||
public static Diagnostic ClassNotPartial(string className) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
DependencyPropertyGenerator_ClassMustBePartial,
|
||||
$"{className} is not partial",
|
||||
"Class {0} must be marked as partial in order for the generator to work",
|
||||
"WpfExtended.SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
null,
|
||||
null),
|
||||
Location.None,
|
||||
className);
|
||||
|
||||
public static Diagnostic ClassNotInNamespace(string className) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
DependencyPropertyGenerator_ClassMustBeInNamespace,
|
||||
$"{className} is not defined in a namespace",
|
||||
"Class {0} must be defined inside a namespace for the generator to work",
|
||||
"WpfExtended.SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
null,
|
||||
null),
|
||||
Location.None,
|
||||
className);
|
||||
|
||||
public static Diagnostic ClassDoesNotImplementINotifyPropertyChanged(string className) => Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged,
|
||||
$"{className} must implement INotifyPropertyChanged",
|
||||
"Class {0} must implement INotifyPropertyChanged for the generator to work",
|
||||
"WpfExtended.SourceGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
null,
|
||||
null),
|
||||
Location.None,
|
||||
className);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Sybil;
|
||||
using System.Linq;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.Extensions;
|
||||
#nullable enable
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public class NotifyPropertyChangedGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AttributeNamespace = "System.Windows.Extensions";
|
||||
private const string AttributeName = "GenerateNotifyPropertyChangedAttribute";
|
||||
private const string AttributeShortName = "GenerateNotifyPropertyChanged";
|
||||
private const string PropertyChangedEventHandler = "PropertyChangedEventHandler";
|
||||
private const string NullablePropertyChangedEventHandler = "PropertyChangedEventHandler?";
|
||||
private const string PropertyChanged = "PropertyChanged";
|
||||
private const string Partial = "partial";
|
||||
private const string Public = "public";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(context =>
|
||||
{
|
||||
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(
|
||||
SyntaxBuilder.CreateNamespace(AttributeNamespace)
|
||||
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
|
||||
.WithModifier(Public)
|
||||
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
|
||||
.WithModifier(Public))
|
||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
||||
.WithArgument(AttributeTargets.Field)
|
||||
.WithArgument("Inherited", false)
|
||||
.WithArgument("AllowMultiple", false))
|
||||
.WithBaseClass(nameof(Attribute))));
|
||||
var compilationUnitSyntax = compilationUnitBuilder.Build();
|
||||
var source = compilationUnitSyntax.ToFullString();
|
||||
context.AddSource($"{AttributeName}.g", source);
|
||||
});
|
||||
|
||||
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
predicate: static (s, _) => s is FieldDeclarationSyntax,
|
||||
transform: static (ctx, _) => GetFilteredFieldDeclarationSyntax(ctx)).Where(static c => c is not null);
|
||||
|
||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax? GetFilteredFieldDeclarationSyntax(GeneratorSyntaxContext context)
|
||||
{
|
||||
var fieldDeclarationSyntax = (FieldDeclarationSyntax)context.Node;
|
||||
if (fieldDeclarationSyntax.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() == AttributeName || s.Name.ToString() == AttributeShortName))
|
||||
{
|
||||
return fieldDeclarationSyntax.Parent as ClassDeclarationSyntax;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax?> classes, SourceProductionContext sourceProductionContext)
|
||||
{
|
||||
if (classes.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion;
|
||||
if (!maybeLanguageVersion.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var languageVersion = maybeLanguageVersion.Value;
|
||||
var distinctClasses = classes.Distinct();
|
||||
foreach (var c in distinctClasses.OfType<ClassDeclarationSyntax>())
|
||||
{
|
||||
var className = c.Identifier.ToFullString();
|
||||
if (!c.Modifiers.Any(m => m.ToString() == Partial))
|
||||
{
|
||||
Diagnostics.ClassNotPartial(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var originalNamespaceSyntax = GetParentOfType<BaseNamespaceDeclarationSyntax>(c);
|
||||
if (originalNamespaceSyntax is null)
|
||||
{
|
||||
Diagnostics.ClassNotInNamespace(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!c.Members
|
||||
.OfType<EventFieldDeclarationSyntax>()
|
||||
.Any(eventField =>
|
||||
(eventField.Declaration.Variables.Any(variable => variable.Identifier.Text == PropertyChanged) &&
|
||||
eventField.Modifiers.Any(modifier => modifier.Text == Public) &&
|
||||
eventField.Declaration.Type.ToString() == PropertyChangedEventHandler) ||
|
||||
eventField.Declaration.Type.ToString() == NullablePropertyChangedEventHandler))
|
||||
{
|
||||
Diagnostics.ClassDoesNotImplementINotifyPropertyChanged(className);
|
||||
continue;
|
||||
}
|
||||
|
||||
var generatedClassBuilder = SyntaxBuilder.CreateClass(className)
|
||||
.WithModifiers(string.Join(" ", c.Modifiers.Select(m => m.ToFullString())));
|
||||
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
|
||||
.WithNamespace(
|
||||
(languageVersion >= LanguageVersion.CSharp10 ?
|
||||
SyntaxBuilder.CreateFileScopedNamespace(originalNamespaceSyntax.Name.ToFullString()) :
|
||||
SyntaxBuilder.CreateNamespace(originalNamespaceSyntax.Name.ToFullString()))
|
||||
.WithClass(generatedClassBuilder));
|
||||
|
||||
foreach (var field in c.DescendantNodes().OfType<FieldDeclarationSyntax>())
|
||||
{
|
||||
if (field.AttributeLists
|
||||
.SelectMany(l => l.Attributes)
|
||||
.OfType<AttributeSyntax>()
|
||||
.Any(s => s.Name.ToString() == AttributeName ||
|
||||
s.Name.ToString() == AttributeShortName) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fieldName = field.Declaration.Variables.First().Identifier.ToFullString();
|
||||
var propertyName = fieldName.TrimStart('_');
|
||||
propertyName = $"{char.ToUpper(propertyName[0])}{propertyName.Substring(1)}";
|
||||
var propertyBuilder = SyntaxBuilder.CreateProperty(field.Declaration.Type.ToFullString(), propertyName)
|
||||
.WithModifier(Public)
|
||||
.WithAccessor(SyntaxBuilder.CreateGetter()
|
||||
.WithBody($"return this.{fieldName};"))
|
||||
.WithAccessor(SyntaxBuilder.CreateSetter()
|
||||
.WithBody($"this.{fieldName} = value;\r\nthis.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));"));
|
||||
generatedClassBuilder.WithProperty(propertyBuilder);
|
||||
}
|
||||
|
||||
var generatedSyntax = compilationUnitBuilder.Build();
|
||||
if (originalNamespaceSyntax.Usings.Count != 0)
|
||||
{
|
||||
generatedSyntax = generatedSyntax.AddUsings([.. originalNamespaceSyntax.Usings]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (originalNamespaceSyntax.Parent is CompilationUnitSyntax compilationUnit)
|
||||
{
|
||||
generatedSyntax = generatedSyntax.AddUsings([.. compilationUnit.Usings]);
|
||||
}
|
||||
}
|
||||
|
||||
var fileSource = generatedSyntax.ToFullString();
|
||||
sourceProductionContext.AddSource($"{className}.g", fileSource);
|
||||
}
|
||||
}
|
||||
|
||||
private static T? GetParentOfType<T>(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode.Parent is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (syntaxNode.Parent is T parentNode)
|
||||
{
|
||||
return parentNode;
|
||||
}
|
||||
|
||||
return GetParentOfType<T>(syntaxNode.Parent);
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal abstract class AbstractTemplate
|
||||
{
|
||||
public abstract void Generate(CodeWriter codeWriter);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class ArgumentTemplate : AbstractTemplate
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
||||
public ArgumentTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArgumentTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append(this.Type)
|
||||
.Append(' ')
|
||||
.Append(this.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class AttributeTemplate : AbstractTemplate
|
||||
{
|
||||
public string Attribute { get; set; }
|
||||
|
||||
public AttributeTemplate WithAttribute(string attribute)
|
||||
{
|
||||
this.Attribute = attribute;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append('[')
|
||||
.Append(this.Attribute)
|
||||
.Append(']');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class ClassTemplate : AbstractTemplate
|
||||
{
|
||||
public List<AttributeTemplate> Attributes { get; } = new List<AttributeTemplate>();
|
||||
public List<UsingsTemplate> Usings { get; } = new List<UsingsTemplate>();
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public List<PropertyTemplate> Properties { get; } = new List<PropertyTemplate>();
|
||||
public List<FieldTemplate> Fields { get; } = new List<FieldTemplate>();
|
||||
public List<MethodTemplate> Methods { get; } = new List<MethodTemplate>();
|
||||
public List<ConstructorTemplate> Constructors { get; } = new List<ConstructorTemplate>();
|
||||
public NamespaceTemplate Namespace { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Base { get; set; }
|
||||
|
||||
public ClassTemplate WithAttributes(params AttributeTemplate[] attributeTemplates)
|
||||
{
|
||||
this.Attributes.Clear();
|
||||
this.Attributes.AddRange(attributeTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithAttribute(AttributeTemplate attributeTemplate)
|
||||
{
|
||||
this.Attributes.Add(attributeTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithUsings(params UsingsTemplate[] usingsTemplates)
|
||||
{
|
||||
this.Usings.Clear();
|
||||
this.Usings.AddRange(usingsTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithUsing(UsingsTemplate usingsTemplate)
|
||||
{
|
||||
this.Usings.Add(usingsTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithModifier(Modifier modifier)
|
||||
{
|
||||
this.Modifiers.Add(modifier);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithMethods(params MethodTemplate[] methodTemplates)
|
||||
{
|
||||
this.Methods.Clear();
|
||||
this.Methods.AddRange(methodTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithMethod(MethodTemplate methodTemplate)
|
||||
{
|
||||
this.Methods.Add(methodTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithFields(params FieldTemplate[] fieldTemplates)
|
||||
{
|
||||
this.Fields.Clear();
|
||||
this.Fields.AddRange(fieldTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithField(FieldTemplate fieldTemplate)
|
||||
{
|
||||
this.Fields.Add(fieldTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithProperties(params PropertyTemplate[] propertyTemplates)
|
||||
{
|
||||
this.Properties.Clear();
|
||||
this.Properties.AddRange(propertyTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithProperty(PropertyTemplate propertyTemplate)
|
||||
{
|
||||
this.Properties.Add(propertyTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithConstructors(params ConstructorTemplate[] constructorTemplates)
|
||||
{
|
||||
this.Constructors.Clear();
|
||||
this.Constructors.AddRange(constructorTemplates);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithConstructor(ConstructorTemplate constructorTemplate)
|
||||
{
|
||||
this.Constructors.Add(constructorTemplate);
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithNamespace(NamespaceTemplate namespaceTemplate)
|
||||
{
|
||||
this.Namespace = namespaceTemplate;
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public ClassTemplate WithBase(string b)
|
||||
{
|
||||
this.Base = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var u in this.Usings)
|
||||
{
|
||||
codeWriter
|
||||
.Append(u)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.AppendLine()
|
||||
.Append(this.Namespace)
|
||||
.BeginCodeBlock();
|
||||
foreach(var attribute in this.Attributes)
|
||||
{
|
||||
codeWriter
|
||||
.Append(attribute)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append("class")
|
||||
.Append(' ')
|
||||
.Append(this.Name);
|
||||
if (!string.IsNullOrEmpty(this.Base))
|
||||
{
|
||||
codeWriter
|
||||
.Append(' ')
|
||||
.Append(':')
|
||||
.Append(' ')
|
||||
.Append(this.Base);
|
||||
}
|
||||
|
||||
codeWriter.BeginCodeBlock();
|
||||
foreach(var field in this.Fields)
|
||||
{
|
||||
codeWriter
|
||||
.Append(field)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var property in this.Properties)
|
||||
{
|
||||
codeWriter
|
||||
.Append(property)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var constructor in this.Constructors)
|
||||
{
|
||||
codeWriter
|
||||
.Append(constructor)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
foreach(var method in this.Methods)
|
||||
{
|
||||
codeWriter
|
||||
.Append(method)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
codeWriter.EndCodeBlock().EndCodeBlock();
|
||||
}
|
||||
|
||||
public string GenerateString()
|
||||
{
|
||||
var codeWriter = new CodeWriter();
|
||||
this.Generate(codeWriter);
|
||||
return codeWriter.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class CodeBlockTemplate : CodeTemplate
|
||||
{
|
||||
public List<string> Lines { get; set; } = new List<string>();
|
||||
|
||||
public CodeBlockTemplate WithLines(params string[] lines)
|
||||
{
|
||||
this.Lines.Clear();
|
||||
this.Lines.AddRange(lines);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeBlockTemplate WithLine(string line)
|
||||
{
|
||||
this.Lines.Add(line);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var line in this.Lines)
|
||||
{
|
||||
codeWriter.AppendLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal abstract class CodeTemplate : AbstractTemplate
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class ConstructorTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public List<ArgumentTemplate> Arguments { get; } = new List<ArgumentTemplate>();
|
||||
public string Type { get; set; }
|
||||
public string Body { get; set; }
|
||||
public string Base { get; set; }
|
||||
|
||||
public ConstructorTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithBody(string body)
|
||||
{
|
||||
this.Body = body;
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithBase(string b)
|
||||
{
|
||||
this.Base = b;
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithArguments(params ArgumentTemplate[] argumentTemplates)
|
||||
{
|
||||
this.Arguments.Clear();
|
||||
this.Arguments.AddRange(argumentTemplates);
|
||||
return this;
|
||||
}
|
||||
public ConstructorTemplate WithArgument(ArgumentTemplate argumentTemplate)
|
||||
{
|
||||
this.Arguments.Add(argumentTemplate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(this.Type)
|
||||
.Append('(');
|
||||
for (int i = 0; i < this.Arguments.Count; i++)
|
||||
{
|
||||
var argument = this.Arguments[i];
|
||||
codeWriter.Append(argument);
|
||||
if (i < this.Arguments.Count - 1)
|
||||
{
|
||||
codeWriter.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
codeWriter.Append(')');
|
||||
if (!string.IsNullOrEmpty(this.Base))
|
||||
{
|
||||
codeWriter
|
||||
.Append(" : base(")
|
||||
.Append(this.Base)
|
||||
.Append(')');
|
||||
}
|
||||
|
||||
codeWriter.BeginCodeBlock()
|
||||
.Append(this.Body)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class FieldTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier> { Modifier.Public };
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
||||
public FieldTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public FieldTemplate WithValue(string value)
|
||||
{
|
||||
this.Value = value;
|
||||
return this;
|
||||
}
|
||||
public FieldTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public FieldTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach (var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter.Append(this.Type)
|
||||
.Append(' ')
|
||||
.Append(this.Name)
|
||||
.Append(' ')
|
||||
.Append('=')
|
||||
.Append(' ')
|
||||
.Append(this.Value)
|
||||
.Append(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class GetterTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public CodeTemplate Code { get; set; }
|
||||
|
||||
public GetterTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public GetterTemplate WithCode(CodeTemplate codeTemplate)
|
||||
{
|
||||
this.Code = codeTemplate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter.Append("get");
|
||||
if (this.Code is SimpleCodeTemplate simpleGetter)
|
||||
{
|
||||
codeWriter
|
||||
.Append(simpleGetter)
|
||||
.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
codeWriter
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Code)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class MethodTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public List<ArgumentTemplate> Arguments { get; } = new List<ArgumentTemplate>();
|
||||
public string Name { get; set; }
|
||||
public string ReturnType { get; set; }
|
||||
public string Body { get; set; }
|
||||
|
||||
public MethodTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithArguments(params ArgumentTemplate[] argumentTemplates)
|
||||
{
|
||||
this.Arguments.Clear();
|
||||
this.Arguments.AddRange(argumentTemplates);
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithReturnType(string returnType)
|
||||
{
|
||||
this.ReturnType = returnType;
|
||||
return this;
|
||||
}
|
||||
public MethodTemplate WithBody(string body)
|
||||
{
|
||||
this.Body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(this.ReturnType)
|
||||
.Append(' ')
|
||||
.Append(this.Name)
|
||||
.Append('(');
|
||||
for(int i = 0; i < this.Arguments.Count; i++)
|
||||
{
|
||||
var argument = this.Arguments[i];
|
||||
codeWriter.Append(argument);
|
||||
if (i < this.Arguments.Count - 1)
|
||||
{
|
||||
codeWriter.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(')')
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Body)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class Modifier : AbstractTemplate
|
||||
{
|
||||
public static Modifier Protected { get; } = new Modifier { Value = "protected" };
|
||||
public static Modifier Readonly { get; } = new Modifier { Value = "readonly" };
|
||||
public static Modifier Static { get; } = new Modifier { Value = "static" };
|
||||
public static Modifier Public { get; } = new Modifier { Value = "public" };
|
||||
public static Modifier Internal { get; } = new Modifier { Value = "internal" };
|
||||
public static Modifier Private { get; } = new Modifier { Value = "private" };
|
||||
public static Modifier Abstract { get; } = new Modifier { Value = "abstract" };
|
||||
public static Modifier Virtual { get; } = new Modifier { Value = "virtual" };
|
||||
public static Modifier Partial { get; } = new Modifier { Value = "partial" };
|
||||
public static Modifier Sealed { get; } = new Modifier { Value = "sealed" };
|
||||
|
||||
public string Value { get; private set; }
|
||||
|
||||
private Modifier()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter.Append(this.Value);
|
||||
}
|
||||
|
||||
public static bool TryParse(string value, out Modifier modifier)
|
||||
{
|
||||
foreach(var supportedModifier in SupportedModifiers)
|
||||
{
|
||||
if (supportedModifier.Value.Equals(value, StringComparison.Ordinal))
|
||||
{
|
||||
modifier = supportedModifier;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
modifier = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Modifier Parse(string value)
|
||||
{
|
||||
if (TryParse(value, out var modifier))
|
||||
{
|
||||
return modifier;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not find a modifier with value {value}");
|
||||
}
|
||||
|
||||
private static IEnumerable<Modifier> SupportedModifiers { get; } = new List<Modifier> { Protected, Readonly, Static, Internal, Public, Private, Abstract, Virtual, Partial, Sealed };
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class NamespaceTemplate : AbstractTemplate
|
||||
{
|
||||
public string Namespace { get; set; }
|
||||
public NamespaceTemplate WithNamespace(string n)
|
||||
{
|
||||
this.Namespace = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append("namespace ")
|
||||
.Append(this.Namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class PropertyTemplate : AbstractTemplate
|
||||
{
|
||||
public static GetterTemplate DefaultGetter = new GetterTemplate()
|
||||
.WithCode(
|
||||
new SimpleCodeTemplate()
|
||||
.WithCode(";"));
|
||||
public static SetterTemplate DefaultSetter = new SetterTemplate()
|
||||
.WithCode(
|
||||
new SimpleCodeTemplate()
|
||||
.WithCode(";"));
|
||||
public List<Modifier> Modifiers { get; private set; } = new List<Modifier>() { Modifier.Public };
|
||||
public GetterTemplate Getter { get; set; } = DefaultGetter;
|
||||
public SetterTemplate Setter { get; set; } = DefaultSetter;
|
||||
public string Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public PropertyTemplate WithGetter(GetterTemplate getter)
|
||||
{
|
||||
this.Getter = getter;
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithSetter(SetterTemplate setter)
|
||||
{
|
||||
this.Setter = setter;
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithName(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
return this;
|
||||
}
|
||||
public PropertyTemplate WithType(string type)
|
||||
{
|
||||
this.Type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach(var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.Append(this.Type)
|
||||
.Append(' ')
|
||||
.Append(this.Name)
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Getter)
|
||||
.Append(this.Setter)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class SetterTemplate : AbstractTemplate
|
||||
{
|
||||
public List<Modifier> Modifiers { get; } = new List<Modifier>();
|
||||
public CodeTemplate Code { get; set; }
|
||||
|
||||
public SetterTemplate WithModifiers(params Modifier[] modifiers)
|
||||
{
|
||||
this.Modifiers.Clear();
|
||||
this.Modifiers.AddRange(modifiers);
|
||||
return this;
|
||||
}
|
||||
public SetterTemplate WithCode(CodeTemplate codeTemplate)
|
||||
{
|
||||
this.Code = codeTemplate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
foreach (var modifier in this.Modifiers)
|
||||
{
|
||||
codeWriter
|
||||
.Append(modifier)
|
||||
.Append(' ');
|
||||
}
|
||||
|
||||
codeWriter.Append("set");
|
||||
if (this.Code is SimpleCodeTemplate simpleSetter)
|
||||
{
|
||||
codeWriter
|
||||
.Append(simpleSetter)
|
||||
.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
codeWriter
|
||||
.BeginCodeBlock()
|
||||
.Append(this.Code)
|
||||
.EndCodeBlock();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class SimpleCodeTemplate : CodeTemplate
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public SimpleCodeTemplate WithCode(string code)
|
||||
{
|
||||
this.Code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter.Append(this.Code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace System.Extensions.Templates
|
||||
{
|
||||
internal sealed class UsingsTemplate : AbstractTemplate
|
||||
{
|
||||
public string Namespace { get; set; }
|
||||
public UsingsTemplate WithNamespace(string n)
|
||||
{
|
||||
this.Namespace = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Generate(CodeWriter codeWriter)
|
||||
{
|
||||
codeWriter
|
||||
.Append("using ")
|
||||
.Append(this.Namespace)
|
||||
.Append(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,20 @@
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<Description>Source generator library.</Description>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<Version>0.1.1</Version>
|
||||
<Version>0.3.1</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Sybil" Version="0.8.4" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -29,6 +33,7 @@
|
||||
<ItemGroup>
|
||||
<!-- Package the generator in the analyzer directory of the nuget package -->
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Include="$(OutputPath)\Sybil.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace SystemExtensions.DependencyInjection.Tests.Configuration
|
||||
{
|
||||
public sealed class DummyOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Extensions;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WpfExtended.Tests.Http
|
||||
{
|
||||
public class HttpMessageLogger : DelegatingHandler
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
public HttpMessageLogger(ILogger logger, HttpMessageHandler innerHandler) : base(innerHandler)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull(nameof(logger));
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
this.logger.LogInformation($"{request.Method} - {request.RequestUri}");
|
||||
var response = await base.SendAsync(request, cancellationToken);
|
||||
this.logger.LogInformation($"{response.RequestMessage.RequestUri} - {response.StatusCode}");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Slim;
|
||||
using System;
|
||||
using System.Extensions;
|
||||
using System.Net.Http;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Extensions.Http;
|
||||
using WpfExtended.Test;
|
||||
using WpfExtended.Tests.Http;
|
||||
|
||||
namespace WpfExtended.Tests
|
||||
{
|
||||
public class Launcher : ExtendedApplication<MainWindow>
|
||||
{
|
||||
private static Launcher Instance { get; } = new Launcher();
|
||||
|
||||
[STAThread]
|
||||
public static int Main()
|
||||
{
|
||||
return Instance.Run();
|
||||
}
|
||||
|
||||
protected override void SetupServiceManager(IServiceManager serviceManager)
|
||||
{
|
||||
serviceManager.RegisterDebugLoggerFactory();
|
||||
serviceManager.RegisterResolver(
|
||||
new HttpClientResolver()
|
||||
.WithHttpMessageHandlerFactory((sp, category) =>
|
||||
{
|
||||
var loggerType = typeof(ILogger<>).MakeGenericType(category);
|
||||
var logger = sp.GetService(loggerType).As<ILogger>();
|
||||
return new HttpMessageLogger(logger, new HttpClientHandler());
|
||||
}));
|
||||
}
|
||||
|
||||
protected override void ApplicationClosing()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplicationStarting()
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool HandleException(Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void RegisterServices(IServiceProducer serviceProducer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Configuration;
|
||||
using System.Extensions;
|
||||
using SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||
|
||||
namespace WpfExtended.Tests.Services
|
||||
{
|
||||
public sealed class DummyService : IDummyService
|
||||
{
|
||||
private readonly ILogger<DummyService> logger;
|
||||
private readonly IOptions<DummyOptions> dummyOptions;
|
||||
private readonly IUpdateableOptions<DummyOptions> updateableDummyOptions;
|
||||
private readonly ILiveOptions<DummyOptions> liveOptions;
|
||||
private readonly ILiveUpdateableOptions<DummyOptions> liveUpdateableOptions;
|
||||
|
||||
public DummyService(
|
||||
ILogger<DummyService> logger,
|
||||
IOptions<DummyOptions> options,
|
||||
IUpdateableOptions<DummyOptions> updateableOptions,
|
||||
ILiveUpdateableOptions<DummyOptions> liveUpdateableOptions,
|
||||
ILiveOptions<DummyOptions> liveOptions)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull(nameof(logger));
|
||||
this.dummyOptions = options.ThrowIfNull(nameof(options));
|
||||
this.updateableDummyOptions = updateableOptions.ThrowIfNull(nameof(updateableOptions));
|
||||
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
|
||||
this.liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull(nameof(liveUpdateableOptions));
|
||||
}
|
||||
|
||||
public void OnClosing()
|
||||
{
|
||||
this.logger.LogInformation($"Closing {nameof(DummyService)}");
|
||||
}
|
||||
|
||||
public void OnStartup()
|
||||
{
|
||||
this.logger.LogInformation($"Starting {nameof(DummyService)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Windows.Extensions.Services;
|
||||
|
||||
namespace WpfExtended.Tests.Services
|
||||
{
|
||||
public interface IDummyService : IApplicationLifetimeService
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<UserControl x:Class="WpfExtended.Tests.TestApplication.Controls.PropertyList"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WpfExtended.Tests.TestApplication.Controls"
|
||||
DataContextChanged="PropertyList_DataContextChanged"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ListView ItemsSource="{Binding ElementName=_this, Path=EffectProperties, Mode=OneWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Name}"></TextBlock>
|
||||
<TextBox Text="{Binding Value, Mode=OneWay}" KeyDown="TextBox_KeyDown"></TextBox>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Extensions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Effects;
|
||||
using WpfExtended.Tests.TestApplication.Models;
|
||||
|
||||
namespace WpfExtended.Tests.TestApplication.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for PropertyList.xaml
|
||||
/// </summary>
|
||||
public partial class PropertyList : UserControl
|
||||
{
|
||||
public ObservableCollection<EffectProperty> EffectProperties { get; } = new ObservableCollection<EffectProperty>();
|
||||
|
||||
public PropertyList()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void PropertyList_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (this.DataContext is not ShaderEffect shaderEffect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.EffectProperties.ClearAnd().AddRange(shaderEffect.GetType().GetProperties()
|
||||
.Where(pInfo => pInfo.PropertyType == typeof(double))
|
||||
.Select(pInfo => new EffectProperty
|
||||
{
|
||||
Name = pInfo.Name,
|
||||
Property = pInfo,
|
||||
Value = (double)pInfo.GetValue(shaderEffect),
|
||||
Source = shaderEffect
|
||||
}));
|
||||
}
|
||||
|
||||
private void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (sender is not TextBox textBox)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (textBox.DataContext is not EffectProperty effectProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key is not System.Windows.Input.Key.Enter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!double.TryParse(textBox.Text, out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
effectProperty.Value = value;
|
||||
effectProperty.UpdateProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Http.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Slim;
|
||||
using Slim.Integration.ServiceCollection;
|
||||
using System;
|
||||
using System.Extensions;
|
||||
using System.Logging;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Extensions;
|
||||
using WpfExtended.Test;
|
||||
using WpfExtended.Tests.Services;
|
||||
|
||||
namespace WpfExtended.Tests
|
||||
{
|
||||
public class Launcher : ExtendedApplication<MainWindow>
|
||||
{
|
||||
private static Launcher Instance { get; } = new Launcher();
|
||||
|
||||
[STAThread]
|
||||
public static void Main()
|
||||
{
|
||||
Instance.Run();
|
||||
}
|
||||
|
||||
protected override System.IServiceProvider SetupServiceProvider(IServiceCollection services)
|
||||
{
|
||||
var serviceManager = new ServiceManager();
|
||||
serviceManager.RegisterResolver(new LoggerResolver());
|
||||
serviceManager.RegisterDebugLoggerFactory();
|
||||
serviceManager.RegisterOptionsManager();
|
||||
services.RegisterHttpClient<MainWindow>()
|
||||
.Build();
|
||||
var provider = services.BuildSlimServiceProvider(serviceManager);
|
||||
return provider;
|
||||
}
|
||||
|
||||
protected override void ApplicationClosing()
|
||||
{
|
||||
}
|
||||
|
||||
protected override ValueTask ApplicationStarting() => ValueTask.CompletedTask;
|
||||
|
||||
protected override bool HandleException(Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IDummyService, DummyService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,19 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfExtended.Test"
|
||||
xmlns:controls="clr-namespace:WpfExtended.Tests.TestApplication.Controls"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="200" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollViewer>
|
||||
<WrapPanel x:Name="WrapPanel" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"></WrapPanel>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer Grid.Column="1">
|
||||
<controls:PropertyList x:Name="PropertyList"></controls:PropertyList>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
+14
-1
@@ -1,8 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Extensions;
|
||||
using System.Http;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Windows;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Media;
|
||||
@@ -64,8 +64,21 @@ namespace WpfExtended.Test
|
||||
BorderBrush = Brushes.LightGray,
|
||||
BorderThickness = new Thickness(1)
|
||||
};
|
||||
|
||||
image.MouseLeftButtonDown += Image_MouseLeftButtonDown;
|
||||
this.WrapPanel.Children.Add(image);
|
||||
}
|
||||
}
|
||||
|
||||
private void Image_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not CaptionedImage captionedImage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var effect = captionedImage.ImageEffect;
|
||||
this.PropertyList.DataContext = effect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace WpfExtended.Tests.TestApplication.Models
|
||||
{
|
||||
public sealed class EffectProperty
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public double Value { get; set; }
|
||||
public object Source { get; set; }
|
||||
public PropertyInfo Property { get; set; }
|
||||
|
||||
public void UpdateProperty()
|
||||
{
|
||||
if (this.Source is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.Property is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Property.SetValue(this.Source, this.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<StartupObject>WpfExtended.Tests.Launcher</StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.3.0" />
|
||||
<PackageReference Include="FlaUI.UIA3" Version="5.0.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="Microsoft.TestPlatform" Version="18.0.1" />
|
||||
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="18.0.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="4.0.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="4.0.2" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30804.86
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfExtended", "WpfExtended\WpfExtended.csproj", "{E822AE0D-5EA1-4C61-A1DC-76CF73C8A524}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfExtended.Tests", "WpfExtended.Test\WpfExtended.Tests.csproj", "{48D2B8E6-F906-40D8-952E-B7B567D76A9B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfExtended.SourceGeneration", "WpfExtended.SourceGeneration\WpfExtended.SourceGeneration.csproj", "{BB890187-DF49-4E58-AE41-09C40F541292}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfExtended.SourceGeneration.Tests", "WpfExtended.SourceGeneration.Tests\WpfExtended.SourceGeneration.Tests.csproj", "{BDD41E4C-3F5D-4734-8BD2-62B444E4845D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E822AE0D-5EA1-4C61-A1DC-76CF73C8A524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E822AE0D-5EA1-4C61-A1DC-76CF73C8A524}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E822AE0D-5EA1-4C61-A1DC-76CF73C8A524}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E822AE0D-5EA1-4C61-A1DC-76CF73C8A524}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{48D2B8E6-F906-40D8-952E-B7B567D76A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{48D2B8E6-F906-40D8-952E-B7B567D76A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{48D2B8E6-F906-40D8-952E-B7B567D76A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{48D2B8E6-F906-40D8-952E-B7B567D76A9B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BB890187-DF49-4E58-AE41-09C40F541292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB890187-DF49-4E58-AE41-09C40F541292}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB890187-DF49-4E58-AE41-09C40F541292}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB890187-DF49-4E58-AE41-09C40F541292}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BDD41E4C-3F5D-4734-8BD2-62B444E4845D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DE159EB4-7D6E-46D0-9366-391E9FDD37DC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,10 @@
|
||||
<Solution>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".github/workflows/cd.yaml" />
|
||||
<File Path=".github/workflows/ci.yaml" />
|
||||
</Folder>
|
||||
<Project Path="WpfExtended.Blazor/WpfExtended.Blazor.csproj" />
|
||||
<Project Path="WpfExtended.SourceGeneration/WpfExtended.SourceGeneration.csproj" />
|
||||
<Project Path="WpfExtended.Test/WpfExtended.Tests.csproj" />
|
||||
<Project Path="WpfExtended/WpfExtended.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Windows.Media.Effects;
|
||||
|
||||
namespace System.Windows.Media.Extensions.Effects
|
||||
{
|
||||
#if SILVERLIGHT
|
||||
using UIPropertyMetadata = System.Windows.PropertyMetadata ;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Combines Embossed effect with a noise effect to distort an image based on the depth of the image contents
|
||||
/// </summary>
|
||||
public class DepthBlur : ShaderEffect
|
||||
{
|
||||
public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(DepthBlur), 0, SamplingMode.Bilinear);
|
||||
public static readonly DependencyProperty FocusDepthProperty = DependencyProperty.Register("FocusDepth", typeof(double), typeof(DepthBlur), new UIPropertyMetadata(0.5, PixelShaderConstantCallback(0)));
|
||||
public static readonly DependencyProperty FocusRangeProperty = DependencyProperty.Register("FocusStrength", typeof(double), typeof(DepthBlur), new UIPropertyMetadata(0.2, PixelShaderConstantCallback(1)));
|
||||
public static readonly DependencyProperty TextureWidthProperty = DependencyProperty.Register("TextureWidth", typeof(double), typeof(DepthBlur), new UIPropertyMetadata(512d, PixelShaderConstantCallback(2)));
|
||||
public static readonly DependencyProperty TextureHeightProperty = DependencyProperty.Register("TextureHeight", typeof(double), typeof(DepthBlur), new UIPropertyMetadata(512d, PixelShaderConstantCallback(3)));
|
||||
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(DepthBlur), new UIPropertyMetadata(0d, PixelShaderConstantCallback(4)));
|
||||
public static readonly DependencyProperty StrengthProperty = DependencyProperty.Register("Strength", typeof(double), typeof(DepthBlur), new UIPropertyMetadata(1d, PixelShaderConstantCallback(5)));
|
||||
|
||||
private readonly static PixelShader pixelShader = new PixelShader();
|
||||
|
||||
static DepthBlur()
|
||||
{
|
||||
pixelShader = PixelShaderUtility.LoadPixelShader<DepthBlur>();
|
||||
}
|
||||
|
||||
public DepthBlur()
|
||||
{
|
||||
this.PixelShader = pixelShader;
|
||||
|
||||
// Update each DependencyProperty that's registered with a shader register. This
|
||||
// is needed to ensure the shader gets sent the proper default value.
|
||||
UpdateShaderValue(InputProperty);
|
||||
UpdateShaderValue(FocusDepthProperty);
|
||||
UpdateShaderValue(FocusRangeProperty);
|
||||
UpdateShaderValue(TextureWidthProperty);
|
||||
UpdateShaderValue(TextureHeightProperty);
|
||||
UpdateShaderValue(AngleProperty);
|
||||
UpdateShaderValue(StrengthProperty);
|
||||
}
|
||||
|
||||
[System.ComponentModel.BrowsableAttribute(false)]
|
||||
public Brush Input
|
||||
{
|
||||
get { return (Brush)GetValue(InputProperty); }
|
||||
set { SetValue(InputProperty, value); }
|
||||
}
|
||||
public double FocusDepth
|
||||
{
|
||||
get { return (double)GetValue(FocusDepthProperty); }
|
||||
set { SetValue(FocusDepthProperty, value); }
|
||||
}
|
||||
public double FocusRange
|
||||
{
|
||||
get { return (double)GetValue(FocusRangeProperty); }
|
||||
set { SetValue(FocusRangeProperty, value); }
|
||||
}
|
||||
public double TextureWidth
|
||||
{
|
||||
get { return (double)GetValue(TextureWidthProperty); }
|
||||
set { SetValue(TextureWidthProperty, value); }
|
||||
}
|
||||
public double TextureHeight
|
||||
{
|
||||
get { return (double)GetValue(TextureHeightProperty); }
|
||||
set { SetValue(TextureHeightProperty, value); }
|
||||
}
|
||||
public double Angle
|
||||
{
|
||||
get { return (double)GetValue(AngleProperty); }
|
||||
set { SetValue(AngleProperty, value); }
|
||||
}
|
||||
public double Strength
|
||||
{
|
||||
get { return (double)GetValue(StrengthProperty); }
|
||||
set { SetValue(StrengthProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//-----------------------------------------------------------------------------------------
|
||||
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
|
||||
//-----------------------------------------------------------------------------------------
|
||||
|
||||
float FocusDepth : register(C0);
|
||||
float FocusRange : register(C1);
|
||||
float TextureWidth : register(C2);
|
||||
float TextureHeight : register(C3);
|
||||
float Strength : register(C5);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Sampler Inputs (Brushes, including ImplicitInput)
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
sampler2D implicitInputSampler : register(S0);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Pixel Shader
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
float4 main(float2 uv : TEXCOORD) : COLOR
|
||||
{
|
||||
float4 originalColor = tex2D(implicitInputSampler, uv);
|
||||
|
||||
// Compute the luminance
|
||||
float luminance = 0.299f * originalColor.r + 0.587f * originalColor.g + 0.114f * originalColor.b;
|
||||
|
||||
// Calculate the blur amount based on the luminance
|
||||
float blurAmount = abs(luminance - FocusDepth) - FocusRange;
|
||||
blurAmount = clamp(blurAmount, 0.0f, 1.0f);
|
||||
|
||||
float2 texelSize = 1.0f / float2(TextureWidth, TextureHeight); // Assuming a texture resolution of 512x512. Change as necessary.
|
||||
float4 color = float4(0, 0, 0, 0);
|
||||
float blurSize = 5;
|
||||
|
||||
// Simple box blur
|
||||
for (int i = -blurSize; i <= blurSize; i++)
|
||||
{
|
||||
for (int j = -blurSize; j <= blurSize; j++)
|
||||
{
|
||||
color += tex2D(implicitInputSampler, uv + blurAmount * float2(i, j) * texelSize);
|
||||
}
|
||||
}
|
||||
|
||||
color /= (2 * blurSize + 1) * (2 * blurSize + 1);
|
||||
|
||||
// Set the pixel colors as a multiple of the luminance
|
||||
color.rgb = luminance * color.rgb;
|
||||
|
||||
// Preserve the original alpha
|
||||
color.a = originalColor.a;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
using System.Windows.Media.Effects;
|
||||
|
||||
namespace System.Windows.Media.Extensions.Effects
|
||||
{
|
||||
#if SILVERLIGHT
|
||||
using UIPropertyMetadata = System.Windows.PropertyMetadata ;
|
||||
#endif
|
||||
public class Impasto : ShaderEffect
|
||||
{
|
||||
public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(Impasto), 0);
|
||||
public static readonly DependencyProperty TexelWidthProperty = DependencyProperty.Register("TexelWidth", typeof(double), typeof(Impasto), new UIPropertyMetadata(512d, PixelShaderConstantCallback(0)));
|
||||
public static readonly DependencyProperty TexelHeightProperty = DependencyProperty.Register("TexelHeight", typeof(double), typeof(Impasto), new UIPropertyMetadata(512d, PixelShaderConstantCallback(1)));
|
||||
public static readonly DependencyProperty StrengthProperty = DependencyProperty.Register("Strength", typeof(double), typeof(Impasto), new UIPropertyMetadata(15d, PixelShaderConstantCallback(2)));
|
||||
public static readonly DependencyProperty DepthProperty = DependencyProperty.Register("Depth", typeof(double), typeof(Impasto), new UIPropertyMetadata(100d, PixelShaderConstantCallback(3)));
|
||||
|
||||
private readonly static PixelShader pixelShader;
|
||||
|
||||
static Impasto()
|
||||
{
|
||||
pixelShader = PixelShaderUtility.LoadPixelShader<Impasto>();
|
||||
}
|
||||
public Impasto()
|
||||
{
|
||||
this.PixelShader = pixelShader;
|
||||
|
||||
UpdateShaderValue(InputProperty);
|
||||
UpdateShaderValue(TexelWidthProperty);
|
||||
UpdateShaderValue(TexelHeightProperty);
|
||||
UpdateShaderValue(StrengthProperty);
|
||||
UpdateShaderValue(DepthProperty);
|
||||
}
|
||||
|
||||
[System.ComponentModel.BrowsableAttribute(false)]
|
||||
public Brush Input
|
||||
{
|
||||
get { return (Brush)GetValue(InputProperty); }
|
||||
set { SetValue(InputProperty, value); }
|
||||
}
|
||||
|
||||
public double TexelWidth
|
||||
{
|
||||
get { return (double)GetValue(TexelWidthProperty); }
|
||||
set { SetValue(TexelWidthProperty, value); }
|
||||
}
|
||||
|
||||
public double TexelHeight
|
||||
{
|
||||
get { return (double)GetValue(TexelHeightProperty); }
|
||||
set { SetValue(TexelHeightProperty, value); }
|
||||
}
|
||||
|
||||
public double Strength
|
||||
{
|
||||
get { return (double)GetValue(StrengthProperty); }
|
||||
set { SetValue(StrengthProperty, value); }
|
||||
}
|
||||
|
||||
public double Depth
|
||||
{
|
||||
get { return (double)GetValue(DepthProperty); }
|
||||
set { SetValue(DepthProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//-----------------------------------------------------------------------------------------
|
||||
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
|
||||
//-----------------------------------------------------------------------------------------
|
||||
|
||||
float textureWidth : register(C0);
|
||||
float textureHeight : register(C1);
|
||||
float strength : register(C2);
|
||||
float depth : register(C3);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Sampler Inputs (Brushes, including ImplicitInput)
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
sampler2D implicitInputSampler : register(S0);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Pixel Shader
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
float4 lightDirection = float4(0.5, 0.21, 0.85, 1);
|
||||
|
||||
float emboss(float2 uv)
|
||||
{
|
||||
float4 outC = { 0.5, 0.5, 0.5, 1.0 };
|
||||
|
||||
outC -= tex2D(implicitInputSampler, uv - (1 / depth)) * strength;
|
||||
outC += tex2D(implicitInputSampler, uv + (1 / depth)) * strength;
|
||||
outC.rgb = (outC.r + outC.g + outC.b) / 3.0f;
|
||||
|
||||
return outC;
|
||||
}
|
||||
|
||||
float4 main(float2 uv : TEXCOORD) : COLOR
|
||||
{
|
||||
// Sample the color
|
||||
float4 color = tex2D(implicitInputSampler, uv);
|
||||
float2 resolution = float2(textureWidth, textureHeight);
|
||||
|
||||
// Use the emboss value of the color as the bump value
|
||||
float bumpValue = emboss(uv);
|
||||
|
||||
// Calculate a basic normal based on the bump value
|
||||
float2 offset = 1.0 / resolution;
|
||||
float left = emboss(uv - float2(offset.x, 0));
|
||||
float right = emboss(uv + float2(offset.x, 0));
|
||||
float top = emboss(uv - float2(0, offset.y));
|
||||
float bottom = emboss(uv + float2(0, offset.y));
|
||||
|
||||
float3 normal;
|
||||
normal.x = (left - right) * 0.5; // Multiplier to increase the effect, tweak as necessary
|
||||
normal.y = (top - bottom) * 0.5; // Same as above
|
||||
normal.z = 0.5;
|
||||
normal = normalize(normal);
|
||||
|
||||
// Compute Lambertian lighting based on the normal
|
||||
float lightIntensity = dot(normal, lightDirection.xyz);
|
||||
float4 invertedColor = 1 - color;
|
||||
invertedColor.a = 1;
|
||||
|
||||
color.rgb = (invertedColor.rgb * lightIntensity * 2) + color.rgb;
|
||||
return color;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,50 @@
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace System.Windows.Media.Extensions.Effects
|
||||
{
|
||||
#if SILVERLIGHT
|
||||
using UIPropertyMetadata = System.Windows.PropertyMetadata ;
|
||||
#endif
|
||||
public class Noise : ShaderEffect
|
||||
{
|
||||
public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(Noise), 0);
|
||||
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof(double), typeof(Noise), new UIPropertyMetadata(1d, PixelShaderConstantCallback(0)));
|
||||
public static readonly DependencyProperty StrengthProperty = DependencyProperty.Register("Strength", typeof(double), typeof(Noise), new UIPropertyMetadata(0.01d, PixelShaderConstantCallback(1)));
|
||||
|
||||
private readonly static PixelShader pixelShader = new PixelShader();
|
||||
|
||||
static Noise()
|
||||
{
|
||||
pixelShader = PixelShaderUtility.LoadPixelShader<Noise>();
|
||||
}
|
||||
|
||||
public Noise()
|
||||
{
|
||||
this.PixelShader = pixelShader;
|
||||
|
||||
// Update each DependencyProperty that's registered with a shader register. This
|
||||
// is needed to ensure the shader gets sent the proper default value.
|
||||
UpdateShaderValue(InputProperty);
|
||||
UpdateShaderValue(TimeProperty);
|
||||
UpdateShaderValue(StrengthProperty);
|
||||
}
|
||||
|
||||
[System.ComponentModel.BrowsableAttribute(false)]
|
||||
public Brush Input
|
||||
{
|
||||
get { return (Brush)GetValue(InputProperty); }
|
||||
set { SetValue(InputProperty, value); }
|
||||
}
|
||||
public double Time
|
||||
{
|
||||
get { return (double)GetValue(TimeProperty); }
|
||||
set { SetValue(TimeProperty, value); }
|
||||
}
|
||||
public double Strength
|
||||
{
|
||||
get { return (double)GetValue(StrengthProperty); }
|
||||
set { SetValue(StrengthProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//-----------------------------------------------------------------------------------------
|
||||
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
|
||||
//-----------------------------------------------------------------------------------------
|
||||
|
||||
float Time : register(C0);
|
||||
float Strength : register(C1);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Sampler Inputs (Brushes, including ImplicitInput)
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
sampler2D implicitInputSampler : register(S0);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Cellular Noise
|
||||
//--------------------------------------------------------------------------------------
|
||||
//
|
||||
// Description : Array and textureless GLSL 2D/3D/4D simplex
|
||||
// noise functions.
|
||||
// Author : Ian McEwan, Ashima Arts.
|
||||
// Maintainer : stegu
|
||||
// Lastmod : 20201014 (stegu)
|
||||
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
|
||||
// Distributed under the MIT License. See LICENSE file.
|
||||
// https://github.com/ashima/webgl-noise
|
||||
// https://github.com/stegu/webgl-noise
|
||||
//
|
||||
|
||||
//
|
||||
// Description : Array and textureless GLSL 2D simplex noise function.
|
||||
// Author : Ian McEwan, Ashima Arts.
|
||||
// Maintainer : stegu
|
||||
// Lastmod : 20110822 (ijm)
|
||||
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
|
||||
// Distributed under the MIT License. See LICENSE file.
|
||||
// https://github.com/ashima/webgl-noise
|
||||
// https://github.com/stegu/webgl-noise
|
||||
//
|
||||
|
||||
float3 mod289(float3 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
float2 mod289(float2 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
float3 permute(float3 x) {
|
||||
return mod289(((x*34.0)+10.0)*x);
|
||||
}
|
||||
|
||||
float snoise(float2 v)
|
||||
{
|
||||
const float4 C = float4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
|
||||
0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
|
||||
-0.577350269189626, // -1.0 + 2.0 * C.x
|
||||
0.024390243902439); // 1.0 / 41.0
|
||||
// First corner
|
||||
float2 i = floor(v + dot(v, C.yy) );
|
||||
float2 x0 = v - i + dot(i, C.xx);
|
||||
|
||||
// Other corners
|
||||
float2 i1;
|
||||
//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
|
||||
//i1.y = 1.0 - i1.x;
|
||||
i1 = (x0.x > x0.y) ? float2(1.0, 0.0) : float2(0.0, 1.0);
|
||||
// x0 = x0 - 0.0 + 0.0 * C.xx ;
|
||||
// x1 = x0 - i1 + 1.0 * C.xx ;
|
||||
// x2 = x0 - 1.0 + 2.0 * C.xx ;
|
||||
float4 x12 = x0.xyxy + C.xxzz;
|
||||
x12.xy -= i1;
|
||||
|
||||
// Permutations
|
||||
i = mod289(i); // Avoid truncation effects in permutation
|
||||
float3 p = permute( permute( i.y + float3(0.0, i1.y, 1.0 ))
|
||||
+ i.x + float3(0.0, i1.x, 1.0 ));
|
||||
|
||||
float3 m = max(0.5 - float3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
||||
m = m*m ;
|
||||
m = m*m ;
|
||||
|
||||
// Gradients: 41 points uniformly over a line, mapped onto a diamond.
|
||||
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
|
||||
|
||||
float3 x = 2.0 * frac(p * C.www) - 1.0;
|
||||
float3 h = abs(x) - 0.5;
|
||||
float3 ox = floor(x + 0.5);
|
||||
float3 a0 = x - ox;
|
||||
|
||||
// Normalise gradients implicitly by scaling m
|
||||
// Approximation of: m *= inversesqrt( a0*a0 + h*h );
|
||||
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
|
||||
|
||||
// Compute final noise value at P
|
||||
float3 g;
|
||||
g.x = a0.x * x0.x + h.x * x0.y;
|
||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||
return 130.0 * dot(m, g);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Pixel Shader
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
float4 main(float2 uv : TEXCOORD) : COLOR
|
||||
{
|
||||
// Compute the noise value at the pixel position and time.
|
||||
float noise = snoise(uv * Time);
|
||||
|
||||
// Offset the UV coordinates by the noise value scaled by the distortion strength.
|
||||
float2 offset = noise * Strength;
|
||||
|
||||
return tex2D(implicitInputSampler, uv + offset);
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,81 @@
|
||||
// (c) Copyright Microsoft Corporation.
|
||||
// This source is subject to the Microsoft Permissive License.
|
||||
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
|
||||
// All other rights reserved.
|
||||
|
||||
using System.Windows.Media.Effects;
|
||||
|
||||
namespace System.Windows.Media.Extensions.Effects
|
||||
{
|
||||
#if SILVERLIGHT
|
||||
using UIPropertyMetadata = System.Windows.PropertyMetadata ;
|
||||
#endif
|
||||
public class Snowfall : ShaderEffect
|
||||
{
|
||||
public static readonly DependencyProperty InputProperty =
|
||||
ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(Snowfall), 0);
|
||||
public static readonly DependencyProperty SpeedProperty =
|
||||
DependencyProperty.Register("Speed", typeof(double), typeof(Snowfall), new UIPropertyMetadata(0.25, PixelShaderConstantCallback(0)));
|
||||
public static readonly DependencyProperty WindStrengthProperty =
|
||||
DependencyProperty.Register("WindStrength", typeof(double), typeof(Snowfall), new UIPropertyMetadata(0.2, PixelShaderConstantCallback(1)));
|
||||
public static readonly DependencyProperty FlakeSizeProperty =
|
||||
DependencyProperty.Register("FlakeSize", typeof(double), typeof(Snowfall), new UIPropertyMetadata(300.0, PixelShaderConstantCallback(2)));
|
||||
public static readonly DependencyProperty TimeProperty =
|
||||
DependencyProperty.Register("Time", typeof(double), typeof(Snowfall), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(3)));
|
||||
public static readonly DependencyProperty ThresholdProperty =
|
||||
DependencyProperty.Register("Threshold", typeof(double), typeof(Snowfall), new UIPropertyMetadata(0.77, PixelShaderConstantCallback(4)));
|
||||
|
||||
private readonly static PixelShader pixelShader = new();
|
||||
|
||||
static Snowfall()
|
||||
{
|
||||
pixelShader = PixelShaderUtility.LoadPixelShader<Snowfall>();
|
||||
}
|
||||
|
||||
public Snowfall()
|
||||
{
|
||||
this.PixelShader = pixelShader;
|
||||
|
||||
// Update each DependencyProperty that's registered with a shader register. This
|
||||
// is needed to ensure the shader gets sent the proper default value.
|
||||
UpdateShaderValue(InputProperty);
|
||||
UpdateShaderValue(SpeedProperty);
|
||||
UpdateShaderValue(WindStrengthProperty);
|
||||
UpdateShaderValue(FlakeSizeProperty);
|
||||
UpdateShaderValue(TimeProperty);
|
||||
UpdateShaderValue(ThresholdProperty);
|
||||
}
|
||||
|
||||
[System.ComponentModel.BrowsableAttribute(false)]
|
||||
public Brush Input
|
||||
{
|
||||
get { return (Brush)GetValue(InputProperty); }
|
||||
set { SetValue(InputProperty, value); }
|
||||
}
|
||||
public double Speed
|
||||
{
|
||||
get { return (double)GetValue(SpeedProperty); }
|
||||
set { SetValue(SpeedProperty, value); }
|
||||
}
|
||||
public double WindStrength
|
||||
{
|
||||
get { return (double)GetValue(WindStrengthProperty); }
|
||||
set { SetValue(WindStrengthProperty, value); }
|
||||
}
|
||||
public double FlakeSize
|
||||
{
|
||||
get { return (double)GetValue(FlakeSizeProperty); }
|
||||
set { SetValue(FlakeSizeProperty, value); }
|
||||
}
|
||||
public double Time
|
||||
{
|
||||
get { return (double)GetValue(TimeProperty); }
|
||||
set { SetValue(TimeProperty, value); }
|
||||
}
|
||||
public double Threshold
|
||||
{
|
||||
get { return (double)GetValue(ThresholdProperty); }
|
||||
set { SetValue(ThresholdProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
//-----------------------------------------------------------------------------------------
|
||||
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
|
||||
//-----------------------------------------------------------------------------------------
|
||||
|
||||
float Speed : register(C0);
|
||||
float WindStrength : register(C1);
|
||||
float FlakeSize : register(C2);
|
||||
float Time : register(C3);
|
||||
float Threshold : register(C4);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Sampler Inputs (Brushes, including ImplicitInput)
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
sampler2D implicitInputSampler : register(S0);
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// https://github.com/keijiro/NoiseShader/tree/master/Packages/jp.keijiro.noiseshader/Shader
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
float wglnoise_mod(float x, float y)
|
||||
{
|
||||
return x - y * floor(x / y);
|
||||
}
|
||||
|
||||
float2 wglnoise_mod(float2 x, float2 y)
|
||||
{
|
||||
return x - y * floor(x / y);
|
||||
}
|
||||
|
||||
float3 wglnoise_mod(float3 x, float3 y)
|
||||
{
|
||||
return x - y * floor(x / y);
|
||||
}
|
||||
|
||||
float4 wglnoise_mod(float4 x, float4 y)
|
||||
{
|
||||
return x - y * floor(x / y);
|
||||
}
|
||||
|
||||
float2 wglnoise_fade(float2 t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
float3 wglnoise_fade(float3 t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
float wglnoise_mod289(float x)
|
||||
{
|
||||
return x - floor(x / 289) * 289;
|
||||
}
|
||||
|
||||
float2 wglnoise_mod289(float2 x)
|
||||
{
|
||||
return x - floor(x / 289) * 289;
|
||||
}
|
||||
|
||||
float3 wglnoise_mod289(float3 x)
|
||||
{
|
||||
return x - floor(x / 289) * 289;
|
||||
}
|
||||
|
||||
float4 wglnoise_mod289(float4 x)
|
||||
{
|
||||
return x - floor(x / 289) * 289;
|
||||
}
|
||||
|
||||
float3 wglnoise_permute(float3 x)
|
||||
{
|
||||
return wglnoise_mod289((x * 34 + 1) * x);
|
||||
}
|
||||
|
||||
float4 wglnoise_permute(float4 x)
|
||||
{
|
||||
return wglnoise_mod289((x * 34 + 1) * x);
|
||||
}
|
||||
|
||||
float3 SimplexNoiseGrad(float2 v)
|
||||
{
|
||||
const float C1 = (3 - sqrt(3)) / 6;
|
||||
const float C2 = (sqrt(3) - 1) / 2;
|
||||
|
||||
// First corner
|
||||
float2 i = floor(v + dot(v, C2));
|
||||
float2 x0 = v - i + dot(i, C1);
|
||||
|
||||
// Other corners
|
||||
float2 i1 = x0.x > x0.y ? float2(1, 0) : float2(0, 1);
|
||||
float2 x1 = x0 + C1 - i1;
|
||||
float2 x2 = x0 + C1 * 2 - 1;
|
||||
|
||||
// Permutations
|
||||
i = wglnoise_mod289(i); // Avoid truncation effects in permutation
|
||||
float3 p = wglnoise_permute(i.y + float3(0, i1.y, 1));
|
||||
p = wglnoise_permute(p + i.x + float3(0, i1.x, 1));
|
||||
|
||||
// Gradients: 41 points uniformly over a unit circle.
|
||||
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
|
||||
float3 phi = p / 41 * 3.14159265359 * 2;
|
||||
float2 g0 = float2(cos(phi.x), sin(phi.x));
|
||||
float2 g1 = float2(cos(phi.y), sin(phi.y));
|
||||
float2 g2 = float2(cos(phi.z), sin(phi.z));
|
||||
|
||||
// Compute noise and gradient at P
|
||||
float3 m = float3(dot(x0, x0), dot(x1, x1), dot(x2, x2));
|
||||
float3 px = float3(dot(g0, x0), dot(g1, x1), dot(g2, x2));
|
||||
|
||||
m = max(0.5 - m, 0);
|
||||
float3 m3 = m * m * m;
|
||||
float3 m4 = m * m3;
|
||||
|
||||
float3 temp = -8 * m3 * px;
|
||||
float2 grad = m4.x * g0 + temp.x * x0 +
|
||||
m4.y * g1 + temp.y * x1 +
|
||||
m4.z * g2 + temp.z * x2;
|
||||
|
||||
return 99.2 * float3(grad, dot(m4, px));
|
||||
}
|
||||
|
||||
float SimplexNoise(float2 v)
|
||||
{
|
||||
return SimplexNoiseGrad(v).z;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Pixel Shader
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
float4 main(float2 uv : TEXCOORD) : COLOR
|
||||
{
|
||||
float4 color = tex2D(implicitInputSampler, uv);
|
||||
|
||||
// Calculate a parallax offset based on depth. Here, we just use the Y coordinate
|
||||
uv.y -= Time * Speed;
|
||||
uv.x += WindStrength * Time;
|
||||
|
||||
// Get the random value to decide where to place a snowflake
|
||||
float randVal = (SimplexNoise(uv * FlakeSize) + 1) / 2;
|
||||
//float randVal = (snoise(floor(tiledUV)) + 1) / 2;
|
||||
|
||||
// Generate a dynamic circular snowflake if within the threshold
|
||||
if (randVal > Threshold)
|
||||
{
|
||||
color.rgb = lerp(color.rgb, float3(1, 1, 1), 0.5 + (randVal - Threshold) / (1 - Threshold));
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,84 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Slim;
|
||||
using System.Extensions;
|
||||
using System.Net.Http;
|
||||
using System.Windows.Extensions.Http;
|
||||
using WpfExtended.Logging;
|
||||
|
||||
namespace System.Windows.Extensions
|
||||
{
|
||||
public static class ServiceManagerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a <see cref="ILogsWriter"/> with the default <see cref="CVLoggerProvider"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TLogsWriter">Implementation of <see cref="ILogsWriter"/>.</typeparam>
|
||||
/// <param name="serviceManager"><see cref="ServiceManager"/>.</param>
|
||||
/// <returns>Provided <see cref="ServiceManager"/>.</returns>
|
||||
public static IServiceProducer RegisterLogWriter<TLogsWriter>(this IServiceProducer serviceManager)
|
||||
where TLogsWriter : ILogsWriter
|
||||
{
|
||||
serviceManager.RegisterSingleton<ILogsWriter, TLogsWriter>();
|
||||
serviceManager.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
|
||||
{
|
||||
var factory = new LoggerFactory();
|
||||
factory.AddProvider(new CVLoggerProvider(sp.GetService<ILogsWriter>()));
|
||||
return factory;
|
||||
});
|
||||
|
||||
return serviceManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a <see cref="ILogsWriter"/> with the default <see cref="CVLoggerProvider"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TILogsWriter">Interface of <see cref="ILogsWriter"/>.</typeparam>
|
||||
/// <typeparam name="TLogsWriter">Implementation of <see cref="ILogsWriter"/>.</typeparam>
|
||||
/// <param name="serviceManager"><see cref="ServiceManager"/>.</param>
|
||||
/// <returns>Provided <see cref="ServiceManager"/>.</returns>
|
||||
public static IServiceProducer RegisterLogWriter<TILogsWriter, TLogsWriter>(this IServiceProducer serviceManager)
|
||||
where TLogsWriter : TILogsWriter
|
||||
where TILogsWriter : class, ILogsWriter
|
||||
{
|
||||
serviceManager.RegisterSingleton<TILogsWriter, TLogsWriter>();
|
||||
serviceManager.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
|
||||
{
|
||||
var factory = new LoggerFactory();
|
||||
factory.AddProvider(new CVLoggerProvider(sp.GetService<TILogsWriter>()));
|
||||
return factory;
|
||||
});
|
||||
|
||||
return serviceManager;
|
||||
}
|
||||
|
||||
public static IServiceManager RegisterLoggerFactory(this IServiceManager serviceManager, Func<Slim.IServiceProvider, ILoggerFactory> loggerFactory)
|
||||
{
|
||||
serviceManager.RegisterSingleton<ILoggerFactory, ILoggerFactory>(loggerFactory);
|
||||
return serviceManager;
|
||||
}
|
||||
|
||||
public static IServiceManager RegisterDebugLoggerFactory(this IServiceManager serviceManager)
|
||||
{
|
||||
serviceManager.RegisterLogWriter<ILogsWriter, DebugLogsWriter>();
|
||||
return serviceManager;
|
||||
}
|
||||
|
||||
public static IServiceManager RegisterHttpFactory(this IServiceManager serviceManager)
|
||||
{
|
||||
serviceManager.ThrowIfNull(nameof(serviceManager));
|
||||
|
||||
serviceManager.RegisterResolver(new HttpClientResolver());
|
||||
return serviceManager;
|
||||
}
|
||||
|
||||
public static IServiceManager RegisterHttpFactory(this IServiceManager serviceManager, Func<Slim.IServiceProvider, Type, HttpMessageHandler> handlerFactory)
|
||||
{
|
||||
serviceManager.ThrowIfNull(nameof(serviceManager));
|
||||
|
||||
serviceManager.RegisterResolver(
|
||||
new HttpClientResolver()
|
||||
.WithHttpMessageHandlerFactory(handlerFactory));
|
||||
return serviceManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Slim.Resolvers;
|
||||
using System.Extensions;
|
||||
using System.Http;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace System.Windows.Extensions.Http
|
||||
{
|
||||
public sealed class HttpClientResolver : IDependencyResolver
|
||||
{
|
||||
private static readonly Type clientType = typeof(HttpClient<>);
|
||||
private static readonly Type loggerType = typeof(ILogger<>);
|
||||
|
||||
/// <summary>
|
||||
/// Factory method. <see cref="Type"/> parameter of the factory is the scope of <see cref="IHttpClient{TScope}"/>.
|
||||
/// </summary>
|
||||
public Func<Slim.IServiceProvider, Type, HttpMessageHandler> HttpMessageHandlerFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="HttpMessageHandlerFactory"/>.
|
||||
/// </summary>
|
||||
/// <param name="factory">Factory method. <see cref="Type"/> parameter of the factory is the scope of <see cref="IHttpClient{TScope}"/>.</param>
|
||||
/// <returns></returns>
|
||||
public HttpClientResolver WithHttpMessageHandlerFactory(Func<Slim.IServiceProvider, Type, HttpMessageHandler> factory)
|
||||
{
|
||||
this.HttpMessageHandlerFactory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool CanResolve(Type type)
|
||||
{
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IHttpClient<>))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||
{
|
||||
var typedClientType = clientType.MakeGenericType(type.GetGenericArguments());
|
||||
var typedLoggerType = loggerType.MakeGenericType(type.GetGenericArguments());
|
||||
var logger = serviceProvider.GetService(typedLoggerType).As<ILogger>();
|
||||
var handler = this.HttpMessageHandlerFactory?.Invoke(serviceProvider, type.GetGenericArguments().First());
|
||||
var httpClient = handler is null ?
|
||||
Activator.CreateInstance(typedClientType) :
|
||||
Activator.CreateInstance(typedClientType, new object[] { handler });
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Debug;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Slim;
|
||||
using Slim.Integration.ServiceCollection;
|
||||
using System.Extensions;
|
||||
using System.Logging;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Extensions.Logging;
|
||||
using System.Windows.Extensions.Services;
|
||||
|
||||
namespace System.Windows.Extensions
|
||||
{
|
||||
@@ -14,36 +15,44 @@ namespace System.Windows.Extensions
|
||||
public abstract class ExtendedApplication<T> : Application
|
||||
where T : Window
|
||||
{
|
||||
protected IServiceManager ServiceManager { get; } = new ServiceManager();
|
||||
protected ILoggerFactory LoggerFactory { get; private set; }
|
||||
private readonly ServiceCollection services = new();
|
||||
|
||||
protected IServiceProvider ServiceProvider { get; private set; }
|
||||
protected virtual bool ShowWindowOnStartup { get; set; } = true;
|
||||
|
||||
public ExtendedApplication()
|
||||
{
|
||||
this.RegisterInternals();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do some work on the <see cref="IServiceManager"/> before calling <see cref="RegisterServices(IServiceProducer)"/>.
|
||||
/// Create a new <see cref="IServiceProvider"/>.
|
||||
/// </summary>
|
||||
protected virtual void SetupServiceManager(IServiceManager serviceManager)
|
||||
protected virtual IServiceProvider SetupServiceProvider(IServiceCollection services)
|
||||
{
|
||||
serviceManager.RegisterDebugLoggerFactory();
|
||||
serviceManager.RegisterHttpFactory();
|
||||
var serviceProvider = services.BuildSlimServiceProvider();
|
||||
if (serviceProvider is IServiceManager serviceManager)
|
||||
{
|
||||
serviceManager.RegisterDebugLoggerFactory();
|
||||
serviceManager.RegisterHttpFactory();
|
||||
serviceManager.RegisterResolver(new LoggerResolver());
|
||||
}
|
||||
|
||||
return serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called right before the window is shown.
|
||||
/// </summary>
|
||||
protected abstract void ApplicationStarting();
|
||||
protected abstract ValueTask ApplicationStarting();
|
||||
/// <summary>
|
||||
/// Called right before the application is closing.
|
||||
/// </summary>
|
||||
protected abstract void ApplicationClosing();
|
||||
/// <summary>
|
||||
/// Register services into the <see cref="IServiceProducer"/>.
|
||||
/// Register services into the <see cref="IServiceCollection"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceProducer"></param>
|
||||
protected abstract void RegisterServices(IServiceProducer serviceProducer);
|
||||
/// <param name="serviceCollection"></param>
|
||||
protected abstract void RegisterServices(IServiceCollection services);
|
||||
/// <summary>
|
||||
/// Handle a caught exception.
|
||||
/// </summary>
|
||||
@@ -54,14 +63,20 @@ namespace System.Windows.Extensions
|
||||
protected sealed override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
this.SetupExceptionHandling();
|
||||
this.SetupServiceManager(this.ServiceManager);
|
||||
this.RegisterServices(this.ServiceManager);
|
||||
this.RegisterInternals();
|
||||
this.RegisterServices(this.services);
|
||||
this.ServiceProvider = this.SetupServiceProvider(this.services);
|
||||
this.SetupApplicationLifetime();
|
||||
this.LaunchWindow();
|
||||
}
|
||||
protected sealed override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
base.OnExit(e);
|
||||
foreach (var service in this.ServiceProvider.GetServices<IApplicationLifetimeService>())
|
||||
{
|
||||
service?.OnClosing();
|
||||
}
|
||||
|
||||
this.ApplicationClosing();
|
||||
}
|
||||
protected sealed override void OnSessionEnding(SessionEndingCancelEventArgs e)
|
||||
@@ -70,17 +85,23 @@ namespace System.Windows.Extensions
|
||||
this.ApplicationClosing();
|
||||
}
|
||||
|
||||
private void LaunchWindow()
|
||||
private async void LaunchWindow()
|
||||
{
|
||||
var window = this.ServiceManager.GetService<T>();
|
||||
this.ApplicationStarting();
|
||||
window.Show();
|
||||
await this.ApplicationStarting().ConfigureAwait(true);
|
||||
foreach(var service in this.ServiceProvider.GetServices<IApplicationLifetimeService>())
|
||||
{
|
||||
service.OnStartup();
|
||||
}
|
||||
|
||||
if (this.ShowWindowOnStartup)
|
||||
{
|
||||
var window = this.ServiceProvider.GetRequiredService<T>();
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
private void RegisterInternals()
|
||||
{
|
||||
this.ServiceManager.RegisterServiceManager();
|
||||
this.ServiceManager.RegisterSingleton<T, T>();
|
||||
this.ServiceManager.RegisterResolver(new LoggerResolver());
|
||||
this.services.AddSingleton<T, T>();
|
||||
}
|
||||
private void SetupExceptionHandling()
|
||||
{
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
Copyright © 2021 Alexandru Macocian
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,46 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Extensions;
|
||||
using WpfExtended.Models;
|
||||
|
||||
namespace WpfExtended.Logging
|
||||
{
|
||||
public sealed class CVLogger : ILogger
|
||||
{
|
||||
private readonly string category;
|
||||
private readonly CVLoggerProvider cvLoggerProvider;
|
||||
|
||||
public CVLogger(string category, CVLoggerProvider debugLoggerProvider)
|
||||
{
|
||||
this.category = category;
|
||||
this.cvLoggerProvider = debugLoggerProvider.ThrowIfNull(nameof(debugLoggerProvider));
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
var message = formatter(state, exception);
|
||||
|
||||
var log = new Log
|
||||
{
|
||||
Exception = exception,
|
||||
LogLevel = logLevel,
|
||||
EventId = eventId.Name,
|
||||
Message = message,
|
||||
Category = category,
|
||||
LogTime = DateTime.Now
|
||||
};
|
||||
|
||||
this.cvLoggerProvider.LogEntry(log);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using Microsoft.CorrelationVector;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Extensions;
|
||||
using WpfExtended.Models;
|
||||
|
||||
namespace WpfExtended.Logging
|
||||
{
|
||||
public sealed class CVLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly ILogsWriter logsManager;
|
||||
private CorrelationVector correlationVector;
|
||||
|
||||
public CVLoggerProvider(ILogsWriter logsWriter)
|
||||
{
|
||||
this.logsManager = logsWriter.ThrowIfNull(nameof(logsWriter));
|
||||
this.correlationVector = new CorrelationVector();
|
||||
}
|
||||
|
||||
public void LogEntry(Log log)
|
||||
{
|
||||
if (this.correlationVector is not null)
|
||||
{
|
||||
log.CorrelationVector = this.correlationVector.Value.ToString();
|
||||
this.correlationVector.Increment();
|
||||
}
|
||||
|
||||
this.logsManager.WriteLog(log);
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
if (this.correlationVector is not null)
|
||||
{
|
||||
this.correlationVector = CorrelationVector.Extend(this.correlationVector.ToString());
|
||||
}
|
||||
|
||||
return new CVLogger(categoryName, this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using WpfExtended.Models;
|
||||
|
||||
namespace WpfExtended.Logging
|
||||
{
|
||||
public sealed class DebugLogsWriter : ILogsWriter
|
||||
{
|
||||
public void WriteLog(Log log)
|
||||
{
|
||||
Debug.WriteLine($"{log.LogTime} - {log.Category} - {log.EventId} - {log.CorrelationVector} - {log.LogLevel} - {log.Message}{Environment.NewLine}{log.Exception}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using WpfExtended.Models;
|
||||
|
||||
namespace WpfExtended.Logging
|
||||
{
|
||||
public interface ILogsWriter
|
||||
{
|
||||
void WriteLog(Log log);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Slim.Resolvers;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Windows.Extensions.Logging
|
||||
{
|
||||
public sealed class LoggerResolver : IDependencyResolver
|
||||
{
|
||||
public bool CanResolve(Type type)
|
||||
{
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILogger<>) ||
|
||||
type == typeof(ILogger))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||
{
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILogger<>))
|
||||
{
|
||||
return ResolveScopedLogger(serviceProvider, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResolveLogger(serviceProvider, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static object ResolveScopedLogger(Slim.IServiceProvider serviceProvider, Type type)
|
||||
{
|
||||
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
|
||||
var categoryTypes = type.GetGenericArguments();
|
||||
var createLoggerMethod = typeof(LoggerFactoryExtensions).GetMethods().Where(m => m.IsGenericMethodDefinition && m.Name == nameof(LoggerFactoryExtensions.CreateLogger)).First();
|
||||
return createLoggerMethod.MakeGenericMethod(categoryTypes.First()).Invoke(null, new object[] { loggerFactory });
|
||||
}
|
||||
|
||||
private static object ResolveLogger(Slim.IServiceProvider serviceProvider, Type type)
|
||||
{
|
||||
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
|
||||
return loggerFactory.CreateLogger(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace WpfExtended.Models
|
||||
{
|
||||
public sealed record Log
|
||||
{
|
||||
public Exception Exception { get; set; }
|
||||
public DateTime LogTime { get; set; }
|
||||
public string Category { get; set; }
|
||||
public string EventId { get; set; }
|
||||
public LogLevel LogLevel { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string CorrelationVector { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace System.Windows.Extensions.Services
|
||||
{
|
||||
public interface IApplicationLifetimeService
|
||||
{
|
||||
void OnStartup();
|
||||
void OnClosing();
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,29 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ShaderDir>$(ProjectDir)</ShaderDir>
|
||||
<Fxc>$(SolutionDir)\tools\fxc.exe</Fxc>
|
||||
<!-- Use MSBuildProjectDirectory to get a more reliable path to the solution root -->
|
||||
<SolutionRootDir Condition="'$(SolutionDir)' == '' OR '$(SolutionDir)' == '*Undefined*'">$(MSBuildProjectDirectory)\..</SolutionRootDir>
|
||||
<SolutionRootDir Condition="'$(SolutionDir)' != '' AND '$(SolutionDir)' != '*Undefined*'">$(SolutionDir)</SolutionRootDir>
|
||||
<Fxc>$(SolutionRootDir)\tools\fxc.exe</Fxc>
|
||||
<FxcArgs>/O3 /T ps_3_0 /nologo /Fo</FxcArgs>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Message Importance="High" Text="Compiling shader effect files..." />
|
||||
<ItemGroup>
|
||||
<!-- Check if fxc.exe exists before attempting compilation -->
|
||||
<PropertyGroup>
|
||||
<FxcExists Condition="Exists('$(Fxc)')">true</FxcExists>
|
||||
</PropertyGroup>
|
||||
|
||||
<Message Importance="High" Text="Looking for fxc.exe at: $(Fxc)" />
|
||||
<Message Condition="'$(FxcExists)' == 'true'" Importance="High" Text="Compiling shader effect files..." />
|
||||
<Message Condition="'$(FxcExists)' != 'true'" Importance="High" Text="fxc.exe not found at $(Fxc), using pre-compiled shaders..." />
|
||||
|
||||
<ItemGroup Condition="'$(FxcExists)' == 'true'">
|
||||
<_ShaderFiles Include="$(ShaderDir)**\*.fx" />
|
||||
</ItemGroup>
|
||||
<Exec Command=""$(Fxc)" $(FxcArgs) "$([System.IO.Path]::ChangeExtension('%(_ShaderFiles.Identity)', '.ps'))" "%(_ShaderFiles.Identity)"" />
|
||||
|
||||
<Exec Condition="'$(FxcExists)' == 'true'" Command=""$(Fxc)" $(FxcArgs) "$([System.IO.Path]::ChangeExtension('%(_ShaderFiles.Identity)', '.ps'))" "%(_ShaderFiles.Identity)"" />
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="$(ShaderDir)**\*.ps" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net5.0-windows;netcoreapp3.1</TargetFrameworks>
|
||||
<TargetFrameworks>net10.0-windows</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<PackageLicenseFile>License.txt</PackageLicenseFile>
|
||||
<Version>0.4.2</Version>
|
||||
<Version>0.9.0</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Description>Extension library for Windows Presentation Platform.
|
||||
|
||||
@@ -24,10 +22,12 @@ http://www.java2s.com/Open-Source/CSharp_Free_Code/Windows_Presentation_Foundati
|
||||
<None Remove="Effects\ColorKeyAlpha\ColorKeyAlpha.ps" />
|
||||
<None Remove="Effects\ColorTone\ColorTone.ps" />
|
||||
<None Remove="Effects\ContrastAdjust\ContrastAdjust.ps" />
|
||||
<None Remove="Effects\DepthBlur\DepthBlur.ps" />
|
||||
<None Remove="Effects\DirectionalBlur\DirectionalBlur.ps" />
|
||||
<None Remove="Effects\Embossed\Embossed.ps" />
|
||||
<None Remove="Effects\Gloom\Gloom.ps" />
|
||||
<None Remove="Effects\GrowablePoissonDisk\GrowablePoissonDisk.ps" />
|
||||
<None Remove="Effects\Impasto\Impasto.ps" />
|
||||
<None Remove="Effects\InvertColor\InvertColor.ps" />
|
||||
<None Remove="Effects\Kuwahara\Kuwahara.ps" />
|
||||
<None Remove="Effects\LightStreak\LightStreak.ps" />
|
||||
@@ -35,11 +35,13 @@ http://www.java2s.com/Open-Source/CSharp_Free_Code/Windows_Presentation_Foundati
|
||||
<None Remove="Effects\Monochrome\Monochrome.ps" />
|
||||
<None Remove="Effects\MotionBlur\MotionBlur.ps" />
|
||||
<None Remove="Effects\Negative\Negative.ps" />
|
||||
<None Remove="Effects\Noise\Noise.ps" />
|
||||
<None Remove="Effects\Pinch\Pinch.ps" />
|
||||
<None Remove="Effects\Pixelate\Pixelate.ps" />
|
||||
<None Remove="Effects\Ripple\Ripple.ps" />
|
||||
<None Remove="Effects\Sharpen\Sharpen.ps" />
|
||||
<None Remove="Effects\SmoothMagnify\SmoothMagnify.ps" />
|
||||
<None Remove="Effects\Snowfall\Snowfall.ps" />
|
||||
<None Remove="Effects\Swirl\Swirl.ps" />
|
||||
<None Remove="Effects\ToneMapping\ToneMapping.ps" />
|
||||
<None Remove="Effects\ToonShader\ToonShader.ps" />
|
||||
@@ -48,7 +50,11 @@ http://www.java2s.com/Open-Source/CSharp_Free_Code/Windows_Presentation_Foundati
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Effects\BandedSwirl\BandedSwirl.ps" />
|
||||
<EmbeddedResource Include="Effects\Snowfall\Snowfall.ps" />
|
||||
<EmbeddedResource Include="Effects\Bloom\Bloom.ps" />
|
||||
<EmbeddedResource Include="Effects\DepthBlur\DepthBlur.ps" />
|
||||
<EmbeddedResource Include="Effects\Impasto\Impasto.ps" />
|
||||
<EmbeddedResource Include="Effects\Noise\Noise.ps" />
|
||||
<EmbeddedResource Include="Effects\BrightExtract\BrightExtract.ps" />
|
||||
<EmbeddedResource Include="Effects\ColorKeyAlpha\ColorKeyAlpha.ps" />
|
||||
<EmbeddedResource Include="Effects\ColorTone\ColorTone.ps" />
|
||||
@@ -76,18 +82,16 @@ http://www.java2s.com/Open-Source/CSharp_Free_Code/Windows_Presentation_Foundati
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="License.txt">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.1" />
|
||||
<PackageReference Include="Slim" Version="1.11.2" />
|
||||
<PackageReference Include="Slim.Integration" Version="1.0.4" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.12" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.6.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="5.0.0" />
|
||||
<PackageReference Include="Slim" Version="1.4.3" />
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
<Import Project="ShaderCompiler.target" />
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user