diff --git a/.editorconfig b/.editorconfig
index 128b6f1712..179491eece 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -134,6 +134,9 @@ csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
space_within_single_line_array_initializer_braces = true
+#Net Analyzer
+dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
+
# Wrapping preferences
csharp_wrap_before_ternary_opsigns = false
diff --git a/Avalonia.sln b/Avalonia.sln
index 81a9b43890..c000f56d09 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -90,6 +90,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
build\ApiDiff.props = build\ApiDiff.props
+ build\AvaloniaPublicKey.props = build\AvaloniaPublicKey.props
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\CoreLibraries.props = build\CoreLibraries.props
@@ -102,8 +103,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props
+ build\NetAnalyzers.props = build\NetAnalyzers.props
build\NetCore.props = build\NetCore.props
build\NetFX.props = build\NetFX.props
+ build\NullableEnable.props = build\NullableEnable.props
build\ReactiveUI.props = build\ReactiveUI.props
build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
build\Rx.props = build\Rx.props
@@ -198,8 +201,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}"
@@ -216,15 +217,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Blazor.Web\ControlCatalog.Blazor.Web.csproj", "{6A710364-AE6D-40BD-968B-024311527AC2}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{8B3E8405-DE18-4048-A459-9CA4AC3319A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -480,10 +483,6 @@ Global
{25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.Build.0 = Release|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -516,10 +515,6 @@ Global
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU
- {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.Build.0 = Release|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -536,6 +531,14 @@ Global
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -579,6 +582,7 @@ Global
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@@ -586,7 +590,6 @@ Global
{676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
- {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@@ -594,12 +597,12 @@ Global
{EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
- {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
- {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {6A710364-AE6D-40BD-968B-024311527AC2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {8B3E8405-DE18-4048-A459-9CA4AC3319A2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/build/NetAnalyzers.props b/build/NetAnalyzers.props
new file mode 100644
index 0000000000..dfca9ecf9e
--- /dev/null
+++ b/build/NetAnalyzers.props
@@ -0,0 +1,5 @@
+
+
+ true
+
+
diff --git a/samples/ControlCatalog.Web/App.razor b/samples/ControlCatalog.Blazor.Web/App.razor
similarity index 100%
rename from samples/ControlCatalog.Web/App.razor
rename to samples/ControlCatalog.Blazor.Web/App.razor
diff --git a/samples/ControlCatalog.Blazor.Web/App.razor.cs b/samples/ControlCatalog.Blazor.Web/App.razor.cs
new file mode 100644
index 0000000000..8cc0095f20
--- /dev/null
+++ b/samples/ControlCatalog.Blazor.Web/App.razor.cs
@@ -0,0 +1,17 @@
+using Avalonia;
+using Avalonia.Web.Blazor;
+
+namespace ControlCatalog.Blazor.Web;
+
+public partial class App
+{
+ protected override void OnParametersSet()
+ {
+ AppBuilder.Configure()
+ .UseBlazor()
+ // .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
+ .SetupWithSingleViewLifetime();
+
+ base.OnParametersSet();
+ }
+}
diff --git a/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj b/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj
new file mode 100644
index 0000000000..03fb31f0d3
--- /dev/null
+++ b/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj
@@ -0,0 +1,29 @@
+
+
+ net7.0
+ browser-wasm
+ enable
+ 16777216
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Web/Pages/Index.razor b/samples/ControlCatalog.Blazor.Web/Pages/Index.razor
similarity index 100%
rename from samples/ControlCatalog.Web/Pages/Index.razor
rename to samples/ControlCatalog.Blazor.Web/Pages/Index.razor
diff --git a/samples/ControlCatalog.Blazor.Web/Program.cs b/samples/ControlCatalog.Blazor.Web/Program.cs
new file mode 100644
index 0000000000..d71b125fa1
--- /dev/null
+++ b/samples/ControlCatalog.Blazor.Web/Program.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using ControlCatalog.Blazor.Web;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ await CreateHostBuilder(args).Build().RunAsync();
+ }
+
+ public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
+ {
+ var builder = WebAssemblyHostBuilder.CreateDefault(args);
+
+ builder.RootComponents.Add("#app");
+
+ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+
+ return builder;
+ }
+}
+
+
+
+
diff --git a/samples/ControlCatalog.Web/Properties/launchSettings.json b/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
similarity index 100%
rename from samples/ControlCatalog.Web/Properties/launchSettings.json
rename to samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
diff --git a/samples/ControlCatalog.Web/Shared/MainLayout.razor b/samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor
similarity index 100%
rename from samples/ControlCatalog.Web/Shared/MainLayout.razor
rename to samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor
diff --git a/samples/ControlCatalog.Web/_Imports.razor b/samples/ControlCatalog.Blazor.Web/_Imports.razor
similarity index 85%
rename from samples/ControlCatalog.Web/_Imports.razor
rename to samples/ControlCatalog.Blazor.Web/_Imports.razor
index 04c7a8690e..0e6d11b419 100644
--- a/samples/ControlCatalog.Web/_Imports.razor
+++ b/samples/ControlCatalog.Blazor.Web/_Imports.razor
@@ -6,6 +6,5 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
-@using ControlCatalog.Web
-@using ControlCatalog.Web.Shared
+@using ControlCatalog.Blazor.Web.Shared
@using SkiaSharp
diff --git a/samples/ControlCatalog.Web/wwwroot/css/app.css b/samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css
similarity index 100%
rename from samples/ControlCatalog.Web/wwwroot/css/app.css
rename to samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css
diff --git a/samples/ControlCatalog.Web/wwwroot/favicon.ico b/samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico
similarity index 100%
rename from samples/ControlCatalog.Web/wwwroot/favicon.ico
rename to samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico
diff --git a/samples/ControlCatalog.Web/wwwroot/index.html b/samples/ControlCatalog.Blazor.Web/wwwroot/index.html
similarity index 100%
rename from samples/ControlCatalog.Web/wwwroot/index.html
rename to samples/ControlCatalog.Blazor.Web/wwwroot/index.html
diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs
deleted file mode 100644
index bcd2a6fefc..0000000000
--- a/samples/ControlCatalog.Web/App.razor.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Avalonia;
-using Avalonia.Web.Blazor;
-
-namespace ControlCatalog.Web;
-
-public partial class App
-{
- protected override void OnParametersSet()
- {
- WebAppBuilder.Configure()
- .AfterSetup(_ =>
- {
- ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
- })
- .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
- .SetupWithSingleViewLifetime();
-
- base.OnParametersSet();
- }
-}
diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
index b2c9ec72eb..0ddec3444b 100644
--- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
+++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
@@ -1,57 +1,45 @@
-
+
- net6.0
- enable
-
- true
- 16777216
- false
- false
+ net7.0
+ browser-wasm
+ main.js
+ Exe
+ true
+ true
+ true
+ -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0
-
-
- false
- -O1
- false
-
-
-
- true
- true
+
+ true
+ true
+ full
+ true
+ true
+ true
-O3
-O3
- false
- false
- false
- false
- false
- false
- true
- false
- true
- true
- true
- link
- true
-
-
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
diff --git a/samples/ControlCatalog.Web/EmbedSample.Browser.cs b/samples/ControlCatalog.Web/EmbedSample.Browser.cs
index 5fe14409de..5cfbb608cc 100644
--- a/samples/ControlCatalog.Web/EmbedSample.Browser.cs
+++ b/samples/ControlCatalog.Web/EmbedSample.Browser.cs
@@ -1,34 +1,42 @@
using System;
-
-using Avalonia;
+using System.Runtime.InteropServices.JavaScript;
using Avalonia.Platform;
-using Avalonia.Web.Blazor;
+using Avalonia.Web;
using ControlCatalog.Pages;
-using Microsoft.JSInterop;
-
namespace ControlCatalog.Web;
public class EmbedSampleWeb : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func createDefault)
{
- var runtime = AvaloniaLocator.Current.GetRequiredService();
-
if (isSecond)
{
- var iframe = runtime.Invoke("document.createElement", "iframe");
- iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70");
+ var iframe = EmbedInterop.CreateElement("iframe");
+ iframe.SetProperty("src", "https://www.youtube.com/embed/kZCIporjJ70");
return new JSObjectControlHandle(iframe);
}
else
{
- // window.createAppButton source is defined in "app.js" file.
- var button = runtime.Invoke("window.createAppButton");
+ var defaultHandle = (JSObjectControlHandle)createDefault();
+
+ _ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ =>
+ {
+ EmbedInterop.AddAppButton(defaultHandle.Object);
+ });
- return new JSObjectControlHandle(button);
+ return defaultHandle;
}
}
}
+
+internal static partial class EmbedInterop
+{
+ [JSImport("globalThis.document.createElement")]
+ public static partial JSObject CreateElement(string tagName);
+
+ [JSImport("addAppButton", "embed.js")]
+ public static partial void AddAppButton(JSObject parentObject);
+}
diff --git a/src/Web/Avalonia.Web.Sample/Logo.svg b/samples/ControlCatalog.Web/Logo.svg
similarity index 100%
rename from src/Web/Avalonia.Web.Sample/Logo.svg
rename to samples/ControlCatalog.Web/Logo.svg
diff --git a/samples/ControlCatalog.Web/Program.cs b/samples/ControlCatalog.Web/Program.cs
index d1a7925813..7d05c8e462 100644
--- a/samples/ControlCatalog.Web/Program.cs
+++ b/samples/ControlCatalog.Web/Program.cs
@@ -1,29 +1,22 @@
-using System;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
-using Microsoft.Extensions.DependencyInjection;
+using System.Runtime.Versioning;
+using Avalonia;
+using Avalonia.Web;
+using ControlCatalog;
using ControlCatalog.Web;
-public class Program
+[assembly:SupportedOSPlatform("browser")]
+
+internal partial class Program
{
- public static async Task Main(string[] args)
+ private static void Main(string[] args)
{
- await CreateHostBuilder(args).Build().RunAsync();
+ BuildAvaloniaApp()
+ .AfterSetup(_ =>
+ {
+ ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
+ }).SetupBrowserApp("out");
}
- public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
- {
- var builder = WebAssemblyHostBuilder.CreateDefault(args);
-
- builder.RootComponents.Add("#app");
-
- builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
-
- return builder;
- }
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure();
}
-
-
-
-
diff --git a/samples/ControlCatalog.Web/Roots.xml b/samples/ControlCatalog.Web/Roots.xml
new file mode 100644
index 0000000000..3c13098159
--- /dev/null
+++ b/samples/ControlCatalog.Web/Roots.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/Web/Avalonia.Web.Sample/app.css b/samples/ControlCatalog.Web/app.css
similarity index 52%
rename from src/Web/Avalonia.Web.Sample/app.css
rename to samples/ControlCatalog.Web/app.css
index 04ea5bee19..27680f6e1a 100644
--- a/src/Web/Avalonia.Web.Sample/app.css
+++ b/samples/ControlCatalog.Web/app.css
@@ -4,12 +4,15 @@
}
#avalonia-splash {
- position: absolute;
+ position: relative;
height: 100%;
width: 100%;
color: whitesmoke;
background: #171C2C;
font-family: 'Nunito', sans-serif;
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
}
#avalonia-splash a{
@@ -24,15 +27,23 @@
}
.splash-close {
- animation: fadeOut 1s forwards;
+ animation: slide 0.5s linear 1s forwards;
}
-@keyframes fadeOut {
- from {
- opacity: 1;
+@keyframes slide {
+ 0% {
+ top: 0%;
}
- to {
+ 50% {
+ opacity: 80%;
+ }
+
+ 100% {
+ top: 100%;
+ overflow: hidden;
opacity: 0;
+ display: none;
+ visibility: collapse;
}
}
diff --git a/src/Web/Avalonia.Web.Sample/embed.js b/samples/ControlCatalog.Web/embed.js
similarity index 100%
rename from src/Web/Avalonia.Web.Sample/embed.js
rename to samples/ControlCatalog.Web/embed.js
diff --git a/src/Web/Avalonia.Web.Sample/favicon.ico b/samples/ControlCatalog.Web/favicon.ico
similarity index 100%
rename from src/Web/Avalonia.Web.Sample/favicon.ico
rename to samples/ControlCatalog.Web/favicon.ico
diff --git a/src/Web/Avalonia.Web.Sample/index.html b/samples/ControlCatalog.Web/index.html
similarity index 95%
rename from src/Web/Avalonia.Web.Sample/index.html
rename to samples/ControlCatalog.Web/index.html
index ee023790fb..226ae70695 100644
--- a/src/Web/Avalonia.Web.Sample/index.html
+++ b/samples/ControlCatalog.Web/index.html
@@ -4,7 +4,7 @@
- Avalonia.Web.Sample
+ AvaloniaUI - ControlCatalog
diff --git a/src/Web/Avalonia.Web.Sample/main.js b/samples/ControlCatalog.Web/main.js
similarity index 84%
rename from src/Web/Avalonia.Web.Sample/main.js
rename to samples/ControlCatalog.Web/main.js
index 3683aea181..87f8a4f943 100644
--- a/src/Web/Avalonia.Web.Sample/main.js
+++ b/samples/ControlCatalog.Web/main.js
@@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { dotnet } from './dotnet.js'
-import { createAvaloniaRuntime } from './avalonia.js';
+import { registerAvaloniaModule } from './avalonia.js';
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
@@ -12,7 +12,7 @@ const dotnetRuntime = await dotnet
.withApplicationArgumentsFromQuery()
.create();
-await createAvaloniaRuntime(dotnetRuntime);
+await registerAvaloniaModule(dotnetRuntime);
const config = dotnetRuntime.getConfig();
diff --git a/src/Web/Avalonia.Web.Sample/runtimeconfig.template.json b/samples/ControlCatalog.Web/runtimeconfig.template.json
similarity index 100%
rename from src/Web/Avalonia.Web.Sample/runtimeconfig.template.json
rename to samples/ControlCatalog.Web/runtimeconfig.template.json
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 869602e452..55a1014188 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -2,6 +2,7 @@
+
Shared\_ModuleInitializer.cs
diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props
deleted file mode 100644
index eb5e5dd733..0000000000
--- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
- 16777216
- false
- false
-
-
diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj
index 693a6a1462..1c31e0eb5d 100644
--- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj
+++ b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj
@@ -1,53 +1,40 @@
- net6.0
- enable
+ net7.0
Avalonia.Web.Blazor
- preview
- false
- true
+ _IncludeGeneratedAvaloniaStaticFiles;$(ResolveStaticWebAssetsInputsDependsOn)
-
-
-
-
- true
- build\
-
-
- true
- build\;buildTransitive\
-
+
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
+
+ <_AvaloniaWebAssets Include="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/**/*.*" />
+
+
+
+
diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props
deleted file mode 100644
index dd96a60c6a..0000000000
--- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets
deleted file mode 100644
index e9052fda88..0000000000
--- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs
deleted file mode 100644
index 11d9bcc98f..0000000000
--- a/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Platform;
-
-namespace Avalonia.Web.Blazor
-{
- public class AvaloniaBlazorAppBuilder : AppBuilderBase
- {
- public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action platformServices)
- : base(platform, platformServices)
- {
- }
-
- public AvaloniaBlazorAppBuilder()
- : base(new StandardRuntimePlatform(),
- builder => StandardRuntimePlatformServices.Register(builder.ApplicationType!.Assembly))
- {
- UseWindowingSubsystem(BlazorWindowingPlatform.Register);
- }
- }
-}
diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs
new file mode 100644
index 0000000000..909e2dd441
--- /dev/null
+++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs
@@ -0,0 +1,44 @@
+using System.Runtime.InteropServices.JavaScript;
+using System.Runtime.Versioning;
+using System.Threading.Tasks;
+using System;
+using Avalonia.Controls.ApplicationLifetimes;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Rendering;
+using BrowserView = Avalonia.Web.AvaloniaView;
+
+namespace Avalonia.Web.Blazor;
+
+[SupportedOSPlatform("browser")]
+public class AvaloniaView : ComponentBase
+{
+ private BrowserView? _browserView;
+ private readonly string _containerId;
+
+ public AvaloniaView()
+ {
+ _containerId = "av_container_" + Guid.NewGuid();
+ }
+
+ protected override void BuildRenderTree(RenderTreeBuilder builder)
+ {
+ builder.OpenElement(0, "div");
+ builder.AddAttribute(1, "id", _containerId);
+ builder.AddAttribute(2, "style", "width:100vw;height:100vh");
+ builder.CloseElement();
+ }
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (OperatingSystem.IsBrowser())
+ {
+ await Avalonia.Web.Interop.AvaloniaModule.ImportMain();
+
+ _browserView = new BrowserView(_containerId);
+ if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime)
+ {
+ _browserView.Content = lifetime.MainView;
+ }
+ }
+ }
+}
diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor
deleted file mode 100644
index 5a3b9d5f71..0000000000
--- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
deleted file mode 100644
index 0e64e98f1e..0000000000
--- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
+++ /dev/null
@@ -1,500 +0,0 @@
-using System.Diagnostics;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Controls.Embedding;
-using Avalonia.Controls.Platform;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Input.TextInput;
-using Avalonia.Platform.Storage;
-using Avalonia.Rendering;
-using Avalonia.Rendering.Composition;
-using Avalonia.Web.Blazor.Interop;
-using Avalonia.Web.Blazor.Interop.Storage;
-
-using Microsoft.AspNetCore.Components;
-using Microsoft.AspNetCore.Components.Forms;
-using Microsoft.AspNetCore.Components.Web;
-using Microsoft.JSInterop;
-
-using SkiaSharp;
-using HTMLPointerEventArgs = Microsoft.AspNetCore.Components.Web.PointerEventArgs;
-
-namespace Avalonia.Web.Blazor
-{
- public partial class AvaloniaView : ITextInputMethodImpl
- {
- private readonly RazorViewTopLevelImpl _topLevelImpl;
- private EmbeddableControlRoot _topLevel;
-
- // Interop
- private SKHtmlCanvasInterop? _interop = null;
- private SizeWatcherInterop? _sizeWatcher = null;
- private DpiWatcherInterop? _dpiWatcher = null;
- private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null;
- private AvaloniaModule? _avaloniaModule = null;
- private InputHelperInterop? _inputHelper = null;
- private FocusHelperInterop? _canvasHelper = null;
- private FocusHelperInterop? _containerHelper = null;
- private NativeControlHostInterop? _nativeControlHost = null;
- private StorageProviderInterop? _storageProvider = null;
- private ElementReference _htmlCanvas;
- private ElementReference _inputElement;
- private ElementReference _containerElement;
- private ElementReference _nativeControlsContainer;
- private double _dpi = 1;
- private SKSize _canvasSize = new (100, 100);
-
- private GRContext? _context;
- private GRGlInterface? _glInterface;
- private const SKColorType ColorType = SKColorType.Rgba8888;
-
- private bool _useGL;
- private bool _inputElementFocused;
-
- private ITextInputMethodClient? _client;
-
-
- [Inject] private IJSRuntime Js { get; set; } = null!;
-
- public AvaloniaView()
- {
- _topLevelImpl = new RazorViewTopLevelImpl(this);
-
- _topLevel = new EmbeddableControlRoot(_topLevelImpl);
-
- if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime)
- {
- _topLevel.Content = lifetime.MainView;
- }
- }
-
- public bool KeyPreventDefault { get; set; }
-
- public ITextInputMethodClient? Client => _client;
-
- public bool IsActive => _client != null;
-
- public bool IsComposing { get; private set; }
-
- internal INativeControlHostImpl GetNativeControlHostImpl()
- {
- return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet");
- }
-
- internal IStorageProvider GetStorageProvider()
- {
- return _storageProvider ?? throw new InvalidOperationException("Blazor View wasn't initialized yet");
- }
-
- private void OnPointerCancel(HTMLPointerEventArgs e)
- {
- if (e.PointerType == "touch")
- {
- _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, e.PointerType, GetPointFromEventArgs(e),
- GetModifiers(e), e.PointerId);
- }
- }
-
- private void OnPointerMove(HTMLPointerEventArgs e)
- {
- var type = e.PointerType switch
- {
- "touch" => RawPointerEventType.TouchUpdate,
- _ => RawPointerEventType.Move
- };
-
- _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId);
- }
-
- private void OnPointerUp(HTMLPointerEventArgs e)
- {
- var type = e.PointerType switch
- {
- "touch" => RawPointerEventType.TouchEnd,
- _ => e.Button switch
- {
- 0 => RawPointerEventType.LeftButtonUp,
- 1 => RawPointerEventType.MiddleButtonUp,
- 2 => RawPointerEventType.RightButtonUp,
- 3 => RawPointerEventType.XButton1Up,
- 4 => RawPointerEventType.XButton2Up,
- // 5 => Pen eraser button,
- _ => RawPointerEventType.Move
- }
- };
-
- _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId);
- }
-
- private void OnPointerDown(HTMLPointerEventArgs e)
- {
- var type = e.PointerType switch
- {
- "touch" => RawPointerEventType.TouchBegin,
- _ => e.Button switch
- {
- 0 => RawPointerEventType.LeftButtonDown,
- 1 => RawPointerEventType.MiddleButtonDown,
- 2 => RawPointerEventType.RightButtonDown,
- 3 => RawPointerEventType.XButton1Down,
- 4 => RawPointerEventType.XButton2Down,
- // 5 => Pen eraser button,
- _ => RawPointerEventType.Move
- }
- };
-
- _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId);
- }
-
- private static RawPointerPoint GetPointFromEventArgs(HTMLPointerEventArgs args)
- {
- return new RawPointerPoint
- {
- Position = new Point(args.ClientX, args.ClientY),
- Pressure = args.Pressure,
- XTilt = args.TiltX,
- YTilt = args.TiltY
- // Twist = args.Twist - read from JS code directly when
- };
- }
-
- private void OnWheel(WheelEventArgs e)
- {
- _topLevelImpl.RawMouseWheelEvent( new Point(e.ClientX, e.ClientY),
- new Vector(-(e.DeltaX / 50), -(e.DeltaY / 50)), GetModifiers(e));
- }
-
- private static RawInputModifiers GetModifiers(MouseEventArgs e)
- {
- var modifiers = RawInputModifiers.None;
-
- if (e.CtrlKey)
- modifiers |= RawInputModifiers.Control;
- if (e.AltKey)
- modifiers |= RawInputModifiers.Alt;
- if (e.ShiftKey)
- modifiers |= RawInputModifiers.Shift;
- if (e.MetaKey)
- modifiers |= RawInputModifiers.Meta;
-
- if ((e.Buttons & 1L) == 1)
- modifiers |= RawInputModifiers.LeftMouseButton;
-
- if ((e.Buttons & 2L) == 2)
- modifiers |= e.Type == "pen" ? RawInputModifiers.PenBarrelButton : RawInputModifiers.RightMouseButton;
-
- if ((e.Buttons & 4L) == 4)
- modifiers |= RawInputModifiers.MiddleMouseButton;
-
- if ((e.Buttons & 8L) == 8)
- modifiers |= RawInputModifiers.XButton1MouseButton;
-
- if ((e.Buttons & 16L) == 16)
- modifiers |= RawInputModifiers.XButton2MouseButton;
-
- if ((e.Buttons & 32L) == 32)
- modifiers |= RawInputModifiers.PenEraser;
-
- return modifiers;
- }
-
- private static RawInputModifiers GetModifiers(KeyboardEventArgs e)
- {
- var modifiers = RawInputModifiers.None;
-
- if (e.CtrlKey)
- modifiers |= RawInputModifiers.Control;
- if (e.AltKey)
- modifiers |= RawInputModifiers.Alt;
- if (e.ShiftKey)
- modifiers |= RawInputModifiers.Shift;
- if (e.MetaKey)
- modifiers |= RawInputModifiers.Meta;
-
- return modifiers;
- }
-
- private void OnKeyDown(KeyboardEventArgs e)
- {
- KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, e.Code, e.Key, GetModifiers(e));
- }
-
- private void OnKeyUp(KeyboardEventArgs e)
- {
- KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, e.Code, e.Key, GetModifiers(e));
- }
-
- private void OnFocus(FocusEventArgs e)
- {
- // if focus has unexpectedly moved from the input element to the container element,
- // shift it back to the input element
- if (_inputElementFocused && _inputHelper is not null)
- {
- _inputHelper.Focus();
- }
- }
-
- [Parameter(CaptureUnmatchedValues = true)]
- public IReadOnlyDictionary? AdditionalAttributes { get; set; }
-
- protected override async Task OnAfterRenderAsync(bool firstRender)
- {
- if (firstRender)
- {
- AvaloniaLocator.CurrentMutable.Bind().ToConstant((IJSInProcessRuntime)Js);
-
- _avaloniaModule = await AvaloniaModule.ImportAsync(Js);
-
- _canvasHelper = new FocusHelperInterop(_avaloniaModule, _htmlCanvas);
- _containerHelper = new FocusHelperInterop(_avaloniaModule, _containerElement);
- _inputHelper = new InputHelperInterop(_avaloniaModule, _inputElement);
-
- _inputHelper.CompositionEvent += InputHelperOnCompositionEvent;
- _inputHelper.InputEvent += InputHelperOnInputEvent;
-
- HideIme();
- _canvasHelper.SetCursor("default");
- _topLevelImpl.SetCssCursor = x =>
- {
- _inputHelper.SetCursor(x); //macOS
- _canvasHelper.SetCursor(x); //windows
- };
-
- _nativeControlHost = new NativeControlHostInterop(_avaloniaModule, _nativeControlsContainer);
- _storageProvider = await StorageProviderInterop.ImportAsync(Js);
-
- Console.WriteLine("starting html canvas setup");
- _interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame);
-
- Console.WriteLine("Interop created");
-
- var skiaOptions = AvaloniaLocator.Current.GetService();
- _useGL = skiaOptions?.CustomGpuFactory != null;
-
- if (_useGL)
- {
- _jsGlInfo = _interop.InitGL();
- Console.WriteLine("jsglinfo created - init gl");
- }
- else
- {
- var rasterInitialized = _interop.InitRaster();
- Console.WriteLine("raster initialized: {0}", rasterInitialized);
- }
-
- if (_useGL)
- {
- // create the SkiaSharp context
- if (_context == null)
- {
- _glInterface = GRGlInterface.Create();
- _context = GRContext.CreateGl(_glInterface);
-
-
- // bump the default resource cache limit
- _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
- Console.WriteLine("glcontext created and resource limit set");
- }
-
- _topLevelImpl.SetSurface(_context, _jsGlInfo!, ColorType,
- new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi);
- }
- else
- {
- _topLevelImpl.SetSurface(ColorType,
- new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
- }
-
- _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
-
- Threading.Dispatcher.UIThread.Post(async () =>
- {
- _interop.RequestAnimationFrame(true);
-
- _sizeWatcher = new SizeWatcherInterop(_avaloniaModule, _htmlCanvas, OnSizeChanged);
- _dpiWatcher = new DpiWatcherInterop(_avaloniaModule, OnDpiChanged);
-
- _sizeWatcher.Start();
- _topLevel.Prepare();
-
- _topLevel.Renderer.Start();
- });
- }
- }
-
- private void InputHelperOnInputEvent(object? sender, WebInputEventArgs e)
- {
- if (IsComposing)
- {
- return;
- }
-
- _topLevelImpl.RawTextEvent(e.Data);
-
- e.Handled = true;
- }
-
- private void InputHelperOnCompositionEvent(object? sender, WebCompositionEventArgs e)
- {
- if(_client == null)
- {
- return;
- }
-
- switch (e.Type)
- {
- case WebCompositionEventArgs.WebCompositionEventType.Start:
- _client.SetPreeditText(null);
- IsComposing = true;
- break;
- case WebCompositionEventArgs.WebCompositionEventType.Update:
- _client.SetPreeditText(e.Data);
- break;
- case WebCompositionEventArgs.WebCompositionEventType.End:
- IsComposing = false;
- _client.SetPreeditText(null);
- _topLevelImpl.RawTextEvent(e.Data);
- break;
- }
- }
-
- private void OnRenderFrame()
- {
- if (_useGL && (_jsGlInfo == null))
- {
- Console.WriteLine("nothing to render");
- return;
- }
- if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0)
- {
- Console.WriteLine("nothing to render");
- return;
- }
-
- ManualTriggerRenderTimer.Instance.RaiseTick();
- }
-
- public void Dispose()
- {
- _dpiWatcher?.Unsubscribe(OnDpiChanged);
- _sizeWatcher?.Dispose();
- _interop?.Dispose();
- }
-
- private void ForceBlit()
- {
- // Note: this is technically a hack, but it's a kinda unique use case when
- // we want to blit the previous frame
- // renderer doesn't have much control over the render target
- // we render on the UI thread
- // We also don't want to have it as a meaningful public API.
- // Therefore we have InternalsVisibleTo hack here.
-
- if (_topLevel.Renderer is CompositingRenderer dr)
- {
- dr.CompositionTarget.ImmediateUIThreadRender();
- }
- }
-
- private void OnDpiChanged(double newDpi)
- {
- if (Math.Abs(_dpi - newDpi) > 0.0001)
- {
- _dpi = newDpi;
-
- _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
-
- _topLevelImpl.SetClientSize(_canvasSize, _dpi);
-
- ForceBlit();
- }
- }
-
- private void OnSizeChanged(SKSize newSize)
- {
- if (_canvasSize != newSize)
- {
- _canvasSize = newSize;
-
- _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
-
- _topLevelImpl.SetClientSize(_canvasSize, _dpi);
-
- ForceBlit();
- }
- }
-
- private void HideIme()
- {
- _inputHelper?.Hide();
- _containerHelper?.Focus();
- }
-
- public void SetClient(ITextInputMethodClient? client)
- {
- if (_inputHelper is null)
- {
- return;
- }
-
- if(_client != null)
- {
- _client.SurroundingTextChanged -= SurroundingTextChanged;
- }
-
- if(client != null)
- {
- client.SurroundingTextChanged += SurroundingTextChanged;
- }
-
- _inputHelper.Clear();
-
- _client = client;
-
- if (IsActive && _client != null)
- {
- _inputHelper.Show();
- _inputElementFocused = true;
- _inputHelper.Focus();
-
- var surroundingText = _client.SurroundingText;
-
- _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
- }
- else
- {
- _inputElementFocused = false;
- HideIme();
- }
- }
-
- private void SurroundingTextChanged(object? sender, EventArgs e)
- {
- if(_client != null)
- {
- var surroundingText = _client.SurroundingText;
-
- _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
- }
- }
-
- public void SetCursorRect(Rect rect)
- {
- _inputHelper?.Focus();
- var bounds = new PixelRect((int)rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height);
-
- _inputHelper?.SetBounds(bounds, _client?.SurroundingText.CursorOffset ?? 0);
- _inputHelper?.Focus();
- }
-
- public void SetOptions(TextInputOptions options)
- {
- }
-
- public void Reset()
- {
- _inputHelper?.Clear();
- _inputHelper?.SetSurroundingText("", 0, 0);
- }
- }
-}
diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs
index 7970f09a58..f38779f834 100644
--- a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs
+++ b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs
@@ -1,31 +1,39 @@
-using Avalonia.Controls;
+using System.Runtime.Versioning;
+
+using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Media;
-namespace Avalonia.Web.Blazor
+namespace Avalonia.Web.Blazor;
+
+[SupportedOSPlatform("browser")]
+public static class WebAppBuilder
{
- public class BlazorSingleViewLifetime : ISingleViewApplicationLifetime
+ public static T SetupWithSingleViewLifetime(
+ this T builder)
+ where T : AppBuilderBase, new()
{
- public Control? MainView { get; set; }
+ return builder.SetupWithLifetime(new BlazorSingleViewLifetime());
}
- public static class WebAppBuilder
+ public static T UseBlazor(this T builder) where T : AppBuilderBase, new()
{
- public static T SetupWithSingleViewLifetime(
- this T builder)
- where T : AppBuilderBase, new()
- {
- return builder.SetupWithLifetime(new BlazorSingleViewLifetime());
- }
+ return builder
+ .UseBrowser()
+ .With(new BrowserPlatformOptions
+ {
+ FrameworkAssetPathResolver = new(filePath => $"/_content/Avalonia.Web.Blazor/{filePath}")
+ });
+ }
- public static AvaloniaBlazorAppBuilder Configure()
- where TApp : Application, new()
- {
- var builder = AvaloniaBlazorAppBuilder.Configure()
- .UseSkia()
- .With(new SkiaOptions { CustomGpuFactory = () => new BlazorSkiaGpu() });
+ public static AppBuilder Configure()
+ where TApp : Application, new()
+ {
+ return AppBuilder.Configure()
+ .UseBlazor();
+ }
- return builder;
- }
+ internal class BlazorSingleViewLifetime : ISingleViewApplicationLifetime
+ {
+ public Control? MainView { get; set; }
}
}
diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs
deleted file mode 100644
index 6fa7bf0bde..0000000000
--- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Avalonia.Skia;
-
-namespace Avalonia.Web.Blazor
-{
- public class BlazorSkiaGpu : ISkiaGpu
- {
- public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable
-
-
-
-
@@ -52,4 +48,8 @@
+
+
+
+
diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs
index e81620ffde..3a31679424 100644
--- a/src/Web/Avalonia.Web/AvaloniaView.cs
+++ b/src/Web/Avalonia.Web/AvaloniaView.cs
@@ -37,16 +37,14 @@ namespace Avalonia.Web
private bool _useGL;
private ITextInputMethodClient? _client;
- private static int _canvasCount;
public AvaloniaView(string divId)
+ : this(DomHelper.GetElementById(divId) ?? throw new Exception($"Element with id {divId} was not found in the html document."))
{
- var host = DomHelper.GetElementById(divId);
- if (host == null)
- {
- throw new Exception($"Element with id {divId} was not found in the html document.");
- }
+ }
+ public AvaloniaView(JSObject host)
+ {
var hostContent = DomHelper.CreateAvaloniaHost(host);
if (hostContent == null)
{
@@ -64,8 +62,6 @@ namespace Avalonia.Web
_splash = DomHelper.GetElementById("avalonia-splash");
- _canvas.SetProperty("id", $"avaloniaCanvas{_canvasCount++}");
-
_topLevelImpl = new BrowserTopLevelImpl(this);
_topLevel = new WebEmbeddableControlRoot(_topLevelImpl, () =>
@@ -137,7 +133,7 @@ namespace Avalonia.Web
_topLevelImpl.SetClientSize(_canvasSize, _dpi);
- DomHelper.ObserveSize(host, divId, OnSizeChanged);
+ DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true);
}
@@ -387,7 +383,7 @@ namespace Avalonia.Web
InputHelper.FocusElement(_containerElement);
}
- public void SetClient(ITextInputMethodClient? client)
+ void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
{
Console.WriteLine("Set Client");
if (_client != null)
@@ -431,18 +427,18 @@ namespace Avalonia.Web
}
}
- public void SetCursorRect(Rect rect)
+ void ITextInputMethodImpl.SetCursorRect(Rect rect)
{
InputHelper.FocusElement(_inputElement);
InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0);
InputHelper.FocusElement(_inputElement);
}
- public void SetOptions(TextInputOptions options)
+ void ITextInputMethodImpl.SetOptions(TextInputOptions options)
{
}
- public void Reset()
+ void ITextInputMethodImpl.Reset()
{
InputHelper.ClearInputElement(_inputElement);
InputHelper.SetSurroundingText(_inputElement, "", 0, 0);
diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
index 091ab3f68c..00ed961fbe 100644
--- a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
+++ b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
@@ -1,41 +1,54 @@
-using System.Runtime.InteropServices.JavaScript;
-using System;
+using System;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Media;
using Avalonia.Web.Skia;
+using System.Runtime.Versioning;
-namespace Avalonia.Web
+namespace Avalonia.Web;
+
+[SupportedOSPlatform("browser")]
+public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
{
- [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
- public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
- {
- public AvaloniaView? View;
+ public AvaloniaView? View;
- public Control? MainView
- {
- get => View!.Content;
- set => View!.Content = value;
- }
+ public Control? MainView
+ {
+ get => View!.Content;
+ set => View!.Content = value;
}
+}
- public static partial class WebAppBuilder
- {
- public static T SetupBrowserApp(
+public class BrowserPlatformOptions
+{
+ public Func FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}");
+}
+
+
+[SupportedOSPlatform("browser")]
+public static class WebAppBuilder
+{
+ public static T SetupBrowserApp(
this T builder, string mainDivId)
where T : AppBuilderBase, new()
- {
- var lifetime = new BrowserSingleViewLifetime();
-
- return builder
- .UseWindowingSubsystem(BrowserWindowingPlatform.Register)
- .UseSkia()
- .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() })
- .AfterSetup(b =>
- {
- lifetime.View = new AvaloniaView(mainDivId);
- })
- .SetupWithLifetime(lifetime);
- }
+ {
+ var lifetime = new BrowserSingleViewLifetime();
+
+ return builder
+ .UseBrowser()
+ .AfterSetup(b =>
+ {
+ lifetime.View = new AvaloniaView(mainDivId);
+ })
+ .SetupWithLifetime(lifetime);
+ }
+
+ public static T UseBrowser(
+ this T builder)
+ where T : AppBuilderBase, new()
+ {
+ return builder
+ .UseWindowingSubsystem(BrowserWindowingPlatform.Register)
+ .UseSkia()
+ .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() });
}
}
diff --git a/src/Web/Avalonia.Web/Cursor.cs b/src/Web/Avalonia.Web/Cursor.cs
index 5db0bdeda2..af7098f800 100644
--- a/src/Web/Avalonia.Web/Cursor.cs
+++ b/src/Web/Avalonia.Web/Cursor.cs
@@ -5,7 +5,7 @@ using Avalonia.Platform;
namespace Avalonia.Web
{
- public class CssCursor : ICursorImpl
+ internal class CssCursor : ICursorImpl
{
public const string Default = "default";
public string? Value { get; set; }
diff --git a/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs
new file mode 100644
index 0000000000..176b8d60fc
--- /dev/null
+++ b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs
@@ -0,0 +1,22 @@
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Interop;
+
+internal static class AvaloniaModule
+{
+ public const string MainModuleName = "avalonia";
+ public const string StorageModuleName = "storage";
+
+ public static Task ImportMain()
+ {
+ var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions();
+ return JSHost.ImportAsync(MainModuleName, options.FrameworkAssetPathResolver("avalonia.js"));
+ }
+
+ public static Task ImportStorage()
+ {
+ var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions();
+ return JSHost.ImportAsync(StorageModuleName, options.FrameworkAssetPathResolver("storage.js"));
+ }
+}
diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
index 66a1d421b7..efa94916fa 100644
--- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
@@ -29,13 +29,13 @@ internal static partial class CanvasHelper
return glInfo;
}
- [JSImport("Canvas.requestAnimationFrame", "avalonia.ts")]
+ [JSImport("Canvas.requestAnimationFrame", AvaloniaModule.MainModuleName)]
public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
- [JSImport("Canvas.setCanvasSize", "avalonia.ts")]
+ [JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)]
public static partial void SetCanvasSize(JSObject canvas, int height, int width);
- [JSImport("Canvas.initGL", "avalonia.ts")]
+ [JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)]
private static partial JSObject InitGL(
JSObject canvas,
string canvasId,
diff --git a/src/Web/Avalonia.Web/Interop/DomHelper.cs b/src/Web/Avalonia.Web/Interop/DomHelper.cs
index c02bc4dae3..80f146a57a 100644
--- a/src/Web/Avalonia.Web/Interop/DomHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/DomHelper.cs
@@ -8,20 +8,20 @@ internal static partial class DomHelper
[JSImport("globalThis.document.getElementById")]
internal static partial JSObject? GetElementById(string id);
- [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia.ts")]
+ [JSImport("AvaloniaDOM.createAvaloniaHost", AvaloniaModule.MainModuleName)]
public static partial JSObject CreateAvaloniaHost(JSObject element);
- [JSImport("AvaloniaDOM.addClass", "avalonia.ts")]
+ [JSImport("AvaloniaDOM.addClass", AvaloniaModule.MainModuleName)]
public static partial void AddCssClass(JSObject element, string className);
- [JSImport("SizeWatcher.observe", "avalonia.ts")]
+ [JSImport("SizeWatcher.observe", AvaloniaModule.MainModuleName)]
public static partial JSObject ObserveSize(
JSObject canvas,
- string canvasId,
+ string? canvasId,
[JSMarshalAs>]
Action onSizeChanged);
- [JSImport("DpiWatcher.start", "avalonia.ts")]
+ [JSImport("DpiWatcher.start", AvaloniaModule.MainModuleName)]
public static partial double ObserveDpi(
[JSMarshalAs>]
Action onDpiChanged);
diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs
index b32b6566f2..cfec9f30dc 100644
--- a/src/Web/Avalonia.Web/Interop/InputHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Web.Interop;
internal static partial class InputHelper
{
- [JSImport("InputHelper.subscribeKeyEvents", "avalonia.ts")]
+ [JSImport("InputHelper.subscribeKeyEvents", AvaloniaModule.MainModuleName)]
public static partial void SubscribeKeyEvents(
JSObject htmlElement,
[JSMarshalAs>]
@@ -14,7 +14,7 @@ internal static partial class InputHelper
[JSMarshalAs>]
Func keyUp);
- [JSImport("InputHelper.subscribeTextEvents", "avalonia.ts")]
+ [JSImport("InputHelper.subscribeTextEvents", AvaloniaModule.MainModuleName)]
public static partial void SubscribeTextEvents(
JSObject htmlElement,
[JSMarshalAs>]
@@ -26,7 +26,7 @@ internal static partial class InputHelper
[JSMarshalAs>]
Func onCompositionEnd);
- [JSImport("InputHelper.subscribePointerEvents", "avalonia.ts")]
+ [JSImport("InputHelper.subscribePointerEvents", AvaloniaModule.MainModuleName)]
public static partial void SubscribePointerEvents(
JSObject htmlElement,
[JSMarshalAs>]
@@ -39,35 +39,35 @@ internal static partial class InputHelper
Func wheel);
- [JSImport("InputHelper.subscribeInputEvents", "avalonia.ts")]
+ [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)]
public static partial void SubscribeInputEvents(
JSObject htmlElement,
[JSMarshalAs>]
Func input);
- [JSImport("InputHelper.clearInput", "avalonia.ts")]
+ [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)]
public static partial void ClearInputElement(JSObject htmlElement);
- [JSImport("InputHelper.isInputElement", "avalonia.ts")]
+ [JSImport("InputHelper.isInputElement", AvaloniaModule.MainModuleName)]
public static partial void IsInputElement(JSObject htmlElement);
- [JSImport("InputHelper.focusElement", "avalonia.ts")]
+ [JSImport("InputHelper.focusElement", AvaloniaModule.MainModuleName)]
public static partial void FocusElement(JSObject htmlElement);
- [JSImport("InputHelper.setCursor", "avalonia.ts")]
+ [JSImport("InputHelper.setCursor", AvaloniaModule.MainModuleName)]
public static partial void SetCursor(JSObject htmlElement, string kind);
- [JSImport("InputHelper.hide", "avalonia.ts")]
+ [JSImport("InputHelper.hide", AvaloniaModule.MainModuleName)]
public static partial void HideElement(JSObject htmlElement);
- [JSImport("InputHelper.show", "avalonia.ts")]
+ [JSImport("InputHelper.show", AvaloniaModule.MainModuleName)]
public static partial void ShowElement(JSObject htmlElement);
- [JSImport("InputHelper.setSurroundingText", "avalonia.ts")]
+ [JSImport("InputHelper.setSurroundingText", AvaloniaModule.MainModuleName)]
public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end);
- [JSImport("InputHelper.setBounds", "avalonia.ts")]
+ [JSImport("InputHelper.setBounds", AvaloniaModule.MainModuleName)]
public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret);
[JSImport("globalThis.navigator.clipboard.readText")]
diff --git a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs
index 5cc86bf622..d3baaa2533 100644
--- a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs
@@ -5,24 +5,24 @@ namespace Avalonia.Web.Interop;
internal static partial class NativeControlHostHelper
{
- [JSImport("NativeControlHost.createDefaultChild", "avalonia.ts")]
+ [JSImport("NativeControlHost.createDefaultChild", AvaloniaModule.MainModuleName)]
internal static partial JSObject CreateDefaultChild(JSObject? parent);
- [JSImport("NativeControlHost.createAttachment", "avalonia.ts")]
+ [JSImport("NativeControlHost.createAttachment", AvaloniaModule.MainModuleName)]
internal static partial JSObject CreateAttachment();
- [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia.ts")]
+ [JSImport("NativeControlHost.initializeWithChildHandle", AvaloniaModule.MainModuleName)]
internal static partial void InitializeWithChildHandle(JSObject element, JSObject child);
- [JSImport("NativeControlHost.attachTo", "avalonia.ts")]
+ [JSImport("NativeControlHost.attachTo", AvaloniaModule.MainModuleName)]
internal static partial void AttachTo(JSObject element, JSObject? host);
- [JSImport("NativeControlHost.showInBounds", "avalonia.ts")]
+ [JSImport("NativeControlHost.showInBounds", AvaloniaModule.MainModuleName)]
internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height);
- [JSImport("NativeControlHost.hideWithSize", "avalonia.ts")]
+ [JSImport("NativeControlHost.hideWithSize", AvaloniaModule.MainModuleName)]
internal static partial void HideWithSize(JSObject element, double width, double height);
- [JSImport("NativeControlHost.releaseChild", "avalonia.ts")]
+ [JSImport("NativeControlHost.releaseChild", AvaloniaModule.MainModuleName)]
internal static partial void ReleaseChild(JSObject element);
}
diff --git a/src/Web/Avalonia.Web/Interop/StorageHelper.cs b/src/Web/Avalonia.Web/Interop/StorageHelper.cs
index 4dc55fd959..9a6cfb9fc2 100644
--- a/src/Web/Avalonia.Web/Interop/StorageHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/StorageHelper.cs
@@ -5,51 +5,51 @@ namespace Avalonia.Web.Interop;
internal static partial class StorageHelper
{
- [JSImport("Caniuse.canShowOpenFilePicker", "avalonia.ts")]
+ [JSImport("Caniuse.canShowOpenFilePicker", AvaloniaModule.MainModuleName)]
public static partial bool CanShowOpenFilePicker();
- [JSImport("Caniuse.canShowSaveFilePicker", "avalonia.ts")]
+ [JSImport("Caniuse.canShowSaveFilePicker", AvaloniaModule.MainModuleName)]
public static partial bool CanShowSaveFilePicker();
- [JSImport("Caniuse.canShowDirectoryPicker", "avalonia.ts")]
+ [JSImport("Caniuse.canShowDirectoryPicker", AvaloniaModule.MainModuleName)]
public static partial bool CanShowDirectoryPicker();
- [JSImport("StorageProvider.selectFolderDialog", "storage.ts")]
+ [JSImport("StorageProvider.selectFolderDialog", AvaloniaModule.StorageModuleName)]
public static partial Task SelectFolderDialog(JSObject? startIn);
- [JSImport("StorageProvider.openFileDialog", "storage.ts")]
+ [JSImport("StorageProvider.openFileDialog", AvaloniaModule.StorageModuleName)]
public static partial Task OpenFileDialog(JSObject? startIn, bool multiple,
[JSMarshalAs>] object[]? types, bool excludeAcceptAllOption);
- [JSImport("StorageProvider.saveFileDialog", "storage.ts")]
+ [JSImport("StorageProvider.saveFileDialog", AvaloniaModule.StorageModuleName)]
public static partial Task SaveFileDialog(JSObject? startIn, string? suggestedName,
[JSMarshalAs>] object[]? types, bool excludeAcceptAllOption);
- [JSImport("StorageProvider.openBookmark", "storage.ts")]
+ [JSImport("StorageProvider.openBookmark", AvaloniaModule.StorageModuleName)]
public static partial Task OpenBookmark(string key);
- [JSImport("StorageItem.saveBookmark", "storage.ts")]
+ [JSImport("StorageItem.saveBookmark", AvaloniaModule.StorageModuleName)]
public static partial Task SaveBookmark(JSObject item);
- [JSImport("StorageItem.deleteBookmark", "storage.ts")]
+ [JSImport("StorageItem.deleteBookmark", AvaloniaModule.StorageModuleName)]
public static partial Task DeleteBookmark(JSObject item);
- [JSImport("StorageItem.getProperties", "storage.ts")]
+ [JSImport("StorageItem.getProperties", AvaloniaModule.StorageModuleName)]
public static partial Task GetProperties(JSObject item);
- [JSImport("StorageItem.openWrite", "storage.ts")]
+ [JSImport("StorageItem.openWrite", AvaloniaModule.StorageModuleName)]
public static partial Task OpenWrite(JSObject item);
- [JSImport("StorageItem.openRead", "storage.ts")]
+ [JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)]
public static partial Task OpenRead(JSObject item);
- [JSImport("StorageItem.getItems", "storage.ts")]
+ [JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)]
[return: JSMarshalAs>]
public static partial Task GetItems(JSObject item);
- [JSImport("StorageItems.itemsArray", "storage.ts")]
+ [JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)]
public static partial JSObject[] ItemsArray(JSObject item);
- [JSImport("StorageProvider.createAcceptType", "storage.ts")]
+ [JSImport("StorageProvider.createAcceptType", AvaloniaModule.StorageModuleName)]
public static partial JSObject CreateAcceptType(string description, string[] mimeTypes);
}
diff --git a/src/Web/Avalonia.Web/Interop/StreamHelper.cs b/src/Web/Avalonia.Web/Interop/StreamHelper.cs
index 0a83bbb871..d9de7bcbd8 100644
--- a/src/Web/Avalonia.Web/Interop/StreamHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/StreamHelper.cs
@@ -2,33 +2,33 @@
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
-namespace Avalonia.Web.Storage;
+namespace Avalonia.Web.Interop;
///
/// Set of FileSystemWritableFileStream and Blob methods.
///
internal static partial class StreamHelper
{
- [JSImport("StreamHelper.seek", "avalonia.ts")]
+ [JSImport("StreamHelper.seek", AvaloniaModule.MainModuleName)]
public static partial void Seek(JSObject stream, [JSMarshalAs] long position);
- [JSImport("StreamHelper.truncate", "avalonia.ts")]
+ [JSImport("StreamHelper.truncate", AvaloniaModule.MainModuleName)]
public static partial void Truncate(JSObject stream, [JSMarshalAs] long size);
- [JSImport("StreamHelper.write", "avalonia.ts")]
+ [JSImport("StreamHelper.write", AvaloniaModule.MainModuleName)]
public static partial Task WriteAsync(JSObject stream, [JSMarshalAs] ArraySegment data);
- [JSImport("StreamHelper.close", "avalonia.ts")]
+ [JSImport("StreamHelper.close", AvaloniaModule.MainModuleName)]
public static partial Task CloseAsync(JSObject stream);
- [JSImport("StreamHelper.byteLength", "avalonia.ts")]
+ [JSImport("StreamHelper.byteLength", AvaloniaModule.MainModuleName)]
[return: JSMarshalAs]
public static partial long ByteLength(JSObject stream);
- [JSImport("StreamHelper.sliceArrayBuffer", "avalonia.ts")]
+ [JSImport("StreamHelper.sliceArrayBuffer", AvaloniaModule.MainModuleName)]
private static partial Task SliceToArrayBuffer(JSObject stream, [JSMarshalAs] long offset, int count);
- [JSImport("StreamHelper.toMemoryView", "avalonia.ts")]
+ [JSImport("StreamHelper.toMemoryView", AvaloniaModule.MainModuleName)]
[return: JSMarshalAs>]
private static partial byte[] ArrayBufferToMemoryView(JSObject stream);
diff --git a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs
index 774848e19f..3309a6dd9f 100644
--- a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs
+++ b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs
@@ -4,7 +4,7 @@ using Avalonia.Rendering;
namespace Avalonia.Web
{
- public class ManualTriggerRenderTimer : IRenderTimer
+ internal class ManualTriggerRenderTimer : IRenderTimer
{
private static readonly Stopwatch s_sw = Stopwatch.StartNew();
diff --git a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs
index 640c2fd745..77734ea62f 100644
--- a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs
+++ b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs
@@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript;
using System.Threading;
using System.Threading.Tasks;
+using Avalonia.Web.Interop;
+
namespace Avalonia.Web.Storage;
[System.Runtime.Versioning.SupportedOSPlatform("browser")]
diff --git a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs
index 55ae2bdef0..3932b79ad0 100644
--- a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs
+++ b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs
@@ -20,7 +20,7 @@ internal class BrowserStorageProvider : IStorageProvider
internal const string PickerCancelMessage = "The user aborted a request";
internal const string NoPermissionsMessage = "Permissions denied";
- private readonly Lazy> _lazyModule = new(() => JSHost.ImportAsync("storage.ts", "./storage.js"));
+ private readonly Lazy _lazyModule = new(() => AvaloniaModule.ImportStorage());
public bool CanOpen => StorageHelper.CanShowOpenFilePicker();
public bool CanSave => StorageHelper.CanShowSaveFilePicker();
@@ -28,7 +28,7 @@ internal class BrowserStorageProvider : IStorageProvider
public async Task> OpenFilePickerAsync(FilePickerOpenOptions options)
{
- _ = await _lazyModule.Value;
+ await _lazyModule.Value;
var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter);
@@ -62,7 +62,7 @@ internal class BrowserStorageProvider : IStorageProvider
public async Task SaveFilePickerAsync(FilePickerSaveOptions options)
{
- _ = await _lazyModule.Value;
+ await _lazyModule.Value;
var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices);
@@ -90,7 +90,7 @@ internal class BrowserStorageProvider : IStorageProvider
public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
- _ = await _lazyModule.Value;
+ await _lazyModule.Value;
var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
try
@@ -106,14 +106,14 @@ internal class BrowserStorageProvider : IStorageProvider
public async Task OpenFileBookmarkAsync(string bookmark)
{
- _ = await _lazyModule.Value;
+ await _lazyModule.Value;
var item = await StorageHelper.OpenBookmark(bookmark);
return item is not null ? new JSStorageFile(item) : null;
}
public async Task OpenFolderBookmarkAsync(string bookmark)
{
- _ = await _lazyModule.Value;
+ await _lazyModule.Value;
var item = await StorageHelper.OpenBookmark(bookmark);
return item is not null ? new JSStorageFolder(item) : null;
}
diff --git a/src/Web/Avalonia.Web/Storage/WriteableStream.cs b/src/Web/Avalonia.Web/Storage/WriteableStream.cs
index f9699fef7a..09e438c34e 100644
--- a/src/Web/Avalonia.Web/Storage/WriteableStream.cs
+++ b/src/Web/Avalonia.Web/Storage/WriteableStream.cs
@@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript;
using System.Threading;
using System.Threading.Tasks;
+using Avalonia.Web.Interop;
+
namespace Avalonia.Web.Storage;
[System.Runtime.Versioning.SupportedOSPlatform("browser")]
diff --git a/src/Web/Avalonia.Web/WindowingPlatform.cs b/src/Web/Avalonia.Web/WindowingPlatform.cs
index 3f14680241..7c2a84516b 100644
--- a/src/Web/Avalonia.Web/WindowingPlatform.cs
+++ b/src/Web/Avalonia.Web/WindowingPlatform.cs
@@ -8,7 +8,7 @@ using Avalonia.Threading;
namespace Avalonia.Web
{
- public class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
+ internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
{
private bool _signaled;
private static KeyboardDevice? s_keyboard;
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts
index 98c912f940..0642bd475d 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts
@@ -6,8 +6,8 @@ import { Caniuse } from "./avalonia/caniuse";
import { StreamHelper } from "./avalonia/stream";
import { NativeControlHost } from "./avalonia/nativeControlHost";
-export async function createAvaloniaRuntime(api: RuntimeAPI): Promise {
- api.setModuleImports("avalonia.ts", {
+async function registerAvaloniaModule(api: RuntimeAPI): Promise {
+ api.setModuleImports("avalonia", {
Caniuse,
Canvas,
InputHelper,
@@ -18,3 +18,15 @@ export async function createAvaloniaRuntime(api: RuntimeAPI): Promise {
NativeControlHost
});
}
+export {
+ Caniuse,
+ Canvas,
+ InputHelper,
+ SizeWatcher,
+ DpiWatcher,
+ AvaloniaDOM,
+ StreamHelper,
+ NativeControlHost,
+
+ registerAvaloniaModule
+};
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
index 017ca58b8f..9ae9b3d2a8 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
@@ -211,7 +211,7 @@ export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map;
- public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void): void {
+ public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void {
if (!element || !callback) {
return;
}
@@ -223,7 +223,7 @@ export class SizeWatcher {
callback
};
- SizeWatcher.elements.set(elementId, element);
+ SizeWatcher.elements.set(elementId ?? element.id, element);
SizeWatcher.observer.observe(element);
SizeWatcher.invoke(element);
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
index 943b8330d5..2257d56a92 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
@@ -4,13 +4,17 @@ export class AvaloniaDOM {
}
static createAvaloniaHost(host: HTMLElement) {
+ const randomIdPart = Math.random().toString(36).replace(/[^a-z]+/g, "").substr(2, 10);
+
// Root element
host.classList.add("avalonia-container");
host.tabIndex = 0;
host.oncontextmenu = function () { return false; };
+ host.style.overflow = "hidden";
// Rendering target canvas
const canvas = document.createElement("canvas");
+ canvas.id = `canvas${randomIdPart}`;
canvas.classList.add("avalonia-canvas");
canvas.style.backgroundColor = "#ccc";
canvas.style.width = "100%";
@@ -19,6 +23,7 @@ export class AvaloniaDOM {
// Native controls host
const nativeHost = document.createElement("div");
+ canvas.id = `nativeHost${randomIdPart}`;
nativeHost.classList.add("avalonia-native-host");
nativeHost.style.left = "0px";
nativeHost.style.top = "0px";
@@ -28,6 +33,7 @@ export class AvaloniaDOM {
// IME
const inputElement = document.createElement("input");
+ canvas.id = `input${randomIdPart}`;
inputElement.classList.add("avalonia-input-element");
inputElement.autocapitalize = "none";
inputElement.type = "text";
@@ -42,6 +48,7 @@ export class AvaloniaDOM {
inputElement.style.color = "transparent";
inputElement.style.display = "none";
inputElement.style.height = "20px";
+ inputElement.style.zIndex = "-1";
inputElement.onpaste = function () { return false; };
inputElement.oncopy = function () { return false; };
inputElement.oncut = function () { return false; };